Compiler exceptions

This commit is contained in:
Michael Schimmel
2025-11-25 15:51:04 +01:00
parent 4e508d90a5
commit 0a1df4e9fe
9 changed files with 194 additions and 303 deletions
+8
View File
@@ -267,6 +267,10 @@ begin
if not IsValidIdentifier(identifier.Name) then if not IsValidIdentifier(identifier.Name) then
raise EBinderException.CreateFmt('Invalid identifier name: "%s".', [identifier.Name]); raise EBinderException.CreateFmt('Invalid identifier name: "%s".', [identifier.Name]);
// Check if the identifier is already defined in the current scope builder to avoid Scope Asserts.
if FCurrentBuilder.FindSlot(identifier.Name) >= 0 then
raise EBinderException.CreateFmt('Variable "%s" is already defined in this scope.', [identifier.Name]);
slot := FCurrentBuilder.Define(identifier.Name); slot := FCurrentBuilder.Define(identifier.Name);
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot); addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
@@ -334,6 +338,10 @@ begin
SetLength(newParams, Length(Node.Parameters)); SetLength(newParams, Length(Node.Parameters));
for i := 0 to High(Node.Parameters) do for i := 0 to High(Node.Parameters) do
begin begin
// Parameter names must also be checked for uniqueness, though usually guaranteed by Parser/AST construction
if FCurrentBuilder.FindSlot(Node.Parameters[i].Name) >= 0 then
raise EBinderException.CreateFmt('Duplicate parameter name "%s".', [Node.Parameters[i].Name]);
slot := FCurrentBuilder.Define(Node.Parameters[i].Name); slot := FCurrentBuilder.Define(Node.Parameters[i].Name);
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot); addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
+21 -10
View File
@@ -185,12 +185,21 @@ begin
if node.Kind = akUnquoteSplicing then if node.Kind = akUnquoteSplicing then
begin begin
var spliceExpr := node.AsUnquoteSplicing.Expression; var spliceExpr := node.AsUnquoteSplicing.Expression;
var evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr); var evaluatedSpliceValue: TDataValue;
// GUARDED EVALUATION
try
evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr);
except
on E: EAstException do
raise;
on E: Exception do
raise EMacroException.Create('Error during macro splice evaluation: ' + E.Message);
end;
if (not evaluatedSpliceValue.IsVoid) and (evaluatedSpliceValue.Kind = vkInterface) then if (not evaluatedSpliceValue.IsVoid) and (evaluatedSpliceValue.Kind = vkInterface) then
begin begin
nodeToSplice := evaluatedSpliceValue.AsIntf<IAstNode>; nodeToSplice := evaluatedSpliceValue.AsIntf<IAstNode>;
// If the result is a block, flatten it into the current list
if nodeToSplice.Kind = akBlockExpression then if nodeToSplice.Kind = akBlockExpression then
newList.AddRange(nodeToSplice.AsBlockExpression.Expressions) newList.AddRange(nodeToSplice.AsBlockExpression.Expressions)
else else
@@ -198,7 +207,6 @@ begin
end end
else if (not evaluatedSpliceValue.IsVoid) then else if (not evaluatedSpliceValue.IsVoid) then
begin begin
// Treat scalar values as constants in the AST
newList.Add(TAst.Constant(evaluatedSpliceValue)); newList.Add(TAst.Constant(evaluatedSpliceValue));
end; end;
end end
@@ -278,12 +286,9 @@ var
begin begin
expr := Node.Expression; expr := Node.Expression;
// Optimization: If unquoting a simple identifier that exists in the macro scope,
// try to resolve it directly to avoid unnecessary evaluation overhead.
if expr.Kind = akIdentifier then if expr.Kind = akIdentifier then
begin begin
addr := FMacroScope.Resolve(expr.AsIdentifier.Name); addr := FMacroScope.Resolve(expr.AsIdentifier.Name);
if (addr.Kind = akLocalOrParent) and (addr.ScopeDepth = 0) then if (addr.Kind = akLocalOrParent) and (addr.ScopeDepth = 0) then
begin begin
var argValue := FMacroScope.Values[addr]; var argValue := FMacroScope.Values[addr];
@@ -295,17 +300,23 @@ begin
end; end;
end; end;
// Evaluate the expression at compile time // Evaluate the expression at compile time.
value := FMacroEvaluator(FMacroScope, expr); // This executes user code! It must be guarded.
try
value := FMacroEvaluator(FMacroScope, expr);
except
on E: EAstException do
raise;
on E: Exception do
raise EMacroException.Create('Error during macro evaluation: ' + E.Message);
end;
if value.Kind = vkInterface then if value.Kind = vkInterface then
begin begin
// It returned an AST node -> Inject it
Result := value.AsIntf<IAstNode>; Result := value.AsIntf<IAstNode>;
exit; exit;
end; end;
// It returned a value -> Wrap as Constant
if value.Kind in [vkScalar, vkText, vkVoid] then if value.Kind in [vkScalar, vkText, vkVoid] then
Result := TAst.Constant(value) Result := TAst.Constant(value)
else else
+10 -67
View File
@@ -25,13 +25,12 @@ type
TTypeChecker = class(TAstTransformer, IAstTypeChecker) TTypeChecker = class(TAstTransformer, IAstTypeChecker)
private private
type type
// Helper to track types per scope during traversal (The "Scratchpad")
TTypeContext = class TTypeContext = class
private private
FParent: TTypeContext; FParent: TTypeContext;
FLayout: IScopeLayout; FLayout: IScopeLayout;
FSlotTypes: TArray<IStaticType>; FSlotTypes: TArray<IStaticType>;
FUpvalueTypes: TArray<IStaticType>; // Types of captured variables FUpvalueTypes: TArray<IStaticType>;
public public
constructor Create(AParent: TTypeContext; ALayout: IScopeLayout; const AUpvalueTypes: TArray<IStaticType>); constructor Create(AParent: TTypeContext; ALayout: IScopeLayout; const AUpvalueTypes: TArray<IStaticType>);
function LookupType(const Address: TResolvedAddress): IStaticType; function LookupType(const Address: TResolvedAddress): IStaticType;
@@ -41,12 +40,9 @@ type
private private
FCurrentContext: TTypeContext; FCurrentContext: TTypeContext;
// Helper to recursively rebuild the context stack from the layout hierarchy
function CreateContextChain(L: IScopeLayout): TTypeContext; function CreateContextChain(L: IScopeLayout): TTypeContext;
protected protected
// Override all visit methods to perform type checking
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override; function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override; function VisitAssignment(const Node: IAssignmentNode): IAstNode; override;
@@ -64,7 +60,6 @@ type
function VisitRecurNode(const Node: IRecurNode): IAstNode; override; function VisitRecurNode(const Node: IRecurNode): IAstNode; override;
function VisitNop(const Node: INopNode): IAstNode; override; function VisitNop(const Node: INopNode): IAstNode; override;
// Base cases (types are now set here)
function VisitConstant(const Node: IConstantNode): IAstNode; override; function VisitConstant(const Node: IConstantNode): IAstNode; override;
function VisitKeyword(const Node: IKeywordNode): IAstNode; override; function VisitKeyword(const Node: IKeywordNode): IAstNode; override;
@@ -92,7 +87,6 @@ begin
FLayout := ALayout; FLayout := ALayout;
FUpvalueTypes := AUpvalueTypes; FUpvalueTypes := AUpvalueTypes;
// Initialize slot types with Unknown
if Assigned(FLayout) then if Assigned(FLayout) then
begin begin
SetLength(FSlotTypes, FLayout.SlotCount); SetLength(FSlotTypes, FLayout.SlotCount);
@@ -125,7 +119,6 @@ begin
end; end;
akUpvalue: akUpvalue:
begin begin
// Look up type in our local upvalue type cache
if (Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FUpvalueTypes)) then if (Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FUpvalueTypes)) then
Result := FUpvalueTypes[Address.SlotIndex] Result := FUpvalueTypes[Address.SlotIndex]
else else
@@ -151,30 +144,20 @@ begin
if L = nil then if L = nil then
exit(nil); exit(nil);
// Recursively build parent context
p := CreateContextChain(L.Parent); p := CreateContextChain(L.Parent);
// Create context for current layout level
// Note: We don't know UpvalueTypes for these static/parent layouts here, so [] is passed.
// Also: Slot types will be initialized to Unknown.
Result := TTypeContext.Create(p, L, []); Result := TTypeContext.Create(p, L, []);
end; end;
constructor TTypeChecker.Create(const RootLayout: IScopeLayout); constructor TTypeChecker.Create(const RootLayout: IScopeLayout);
begin begin
inherited Create; inherited Create;
// Build the full context chain based on the Layout's parent hierarchy
// This ensures that ScopeDepth lookups can traverse up to the root.
FCurrentContext := CreateContextChain(RootLayout); FCurrentContext := CreateContextChain(RootLayout);
// Fallback if RootLayout is nil (should not happen in valid pipeline)
if FCurrentContext = nil then if FCurrentContext = nil then
FCurrentContext := TTypeContext.Create(nil, nil, []); FCurrentContext := TTypeContext.Create(nil, nil, []);
end; end;
destructor TTypeChecker.Destroy; destructor TTypeChecker.Destroy;
begin begin
// Cleanup context stack
while Assigned(FCurrentContext) do while Assigned(FCurrentContext) do
begin begin
var temp := FCurrentContext; var temp := FCurrentContext;
@@ -192,7 +175,6 @@ end;
function TTypeChecker.Execute(const RootNode: IAstNode; const Layout: IScopeLayout): IAstNode; function TTypeChecker.Execute(const RootNode: IAstNode; const Layout: IScopeLayout): IAstNode;
begin begin
// Note: Layout passed here matches what was passed to Create/Constructor (via recursed ContextChain)
Result := Accept(RootNode); Result := Accept(RootNode);
if not Assigned(Result) then if not Assigned(Result) then
Result := TAst.Block([]); Result := TAst.Block([]);
@@ -216,10 +198,7 @@ var
adr: TResolvedAddress; adr: TResolvedAddress;
begin begin
adr := Node.Address; adr := Node.Address;
// Lookup type in our scratchpad context using the address resolved by Binder
typ := FCurrentContext.LookupType(adr); typ := FCurrentContext.LookupType(adr);
// Create a new node with the inferred type
Result := TAst.Identifier(Node.Name, adr, typ); Result := TAst.Identifier(Node.Name, adr, typ);
end; end;
@@ -247,7 +226,6 @@ begin
adr := Node.Target.AsIdentifier.Address; adr := Node.Target.AsIdentifier.Address;
initType := TTypes.Unknown; initType := TTypes.Unknown;
// Recursive lambda bootstrap logic
placeholderType := nil; placeholderType := nil;
if (Node.Initializer <> nil) and (Node.Initializer.Kind = akLambdaExpression) then if (Node.Initializer <> nil) and (Node.Initializer.Kind = akLambdaExpression) then
begin begin
@@ -258,8 +236,6 @@ begin
paramTypes[i] := TTypes.Unknown; paramTypes[i] := TTypes.Unknown;
placeholderType := TTypes.CreateMethod(paramTypes, TTypes.Unknown); placeholderType := TTypes.CreateMethod(paramTypes, TTypes.Unknown);
// Update Context (Scratchpad)
FCurrentContext.SetType(adr.SlotIndex, placeholderType); FCurrentContext.SetType(adr.SlotIndex, placeholderType);
initType := placeholderType; initType := placeholderType;
end; end;
@@ -274,12 +250,10 @@ begin
else if not Assigned(placeholderType) then else if not Assigned(placeholderType) then
initType := TTypes.Unknown; initType := TTypes.Unknown;
// Update Context with final type
if initType.Kind <> stUnknown then if initType.Kind <> stUnknown then
FCurrentContext.SetType(adr.SlotIndex, initType); FCurrentContext.SetType(adr.SlotIndex, initType);
newIdent := TAst.Identifier(Node.Target.AsIdentifier.Name, adr, initType); newIdent := TAst.Identifier(Node.Target.AsIdentifier.Name, adr, initType);
Result := TAst.VarDecl(newIdent.AsIdentifier, newInitializer, initType, Node.IsBoxed); Result := TAst.VarDecl(newIdent.AsIdentifier, newInitializer, initType, Node.IsBoxed);
end; end;
@@ -296,7 +270,6 @@ begin
targetType := newIdent.AsTypedNode.StaticType; targetType := newIdent.AsTypedNode.StaticType;
adr := newIdent.AsIdentifier.Address; adr := newIdent.AsIdentifier.Address;
// Recursive lambda assignment check
placeholderType := nil; placeholderType := nil;
if (Node.Value <> nil) and (Node.Value.Kind = akLambdaExpression) then if (Node.Value <> nil) and (Node.Value.Kind = akLambdaExpression) then
begin begin
@@ -344,28 +317,18 @@ var
newBody: IAstNode; newBody: IAstNode;
bodyType, methodType: IStaticType; bodyType, methodType: IStaticType;
paramTypes: TArray<IStaticType>; paramTypes: TArray<IStaticType>;
// Upvalue Type Resolution
upvalueTypes: TArray<IStaticType>; upvalueTypes: TArray<IStaticType>;
upvalueAddrs: TArray<TResolvedAddress>; upvalueAddrs: TArray<TResolvedAddress>;
i: Integer; i: Integer;
finalDescriptor: IScopeDescriptor; finalDescriptor: IScopeDescriptor;
begin begin
// 1. Resolve Upvalue Types *in the current (parent) context*
upvalueAddrs := Node.Upvalues; upvalueAddrs := Node.Upvalues;
SetLength(upvalueTypes, Length(upvalueAddrs)); SetLength(upvalueTypes, Length(upvalueAddrs));
for i := 0 to High(upvalueAddrs) do for i := 0 to High(upvalueAddrs) do
begin
// We look up the physical address (from Binder) in the current context chain
upvalueTypes[i] := FCurrentContext.LookupType(upvalueAddrs[i]); upvalueTypes[i] := FCurrentContext.LookupType(upvalueAddrs[i]);
end;
// 2. Create new TypeContext for this lambda scope, PASSING the upvalue types
// We use the layout from the Binder-Result-Node
FCurrentContext := TTypeContext.Create(FCurrentContext, Node.Layout, upvalueTypes); FCurrentContext := TTypeContext.Create(FCurrentContext, Node.Layout, upvalueTypes);
try try
// 3. Set parameter types in the context
SetLength(newParams, Length(Node.Parameters)); SetLength(newParams, Length(Node.Parameters));
SetLength(paramTypes, Length(Node.Parameters)); SetLength(paramTypes, Length(Node.Parameters));
@@ -373,37 +336,22 @@ begin
begin begin
var paramIdent := Node.Parameters[i]; var paramIdent := Node.Parameters[i];
var paramAdr := paramIdent.Address; var paramAdr := paramIdent.Address;
// Here we could infer types if we had type annotations. For now, parameters are Unknown.
paramTypes[i] := TTypes.Unknown; paramTypes[i] := TTypes.Unknown;
// Update context so body can resolve params
FCurrentContext.SetType(paramAdr.SlotIndex, paramTypes[i]); FCurrentContext.SetType(paramAdr.SlotIndex, paramTypes[i]);
newParams[i] := TAst.Identifier(paramIdent.Name, paramAdr, paramTypes[i]); newParams[i] := TAst.Identifier(paramIdent.Name, paramAdr, paramTypes[i]);
end; end;
// 4. Visit body
newBody := Accept(Node.Body); newBody := Accept(Node.Body);
bodyType := newBody.AsTypedNode.StaticType; bodyType := newBody.AsTypedNode.StaticType;
// 5. Create method type
methodType := TTypes.CreateMethod(paramTypes, bodyType); methodType := TTypes.CreateMethod(paramTypes, bodyType);
// 6. Update <self> type in context (Slot 0)
FCurrentContext.SetType(0, methodType); FCurrentContext.SetType(0, methodType);
// 7. FINALIZE: Bake the Descriptor
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types); finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types);
finally finally
// 8. Pop Context
var temp := FCurrentContext; var temp := FCurrentContext;
FCurrentContext := FCurrentContext.FParent; FCurrentContext := FCurrentContext.FParent;
temp.Free; temp.Free;
end; end;
// 9. Create new node with the Descriptor attached!
Result := Result :=
TAst.LambdaExpr(newParams, newBody, Node.Layout, finalDescriptor, Node.Upvalues, Node.HasNestedLambdas, Node.IsPure, methodType); TAst.LambdaExpr(newParams, newBody, Node.Layout, finalDescriptor, Node.Upvalues, Node.HasNestedLambdas, Node.IsPure, methodType);
end; end;
@@ -509,7 +457,6 @@ begin
newElse := Accept(Node.ElseBranch); newElse := Accept(Node.ElseBranch);
conditionType := newCond.AsTypedNode.StaticType; conditionType := newCond.AsTypedNode.StaticType;
// Ordinal and Boolean are valid for if-conditions
if (conditionType.Kind <> stUnknown) if (conditionType.Kind <> stUnknown)
and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType) and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType)
and not TTypeRules.CanAssign(TTypes.Boolean, conditionType) then and not TTypeRules.CanAssign(TTypes.Boolean, conditionType) then
@@ -520,12 +467,9 @@ begin
if newElse <> nil then newElse.AsTypedNode.StaticType if newElse <> nil then newElse.AsTypedNode.StaticType
else TTypes.Void; else TTypes.Void;
try resultType := TTypeRules.Promote(thenType, elseType);
resultType := TTypeRules.Promote(thenType, elseType); if resultType = nil then
except raise ETypeCheckException.CreateFmt('Cannot promote types %s and %s in If-Expression', [thenType.ToString, elseType.ToString]);
on E: Exception do // Catch base exception (ETypeException from Myc.Ast.Types)
raise ETypeCheckException.Create(E.Message);
end;
Result := TAst.IfExpr(newCond, newThen, newElse, resultType); Result := TAst.IfExpr(newCond, newThen, newElse, resultType);
end; end;
@@ -548,12 +492,9 @@ begin
thenType := newThen.AsTypedNode.StaticType; thenType := newThen.AsTypedNode.StaticType;
elseType := newElse.AsTypedNode.StaticType; elseType := newElse.AsTypedNode.StaticType;
try resultType := TTypeRules.Promote(thenType, elseType);
resultType := TTypeRules.Promote(thenType, elseType); if resultType = nil then
except raise ETypeCheckException.CreateFmt('Cannot promote types %s and %s in Ternary-Expression', [thenType.ToString, elseType.ToString]);
on E: Exception do
raise ETypeCheckException.Create(E.Message);
end;
Result := TAst.TernaryExpr(newCond, newThen, newElse, resultType); Result := TAst.TernaryExpr(newCond, newThen, newElse, resultType);
end; end;
@@ -698,10 +639,12 @@ var
elemType: IStaticType; elemType: IStaticType;
begin begin
try try
// TScalar.StringToKind might throw EConvertError or generic Exception if string is invalid
elemType := TTypes.FromScalarKind(TScalar.StringToKind(Node.Definition)); elemType := TTypes.FromScalarKind(TScalar.StringToKind(Node.Definition));
except except
on E: Exception do on E: Exception do
elemType := TTypes.Unknown; // Instead of silently using Unknown, we now raise a proper compiler error
raise ETypeCheckException.CreateFmt('Invalid type definition "%s" in new-series.', [Node.Definition]);
end; end;
Result := TAst.CreateSeries(Node.Definition, TTypes.CreateSeries(elemType)); Result := TAst.CreateSeries(Node.Definition, TTypes.CreateSeries(elemType));
+51 -77
View File
@@ -8,7 +8,7 @@ uses
System.Generics.Collections, System.Generics.Collections,
Myc.Data.Scalar, Myc.Data.Scalar,
Myc.Data.Value, Myc.Data.Value,
Myc.Ast.Nodes, Myc.Ast.Nodes, // Provides EAstException
Myc.Ast.Scope; Myc.Ast.Scope;
type type
@@ -16,8 +16,6 @@ type
EEvaluatorException = class(EAstException); EEvaluatorException = class(EAstException);
// The standard AST evaluator for production use. // The standard AST evaluator for production use.
// This class inherits directly from TInterfacedObject as it is an
// Interpreter (AST -> TDataValue), not a Transformer (AST -> IAstNode).
TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor, IEvaluatorVisitor) TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor, IEvaluatorVisitor)
private private
FScope: IExecutionScope; FScope: IExecutionScope;
@@ -139,8 +137,16 @@ begin
if not Assigned(RootNode) then if not Assigned(RootNode) then
exit(TDataValue.Void); exit(TDataValue.Void);
Result := RootNode.Accept(Self); try
HandleTCO(Result); Result := RootNode.Accept(Self);
HandleTCO(Result);
except
on E: EAstException do
raise; // Already a compiler/runtime exception, pass through
on E: Exception do
// Wrap unexpected RTL exceptions (e.g. EDivByZero, EConvertError)
raise EEvaluatorException.Create('Runtime Error: ' + E.Message);
end;
end; end;
function TEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory; function TEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory;
@@ -152,12 +158,18 @@ end;
class procedure TEvaluatorVisitor.HandleTCO(var ResultValue: TDataValue); class procedure TEvaluatorVisitor.HandleTCO(var ResultValue: TDataValue);
begin begin
// This is the central trampoline loop for Tail Call Optimization. // This is the central trampoline loop for Tail Call Optimization.
// It runs as long as the evaluation returns a thunk. try
while ResultValue.Kind = vkGeneric do while ResultValue.Kind = vkGeneric do
begin begin
var thunk := ResultValue.AsGeneric<TThunk>; var thunk := ResultValue.AsGeneric<TThunk>;
var callee := thunk.Callee.AsMethod(); var callee := thunk.Callee.AsMethod();
ResultValue := callee(thunk.Args); ResultValue := callee(thunk.Args);
end;
except
on E: EAstException do
raise;
on E: Exception do
raise EEvaluatorException.Create('Runtime Error (TCO): ' + E.Message);
end; end;
end; end;
@@ -186,8 +198,6 @@ var
visitorFactory: TEvaluatorFactory; visitorFactory: TEvaluatorFactory;
begin begin
// 1. Capture Upvalues // 1. Capture Upvalues
// The Node.Upvalues array contains the physical addresses in the *current* scope (FScope)
// that map to the closure's upvalues.
if Node.Upvalues <> nil then if Node.Upvalues <> nil then
begin begin
SetLength(capturedCells, Length(Node.Upvalues)); SetLength(capturedCells, Length(Node.Upvalues));
@@ -198,9 +208,6 @@ begin
capturedCells := nil; capturedCells := nil;
// 2. Determine Parent Scope for Closure // 2. Determine Parent Scope for Closure
// Memory optimization: a lambda's scope does not need to be kept alive as a parent
// if it contains no nested lambdas that might need to capture from it later.
// (Assuming HasNestedLambdas flag is set correctly by Binder)
if Node.HasNestedLambdas then if Node.HasNestedLambdas then
closureScope := FScope closureScope := FScope
else else
@@ -210,11 +217,10 @@ begin
visitorFactory := CreateVisitorFactory(); visitorFactory := CreateVisitorFactory();
// 4. Capture Metadata for Closure // 4. Capture Metadata for Closure
var descriptor := Node.Descriptor; // Runtime Descriptor (contains types + layout) var descriptor := Node.Descriptor;
var params := Node.Parameters; var params := Node.Parameters;
var cNode: ILambdaExpressionNode := Node; var cNode: ILambdaExpressionNode := Node;
// [unsafe] prevents a reference cycle since the closure captures itself for 'recur'.
var [unsafe] closure: TDataValue.TFunc; var [unsafe] closure: TDataValue.TFunc;
closure := closure :=
function(const ArgValues: TArray<TDataValue>): TDataValue function(const ArgValues: TArray<TDataValue>): TDataValue
@@ -231,22 +237,16 @@ begin
lambdaScope := TScope.CreateScope(closureScope, descriptor, capturedCells); lambdaScope := TScope.CreateScope(closureScope, descriptor, capturedCells);
// Capture the closure itself in slot 0 for 'recur' to find it (if needed). // Capture the closure itself in slot 0 for 'recur' to find it (if needed).
// Note: The Binder reserves Slot 0 for <self>.
adr.Kind := akLocalOrParent; adr.Kind := akLocalOrParent;
adr.ScopeDepth := 0; adr.ScopeDepth := 0;
adr.SlotIndex := 0; adr.SlotIndex := 0;
lambdaScope[adr] := TDataValue(closure); // Explicit cast lambdaScope[adr] := TDataValue(closure);
// Populate the scope with the actual parameters passed to the function. // Populate the scope with the actual parameters passed to the function.
for i := 0 to High(ArgValues) do for i := 0 to High(ArgValues) do
begin begin
// Parameters are bound to specific slots by the Binder.
// We access them via the Address stored in the parameter node.
adr := params[i].Address; adr := params[i].Address;
// Defensive check: Ensure we are writing to local scope
Assert(adr.ScopeDepth = 0); Assert(adr.ScopeDepth = 0);
lambdaScope[adr] := ArgValues[i]; lambdaScope[adr] := ArgValues[i];
end; end;
@@ -255,14 +255,11 @@ begin
Result := cNode.Body.Accept(bodyVisitor); Result := cNode.Body.Accept(bodyVisitor);
end; end;
// The result of visiting a lambda node is the callable closure itself. Result := TDataValue(closure);
Result := TDataValue(closure); // Explicit cast
end; end;
function TEvaluatorVisitor.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; function TEvaluatorVisitor.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue;
begin begin
// Macro definitions are compile-time constructs.
// The Evaluator ignores them (treated as Void).
Result := TDataValue.Void; Result := TDataValue.Void;
end; end;
@@ -291,19 +288,13 @@ begin
if Assigned(Node.StaticTarget) then if Assigned(Node.StaticTarget) then
begin begin
// --- Static Path (Optimized) --- // --- Static Path (Optimized) ---
// 1. Evaluate arguments
argNodes := Node.Arguments; argNodes := Node.Arguments;
SetLength(argValues, Length(argNodes)); SetLength(argValues, Length(argNodes));
for i := 0 to High(argNodes) do for i := 0 to High(argNodes) do
begin
// Assuming arguments passed type check
argValues[i] := argNodes[i].Accept(Self); argValues[i] := argNodes[i].Accept(Self);
end;
// 2. Call the static target directly // Call static target. Exceptions here are caught by Execute/HandleTCO.
Result := Node.StaticTarget(argValues); Result := Node.StaticTarget(argValues);
// 3. Handle TCO
HandleTCO(Result); HandleTCO(Result);
end end
else else
@@ -320,12 +311,10 @@ begin
if Node.IsTailCall then if Node.IsTailCall then
begin begin
// This is a tail call. Return a thunk to be processed by the trampoline.
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, false)); Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, false));
end end
else else
begin begin
// This is a non-tail call. It must execute the call and act as the trampoline.
Result := (calleeValue.AsMethod)(argValues); Result := (calleeValue.AsMethod)(argValues);
HandleTCO(Result); HandleTCO(Result);
end; end;
@@ -334,8 +323,6 @@ end;
function TEvaluatorVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; function TEvaluatorVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue;
begin begin
// The evaluator simply "unwraps" the macro expansion node
// and executes the expanded body it contains.
Result := Node.ExpandedBody.Accept(Self); Result := Node.ExpandedBody.Accept(Self);
end; end;
@@ -346,18 +333,15 @@ var
calleeValue: TDataValue; calleeValue: TDataValue;
i: Integer; i: Integer;
begin begin
// The binder ensures this is only in a tail position.
SetLength(argValues, Length(Node.Arguments)); SetLength(argValues, Length(Node.Arguments));
for i := 0 to High(Node.Arguments) do for i := 0 to High(Node.Arguments) do
argValues[i] := Node.Arguments[i].Accept(Self); argValues[i] := Node.Arguments[i].Accept(Self);
// The callee is the current function, stored in slot 0 of the current scope.
calleeAddress.Kind := akLocalOrParent; calleeAddress.Kind := akLocalOrParent;
calleeAddress.ScopeDepth := 0; calleeAddress.ScopeDepth := 0;
calleeAddress.SlotIndex := 0; calleeAddress.SlotIndex := 0;
calleeValue := FScope[calleeAddress]; calleeValue := FScope[calleeAddress];
// Recur always returns a thunk for the trampoline.
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, true)); Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, true));
end; end;
@@ -400,9 +384,7 @@ begin
if Node.Target.Kind <> akIdentifier then if Node.Target.Kind <> akIdentifier then
raise EEvaluatorException.Create('Runtime Error: Assignment target must be an identifier.'); raise EEvaluatorException.Create('Runtime Error: Assignment target must be an identifier.');
// Evaluate value
Result := Node.Value.Accept(Self); Result := Node.Value.Accept(Self);
// Assign
FScope[Node.Target.AsIdentifier.Address] := Result; FScope[Node.Target.AsIdentifier.Address] := Result;
end; end;
@@ -413,8 +395,7 @@ end;
function TEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue; function TEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue;
begin begin
// Return the keyword as a TScalar value Result := TDataValue(TScalar.FromKeyword(Node.Value));
Result := TDataValue(TScalar.FromKeyword(Node.Value)); // Explicit cast
end; end;
function TEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; function TEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
@@ -422,24 +403,30 @@ var
def: string; def: string;
begin begin
def := Node.Definition.Trim; def := Node.Definition.Trim;
if def.StartsWith('[') then try
begin if def.StartsWith('[') then
var recordDef := TRttiAstHelper.JsonToRecordDefinition(def); begin
if Length(recordDef.Fields) = 0 then var recordDef := TRttiAstHelper.JsonToRecordDefinition(def);
raise EEvaluatorException.Create('Failed to parse record definition from JSON array.'); if Length(recordDef.Fields) = 0 then
var recordSeries := TScalarRecordSeries.Create(recordDef); raise EEvaluatorException.Create('Failed to parse record definition from JSON array.');
Result := TDataValue.FromRecordSeries(recordSeries); var recordSeries := TScalarRecordSeries.Create(recordDef);
end Result := TDataValue.FromRecordSeries(recordSeries);
else end
begin else
var scalarKind := TScalar.StringToKind(def); begin
Result := TDataValue.FromSeries(TScalarSeries.Create(scalarKind)); var scalarKind := TScalar.StringToKind(def);
Result := TDataValue.FromSeries(TScalarSeries.Create(scalarKind));
end;
except
on E: EAstException do
raise;
on E: Exception do
raise EEvaluatorException.Create('Invalid series definition: ' + E.Message);
end; end;
end; end;
function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue; function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin begin
// The scope's GetValues implementation handles unboxing and parent lookup.
Result := FScope[Node.Address]; Result := FScope[Node.Address];
end; end;
@@ -470,7 +457,7 @@ begin
series := baseValue.AsSeries; series := baseValue.AsSeries;
if (index < 0) or (index >= series.TotalCount) then if (index < 0) or (index >= series.TotalCount) then
raise EEvaluatorException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.TotalCount]); raise EEvaluatorException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, series.TotalCount]);
Result := TDataValue(series.Items[Integer(index)]); // Explicit cast Result := TDataValue(series.Items[Integer(index)]);
end; end;
vkRecordSeries: vkRecordSeries:
begin begin
@@ -479,7 +466,6 @@ begin
raise EEvaluatorException raise EEvaluatorException
.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, recSeries.TotalCount]); .CreateFmt('Index %d is out of bounds for series with %d elements.', [index, recSeries.TotalCount]);
// Materialize the TScalarRecord by accessing each member series by index
fieldCount := Length(recSeries.Def.Fields); fieldCount := Length(recSeries.Def.Fields);
SetLength(values, fieldCount); SetLength(values, fieldCount);
@@ -508,7 +494,7 @@ begin
case baseValue.Kind of case baseValue.Kind of
vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries[Node.Member.Value]); vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries[Node.Member.Value]);
vkRecord: Result := TDataValue(baseValue.AsRecord[Node.Member.Value]); // Explicit cast vkRecord: Result := TDataValue(baseValue.AsRecord[Node.Member.Value]);
vkGenericRecord: vkGenericRecord:
begin begin
@@ -528,20 +514,14 @@ function TEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): T
var var
i: Integer; i: Integer;
begin begin
// Use the properties populated by the TypeChecker/Binder
if Assigned(Node.GenericDefinition) then if Assigned(Node.GenericDefinition) then
begin begin
// --- GENERIC RECORD PATH ---
var genFields: TArray<TPair<IKeyword, TDataValue>>; var genFields: TArray<TPair<IKeyword, TDataValue>>;
SetLength(genFields, Length(Node.Fields)); SetLength(genFields, Length(Node.Fields));
for i := 0 to High(Node.Fields) do for i := 0 to High(Node.Fields) do
begin begin
genFields[i] := genFields[i] := TPair<IKeyword, TDataValue>.Create(Node.Fields[i].Key.Value, Node.Fields[i].Value.Accept(Self));
TPair<IKeyword, TDataValue>.Create(
Node.Fields[i].Key.Value,
Node.Fields[i].Value.Accept(Self) // Evaluate expression
);
end; end;
var dynRec := TDynamicRecord.Create(genFields); var dynRec := TDynamicRecord.Create(genFields);
@@ -549,7 +529,6 @@ begin
end end
else if Assigned(Node.ScalarDefinition) then else if Assigned(Node.ScalarDefinition) then
begin begin
// --- SCALAR RECORD PATH ---
var values: TArray<TScalar.TValue>; var values: TArray<TScalar.TValue>;
SetLength(values, Length(Node.Fields)); SetLength(values, Length(Node.Fields));
@@ -573,25 +552,20 @@ begin
if Node.Target.Kind <> akIdentifier then if Node.Target.Kind <> akIdentifier then
raise EEvaluatorException.Create('Runtime Error: Variable declaration target must be an identifier.'); raise EEvaluatorException.Create('Runtime Error: Variable declaration target must be an identifier.');
// 1. Evaluate Initializer
if Assigned(Node.Initializer) then if Assigned(Node.Initializer) then
Result := Node.Initializer.Accept(Self) Result := Node.Initializer.Accept(Self)
else else
Result := TDataValue.Void; Result := TDataValue.Void;
// 2. Get Address (assigned by Binder)
address := Node.Target.AsIdentifier.Address; address := Node.Target.AsIdentifier.Address;
// 3. Store Value
if Node.IsBoxed then if Node.IsBoxed then
begin begin
// Capture (heap alloc)
Assert(address.ScopeDepth = 0); Assert(address.ScopeDepth = 0);
FScope.DefineBoxed(address.SlotIndex, Result); FScope.DefineBoxed(address.SlotIndex, Result);
end end
else else
begin begin
// Stack alloc
FScope[address] := Result; FScope[address] := Result;
end; end;
end; end;
@@ -643,7 +617,7 @@ begin
.CreateFmt('Cannot get length of type %s.', [GetEnumName(TypeInfo(TDataValueKind), Ord(seriesValue.Kind))]); .CreateFmt('Cannot get length of type %s.', [GetEnumName(TypeInfo(TDataValueKind), Ord(seriesValue.Kind))]);
end; end;
Result := TDataValue(TScalar.FromInt64(len)); // Explicit cast Result := TDataValue(TScalar.FromInt64(len));
end; end;
end. end.
+11 -15
View File
@@ -11,9 +11,6 @@ uses
Myc.Ast.Types; Myc.Ast.Types;
type type
// Exception specific to scope resolution and definition errors
EScopeException = class(Exception);
IExecutionScope = interface; IExecutionScope = interface;
IScopeDescriptor = interface; IScopeDescriptor = interface;
IScopeLayout = interface; IScopeLayout = interface;
@@ -359,8 +356,8 @@ end;
function TScopeBuilder.Define(const Name: string): Integer; function TScopeBuilder.Define(const Name: string): Integer;
begin begin
// Rule: Shadowing / Redefinition in the same scope is forbidden. // Rule: Shadowing / Redefinition in the same scope is forbidden.
if FMap.ContainsKey(Name) then // The Client (Binder) must ensure this name is not already taken before calling Define.
raise EScopeException.CreateFmt('Variable "%s" is already defined in this scope.', [Name]); Assert(not FMap.ContainsKey(Name), 'Scope Error: Variable "' + Name + '" is already defined in this scope builder.');
// Allocate a new slot // Allocate a new slot
Result := FNextSlot; Result := FNextSlot;
@@ -574,6 +571,8 @@ var
targetScope: TExecutionScope; targetScope: TExecutionScope;
i: Integer; i: Integer;
begin begin
Assert(Address.Kind <> akUnresolved, 'Scope Error: Cannot capture an unresolved address.');
case Address.Kind of case Address.Kind of
akUpvalue: akUpvalue:
begin begin
@@ -600,8 +599,6 @@ begin
TMonitor.Exit(targetScope); TMonitor.Exit(targetScope);
end; end;
end; end;
else
raise EScopeException.Create('Cannot capture an unresolved address.');
end; end;
end; end;
@@ -613,10 +610,9 @@ begin
NeedNameToIndex; NeedNameToIndex;
id := GetNameID(Name); id := GetNameID(Name);
// NOTE: Runtime redefinition is generally restricted in this dynamic Define // NOTE: Runtime redefinition is generally restricted in this dynamic Define.
// (typically used for Root/Global definitions). // The Client must ensure uniqueness.
if FNameToIndex.ContainsKey(id) then Assert(not FNameToIndex.ContainsKey(id), 'Scope Error: Variable "' + Name + '" is already defined in this execution scope.');
raise EScopeException.CreateFmt('Variable "%s" is already defined in this scope.', [Name]);
index := Length(FValues); index := Length(FValues);
SetLength(FValues, index + 1); SetLength(FValues, index + 1);
@@ -719,6 +715,8 @@ function TExecutionScope.GetValues(const Address: TResolvedAddress): TDataValue;
var var
item: TScopeItem; item: TScopeItem;
begin begin
Assert(Address.Kind <> akUnresolved, 'Scope Error: Cannot get value for an unresolved address.');
case Address.Kind of case Address.Kind of
akUpvalue: akUpvalue:
begin begin
@@ -736,8 +734,6 @@ begin
else else
Result := item.Value; Result := item.Value;
end; end;
else
raise EScopeException.Create('Cannot get value for an unresolved address.');
end; end;
end; end;
@@ -795,6 +791,8 @@ var
targetScope: TExecutionScope; targetScope: TExecutionScope;
i: Integer; i: Integer;
begin begin
Assert(Address.Kind <> akUnresolved, 'Scope Error: Cannot set value for an unresolved address.');
case Address.Kind of case Address.Kind of
akUpvalue: akUpvalue:
begin begin
@@ -812,8 +810,6 @@ begin
else else
pItem.Value := Value; pItem.Value := Value;
end; end;
else
raise EScopeException.Create('Cannot set value for an unresolved address.');
end; end;
end; end;
+30 -95
View File
@@ -9,10 +9,6 @@ uses
Myc.Data.Keyword; Myc.Data.Keyword;
type type
// Exception for atomic type errors (promotion failures, invalid operations)
// Inherits from Exception directly to keep Types unit independent of Nodes/AST.
ETypeException = class(Exception);
IStaticType = interface; IStaticType = interface;
// Defines the categories of types available in the language. // Defines the categories of types available in the language.
@@ -68,8 +64,6 @@ type
property ElementType: IStaticType read GetElementType; property ElementType: IStaticType read GetElementType;
// The signatures (if Kind = stMethod). // The signatures (if Kind = stMethod).
// This array contains one entry for simple methods,
// and multiple entries for overloaded functions (like RTL).
property Signatures: TArray<IMethodSignature> read GetSignatures; property Signatures: TArray<IMethodSignature> read GetSignatures;
// The definition (if Kind = stRecord or stRecordSeries) // The definition (if Kind = stRecord or stRecordSeries)
@@ -84,7 +78,6 @@ type
end; end;
// Factory and Flyweight access for static types. // Factory and Flyweight access for static types.
// Use this record to create or access all IStaticType instances.
TTypes = record TTypes = record
private private
class var class var
@@ -105,7 +98,7 @@ type
FKeyword: IStaticType; FKeyword: IStaticType;
class constructor Create; class constructor Create;
public public
// Flyweight accessors for simple types // Flyweight accessors
class property Unknown: IStaticType read FUnknown; class property Unknown: IStaticType read FUnknown;
class property Void: IStaticType read FVoid; class property Void: IStaticType read FVoid;
class property Ordinal: IStaticType read FOrdinal; class property Ordinal: IStaticType read FOrdinal;
@@ -115,7 +108,7 @@ type
class property Text: IStaticType read FText; class property Text: IStaticType read FText;
class property Keyword: IStaticType read FKeyword; class property Keyword: IStaticType read FKeyword;
// Factory functions for complex types // Factory functions
class function CreateSeries(const AElementType: IStaticType): IStaticType; static; class function CreateSeries(const AElementType: IStaticType): IStaticType; static;
class function CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType; static; class function CreateMethod(const AParamTypes: TArray<IStaticType>; const AReturnType: IStaticType): IStaticType; static;
class function CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType; static; class function CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType; static;
@@ -129,14 +122,16 @@ type
// Defines the rules of the type system. // Defines the rules of the type system.
TTypeRules = record TTypeRules = record
public public
// Returns the common type (promotion) or nil if incompatible.
class function Promote(const A, B: IStaticType): IStaticType; static; class function Promote(const A, B: IStaticType): IStaticType; static;
// Checks if a value of type 'Source' can be assigned to 'Target'. // Checks if a value of type 'Source' can be assigned to 'Target'.
class function CanAssign(const Target, Source: IStaticType): Boolean; static; class function CanAssign(const Target, Source: IStaticType): Boolean; static;
// Determines the result type of a binary operation.
// Raises ETypeException on failure. // Determines the result type of a binary operation. Returns nil on failure.
class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; static; class function ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; static;
// Determines the result type of a unary operation.
// Determines the result type of a unary operation. Returns nil on failure.
class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; static; class function ResolveUnaryOp(Op: TScalar.TUnaryOp; const Right: IStaticType): IStaticType; static;
end; end;
@@ -185,7 +180,6 @@ end;
type type
TAbstractStaticType = class(TInterfacedObject, IStaticType) TAbstractStaticType = class(TInterfacedObject, IStaticType)
protected protected
// IStaticType (default implementations for non-applicable properties)
function GetKind: TStaticTypeKind; virtual; abstract; function GetKind: TStaticTypeKind; virtual; abstract;
function GetElementType: IStaticType; virtual; function GetElementType: IStaticType; virtual;
function GetSignatures: TArray<IMethodSignature>; virtual; function GetSignatures: TArray<IMethodSignature>; virtual;
@@ -208,7 +202,6 @@ end;
function TAbstractStaticType.GetDefinition: IScalarRecordDefinition; function TAbstractStaticType.GetDefinition: IScalarRecordDefinition;
begin begin
// Return an empty/invalid definition
Result := Default(IScalarRecordDefinition); Result := Default(IScalarRecordDefinition);
end; end;
@@ -219,8 +212,6 @@ end;
function TAbstractStaticType.IsEqual(const Other: IStaticType): Boolean; function TAbstractStaticType.IsEqual(const Other: IStaticType): Boolean;
begin begin
// Default implementation: types are equal if their kinds are equal.
// Complex types (Series, Record, Method) MUST override this.
if not Assigned(Other) then if not Assigned(Other) then
exit(False); exit(False);
Result := (GetKind = Other.Kind); Result := (GetKind = Other.Kind);
@@ -256,7 +247,6 @@ end;
function TSimpleStaticType.GetHashCode: Integer; function TSimpleStaticType.GetHashCode: Integer;
begin begin
// Simple types only hash their kind
Result := Ord(FKind); Result := Ord(FKind);
end; end;
@@ -298,11 +288,9 @@ end;
function TSeriesType.GetHashCode: Integer; function TSeriesType.GetHashCode: Integer;
begin begin
// Consistent with IsEqual
Result := Ord(stSeries); Result := Ord(stSeries);
if Assigned(FElementType) then if Assigned(FElementType) then
begin begin
// Combine hash of stSeries with hash of element type
var hash := FElementType.GetHashCode; var hash := FElementType.GetHashCode;
Result := THashBobJenkins.GetHashValue(Hash, SizeOf(Integer), Result); Result := THashBobJenkins.GetHashValue(Hash, SizeOf(Integer), Result);
end; end;
@@ -336,7 +324,6 @@ function TMethodSignature.GetHashCode: Integer;
var var
i: Integer; i: Integer;
begin begin
// Consistent with TMethodType.IsEqual
Result := 0; Result := 0;
if Assigned(FReturnType) then if Assigned(FReturnType) then
Result := FReturnType.GetHashCode; Result := FReturnType.GetHashCode;
@@ -409,11 +396,6 @@ begin
if Length(Self.FSignatures) <> Length(otherSigs) then if Length(Self.FSignatures) <> Length(otherSigs) then
exit(False); exit(False);
// This is complex. For now, assume equality means identical sets,
// which requires comparing O(N^2) signatures if order doesn't matter.
// Let's assume order *does* matter for equality to keep this simple (O(N)).
// Note: A better IsEqual would compare hashes of signatures.
for i := 0 to High(Self.FSignatures) do for i := 0 to High(Self.FSignatures) do
begin begin
var sig1 := Self.FSignatures[i]; var sig1 := Self.FSignatures[i];
@@ -438,7 +420,6 @@ function TMethodType.GetHashCode: Integer;
var var
sig: IMethodSignature; sig: IMethodSignature;
begin begin
// Consistent with IsEqual
Result := Ord(stMethod); Result := Ord(stMethod);
for sig in FSignatures do for sig in FSignatures do
begin begin
@@ -461,7 +442,6 @@ begin
end end
else else
begin begin
// Handle overloaded methods
sb := TStringBuilder.Create; sb := TStringBuilder.Create;
try try
sb.Append('Method{'); sb.Append('Method{');
@@ -471,7 +451,7 @@ begin
sb.Append(SignatureToString(sig)); sb.Append(SignatureToString(sig));
sb.Append('), '); sb.Append('), ');
end; end;
sb.Remove(sb.Length - 2, 2); // Remove last ', ' sb.Remove(sb.Length - 2, 2);
sb.Append('}'); sb.Append('}');
Result := sb.ToString; Result := sb.ToString;
finally finally
@@ -482,7 +462,6 @@ end;
// --- // ---
type type
// Represents stRecord and stRecordSeries (Scalar Records)
TRecordType = class(TAbstractStaticType) TRecordType = class(TAbstractStaticType)
private private
FKind: TStaticTypeKind; FKind: TStaticTypeKind;
@@ -523,14 +502,13 @@ begin
var otherDef := Other.Definition; var otherDef := Other.Definition;
if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then if (not Assigned(Self.FDefinition)) or (not Assigned(otherDef)) then
exit(False); // Should not happen if Kind is equal, but defensive check exit(False);
if Length(Self.FDefinition.Fields) <> Length(otherDef.Fields) then if Length(Self.FDefinition.Fields) <> Length(otherDef.Fields) then
exit(False); exit(False);
for i := 0 to High(Self.FDefinition.Fields) do for i := 0 to High(Self.FDefinition.Fields) do
begin begin
// Use fast interface comparison for Keys
if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key) or (Self.FDefinition.Fields[i].Value <> otherDef.Fields[i].Value) then if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key) or (Self.FDefinition.Fields[i].Value <> otherDef.Fields[i].Value) then
exit(False); exit(False);
end; end;
@@ -542,16 +520,13 @@ function TRecordType.GetHashCode: Integer;
var var
field: TPair<IKeyword, TScalar.TKind>; field: TPair<IKeyword, TScalar.TKind>;
begin begin
// Consistent with IsEqual
Result := Ord(GetKind); Result := Ord(GetKind);
if Assigned(FDefinition) then if Assigned(FDefinition) then
begin begin
for field in FDefinition.Fields do for field in FDefinition.Fields do
begin begin
// Hash the Keyword pointer (fast, from flyweight)
var hash := TEqualityComparer<IKeyword>.Default.GetHashCode(field.Key); var hash := TEqualityComparer<IKeyword>.Default.GetHashCode(field.Key);
Result := THashBobJenkins.GetHashValue(hash, SizeOf(Pointer), Result); Result := THashBobJenkins.GetHashValue(hash, SizeOf(Pointer), Result);
// Hash the TScalar.TKind value
var data := Ord(field.Value); var data := Ord(field.Value);
Result := THashBobJenkins.GetHashValue(data, SizeOf(Byte), Result); Result := THashBobJenkins.GetHashValue(data, SizeOf(Byte), Result);
end; end;
@@ -621,7 +596,6 @@ begin
for i := 0 to High(Self.FDefinition.Fields) do for i := 0 to High(Self.FDefinition.Fields) do
begin begin
// Compare keys (fast) and static types (must use IsEqual)
if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key) if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key)
or (not Self.FDefinition.Fields[i].Value.IsEqual(otherDef.Fields[i].Value)) then or (not Self.FDefinition.Fields[i].Value.IsEqual(otherDef.Fields[i].Value)) then
exit(False); exit(False);
@@ -634,16 +608,13 @@ function TGenericRecordType.GetHashCode: Integer;
var var
field: TPair<IKeyword, IStaticType>; field: TPair<IKeyword, IStaticType>;
begin begin
// Consistent with IsEqual
Result := Ord(stGenericRecord); Result := Ord(stGenericRecord);
if Assigned(FDefinition) then if Assigned(FDefinition) then
begin begin
for field in FDefinition.Fields do for field in FDefinition.Fields do
begin begin
// Hash the Keyword pointer (fast, from flyweight)
var data := TEqualityComparer<IKeyword>.Default.GetHashCode(field.Key); var data := TEqualityComparer<IKeyword>.Default.GetHashCode(field.Key);
Result := THashBobJenkins.GetHashValue(data, SizeOf(Pointer), Result); Result := THashBobJenkins.GetHashValue(data, SizeOf(Pointer), Result);
// Hash the IStaticType value
if Assigned(field.Value) then if Assigned(field.Value) then
begin begin
var hash := field.Value.GetHashCode; var hash := field.Value.GetHashCode;
@@ -674,7 +645,6 @@ end;
class constructor TTypes.Create; class constructor TTypes.Create;
begin begin
// Create the flyweight singletons
FUnknown := TSimpleStaticType.Create(stUnknown); FUnknown := TSimpleStaticType.Create(stUnknown);
FVoid := TSimpleStaticType.Create(stVoid); FVoid := TSimpleStaticType.Create(stVoid);
FOrdinal := TSimpleStaticType.Create(stOrdinal); FOrdinal := TSimpleStaticType.Create(stOrdinal);
@@ -690,7 +660,6 @@ var
sig: IMethodSignature; sig: IMethodSignature;
sigs: TArray<IMethodSignature>; sigs: TArray<IMethodSignature>;
begin begin
// This is now a convenience helper for CreateMethodSet
sig := TMethodSignature.Create(AParamTypes, AReturnType); sig := TMethodSignature.Create(AParamTypes, AReturnType);
SetLength(sigs, 1); SetLength(sigs, 1);
sigs[0] := sig; sigs[0] := sig;
@@ -699,7 +668,6 @@ end;
class function TTypes.CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType; class function TTypes.CreateMethodSet(const ASignatures: TArray<IMethodSignature>): IStaticType;
begin begin
// This is the new primary factory for method types
Result := TMethodType.Create(ASignatures); Result := TMethodType.Create(ASignatures);
end; end;
@@ -732,7 +700,8 @@ begin
TScalar.TKind.Boolean: Result := FBoolean; TScalar.TKind.Boolean: Result := FBoolean;
TScalar.TKind.DateTime: Result := FDateTime; TScalar.TKind.DateTime: Result := FDateTime;
else else
raise ETypeException.Create('Cannot convert invalid TScalar.TKind to TStaticType.'); Assert(False, 'Invalid Scalar Kind');
Result := nil;
end; end;
end; end;
@@ -740,39 +709,30 @@ end;
class function TTypeRules.CanAssign(const Target, Source: IStaticType): Boolean; class function TTypeRules.CanAssign(const Target, Source: IStaticType): Boolean;
begin begin
// Basic nil-check for safety.
if (not Assigned(Target)) or (not Assigned(Source)) then if (not Assigned(Target)) or (not Assigned(Source)) then
exit(False); exit(False);
// During inference, allow assignment involving Unknown
if (Target.Kind = stUnknown) or (Source.Kind = stUnknown) then if (Target.Kind = stUnknown) or (Source.Kind = stUnknown) then
exit(True); exit(True);
// Types are always assignable to themselves (identity).
if Target.IsEqual(Source) then if Target.IsEqual(Source) then
exit(True); exit(True);
// Allow assigning an integer (Ordinal) to a float variable.
if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then if (Target.Kind = stFloat) and (Source.Kind = stOrdinal) then
exit(True); exit(True);
// Allow assigning Boolean to Ordinal (0/1)
if (Target.Kind = stOrdinal) and (Source.Kind = stBoolean) then if (Target.Kind = stOrdinal) and (Source.Kind = stBoolean) then
exit(True); exit(True);
// Allow assigning DateTime to Float (TDateTime is Double)
if (Target.Kind = stFloat) and (Source.Kind = stDateTime) then if (Target.Kind = stFloat) and (Source.Kind = stDateTime) then
exit(True); exit(True);
// Allow discarding the return value of a method (e.g., in a 'do' block).
if (Target.Kind = stVoid) and (Source.Kind = stMethod) then if (Target.Kind = stVoid) and (Source.Kind = stMethod) then
exit(True); exit(True);
// Allow implicit conversion from Keyword (internally an index) to Ordinal.
if (Target.Kind = stOrdinal) and (Source.Kind = stKeyword) then if (Target.Kind = stOrdinal) and (Source.Kind = stKeyword) then
exit(True); exit(True);
// Default: Assignment is not allowed if no specific rule matches.
Result := False; Result := False;
end; end;
@@ -798,17 +758,12 @@ begin
if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then if (A.Kind = stOrdinal) and (B.Kind = stOrdinal) then
exit(TTypes.Ordinal); exit(TTypes.Ordinal);
// Implicit DateTime/Boolean logic via Ordinal/Float mapping? // If types are identical, return that type.
// If types are identical (incl. Keyword, Text, Boolean, DateTime), return that type.
if A.IsEqual(B) then if A.IsEqual(B) then
exit(A); exit(A);
// Cannot promote stGenericRecord or stRecord with anything other than itself/unknown // No promotion possible
if (A.Kind in [stRecord, stGenericRecord]) or (B.Kind in [stRecord, stGenericRecord]) then Result := nil;
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
// Cannot promote other combinations
raise ETypeException.CreateFmt('Cannot promote types %s and %s', [A.ToString, B.ToString]);
end; end;
class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType; class function TTypeRules.ResolveBinaryOp(Op: TScalar.TBinaryOp; const Left, Right: IStaticType): IStaticType;
@@ -816,65 +771,48 @@ var
promotedType: IStaticType; promotedType: IStaticType;
promotedKind: TStaticTypeKind; promotedKind: TStaticTypeKind;
begin begin
// 1. Determine the effective type after promotion, handling Unknown. promotedType := Promote(Left, Right);
try
promotedType := Promote(Left, Right); // Mismatch during promotion
except if not Assigned(promotedType) then
// If promotion fails (e.g., Text and Ordinal), the operation is invalid. exit(nil);
on E: ETypeException do
raise ETypeException.CreateFmt(
'Operator %s cannot be applied to %s and %s (promotion failed: %s)',
[Op.ToString, Left.ToString, Right.ToString, E.Message]);
end;
// If promotion results in Unknown, the result type is also Unknown for now.
if promotedType.Kind = stUnknown then if promotedType.Kind = stUnknown then
exit(TTypes.Unknown); exit(TTypes.Unknown);
// 2. Check if the operator is valid for the promoted type.
promotedKind := promotedType.Kind; promotedKind := promotedType.Kind;
case Op of case Op of
TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply: TScalar.TBinaryOp.Add, TScalar.TBinaryOp.Subtract, TScalar.TBinaryOp.Multiply:
begin begin
// Numeric operations require Ordinal, Float, or DateTime(as Float)
if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then
raise ETypeException exit(nil);
.CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]); Result := promotedType;
Result := promotedType; // Result has the promoted type
end; end;
TScalar.TBinaryOp.Divide: TScalar.TBinaryOp.Divide:
begin begin
// Division requires Ordinal or Float, but *always* results in Float
if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then
raise ETypeException exit(nil);
.CreateFmt('Operator %s requires Ordinal or Float, but got %s after promotion', [Op.ToString, promotedType.ToString]);
Result := TTypes.Float; Result := TTypes.Float;
end; end;
TScalar.TBinaryOp.Equal, TScalar.TBinaryOp.NotEqual: TScalar.TBinaryOp.Equal, TScalar.TBinaryOp.NotEqual:
begin begin
// Allow equality checks for Ordinal, Float, Keyword, Boolean, DateTime
if not (promotedKind in [stOrdinal, stFloat, stKeyword, stBoolean, stDateTime]) then if not (promotedKind in [stOrdinal, stFloat, stKeyword, stBoolean, stDateTime]) then
raise ETypeException.CreateFmt( exit(nil);
'Comparison operator %s requires scalar type, but got %s after promotion',
[Op.ToString, promotedType.ToString]);
// Result is always Boolean
Result := TTypes.Boolean; Result := TTypes.Boolean;
end; end;
TScalar.TBinaryOp.Less, TScalar.TBinaryOp.Greater, TScalar.TBinaryOp.LessOrEqual, TScalar.TBinaryOp.GreaterOrEqual: TScalar.TBinaryOp.Less, TScalar.TBinaryOp.Greater, TScalar.TBinaryOp.LessOrEqual, TScalar.TBinaryOp.GreaterOrEqual:
begin begin
// Comparison requires Ordinal or Float (Keywords are not ordered)
if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then if not (promotedKind in [stOrdinal, stFloat, stDateTime]) then
raise ETypeException.CreateFmt( exit(nil);
'Comparison operator %s requires ordered type (Ordinal, Float, DateTime), but got %s after promotion',
[Op.ToString, promotedType.ToString]);
Result := TTypes.Boolean; Result := TTypes.Boolean;
end; end;
else else
raise ETypeException.Create('Unknown binary operator for type resolution.'); // Operation not supported at the AST level (might need folding later, but here it implies invalid usage)
Result := nil;
end; end;
end; end;
@@ -884,28 +822,25 @@ var
begin begin
rightKind := Right.Kind; rightKind := Right.Kind;
// If operand is Unknown, result is Unknown.
if rightKind = stUnknown then if rightKind = stUnknown then
exit(TTypes.Unknown); exit(TTypes.Unknown);
case Op of case Op of
TScalar.TUnaryOp.Negate: TScalar.TUnaryOp.Negate:
begin begin
// Negation requires Ordinal or Float
if not (rightKind in [stOrdinal, stFloat]) then if not (rightKind in [stOrdinal, stFloat]) then
raise ETypeException.CreateFmt('Unary negation cannot be applied to %s', [Right.ToString]); exit(nil);
Result := Right; // Negation preserves type Result := Right;
end; end;
TScalar.TUnaryOp.Not: TScalar.TUnaryOp.Not:
begin begin
// Logical not only applies to Boolean (or Ordinal if treated as bool)
if not (rightKind in [stOrdinal, stBoolean]) then if not (rightKind in [stOrdinal, stBoolean]) then
raise ETypeException.CreateFmt('Logical not cannot be applied to %s', [Right.ToString]); exit(nil);
Result := TTypes.Boolean; Result := TTypes.Boolean;
end; end;
else else
raise ETypeException.Create('Unknown unary operator for type resolution.'); Result := nil;
end; end;
end; end;
+61 -38
View File
@@ -77,14 +77,13 @@ type
class function Unquote(const AExpression: IAstNode): IUnquoteNode; static; class function Unquote(const AExpression: IAstNode): IUnquoteNode; static;
class function UnquoteSplicing(const AExpression: IQuasiquoteNode): IUnquoteSplicingNode; static; class function UnquoteSplicing(const AExpression: IQuasiquoteNode): IUnquoteSplicingNode; static;
// (* UPDATED Factory: Added AIsTargetPure *)
class function FunctionCall( class function FunctionCall(
const ACallee: IAstNode; const ACallee: IAstNode;
const AArguments: TArray<IAstNode>; const AArguments: TArray<IAstNode>;
const AStaticType: IStaticType = nil; const AStaticType: IStaticType = nil;
const AIsTailCall: Boolean = False; const AIsTailCall: Boolean = False;
const AStaticTarget: TDataValue.TFunc = nil; const AStaticTarget: TDataValue.TFunc = nil;
const AIsTargetPure: Boolean = False // (* ADDED *) const AIsTargetPure: Boolean = False
): IFunctionCallNode; static; ): IFunctionCallNode; static;
class function MacroExpansionNode( class function MacroExpansionNode(
@@ -193,7 +192,7 @@ type
FDescriptor: IScopeDescriptor; FDescriptor: IScopeDescriptor;
FUpvalues: TArray<TResolvedAddress>; FUpvalues: TArray<TResolvedAddress>;
FHasNestedLambdas: Boolean; FHasNestedLambdas: Boolean;
FIsPure: Boolean; // (* ADDED *) FIsPure: Boolean;
function GetParameters: TArray<IIdentifierNode>; function GetParameters: TArray<IIdentifierNode>;
function GetBody: IAstNode; function GetBody: IAstNode;
@@ -201,7 +200,7 @@ type
function GetDescriptor: IScopeDescriptor; function GetDescriptor: IScopeDescriptor;
function GetUpvalues: TArray<TResolvedAddress>; function GetUpvalues: TArray<TResolvedAddress>;
function GetHasNestedLambdas: Boolean; function GetHasNestedLambdas: Boolean;
function GetIsPure: Boolean; // (* ADDED *) function GetIsPure: Boolean;
function GetKind: TAstNodeKind; override; function GetKind: TAstNodeKind; override;
public public
constructor Create( constructor Create(
@@ -212,7 +211,7 @@ type
const ADescriptor: IScopeDescriptor; const ADescriptor: IScopeDescriptor;
const AUpvalues: TArray<TResolvedAddress>; const AUpvalues: TArray<TResolvedAddress>;
const AHasNestedLambdas: Boolean; const AHasNestedLambdas: Boolean;
const AIsPure: Boolean // (* ADDED *) const AIsPure: Boolean
); );
destructor Destroy; override; destructor Destroy; override;
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
@@ -225,12 +224,12 @@ type
FArguments: TArray<IAstNode>; FArguments: TArray<IAstNode>;
FIsTailCall: Boolean; FIsTailCall: Boolean;
FStaticTarget: TDataValue.TFunc; FStaticTarget: TDataValue.TFunc;
FIsTargetPure: Boolean; // (* ADDED *) FIsTargetPure: Boolean;
function GetCallee: IAstNode; function GetCallee: IAstNode;
function GetArguments: TArray<IAstNode>; function GetArguments: TArray<IAstNode>;
function GetIsTailCall: Boolean; function GetIsTailCall: Boolean;
function GetStaticTarget: TDataValue.TFunc; function GetStaticTarget: TDataValue.TFunc;
function GetIsTargetPure: Boolean; // (* ADDED *) function GetIsTargetPure: Boolean;
function GetKind: TAstNodeKind; override; function GetKind: TAstNodeKind; override;
public public
constructor Create( constructor Create(
@@ -239,7 +238,7 @@ type
const AStaticType: IStaticType; const AStaticType: IStaticType;
const AIsTailCall: Boolean; const AIsTailCall: Boolean;
const AStaticTarget: TDataValue.TFunc; const AStaticTarget: TDataValue.TFunc;
const AIsTargetPure: Boolean // (* ADDED *) const AIsTargetPure: Boolean
); );
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
function AsFunctionCall: IFunctionCallNode; override; function AsFunctionCall: IFunctionCallNode; override;
@@ -677,7 +676,7 @@ class function TAst.FunctionCall(
const AStaticType: IStaticType = nil; const AStaticType: IStaticType = nil;
const AIsTailCall: Boolean = False; const AIsTailCall: Boolean = False;
const AStaticTarget: TDataValue.TFunc = nil; const AStaticTarget: TDataValue.TFunc = nil;
const AIsTargetPure: Boolean = False // (* ADDED *) const AIsTargetPure: Boolean = False
): IFunctionCallNode; ): IFunctionCallNode;
begin begin
Result := Result :=
@@ -753,7 +752,7 @@ class function TAst.LambdaExpr(
const ADescriptor: IScopeDescriptor = nil; const ADescriptor: IScopeDescriptor = nil;
const AUpvalues: TArray<TResolvedAddress> = nil; const AUpvalues: TArray<TResolvedAddress> = nil;
const AHasNestedLambdas: Boolean = False; const AHasNestedLambdas: Boolean = False;
const AIsPure: Boolean = False; // (* ADDED *) const AIsPure: Boolean = False;
const AStaticType: IStaticType = nil const AStaticType: IStaticType = nil
): ILambdaExpressionNode; ): ILambdaExpressionNode;
begin begin
@@ -917,122 +916,146 @@ end;
function TAstNode.AsAddSeriesItem: IAddSeriesItemNode; function TAstNode.AsAddSeriesItem: IAddSeriesItemNode;
begin begin
raise ETypeException.Create('Node is not an AddSeriesItem'); Assert(False, 'Node is not an AddSeriesItem');
Result := nil;
end; end;
function TAstNode.AsAssignment: IAssignmentNode; function TAstNode.AsAssignment: IAssignmentNode;
begin begin
raise ETypeException.Create('Node is not an Assignment'); Assert(False, 'Node is not an Assignment');
Result := nil;
end; end;
function TAstNode.AsBlockExpression: IBlockExpressionNode; function TAstNode.AsBlockExpression: IBlockExpressionNode;
begin begin
raise ETypeException.Create('Node is not a BlockExpression'); Assert(False, 'Node is not a BlockExpression');
Result := nil;
end; end;
function TAstNode.AsConstant: IConstantNode; function TAstNode.AsConstant: IConstantNode;
begin begin
raise ETypeException.Create('Node is not a Constant'); Assert(False, 'Node is not a Constant');
Result := nil;
end; end;
function TAstNode.AsCreateSeries: ICreateSeriesNode; function TAstNode.AsCreateSeries: ICreateSeriesNode;
begin begin
raise ETypeException.Create('Node is not a CreateSeries'); Assert(False, 'Node is not a CreateSeries');
Result := nil;
end; end;
function TAstNode.AsFunctionCall: IFunctionCallNode; function TAstNode.AsFunctionCall: IFunctionCallNode;
begin begin
raise ETypeException.Create('Node is not a FunctionCall'); Assert(False, 'Node is not a FunctionCall');
Result := nil;
end; end;
function TAstNode.AsIdentifier: IIdentifierNode; function TAstNode.AsIdentifier: IIdentifierNode;
begin begin
raise ETypeException.Create('Node is not an Identifier'); Assert(False, 'Node is not an Identifier');
Result := nil;
end; end;
function TAstNode.AsIfExpression: IIfExpressionNode; function TAstNode.AsIfExpression: IIfExpressionNode;
begin begin
raise ETypeException.Create('Node is not an IfExpression'); Assert(False, 'Node is not an IfExpression');
Result := nil;
end; end;
function TAstNode.AsIndexer: IIndexerNode; function TAstNode.AsIndexer: IIndexerNode;
begin begin
raise ETypeException.Create('Node is not an Indexer'); Assert(False, 'Node is not an Indexer');
Result := nil;
end; end;
function TAstNode.AsKeyword: IKeywordNode; function TAstNode.AsKeyword: IKeywordNode;
begin begin
raise ETypeException.Create('Node is not a Keyword'); Assert(False, 'Node is not a Keyword');
Result := nil;
end; end;
function TAstNode.AsLambdaExpression: ILambdaExpressionNode; function TAstNode.AsLambdaExpression: ILambdaExpressionNode;
begin begin
raise ETypeException.Create('Node is not a LambdaExpression'); Assert(False, 'Node is not a LambdaExpression');
Result := nil;
end; end;
function TAstNode.AsMacroDefinition: IMacroDefinitionNode; function TAstNode.AsMacroDefinition: IMacroDefinitionNode;
begin begin
raise ETypeException.Create('Node is not a MacroDefinition'); Assert(False, 'Node is not a MacroDefinition');
Result := nil;
end; end;
function TAstNode.AsMacroExpansion: IMacroExpansionNode; function TAstNode.AsMacroExpansion: IMacroExpansionNode;
begin begin
raise ETypeException.Create('Node is not a MacroExpansion'); Assert(False, 'Node is not a MacroExpansion');
Result := nil;
end; end;
function TAstNode.AsMemberAccess: IMemberAccessNode; function TAstNode.AsMemberAccess: IMemberAccessNode;
begin begin
raise ETypeException.Create('Node is not a MemberAccess'); Assert(False, 'Node is not a MemberAccess');
Result := nil;
end; end;
function TAstNode.AsNop: INopNode; function TAstNode.AsNop: INopNode;
begin begin
raise ETypeException.Create('Node is not a Nop'); Assert(False, 'Node is not a Nop');
Result := nil;
end; end;
function TAstNode.AsQuasiquote: IQuasiquoteNode; function TAstNode.AsQuasiquote: IQuasiquoteNode;
begin begin
raise ETypeException.Create('Node is not a Quasiquote'); Assert(False, 'Node is not a Quasiquote');
Result := nil;
end; end;
function TAstNode.AsRecordLiteral: IRecordLiteralNode; function TAstNode.AsRecordLiteral: IRecordLiteralNode;
begin begin
raise ETypeException.Create('Node is not a RecordLiteral'); Assert(False, 'Node is not a RecordLiteral');
Result := nil;
end; end;
function TAstNode.AsRecur: IRecurNode; function TAstNode.AsRecur: IRecurNode;
begin begin
raise ETypeException.Create('Node is not a Recur'); Assert(False, 'Node is not a Recur');
Result := nil;
end; end;
function TAstNode.AsSeriesLength: ISeriesLengthNode; function TAstNode.AsSeriesLength: ISeriesLengthNode;
begin begin
raise ETypeException.Create('Node is not a SeriesLength'); Assert(False, 'Node is not a SeriesLength');
Result := nil;
end; end;
function TAstNode.AsTernaryExpression: ITernaryExpressionNode; function TAstNode.AsTernaryExpression: ITernaryExpressionNode;
begin begin
raise ETypeException.Create('Node is not a TernaryExpression'); Assert(False, 'Node is not a TernaryExpression');
Result := nil;
end; end;
function TAstNode.AsTypedNode: IAstTypedNode; function TAstNode.AsTypedNode: IAstTypedNode;
begin begin
raise ETypeException.Create('Node has no type'); Assert(False, 'Node has no type');
Result := nil;
end; end;
function TAstNode.AsUnquote: IUnquoteNode; function TAstNode.AsUnquote: IUnquoteNode;
begin begin
raise ETypeException.Create('Node is not an Unquote'); Assert(False, 'Node is not an Unquote');
Result := nil;
end; end;
function TAstNode.AsUnquoteSplicing: IUnquoteSplicingNode; function TAstNode.AsUnquoteSplicing: IUnquoteSplicingNode;
begin begin
raise ETypeException.Create('Node is not an UnquoteSplicing'); Assert(False, 'Node is not an UnquoteSplicing');
Result := nil;
end; end;
function TAstNode.AsVariableDeclaration: IVariableDeclarationNode; function TAstNode.AsVariableDeclaration: IVariableDeclarationNode;
begin begin
raise ETypeException.Create('Node is not a VariableDeclaration'); Assert(False, 'Node is not a VariableDeclaration');
Result := nil;
end; end;
function TAstNode.GetIsTyped: Boolean; function TAstNode.GetIsTyped: Boolean;
@@ -1244,7 +1267,7 @@ constructor TLambdaExpressionNode.Create(
const ADescriptor: IScopeDescriptor; const ADescriptor: IScopeDescriptor;
const AUpvalues: TArray<TResolvedAddress>; const AUpvalues: TArray<TResolvedAddress>;
const AHasNestedLambdas: Boolean; const AHasNestedLambdas: Boolean;
const AIsPure: Boolean // (* ADDED *) const AIsPure: Boolean
); );
begin begin
inherited Create(AStaticType); inherited Create(AStaticType);
@@ -1306,7 +1329,7 @@ begin
Result := FHasNestedLambdas; Result := FHasNestedLambdas;
end; end;
function TLambdaExpressionNode.GetIsPure: Boolean; // (* ADDED *) function TLambdaExpressionNode.GetIsPure: Boolean;
begin begin
Result := FIsPure; Result := FIsPure;
end; end;
@@ -1448,7 +1471,7 @@ constructor TFunctionCallNode.Create(
const AStaticType: IStaticType; const AStaticType: IStaticType;
const AIsTailCall: Boolean; const AIsTailCall: Boolean;
const AStaticTarget: TDataValue.TFunc; const AStaticTarget: TDataValue.TFunc;
const AIsTargetPure: Boolean // (* ADDED *) const AIsTargetPure: Boolean
); );
begin begin
inherited Create(AStaticType); inherited Create(AStaticType);
@@ -1494,7 +1517,7 @@ begin
Result := FStaticTarget; Result := FStaticTarget;
end; end;
function TFunctionCallNode.GetIsTargetPure: Boolean; // (* ADDED *) function TFunctionCallNode.GetIsTargetPure: Boolean;
begin begin
Result := FIsTargetPure; Result := FIsTargetPure;
end; end;
+1 -1
View File
@@ -154,7 +154,7 @@ begin
// (do (def a 1) (def a 2)) - Forbidden! // (do (def a 1) (def a 2)) - Forbidden!
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(1)), TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(2))]); root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(1)), TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(2))]);
Assert.WillRaise(procedure begin Bind(root, layout); end, Exception, 'Binder must forbid redefinition in the same scope.'); Assert.WillRaise(procedure begin Bind(root, layout); end, EBinderException, 'Binder must forbid redefinition in the same scope.');
end; end;
// ... (Rest of validation tests remain identical) // ... (Rest of validation tests remain identical)
+1
View File
@@ -39,6 +39,7 @@ type
procedure Scope_DynamicGrowth_ResizesValuesArray; procedure Scope_DynamicGrowth_ResizesValuesArray;
[Test] [Test]
[IgnoreMemoryLeaks]
procedure Scope_Define_DuplicateName_ThrowsException; procedure Scope_Define_DuplicateName_ThrowsException;
// --- Group 3: Hierarchy & Shadowing (Parameterized Depth) --- // --- Group 3: Hierarchy & Shadowing (Parameterized Depth) ---