02-grammar.md
QPlan Language Grammar β Full Specification (includes EBNF)
This document defines the official grammar of QPlan Language.
It contains both Version A (core grammar) and Version B (full EBNF), so this file alone lets you fully understand QPlan.
1. QPlan Language overview
QPlan Language is a step-based workflow language where every action runs inside a step block.
Scripts flow through Tokenizer β Parser β AST β Executor, and values stored in the ExecutionContext (ctx) can be reused via dot paths like stats.total or bracket indices like items[0].
Minimal example:
step id="demo" desc="Simple sum" {
var [1,2,3] -> items
math op="sum" arr=items -> total
return total=total
}
2. Core grammar (Version A)
2.1 Script structure & steps
- A root script may list step statements only. Actions/If/Set outside a step cause a parser error.
- Optionally, wrap the script in a
plan { ... }block to attach metadata that humans/tools can read.plan { @title "Onboarding Plan" @summary "Create accounts and schedule training" @version "0.1" @since "2025-01-01" @params "keyword,item" step id="setup" { ... } }- Supported meta keys:
title,summary,version,since,params. - Plan meta must appear at the top of the plan block (before any steps), or at the top of the script without the plan wrapper.
- Single-token meta values can omit quotes; use quotes for multi-word values.
@paramsis a single line, comma-separated list (whitespace allowed). Missing params cause a runtime error.- You can also omit the
plan { ... }wrapper and place meta lines at the top of the script.
- Supported meta keys:
- Step form:
step ["desc"] id="stepId" [desc="Description"] [type="task"] [onError="retry=3"] { ... (Action / If / While / Each / Parallel / Set / Return / Jump / Step ...) } - The
descstring can appear immediately after thestepkeyword or as an attribute. - Allowed metadata:
id: identifier referenced by jumps and step events.desc: human-readable description; if omitted, raw text afterstepmay be used.type: arbitrary tag (task/group/loop, etc.).onError:fail(default) /continue/retry=<N>/jump="<stepId>".
- Step IDs must contain Unicode letters or ASCII underscores, start with a letter/underscore, and may include digits after the first character.
- Comments are supported anywhere: use
// commentor# notefor single-line comments and/* block */for multi-line comments. The parser ignores them entirely. - Step results are automatically stored under
ctx[runId][namespace], wherenamespacedefaults to the step ID (override with-> resultVar). Other steps reuse them vianamespace.field, and the engine also mirrors the same object under the original step ID so both names stay valid. - Steps can contain nested steps to form a sub-step tree.
- Identifiers (module names, action outputs, variables, return keys,
settargets, etc.) may include any Unicode letter/digit plus_, but they must start with a letter or underscore.
2.2 Return statement
- Written inside a step block as
return key=expression .... =is optional:return gear accounts total=sum(orreturn gear, accounts, total=sum) expands toreturn gear=gear accounts=accounts total=sum. Mixed forms are allowed.- Entries may be separated by spaces or commas.
- Requires at least one value; multiple entries are combined into an object result.
- If omitted, the runtime still produces an object exposing every action output in the step, so
later steps can reference
namespace.outputName. The final action result is also stored internally. - Whatever value is produced becomes the payload stored at
ctx[runId][namespace](namespace = step ID unless overridden via-> resultVar). When overridden, the same payload is also placed under the step ID for convenience.
return total=total average=avg
return gear, accounts, total=sum # shorthand
2.3 Action syntax
<module> [<option> { AND <option> }] <args> [-> <var>]
- All actions must be inside steps.
- Options are identifiers after the module name; chain multiples with uppercase
AND. The first option automatically maps to theopinput. - Without
-> var, results are not stored in ctx.
Example:
math add a=1 b=2 -> sum
file read path="./data.txt" -> txt
sleep ms=500 # no output capture
http get AND cache url="..." -> raw
2.4 Arguments (key=value)
Supported types:
- Numbers
- Strings
"text" - JSON
[1,2,3],{ "x":1 } - ctx value references (if a string matches a ctx variable, a dot path like
stats.total, or a bracket index likeitems[0], it resolves automatically)
Example:
math op="avg" arr="[1,2,3]"
ai prompt="Summary" context=html
2.5 If statements
if <expr> {
...
}
if <left> <op> <right> [and/or <left> <op> <right> ...] {
...
} else {
...
}
Supported comparison operators: > < >= <= == != EXISTS NOT_EXISTS.
EXISTS and NOT_EXISTS are unary operators (e.g., if value EXISTS); they treat undefined/null/"" as False.
If you omit a comparator (if <expr>), QPlan evaluates the expression using truthy/falsy rules.
Combine conditions with and, or, not, and use parentheses () for precedence.
Operands may reference ctx variables, dot paths like stats.average (missing props return undefined), or bracket indices like items[0]. Arrays support .length and .count.
2.6 Parallel execution
parallel {
...
...
} concurrency=3 ignoreErrors=true
Options:
concurrency: number of simultaneous executions.ignoreErrors: if true, suppress errors.concurrency/ignoreErrorscan appear right after theparallelkeyword or after the block.parallel: ... ENDsyntax is also allowed.
2.7 Future / Join
Create a future:
future delay=500 value="done" -> f1
Join:
join futures="f1,f2,f3" -> result
2.8 Each loops
each item in list {
...
}
list must be an array/iterable in ctx. Use each (value, idx) in list to access value and index together.
Inside the loop (and similarly in while) use break / continue to control flow.
2.9 While loops
while count < 10 {
...
}
Shares the same condition syntax as if, repeating while the condition is true.
break / continue are available to control the loop body.
2.10 Set statements
set count = count + 1
set message = "hello"
set config = {"limit":10}
Only existing ctx variables can be mutated (missing variables cause errors).
Expressions may combine numbers, strings, JSON literals, existing variables, parentheses, and arithmetic operators (+,-,*,/).
2.11 Control Flow
Loop Control
break: immediately terminate the current each/while loop.continue: skip the current iteration and continue with the next one.
Plan/Step Control
stop: terminate the entire plan execution with statusstopped(normal completion).skip: skip the rest of the current step (move to the next step).
Step Navigation
jump to="stepId": instantly move to another step at the same/parent/child level. Targets must be step IDs, given as strings or identifiers.
3. Full EBNF grammar (Version B)
Script = PlanBlock | { StepStmt } ;
PlanBlock = "plan" , "{" , { PlanMeta } , { StepStmt } , "}" ;
PlanMeta = "@" , PlanMetaKey , PlanMetaValue ;
PlanMetaKey = "title" | "summary" | "version" | "since" ;
PlanMetaValue = QuotedString | Number ;
StepStmt = "step" , StepHead , [ "->" , Identifier ] , Block ;
StepHead = [ QuotedString ] , { StepAttr } ;
StepAttr = Identifier , "=" , StepAttrValue ;
StepAttrValue = QuotedString | Identifier | Number ;
Block = "{" , { Statement } , "}" ;
Statement = Action
| IfStmt
| WhileStmt
| ParallelStmt
| EachStmt
| BreakStmt
| ContinueStmt
| StopStmt
| SkipStmt
| SetStmt
| StepStmt
| JumpStmt
| ReturnStmt ;
Action = ModuleName , [ OptionSeq ] , { Argument } , [ "->" , Identifier ] ;
OptionSeq = Option , { "AND" , Option } ;
Option = Identifier ;
ModuleName = Identifier ;
Argument = Identifier , "=" , Value ;
Value = Number
| QuotedString
| JsonObject
| JsonArray
| IdentifierPath ;
ReturnStmt = "return" , ReturnEntry , { ReturnEntry } ;
ReturnEntry = Identifier , "=" , Expression ;
IfStmt = "if" , Condition , Block , [ ElseBlock ] ;
ElseBlock = "else" , Block ;
WhileStmt = "while" , Condition , Block ;
EachStmt = "each" ,
( "(" , Identifier , [ "," , Identifier ] , ")" | Identifier ) ,
"in" , Identifier , Block ;
ParallelStmt = "parallel" , [ ParallelOptions ] , Block , [ ParallelOptions ] ;
ParallelOptions = { ParallelOption } ;
ParallelOption = "concurrency" , "=" , Number
| "ignoreErrors" , "=" , Boolean ;
JumpStmt = "jump" , "to" , "=" , (QuotedString | Identifier) ;
BreakStmt = "break" ;
ContinueStmt = "continue" ;
StopStmt = "stop" ;
SkipStmt = "skip" ;
SetStmt = "set" , Identifier , "=" , Expression ;
Condition = SimpleCondition , { LogicOp , SimpleCondition } ;
SimpleCondition = [ "NOT" ] , Expression , [ Comparator , Expression ] ;
LogicOp = "AND" | "OR" ;
Comparator = ">" | "<" | ">=" | "<=" | "==" | "!=" | "EXISTS" | "NOT_EXISTS" ;
Expression = Term , { ("+" | "-") , Term } ;
Term = Factor , { ("*" | "/") , Factor } ;
Factor = Number
| QuotedString
| IdentifierPath
| JsonObject
| JsonArray
| "(" , Expression , ")"
| "-" , Factor ;
Identifier = Letter , { Letter | Digit | "_" | "-" } ;
IdentifierPath = Identifier , { "." , Identifier } ;
QuotedString = """ , { ANY_CHAR_BUT_QUOTE } , """ ;
Number = Digit , { Digit } ;
Boolean = "true" | "false" ;
JsonObject = "{" , [ JsonPair , { "," , JsonPair } ] , "}" ;
JsonPair = QuotedString , ":" , JsonValue ;
JsonValue = QuotedString | Number | JsonObject | JsonArray ;
JsonArray = "[" , [ JsonValue , { "," , JsonValue } ] , "]" ;
4. Execution rules
4.1 Steps & actions
- Save step header info (id/desc/type/onError/output) and execute the block.
- For each action, look up the module in the ModuleRegistry and call
execute(inputs, ctx). - When
-> outexists, store the result viactx.set(out, result). The step result is either the last action result or thereturnobject.
4.2 ctx variable resolution
- If an argument value is a string matching an existing ctx variable, it resolves to that value.
- Dot paths such as
stats.totaloruser.profile.nameare allowed and traverse nested ctx objects. - JSON strings are not auto-parsed; modules handle conversion themselves.
4.3 Future / Join behavior
- The
futuremodule must return{ __future: Promise }. - The executor stores the promise in ctx;
joinpulls the promises from ctx and runsPromise.all.
4.4 Parallel behavior
- Statements inside run concurrently.
concurrencylimits the number of simultaneous tasks.- With
ignoreErrors=true, some action errors are ignored so execution continues.
4.5 Jump & error policy
jump to="stepId"locates the target step and resets execution to that block.- A stepβs
onErrorpolicy (fail/continue/retry/jump) governs error flow, and retries triggeronStepRetryevents.
5. Example (grammar in practice)
step id="load" desc="Read file" {
file read path="./nums.txt" -> raw
json parse data=raw -> parsed
return list=parsed
}
step id="stats" desc="Compute average" {
math op="avg" arr=load.list -> avg
if avg > 50 {
echo msg="high" -> note
} else {
echo msg="low" -> note
}
return average=avg note=note
}
step id="sleepers" desc="Parallel tasks" {
parallel concurrency=2 ignoreErrors=true {
sleep ms=300 -> slow
sleep ms=100 -> fast
}
}
6. Grammar summary
- Step-enforced structure: every action/control statement lives inside a step.
- Steps control flow via onError (fail/continue/retry/jump) and
jump. - Action = module name + optional options + key=value arguments + optional output capture.
- Arguments support numbers/strings/JSON/dot-path or bracket-index variable references.
- Built-in Future/Join/Parallel/Each/While/Set/Return/Jump constructs.
- If conditions compare numbers/strings, support EXISTS/NOT_EXISTS, support unary truthy checks, and allow parentheses.
With this document you can fully parse and execute QPlan Language.