L» Luna Pipes

Grammar

Five operators, four primitives, one production tree. The whole language fits on a postcard.

<pipe> ::= <stage> ( ">>" <stage> )*

Luna Pipes is a concatenative expression language. A program is a sequence of stages separated by operators. There are no statements, no blocks, no return values to track. The value flows left-to-right; each operator decides how the next stage receives it.

The grammar below is given in EBNF. The semantics follow in plain English, with one runnable example per operator. Whitespace is insignificant; identifiers are case-sensitive; the operator >> is right-associative.

Production rules.

<pipe> <stage> ( ">>" <stage> )*

A pipe is one or more stages chained with the pipe operator.

<stage> <verb> <arg>* <modifier>?

A stage is a verb followed by zero or more arguments and an optional modifier.

<verb> "/" <identifier>

A verb is a leading slash followed by an identifier. Identifiers may contain a-z, 0-9, hyphen.

<arg> <string> | <number> | <flag>

An argument is a quoted string, a number, or a flag of the form --name or --name=value.

<flag> "--" <identifier> ( "=" <value> )?
<modifier> <repeat> | <parallel> | <conditional> | <sink>

A stage can carry one modifier. Modifiers attach to the stage they follow.

<repeat> "*" <integer>

Repeat the preceding stage N times, in parallel.

<parallel> "||" <stage>

Fan out: run the previous stage and this stage concurrently; join the results.

<conditional> "?" <flag> ">>" <pipe> ":" <pipe>

Branch on a truthy flag.

<sink> "!"

Run the preceding stage for effect; discard its output.

That is the full grammar. The runtime is responsible for resolving verbs to skills, for marshaling arguments, and for serializing the output of one stage as the input of the next. See the source for the parser.

Operators.

pipe

binary, left input feeds right input

The pipe operator passes the output of the left stage as the input of the right stage. It is the only required operator: a Luna Pipes program with no operators is one stage, executed alone.

/persona generate >> ghost "launch post"
composed: 2 stages, sequential
/persona generate → 4 personas
ghost "launch post" → 4 drafts

repeat

unary postfix, takes an integer

Repeat the preceding stage N times in parallel. The repeated stage receives the same input N times; outputs are joined into an array for the next stage.

/persona generate >> ghost "post" *4 >> publish notion
composed: 6 stages, ghost runs 4 times concurrently
join: 4 drafts → publish (1 stage)

fanout

binary, both stages run concurrently

Run the left and right stages in parallel against the same input. Their outputs are joined as a tuple and passed to the next stage. Use for embarrassingly parallel work where the two branches do different things.

/persona generate >> ghost "post" || imagine "hero" >> publish notion
composed: 4 stages, fanout join
ghost → text
imagine → image
join → publish

conditional

ternary, branches on a truthy flag

Branch on the named flag from the input. The left sub-pipe runs if the flag is truthy; the right runs otherwise. Either side is a full pipe expression.

/persona generate >> ? premium >> ghost "longform" : ghost "short"
composed: branches on premium
premium=true → longform path
premium=false → short path

sink

unary postfix, no input to the next stage

Run the preceding stage for effect. The runtime executes it, awaits completion, then discards its output. Useful for notifications, logs, or side-effects that should not contaminate the value flowing through.

/persona generate >> ghost "post" >> publish notion >> notify slack !
composed: 4 stages, notify runs, output discarded

Errata.

The grammar is closed for v1. The lexicon is open. New verbs ship in every release; new operators do not. If you find yourself wanting one, compose two existing ones first; if the composition is awkward, file an issue with the example.