TYCHO Search

Search documentation

    ↑ ↓ to move · Enter to open · Esc to close

    4. Grammar

    This chapter defines the phrase grammar — the mapping from the token stream (§3) to program structure. Terminals are token kinds in UPPER_CASE (IDENT, INT, FLOAT, STR, CHAR, NEWLINE, INDENT, DEDENT) or the literal spellings of keywords and punctuation in double quotes. The notation is defined in §2.1.

    The grammar defines *shape*, not *validity*: some productions accept forms that a later static rule rejects (for example, a value if without an else, §4.4). Such forms are flagged here and constrained in the semantic chapters. A program is valid only if it parses and satisfies every static-semantic rule.

    4.1 Program and top-level declarations

    A compilation input is a sequence of top-level declarations. Blank lines between them are insignificant.

    Program   ::= TopDecl*
    TopDecl   ::= PackageDecl | ImportDecl | ExternFn | ConstDecl
                | Subscript | Struct | Enum | Handle | TypeDecl | Fn
    

    The leader disambiguates: package, import, const, subscript (contextual identifiers, §3.7), extern (contextual, when not immediately followed by (), and the keywords struct, enum, handle, type each select their declaration; anything else begins a function.

    PackageDecl ::= "package" IDENT NEWLINE
    ImportDecl  ::= "import" IDENT? STR NEWLINE
    ConstDecl   ::= "const" IDENT "=" ConstExpr NEWLINE
    

    ImportDecl's optional IDENT is an alias for the imported package's prefix; the STR is the path (a core: prefix selects the corelib root). ConstExpr is an expression that folds to a single literal at compile time (integer arithmetic, bitwise, unary, and backward references to earlier top-level constants); its rules are given in §8 and §13. Package resolution, visibility, and merging are specified in §28.

    4.1.1 Functions

    Fn         ::= "fn" IDENT "(" ParamList? ")" ( "->" Type )? WhereClause? ":" NEWLINE Block
    ParamList  ::= Param ( "," Param )*
    Param      ::= IDENT ":" ( "inout" | "sink" )? "..."? Type
    

    A parameter is a name, a colon, an optional passing mode (inout for a copy-in/copy-out borrow, or the contextual sink for an owned/consumed parameter — §15), an optional ... marking a variadic parameter, and its type. A variadic parameter has type [T] (the call packs its trailing arguments into it) and MUST be the last parameter; it cannot also be inout or sink. A missing -> return type means the function returns nothing (void). A function MUST NOT return a handle (§25).

    Type parameters are introduced *inside* Type by the $ sigil (§4.2); a function that mentions any $T type parameter or $N size parameter in its signature is generic. A WhereClause constrains a generic function:

    WhereClause ::= "where" Constraint ( "," Constraint )*
    Constraint  ::= Predicate "(" IDENT ")"
                  | IDENT ":" Type ( "|" Type )*
    Predicate   ::= "numeric" | "comparable" | "has_str" | "hashable" | "defaultable"
    

    The predicate set is closed (exactly the five names above; an unknown predicate is rejected) — see §7 for constraints. The type-set form T: A | B | … constrains T to one of a listed set (up to 16 types). A where clause requires a generic function; at most 8 constraints are allowed.

    4.1.2 Structs, enums, newtypes, handles

    Struct     ::= "packed"? "struct" IDENT TypeParams? ":" NEWLINE INDENT FieldDecl+ DEDENT
    FieldDecl  ::= IDENT ":" Type NEWLINE
    Enum       ::= "enum" IDENT TypeParams? ":" NEWLINE INDENT VariantDecl+ DEDENT
    VariantDecl::= IDENT ( "(" Type ( "," Type )* ")" )? NEWLINE
    Handle     ::= "handle" IDENT ":" NEWLINE INDENT "free" ":" IDENT NEWLINE DEDENT
    TypeDecl   ::= "type" IDENT "=" Type NEWLINE
    TypeParams ::= "(" "$" IDENT ( "," "$" IDENT )* ")"
    

    A struct has one or more fields; an enum has one or more variants, each with an optional payload of up to 8 types. A handle names an opaque FFI resource and its C free function (§25). A type declaration introduces a distinct newtype over an underlying type; the permitted underlying types are constrained in §5. Recursion through a struct field is permitted only via a container (e.g. [Node]), never as a direct by-value self-field (§17).

    4.1.3 Extern functions and subscripts

    ExternFn   ::= "extern" STR? "fn" IDENT "(" ExternParamList? ")" ( "->" Type )? NEWLINE
    Subscript  ::= "subscript" IDENT "(" ParamList? ")" "->" "inout" Type ":" NEWLINE
                   INDENT "yield" "&" Place NEWLINE DEDENT
    

    An ExternFn is bodyless and binds to a C symbol; its optional STR names a link library. Its parameter and return types are restricted to the FFI-crossable set — the scalars (int, char, float, bool), string, bytes, ptr, [int]/[float], typed handles, the first-class fixed-width numerics u8…u64 / i8…i64 / f32, plus inout scalar out-parameters (not string) and an Option(string) return (§24).

    A Subscript declares a user-defined projection: it yields a place (an lvalue) rooted in one of its parameters. Its rules — the place must be rooted in a parameter, each parameter used at most once — are given in §18. Place is defined in §4.4.

    4.2 Types

    Type      ::= "$" IDENT                                        /* type parameter */
                | "soa" "[" Type "]"                               /* struct-of-arrays */
                | "bounded" "[" ( INT | IDENT ) "]" Type            /* bounded[N]T / bounded[C]T, C an int const */
                | "vector" "[" ( INT | IDENT ) "]" Type             /* vector[N]T, N a power of two 2..64 */
                | "fn" "(" ( Type ( "," Type )* )? ")" ( "->" Type )?
                | "(" Type ( "," Type )+ ")"                       /* tuple, 2..8 elements */
                | "[" ArrayOrMap
                | "Option" "(" Type ")"
                | "Result" "(" ( Type | "void" ) "," Type ")"   /* void: ok payload only */
                | "Channel" "(" Type ")"
                | QualName TypeArgs?                               /* struct/enum/newtype/handle */
                | PrimType
    ArrayOrMap::= "$" IDENT "]" Type                              /* [$N]T  size-parameter array */
                | INT "]" Type                                     /* [N]T   fixed-size array */
                | IDENT "]" Type                                   /* [C]T   fixed size named by an int const */
                | Type "]"                                         /* [T]    dynamic array */
                | Type ":" Type "]"                                /* [K:V]  map */
    TypeArgs  ::= "(" Type ( "," Type )* ")"
    QualName  ::= IDENT ( "." IDENT )?                            /* Name or pkg.Name */
    PrimType  ::= "int" | "float" | "bool" | "string" | "ptr" | "bytes"
                | "u32" | "u64" | "f32"
    

    Notes (constrained further in §5–§7):

    4.3 Blocks and statements

    A block is a newline-introduced, indentation-delimited sequence of statements:

    Block ::= INDENT Stmt+ DEDENT
    Stmt  ::= ConstStmt | DeleteStmt | Return | Break | Continue | PassStmt
            | Select | Match | If | ParallelFor | For
            | MultiDecl | MultiAssign | SimulAssign
            | Decl | TypedDecl | Assign | PlaceAssign | CompoundAssign
            | ExprStmt
    

    4.3.1 Simple statements

    ConstStmt      ::= "const" IDENT "=" ConstExpr NEWLINE
    DeleteStmt     ::= "delete" Postfix NEWLINE            /* Postfix MUST be an index m[k] */
    Return         ::= "return" ( Expr ( "," Expr )* )? NEWLINE
                     | "return" ValueCtrl
    Break          ::= "break" NEWLINE
    Continue       ::= "continue" NEWLINE
    PassStmt       ::= "pass" NEWLINE                       /* the no-op; contextual, see App. B */
    Decl           ::= IDENT ":=" ( Expr | ValueCtrl ) NEWLINE
    TypedDecl      ::= IDENT ":" Type "=" ( Expr | ValueCtrl ) NEWLINE
    Assign         ::= IDENT "=" ( Expr | ValueCtrl ) NEWLINE
    MultiDecl      ::= IDENT ( "," IDENT )+ ":=" Expr NEWLINE
    MultiAssign    ::= IDENT ( "," IDENT )+ "=" Expr NEWLINE
    SimulAssign    ::= "(" Place ( "," Place )+ ")" "=" Expr NEWLINE
    PlaceAssign    ::= Place "=" ( Expr | ValueCtrl ) NEWLINE
    CompoundAssign ::= Place CompoundOp "=" Expr NEWLINE
    CompoundOp     ::= "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<<" | ">>"
    ExprStmt       ::= ( Call | Call "or_return" | Call OrElse ) NEWLINE   /* or_return/or_else form: ok payload must be void */
    

    4.3.2 Compound statements

    If          ::= "if" Expr ":" NEWLINE Block
                    ( "elif" Expr ":" NEWLINE Block )*
                    ( "else" ":" NEWLINE Block )?
    Match       ::= "match" Expr ":" NEWLINE INDENT MatchArm+ DEDENT
    MatchArm    ::= Pattern ":" NEWLINE Block
    Pattern     ::= VariantName ( "(" IDENT ( "," IDENT )* ")" )?   /* variant, with 0..8 bindings */
                  | VariantName "(" SubPattern ")"               /* Ok/Err/Some: one nested level */
                  | ScalarPattern                                /* int/char/bool subject */
                  | "_"                                          /* wildcard */
    VariantName ::= IDENT | IDENT "." IDENT                       /* Variant or pkg.Variant */
    SubPattern  ::= VariantName ( "(" IDENT ( "," IDENT )* ")" )?
    ScalarPattern ::= ScalarElem ( ".." ScalarElem )? ( "|" ScalarElem ( ".." ScalarElem )? )*
    ScalarElem  ::= "-"? ( INT | CHAR | TRUE | FALSE ) | IDENT    /* IDENT: an int constant */
    For         ::= "for" ForInit ";" Expr ";" ForPost ":" NEWLINE Block  /* three-clause; all three REQUIRED */
                  | "for" IDENT "in" Expr ":" NEWLINE Block               /* foreach */
                  | "for" Expr ":" NEWLINE Block                          /* condition */
                  | "for" ":" NEWLINE Block                               /* infinite */
    ForInit     ::= Decl | TypedDecl | Assign | CompoundAssign  /* the ';' supplies the clause's NEWLINE */
    ForPost     ::= Assign | CompoundAssign                     /* the ':' supplies it; target MUST be a variable */
    ParallelFor ::= "parallel" Width? "for" IDENT "in" "0" "..<" Expr ":" NEWLINE Block  /* counting; the `0` is a literal */
                  | "parallel" Width? "for" IDENT "in" IDENT ":" NEWLINE Block           /* foreach; the source MUST be a name */
    Width       ::= "(" Expr ")"                                  /* fan-out width, 1..64; omitted = ncpu() */
    Select      ::= "select" ":" NEWLINE INDENT SelectArm+ DEDENT
    SelectArm   ::= "recv" "(" Expr "," IDENT ")" ":" NEWLINE Block
                  | "default" ":" NEWLINE Block
                  | "closed" ":" NEWLINE Block
    ValueCtrl   ::= If | Match              /* value form: block branches ending in a value expression, tail position */
    

    4.4 Expressions

    The expression grammar encodes precedence and associativity by nesting: each level parses the next-tighter level and then, left-associatively, its own operators (except the prefix levels, which are right-recursive).

    Expr      ::= OrExpr
    OrExpr    ::= AndExpr ( "or" AndExpr )*
    AndExpr   ::= NotExpr ( "and" NotExpr )*
    NotExpr   ::= "not" NotExpr | CmpExpr
    CmpExpr   ::= IsExpr ( ( "==" | "!=" | "<" | ">" | "<=" | ">=" | "in" ) IsExpr )*
    IsExpr    ::= AddExpr ( "is" VariantName )?             /* variant test; does NOT chain */
    AddExpr   ::= MulExpr ( ( "+" | "-" | "|" | "^" ) MulExpr )*
    MulExpr   ::= UnaryExpr ( ( "*" | "/" | "%" | "<<" | ">>" | "&" ) UnaryExpr )*
    UnaryExpr ::= ( "-" | "&" | "~" ) UnaryExpr | Postfix
    Postfix   ::= Primary PostfixOp* ( "or_return" | OrElse )? "..."?
    OrElse    ::= "or_else" IDENT ":" Expr                  /* IDENT or `_`; returns Expr on failure (§14.6.1) */
    PostfixOp ::= "[" Expr "]"                              /* index */
                | "[" Expr? ":" Expr? "]"                   /* slice (either bound optional) */
                | "." IDENT                                 /* field access */
                | "." INT                                   /* tuple index */
                | "." "(" Component ( "," Component )* ")"  /* swizzle, 2-8 components (§17.5) */
                | "." IDENT CallTypeArgs? "(" ArgList? ")"  /* qualified pkg call (on a bare identifier) */
                | "." IDENT CallTypeArgs                    /* ... with no value arguments */
                | "(" ArgList? ")"                          /* call */
    ArgList   ::= Expr ( "," Expr )*
    Component ::= IDENT | INT                               /* a struct field, or a lane of an array / bounded / vector */
    

    The place grammar (assignable lvalues, §4.3.1) is the subset of Postfix whose spine is an index, field, tuple-index, or subscript call rooted in a variable:

    Place ::= IDENT ( "[" Expr "]" | "." IDENT | "." INT | "(" ArgList? ")" )*
    

    Primary expressions:

    Primary ::= INT | FLOAT | STR | CHAR
              | "true" | "false" | "null"
              | "(" Expr ")"                                /* grouping */
              | "(" Expr ( "," Expr )+ ")"                  /* tuple literal, 2..8 */
              | ArrayOrMapLit                               /* [ ... ] forms (§16, §18) */
              | "soa" "[" "]" Type                          /* empty soa literal */
              | "channel" "(" Type "," Expr ")"             /* only as a declaration RHS */
              | "spawn" Call                                /* spawn a direct call */
              | Lambda
              | "None" | "Some" "(" Expr ")" | "Ok" "(" Expr? ")" | "Err" "(" Expr ")"
              | IDENT CallTypeArgs ( "(" ArgList? ")" )?    /* explicit type args */
              | IDENT "(" ArgList? ")"                      /* named call */
              | IDENT                                       /* variable, or nullary enum variant */
    CallTypeArgs ::= "$" "(" Type ( "," Type )* ")"  /* at least one; `f$()` is an error */
    Lambda  ::= "fn" "(" LambdaParams? ")" ( "->" Type )? ":" Expr
    LambdaParams ::= IDENT ( ":" Type )? ( "," IDENT ( ":" Type )? )*
    

    A lambda parameter's type is optional (inferred from the expected function type when omitted, §6.2), and a lambda parameter may not carry inout, sink, or ... — so a lambda uses LambdaParams, not the ParamList of §4.1.1. Call in ExprStmt and after spawn denotes a Postfix whose outermost operation is a call (...). A Subscript (§4.1.3) parameter is likewise plain IDENT ":" Type (no passing modes).

    .

    4.5 Operator precedence and associativity

    From tightest-binding to loosest. Every binary level is left-associative; the prefix levels are right-associative.

    PrecedenceOperatorsKindAssoc
    1 (tightest)[] index/slice · . field/tuple-index · call · or_return · or_else · ...postfixleft
    2- & ~unary prefixright
    3* / % << >> &binaryleft
    4+ - `\ ^`binaryleft
    5isbinary, non-associative—
    6== != < > <= >= inbinaryleft
    7notunary prefixright
    8andbinaryleft
    9 (loosest)orbinaryleft

    This is Go's precedence: the shift operators (<<, >>) and bitwise-AND (&) bind at the multiplicative level, and bitwise-OR/XOR (|, ^) bind at the additive level — so every bitwise operator binds *tighter* than any comparison. Consequently a & b == c parses as (a & b) == c. The unary & (address-of / inout argument) and binary & (bitwise-AND) share a spelling but occupy different precedence levels (2 and 3). is is non-associative: its right operand is a variant name rather than an expression, so a is X is Y does not parse (§19.8). and/or short-circuit (§13). Evaluation order within a precedence level, and the order of argument and place sub-expression evaluation, is specified in §13 — the grammar fixes only associativity, not side-effect order.