ast

package
v1.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package ast defines the abstract syntax tree produced by the parser.

Every node carries a Pos (alias for lexer.Position) so diagnostics from later stages - semantic analysis, codegen, formatter, LSP - can map back to the originating source location. The AST is the single shared representation consumed by every downstream tool: keeping it small and strongly-typed lets the parser, semantic analyser, and codegen evolve independently.

AST: top-level declaration types (Type / Enum / Error / Scalar / Middleware / Service) + method / path / member shapes.

AST: expression / literal nodes + Decorator / DecoratorArg / ObjectField.

AST: TypeRef family + QualifiedIdent.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EachField

func EachField(body []TypeMember, fn func(*Field) bool)

EachField calls fn for every Field directly declared in body, in source order. Mixin members are skipped — they're embedded type references, not fields with their own decorator chain or shape.

The callback may return false to stop iteration early (useful for "find first matching field" lookups). Returning true continues.

func EachMember

func EachMember(body []TypeMember, fn func(TypeMember) bool)

EachMember calls fn for every TypeMember in body. Used by walkers that need to see both Fields AND Mixins (e.g. validate emission, where the host's Validate() must call the embedded mixin's Validate()). Fn returns false to stop.

func HasDecorator

func HasDecorator(decs []*Decorator, name string) bool

HasDecorator reports whether decs contains a decorator with the given Name. Equivalent to `FindDecorator(decs, name) != nil` but reads better at call sites that only need the boolean.

func MemberEqual

func MemberEqual(a, b TypeMember) bool

MemberEqual reports whether two TypeMembers (Field OR Mixin) are equivalent. The interface itself has no Equal method (would require every implementer to know about every other), so callers route through this helper. Type-asserting both to the same concrete and delegating to the typed Equal keeps the dispatch in one place.

func MembersEqual

func MembersEqual(a, b []TypeMember) bool

MembersEqual is the slice form. Tests compare expected vs got body in a single call instead of looping per element.

Types

type ArrayLit

type ArrayLit struct {
	Pos      Pos
	Elements []Expr
}

ArrayLit is a `[v1, v2, ...]` literal. Elements may be of mixed kind so the runtime / codegen handles validation per decorator.

func (*ArrayLit) ExprPos

func (e *ArrayLit) ExprPos() Pos

type BoolLit

type BoolLit struct {
	Pos   Pos
	Value bool
}

BoolLit holds `true` or `false`.

func (*BoolLit) ExprPos

func (e *BoolLit) ExprPos() Pos

type Comment

type Comment = lexer.Comment

Comment aliases lexer.Comment so consumers of the AST (codegen, lint tools, the formatter) can refer to one canonical type without pulling the lexer package into every import block.

type CommentKind

type CommentKind = lexer.CommentKind

CommentKind aliases lexer.CommentKind for the same reason.

type Decl

type Decl interface {

	// DeclName returns the declared identifier (used for uniqueness checks).
	DeclName() string
	// DeclPos returns the position of the declaration keyword.
	DeclPos() Pos
	// contains filtered or unexported methods
}

Decl is the interface implemented by every top-level declaration node (TypeDecl, EnumDecl, ErrorDecl, ScalarDecl, MiddlewareDecl, ServiceDecl). Use a type switch on Decl to dispatch.

type Decorator

type Decorator struct {
	Pos         Pos
	Name        string
	Args        []*DecoratorArg
	TrailingDoc string
	// HasParens reports whether the source wrote `(...)` after the
	// decorator name, even when the inside was empty. The parser sets
	// it so the semantic analyzer can flag `@positive()` (empty parens
	// on a Flag decorator) and the formatter can normalise it away.
	HasParens bool
	// Propagated marks decorators that the semantic phase copied onto
	// this site from an enclosing scope - currently used by
	// `mergeServices` to flag method-level decorators it cloned from an
	// `extend service` block. Codegen uses the flag to distinguish
	// "inherited" decorators (which `@ignoreMiddleware` / `@ignoreTags`
	// / `@ignoreSecurity` should clear) from the method's own
	// decorators (which they should not).
	Propagated bool
}

func FindDecorator

func FindDecorator(decs []*Decorator, name string) *Decorator

FindDecorator returns the FIRST decorator in decs whose Name matches. Returns nil when no decorator matches or decs is empty. Nil entries in decs are skipped, so callers iterate without their own nil guard.

func FindDecoratorAny

func FindDecoratorAny(decs []*Decorator, names ...string) *Decorator

FindDecoratorAny returns the first decorator whose Name matches ANY of the supplied names. Useful for binding-kind classifiers (`path` vs `query` vs `header` vs `cookie` vs `body` vs `form`) that want a single pass instead of N FindDecorator calls.

type DecoratorArg

type DecoratorArg struct {
	Pos    Pos
	Name   string
	Named  bool
	Value  Expr
	Nested *Decorator
	Object []*ObjectField
}

DecoratorArg is one argument inside `@name(...)`. Exactly one of Value, Nested, or Object is populated:

  • Value (and optional Name+Named=true) for bare or `name: value` literals;
  • Nested for `@inner(...)` arguments — the parser preserves the nested-decorator shape so future meta-decorators that consume another decorator can land without grammar churn;
  • Object for `{ key: value, ... }` literals such as `@example({...})`.

type DurationLit

type DurationLit struct {
	Pos  Pos
	Text string
}

DurationLit keeps the original source text (e.g. "5s", "1.5ms") rather than a parsed time.Duration so the formatter can round-trip exactly and the runtime can choose its own resolution.

func (*DurationLit) ExprPos

func (e *DurationLit) ExprPos() Pos

type EnumDecl

type EnumDecl struct {
	Pos         Pos
	Decorators  []*Decorator
	Doc         []string
	Name        string
	Members     []EnumMember
	TrailingDoc []string // `// note` on the same line as the body's closing `}`
}

EnumDecl is `enum Name { Members* }`. Members are a mix of EnumValue (the actual enum entries) and FreeComment (free-floating section dividers / closing notes). All EnumValue entries must share a kind (all bare, all int, or all string); semantic phase enforces that. Use EnumDecl.EnumValues to iterate only the typed values when free-floating comments are not relevant.

func (*EnumDecl) DeclName

func (d *EnumDecl) DeclName() string

func (*EnumDecl) DeclPos

func (d *EnumDecl) DeclPos() Pos

func (*EnumDecl) EnumValues

func (d *EnumDecl) EnumValues() []*EnumValue

EnumValues returns only the *EnumValue entries from Members, preserving source order. Convenience for callers (semantic, codegen) that want the typed list and treat free-floating comments as cosmetic.

type EnumMember

type EnumMember interface {

	// MemberPos returns the position of the member's first token.
	MemberPos() Pos
	// contains filtered or unexported methods
}

EnumMember is the interface implemented by anything that can appear inside an `enum` body: *EnumValue for typed entries, *FreeComment for free-floating notes / section dividers.

type EnumValue

type EnumValue struct {
	Pos        Pos
	Doc        []string
	Name       string
	Kind       EnumValueKind
	IntValue   int64
	StrValue   string
	Decorators []*Decorator
}

EnumValue is one entry inside an enum declaration. IntValue / StrValue are only meaningful when Kind matches.

func (*EnumValue) MemberPos

func (v *EnumValue) MemberPos() Pos

type EnumValueKind

type EnumValueKind int

EnumValueKind tags the runtime representation of an enum value.

const (
	// EnumBare - `Active` (no `=`); rendered as a Go string constant whose
	// value matches the identifier.
	EnumBare EnumValueKind = iota
	// EnumInt - `Active = 1`; rendered as an `int` constant.
	EnumInt
	// EnumString - `Active = "active"`; rendered as a `string` constant.
	EnumString
)

type ErrorDecl

type ErrorDecl struct {
	Pos         Pos
	Decorators  []*Decorator
	Doc         []string
	Category    string
	Name        string
	Body        []TypeMember
	HasBody     bool
	TrailingDoc []string // `// note` on the same line as the body's closing `}`
}

ErrorDecl is `error <Category> Name [{ Body }]`. Body is optional - the shortest form (`error NotFound UserNotFound`) inherits all defaults from the category. HasBody distinguishes "explicit empty body `{}`" from "no body at all" (both produce empty Body slice).

func (*ErrorDecl) DeclName

func (d *ErrorDecl) DeclName() string

func (*ErrorDecl) DeclPos

func (d *ErrorDecl) DeclPos() Pos

type Expr

type Expr interface {

	// ExprPos returns the start position of the expression.
	ExprPos() Pos
	// contains filtered or unexported methods
}

Expr is the interface implemented by every literal value node that may appear as a decorator argument or default value.

func DecoratorArgValues added in v1.3.0

func DecoratorArgValues(a *DecoratorArg) []Expr

DecoratorArgValues flattens one decorator argument into the underlying value expressions, expanding the `@x([a, b])` array-shortcut form into its elements. A decorator marked AllowArrayShortcut accepts BOTH the variadic `@x(a, b)` and the array `@x([a, b])` forms, and the analyzer treats them as equivalent — so every consumer (semantic checks AND every codegen emitter) must read them identically through this one helper, or the array form silently contributes nothing (the bug class that dropped @errors / @tags / @middlewares array shortcuts).

type Field

type Field struct {
	Pos        Pos
	Doc        []string
	Name       string
	Type       *TypeRef
	Decorators []*Decorator
}

Field is a single `name TypeRef [@decorators]` line in a type body. The Decorators slice holds both the leading and trailing decorator chains merged in source order (parser-side concatenation).

func FieldOf

func FieldOf(name, typeName string) *Field

FieldOf is shorthand for a Field with a single-segment named type (`FieldOf("id", "string")` → DSL `id string`).

func FieldT

func FieldT(name string, t *TypeRef) *Field

FieldT is the general form: arbitrary TypeRef.

func FindField

func FindField(body []TypeMember, name string) *Field

FindField returns the first Field in body whose Name matches, or nil. Mixin members are not considered (a mixin's fields surface via Go's struct embedding at runtime, not under a single name in the host's body).

func (*Field) Equal

func (f *Field) Equal(o *Field) bool

Equal reports whether two Fields have the same Name + Type. Decorators and doc/comment are NOT compared — tests asserting decorator shape build smaller comparisons; tree-level equality cares about wire shape.

func (*Field) MemberPos

func (f *Field) MemberPos() Pos

type File

type File struct {
	// LeadingDoc preserves a `//` block at the very top of the file
	// when the first AST-bearing token is a file-level decorator
	// (`@version`, `@doc`, ...). [Decorator] has no Doc field, so
	// without LeadingDoc the lexer-attached comment would be lost
	// after the parse / format round trip. When the first token is
	// `package`, the same comment lands on [PackageDecl.Doc] instead
	// and LeadingDoc stays empty.
	LeadingDoc []string
	Decorators []*Decorator
	Package    *PackageDecl
	Imports    []*Import
	Decls      []Decl
	// Comments is the side channel containing every `//` comment in
	// the source, in source order, with leading/trailing kind. The
	// parser populates it from the lexer's accumulated set so tools
	// (formatter, linters, doc generators) can read every comment
	// without re-scanning the source. Decl-level Doc/TrailingDoc
	// fields are convenience copies of comments that AST attachment
	// already captured; this slice is the exhaustive view.
	Comments []*Comment
}

type FloatLit

type FloatLit struct {
	Pos   Pos
	Value float64
}

FloatLit is a parsed signed float64 literal.

func (*FloatLit) ExprPos

func (e *FloatLit) ExprPos() Pos

type FreeComment

type FreeComment struct {
	Pos  Pos
	Text []string
}

FreeComment is a free-floating `//` comment block that appears inside a type / enum / service body and does not attach to any field, value, or method. Common patterns:

  • Section dividers immediately after the opening `{`.
  • Closing notes (TODO / NOTE) immediately before the `}`.
  • Stand-alone blocks separated from surrounding members by a blank line.

Text holds one entry per `//` source line, with the leading `// ` (slashes plus optional single space) already stripped — the parser populates this from the lexer's Doc-attached buffer when the buffer is decided to be "free-floating" rather than the next member's leading doc.

Implements TypeMember, EnumMember, and ServiceMember so the same node can sit inside any body kind.

func (*FreeComment) MemberPos

func (c *FreeComment) MemberPos() Pos

type IdentExpr

type IdentExpr struct {
	Pos  Pos
	Name *QualifiedIdent
}

IdentExpr is a reference to a named value (an enum value, a middleware name, etc.) used inside decorator arguments. Resolution happens in the semantic phase.

func (*IdentExpr) ExprPos

func (e *IdentExpr) ExprPos() Pos

type Import

type Import struct {
	Pos         Pos
	Alias       string
	Path        string
	Doc         []string
	TrailingDoc string
}

Import models a single `import [alias] "path"` line. Alias is empty when omitted; semantic phase derives a default alias from the last path segment.

Doc captures the run of `//` comments immediately above the `import` keyword. TrailingDoc captures a `// note` on the same line as the path string, e.g. `import "auth" // for AuthRequired middleware`.

type IntLit

type IntLit struct {
	Pos   Pos
	Value int64
}

IntLit is a parsed signed integer literal.

func (*IntLit) ExprPos

func (e *IntLit) ExprPos() Pos

type MapType

type MapType struct {
	Pos   Pos
	Key   *TypeRef
	Value *TypeRef
}

MapType represents `map<K, V>`. Both Key and Value are recursive TypeRef values so that nested maps and generic instances work uniformly.

func (*MapType) Equal

func (m *MapType) Equal(o *MapType) bool

Equal reports whether two MapTypes share the same key/value shape.

type Method

type Method struct {
	Pos         Pos
	Decorators  []*Decorator
	Doc         []string
	Verb        string
	Name        string
	Path        *Path
	Request     *NamedTypeRef
	Response    *MethodResponse
	TrailingDoc []string
}

Method is a single `<verb> Name path { request? response? }`. Path is nil when the method body had no leading `/segment` - the runtime listens at `basePath + servicePrefix` in that case.

TrailingDoc captures a `// note` on the same line as the closing `}` of the method body, e.g. `} // returns 404 if not found`.

func (*Method) MemberPos

func (m *Method) MemberPos() Pos

type MethodResponse

type MethodResponse struct {
	Pos  Pos
	Type *NamedTypeRef
}

MethodResponse describes the response side of a method. The framework always JSON-encodes the named type; endpoints that want to bypass the framework entirely use the `@passthrough` decorator (which forbids a response block) instead.

type MiddlewareDecl

type MiddlewareDecl struct {
	Pos        Pos
	Decorators []*Decorator
	Doc        []string
	Name       string
}

MiddlewareDecl is `middleware Name`. The DSL captures only the name - configuration (parameter shape, defaults, behaviour) lives in the hand-written Go impl file the scaffolder produces. Doc preserves the leading `//` block.

func (*MiddlewareDecl) DeclName

func (d *MiddlewareDecl) DeclName() string

func (*MiddlewareDecl) DeclPos

func (d *MiddlewareDecl) DeclPos() Pos

type Mixin

type Mixin struct {
	Pos Pos
	Ref *NamedTypeRef
}

Mixin is a bare reference (qualified ident, optionally generic) inside a type body. The semantic phase expands its fields into the host type.

func MixinOf

func MixinOf(name string) *Mixin

MixinOf builds a Mixin for a single-segment ref (`MixinOf("Profile")` → DSL bare `Profile`).

func MixinQualified

func MixinQualified(parts ...string) *Mixin

MixinQualified builds a Mixin for a multi-segment qualified ref (`MixinQualified("shared", "Audit")` → DSL `shared.Audit`).

func (*Mixin) Equal

func (m *Mixin) Equal(o *Mixin) bool

Equal reports whether two Mixins reference the same type (qualified or generic). Doc/decorators ignored — mixins carry no decorators anyway by parser contract.

func (*Mixin) MemberPos

func (m *Mixin) MemberPos() Pos

type NamedTypeRef

type NamedTypeRef struct {
	Pos  Pos
	Name *QualifiedIdent
	Args []*TypeRef
}

NamedTypeRef references a declared type, possibly with generic arguments. Args is non-empty only for generic instances; the codegen renames such instances to e.g. `FooOfUserAndOrg`.

func (*NamedTypeRef) Equal

func (n *NamedTypeRef) Equal(o *NamedTypeRef) bool

Equal reports whether two NamedTypeRefs share the same qualified name AND generic arguments (recursively).

type NullLit

type NullLit struct {
	Pos Pos
}

NullLit holds the `null` keyword.

func (*NullLit) ExprPos

func (e *NullLit) ExprPos() Pos

type ObjectField

type ObjectField struct {
	Pos   Pos
	Name  string
	Value Expr
}

ObjectField is one `name: value` pair inside a `{}` decorator argument.

type PackageDecl

type PackageDecl struct {
	Pos  Pos
	Doc  []string
	Name string
}

PackageDecl is the `package <name>` line. Optional in single-file projects; required when more than one file participates in the same logical package.

type Path

type Path struct {
	Pos      Pos
	Segments []*PathSegment
}

Path is the parsed representation of a route path. Each segment is either a literal (possibly hyphenated like `api-v1`) or a `{param}`.

type PathSegment

type PathSegment struct {
	Pos     Pos
	Param   bool
	Literal string
}

PathSegment models one `/segment` between slashes. Param == true means the source had `{Literal}`; otherwise Literal is the literal text. An empty Literal with Param == false represents a trailing slash.

type Pos

type Pos = lexer.Position

Pos aliases lexer.Position to keep ast/* free of a hard dependency on lexer naming and to make node-position fields read clearly.

type QualifiedIdent

type QualifiedIdent struct {
	Pos   Pos
	Parts []string
}

func (*QualifiedIdent) Equal

func (q *QualifiedIdent) Equal(o *QualifiedIdent) bool

Equal reports whether two QualifiedIdent reference the same dotted name.

func (*QualifiedIdent) String

func (q *QualifiedIdent) String() string

String returns the dotted form, e.g. `pkg.Name` or `Name`.

type ScalarDecl

type ScalarDecl struct {
	Pos        Pos
	Decorators []*Decorator
	Doc        []string
	Name       string
	Primitive  string
}

ScalarDecl is `scalar Name <PrimitiveType> [@decorators]`. Doc holds the run of `//` comments immediately preceding the `scalar` keyword, captured for hover popups and round-trip-safe formatting.

func (*ScalarDecl) DeclName

func (d *ScalarDecl) DeclName() string

func (*ScalarDecl) DeclPos

func (d *ScalarDecl) DeclPos() Pos

type ServiceDecl

type ServiceDecl struct {
	Pos         Pos
	Decorators  []*Decorator
	Doc         []string
	Name        string
	Members     []ServiceMember
	Extend      bool
	TrailingDoc []string // `// note` on the same line as the body's closing `}`
}

ServiceDecl is either a primary `service Name { ... }` (Extend == false) or a continuation `extend service Name { ... }` (Extend == true). The semantic phase merges all extends into the primary.

Members is a heterogeneous list of *Method (the actual endpoints) and *FreeComment (free-floating section dividers / closing notes). Use ServiceDecl.Methods when only the typed endpoints are needed.

func (*ServiceDecl) DeclName

func (d *ServiceDecl) DeclName() string

func (*ServiceDecl) DeclPos

func (d *ServiceDecl) DeclPos() Pos

func (*ServiceDecl) Methods

func (d *ServiceDecl) Methods() []*Method

Methods returns only the *Method entries from Members in source order. Convenience for callers (semantic, codegen) that ignore free-floating comments and want the typed list.

type ServiceMember

type ServiceMember interface {

	// MemberPos returns the position of the member's first token.
	MemberPos() Pos
	// contains filtered or unexported methods
}

ServiceMember is the interface implemented by anything that can appear inside a `service` body: *Method for typed endpoints, *FreeComment for free-floating notes / section dividers.

type SizeLit

type SizeLit struct {
	Pos  Pos
	Text string
}

SizeLit keeps the original source text (e.g. "1MB"). Same rationale as DurationLit.

func (*SizeLit) ExprPos

func (e *SizeLit) ExprPos() Pos

type StringLit

type StringLit struct {
	Pos   Pos
	Value string
}

StringLit holds the unescaped contents of a `"..."` or backtick literal. (The lexer keeps escapes verbatim; the parser unescapes when constructing this node.)

func (*StringLit) ExprPos

func (e *StringLit) ExprPos() Pos

type TypeDecl

type TypeDecl struct {
	Pos         Pos
	Decorators  []*Decorator
	Doc         []string
	Name        string
	TypeParams  []string
	Body        []TypeMember
	TrailingDoc []string
}

func (*TypeDecl) DeclName

func (d *TypeDecl) DeclName() string

func (*TypeDecl) DeclPos

func (d *TypeDecl) DeclPos() Pos

type TypeMember

type TypeMember interface {

	// MemberPos returns the position of the member's first token.
	MemberPos() Pos
	// contains filtered or unexported methods
}

TypeMember is the interface for items inside a `{}` type body - either a Field or a Mixin.

type TypeRef

type TypeRef struct {
	Pos      Pos
	Map      *MapType
	Named    *NamedTypeRef
	Array    bool
	Optional bool
	// ArrayDepth captures multi-dimensional arrays (`Tag[][]` →
	// 2). Single-dim arrays use depth 1. Code that only needs
	// "is this an array?" can keep checking [Array] / `> 0`
	// equivalently.
	ArrayDepth int
}

TypeRef describes a type expression. Exactly one of Map or Named is set; Array and Optional are independent suffix flags so `T[]?` is legal.

`ArrayDepth` is the number of trailing `[]` suffixes parsed (0 = not an array). `Array bool` is a derived convenience for call sites that only care whether the field is "any kind of array" - it equals `ArrayDepth > 0` after every parse.

func Generic

func Generic(name string, args ...*TypeRef) *TypeRef

Generic builds a generic-instantiation TypeRef (`Generic("Page", Named("Order"))` → DSL `Page<Order>`).

func MapOf

func MapOf(key, value string) *TypeRef

MapOf builds a `map<K, V>` TypeRef from two single-segment names. Use MapOfTypes when key or value need a more complex shape.

func MapOfTypes

func MapOfTypes(key, value *TypeRef) *TypeRef

MapOfTypes is the general form taking already-built TypeRefs.

func Named

func Named(name string) *TypeRef

Named builds a TypeRef for a single-segment named type (`Named("string")` → DSL `string`). Use Qualified for dotted refs.

func NamedArr

func NamedArr(name string) *TypeRef

NamedArr is `Named` + Array flag (`string[]`).

func NamedArrOpt

func NamedArrOpt(name string) *TypeRef

NamedArrOpt is array + optional (`string[]?`).

func NamedOpt

func NamedOpt(name string) *TypeRef

NamedOpt is `Named` + Optional flag (`string?`).

func Qualified

func Qualified(parts ...string) *TypeRef

Qualified builds a multi-segment named ref (`Qualified("shared", "User")` → DSL `shared.User`). Returns a TypeRef ready to slot into a field.

func (*TypeRef) ElemTypeRef added in v1.3.0

func (t *TypeRef) ElemTypeRef() *TypeRef

ElemTypeRef returns a copy of t with ONE array dimension peeled: the depth is decremented and Array re-set while an inner dimension remains, so a multi-dimensional element keeps its array shape. Optional is dropped — the `?` belongs to the outer field, not each element. Returns nil for a nil receiver. The semantic type-checker (literal type-fit) and the codegen default pre-fill both peel array elements this way; sharing one definition keeps them from drifting.

func (*TypeRef) Equal

func (t *TypeRef) Equal(o *TypeRef) bool

Equal reports whether two TypeRefs describe the same type shape: same Map / Named / Array / Optional / ArrayDepth.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL