Documentation
¶
Overview ¶
Package lang implements the lexer, parser, and typed AST for the .agent surface syntax fixed by ADR 002 (docs/adr/002-language-frontend-and-ir-expressiveness.md).
Scope is parsing (this package), resource lowering (#197, internal/lang/lower), type and effect checking (#198, internal/lang/check), and — added in #199 — conditionals, loops, and dynamic fan-out with the boolean expression language they require, lowered to the execution IR (internal/execir). Every AST node carries a spec.Pos so positions are compatible with the IR positions threaded by #187, and the parser recovers from errors to report multiple diagnostics per file rather than stopping at the first.
Index ¶
- func Parse(file, src string) (*File, Diagnostics)
- func Print(f *File) string
- type AgentDecl
- type Arg
- type AssignStmt
- type BinaryExpr
- type CallExpr
- type Decl
- type Diagnostic
- type Diagnostics
- type EffectRef
- type Expr
- type ExprStmt
- type File
- type ForStmt
- type Grant
- type Ident
- type IfStmt
- type Kind
- type Lexer
- type LitExpr
- type ModelRef
- type Node
- type ParallelStmt
- type Param
- type Pos
- type RefExpr
- type ReturnStmt
- type Severity
- type Stmt
- type Token
- type TypeRef
- type UnaryExpr
- type WorkflowDecl
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Parse ¶
func Parse(file, src string) (*File, Diagnostics)
Parse lexes and parses src into a typed AST. It always returns a non-nil *File (possibly with a partial or empty Decls slice) plus every diagnostic found — lexical and syntactic — sorted by position. The parser recovers after each error (see the sync* helpers) so malformed input yields multiple positioned diagnostics rather than stopping at the first (issue #196).
Parsing does not resolve references, check types, or verify the effects clause; those are #198, and lowering to the resource model is #197.
func Print ¶
Print reconstructs canonical .agent source from an AST. Output is normalized — 4-space indentation, single spaces around operators, comma-joined effects — so it does not depend on the incidental formatting of the input. Print is the engine behind `terfyn fmt`; parse -> Print -> parse -> Print is stable (idempotent) for any file that parses without error.
Types ¶
type AgentDecl ¶
type AgentDecl struct {
Pos Pos
Name *Ident
Model *ModelRef // model <provider>/<name>
Policy *Ident // policy <name> (reference to a Policy resource)
Grants []*Grant // grants { tool.<name>.<operation> ... }
Input *TypeRef // input <Type>
Output *TypeRef // output <Type>
}
AgentDecl is `agent <Name> { ... }`. Each field appears at most once; a field the author omitted is a nil pointer (requiredness is a checking concern, #198) and a repeated field keeps the first occurrence and yields a duplicate-field diagnostic (the grammar admits each field once). Fields do not preserve source order across kinds — the surface fixes their meaning by keyword, not position.
type Arg ¶
Arg is one call argument. Name is nil for a positional argument and set for a named one (repo: input.repo). A single call may mix positional and named arguments at the parse layer; validity is a checking concern (#198).
type AssignStmt ¶
AssignStmt is `<Target> = <Value>` binding a name to an expression result.
func (*AssignStmt) Position ¶
func (s *AssignStmt) Position() Pos
type BinaryExpr ¶
BinaryExpr is `<X> <Op> <Y>`: a comparison (== != < <= > >=) or a logical connective (&& ||). Comparisons do not chain (a < b < c is a syntax error); logical connectives are left-associative with && binding tighter than ||.
func (*BinaryExpr) Position ¶
func (e *BinaryExpr) Position() Pos
type CallExpr ¶
CallExpr is `<Callee>(<args>)`. Callee is the dotted reference being invoked (a workflow-level tool call like github.get_pr, or an agent/subworkflow invocation like SecurityReviewer). Args may nest arbitrarily.
type Decl ¶
type Decl interface {
Node
// contains filtered or unexported methods
}
Decl is a top-level declaration: an AgentDecl or a WorkflowDecl.
type Diagnostic ¶
Diagnostic is one positioned parse, lowering, or checking problem. The parser recovers after each error (see parser.synchronize) so a single file yields every diagnostic it can find in one pass rather than stopping at the first.
func (Diagnostic) Error ¶
func (d Diagnostic) Error() string
Error formats the diagnostic as "file:line:col: message" using the shared spec.Pos formatting; the location prefix is omitted when unknown. A warning is prefixed so it reads distinctly from an error in combined output.
type Diagnostics ¶
type Diagnostics []Diagnostic
Diagnostics is an ordered collection of parse diagnostics.
func Format ¶
func Format(file, src string) (string, Diagnostics)
Format parses src and returns canonical .agent source plus any diagnostics. When parsing reports errors the returned source is best-effort (formatted from the partial AST) and callers should surface the diagnostics rather than write the output.
func (Diagnostics) AsError ¶
func (ds Diagnostics) AsError() error
AsError returns ds as an error, or nil when ds has no SeverityError entry — including when ds is a non-empty slice holding only warnings.
This is the ONLY safe way to convert a Diagnostics value to a plain error for a pass/fail check. Diagnostics is a slice type, so a bare interface conversion (`var err error = ds`), `len(ds) != 0`, or `fmt.Errorf("%w", ds)` all produce a non-nil error whenever ds is non-empty, warnings included — none of those consult Severity. Error() does not fix this either: it still renders every diagnostic (warnings included) for a caller that wants a full human-readable dump, and a warning-only Error() string is merely prefixed "warning:", not absent — the slice itself is still non-empty and would still test as a "failure" under any of the patterns above. Use AsError (or HasErrors directly) wherever "did this fail" matters.
func (Diagnostics) Error ¶
func (ds Diagnostics) Error() string
Error joins every diagnostic on its own line. This renders ds for display — including warnings — and is NOT a pass/fail signal; see AsError.
func (Diagnostics) HasErrors ¶
func (ds Diagnostics) HasErrors() bool
HasErrors reports whether ds contains at least one SeverityError diagnostic. A caller should treat a Diagnostics value with only warnings as non-fatal.
func (Diagnostics) Sorted ¶
func (ds Diagnostics) Sorted() Diagnostics
Sorted returns the diagnostics ordered by position (file, then line, then column) so caller output is deterministic regardless of recovery order.
type EffectRef ¶
EffectRef is one bare dotted effect identifier in the effects clause, such as github.read or external.visible. Unlike a Grant it carries no tool. prefix; the two namespaces must never be interchangeable (ADR 002).
type Expr ¶
type Expr interface {
Node
// contains filtered or unexported methods
}
Expr is a workflow expression: a CallExpr, a RefExpr, or — in a condition or call argument (#199) — a LitExpr, UnaryExpr, or BinaryExpr.
type ExprStmt ¶
ExprStmt is a bare expression used for its effect, e.g. a deterministic tool call whose result is unbound: github.post_comment(...).
type ForStmt ¶
ForStmt is `for <Var> in <In> { <Body> }` (#199): iteration over a runtime collection. Parallel marks the dynamic fan-out form `parallel for <Var> in <In> { }` — ADR 002 §1 classifies dynamic fan-out over a runtime collection as "a loop wearing a graph costume," so it is language work, not a graph field. Both forms lower to the execution IR's Loop; only Parallel runs its iterations with bounded concurrency. Var binds inside Body only.
type Grant ¶
type Grant struct {
Pos Pos
Name *Ident // the <Name> segment (tool.<Name>.<operation>); a single identifier
// Operation is the operation path (the segments after the tool name), at
// least one identifier and possibly dotted (pull_request.post_comment).
Operation []*Ident
// Segments is the full dotted path as written (including the leading
// "tool"), preserved for diagnostics and round-tripping.
Segments []*Ident
}
Grant is one autonomous capability bound inside a `grants { }` block. Per the ADR 002 amendment (#188) a grant names a concrete operation as tool.<Name>.<Operation> — the exact reference vocabulary of approvals.requiredFor / uses: — and lives in a namespace distinct from effects. It is split the same way as tools.ParseUses: the leading "tool" segment is the namespace marker, Name is the single tool-name segment, and Operation is everything after it. Operation is therefore a dotted path, not a single identifier: shipped strings such as tool.github.pull_request.get carry a multi-segment operation (pull_request.get), and a lowering pass (#197) reconstructs the uses string as tool.<Name>.<Operation joined by ".">. A grant that omits the tool. prefix is a diagnostic, not an EffectRef.
func (*Grant) OperationName ¶
OperationName returns the dotted operation path (e.g. "pull_request.get"), or "" if the grant is malformed. This is the <operation> half that ParseUses yields and that a uses string reconstructs after the tool name.
type IfStmt ¶
IfStmt is `if <Cond> { <Then> } (else ({ <Else> } | <IfStmt>))?` (#199). Cond is a boolean expression; Then and Else are statement lists. An `else if` chain parses as an Else holding a single nested IfStmt. Control flow never becomes a field on the resource-model WorkflowStep (ADR 002 §4): it lowers to the execution IR's Branch, and its two arms both flatten into the resource projection so the effect bound is the union over branches (ADR 002 §5).
type Kind ¶
type Kind int
Kind enumerates the lexical token classes of the .agent language.
const ( // KindError is a lexer-level malformed token (e.g. a stray rune). The // offending text is carried in Token.Lit and a diagnostic is emitted. KindError Kind = iota // KindEOF marks the end of input. The lexer always terminates the stream // with exactly one KindEOF token. KindEOF // KindIdent is a bare identifier: [A-Za-z_][A-Za-z0-9_-]*. Hyphens are // permitted after the first rune so DNS-style resource references // (guarded-writes) and model name segments (gpt-5) are single tokens; the // language has no arithmetic, so '-' is never an operator. KindIdent // Structural keywords. These always begin a construct and never serve as a // name in the ADR 002 surface, so they are reserved. The field words // (model, policy, grants, input, output, effects) are NOT reserved because // they double as parameter names; the parser treats them contextually. The // loop keyword `in` (as in `for x in coll`) is likewise contextual — it is // lexed as an ordinary identifier and matched by the parser only in loop // position, so a parameter may still be named `in`. `true`/`false` are // contextual boolean literals recognized by the expression parser (#199), // not reserved words. KindAgent // agent KindWorkflow // workflow KindParallel // parallel KindReturn // return KindIf // if KindElse // else KindFor // for // Punctuation. KindLBrace // { KindRBrace // } KindLParen // ( KindRParen // ) KindDot // . KindSlash // / KindComma // , KindColon // : KindEquals // = KindArrow // -> // Comparison, logical, and grouping operators for the expression language // that conditionals and loops require (#199). The surface has no arithmetic; // these appear only in `if` conditions and other boolean positions. KindEqEq // == KindBangEq // != KindLt // < KindLte // <= KindGt // > KindGte // >= KindAndAnd // && KindOrOr // || KindBang // ! // Literals (#199): string and number literals usable in conditions and as // call arguments. For KindString, Token.Lit holds the DECODED string value // (escapes already applied); for KindNumber, Token.Lit holds the raw source // text, which the parser converts to an int64 or float64. KindString // "..." KindNumber // 123, 1.5 )
type Lexer ¶
type Lexer struct {
// contains filtered or unexported fields
}
Lexer scans .agent source into a token stream. It is newline-insensitive: statement and field boundaries are recovered by the grammar (each construct has a deterministic shape), so newlines are ordinary whitespace and are not emitted as tokens. Line comments (// to end of line) are skipped.
func (*Lexer) Diagnostics ¶
func (l *Lexer) Diagnostics() Diagnostics
Diagnostics returns any lexical errors accumulated so far (stray runes).
type LitExpr ¶
LitExpr is a literal operand: a string, a number, or a boolean (#199). Kind is one of KindString, KindNumber, or a boolean (recorded as KindIdent with a bool Value). Value holds the decoded Go value: string, int64, float64, or bool. Literals appear in conditions and as call arguments; the surface has no arithmetic, so numbers are only ever compared or passed, never combined.
type ModelRef ¶
ModelRef is a `<provider>/<name>` model reference such as openai/gpt-5. Provider and Name preserve hyphens (gpt-5); Raw is the reassembled text.
type Node ¶
type Node interface {
Position() Pos
}
Node is any AST node. Position returns the node's start position.
type ParallelStmt ¶
type ParallelStmt struct {
Pos Pos
Body []*AssignStmt
}
ParallelStmt is `parallel { <AssignStmt>... }` — static fan-out into named branches with fan-in (ADR 002 graph structure; #192). Each branch binds a name, so the body admits only assignments.
func (*ParallelStmt) Position ¶
func (s *ParallelStmt) Position() Pos
type Pos ¶
Pos is the source position carried by every token and AST node. It is a type alias for spec.Pos so .agent positions are the *same* type as the IR positions threaded through the resource model by #187 — a lowering pass (#197) can copy an AST node's Pos onto an IR node with no conversion.
type RefExpr ¶
RefExpr is a dotted reference path: a bare name (pr), a member access (input.repo, result.summary), or a callee path (github.get_pr). Parts holds each dotted segment in order and is always non-empty.
type ReturnStmt ¶
ReturnStmt is `return <Value>`.
func (*ReturnStmt) Position ¶
func (s *ReturnStmt) Position() Pos
type Severity ¶
type Severity int
Severity distinguishes a fatal diagnostic from an advisory one. The zero value is SeverityError so every pre-existing call site (lexer, parser, lowering) that constructs a Diagnostic{Pos, Msg} without setting Severity is unaffected — every diagnostic before #198 was an error.
type Stmt ¶
type Stmt interface {
Node
// contains filtered or unexported methods
}
Stmt is a workflow body statement.
type Token ¶
type Token struct {
Kind Kind
// Lit is the source text of the token. For punctuation it is the literal
// symbol; for KindIdent and keywords it is the matched identifier; for
// KindEOF it is empty.
Lit string
// Pos is the 1-based start position of the token (spec.Pos, #187).
Pos Pos
}
Token is one lexeme with its class, source text, and position.
type TypeRef ¶
TypeRef names a schema/type by identifier (e.g. PullRequest, Review). It is an unresolved reference at the parse layer.
type WorkflowDecl ¶
type WorkflowDecl struct {
Pos Pos
Name *Ident
Params []*Param
Result *TypeRef // return type after ->; nil if omitted
Effects []*EffectRef // effects { github.read, ... }; nil if no clause
Body []Stmt
}
WorkflowDecl is `workflow <Name>(<params>) -> <Result> effects { ... } { body }`. Result and the effects clause are optional in the grammar; the body is a statement list that may include conditionals and loops (IfStmt, ForStmt; #199) in addition to assignments, calls, parallel blocks, and a return.
func (*WorkflowDecl) Position ¶
func (d *WorkflowDecl) Position() Pos
Directories
¶
| Path | Synopsis |
|---|---|
|
Package check implements the ADR 002 §5 "checked program": the pass that sits between the #196 typed AST and the two sibling projections (the #197 resource projection and the #199 execution lowering, internal/execir).
|
Package check implements the ADR 002 §5 "checked program": the pass that sits between the #196 typed AST and the two sibling projections (the #197 resource projection and the #199 execution lowering, internal/execir). |
|
Package lower turns the #196 typed .agent AST into the existing resource model — the resource projection of ADR 002 §5.
|
Package lower turns the #196 typed .agent AST into the existing resource model — the resource projection of ADR 002 §5. |