1213 lines
50 KiB
EBNF
1213 lines
50 KiB
EBNF
(* Blaise Language Grammar — EBNF
|
||
*
|
||
* This grammar reflects the language as implemented through Phase 3 of the
|
||
* Blaise compiler. It is derived directly from uParser.pas and serves as
|
||
* the authoritative reference for the parser, language tooling, and future
|
||
* extensions.
|
||
*
|
||
* Notation
|
||
* --------
|
||
* { x } zero or more repetitions of x
|
||
* [ x ] optional x (zero or one)
|
||
* ( x | y ) alternation
|
||
* "..." terminal — literal token text (case-insensitive keywords)
|
||
* UPPER terminal — token class defined in the Tokens section
|
||
*
|
||
* The Tokens section defines the terminal vocabulary produced by the lexer.
|
||
* The Grammar section defines the syntactic structure.
|
||
*)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* TOKENS
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
(* Literals *)
|
||
(*
|
||
* Integer literals support four bases. Underscore separators may appear
|
||
* between digits (never at the start or end of the digit run, and never
|
||
* immediately after the base prefix).
|
||
*
|
||
* dec_digit ::= "0" .. "9"
|
||
* hex_digit ::= "0" .. "9" | "A" .. "F" | "a" .. "f"
|
||
* oct_digit ::= "0" .. "7"
|
||
* bin_digit ::= "0" | "1"
|
||
*
|
||
* digit_seq(d) ::= d { [ "_" ] d }
|
||
* (underscore may appear between two digits of kind d, but not
|
||
* before the first digit or after the last)
|
||
*
|
||
* INT_LIT is one of:
|
||
* decimal : digit_seq(dec_digit)
|
||
* hex : "$" digit_seq(hex_digit)
|
||
* octal : "&" digit_seq(oct_digit)
|
||
* binary : "%" digit_seq(bin_digit)
|
||
*
|
||
* FLOAT_LIT is a decimal-only form (no base prefix):
|
||
* digit_seq(dec_digit) "." digit_seq(dec_digit)
|
||
* [ ( "e" | "E" ) [ "+" | "-" ] digit_seq(dec_digit) ]
|
||
*
|
||
* Underscores are also permitted between digits in the fractional and
|
||
* exponent parts of FLOAT_LIT, subject to the same adjacency rules.
|
||
* Underscores may not appear adjacent to the decimal point or the
|
||
* "e"/"E" exponent marker.
|
||
*)
|
||
INT_LIT
|
||
= dec_digit { [ "_" ] dec_digit }
|
||
| "$" hex_digit { [ "_" ] hex_digit }
|
||
| "&" oct_digit { [ "_" ] oct_digit }
|
||
| "%" bin_digit { [ "_" ] bin_digit }
|
||
;
|
||
|
||
FLOAT_LIT
|
||
= dec_digit { [ "_" ] dec_digit }
|
||
"."
|
||
dec_digit { [ "_" ] dec_digit }
|
||
[ ( "e" | "E" ) [ "+" | "-" ] dec_digit { [ "_" ] dec_digit } ]
|
||
;
|
||
|
||
STRING_LIT = "'" { char | "''" } "'" ;
|
||
TEXT_BLOCK_LIT = "'''" NEWLINE { any_char | NEWLINE } "'''" ;
|
||
(* TEXT_BLOCK_LIT: opening ''' must be followed immediately by a newline
|
||
(LF, CR, or CRLF); any other character after ''' falls back to the
|
||
classic string parse. Closing ''' must not be followed by another '.
|
||
The column position of the closing ''' sets the indentation baseline —
|
||
all content lines are de-dented by that many columns. Single quotes
|
||
inside the block need no escaping. *)
|
||
|
||
(* Identifiers (case-insensitive) *)
|
||
IDENT = letter { letter | digit | "_" } ;
|
||
|
||
(* Keywords — all case-insensitive *)
|
||
PROGRAM = "program" ;
|
||
UNIT = "unit" ;
|
||
INTERFACE = "interface" ;
|
||
IMPLEMENTATION = "implementation" ;
|
||
USES = "uses" ;
|
||
TYPE = "type" ;
|
||
RECORD = "record" ;
|
||
PACKED = "packed" ;
|
||
CLASS = "class" ;
|
||
VAR = "var" ;
|
||
THREADVAR = "threadvar" ;
|
||
OUT = "out" ;
|
||
BEGIN = "begin" ;
|
||
END = "end" ;
|
||
IF = "if" ;
|
||
THEN = "then" ;
|
||
ELSE = "else" ;
|
||
WHILE = "while" ;
|
||
DO = "do" ;
|
||
FOR = "for" ;
|
||
TO = "to" ;
|
||
DOWNTO = "downto" ;
|
||
TRY = "try" ;
|
||
EXCEPT = "except" ;
|
||
FINALLY = "finally" ;
|
||
RAISE = "raise" ;
|
||
EXIT = "exit" ;
|
||
NIL = "nil" ;
|
||
IS = "is" ;
|
||
AS = "as" ;
|
||
DIV = "div" ;
|
||
VIRTUAL = "virtual" ;
|
||
OVERRIDE = "override" ;
|
||
PROCEDURE = "procedure" ;
|
||
FUNCTION = "function" ;
|
||
CONSTRUCTOR = "constructor" ;
|
||
DESTRUCTOR = "destructor" ;
|
||
|
||
(* Context-sensitive soft keywords — IDENT that is treated as a keyword in a
|
||
* specific position but may be used as an identifier elsewhere *)
|
||
PROPERTY = "property" ; (* only in class bodies *)
|
||
READ = "read" ; (* only in property declarations *)
|
||
WRITE = "write" ; (* only in property declarations *)
|
||
|
||
(* Operators and punctuation *)
|
||
ASSIGN = ":=" ;
|
||
EQUALS = "=" ;
|
||
NOT_EQUALS = "<>" ;
|
||
LESS = "<" ;
|
||
GREATER = ">" ;
|
||
LESS_EQ = "<=" ;
|
||
GREATER_EQ = ">=" ;
|
||
COLON = ":" ;
|
||
PLUS = "+" ;
|
||
MINUS = "-" ;
|
||
STAR = "*" ;
|
||
SLASH = "/" ;
|
||
LPAREN = "(" ;
|
||
RPAREN = ")" ;
|
||
COMMA = "," ;
|
||
SEMICOLON = ";" ;
|
||
DOT = "." ;
|
||
CARET = "^" ; (* pointer dereference / pointer type prefix *)
|
||
|
||
(* Ignored by the lexer *)
|
||
WHITESPACE = { " " | "\t" | "\r" | "\n" } ;
|
||
LINE_COMMENT = "//" { char } newline ;
|
||
BLOCK_COMMENT = "{" { char } "}" | "(*" { char } "*)" ;
|
||
DIRECTIVE = "{$" { char } "}" ; (* compiler directive *)
|
||
|
||
(* Conditional-compilation directives, handled in the lexer. Symbols are
|
||
* case-insensitive. Predefined: BLAISE (always), and the target's CPU/OS
|
||
* symbols (CPUX86_64, CPUAMD64, LINUX, UNIX on Linux/x86-64). The -d/--define
|
||
* command-line flag and {$DEFINE} add symbols; {$UNDEF} removes them.
|
||
*
|
||
* {$DEFINE sym} define a symbol
|
||
* {$UNDEF sym} undefine a symbol
|
||
* {$IFDEF sym} ... {$ENDIF}
|
||
* {$IFDEF sym} ... {$ELSE} ... {$ENDIF}
|
||
* {$IFNDEF sym} ... [ {$ELSE} ... ] {$ENDIF}
|
||
*
|
||
* IFDEF keeps its body when the symbol is defined (IFNDEF when not); the
|
||
* optional ELSE branch is taken otherwise. IFDEF/IFNDEF blocks nest. There
|
||
* is deliberately no expression form ({$IF ...}) yet — only symbol presence.
|
||
* All other directives (e.g. {$H+}, {$mode ...}) are accepted and ignored. *)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* COMPILATION UNITS
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
(* A source file is either a program or a unit. *)
|
||
|
||
Program
|
||
= PROGRAM IDENT SEMICOLON
|
||
[ UsesClause ]
|
||
Block
|
||
DOT
|
||
;
|
||
|
||
Unit
|
||
= UNIT UnitName SEMICOLON
|
||
InterfaceSection
|
||
ImplementationSection
|
||
DOT
|
||
;
|
||
|
||
InterfaceSection
|
||
= INTERFACE
|
||
[ UsesClause ]
|
||
{ ConstSection | ProcSignature }
|
||
;
|
||
|
||
ImplementationSection
|
||
= IMPLEMENTATION
|
||
[ UsesClause ]
|
||
{ ConstSection | StandaloneDecl }
|
||
;
|
||
|
||
UsesClause
|
||
= USES UnitName { COMMA UnitName } SEMICOLON
|
||
;
|
||
|
||
UnitName
|
||
= IDENT { DOT IDENT }
|
||
;
|
||
|
||
(* Dotted unit names map to path-separated files: Generics.Collections →
|
||
* Generics/Collections.pas. The dot is part of the name, not a field
|
||
* access operator; it is valid in the Unit declaration header and in
|
||
* any UnitName position inside a UsesClause. *)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* BLOCK
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
Block
|
||
= [ TypeSection ]
|
||
{ ConstSection | VarSection | ThreadVarSection | StandaloneDecl }
|
||
BEGIN StmtList END
|
||
;
|
||
|
||
(* VarSection and StandaloneDecl may be interleaved with each other. *)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* TYPE SECTION
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
TypeSection
|
||
= TYPE TypeDecl { TypeDecl }
|
||
;
|
||
|
||
TypeDecl
|
||
= IDENT EQUALS TypeDef SEMICOLON
|
||
| IDENT LESS TypeParamList GREATER EQUALS TypeDef SEMICOLON
|
||
;
|
||
|
||
(* The second form declares a generic type template. TypeDef must be either
|
||
* RecordDef (→ TGenericRecordDef), ClassDef (→ TGenericTypeDef), or
|
||
* InterfaceDef (→ TGenericInterfaceDef). The parser rejects other TypeDef
|
||
* kinds at that position. *)
|
||
|
||
TypeDef
|
||
= RecordDef
|
||
| ClassDef
|
||
| GenericRecordDef
|
||
| GenericClassDef
|
||
| InterfaceDef
|
||
| GenericInterfaceDef
|
||
| EnumDef
|
||
| SetType
|
||
| ProceduralType
|
||
| IntSubrange (* named integer subrange, e.g. TByte = 0..255 *)
|
||
| TypeName (* covers ArrayType, pointer alias ^T, simple alias, metaclass *)
|
||
;
|
||
|
||
IntSubrange
|
||
= [ MINUS ] INT_LIT DOTDOT [ MINUS ] INT_LIT
|
||
;
|
||
|
||
(* A named integer subrange is an alias to the narrowest STANDARD integer type
|
||
* that holds both bounds (0..255 -> Byte, -10..10 -> ShortInt, etc.). Blaise
|
||
* does not range-check, so the subrange carries no run-time bound enforcement;
|
||
* the base-type choice exists only to keep record/array element layout correct.
|
||
* Only integer-literal bounds are accepted as a named type; identifier- or
|
||
* enum-bounded subranges (TLow..THigh, red..blue) are not a named-type form. *)
|
||
|
||
RecordDef
|
||
= [ PACKED ] RECORD { FieldDecl | MethodDecl } END
|
||
;
|
||
|
||
(* Records may declare methods alongside fields. Method bodies are written
|
||
* out-of-line as TRecordName.MethodName, identical to classes. The
|
||
* receiver is passed by pointer (`Self` is a `var` parameter under the hood);
|
||
* the language gives normal value semantics — assignments copy the record.
|
||
* No constructors, destructors, or virtual methods are allowed in records.
|
||
*
|
||
* `packed` is permitted only in front of `record`. It disables natural
|
||
* alignment padding between fields and disables tail padding on the record
|
||
* as a whole, so the record's size equals the cumulative byte size of its
|
||
* fields. ARC-managed field types (`string`, class, interface, dynamic
|
||
* array) keep their natural 8-byte alignment even inside a packed record
|
||
* because the runtime helpers (`_StringRelease`, `_ClassRelease`, etc.)
|
||
* perform 64-bit loads through the field pointer. `packed class` and
|
||
* `packed array` are rejected as parse errors. *)
|
||
|
||
ClassDef
|
||
= CLASS [ LPAREN GenericName { COMMA GenericName } RPAREN ]
|
||
{ ConstSection | FieldList }
|
||
MethodDeclList
|
||
END
|
||
;
|
||
|
||
GenericName
|
||
= IDENT [ LESS TypeArgList GREATER ] (* plain name or generic specialisation *)
|
||
;
|
||
|
||
(* The parser stores the first name in the paren list as ParentName. During
|
||
* semantic analysis, if ParentName contains '<' it is resolved as a generic
|
||
* interface and silently moved to the ImplementsNames list. Subsequent names
|
||
* are always treated as implements entries.
|
||
* Example: class(TObject, IFoo, IBar<Integer>) *)
|
||
|
||
GenericRecordDef
|
||
= RECORD (* generic template, e.g. record with <T> *)
|
||
{ FieldDecl | MethodDecl }
|
||
END
|
||
;
|
||
|
||
(* GenericRecordDef is syntactically identical to RecordDef; the distinction
|
||
* is that it appears after "IDENT < TypeParamList > =" in a TypeDecl. The
|
||
* parser creates a TGenericRecordDef AST node wrapping the TRecordTypeDef
|
||
* body. Type parameters are substituted at instantiation time (e.g.
|
||
* TMyVal<Integer>). Records are value types — no inheritance, no vtable,
|
||
* no typeinfo — so monomorphized instances produce only field layout and
|
||
* method bodies, not class metadata. *)
|
||
|
||
GenericClassDef
|
||
= CLASS LESS TypeParamList GREATER (* generic template, e.g. class<T> *)
|
||
[ LPAREN IDENT RPAREN ]
|
||
{ ConstSection | FieldList }
|
||
MethodDeclList
|
||
END
|
||
;
|
||
|
||
(* Interface members are method signatures and properties. Property
|
||
* accessors must name methods of the interface (or an inherited one) —
|
||
* interfaces have no fields, so field-backed accessors cannot exist.
|
||
* I.Prop reads lower to the getter dispatch; I.Prop := V lowers to the
|
||
* setter dispatch through the itab. *)
|
||
InterfaceDef
|
||
= INTERFACE
|
||
[ LPAREN IDENT RPAREN ] (* optional parent interface *)
|
||
{ MethodSignature | PropertyDecl }
|
||
END
|
||
;
|
||
|
||
GenericInterfaceDef
|
||
= INTERFACE
|
||
[ LPAREN IDENT RPAREN ] (* optional parent interface *)
|
||
{ MethodSignature | PropertyDecl }
|
||
END
|
||
;
|
||
|
||
(* GenericInterfaceDef is syntactically identical to InterfaceDef; the distinction
|
||
* is that it appears after "IDENT < TypeParamList > =" in a TypeDecl. The parser
|
||
* creates a TGenericInterfaceDef AST node wrapping the TInterfaceTypeDef body.
|
||
* Type parameters are substituted at instantiation time (e.g. IFoo<Integer>). *)
|
||
|
||
FieldList
|
||
= { FieldDecl }
|
||
;
|
||
|
||
FieldDecl
|
||
= IdentList COLON TypeName SEMICOLON
|
||
;
|
||
|
||
MethodDeclList
|
||
= { MethodDecl | PropertyDecl }
|
||
;
|
||
|
||
MethodDecl
|
||
= ( PROCEDURE | FUNCTION | CONSTRUCTOR | DESTRUCTOR ) IDENT
|
||
[ LESS TypeParamList GREATER ] (* optional generic type params *)
|
||
[ LPAREN ParamList RPAREN ]
|
||
[ COLON TypeName ] (* return type for functions *)
|
||
SEMICOLON
|
||
{ MethodDirective SEMICOLON }
|
||
[ Block SEMICOLON ] (* body absent for forward, external, or abstract decls *)
|
||
;
|
||
|
||
MethodDirective
|
||
= VIRTUAL
|
||
| OVERRIDE
|
||
| ExternalDirective
|
||
| "overload" (* required on every overload of a same-named proc/func/method *)
|
||
| IDENT (* inline, stdcall, cdecl, abstract, reintroduce, … *)
|
||
;
|
||
|
||
(* Calling-convention directives (cdecl, stdcall, register, pascal, safecall)
|
||
* are parsed as the IDENT alternative and recorded on the declaration
|
||
* (TMethodDecl.CallingConv, carried into TRoutineSig.CallingConv and the
|
||
* .bif interface metadata). They are metadata only — the native backend
|
||
* currently emits the System V AMD64 convention regardless; the directive is
|
||
* preserved for FFI/tooling fidelity, not yet honoured at the ABI level. *)
|
||
|
||
ExternalDirective
|
||
= EXTERNAL [ NAME StrLit ]
|
||
;
|
||
|
||
(* Standalone procedure/function/constructor/destructor — top-level or within a block. *)
|
||
StandaloneDecl
|
||
= ( PROCEDURE | FUNCTION | CONSTRUCTOR | DESTRUCTOR ) IDENT
|
||
[ DOT IDENT ] (* TTypeName.MethodName — non-generic class impl *)
|
||
[ LESS TypeParamList GREATER DOT IDENT ] (* TName<T>.MethodName — generic class impl *)
|
||
[ LESS TypeParamList GREATER ] (* function Identity<T> — generic standalone *)
|
||
[ LPAREN ParamList RPAREN ]
|
||
[ COLON TypeName ]
|
||
SEMICOLON
|
||
{ MethodDirective SEMICOLON }
|
||
[ Block SEMICOLON ] (* body absent for external declarations *)
|
||
;
|
||
|
||
(* Disambiguation of the three IDENT forms above:
|
||
*
|
||
* IDENT DOT IDENT — non-generic class method impl (TFoo.Bar)
|
||
* IDENT LESS TypeParamList GREATER DOT IDENT — generic class method impl (TList<T>.Add)
|
||
* IDENT LESS TypeParamList GREATER — generic standalone function (Identity<T>)
|
||
*
|
||
* The parser reads the IDENT, then checks LESS IDENT (GT|COMMA) to enter the
|
||
* type-param list. After consuming the closing GREATER it inspects the next
|
||
* token: DOT → generic class method impl; anything else → generic standalone.
|
||
* For the non-generic DOT form, DOT is checked before any LESS test. *)
|
||
|
||
ProcSignature
|
||
= ( PROCEDURE | FUNCTION ) IDENT
|
||
[ LPAREN ParamList RPAREN ]
|
||
[ COLON TypeName ]
|
||
SEMICOLON
|
||
;
|
||
|
||
MethodSignatureList
|
||
= { ProcSignature }
|
||
;
|
||
|
||
PropertyDecl
|
||
= "property" IDENT
|
||
[ LBRACKET IDENT COLON TypeName RBRACKET ] (* optional index parameter *)
|
||
COLON TypeName
|
||
[ "read" IDENT ]
|
||
[ "write" IDENT ]
|
||
SEMICOLON
|
||
[ "default" SEMICOLON ] (* default array property *)
|
||
;
|
||
|
||
(* If the index parameter is present, the property is indexed:
|
||
* property Items[Index: Integer]: Pointer read Get write Put;
|
||
* Indexed reads emit a getter call with one extra argument (the index).
|
||
* Indexed writes emit a setter call with two extra arguments (index, value).
|
||
* Only a single index parameter is supported; multiple-index properties
|
||
* (as in Delphi) are not.
|
||
*
|
||
* The optional "default" directive (only meaningful on an indexed property)
|
||
* marks it as the class's default array property, enabling the Obj[I] sugar:
|
||
* property Items[I: Integer]: T read Get write Put; default;
|
||
* Obj[I] then lowers to Obj.Items[I] (getter on read, setter on write). *)
|
||
|
||
(* "property", "read", "write" are contextual soft keywords; they may be used
|
||
* as identifiers outside a class body. *)
|
||
|
||
ParamList
|
||
= ParamGroup { SEMICOLON ParamGroup }
|
||
;
|
||
|
||
ParamGroup
|
||
= [ VAR | OUT | CONST ] IdentList COLON ParamType [ EQUALS DefaultValue ]
|
||
;
|
||
|
||
ParamType
|
||
= ARRAY OF CONST (* heterogeneous variadic list *)
|
||
| ARRAY OF TypeName
|
||
| TypeName
|
||
;
|
||
|
||
DefaultValue
|
||
= IntLiteral
|
||
| FloatLiteral
|
||
| StrLiteral
|
||
| NIL
|
||
| IDENT (* must resolve to a named constant *)
|
||
;
|
||
|
||
(* VAR marks a by-reference parameter; OUT marks a write-only by-reference
|
||
* parameter (passed by reference exactly like VAR, but flagged as OUT for
|
||
* tooling/interface metadata); CONST marks a read-only parameter.
|
||
* ARRAY OF TypeName denotes an open-array parameter using the two-register
|
||
* ABI: a data pointer and a high-index value passed as separate arguments.
|
||
*
|
||
* ARRAY OF CONST is a heterogeneous variadic parameter. Its element type is
|
||
* the intrinsic record TVarRec; a call-site bracket literal [a, b, ...] is
|
||
* boxed into an array of TVarRec (each a VType discriminant + a pointer-sized
|
||
* VValue) and passed via the same open-array ABI. The callee reads each
|
||
* element by switching on VType. See language-rationale.adoc.
|
||
*
|
||
* A DefaultValue is permitted only on a single-name ParamGroup that is not
|
||
* VAR/OUT and not an open-array. A call site may omit any trailing
|
||
* argument whose corresponding ParamGroup carries a DefaultValue; the
|
||
* compiler fills the missing arguments with a clone of the default
|
||
* expression. Where two overloads resolve equally, the candidate
|
||
* requiring fewer defaulted slots wins (defaulting penalty in
|
||
* ResolveStandaloneOverload / ResolveMethodOverload). *)
|
||
|
||
TypeParamList
|
||
= IDENT { COMMA IDENT }
|
||
;
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* CONSTANT SECTION
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
ConstSection
|
||
= CONST ConstDecl { ConstDecl }
|
||
;
|
||
|
||
ConstDecl
|
||
= IDENT [ COLON ConstTypeAnnotation ] EQUALS ConstRhs SEMICOLON
|
||
;
|
||
|
||
ConstTypeAnnotation
|
||
= TypeName (* scalar typed const *)
|
||
| ARRAY LBRACKET TypeName RBRACKET OF TypeName (* array-of-enum const *)
|
||
| ConstArrayType (* range-indexed array const *)
|
||
;
|
||
|
||
(* Range-indexed array const types, single- or multi-dimensional. The comma
|
||
* form (array[L0..H0, L1..H1, ...]) and the nested form (array[L0..H0] of
|
||
* array[L1..H1] of ...) are equivalent and may be mixed; both desugar to a
|
||
* nested static-array type. Enum-indexed const arrays remain single-dimension
|
||
* (see the ARRAY LBRACKET TypeName ... alternative above). *)
|
||
ConstArrayType
|
||
= ARRAY LBRACKET ConstArrayRange { COMMA ConstArrayRange } RBRACKET
|
||
OF ( ConstArrayType | TypeName )
|
||
;
|
||
|
||
ConstArrayRange
|
||
= ArrayBound DOTDOT ArrayBound
|
||
;
|
||
|
||
ConstRhs
|
||
= ConstExpr (* scalar value *)
|
||
| ConstArrayValue (* array value list *)
|
||
| LBRACKET [ IDENT { COMMA IDENT } ] RBRACKET (* set value: [a, b] or [] *)
|
||
;
|
||
|
||
(* Array value list. A multi-dimensional const nests one parenthesised group
|
||
* per dimension; values are stored flattened in row-major order. *)
|
||
ConstArrayValue
|
||
= LPAREN ( ConstArrayValue | ConstExpr )
|
||
{ COMMA ( ConstArrayValue | ConstExpr ) } RPAREN
|
||
;
|
||
|
||
(* A constant value position. A scalar const may be a string/float literal, a
|
||
* compile-time integer expression, or a compile-time float expression.
|
||
*
|
||
* INTEGER expressions: integer literals, named integer-constant references,
|
||
* binary operators ( + - * div mod and or xor shl shr ), unary minus, and
|
||
* parentheses, with the usual operator precedence.
|
||
*
|
||
* FLOAT expressions: any expression that contains at least one float literal,
|
||
* a named float constant, or uses the '/' operator (which always yields a
|
||
* float result, even with integer operands — matching Delphi/FPC semantics).
|
||
* Supports the arithmetic operators + - * / div, unary minus, named-constant
|
||
* references, and parentheses.
|
||
*
|
||
* Both integer and float expressions are folded to a single value by the
|
||
* semantic pass, which resolves named-constant references. *)
|
||
ConstExpr
|
||
= ConstArithExpr
|
||
| StrLit { PLUS StrLit } (* string constant, with concatenation *)
|
||
| IDENT (* named constant reference, e.g. True / False *)
|
||
;
|
||
|
||
(* Arithmetic constant expression — covers both integer and float. The
|
||
* semantic pass inspects the AST: if any leaf is a float literal, a named
|
||
* float constant, or the '/' operator is used, the expression is folded as
|
||
* a float; otherwise it is folded as an integer. *)
|
||
ConstArithExpr
|
||
= ConstArithTerm { ( PLUS | MINUS | OR | XOR ) ConstArithTerm }
|
||
;
|
||
|
||
ConstArithTerm
|
||
= ConstArithFactor { ( STAR | SLASH | DIV | MOD | AND | SHL | SHR ) ConstArithFactor }
|
||
;
|
||
|
||
ConstArithFactor
|
||
= IntLit
|
||
| FloatLit
|
||
| IDENT (* named integer or float constant *)
|
||
| MINUS ConstArithFactor (* unary minus *)
|
||
| LPAREN ConstArithExpr RPAREN
|
||
;
|
||
|
||
(* ConstDecl supports untyped, scalar-typed, array-typed, and set-typed forms:
|
||
* Untyped: const MaxItems = 100;
|
||
* Scalar-typed: const Pi: Double = 3.14159;
|
||
* Array-typed: const Names: array[TWeather] of string = ('Sunny', 'Cloudy', 'Rainy');
|
||
* const Days: array[0..6] of string = ('Sun', 'Mon', ...);
|
||
* Multi-dim: const M: array[0..1, 0..2] of Integer = ((1,2,3), (4,5,6));
|
||
* const N: array[0..1] of array[0..1] of Integer = ((1,2), (3,4));
|
||
* Set-typed: const Primary = [cRed, cBlue]; (* type inferred *)
|
||
* const Both: TDirSet = [dNorth, dEast]; (* type annotated *)
|
||
* const None: TDirSet = []; (* empty set *)
|
||
*
|
||
* A set-valued const lists enum-constant members between brackets. Members
|
||
* must all belong to one enum; the const's type is that enum's set type —
|
||
* either the annotated set type or the inferred 'set of <Enum>'. The empty
|
||
* set [] requires a set-type annotation (there is no member to infer from).
|
||
* The bitmask is folded at compile time and the const is emitted like any
|
||
* integer set value.
|
||
*
|
||
* Array-typed constants accept either an enum type name or an integer
|
||
* range as the index. The element count in the value list must exactly
|
||
* match the number of index positions (enum member count or High-Low+1).
|
||
* Array consts are emitted as pre-initialised global data sections.
|
||
*
|
||
* ConstSection may appear in: program/unit Block, InterfaceSection,
|
||
* ImplementationSection, standalone procedure/function/method bodies,
|
||
* and class declaration bodies (class-level constants). *)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* VARIABLE SECTION
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
VarSection
|
||
= VAR VarDecl { VarDecl }
|
||
;
|
||
|
||
ThreadVarSection
|
||
= THREADVAR VarDecl { VarDecl }
|
||
;
|
||
|
||
(* ThreadVarSection declares thread-local storage variables. Only allowed
|
||
* at program/unit scope (not inside procedures). Each thread gets its own
|
||
* copy of the variable, zero-initialised on thread creation. Syntactically
|
||
* identical to VarSection except for the keyword. *)
|
||
|
||
(* A variable declaration may carry an initialiser ( = ConstValue ). The
|
||
* initialiser uses the same value grammar as a typed constant (scalar,
|
||
* string, set, or parenthesised aggregate list) and is folded at compile
|
||
* time, then emitted into the data section. An initialised declaration may
|
||
* declare only a single name (IdentList must be one IDENT). *)
|
||
VarDecl
|
||
= IdentList COLON TypeName [ EQUALS ConstValue ] SEMICOLON
|
||
;
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* SHARED HELPERS
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
IdentList
|
||
= IDENT { COMMA IDENT }
|
||
;
|
||
|
||
TypeName
|
||
= ArrayType (* static array: array[L..H] of T *)
|
||
| DynArrayType (* dynamic array: array of T *)
|
||
| SetType (* bit-set over an enum base type *)
|
||
| CARET TypeName (* pointer-to type: ^Integer, ^T, ^^T *)
|
||
| QualIdent LESS TypeArgList GREATER (* generic instantiation: TList<Integer> *)
|
||
| QualIdent (* simple type, built-in, or UnitName.TypeName *)
|
||
;
|
||
|
||
(* A type may be written with a unit qualifier, e.g. SysUtils.TFormatSettings,
|
||
* and the qualifier may itself be dotted (System.SysUtils.TFormatSettings).
|
||
* The parser reads the full dotted path; a type is always named by its FINAL
|
||
* component, with the leading components naming a unit already brought in by
|
||
* the uses clause. See language-rationale.adoc "Qualified type names". *)
|
||
QualIdent
|
||
= IDENT { DOT IDENT }
|
||
;
|
||
|
||
(* A static array may declare multiple dimensions either as a comma-separated
|
||
* index list (ARRAY [L1..H1, L2..H2] OF T) or, equivalently, as nested
|
||
* single-dimension arrays (ARRAY [L1..H1] OF ARRAY [L2..H2] OF T). The
|
||
* comma form is desugared by the parser into the nested form, so the two
|
||
* are interchangeable.
|
||
*
|
||
* Each bound is a ConstArithExpr — an integer literal, a named integer
|
||
* constant, or a compile-time integer expression (e.g. N-1, 2*K). The
|
||
* semantic pass resolves the bound text to an integer value. *)
|
||
ArrayType
|
||
= ARRAY LBRACKET ArrayBound DOTDOT ArrayBound
|
||
{ COMMA ArrayBound DOTDOT ArrayBound } RBRACKET OF TypeName
|
||
| ARRAY LBRACKET IDENT RBRACKET OF TypeName (* ordinal/enum-indexed *)
|
||
;
|
||
|
||
ArrayBound
|
||
= ConstArithExpr
|
||
;
|
||
|
||
DynArrayType
|
||
= ARRAY OF TypeName
|
||
;
|
||
|
||
EnumDef
|
||
= LPAREN EnumMember { COMMA EnumMember } RPAREN
|
||
;
|
||
|
||
EnumMember
|
||
= IDENT [ EQUALS [ MINUS ] IntLit ]
|
||
;
|
||
|
||
(* When EQUALS is absent, the member's ordinal is the previous member's
|
||
* ordinal plus one (or zero for the first member). When EQUALS is present,
|
||
* the specified integer (optionally negated by MINUS) becomes the ordinal;
|
||
* subsequent members without EQUALS continue incrementing from that value.
|
||
* This matches Delphi and FPC semantics. *)
|
||
|
||
(* A set's element type is either an enumeration (named or anonymous inline)
|
||
* or a small ordinal type (Byte, Boolean). Inline set types are accepted
|
||
* anywhere a TypeName appears (var, parameter, record field, …), not only
|
||
* in a named type declaration — e.g. var S: set of TColour;
|
||
* var T: set of (a, b, c); var F: set of Byte;
|
||
* The anonymous enum form synthesises an enum type whose members become
|
||
* ordinary enum constants in scope. *)
|
||
SetType
|
||
= SET OF ( IDENT | EnumDef | Subrange )
|
||
;
|
||
|
||
Subrange
|
||
= ConstExpr DOTDOT ConstExpr
|
||
;
|
||
|
||
(* The base type must have at most 256 ordinal values. Storage scales with
|
||
* the value count:
|
||
* <= 32 values : 4 bytes, a QBE 'w' register bitmask
|
||
* <= 64 values : 8 bytes, a QBE 'l' register bitmask
|
||
* <= 256 values : an inline byte-array bitmap of ceil(N/8) bytes (a "jumbo"
|
||
* set), operated on via the _Set* RTL helpers like an
|
||
* aggregate (record/static array)
|
||
* Value ordinal N maps to bit (N mod 8) of byte (N div 8) in all forms.
|
||
* Types with more than 256 possible values (e.g. Integer, Word) are rejected
|
||
* by the semantic analyser — only Byte (256 values), Boolean (2 values),
|
||
* enumerations with at most 256 members, and integer subranges within
|
||
* 0..255 are accepted. The subrange form `set of L..H` (e.g. `set of 0..255`,
|
||
* `set of 1..10`) sizes the bitmap to H+1 bits; both bounds may be integer
|
||
* literals, named constants, or constant expressions. L must be >= 0, H must
|
||
* be <= 255, and the range must be ascending (H >= L) — a descending or
|
||
* out-of-range subrange is a semantic error. Set literals with ordinal base
|
||
* types accept integer literals and ranges: [1, 2, 10..20]. *)
|
||
|
||
(* Procedural type — bare or method pointer.
|
||
*
|
||
* Without 'of object', the type is a bare function/procedure pointer
|
||
* stored as a single 8-byte code pointer (QBE 'l'). Sufficient for
|
||
* callback APIs such as the punit test framework's TTestRun and
|
||
* TTestSetup hooks.
|
||
*
|
||
* With 'of object', the type is a method pointer carrying both a code
|
||
* pointer and a data pointer (Self). Storage is a 16-byte block:
|
||
* Code at offset 0, Data at offset 8. This layout is byte-for-byte
|
||
* identical to the intrinsic TMethod record, so the cast TMyMethod(m)
|
||
* (m: TMethod) is a no-op at the QBE level; the call site loads both
|
||
* halves and emits 'call code(l data, args...)' — Data is passed as
|
||
* the implicit first argument so the callee sees it as Self.
|
||
*
|
||
* Used by xUnit-style frameworks (fpcunit, fptest) for test-method
|
||
* dispatch:
|
||
* type
|
||
* TRunMethod = procedure of object;
|
||
* m.Code := MethodAddress(Self, FName);
|
||
* m.Data := Self;
|
||
* RunMethod := TRunMethod(m);
|
||
* RunMethod; (* invokes Self.<FName> *)
|
||
*
|
||
* 'reference to procedure' (anonymous method / closure) is NOT
|
||
* supported — closures need an environment record, which adds a
|
||
* lifetime story we have deliberately deferred.
|
||
*)
|
||
ProceduralType
|
||
= ( FUNCTION [ LPAREN ParamList RPAREN ] COLON TypeName [ OF "object" ] )
|
||
| ( PROCEDURE [ LPAREN ParamList RPAREN ] [ OF "object" ] )
|
||
;
|
||
|
||
(* Set literals use the same bracket notation as array literals.
|
||
The semantic pass distinguishes them by assignment-target type context:
|
||
when the LHS is a set type, LBRACKET ExprList RBRACKET is a set literal;
|
||
otherwise it is an array literal. An empty LBRACKET RBRACKET is valid
|
||
only as a set literal (the empty set).
|
||
|
||
A set element may be a single value or a range `lo .. hi` (issue #105).
|
||
Both range bounds must be compile-time constants of the set's base type;
|
||
the semantic pass expands the range into its individual members and
|
||
rejects a reversed range (low ordinal > high ordinal). The range form is
|
||
meaningful only in set context — in array-literal context DOTDOT is a
|
||
syntax error. *)
|
||
SetLiteral
|
||
= LBRACKET [ SetElement { COMMA SetElement } ] RBRACKET
|
||
;
|
||
|
||
SetElement
|
||
= Expr [ DOTDOT Expr ]
|
||
;
|
||
|
||
(* Each type argument is itself a (possibly-generic) type name, so type
|
||
* arguments nest: TList<TList<Integer>>, TBox<TPair<Integer, string>>. *)
|
||
TypeArgList
|
||
= GenericName { COMMA GenericName }
|
||
;
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* STATEMENTS
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
StmtList
|
||
= { Stmt SEMICOLON } [ Stmt ]
|
||
;
|
||
|
||
Stmt
|
||
= IfStmt
|
||
| WhileStmt
|
||
| ForStmt
|
||
| TryFinallyStmt
|
||
| TryExceptStmt
|
||
| RaiseStmt
|
||
| ExitStmt
|
||
| CompoundStmt
|
||
| PointerWriteStmt
|
||
| StaticSubscriptAssign
|
||
| SubscriptFieldAssign
|
||
| SubscriptMethodCall
|
||
| FieldAssignment
|
||
| MethodCall
|
||
| Assignment
|
||
| ProcCall
|
||
| (* empty *)
|
||
;
|
||
|
||
(* A multi-dimensional element write addresses nested static arrays. Both the
|
||
* comma form a[i, j] := v and the chained form a[i][j] := v are accepted and
|
||
* lower to the same element write through the inner-array address.
|
||
*
|
||
* The same single-subscript form also covers an indexed write into a string
|
||
* (S[I] := <byte>), a PChar (P[I] := <byte>), a dynamic array, and a default
|
||
* indexed property. The base kind is resolved by the semantic pass, not the
|
||
* grammar. For a string the write is copy-on-write (the buffer is made
|
||
* uniquely owned before the byte store) — see the "String Subscript S[N]"
|
||
* section of language-rationale.adoc. *)
|
||
StaticSubscriptAssign
|
||
= IDENT LBRACKET Expr { COMMA Expr } RBRACKET
|
||
{ LBRACKET Expr { COMMA Expr } RBRACKET } ASSIGN Expr
|
||
;
|
||
|
||
(* A statement starting with IDENT LBRACKET Expr RBRACKET that is NOT
|
||
* directly followed by ASSIGN continues as a postfix chain on the element:
|
||
* field accesses and further subscripts, terminated by either a field
|
||
* assignment (SubscriptFieldAssign) or a method call on the element
|
||
* (SubscriptMethodCall). Examples:
|
||
* a[0].Name := 'x'; a[i].Inner.N := 5;
|
||
* a[0].Bump; a[i].Items.Add(x);
|
||
* a[i].Arr[j] := v; — element write into an array-typed field
|
||
* A terminating subscript directly over another subscript (a[i][j] := v, or
|
||
* the comma form a[i, j] := v) is a multi-dimensional element write — see
|
||
* StaticSubscriptAssign. *)
|
||
SubscriptFieldAssign
|
||
= IDENT LBRACKET Expr RBRACKET { SubscriptSuffix }
|
||
DOT IDENT [ LBRACKET Expr RBRACKET ] ASSIGN Expr
|
||
;
|
||
|
||
SubscriptMethodCall
|
||
= IDENT LBRACKET Expr RBRACKET { SubscriptSuffix }
|
||
DOT IDENT [ LPAREN [ ArgList ] RPAREN ]
|
||
;
|
||
|
||
(* A subscript suffix indexes one or more dimensions. The comma form
|
||
* [i, j] is desugared into chained single-index subscripts [i][j], so a
|
||
* multi-dimensional read a[i, j] and a[i][j] are equivalent. *)
|
||
SubscriptSuffix
|
||
= DOT IDENT
|
||
| LBRACKET Expr { COMMA Expr } RBRACKET
|
||
;
|
||
|
||
IfStmt
|
||
= IF Expr THEN Stmt [ ELSE Stmt ]
|
||
;
|
||
|
||
WhileStmt
|
||
= WHILE Expr DO Stmt
|
||
;
|
||
|
||
ForStmt
|
||
= FOR IDENT ASSIGN Expr ( TO | DOWNTO ) Expr DO Stmt
|
||
| FOR IDENT IN Expr DO Stmt
|
||
;
|
||
|
||
(* FOR IDENT IN Expr is an enhanced for loop. The element type depends on
|
||
* the collection expression:
|
||
* - Static array (array[L..H] of T): IDENT must be compatible with T.
|
||
* - Class with GetEnumerator protocol: IDENT must be compatible with
|
||
* the Current property type of the enumerator.
|
||
* - String: IDENT type selects iteration mode:
|
||
* * Byte — iterates raw UTF-8 bytes (one byte per iteration).
|
||
* * Integer — iterates Unicode codepoints (one codepoint per
|
||
* iteration, advancing 1–4 bytes via _Utf8DecodeAt).
|
||
* * Any other type — compile-time error.
|
||
* There is no Char type in Blaise.
|
||
* - Set (set of TEnum): IDENT must be an ordinal type; each iteration
|
||
* yields one enum member whose bit is set, in ascending ordinal order.
|
||
* The set expression is evaluated once before iteration begins. *)
|
||
|
||
TryFinallyStmt
|
||
= TRY StmtList FINALLY StmtList END
|
||
;
|
||
|
||
TryExceptStmt
|
||
= TRY StmtList EXCEPT ExceptBody END
|
||
;
|
||
|
||
ExceptBody
|
||
= OnClause { ";" OnClause } [ ";" ] [ ELSE StmtList ]
|
||
| StmtList
|
||
;
|
||
|
||
OnClause
|
||
= "on" [ IDENT ":" ] TypeRef DO Stmt
|
||
;
|
||
|
||
RaiseStmt
|
||
= RAISE [ Expr ]
|
||
;
|
||
|
||
(* Exit returns from the current routine. The optional Exit(X) form is the
|
||
* function-result shorthand: it assigns X to Result, then returns. X must be
|
||
* assignment-compatible with the return type; Exit(X) is only valid inside a
|
||
* function (a procedure has no Result). *)
|
||
ExitStmt
|
||
= EXIT [ LPAREN Expr RPAREN ]
|
||
;
|
||
|
||
CompoundStmt
|
||
= BEGIN StmtList END
|
||
;
|
||
|
||
(* Pointer write: P^ := Expr — store through pointer *)
|
||
PointerWriteStmt
|
||
= IDENT CARET ASSIGN Expr
|
||
;
|
||
|
||
FieldAssignment
|
||
= IDENT DOT IDENT { Selector } ASSIGN Expr
|
||
| LPAREN Expr RPAREN DOT IDENT { Selector } ASSIGN Expr (* parenthesised lvalue, e.g. (a as TB).FX := v *)
|
||
;
|
||
|
||
(* A Selector continues an l-value path after the first member: a further
|
||
* .Field, an [Index] subscript (indexed property or array-typed field
|
||
* element), or a .Method(...) call whose result is itself selected into.
|
||
* This covers chains such as Self.FStack[0].Count := value and
|
||
* r.A[0] := 10 — the subscript-then-field chain the parser added in
|
||
* baf5079. See language-rationale.adoc "Chained l-value assignment". *)
|
||
Selector
|
||
= DOT IDENT
|
||
| LBRACKET Expr RBRACKET
|
||
| DOT IDENT LPAREN [ ArgList ] RPAREN
|
||
;
|
||
|
||
MethodCall
|
||
= IDENT DOT IDENT
|
||
[ LESS TypeArgList GREATER ] (* explicit args for a generic method *)
|
||
LPAREN [ ArgList ] RPAREN
|
||
;
|
||
|
||
(* Generic method call: Obj.Method<Integer>(args). The optional
|
||
* LESS TypeArgList GREATER supplies explicit type arguments for a method that
|
||
* declares its own type parameters (MethodDecl's [ LESS TypeParamList GREATER ]).
|
||
* The parser folds the type args into the method name (Method<Integer>) and the
|
||
* semantic pass monomorphises the method on first use. The leading-'<' is
|
||
* disambiguated from a comparison by the same two-token lookahead used for
|
||
* generic free-function calls. Both the inline-body method form and the
|
||
* out-of-line impl function TOwner.Method<T>(...) are supported. *)
|
||
|
||
(* Semantic disambiguation of IDENT DOT IDENT in Stmt:
|
||
* - Followed by ASSIGN or LBRACKET ASSIGN → FieldAssignment (plain or indexed)
|
||
* - Otherwise → MethodCall
|
||
*
|
||
* The LBRACKET Expr RBRACKET ASSIGN form covers two cases, disambiguated by
|
||
* the semantic pass: when the member is an indexed property the subscript is
|
||
* the property index (setter call); when the member is an ARRAY-TYPED FIELD
|
||
* (dynamic or static) the subscript selects the element to assign:
|
||
* r.A[0] := 10; c.Buf[i] := b; Self.A[1] := x;
|
||
* The same applies after a chain (c.N.A[0] := 7 — see MethodCall chains) and
|
||
* to a bare implicit-Self field inside a method (A[0] := v parses as
|
||
* StaticSubscriptAssign and resolves to the Self field).
|
||
*
|
||
* When IDENT DOT IDENT is followed by LBRACKET in a READ position, the parser
|
||
* checks whether the member is an indexed property (symbol table lookup). If
|
||
* it is, LBRACKET Expr RBRACKET is consumed as the index; the result is an
|
||
* indexed property access. *)
|
||
|
||
Assignment
|
||
= IDENT ASSIGN Expr
|
||
;
|
||
|
||
ProcCall
|
||
= IDENT LPAREN [ ArgList ] RPAREN
|
||
;
|
||
|
||
ArgList
|
||
= Expr { COMMA Expr }
|
||
;
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* EXPRESSIONS
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
Expr
|
||
= Comparison [ ( IS | AS ) IDENT ]
|
||
;
|
||
|
||
(* IS and AS have the lowest precedence. IDENT is the target type name. *)
|
||
|
||
Comparison
|
||
= Additive [ ( EQUALS | NOT_EQUALS | LESS | GREATER | LESS_EQ | GREATER_EQ ) Additive ]
|
||
;
|
||
|
||
Additive
|
||
= Term { ( PLUS | MINUS ) Term }
|
||
;
|
||
|
||
Term
|
||
= Factor { ( STAR | SLASH | DIV | MOD | AND | XOR | SHL | SHR | SAR ) Factor }
|
||
;
|
||
|
||
Factor
|
||
= INT_LIT
|
||
| STRING_LIT
|
||
| TEXT_BLOCK_LIT
|
||
| NIL
|
||
| ArrayLiteral
|
||
| AddrOf
|
||
| NOT Factor (* Boolean or bitwise NOT *)
|
||
| INHERITED IDENT [ LPAREN [ ArgList ] RPAREN ] (* inherited function call in expr position *)
|
||
| "Supports" LPAREN Expr COMMA IDENT [ COMMA IDENT ] RPAREN (* compiler intrinsic *)
|
||
| IDENT DOT IDENT [ LBRACKET Expr RBRACKET ] (* indexed property read *)
|
||
LPAREN [ ArgList ] RPAREN (* constructor/method call *)
|
||
| IDENT DOT IDENT (* field read *)
|
||
| IDENT LESS TypeArgList GREATER LPAREN [ ArgList ] RPAREN (* generic func call *)
|
||
| IDENT LPAREN [ ArgList ] RPAREN (* func call, type cast *)
|
||
| IDENT (* variable read *)
|
||
| LPAREN Expr RPAREN
|
||
;
|
||
|
||
AddrOf
|
||
= AT Factor
|
||
;
|
||
|
||
ArrayLiteral
|
||
= LBRACKET Expr { COMMA Expr } RBRACKET
|
||
;
|
||
|
||
(* ArrayLiteral is a stack-allocated temporary array passed to open-array formals.
|
||
* The element type is inferred from the first element; all elements must match.
|
||
* Empty literals [] are a semantic error (element type cannot be inferred).
|
||
* The postfix subscript Expr[N] is not available on an ArrayLiteral directly. *)
|
||
|
||
(* Pointer dereference P^ is a postfix on any Factor result:
|
||
*
|
||
* Deref
|
||
* = Factor CARET
|
||
* ;
|
||
*
|
||
* For simplicity this is shown as a postfix suffix on Factor above rather
|
||
* than a separate rule; the parser handles it by wrapping the parsed Factor
|
||
* in a TDerefExpr node when CARET follows.
|
||
*
|
||
* Type cast: TypeName LPAREN Expr RPAREN — parsed as an IDENT followed by
|
||
* LPAREN, identical to a function call. The semantic pass distinguishes casts
|
||
* from calls by checking whether the symbol resolves to a type or a function.
|
||
*)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* BUILT-IN PROCEDURES, FUNCTIONS, AND TYPES
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
(*
|
||
* Built-in procedures (recognised by the code generator, not the parser):
|
||
*
|
||
* WriteLn ( [ Expr ] ) — print with trailing newline; calls libc printf
|
||
* Write ( [ Expr ] ) — print without newline
|
||
* FreeMem ( Pointer ) — calls libc free()
|
||
*
|
||
* Built-in functions (recognised by the code generator):
|
||
*
|
||
* Supports ( Obj, IntfType ) : Boolean
|
||
* — returns True if Obj's class implements IntfType.
|
||
* Obj must be a class or interface reference;
|
||
* IntfType is a type name (not a runtime value).
|
||
* Supports ( Obj, IntfType, out OutVar ) : Boolean
|
||
* — same as above; on success also assigns Obj and its
|
||
* itab for IntfType to OutVar's fat-pointer slots (ARC).
|
||
* OutVar must be a variable of an interface type.
|
||
*
|
||
* GetMem ( Integer ) : Pointer — calls libc malloc()
|
||
* ReallocMem( Pointer, Integer ) : Pointer — calls libc realloc()
|
||
* MethodAddress( Obj, Name ) : Pointer — published-method lookup
|
||
* ClassCreate ( Cls, ...args ) : Cls.BaseClass
|
||
* — runtime construction from a metaclass
|
||
* value; lowers to _ClassCreate(Cls)
|
||
* followed by a vtable-indirect call to
|
||
* the constructor on Cls.BaseClass.
|
||
* Low ( Ordinal | Array | String ) : T — for ordinal type/expression
|
||
* returns lower bound of T;
|
||
* 0 for open/dynamic arrays and
|
||
* strings; LowBound for static
|
||
* arrays. Float types: error.
|
||
* High ( Ordinal | Array | String ) : T — for ordinal type/expression
|
||
* returns upper bound of T;
|
||
* HighBound for static arrays;
|
||
* high-index slot for open arrays;
|
||
* Length(S)-1 for strings (runtime).
|
||
* Float types: error.
|
||
*
|
||
* Built-in types (pre-populated in the symbol table):
|
||
*
|
||
* Integer — 32-bit signed integer (QBE type: w)
|
||
* Int64 — 64-bit signed integer (QBE type: l)
|
||
* UInt32 — 32-bit unsigned integer (QBE type: w)
|
||
* Alias: Cardinal
|
||
* UInt64 — 64-bit unsigned integer (QBE type: l)
|
||
* Aliases: QWord, PtrUInt (the pointer-sized unsigned)
|
||
* SmallInt — 16-bit signed integer (QBE type: w, stored as h)
|
||
* Alias: Int16
|
||
* Word — 16-bit unsigned integer (QBE type: w, stored as h)
|
||
* Alias: UInt16
|
||
* Boolean — 1-byte boolean (QBE type: w; 0 = false, 1 = true)
|
||
* Byte — 8-bit unsigned integer (QBE type: w, stored as b)
|
||
* String — ARC-managed UTF-8 string pointer (QBE type: l)
|
||
* Pointer — untyped pointer (QBE type: l)
|
||
* ^T — typed pointer to T; created on demand (QBE type: l)
|
||
*
|
||
* Integer literal typing rules (untyped constant promotion):
|
||
*
|
||
* - Decimal/hex/oct/bin literal whose value fits in [MinInt32..MaxInt32]
|
||
* resolves to Integer.
|
||
* - Literal in [MinInt64..MaxInt64] but outside Int32 range resolves
|
||
* to Int64.
|
||
* - Literal in (MaxInt64..MaxUInt64] resolves to UInt64. The bit
|
||
* pattern is preserved; signedness is selected by the type.
|
||
* - Mixing Int64 and UInt64 in an arithmetic or comparison expression
|
||
* requires an explicit cast (UInt64(x) / Int64(x)). All other
|
||
* integer types widen implicitly into both.
|
||
*)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* SEMANTIC DISAMBIGUATION NOTES
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
(*
|
||
* IDENT LPAREN ArgList RPAREN in Factor:
|
||
* - Symbol resolves to a function/procedure → function call
|
||
* - Symbol resolves to a type → type cast (e.g. Integer(x), Pointer(p))
|
||
*
|
||
* IDENT DOT IDENT in Factor:
|
||
* - Left IDENT resolves to a type symbol → constructor call (TypeName.Create)
|
||
* - Left IDENT resolves to a metaclass variable and right IDENT is a
|
||
* constructor name → metaclass constructor dispatch (_ClassCreate +
|
||
* vtable-indirect call to the most-derived Create body)
|
||
* - Left IDENT resolves to a class variable → class field read
|
||
* - Left IDENT resolves to a record variable → record field read
|
||
*
|
||
* IDENT DOT IDENT in Stmt:
|
||
* - Followed by ASSIGN → FieldAssignment (record/class field write)
|
||
* - Otherwise → MethodCall
|
||
*
|
||
* Generic instantiation vs. comparison:
|
||
* IDENT LESS ... is disambiguated by two-token lookahead. LESS is treated
|
||
* as opening a type-arg list only when both: (a) the token after LESS is an
|
||
* IDENT, and (b) the token after that is GREATER or COMMA. Otherwise LESS
|
||
* is a comparison operator (e.g. "if A < B then").
|
||
*
|
||
* Diamond operator (type-argument inference):
|
||
* IDENT NOTEQUALS DOT ... is parsed as IDENT '<>' DOT — the lexer folds '<>'
|
||
* into a single tkNotEquals token. When '<>' appears as generic type args in
|
||
* an assignment RHS constructor call (TFoo<>.Create), the semantic pass infers
|
||
* all type arguments from the declared type of the LHS variable.
|
||
* Only valid in assignment context; the LHS must be a concrete generic
|
||
* instantiation whose base name matches the constructor type name.
|
||
* Examples:
|
||
* var S: TStack<string>; S := TStack<>.Create;
|
||
* var D: TDictionary<string,Integer>; D := TDictionary<>.Create;
|
||
*
|
||
* Virtual dispatch vs. static call:
|
||
* Determined at semantic time from the VTableSlot field on the resolved
|
||
* method declaration. Grammar is identical for both cases.
|
||
*
|
||
* Interface method call:
|
||
* IDENT DOT IDENT where the left IDENT resolves to an interface variable.
|
||
* Generates an indirect call through the itab pointer at the appropriate slot.
|
||
*)
|
||
|
||
|
||
(* ═══════════════════════════════════════════════════════════════════════════
|
||
* ARC IMPLICIT OPERATIONS
|
||
* ═══════════════════════════════════════════════════════════════════════════ *)
|
||
|
||
(*
|
||
* The compiler inserts reference-counting calls automatically.
|
||
* These are not visible in source syntax but are part of the language semantics.
|
||
*
|
||
* String assignment:
|
||
* _StringAddRef ( new_value )
|
||
* _StringRelease ( old_value )
|
||
* store new_value → target
|
||
*
|
||
* Block exit — for every String variable in scope:
|
||
* _StringRelease ( variable )
|
||
*
|
||
* Exception path (try/finally, try/except):
|
||
* _StringRelease called and slot zeroed on exception unwind.
|
||
*
|
||
* String literal layout: { w -1, w len, w cap, b "data", b 0 }
|
||
* refcount = -1 → immortal (never released); char data starts at ptr+12.
|
||
*)
|