feat(const): compile-time integer constant expressions (#96)
A const declaration may now take a compile-time integer expression on the
right-hand side, not just a single literal:
const A = 2 * 3; // 6
B = 2 + 3 * 4; // 14 (precedence)
C = (2 + 3) * 4; // 20 (parentheses)
D = 100 div 7; // 14
E = Base * 2 + 1; // references a prior const
Previously only single literals and a flat, single-precedence bit-op chain
(a or b or c) were accepted; 2 * 3 failed with "Expected ';' but got '*'"
and (2 * 3) with "Expected numeric or string constant".
Parser: when the const RHS starts an integer expression (a leading '(', an
integer literal or leading-minus literal followed by a binary operator, or an
ident followed by a numeric operator), parse it with the existing
full-precedence ParseExpr into a normal expression AST stored on
TConstDecl.IntValueExpr. A bare literal keeps the fast IntVal path; '+' on an
ident is left to the string-concat path for backward compatibility.
Semantic: EvalConstIntExpr recursively folds the AST to an Int64, resolving
named-constant references against the symbol table. Folding in the semantic
pass (after all consts are registered) is what makes forward references work
regardless of declaration order. Wired into both the const-block and the
initialised-variable (var G = expr) fold sites. Operator precedence and
grouping come from the AST shape, superseding the precedence-unaware bit-op
token chain.
Scope: folds to integer. Float constant arithmetic and using a const as a
static-array bound remain separate, pre-existing limitations (noted in the
rationale).
Tests: eight IR/fold cases in cp.test.constants (multiply, parens, precedence,
div/mod, named-ref, unary minus, mixed arith+bitwise); one e2e case on both
backends in cp.test.e2e.misc. Docs: grammar.ebnf gains the ConstIntExpr rules;
language-rationale gains a Constant Expressions section.
This commit is contained in:
parent
9a511a22e2
commit
2708a6209e
|
|
@ -563,6 +563,15 @@ type
|
|||
operator names (one of 'or'/'and'/'xor'/'shl'/'shr'). Semantic
|
||||
resolves idents and folds to CD.IntVal. }
|
||||
IntExprTokens: TStringList;
|
||||
{ Integer constant expression in the value position — non-nil when the RHS
|
||||
is a compile-time arithmetic/bitwise expression with operator precedence
|
||||
and/or parentheses (e.g. '2 + 3 * 4', '(1 shl 8) or 7', 'BASE * 2').
|
||||
Stored as a normal expression AST so precedence and grouping are encoded
|
||||
structurally; the semantic pass folds it to CD.IntVal via EvalConstIntExpr
|
||||
(resolving any named-constant references after all consts are declared).
|
||||
Supersedes the flat IntExprTokens form for newly-parsed expressions; the
|
||||
token form is retained only for the legacy single-precedence bit-op path. }
|
||||
IntValueExpr: TASTExpr;
|
||||
{ Parallel to ArrayElements — each non-nil entry is an IntExprTokens-
|
||||
shaped TStringList describing the bit-op expression for that
|
||||
element. Semantic folds it and overwrites ArrayElements[i] with
|
||||
|
|
@ -2153,6 +2162,8 @@ begin
|
|||
Result.IntExprTokens.AddObject(
|
||||
ASrc.IntExprTokens.Strings[I], ASrc.IntExprTokens.Objects[I]);
|
||||
end;
|
||||
if ASrc.IntValueExpr <> nil then
|
||||
Result.IntValueExpr := CloneExpr(ASrc.IntValueExpr);
|
||||
end;
|
||||
|
||||
function CloneMethodParam(ASrc: TMethodParam): TMethodParam;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ type
|
|||
procedure ParseConstArrayGroup(CD: TConstDecl);
|
||||
procedure ParseConstValue(CD: TConstDecl);
|
||||
function TryParseConstIntTypecast(out AValue: Int64): Boolean;
|
||||
function IsConstExprOp(AKind: TTokenKind): Boolean;
|
||||
function ConstRhsStartsIntExpr: Boolean;
|
||||
function CurrentIsConstBitOp: Boolean;
|
||||
function ConsumeConstBitOpName: string;
|
||||
function CollectConstBitOpExpr(const AFirstStr: string;
|
||||
|
|
@ -761,6 +763,42 @@ end;
|
|||
const initialiser: 'or', 'and', 'xor', 'shl', 'shr'. Used to detect
|
||||
the start of a deferred bit-op expression chain after the first
|
||||
operand has been parsed. }
|
||||
{ True when the const RHS is a multi-token integer expression that must be
|
||||
parsed with full operator precedence (via ParseExpr) rather than the
|
||||
single-literal / single-precedence bit-op fast paths. Recognises:
|
||||
- a leading '(' (always an arithmetic grouping in const position),
|
||||
- an integer literal followed by any binary operator,
|
||||
- a leading minus + integer literal followed by any binary operator.
|
||||
A lone integer literal (no trailing operator) is deliberately NOT matched,
|
||||
so the simple 'const X = 5;' fast path is preserved. }
|
||||
function TParser.IsConstExprOp(AKind: TTokenKind): Boolean;
|
||||
begin
|
||||
Result := (AKind = tkPlus) or (AKind = tkMinus) or (AKind = tkStar)
|
||||
or (AKind = tkDiv) or (AKind = tkMod)
|
||||
or (AKind = tkOr) or (AKind = tkAnd) or (AKind = tkXor)
|
||||
or (AKind = tkShl) or (AKind = tkShr);
|
||||
end;
|
||||
|
||||
function TParser.ConstRhsStartsIntExpr: Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if Check(tkLParen) then
|
||||
Result := True
|
||||
else if Check(tkIntLit) and Self.IsConstExprOp(PeekKind()) then
|
||||
Result := True
|
||||
else if Check(tkMinus) and (PeekKind() = tkIntLit)
|
||||
and Self.IsConstExprOp(PeekKind2()) then
|
||||
Result := True
|
||||
{ Ident-led integer expression (e.g. BASE * 2). Only the unambiguously
|
||||
numeric operators trigger this — '+' is left to the string path so that
|
||||
string-concat consts ('a' + b) keep their existing behaviour, and the
|
||||
legacy ident-bitop chain handles or/and/xor/shl/shr starting on an ident. }
|
||||
else if Check(tkIdent) and
|
||||
((PeekKind() = tkStar) or (PeekKind() = tkDiv) or
|
||||
(PeekKind() = tkMod) or (PeekKind() = tkMinus)) then
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TParser.CurrentIsConstBitOp: Boolean;
|
||||
begin
|
||||
Result := Check(tkOr) or Check(tkAnd) or Check(tkXor)
|
||||
|
|
@ -1099,6 +1137,17 @@ begin
|
|||
Self.ParseConstArrayGroup(CD);
|
||||
Exit;
|
||||
end;
|
||||
{ Compile-time integer expression with precedence / parentheses
|
||||
(e.g. 2 * 3, 2 + 3 * 4, (1 shl 8) or 7). Parsed into a normal
|
||||
expression AST and folded by the semantic pass (EvalConstIntExpr),
|
||||
which also resolves any named-constant references. A bare literal
|
||||
without a trailing operator skips this and uses the fast path below. }
|
||||
if Self.ConstRhsStartsIntExpr() then
|
||||
begin
|
||||
CD.IsString := False;
|
||||
CD.IntValueExpr := Self.ParseExpr();
|
||||
Exit;
|
||||
end;
|
||||
if Check(tkMinus) then
|
||||
begin
|
||||
Advance();
|
||||
|
|
|
|||
|
|
@ -146,6 +146,12 @@ type
|
|||
function NewArrayConstLabel(const AName: string): string;
|
||||
function FoldConstBitOpExpr(ATokens: TStringList;
|
||||
ALine, ACol: Integer): Int64;
|
||||
{ Fold a compile-time integer constant expression AST (literals, named
|
||||
constants, and the integer binary/unary operators with the precedence
|
||||
already encoded in the tree shape) to its Int64 value. Named-constant
|
||||
references are resolved against the symbol table, so this must run after
|
||||
the referenced consts are declared. }
|
||||
function EvalConstIntExpr(AExpr: TASTExpr; ALine, ACol: Integer): Int64;
|
||||
procedure AnalyseTypeDecls(ABlock: TBlock);
|
||||
procedure LinkClassMethodImpls(ABlock: TBlock);
|
||||
procedure LinkGenericClassMethodImpls(ABlock: TBlock);
|
||||
|
|
@ -3048,6 +3054,81 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
function TSemanticAnalyser.EvalConstIntExpr(AExpr: TASTExpr;
|
||||
ALine, ACol: Integer): Int64;
|
||||
var
|
||||
Bin: TBinaryExpr;
|
||||
IdSym: TSymbol;
|
||||
L, R: Int64;
|
||||
begin
|
||||
Result := 0;
|
||||
if AExpr = nil then Exit;
|
||||
|
||||
if AExpr is TIntLiteral then
|
||||
Exit(TIntLiteral(AExpr).Value);
|
||||
|
||||
if AExpr is TIdentExpr then
|
||||
begin
|
||||
{ Named-constant reference; also resolves True/False to 1/0. }
|
||||
if SameText(TIdentExpr(AExpr).Name, 'True') then Exit(1);
|
||||
if SameText(TIdentExpr(AExpr).Name, 'False') then Exit(0);
|
||||
IdSym := FTable.Lookup(TIdentExpr(AExpr).Name);
|
||||
if (IdSym = nil) or (IdSym.Kind <> skConstant) then
|
||||
begin
|
||||
SemanticError(Format(
|
||||
'Constant expression references ''%s'', which is not a constant',
|
||||
[TIdentExpr(AExpr).Name]), ALine, ACol);
|
||||
Exit;
|
||||
end;
|
||||
Exit(IdSym.ConstValue);
|
||||
end;
|
||||
|
||||
if AExpr is TNotExpr then
|
||||
Exit(not EvalConstIntExpr(TNotExpr(AExpr).Expr, ALine, ACol));
|
||||
|
||||
if AExpr is TBinaryExpr then
|
||||
begin
|
||||
Bin := TBinaryExpr(AExpr);
|
||||
L := EvalConstIntExpr(Bin.Left, ALine, ACol);
|
||||
R := EvalConstIntExpr(Bin.Right, ALine, ACol);
|
||||
case Bin.Op of
|
||||
boAdd: Exit(L + R);
|
||||
boSub: Exit(L - R);
|
||||
boMul: Exit(L * R);
|
||||
boDiv:
|
||||
begin
|
||||
if R = 0 then
|
||||
begin
|
||||
SemanticError('Division by zero in constant expression', ALine, ACol);
|
||||
Exit;
|
||||
end;
|
||||
Exit(L div R);
|
||||
end;
|
||||
boMod:
|
||||
begin
|
||||
if R = 0 then
|
||||
begin
|
||||
SemanticError('Division by zero in constant expression', ALine, ACol);
|
||||
Exit;
|
||||
end;
|
||||
Exit(L mod R);
|
||||
end;
|
||||
boAnd: Exit(L and R);
|
||||
boOr: Exit(L or R);
|
||||
boXor: Exit(L xor R);
|
||||
boShl: Exit(L shl R);
|
||||
boShr, boSar: Exit(L shr R);
|
||||
else
|
||||
SemanticError(
|
||||
'Unsupported operator in integer constant expression', ALine, ACol);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
SemanticError(
|
||||
'Constant expression is not a compile-time integer', ALine, ACol);
|
||||
end;
|
||||
|
||||
procedure TSemanticAnalyser.AnalyseSetConstDecl(ACD: TConstDecl);
|
||||
var
|
||||
I: Integer;
|
||||
|
|
@ -3185,6 +3266,10 @@ begin
|
|||
named-constant values in scope. }
|
||||
if CD.IntExprTokens <> nil then
|
||||
CD.IntVal := FoldConstBitOpExpr(CD.IntExprTokens, CD.Line, CD.Col);
|
||||
{ Compile-time integer expression (arithmetic/bitwise with precedence):
|
||||
fold the AST now that prior consts in scope are resolvable. }
|
||||
if CD.IntValueExpr <> nil then
|
||||
CD.IntVal := EvalConstIntExpr(CD.IntValueExpr, CD.Line, CD.Col);
|
||||
{ Set-valued constants reference enum members, which are not registered
|
||||
until AnalyseTypeDecls runs — so they are resolved in the second pass
|
||||
(AnalyseArrayConstDecls), like array consts. }
|
||||
|
|
@ -5175,6 +5260,8 @@ begin
|
|||
CD.Line, CD.Col);
|
||||
if CD.IntExprTokens <> nil then
|
||||
CD.IntVal := FoldConstBitOpExpr(CD.IntExprTokens, CD.Line, CD.Col);
|
||||
if CD.IntValueExpr <> nil then
|
||||
CD.IntVal := EvalConstIntExpr(CD.IntValueExpr, CD.Line, CD.Col);
|
||||
|
||||
{ Type compatibility check between the folded value kind and the declared
|
||||
type — catches 'var N: Integer = ''text''' and similar. }
|
||||
|
|
|
|||
|
|
@ -115,6 +115,17 @@ type
|
|||
procedure TestTypedConst_OrChain_MixedLiteralAndNamed_Folds;
|
||||
procedure TestTypedConst_AndMaskOfHexLiteral_Folds;
|
||||
procedure TestTypedConst_ShiftLeftLiteral_Folds;
|
||||
|
||||
{ Compile-time integer constant expressions (issue #96) — arithmetic
|
||||
operators, precedence, parentheses, and forward const references }
|
||||
procedure TestConstExpr_Multiply_Folds;
|
||||
procedure TestConstExpr_Parenthesised_Folds;
|
||||
procedure TestConstExpr_PrecedenceMulOverAdd_Folds;
|
||||
procedure TestConstExpr_ParensOverridePrecedence_Folds;
|
||||
procedure TestConstExpr_DivAndMod_Folds;
|
||||
procedure TestConstExpr_NamedConstReference_Folds;
|
||||
procedure TestConstExpr_UnaryMinus_Folds;
|
||||
procedure TestConstExpr_MixedArithAndBitwise_Folds;
|
||||
procedure TestArrayConst_OrChainElement_FoldsToFinalInteger;
|
||||
end;
|
||||
|
||||
|
|
@ -1324,6 +1335,123 @@ begin
|
|||
AssertTrue('1 shl 8 = 256 in IR', IRContains(IR, '256'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_Multiply_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const X = 2 * 3;
|
||||
var v: Integer;
|
||||
begin v := X end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
AssertTrue('2 * 3 = 6 in IR', IRContains(IR, '6'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_Parenthesised_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const X = (2 * 3);
|
||||
var v: Integer;
|
||||
begin v := X end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
AssertTrue('(2 * 3) = 6 in IR', IRContains(IR, '6'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_PrecedenceMulOverAdd_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const X = 2 + 3 * 4;
|
||||
var v: Integer;
|
||||
begin v := X end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
{ 2 + (3 * 4) = 14, not (2 + 3) * 4 = 20 }
|
||||
AssertTrue('2 + 3 * 4 = 14 in IR', IRContains(IR, '14'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_ParensOverridePrecedence_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const X = (2 + 3) * 4;
|
||||
var v: Integer;
|
||||
begin v := X end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
AssertTrue('(2 + 3) * 4 = 20 in IR', IRContains(IR, '20'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_DivAndMod_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const Q = 100 div 7;
|
||||
R = 100 mod 7;
|
||||
var a, b: Integer;
|
||||
begin a := Q; b := R end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
AssertTrue('100 div 7 = 14 in IR', IRContains(IR, '14'));
|
||||
AssertTrue('100 mod 7 = 2 in IR', IRContains(IR, '2'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_NamedConstReference_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const Base = 10;
|
||||
Derived = Base * 2 + 1;
|
||||
var v: Integer;
|
||||
begin v := Derived end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
AssertTrue('Base * 2 + 1 = 21 in IR', IRContains(IR, '21'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_UnaryMinus_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const X = -5 * 3;
|
||||
var v: Integer;
|
||||
begin v := X end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
AssertTrue('-5 * 3 = -15 in IR', IRContains(IR, '-15'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestConstExpr_MixedArithAndBitwise_Folds;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
const X = (1 shl 4) or 3;
|
||||
var v: Integer;
|
||||
begin v := X end.
|
||||
''');
|
||||
AssertTrue('IR non-empty', IR <> '');
|
||||
{ (1 shl 4) or 3 = 16 or 3 = 19 }
|
||||
AssertTrue('(1 shl 4) or 3 = 19 in IR', IRContains(IR, '19'));
|
||||
end;
|
||||
|
||||
procedure TConstTests.TestArrayConst_OrChainElement_FoldsToFinalInteger;
|
||||
var IR: string;
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ type
|
|||
procedure TestRun_Const_IntegerConst;
|
||||
procedure TestRun_Const_StringConst;
|
||||
procedure TestRun_Const_NegativeConst;
|
||||
procedure TestRun_Const_CompileTimeExpression;
|
||||
procedure TestRun_Const_LocalArrayInFunction;
|
||||
|
||||
{ Procedural types }
|
||||
|
|
@ -224,6 +225,26 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
{ issue #96 — const declared with a compile-time formula (precedence,
|
||||
parentheses, division, and a forward reference to a prior const). }
|
||||
SrcConstExpr = '''
|
||||
program Prg;
|
||||
const
|
||||
A = 2 * 3;
|
||||
B = 2 + 3 * 4;
|
||||
C = (2 + 3) * 4;
|
||||
D = 100 div 7;
|
||||
Base = 10;
|
||||
E = Base * 2 + 1;
|
||||
begin
|
||||
WriteLn(A);
|
||||
WriteLn(B);
|
||||
WriteLn(C);
|
||||
WriteLn(D);
|
||||
WriteLn(E)
|
||||
end.
|
||||
''';
|
||||
|
||||
{ Regression: typed array constant declared inside a function body was
|
||||
referenced as $Name but never emitted as a data item, producing a
|
||||
link error. Exercises the full toolchain (codegen + QBE + ld). }
|
||||
|
|
@ -754,6 +775,14 @@ begin
|
|||
AssertRunsOnAll(SrcConstNeg, '-20' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EMiscTests.TestRun_Const_CompileTimeExpression;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
{ A=6, B=14 (precedence), C=20 (parens), D=14 (div), E=21 (named-ref). }
|
||||
AssertRunsOnAll(SrcConstExpr,
|
||||
'6' + LE + '14' + LE + '20' + LE + '14' + LE + '21' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EMiscTests.TestRun_ProcType_CallViaVariable;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
|
|
|
|||
|
|
@ -509,13 +509,34 @@ ConstArrayValue
|
|||
{ COMMA ( ConstArrayValue | ConstExpr ) } RPAREN
|
||||
;
|
||||
|
||||
(* A constant value position. A scalar const may be a string/float literal or
|
||||
* a compile-time INTEGER expression: integer literals, named-constant
|
||||
* references, the binary operators ( + - * div mod and or xor shl shr ), unary
|
||||
* minus, and parentheses, with the usual operator precedence. The integer
|
||||
* expression is folded to a single value by the semantic pass (which resolves
|
||||
* named-constant references), so it may appear anywhere a constant is required. *)
|
||||
ConstExpr
|
||||
= [ MINUS ] IntLit
|
||||
= ConstIntExpr
|
||||
| [ MINUS ] FloatLit
|
||||
| StrLit
|
||||
| StrLit { PLUS StrLit } (* string constant, with concatenation *)
|
||||
| IDENT (* named constant reference, e.g. True / False *)
|
||||
;
|
||||
|
||||
ConstIntExpr
|
||||
= ConstIntTerm { ( PLUS | MINUS | OR | XOR ) ConstIntTerm }
|
||||
;
|
||||
|
||||
ConstIntTerm
|
||||
= ConstIntFactor { ( STAR | DIV | MOD | AND | SHL | SHR ) ConstIntFactor }
|
||||
;
|
||||
|
||||
ConstIntFactor
|
||||
= IntLit
|
||||
| IDENT (* named integer constant *)
|
||||
| MINUS ConstIntFactor (* unary minus *)
|
||||
| LPAREN ConstIntExpr RPAREN
|
||||
;
|
||||
|
||||
(* ConstDecl supports untyped, scalar-typed, array-typed, and set-typed forms:
|
||||
* Untyped: const MaxItems = 100;
|
||||
* Scalar-typed: const Pi: Double = 3.14159;
|
||||
|
|
|
|||
|
|
@ -2731,6 +2731,56 @@ to distinguish a type-cast expression from a function call inside a const
|
|||
initialiser, which is more complex than a simple colon annotation and is not
|
||||
the FPC/Delphi convention.
|
||||
|
||||
== Constant Expressions
|
||||
|
||||
=== Decision
|
||||
|
||||
A constant declaration may take a compile-time *integer expression* on the
|
||||
right-hand side, not just a single literal:
|
||||
|
||||
[source]
|
||||
----
|
||||
const
|
||||
A = 2 * 3; // 6
|
||||
B = 2 + 3 * 4; // 14 — '*' binds tighter than '+'
|
||||
C = (2 + 3) * 4; // 20 — parentheses override precedence
|
||||
D = 100 div 7; // 14
|
||||
Base = 10;
|
||||
Derived = Base * 2 + 1; // 21 — references a prior constant
|
||||
Flags = (1 shl 4) or 3; // 19 — arithmetic and bitwise mixed
|
||||
----
|
||||
|
||||
Supported operators are `+ - * div mod` and the bitwise/shift set
|
||||
`and or xor shl shr`, together with unary minus and parentheses, using the same
|
||||
precedence as the executable-expression grammar. Operands are integer
|
||||
literals and references to previously declared integer constants.
|
||||
|
||||
=== Rationale
|
||||
|
||||
The right-hand side is parsed with the existing full-precedence expression
|
||||
parser into a normal expression AST, then folded to a single `Int64` by the
|
||||
semantic pass. Folding in the semantic pass (rather than at parse time) is
|
||||
what makes forward references to other constants work: every constant in scope
|
||||
is registered before any expression is folded, so `Derived = Base * 2 + 1`
|
||||
resolves `Base` cleanly regardless of declaration order within the block.
|
||||
|
||||
Reusing the executable-expression parser means operator precedence,
|
||||
associativity, and parenthesised grouping are encoded in the AST shape for
|
||||
free — there is no separate, precedence-unaware constant evaluator to keep in
|
||||
sync. This supersedes the earlier flat bit-op token-chain representation,
|
||||
which only handled a single left-to-right precedence level and could not
|
||||
express `2 + 3 * 4` correctly.
|
||||
|
||||
=== Scope
|
||||
|
||||
Constant expressions fold to an integer. Floating-point constant arithmetic
|
||||
(`const X = 3.14 * 2`) and string concatenation beyond the existing literal/
|
||||
named-constant `+` form are not folded by this path; a multi-token float or
|
||||
string expression is left for a future extension. Compile-time constants are
|
||||
not yet accepted as static-array bounds (`array[0..N-1]`), which is a separate
|
||||
parser limitation in the type grammar, independent of constant-expression
|
||||
folding.
|
||||
|
||||
== Array-of-enum Typed Constants
|
||||
|
||||
=== Decision
|
||||
|
|
|
|||
Loading…
Reference in a new issue