Generic Visitors

This commit is contained in:
Michael Schimmel
2026-01-03 19:14:18 +01:00
parent db74b83e11
commit 22674b962b
27 changed files with 3573 additions and 3547 deletions
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -42,7 +42,8 @@ uses
Myc.Data.Stream.Pipes in '..\Src\Data\Myc.Data.Stream.Pipes.pas', Myc.Data.Stream.Pipes in '..\Src\Data\Myc.Data.Stream.Pipes.pas',
Myc.Data.Stream in '..\Src\Data\Myc.Data.Stream.pas', Myc.Data.Stream in '..\Src\Data\Myc.Data.Stream.pas',
Myc.Fmx.AstEditor.Handlers.Pipes in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Pipes.pas', Myc.Fmx.AstEditor.Handlers.Pipes in '..\Src\AST\Myc.Fmx.AstEditor.Handlers.Pipes.pas',
Demo.Finance in '..\Test\Demo.Finance.pas'; Demo.Finance in '..\Test\Demo.Finance.pas',
Myc.Ast.Script.Print in '..\Src\AST\Myc.Ast.Script.Print.pas';
{$R *.res} {$R *.res}
+1
View File
@@ -173,6 +173,7 @@
<DCCReference Include="..\Src\Data\Myc.Data.Stream.pas"/> <DCCReference Include="..\Src\Data\Myc.Data.Stream.pas"/>
<DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Pipes.pas"/> <DCCReference Include="..\Src\AST\Myc.Fmx.AstEditor.Handlers.Pipes.pas"/>
<DCCReference Include="..\Test\Demo.Finance.pas"/> <DCCReference Include="..\Test\Demo.Finance.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Script.Print.pas"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>
+185 -98
View File
@@ -7,58 +7,65 @@ uses
Myc.Data.Value, Myc.Data.Value,
Myc.Ast.Nodes, Myc.Ast.Nodes,
Myc.Ast.Visitor, Myc.Ast.Visitor,
Myc.Ast.Scope; Myc.Ast.Scope,
Myc.Ast;
type type
/// <summary> // Analyzes an AST to determine if it is referentially transparent and free of side effects.
/// Analyzes an AST to determine if it is referentially transparent and free of side effects.
/// </summary>
TPurityAnalyzer = class(TAstVisitor<Boolean>) TPurityAnalyzer = class(TAstVisitor<Boolean>)
protected strict private
// Default behavior: Visit children. If all children return True, then True.
// However, we must explicitly define what is allowed.
function Accept(const Node: IAstNode): Boolean; override;
// --- Safe Leaves / Constructs --- // --- Safe Leaves / Constructs ---
function VisitConstant(const Node: IConstantNode): Boolean; override; function VisitConstant(const Node: IAstNode): Boolean;
function VisitKeyword(const Node: IKeywordNode): Boolean; override; function VisitKeyword(const Node: IAstNode): Boolean;
function VisitIfExpression(const Node: IIfExpressionNode): Boolean; override; function VisitIfExpression(const Node: IAstNode): Boolean;
function VisitCondExpression(const Node: ICondExpressionNode): Boolean; override; // Replaces Ternary function VisitCondExpression(const Node: IAstNode): Boolean;
function VisitBlockExpression(const Node: IBlockExpressionNode): Boolean; override; function VisitBlockExpression(const Node: IAstNode): Boolean;
function VisitRecordLiteral(const Node: IRecordLiteralNode): Boolean; override; function VisitRecordLiteral(const Node: IAstNode): Boolean;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): Boolean; override; function VisitVariableDeclaration(const Node: IAstNode): Boolean;
function VisitSeriesLength(const Node: ISeriesLengthNode): Boolean; override; function VisitSeriesLength(const Node: IAstNode): Boolean;
// --- List Visitors (New) --- // --- List Visitors (Aggregation Logic: All must be pure) ---
function VisitParameterList(const Node: IParameterList): Boolean; override; function VisitParameterList(const Node: IAstNode): Boolean;
function VisitArgumentList(const Node: IArgumentList): Boolean; override; function VisitArgumentList(const Node: IAstNode): Boolean;
function VisitExpressionList(const Node: IExpressionList): Boolean; override; function VisitExpressionList(const Node: IAstNode): Boolean;
function VisitRecordFieldList(const Node: IRecordFieldList): Boolean; override; function VisitRecordFieldList(const Node: IAstNode): Boolean;
function VisitRecordField(const Node: IRecordFieldNode): Boolean; override; function VisitRecordField(const Node: IAstNode): Boolean;
// --- Critical Checks --- // --- Critical Checks ---
function VisitIdentifier(const Node: IIdentifierNode): Boolean; override; function VisitIdentifier(const Node: IAstNode): Boolean;
function VisitFunctionCall(const Node: IFunctionCallNode): Boolean; override; function VisitFunctionCall(const Node: IAstNode): Boolean;
function VisitRecurNode(const Node: IRecurNode): Boolean; override; function VisitRecurNode(const Node: IAstNode): Boolean;
// --- Forbidden Constructs (Side Effects / Unsafe) --- // --- Forbidden Constructs (Side Effects / Unsafe) ---
function VisitAssignment(const Node: IAssignmentNode): Boolean; override; function VisitAssignment(const Node: IAstNode): Boolean;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): Boolean; override; function VisitAddSeriesItem(const Node: IAstNode): Boolean;
// Allocation is considered pure in this context (it creates a new value, doesn't mutate existing world) // Allocation is considered pure in this context
function VisitCreateSeries(const Node: ICreateSeriesNode): Boolean; override; function VisitCreateSeries(const Node: IAstNode): Boolean;
function VisitIndexer(const Node: IIndexerNode): Boolean; override; function VisitIndexer(const Node: IAstNode): Boolean;
function VisitMemberAccess(const Node: IMemberAccessNode): Boolean; override; function VisitMemberAccess(const Node: IAstNode): Boolean;
// Ignored / Irrelevant for Runtime Purity (Compile-time constructs or Definitions) // Ignored / Irrelevant for Runtime Purity
function VisitLambdaExpression(const Node: ILambdaExpressionNode): Boolean; override; function VisitLambdaExpression(const Node: IAstNode): Boolean;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): Boolean; override; function VisitMacroDefinition(const Node: IAstNode): Boolean;
function VisitQuasiquote(const Node: IQuasiquoteNode): Boolean; override; function VisitQuasiquote(const Node: IAstNode): Boolean;
function VisitUnquote(const Node: IUnquoteNode): Boolean; override; function VisitUnquote(const Node: IAstNode): Boolean;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): Boolean; override; function VisitUnquoteSplicing(const Node: IAstNode): Boolean;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): Boolean; override; function VisitMacroExpansionNode(const Node: IAstNode): Boolean;
function VisitNop(const Node: INopNode): Boolean; override; function VisitNop(const Node: IAstNode): Boolean;
// Pipe Support (Structural Check)
function VisitPipeInput(const Node: IAstNode): Boolean;
function VisitPipeSelectorList(const Node: IAstNode): Boolean;
function VisitPipeInputList(const Node: IAstNode): Boolean;
function VisitPipe(const Node: IAstNode): Boolean;
// Helper to check if a node is pure (handling nil gracefully)
function IsNodePure(const Node: IAstNode): Boolean;
protected
procedure SetupHandlers; override;
public public
class function IsPure(const RootNode: IAstNode): Boolean; class function IsPure(const RootNode: IAstNode): Boolean;
@@ -72,80 +79,125 @@ class function TPurityAnalyzer.IsPure(const RootNode: IAstNode): Boolean;
begin begin
var analyzer := TPurityAnalyzer.Create; var analyzer := TPurityAnalyzer.Create;
try try
Result := analyzer.Accept(RootNode); // Use the visitor's central dispatch method
Result := analyzer.Visit(RootNode);
finally finally
analyzer.Free; analyzer.Free;
end; end;
end; end;
function TPurityAnalyzer.Accept(const Node: IAstNode): Boolean; function TPurityAnalyzer.IsNodePure(const Node: IAstNode): Boolean;
begin begin
// If a node is nil (e.g. optional else-branch), it's "nothing", which is pure. // If a node is nil (e.g. optional else-branch), it's "nothing", which is pure.
if not Assigned(Node) then if not Assigned(Node) then
exit(True); exit(True);
// Dispatch to specific Visit method via generic base // Dispatch to registry
Result := Node.Accept(Self).AsGeneric<Boolean>; Result := Visit(Node);
end;
procedure TPurityAnalyzer.SetupHandlers;
begin
// Core
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
// Lists
Register(akParameterList, VisitParameterList);
Register(akArgumentList, VisitArgumentList);
Register(akExpressionList, VisitExpressionList);
Register(akRecordFieldList, VisitRecordFieldList);
Register(akRecordField, VisitRecordField);
// Structural
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akMacroExpansion, VisitMacroExpansionNode);
Register(akBlockExpression, VisitBlockExpression);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akMacroDefinition, VisitMacroDefinition);
Register(akQuasiquote, VisitQuasiquote);
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akCreateSeries, VisitCreateSeries);
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
// Pipes
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end; end;
// --- List Visitors --- // --- List Visitors ---
function TPurityAnalyzer.VisitParameterList(const Node: IParameterList): Boolean; function TPurityAnalyzer.VisitParameterList(const Node: IAstNode): Boolean;
begin begin
// Declarations are pure // Declarations are pure
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitArgumentList(const Node: IArgumentList): Boolean; function TPurityAnalyzer.VisitArgumentList(const Node: IAstNode): Boolean;
begin begin
for var item in Node do for var item in Node.AsArgumentList do
if not Accept(item) then if not IsNodePure(item) then
exit(False); exit(False);
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitExpressionList(const Node: IExpressionList): Boolean; function TPurityAnalyzer.VisitExpressionList(const Node: IAstNode): Boolean;
begin begin
for var item in Node do for var item in Node.AsExpressionList do
if not Accept(item) then if not IsNodePure(item) then
exit(False); exit(False);
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitRecordFieldList(const Node: IRecordFieldList): Boolean; function TPurityAnalyzer.VisitRecordFieldList(const Node: IAstNode): Boolean;
begin begin
for var item in Node do for var item in Node.AsRecordFieldList do
if not Accept(item) then if not IsNodePure(item) then
exit(False); exit(False);
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitRecordField(const Node: IRecordFieldNode): Boolean; function TPurityAnalyzer.VisitRecordField(const Node: IAstNode): Boolean;
begin begin
// Key is usually pure (Keyword), check Value // Key is usually pure (Keyword), check Value
Result := Accept(Node.Key) and Accept(Node.Value); var F := Node.AsRecordField;
Result := IsNodePure(F.Key) and IsNodePure(F.Value);
end; end;
// --- Safe Leaves --- // --- Safe Leaves ---
function TPurityAnalyzer.VisitConstant(const Node: IConstantNode): Boolean; function TPurityAnalyzer.VisitConstant(const Node: IAstNode): Boolean;
begin begin
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitKeyword(const Node: IKeywordNode): Boolean; function TPurityAnalyzer.VisitKeyword(const Node: IAstNode): Boolean;
begin begin
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitNop(const Node: INopNode): Boolean; function TPurityAnalyzer.VisitNop(const Node: IAstNode): Boolean;
begin begin
Result := True; Result := True;
end; end;
// --- Identifier: Only local variables are safe --- // --- Identifier: Only local variables are safe ---
function TPurityAnalyzer.VisitIdentifier(const Node: IIdentifierNode): Boolean; function TPurityAnalyzer.VisitIdentifier(const Node: IAstNode): Boolean;
begin begin
// We only allow access to local variables (ScopeDepth = 0). // We only allow access to local variables (ScopeDepth = 0).
// Accessing Parent/Upvalues (ScopeDepth > 0) makes the function state-dependent (closure state), // Accessing Parent/Upvalues (ScopeDepth > 0) makes the function state-dependent (closure state),
@@ -153,82 +205,84 @@ begin
// unless we could prove the upvalue is constant (which we don't track yet). // unless we could prove the upvalue is constant (which we don't track yet).
// Note: Parameters are also ScopeDepth=0 in the Binder logic. // Note: Parameters are also ScopeDepth=0 in the Binder logic.
Result := (Node.Address.Kind = akLocalOrParent) and (Node.Address.ScopeDepth = 0); var I := Node.AsIdentifier;
Result := (I.Address.Kind = akLocalOrParent) and (I.Address.ScopeDepth = 0);
end; end;
// --- Function Call: The Core Logic --- // --- Function Call: The Core Logic ---
function TPurityAnalyzer.VisitFunctionCall(const Node: IFunctionCallNode): Boolean; function TPurityAnalyzer.VisitFunctionCall(const Node: IAstNode): Boolean;
begin begin
var C := Node.AsFunctionCall;
// 1. The target function MUST be marked as Pure (from RTL or previous inference). // 1. The target function MUST be marked as Pure (from RTL or previous inference).
if not Node.IsTargetPure then if not C.IsTargetPure then
exit(False); exit(False);
// 2. All arguments must be pure expressions. // 2. All arguments must be pure expressions.
// Delegate to ArgumentList visitor Result := IsNodePure(C.Arguments);
Result := Accept(Node.Arguments);
end; end;
// --- Recursion --- // --- Recursion ---
function TPurityAnalyzer.VisitRecurNode(const Node: IRecurNode): Boolean; function TPurityAnalyzer.VisitRecurNode(const Node: IAstNode): Boolean;
begin begin
// 'recur' is just control flow. It is pure if its arguments are pure. // 'recur' is just control flow. It is pure if its arguments are pure.
Result := Accept(Node.Arguments); Result := IsNodePure(Node.AsRecur.Arguments);
end; end;
// --- Structures: Recursive Checks --- // --- Structures: Recursive Checks ---
function TPurityAnalyzer.VisitIfExpression(const Node: IIfExpressionNode): Boolean; function TPurityAnalyzer.VisitIfExpression(const Node: IAstNode): Boolean;
begin begin
Result := Accept(Node.Condition) and Accept(Node.ThenBranch) and Accept(Node.ElseBranch); var E := Node.AsIfExpression;
Result := IsNodePure(E.Condition) and IsNodePure(E.ThenBranch) and IsNodePure(E.ElseBranch);
end; end;
function TPurityAnalyzer.VisitCondExpression(const Node: ICondExpressionNode): Boolean; function TPurityAnalyzer.VisitCondExpression(const Node: IAstNode): Boolean;
var
pair: TCondPair;
begin begin
var E := Node.AsCondExpression;
// All conditions and all branches must be pure // All conditions and all branches must be pure
for pair in Node.Pairs do for var pair in E.Pairs do
begin begin
if not (Accept(pair.Condition) and Accept(pair.Branch)) then if not (IsNodePure(pair.Condition) and IsNodePure(pair.Branch)) then
Exit(False); Exit(False);
end; end;
// And the Else branch // And the Else branch
Result := Accept(Node.ElseBranch); Result := IsNodePure(E.ElseBranch);
end; end;
function TPurityAnalyzer.VisitBlockExpression(const Node: IBlockExpressionNode): Boolean; function TPurityAnalyzer.VisitBlockExpression(const Node: IAstNode): Boolean;
begin begin
// Delegate to ExpressionList // Delegate to ExpressionList
Result := Accept(Node.Expressions); Result := IsNodePure(Node.AsBlockExpression.Expressions);
end; end;
function TPurityAnalyzer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): Boolean; function TPurityAnalyzer.VisitVariableDeclaration(const Node: IAstNode): Boolean;
begin begin
// 'def x = ...' is locally pure if the initializer is pure. // 'def x = ...' is locally pure if the initializer is pure.
// It mutates the local scope (stack), but that is contained within the function execution. // It mutates the local scope (stack), but that is contained within the function execution.
Result := Accept(Node.Initializer); Result := IsNodePure(Node.AsVariableDeclaration.Initializer);
end; end;
function TPurityAnalyzer.VisitRecordLiteral(const Node: IRecordLiteralNode): Boolean; function TPurityAnalyzer.VisitRecordLiteral(const Node: IAstNode): Boolean;
begin begin
// Delegate to RecordFieldList // Delegate to RecordFieldList
Result := Accept(Node.Fields); Result := IsNodePure(Node.AsRecordLiteral.Fields);
end; end;
function TPurityAnalyzer.VisitIndexer(const Node: IIndexerNode): Boolean; function TPurityAnalyzer.VisitIndexer(const Node: IAstNode): Boolean;
begin begin
// Reading from a structure is pure if the indices/base are pure. // Reading from a structure is pure if the indices/base are pure.
Result := Accept(Node.Base) and Accept(Node.Index); var I := Node.AsIndexer;
Result := IsNodePure(I.Base) and IsNodePure(I.Index);
end; end;
function TPurityAnalyzer.VisitMemberAccess(const Node: IMemberAccessNode): Boolean; function TPurityAnalyzer.VisitMemberAccess(const Node: IAstNode): Boolean;
begin begin
Result := Accept(Node.Base); Result := IsNodePure(Node.AsMemberAccess.Base);
end; end;
function TPurityAnalyzer.VisitSeriesLength(const Node: ISeriesLengthNode): Boolean; function TPurityAnalyzer.VisitSeriesLength(const Node: IAstNode): Boolean;
begin begin
// Querying length is pure. // Querying length is pure.
// The series identifier check happens in VisitIdentifier. // The series identifier check happens in VisitIdentifier.
@@ -237,19 +291,19 @@ end;
// --- Forbidden (Impure) --- // --- Forbidden (Impure) ---
function TPurityAnalyzer.VisitAssignment(const Node: IAssignmentNode): Boolean; function TPurityAnalyzer.VisitAssignment(const Node: IAstNode): Boolean;
begin begin
// Mutation of variables is defined as impure. // Mutation of variables is defined as impure.
Result := False; Result := False;
end; end;
function TPurityAnalyzer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): Boolean; function TPurityAnalyzer.VisitAddSeriesItem(const Node: IAstNode): Boolean;
begin begin
// Mutation of a series (side effect). // Mutation of a series (side effect).
Result := False; Result := False;
end; end;
function TPurityAnalyzer.VisitCreateSeries(const Node: ICreateSeriesNode): Boolean; function TPurityAnalyzer.VisitCreateSeries(const Node: IAstNode): Boolean;
begin begin
// Creating a NEW object is considered pure in this context, // Creating a NEW object is considered pure in this context,
// as it does not mutate existing global state. // as it does not mutate existing global state.
@@ -258,37 +312,70 @@ end;
// --- Irrelevant / Nested (Definitions are pure, execution logic checked separately) --- // --- Irrelevant / Nested (Definitions are pure, execution logic checked separately) ---
function TPurityAnalyzer.VisitLambdaExpression(const Node: ILambdaExpressionNode): Boolean; function TPurityAnalyzer.VisitLambdaExpression(const Node: IAstNode): Boolean;
begin begin
// Defining a function is a pure operation. // Defining a function is a pure operation.
// Whether the function itself is pure when executed is determined when *that* function is compiled. // Whether the function itself is pure when executed is determined when *that* function is compiled.
Result := True; Result := True;
end; end;
function TPurityAnalyzer.VisitMacroDefinition(const Node: IMacroDefinitionNode): Boolean; function TPurityAnalyzer.VisitMacroDefinition(const Node: IAstNode): Boolean;
begin begin
Result := True; // Compile-time construct Result := True; // Compile-time construct
end; end;
function TPurityAnalyzer.VisitQuasiquote(const Node: IQuasiquoteNode): Boolean; function TPurityAnalyzer.VisitQuasiquote(const Node: IAstNode): Boolean;
begin begin
Result := True; // Structural construction Result := True; // Structural construction
end; end;
function TPurityAnalyzer.VisitUnquote(const Node: IUnquoteNode): Boolean; function TPurityAnalyzer.VisitUnquote(const Node: IAstNode): Boolean;
begin begin
Result := Accept(Node.Expression); Result := IsNodePure(Node.AsUnquote.Expression);
end; end;
function TPurityAnalyzer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): Boolean; function TPurityAnalyzer.VisitUnquoteSplicing(const Node: IAstNode): Boolean;
begin begin
Result := Accept(Node.Expression); Result := IsNodePure(Node.AsUnquoteSplicing.Expression);
end; end;
function TPurityAnalyzer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): Boolean; function TPurityAnalyzer.VisitMacroExpansionNode(const Node: IAstNode): Boolean;
begin begin
// We analyze the already expanded body. // We analyze the already expanded body.
Result := Accept(Node.ExpandedBody); Result := IsNodePure(Node.AsMacroExpansion.ExpandedBody);
end;
// --- Pipe Support ---
function TPurityAnalyzer.VisitPipeInput(const Node: IAstNode): Boolean;
begin
var P := Node.AsPipeInput;
// Input node itself is declarative.
// But we check its parts just in case.
Result := IsNodePure(P.StreamSource) and IsNodePure(P.Selectors);
end;
function TPurityAnalyzer.VisitPipeSelectorList(const Node: IAstNode): Boolean;
begin
for var item in Node.AsPipeSelectorList do
if not IsNodePure(item) then
exit(False);
Result := True;
end;
function TPurityAnalyzer.VisitPipeInputList(const Node: IAstNode): Boolean;
begin
for var item in Node.AsPipeInputList do
if not IsNodePure(item) then
exit(False);
Result := True;
end;
function TPurityAnalyzer.VisitPipe(const Node: IAstNode): Boolean;
begin
var P := Node.AsPipe;
// A pipe is pure if its input definitions are pure AND its transformation lambda is pure.
Result := IsNodePure(P.Inputs) and IsNodePure(P.Transformation);
end; end;
end. end.
+36 -14
View File
@@ -40,11 +40,16 @@ type
FCurrentScope: TAnalysisScope; FCurrentScope: TAnalysisScope;
procedure MarkDeclarationForBoxing(const AName: string); procedure MarkDeclarationForBoxing(const AName: string);
strict private
// Analysis Handlers (IAstNode signature)
function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitIdentifier(const Node: IAstNode): IAstNode;
function VisitVariableDeclaration(const Node: IAstNode): IAstNode;
protected protected
// Overridden Visit methods to perform analysis during traversal. procedure SetupHandlers; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
@@ -126,6 +131,16 @@ begin
inherited Destroy; inherited Destroy;
end; end;
procedure TUpvalueAnalyzer.SetupHandlers;
begin
inherited SetupHandlers; // Load default transformer logic
// Override specific handlers for analysis
Register(akLambdaExpression, VisitLambdaExpression);
Register(akIdentifier, VisitIdentifier);
Register(akVariableDeclaration, VisitVariableDeclaration);
end;
function TUpvalueAnalyzer.Execute(const ARootNode: IAstNode): IAstNode; function TUpvalueAnalyzer.Execute(const ARootNode: IAstNode): IAstNode;
begin begin
Result := Accept(ARootNode); Result := Accept(ARootNode);
@@ -167,32 +182,35 @@ begin
end; end;
end; end;
function TUpvalueAnalyzer.VisitIdentifier(const Node: IIdentifierNode): IAstNode; function TUpvalueAnalyzer.VisitIdentifier(const Node: IAstNode): IAstNode;
begin begin
// Check if this identifier refers to a variable from an outer scope // Check if this identifier refers to a variable from an outer scope
MarkDeclarationForBoxing(Node.Name); MarkDeclarationForBoxing(Node.AsIdentifier.Name);
// Return original node (Analysis pass only) // Return original node (Analysis pass only)
Result := Node; Result := Node;
end; end;
function TUpvalueAnalyzer.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TUpvalueAnalyzer.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var var
L: ILambdaExpressionNode;
i: Integer; i: Integer;
begin begin
L := Node.AsLambdaExpression;
// 1. Enter new analysis scope // 1. Enter new analysis scope
FCurrentScope := TAnalysisScope.Create(FCurrentScope); FCurrentScope := TAnalysisScope.Create(FCurrentScope);
try try
// 2. Register parameters (they mask outer variables) // 2. Register parameters (they mask outer variables)
// We pass 'nil' as the node because we currently don't box parameters, // We pass 'nil' as the node because we currently don't box parameters,
// but we must ensure Resolve() finds them so we don't accidentally box a shadowed variable. // but we must ensure Resolve() finds them so we don't accidentally box a shadowed variable.
for i := 0 to Node.Parameters.Count - 1 do for i := 0 to L.Parameters.Count - 1 do
begin begin
FCurrentScope.Define(Node.Parameters[i].Name, nil); FCurrentScope.Define(L.Parameters[i].Name, nil);
end; end;
// 3. Visit Body // 3. Visit Body
Accept(Node.Body); // Recursive call Accept(L.Body); // Recursive call
// Rebuild if needed (default CoW behavior) // Rebuild if needed (default CoW behavior)
Result := Node; Result := Node;
@@ -204,15 +222,19 @@ begin
end; end;
end; end;
function TUpvalueAnalyzer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TUpvalueAnalyzer.VisitVariableDeclaration(const Node: IAstNode): IAstNode;
var
V: IVariableDeclarationNode;
begin begin
V := Node.AsVariableDeclaration;
// 1. Visit initializer first (it executes in the CURRENT scope) // 1. Visit initializer first (it executes in the CURRENT scope)
if Assigned(Node.Initializer) then if Assigned(V.Initializer) then
Accept(Node.Initializer); Accept(V.Initializer);
// 2. Define the variable in the CURRENT scope // 2. Define the variable in the CURRENT scope
// Store the Node reference so we can add it to FBoxedDeclarations if captured. // Store the Node reference so we can add it to FBoxedDeclarations if captured.
FCurrentScope.Define(Node.Target.AsIdentifier.Name, Node); FCurrentScope.Define(V.Target.AsIdentifier.Name, V);
Result := Node; Result := Node;
end; end;
+67 -54
View File
@@ -46,16 +46,16 @@ type
function ResolveSymbol(const Name: string; out Address: TResolvedAddress): Boolean; function ResolveSymbol(const Name: string; out Address: TResolvedAddress): Boolean;
function CaptureUpvalue(const PhysicalAddress: TResolvedAddress): Integer; function CaptureUpvalue(const PhysicalAddress: TResolvedAddress): Integer;
protected strict private
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override; // Specific Handlers (IAstNode signature)
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override; function VisitIdentifier(const Node: IAstNode): IAstNode;
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override; function VisitVariableDeclaration(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override; function VisitAssignment(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override; function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitConstant(const Node: IConstantNode): IAstNode; override; protected
function VisitKeyword(const Node: IKeywordNode): IAstNode; override; procedure SetupHandlers; override;
function VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; override;
public public
constructor Create( constructor Create(
@@ -114,7 +114,7 @@ constructor TAstBinder.Create(
const ALog: ICompilerLog const ALog: ICompilerLog
); );
begin begin
inherited Create; inherited Create; // Calls SetupHandlers
FCurrentBuilder := TScope.CreateBuilder(AParentLayout); FCurrentBuilder := TScope.CreateBuilder(AParentLayout);
FUpvalueStack := TObjectStack<TUpvalueMap>.Create(True); FUpvalueStack := TObjectStack<TUpvalueMap>.Create(True);
FUpvalueStack.Push(TUpvalueMap.Create(TResolvedAddressComparer.Create)); FUpvalueStack.Push(TUpvalueMap.Create(TResolvedAddressComparer.Create));
@@ -133,6 +133,18 @@ begin
inherited; inherited;
end; end;
procedure TAstBinder.SetupHandlers;
begin
inherited SetupHandlers; // Loads default transformers (Identity, Lists, etc.)
// Override specific binding logic
Register(akIdentifier, VisitIdentifier);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
end;
class function TAstBinder.Bind( class function TAstBinder.Bind(
const ParentLayout: IScopeLayout; const ParentLayout: IScopeLayout;
const RootNode: IAstNode; const RootNode: IAstNode;
@@ -148,6 +160,7 @@ end;
function TAstBinder.Execute(const RootNode: IAstNode; out Layout: IScopeLayout): IAstNode; function TAstBinder.Execute(const RootNode: IAstNode; out Layout: IScopeLayout): IAstNode;
begin begin
// Pre-pass to find captured variables
FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode); FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode);
Result := Accept(RootNode); Result := Accept(RootNode);
@@ -212,35 +225,22 @@ begin
end; end;
end; end;
function TAstBinder.VisitConstant(const Node: IConstantNode): IAstNode; function TAstBinder.VisitIdentifier(const Node: IAstNode): IAstNode;
begin
Result := Node;
end;
function TAstBinder.VisitKeyword(const Node: IKeywordNode): IAstNode;
begin
Result := Node;
end;
function TAstBinder.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
begin
Result := Node;
end;
function TAstBinder.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
var var
I: IIdentifierNode;
physAddr: TResolvedAddress; physAddr: TResolvedAddress;
upvalueIndex: Integer; upvalueIndex: Integer;
identity: INamedIdentity; identity: INamedIdentity;
begin begin
identity := Node.Identity.AsNamed; I := Node.AsIdentifier;
identity := I.Identity.AsNamed;
if Node.Address.Kind = akUnresolved then if I.Address.Kind = akUnresolved then
begin begin
if not ResolveSymbol(Node.Name, physAddr) then if not ResolveSymbol(I.Name, physAddr) then
begin begin
if Assigned(FLog) then if Assigned(FLog) then
FLog.AddError(Format('Undefined identifier: "%s"', [Node.Name]), Node); FLog.AddError(Format('Undefined identifier: "%s"', [I.Name]), Node);
Result := Node; Result := Node;
Exit; Exit;
end; end;
@@ -257,8 +257,9 @@ begin
Result := Node; Result := Node;
end; end;
function TAstBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TAstBinder.VisitVariableDeclaration(const Node: IAstNode): IAstNode;
var var
V: IVariableDeclarationNode;
slot: Integer; slot: Integer;
addr: TResolvedAddress; addr: TResolvedAddress;
newInit: IAstNode; newInit: IAstNode;
@@ -267,7 +268,8 @@ var
identifier: IIdentifierNode; identifier: IIdentifierNode;
identIdentity: INamedIdentity; identIdentity: INamedIdentity;
begin begin
identifier := Node.Target.AsIdentifier; V := Node.AsVariableDeclaration;
identifier := V.Target.AsIdentifier;
identIdentity := identifier.Identity.AsNamed; identIdentity := identifier.Identity.AsNamed;
if not IsValidIdentifier(identifier.Name) then if not IsValidIdentifier(identifier.Name) then
@@ -281,8 +283,8 @@ begin
if Assigned(FLog) then if Assigned(FLog) then
FLog.AddError(Format('Variable "%s" is already defined in this scope.', [identifier.Name]), Node); FLog.AddError(Format('Variable "%s" is already defined in this scope.', [identifier.Name]), Node);
if Assigned(Node.Initializer) then if Assigned(V.Initializer) then
Accept(Node.Initializer); Accept(V.Initializer);
Result := Node; Result := Node;
Exit; Exit;
@@ -291,38 +293,42 @@ begin
slot := FCurrentBuilder.Define(identifier.Name); slot := FCurrentBuilder.Define(identifier.Name);
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot); addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
if Assigned(Node.Initializer) then if Assigned(V.Initializer) then
newInit := Accept(Node.Initializer) newInit := Accept(V.Initializer)
else else
newInit := nil; newInit := nil;
newIdent := TAst.Identifier(identIdentity, addr, TTypes.Unknown); newIdent := TAst.Identifier(identIdentity, addr, TTypes.Unknown);
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node); isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(V);
Result := TAst.VarDecl(Node.Identity, newIdent, newInit, TTypes.Unknown, isBoxed); Result := TAst.VarDecl(Node.Identity, newIdent, newInit, TTypes.Unknown, isBoxed);
if Assigned(FFunctionRegistry) and (newInit <> nil) and (newInit.Kind = akLambdaExpression) then if Assigned(FFunctionRegistry) and (newInit <> nil) and (newInit.Kind = akLambdaExpression) then
FFunctionRegistry.Register(addr, newInit.AsLambdaExpression); FFunctionRegistry.Register(addr, newInit.AsLambdaExpression);
end; end;
function TAstBinder.VisitAssignment(const Node: IAssignmentNode): IAstNode; function TAstBinder.VisitAssignment(const Node: IAstNode): IAstNode;
var var
A: IAssignmentNode;
newIdent: IAstNode; newIdent: IAstNode;
newValue: IAstNode; newValue: IAstNode;
begin begin
newIdent := Accept(Node.Target); A := Node.AsAssignment;
newValue := Accept(Node.Value); // Manually accept children because we need the results for registry logic
newIdent := Accept(A.Target);
newValue := Accept(A.Value);
Result := TAst.Assign(Node.Identity, newIdent, newValue, TTypes.Unknown); Result := TAst.Assign(Node.Identity, newIdent, newValue, TTypes.Unknown);
if Assigned(FFunctionRegistry) and (newValue <> nil) and (newValue.Kind = akLambdaExpression) then if Assigned(FFunctionRegistry) and (newValue <> nil) and (newValue.Kind = akLambdaExpression) then
begin begin
if newIdent.AsIdentifier.Address.Kind = akLocalOrParent then if (newIdent.Kind = akIdentifier) and (newIdent.AsIdentifier.Address.Kind = akLocalOrParent) then
FFunctionRegistry.Register(newIdent.AsIdentifier.Address, newValue.AsLambdaExpression); FFunctionRegistry.Register(newIdent.AsIdentifier.Address, newValue.AsLambdaExpression);
end; end;
end; end;
function TAstBinder.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TAstBinder.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var var
L: ILambdaExpressionNode;
parentBuilder: IScopeBuilder; parentBuilder: IScopeBuilder;
paramType: IStaticType; paramType: IStaticType;
newParams: TArray<IIdentifierNode>; newParams: TArray<IIdentifierNode>;
@@ -338,6 +344,7 @@ var
paramIdentity: INamedIdentity; paramIdentity: INamedIdentity;
paramList: IParameterList; paramList: IParameterList;
begin begin
L := Node.AsLambdaExpression;
startCount := FLambdaCounter; startCount := FLambdaCounter;
Inc(FLambdaCounter); Inc(FLambdaCounter);
@@ -350,11 +357,11 @@ begin
try try
FCurrentBuilder.Define('<self>'); FCurrentBuilder.Define('<self>');
SetLength(newParams, Node.Parameters.Count); SetLength(newParams, L.Parameters.Count);
for i := 0 to Node.Parameters.Count - 1 do for i := 0 to L.Parameters.Count - 1 do
begin begin
var paramNode := Node.Parameters[i]; var paramNode := L.Parameters[i];
var paramName := paramNode.Name; var paramName := paramNode.Name;
paramIdentity := paramNode.Identity.AsNamed; paramIdentity := paramNode.Identity.AsNamed;
@@ -379,7 +386,7 @@ begin
newParams[i] := TAst.Identifier(paramIdentity, addr, paramType); newParams[i] := TAst.Identifier(paramIdentity, addr, paramType);
end; end;
newBody := Accept(Node.Body); newBody := Accept(L.Body);
finalLayout := FCurrentBuilder.Build; finalLayout := FCurrentBuilder.Build;
sortedPairs := capturedMap.ToArray; sortedPairs := capturedMap.ToArray;
@@ -408,28 +415,34 @@ begin
end; end;
hasNested := FLambdaCounter > (startCount + 1); hasNested := FLambdaCounter > (startCount + 1);
paramList := TParameterList.Create(newParams, L.Parameters.Identity);
paramList := TParameterList.Create(newParams, Node.Parameters.Identity); Result := TAst.LambdaExpr(Node.Identity, paramList, newBody, finalLayout, nil, upvaluesList, hasNested, L.IsPure);
Result := TAst.LambdaExpr(Node.Identity, paramList, newBody, finalLayout, nil, upvaluesList, hasNested, Node.IsPure);
end; end;
function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TAstBinder.VisitFunctionCall(const Node: IAstNode): IAstNode;
var
C: IFunctionCallNode;
begin begin
if Node.Callee.Kind = akKeyword then C := Node.AsFunctionCall;
// 1. Keyword as Function Logic
if C.Callee.Kind = akKeyword then
begin begin
if Node.Arguments.Count <> 1 then if C.Arguments.Count <> 1 then
begin begin
if Assigned(FLog) then if Assigned(FLog) then
FLog.AddError(Format('Keyword access :%s requires exactly one argument.', [Node.Callee.AsKeyword.Value.Name]), Node); FLog.AddError(Format('Keyword access :%s requires exactly one argument.', [C.Callee.AsKeyword.Value.Name]), Node);
end end
else else
begin begin
var memberAccess := TAst.MemberAccess(Node.Identity, Accept(Node.Arguments[0]), Node.Callee.AsKeyword); var memberAccess := TAst.MemberAccess(Node.Identity, Accept(C.Arguments[0]), C.Callee.AsKeyword);
Result := memberAccess; Result := memberAccess;
exit; exit;
end; end;
end; end;
// 2. Delegate to standard AST transformation (recurse on callee and args)
// This reuses logic from TAstTransformer!
Result := inherited VisitFunctionCall(Node); Result := inherited VisitFunctionCall(Node);
end; end;
+128 -134
View File
@@ -15,42 +15,39 @@ uses
Myc.Ast; Myc.Ast;
type type
// Exception specific to macro expansion errors
EMacroException = class(EAstException); EMacroException = class(EAstException);
IAstMacroExpander = interface(IAstVisitor) IAstMacroExpander = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode; function Execute(const RootNode: IAstNode): IAstNode;
end; end;
// Callback used by TMacroExpander to request full compilation and execution of macro arguments.
TMacroEvaluatorProc = reference to function(const Scope: IExecutionScope; const Node: IAstNode): TDataValue; TMacroEvaluatorProc = reference to function(const Scope: IExecutionScope; const Node: IAstNode): TDataValue;
// Handles the expansion of the macro body template (Quasiquotes/Unquotes). // Handles body expansion (Template instantiation)
TExpansionVisitor = class(TAstTransformer) TExpansionVisitor = class(TAstTransformer)
private strict private
FMacroEvaluator: TMacroEvaluatorProc; FMacroEvaluator: TMacroEvaluatorProc;
FMacroScope: IExecutionScope; FMacroScope: IExecutionScope;
FRenameMap: TDictionary<string, string>; FRenameMap: TDictionary<string, string>;
class var class var
FGensymCounter: Int64; FGensymCounter: Int64;
// Helper to handle unquote-splicing (~@) within lists.
// Takes a source list (e.g., Arguments) and returns a flattened array for the new node.
function TransformAndSpliceNodes(const ANodes: INodeList<IAstNode>): TArray<IAstNode>; function TransformAndSpliceNodes(const ANodes: INodeList<IAstNode>): TArray<IAstNode>;
function Gensym(const ABaseName: string): string; function Gensym(const ABaseName: string): string;
// Custom Logic Handlers (IAstNode signature)
function VisitUnquote(const Node: IAstNode): IAstNode;
function VisitUnquoteSplicing(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitBlockExpression(const Node: IAstNode): IAstNode;
function VisitRecordLiteral(const Node: IAstNode): IAstNode;
function VisitVariableDeclaration(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitIdentifier(const Node: IAstNode): IAstNode;
protected protected
function VisitUnquote(const Node: IUnquoteNode): IAstNode; override; procedure SetupHandlers; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; override;
// We override these container visits to handle Splicing manually,
// bypassing the standard 1:1 list visitor.
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override;
public public
constructor Create(const AMacroScope: IExecutionScope; const AMacroEvaluator: TMacroEvaluatorProc); constructor Create(const AMacroScope: IExecutionScope; const AMacroEvaluator: TMacroEvaluatorProc);
destructor Destroy; override; destructor Destroy; override;
@@ -72,9 +69,9 @@ type
property Parent: IMacroRegistry read GetParent; property Parent: IMacroRegistry read GetParent;
end; end;
// Handles the expansion of macro calls within the AST. // Handles macro calls in AST
TMacroExpander = class(TAstTransformer, IAstMacroExpander) TMacroExpander = class(TAstTransformer, IAstMacroExpander)
private strict private
FInitialScope: IExecutionScope; FInitialScope: IExecutionScope;
FCurrentMacroRegistry: IMacroRegistry; FCurrentMacroRegistry: IMacroRegistry;
FMacroEvaluator: TMacroEvaluatorProc; FMacroEvaluator: TMacroEvaluatorProc;
@@ -82,16 +79,16 @@ type
procedure EnterMacroScope; procedure EnterMacroScope;
procedure ExitMacroScope; procedure ExitMacroScope;
function VisitMacroDefinition(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitQuasiquote(const Node: IAstNode): IAstNode;
function VisitUnquote(const Node: IAstNode): IAstNode;
function VisitUnquoteSplicing(const Node: IAstNode): IAstNode;
function VisitBlockExpression(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: IAstNode): IAstNode;
protected protected
function VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; override; procedure SetupHandlers; override;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
function VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; override;
function VisitUnquote(const Node: IUnquoteNode): IAstNode; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
public public
constructor Create( constructor Create(
@@ -111,7 +108,6 @@ type
): IAstNode; static; ): IAstNode; static;
end; end;
// Implementation of the macro registry interface defined in Myc.Ast.Environment.
TMacroRegistry = class(TInterfacedObject, IMacroRegistry) TMacroRegistry = class(TInterfacedObject, IMacroRegistry)
private private
FParent: IMacroRegistry; FParent: IMacroRegistry;
@@ -148,6 +144,22 @@ begin
inherited; inherited;
end; end;
procedure TExpansionVisitor.SetupHandlers;
begin
inherited SetupHandlers; // Defaults
// Register custom handlers
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
Register(akFunctionCall, VisitFunctionCall);
Register(akBlockExpression, VisitBlockExpression);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akIdentifier, VisitIdentifier);
end;
function TExpansionVisitor.Gensym(const ABaseName: string): string; function TExpansionVisitor.Gensym(const ABaseName: string): string;
begin begin
if not FRenameMap.TryGetValue(ABaseName, Result) then if not FRenameMap.TryGetValue(ABaseName, Result) then
@@ -189,7 +201,6 @@ begin
begin begin
var spliceExpr := node.AsUnquoteSplicing.Expression; var spliceExpr := node.AsUnquoteSplicing.Expression;
var evaluatedSpliceValue: TDataValue; var evaluatedSpliceValue: TDataValue;
try try
evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr); evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr);
except except
@@ -202,36 +213,20 @@ begin
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>;
// Splicing implies the value must be a list-like AST structure (Block or just a List Node)
// If the evaluated value is an IArgumentList, IExpressionList etc., we flatten it.
if nodeToSplice.Kind = akExpressionList then if nodeToSplice.Kind = akExpressionList then
begin
for var subItem in nodeToSplice.AsExpressionList do for var subItem in nodeToSplice.AsExpressionList do
newList.Add(subItem); newList.Add(subItem)
end
else if nodeToSplice.Kind = akArgumentList then else if nodeToSplice.Kind = akArgumentList then
begin
for var subItem in nodeToSplice.AsArgumentList do for var subItem in nodeToSplice.AsArgumentList do
newList.Add(subItem); newList.Add(subItem)
end
else if nodeToSplice.Kind = akBlockExpression then else if nodeToSplice.Kind = akBlockExpression then
begin
// Splice content of block
for var subItem in nodeToSplice.AsBlockExpression.Expressions do for var subItem in nodeToSplice.AsBlockExpression.Expressions do
newList.Add(subItem); newList.Add(subItem)
end
else else
begin
// Treat as single item
newList.Add(nodeToSplice); newList.Add(nodeToSplice);
end;
end end
else if (not evaluatedSpliceValue.IsVoid) then else if (not evaluatedSpliceValue.IsVoid) then
begin
// Use Splice Node Identity for the new Constant
newList.Add(TAst.Constant(evaluatedSpliceValue, node.Identity.Location)); newList.Add(TAst.Constant(evaluatedSpliceValue, node.Identity.Location));
end;
end end
else else
begin begin
@@ -246,76 +241,69 @@ begin
end; end;
end; end;
function TExpansionVisitor.VisitIdentifier(const Node: IIdentifierNode): IAstNode; function TExpansionVisitor.VisitIdentifier(const Node: IAstNode): IAstNode;
var var
I: IIdentifierNode;
newName: string; newName: string;
begin begin
// Apply hygienic renaming I := Node.AsIdentifier;
if FRenameMap.TryGetValue(Node.Name, newName) then if FRenameMap.TryGetValue(I.Name, newName) then
begin begin
// Create new Identifier with new Name but preserve Location from original Node Result := TAst.Identifier(newName, I.Identity.Location);
Result := TAst.Identifier(newName, Node.Identity.Location);
exit; exit;
end; end;
Result := Node; Result := Node;
end; end;
function TExpansionVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TExpansionVisitor.VisitVariableDeclaration(const Node: IAstNode): IAstNode;
var var
V: IVariableDeclarationNode;
newTarget, newInit: IAstNode; newTarget, newInit: IAstNode;
newName: string; newName: string;
begin begin
newInit := Accept(Node.Initializer); V := Node.AsVariableDeclaration;
newInit := Accept(V.Initializer);
if Node.Target.Kind = akIdentifier then if V.Target.Kind = akIdentifier then
begin begin
newName := Gensym(Node.Target.AsIdentifier.Name); newName := Gensym(V.Target.AsIdentifier.Name);
// Use location from original target newTarget := TAst.Identifier(newName, V.Target.Identity.Location);
newTarget := TAst.Identifier(newName, Node.Target.Identity.Location);
end end
else else
begin newTarget := Accept(V.Target);
newTarget := Accept(Node.Target);
end;
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, TTypes.Unknown, False); Result := TAst.VarDecl(Node.Identity, newTarget, newInit, TTypes.Unknown, False);
end; end;
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TExpansionVisitor.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var var
L: ILambdaExpressionNode;
newParams: TArray<IIdentifierNode>; newParams: TArray<IIdentifierNode>;
newBody: IAstNode; newBody: IAstNode;
newName: string; newName: string;
i: Integer; i: Integer;
paramList: IParameterList;
begin begin
SetLength(newParams, Node.Parameters.Count); L := Node.AsLambdaExpression;
for i := 0 to Node.Parameters.Count - 1 do SetLength(newParams, L.Parameters.Count);
for i := 0 to L.Parameters.Count - 1 do
begin begin
var param := Node.Parameters[i]; var param := L.Parameters[i];
newName := Gensym(param.Name); newName := Gensym(param.Name);
// Use location from original param
newParams[i] := TAst.Identifier(newName, param.Identity.Location); newParams[i] := TAst.Identifier(newName, param.Identity.Location);
end; end;
newBody := Accept(Node.Body); newBody := Accept(L.Body);
Result := TAst.LambdaExpr(Node.Identity, TParameterList.Create(newParams, L.Parameters.Identity), newBody);
// Rebuild: Wrap array in TParameterList with original Identity
paramList := TParameterList.Create(newParams, Node.Parameters.Identity);
// Use transformation overload
Result := TAst.LambdaExpr(Node.Identity, paramList, newBody);
end; end;
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode; function TExpansionVisitor.VisitUnquote(const Node: IAstNode): IAstNode;
var var
U: IUnquoteNode;
value: TDataValue; value: TDataValue;
expr: IAstNode; expr: IAstNode;
addr: TResolvedAddress; addr: TResolvedAddress;
begin begin
expr := Node.Expression; U := Node.AsUnquote;
expr := U.Expression;
// Optimization: If unquote refers to a local macro variable directly, try to get it.
if expr.Kind = akIdentifier then if expr.Kind = akIdentifier then
begin begin
addr := FMacroScope.Resolve(expr.AsIdentifier.Name); addr := FMacroScope.Resolve(expr.AsIdentifier.Name);
@@ -323,10 +311,7 @@ begin
begin begin
var argValue := FMacroScope.Values[addr]; var argValue := FMacroScope.Values[addr];
if argValue.Kind = vkInterface then if argValue.Kind = vkInterface then
begin exit(argValue.AsIntf<IAstNode>);
Result := argValue.AsIntf<IAstNode>;
exit;
end;
end; end;
end; end;
@@ -340,57 +325,45 @@ begin
end; end;
if value.Kind = vkInterface then if value.Kind = vkInterface then
begin exit(value.AsIntf<IAstNode>);
Result := value.AsIntf<IAstNode>;
exit;
end;
if value.Kind in [vkScalar, vkText, vkVoid] then if value.Kind in [vkScalar, vkText, vkVoid] then
// Create Constant node, inheriting location from the Unquote node
Result := TAst.Constant(value, Node.Identity.Location) Result := TAst.Constant(value, Node.Identity.Location)
else else
raise EMacroException.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]); raise EMacroException.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]);
end; end;
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; function TExpansionVisitor.VisitUnquoteSplicing(const Node: IAstNode): IAstNode;
begin begin
raise EMacroException.Create('Unquote-splicing (`~@`) can only appear inside a list form.'); raise EMacroException.Create('Unquote-splicing (`~@`) can only appear inside a list form.');
end; end;
function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TExpansionVisitor.VisitFunctionCall(const Node: IAstNode): IAstNode;
var var
C: IFunctionCallNode;
newArgs: TArray<IAstNode>; newArgs: TArray<IAstNode>;
transformedCallee: IAstNode; transformedCallee: IAstNode;
argList: IArgumentList;
begin begin
transformedCallee := Self.Accept(Node.Callee); C := Node.AsFunctionCall;
transformedCallee := Self.Accept(C.Callee);
// Use splicing aware transformation for arguments (returns TArray) newArgs := TransformAndSpliceNodes(C.Arguments);
newArgs := TransformAndSpliceNodes(Node.Arguments); Result := TAst.FunctionCall(Node.Identity, transformedCallee, TArgumentList.Create(newArgs, C.Arguments.Identity));
// Wrap in List container to use Transformation overload
argList := TArgumentList.Create(newArgs, Node.Arguments.Identity);
Result := TAst.FunctionCall(Node.Identity, transformedCallee, argList);
end; end;
function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; function TExpansionVisitor.VisitBlockExpression(const Node: IAstNode): IAstNode;
var var
B: IBlockExpressionNode;
newExprs: TArray<IAstNode>; newExprs: TArray<IAstNode>;
exprList: IExpressionList;
begin begin
// Use splicing aware transformation for block expressions B := Node.AsBlockExpression;
newExprs := TransformAndSpliceNodes(Node.Expressions); newExprs := TransformAndSpliceNodes(B.Expressions);
Result := TAst.Block(Node.Identity, TExpressionList.Create(newExprs, B.Expressions.Identity));
// Wrap in List
exprList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
Result := TAst.Block(Node.Identity, exprList);
end; end;
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; function TExpansionVisitor.VisitRecordLiteral(const Node: IAstNode): IAstNode;
begin begin
// For now, standard transformation for records (no splicing key-values yet) // Delegate to standard transformer to process fields recursively
// This calls inherited VisitRecordLiteral(IAstNode)
Result := inherited VisitRecordLiteral(Node); Result := inherited VisitRecordLiteral(Node);
end; end;
@@ -413,6 +386,21 @@ begin
inherited Destroy; inherited Destroy;
end; end;
procedure TMacroExpander.SetupHandlers;
begin
inherited SetupHandlers; // Defaults
Register(akMacroDefinition, VisitMacroDefinition);
Register(akFunctionCall, VisitFunctionCall);
Register(akQuasiquote, VisitQuasiquote);
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
// Wrappers for scope management
Register(akBlockExpression, VisitBlockExpression);
Register(akLambdaExpression, VisitLambdaExpression);
end;
procedure TMacroExpander.EnterMacroScope; procedure TMacroExpander.EnterMacroScope;
begin begin
FCurrentMacroRegistry := FCurrentMacroRegistry.CreateChildRegistry; FCurrentMacroRegistry := FCurrentMacroRegistry.CreateChildRegistry;
@@ -441,75 +429,81 @@ begin
Result := TAst.Block([], nil); Result := TAst.Block([], nil);
end; end;
function TMacroExpander.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; function TMacroExpander.VisitBlockExpression(const Node: IAstNode): IAstNode;
begin begin
EnterMacroScope; EnterMacroScope;
try try
// Delegate to standard recursive transform
Result := inherited VisitBlockExpression(Node); Result := inherited VisitBlockExpression(Node);
finally finally
ExitMacroScope; ExitMacroScope;
end; end;
end; end;
function TMacroExpander.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TMacroExpander.VisitLambdaExpression(const Node: IAstNode): IAstNode;
begin begin
EnterMacroScope; EnterMacroScope;
try try
// Delegate to standard recursive transform
Result := inherited VisitLambdaExpression(Node); Result := inherited VisitLambdaExpression(Node);
finally finally
ExitMacroScope; ExitMacroScope;
end; end;
end; end;
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; function TMacroExpander.VisitMacroDefinition(const Node: IAstNode): IAstNode;
begin begin
FCurrentMacroRegistry.Define(Node); FCurrentMacroRegistry.Define(Node.AsMacroDefinition);
Result := Node; Result := Node;
end; end;
function TMacroExpander.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TMacroExpander.VisitFunctionCall(const Node: IAstNode): IAstNode;
var var
C: IFunctionCallNode;
calleeIdentifier: IIdentifierNode; calleeIdentifier: IIdentifierNode;
macroDef: IMacroDefinitionNode; macroDef: IMacroDefinitionNode;
i: Integer; i: Integer;
begin begin
if Node.Callee.Kind <> akIdentifier then C := Node.AsFunctionCall;
exit(inherited VisitFunctionCall(Node)); if C.Callee.Kind = akIdentifier then
begin
calleeIdentifier := Node.Callee.AsIdentifier; calleeIdentifier := C.Callee.AsIdentifier;
macroDef := FCurrentMacroRegistry.Find(calleeIdentifier.Name); macroDef := FCurrentMacroRegistry.Find(calleeIdentifier.Name);
if macroDef = nil then
exit(inherited VisitFunctionCall(Node));
if macroDef <> nil then
begin
var expansionScope := TScope.CreateScope(FInitialScope, nil, nil); var expansionScope := TScope.CreateScope(FInitialScope, nil, nil);
var params := macroDef.Parameters; var params := macroDef.Parameters;
if Node.Arguments.Count <> params.Count then
if C.Arguments.Count <> params.Count then
raise EMacroException.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, params.Count]); raise EMacroException.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, params.Count]);
for i := 0 to params.Count - 1 do for i := 0 to params.Count - 1 do
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i])); expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(C.Arguments[i]));
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsQuasiquote.Expression, FMacroEvaluator); var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsQuasiquote.Expression, FMacroEvaluator);
var macroNode := TAst.MacroExpansionNode(Node.Identity, C, expandedBody);
// The MacroExpansionNode wraps the expansion, preserving the original call's identity (Source Location)
// for better error reporting.
var macroNode := TAst.MacroExpansionNode(Node.Identity, Node, expandedBody);
Result := Self.Accept(macroNode); Result := Self.Accept(macroNode);
exit;
end;
end; end;
function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode; // Standard recursion for non-macro calls
Result := inherited VisitFunctionCall(Node);
end;
function TMacroExpander.VisitQuasiquote(const Node: IAstNode): IAstNode;
begin begin
Result := Node; Result := Node;
end; end;
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode; function TMacroExpander.VisitUnquote(const Node: IAstNode): IAstNode;
begin begin
raise EMacroException.Create('Unquote (`~`) can only be used inside a quasiquote.'); raise EMacroException.Create('Unquote (`~`) can only be used inside a quasiquote.');
end; end;
function TMacroExpander.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; function TMacroExpander.VisitUnquoteSplicing(const Node: IAstNode): IAstNode;
begin begin
raise EMacroException.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.'); raise EMacroException.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.');
end; end;
+34 -28
View File
@@ -16,7 +16,7 @@ uses
Myc.Ast.Compiler.Binder; Myc.Ast.Compiler.Binder;
type type
// Exception specific to specialization errors (e.g. internal compilation failures) // Exception specific to specialization errors
ESpecializerException = class(EAstException); ESpecializerException = class(EAstException);
IAstSpecializer = interface(IAstVisitor) IAstSpecializer = interface(IAstVisitor)
@@ -29,7 +29,7 @@ type
public public
Address: TResolvedAddress; Address: TResolvedAddress;
ArgTypes: TArray<IStaticType>; ArgTypes: TArray<IStaticType>;
Func: TDataValue.TFunc; Func: TDataValue.TFunc; // Usually nil in key, but part of structure if needed
constructor Create(const AAddress: TResolvedAddress; const AArgTypes: TArray<IStaticType>); constructor Create(const AAddress: TResolvedAddress; const AArgTypes: TArray<IStaticType>);
end; end;
@@ -39,10 +39,9 @@ type
end; end;
// This transformer runs *after* TypeChecker. // This transformer runs *after* TypeChecker.
// It specializes all statically resolvable function calls (RTL and user-defined) // It specializes all statically resolvable function calls.
// by replacing them with nodes that have a direct StaticTarget.
// It propagates Purity information but does NOT perform Constant Folding yet.
TStaticSpecializer = class(TAstTransformer, IAstSpecializer) TStaticSpecializer = class(TAstTransformer, IAstSpecializer)
public
type type
TCompileFunc = reference to function(const Node: IFunctionDefinition; const ArgTypes: TArray<IStaticType>): TCompiledFunction; TCompileFunc = reference to function(const Node: IFunctionDefinition; const ArgTypes: TArray<IStaticType>): TCompiledFunction;
private private
@@ -52,8 +51,12 @@ type
function GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod; function GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
strict private
// Specialization Handler (IAstNode signature)
function VisitFunctionCall(const Node: IAstNode): IAstNode;
protected protected
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override; procedure SetupHandlers; override;
public public
constructor Create( constructor Create(
@@ -89,13 +92,19 @@ begin
raise ESpecializerException.Create('MonomorphCache cannot be nil.'); raise ESpecializerException.Create('MonomorphCache cannot be nil.');
if not Assigned(AFunctionRegistry) then if not Assigned(AFunctionRegistry) then
raise ESpecializerException.Create('FunctionRegistry cannot be nil.'); raise ESpecializerException.Create('FunctionRegistry cannot be nil.');
// CompileFunc can be nil if no recursion is supported, but usually required.
FMonomorphCache := AMonomorphCache; FMonomorphCache := AMonomorphCache;
FFunctionRegistry := AFunctionRegistry; FFunctionRegistry := AFunctionRegistry;
FCompileFunc := ACompileFunc; FCompileFunc := ACompileFunc;
end; end;
procedure TStaticSpecializer.SetupHandlers;
begin
inherited SetupHandlers; // Load default transformations
// Override FunctionCall logic
Register(akFunctionCall, VisitFunctionCall);
end;
class function TStaticSpecializer.Specialize( class function TStaticSpecializer.Specialize(
const RootNode: IAstNode; const RootNode: IAstNode;
const AMonomorphCache: IMonomorphCache; const AMonomorphCache: IMonomorphCache;
@@ -119,8 +128,10 @@ begin
Result := TRtlRegistry.GetStaticSpecialization(AName, AArgTypes); Result := TRtlRegistry.GetStaticSpecialization(AName, AArgTypes);
end; end;
function TStaticSpecializer.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TStaticSpecializer.VisitFunctionCall(const Node: IAstNode): IAstNode;
var var
C: IFunctionCallNode;
newCall: IFunctionCallNode;
newCallee: IAstNode; newCallee: IAstNode;
newArgsList: IArgumentList; newArgsList: IArgumentList;
i: Integer; i: Integer;
@@ -132,18 +143,18 @@ var
specializedMethod: TSpecializedMethod; specializedMethod: TSpecializedMethod;
funcDef: IFunctionDefinition; funcDef: IFunctionDefinition;
begin begin
// 1. Specialize children first (bottom-up) C := Node.AsFunctionCall;
newCallee := Accept(Node.Callee);
// Arguments are now visited as a List, returning an IArgumentList // 1. Specialize children first (bottom-up) by calling inherited
newArgsList := Accept(Node.Arguments).AsArgumentList; // inherited VisitFunctionCall returns an IAstNode (which is a new IFunctionCallNode if changed)
newCall := inherited VisitFunctionCall(Node).AsFunctionCall;
newCallee := newCall.Callee;
newArgsList := newCall.Arguments;
// 2. Check if this call is a candidate for specialization // 2. Check if this call is a candidate for specialization
if newCallee.Kind <> akIdentifier then if newCallee.Kind <> akIdentifier then
begin begin
if (newCallee = Node.Callee) and (newArgsList = Node.Arguments) then Result := newCall;
Result := Node
else
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, Node.StaticType, Node.IsTailCall, nil);
exit; exit;
end; end;
@@ -165,10 +176,7 @@ begin
if not allTypesKnown then if not allTypesKnown then
begin begin
if (newCallee = Node.Callee) and (newArgsList = Node.Arguments) then Result := newCall;
Result := Node
else
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, Node.StaticType, Node.IsTailCall, nil);
exit; exit;
end; end;
@@ -186,7 +194,7 @@ begin
newCallee, newCallee,
newArgsList, newArgsList,
specializedMethod.ReturnType, specializedMethod.ReturnType,
Node.IsTailCall, C.IsTailCall,
specializedMethod.Target, specializedMethod.Target,
specializedMethod.IsPure specializedMethod.IsPure
); );
@@ -206,7 +214,7 @@ begin
newCallee, newCallee,
newArgsList, newArgsList,
specializedMethod.ReturnType, specializedMethod.ReturnType,
Node.IsTailCall, C.IsTailCall,
specializedMethod.Target, specializedMethod.Target,
specializedMethod.IsPure specializedMethod.IsPure
); );
@@ -224,7 +232,7 @@ begin
var lambdaDef := funcDef.AsLambdaExpression; var lambdaDef := funcDef.AsLambdaExpression;
if (Length(lambdaDef.Upvalues) > 0) or (lambdaDef.HasNestedLambdas) then if (Length(lambdaDef.Upvalues) > 0) or (lambdaDef.HasNestedLambdas) then
begin begin
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, Node.StaticType, Node.IsTailCall, nil); Result := newCall;
exit; exit;
end; end;
end; end;
@@ -245,15 +253,12 @@ begin
FMonomorphCache.Add(key, specializedMethod); FMonomorphCache.Add(key, specializedMethod);
// 6c. Return the new node // 6c. Return the new node
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure); Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, returnType, C.IsTailCall, compiled.Func, compiled.IsPure);
exit; exit;
end; end;
// 7. Fallback: Not RTL, Not User-Code -> Dynamic // 7. Fallback: Not RTL, Not User-Code -> Dynamic
if (newCallee = Node.Callee) and (newArgsList = Node.Arguments) then Result := newCall;
Result := Node
else
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, Node.StaticType, Node.IsTailCall, nil);
end; end;
{ TMonoCacheKey } { TMonoCacheKey }
@@ -262,6 +267,7 @@ constructor TMonoCacheKey.Create(const AAddress: TResolvedAddress; const AArgTyp
begin begin
Address := AAddress; Address := AAddress;
ArgTypes := AArgTypes; ArgTypes := AArgTypes;
Func := nil;
end; end;
end. end.
+89 -83
View File
@@ -28,18 +28,23 @@ type
private private
FIsTailStack: TStack<Boolean>; FIsTailStack: TStack<Boolean>;
FNextIsTail: Boolean; FNextIsTail: Boolean;
protected
function Accept(const Node: IAstNode): IAstNode; override;
// Overrides for TCO propagation
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
function VisitCondExpression(const Node: ICondExpressionNode): IAstNode; override; // Replaces Ternary
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
function VisitRecurNode(const Node: IRecurNode): IAstNode; override;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode; override;
// The core TCO logic strict private
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override; // Typed Handlers (IAstNode signature)
function VisitBlockExpression(const Node: IAstNode): IAstNode;
function VisitIfExpression(const Node: IAstNode): IAstNode;
function VisitCondExpression(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitRecurNode(const Node: IAstNode): IAstNode;
function VisitMacroExpansionNode(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
protected
procedure SetupHandlers; override;
// Intercept dispatch to manage the TCO Stack state
function Accept(const Node: IAstNode): IAstNode; override;
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
@@ -65,6 +70,19 @@ begin
inherited; inherited;
end; end;
procedure TAstTCO.SetupHandlers;
begin
inherited SetupHandlers; // Defaults
Register(akBlockExpression, VisitBlockExpression);
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akRecur, VisitRecurNode);
Register(akMacroExpansion, VisitMacroExpansionNode);
Register(akFunctionCall, VisitFunctionCall);
end;
class function TAstTCO.Optimize(const RootNode: IAstNode): IAstNode; class function TAstTCO.Optimize(const RootNode: IAstNode): IAstNode;
begin begin
var optimizer := TAstTCO.Create as IAstTCO; var optimizer := TAstTCO.Create as IAstTCO;
@@ -81,22 +99,22 @@ end;
function TAstTCO.Accept(const Node: IAstNode): IAstNode; function TAstTCO.Accept(const Node: IAstNode): IAstNode;
begin begin
if (not Assigned(Node)) then if (not Assigned(Node)) then
begin exit(nil);
Result := nil;
exit;
end;
// Push current context state before visiting children
FIsTailStack.Push(FNextIsTail); FIsTailStack.Push(FNextIsTail);
try try
// Call inherited Accept // Dispatch to handler (which will call VisitXyz)
Result := inherited Accept(Node); Result := inherited Accept(Node);
finally finally
// Restore context state
FNextIsTail := FIsTailStack.Pop; FNextIsTail := FIsTailStack.Pop;
end; end;
end; end;
function TAstTCO.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; function TAstTCO.VisitBlockExpression(const Node: IAstNode): IAstNode;
var var
B: IBlockExpressionNode;
isContextTail: Boolean; isContextTail: Boolean;
newExprs: TArray<IAstNode>; newExprs: TArray<IAstNode>;
exprsList: IExpressionList; exprsList: IExpressionList;
@@ -104,8 +122,9 @@ var
item, newItem: IAstNode; item, newItem: IAstNode;
hasChanged: Boolean; hasChanged: Boolean;
begin begin
B := Node.AsBlockExpression;
isContextTail := FIsTailStack.Peek; isContextTail := FIsTailStack.Peek;
exprsList := Node.Expressions; exprsList := B.Expressions;
SetLength(newExprs, exprsList.Count); SetLength(newExprs, exprsList.Count);
hasChanged := False; hasChanged := False;
@@ -129,39 +148,36 @@ begin
if not hasChanged then if not hasChanged then
Result := Node Result := Node
else else
begin Result := TAst.Block(Node.Identity, TExpressionList.Create(newExprs, B.Expressions.Identity), B.StaticType);
// Wrap array in List container
var newList := TExpressionList.Create(newExprs, Node.Expressions.Identity);
// CoW: Reuse Identity
Result := TAst.Block(Node.Identity, newList, Node.StaticType);
end;
end; end;
function TAstTCO.VisitIfExpression(const Node: IIfExpressionNode): IAstNode; function TAstTCO.VisitIfExpression(const Node: IAstNode): IAstNode;
var var
I: IIfExpressionNode;
isContextTail: Boolean; isContextTail: Boolean;
newCond, newThen, newElse: IAstNode; newCond, newThen, newElse: IAstNode;
begin begin
I := Node.AsIfExpression;
isContextTail := FIsTailStack.Peek; isContextTail := FIsTailStack.Peek;
// Condition is never in tail position // Condition is never in tail position
FNextIsTail := False; FNextIsTail := False;
newCond := Accept(Node.Condition); newCond := Accept(I.Condition);
// Then/Else branches ARE in tail position if the IfExpr is // Then/Else branches ARE in tail position if the IfExpr is
FNextIsTail := isContextTail; FNextIsTail := isContextTail;
newThen := Accept(Node.ThenBranch); newThen := Accept(I.ThenBranch);
newElse := Accept(Node.ElseBranch); newElse := Accept(I.ElseBranch);
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then if (newCond = I.Condition) and (newThen = I.ThenBranch) and (newElse = I.ElseBranch) then
Result := Node Result := Node
else else
// CoW: Reuse Identity Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, I.StaticType);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
end; end;
function TAstTCO.VisitCondExpression(const Node: ICondExpressionNode): IAstNode; function TAstTCO.VisitCondExpression(const Node: IAstNode): IAstNode;
var var
C: ICondExpressionNode;
isContextTail: Boolean; isContextTail: Boolean;
hasChanged: Boolean; hasChanged: Boolean;
i: Integer; i: Integer;
@@ -169,143 +185,133 @@ var
newElse: IAstNode; newElse: IAstNode;
newCond, newBranch: IAstNode; newCond, newBranch: IAstNode;
begin begin
C := Node.AsCondExpression;
isContextTail := FIsTailStack.Peek; isContextTail := FIsTailStack.Peek;
hasChanged := False; hasChanged := False;
SetLength(newPairs, Length(Node.Pairs)); SetLength(newPairs, Length(C.Pairs));
for i := 0 to High(Node.Pairs) do for i := 0 to High(C.Pairs) do
begin begin
// 1. Condition is never in tail position // 1. Condition is never in tail position
FNextIsTail := False; FNextIsTail := False;
newCond := Accept(Node.Pairs[i].Condition); newCond := Accept(C.Pairs[i].Condition);
// 2. Branch IS in tail position (if CondExpr is) // 2. Branch IS in tail position (if CondExpr is)
FNextIsTail := isContextTail; FNextIsTail := isContextTail;
newBranch := Accept(Node.Pairs[i].Branch); newBranch := Accept(C.Pairs[i].Branch);
newPairs[i] := TCondPair.Create(newCond, newBranch); newPairs[i] := TCondPair.Create(newCond, newBranch);
if (newCond <> Node.Pairs[i].Condition) or (newBranch <> Node.Pairs[i].Branch) then if (newCond <> C.Pairs[i].Condition) or (newBranch <> C.Pairs[i].Branch) then
hasChanged := True; hasChanged := True;
end; end;
// 3. Else Branch IS in tail position // 3. Else Branch IS in tail position
FNextIsTail := isContextTail; FNextIsTail := isContextTail;
newElse := Accept(Node.ElseBranch); newElse := Accept(C.ElseBranch);
if newElse <> Node.ElseBranch then if newElse <> C.ElseBranch then
hasChanged := True; hasChanged := True;
if not hasChanged then if not hasChanged then
Result := Node Result := Node
else else
Result := TAst.CondExpr(Node.Identity, newPairs, newElse, Node.StaticType); Result := TAst.CondExpr(Node.Identity, newPairs, newElse, C.StaticType);
end; end;
function TAstTCO.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TAstTCO.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var var
L: ILambdaExpressionNode;
newParams: IParameterList; newParams: IParameterList;
newBody: IAstNode; newBody: IAstNode;
begin begin
L := Node.AsLambdaExpression;
// Parameters are not in tail position // Parameters are not in tail position
FNextIsTail := False; FNextIsTail := False;
// Visit list node to handle potential transformations in children newParams := Accept(L.Parameters).AsParameterList;
newParams := Accept(Node.Parameters).AsParameterList;
// The body of a lambda is *always* a tail position (relative to the lambda execution) // The body of a lambda is *always* a tail position (relative to the lambda execution)
FNextIsTail := True; FNextIsTail := True;
newBody := Accept(Node.Body); newBody := Accept(L.Body);
if (newParams = Node.Parameters) and (newBody = Node.Body) then if (newParams = L.Parameters) and (newBody = L.Body) then
Result := Node Result := Node
else else
begin begin
// Rebuild using factory.
// CoW: Reuse Identity. Factory expects IParameterList now.
Result := Result :=
TAst.LambdaExpr( TAst.LambdaExpr(
Node.Identity, Node.Identity,
newParams, newParams,
newBody, newBody,
Node.Layout, L.Layout,
Node.Descriptor, L.Descriptor,
Node.Upvalues, L.Upvalues,
Node.HasNestedLambdas, L.HasNestedLambdas,
Node.IsPure, L.IsPure,
Node.StaticType L.StaticType
); );
end; end;
end; end;
function TAstTCO.VisitRecurNode(const Node: IRecurNode): IAstNode; function TAstTCO.VisitRecurNode(const Node: IAstNode): IAstNode;
var var
R: IRecurNode;
newArgsList: IArgumentList; newArgsList: IArgumentList;
begin begin
R := Node.AsRecur;
if not FIsTailStack.Peek then if not FIsTailStack.Peek then
raise EOptimizerException.Create('''recur'' can only be used in a tail position.'); raise EOptimizerException.Create('''recur'' can only be used in a tail position.');
// Arguments are not in tail position // Arguments are not in tail position
FNextIsTail := False; FNextIsTail := False;
// Visit the list node. Accept() ensures children are visited via TAstTransformer.VisitArgumentList newArgsList := Accept(R.Arguments).AsArgumentList;
newArgsList := Accept(Node.Arguments).AsArgumentList;
if newArgsList = Node.Arguments then if newArgsList = R.Arguments then
Result := Node Result := Node
else else
// CoW: Reuse Identity. Factory expects IArgumentList. Result := TAst.Recur(Node.Identity, newArgsList, R.StaticType);
Result := TAst.Recur(Node.Identity, newArgsList, Node.StaticType);
end; end;
function TAstTCO.VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode; function TAstTCO.VisitMacroExpansionNode(const Node: IAstNode): IAstNode;
var var
M: IMacroExpansionNode;
newBody: IAstNode; newBody: IAstNode;
begin begin
M := Node.AsMacroExpansion;
// Propagate tail call status to the expanded body // Propagate tail call status to the expanded body
FNextIsTail := FIsTailStack.Peek; FNextIsTail := FIsTailStack.Peek;
newBody := Accept(Node.ExpandedBody); newBody := Accept(M.ExpandedBody);
if newBody = Node.ExpandedBody then if newBody = M.ExpandedBody then
Result := Node Result := Node
else else
// CoW: Reuse Identity Result := TAst.MacroExpansionNode(Node.Identity, M.CallNode, newBody);
Result := TAst.MacroExpansionNode(Node.Identity, Node.CallNode, newBody);
end; end;
function TAstTCO.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TAstTCO.VisitFunctionCall(const Node: IAstNode): IAstNode;
var var
C: IFunctionCallNode;
isTailCall: Boolean; isTailCall: Boolean;
newCallee: IAstNode; newCallee: IAstNode;
newArgsList: IArgumentList; newArgsList: IArgumentList;
begin begin
C := Node.AsFunctionCall;
isTailCall := FIsTailStack.Peek; isTailCall := FIsTailStack.Peek;
// Callee/Arguments are not in tail position // Callee/Arguments are not in tail position
FNextIsTail := False; FNextIsTail := False;
newCallee := Accept(Node.Callee); newCallee := Accept(C.Callee);
newArgsList := Accept(C.Arguments).AsArgumentList;
// Visit the arguments list if (newCallee = C.Callee) and (newArgsList = C.Arguments) and (isTailCall = C.IsTailCall) then
newArgsList := Accept(Node.Arguments).AsArgumentList;
// CoW check: Create a new node only if children changed OR IsTailCall needs update
if (newCallee = Node.Callee) and (newArgsList = Node.Arguments) and (isTailCall = Node.IsTailCall) then
begin begin
Result := Node; Result := Node;
exit; exit;
end; end;
// Use factory to create new node, passing the *new* TCO status // Use factory to create new node with TCO status
// CoW: Reuse Identity. Factory expects IArgumentList. Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, C.StaticType, isTailCall, C.StaticTarget, C.IsTargetPure);
Result :=
TAst.FunctionCall(
Node.Identity,
newCallee,
newArgsList,
Node.StaticType,
isTailCall,
Node.StaticTarget, // Preserve the static target from Specializer
Node.IsTargetPure // Preserve purity flag
);
end; end;
end. end.
+183 -151
View File
@@ -52,28 +52,31 @@ type
function PrepareBaseType(const BaseNode: IAstNode; out IsOptional: Boolean): IStaticType; function PrepareBaseType(const BaseNode: IAstNode; out IsOptional: Boolean): IStaticType;
function ApplyOptionality(const AType: IStaticType; IsOptional: Boolean): IStaticType; function ApplyOptionality(const AType: IStaticType; IsOptional: Boolean): IStaticType;
protected strict private
function VisitIdentifier(const Node: IIdentifierNode): IAstNode; override; // Typed Handlers (non-virtual, IAstNode signature)
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override; function VisitIdentifier(const Node: IAstNode): IAstNode;
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override; function VisitVariableDeclaration(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override; function VisitAssignment(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override; function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override; function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override; function VisitBlockExpression(const Node: IAstNode): IAstNode;
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override; function VisitIfExpression(const Node: IAstNode): IAstNode;
function VisitIndexer(const Node: IIndexerNode): IAstNode; override; function VisitMemberAccess(const Node: IAstNode): IAstNode;
function VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; override; function VisitIndexer(const Node: IAstNode): IAstNode;
function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override; function VisitCreateSeries(const Node: IAstNode): IAstNode;
function VisitRecurNode(const Node: IRecurNode): IAstNode; override; function VisitSeriesLength(const Node: IAstNode): IAstNode;
function VisitNop(const Node: INopNode): IAstNode; override; function VisitRecurNode(const Node: IAstNode): IAstNode;
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override; function VisitNop(const Node: IAstNode): IAstNode;
function VisitRecordLiteral(const Node: IAstNode): IAstNode;
function VisitConstant(const Node: IConstantNode): IAstNode; override; function VisitConstant(const Node: IAstNode): IAstNode;
function VisitKeyword(const Node: IKeywordNode): IAstNode; override; function VisitKeyword(const Node: IAstNode): IAstNode;
// Pipe Support // Pipe Support
function VisitPipeInput(const Node: IPipeInputNode): IAstNode; override; function VisitPipeInput(const Node: IAstNode): IAstNode;
function VisitPipe(const Node: IPipeNode): IAstNode; override; function VisitPipe(const Node: IAstNode): IAstNode;
protected
procedure SetupHandlers; override;
public public
constructor Create(const RootLayout: IScopeLayout; const RootScope: IExecutionScope; const ALog: ICompilerLog); constructor Create(const RootLayout: IScopeLayout; const RootScope: IExecutionScope; const ALog: ICompilerLog);
@@ -215,6 +218,32 @@ begin
inherited; inherited;
end; end;
procedure TTypeChecker.SetupHandlers;
begin
inherited SetupHandlers; // Load Defaults
Register(akIdentifier, VisitIdentifier);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akBlockExpression, VisitBlockExpression);
Register(akIfExpression, VisitIfExpression);
Register(akMemberAccess, VisitMemberAccess);
Register(akIndexer, VisitIndexer);
Register(akCreateSeries, VisitCreateSeries);
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akConstant, VisitConstant);
Register(akKeyword, VisitKeyword);
// Pipe Support
Register(akPipeInput, VisitPipeInput);
Register(akPipe, VisitPipe);
end;
class function TTypeChecker.CheckTypes( class function TTypeChecker.CheckTypes(
const RootNode: IAstNode; const RootNode: IAstNode;
const Layout: IScopeLayout; const Layout: IScopeLayout;
@@ -261,24 +290,27 @@ end;
// --- Visits --- // --- Visits ---
function TTypeChecker.VisitConstant(const Node: IConstantNode): IAstNode; function TTypeChecker.VisitConstant(const Node: IAstNode): IAstNode;
begin
// Base implementation already returns Node, but here we explicitly confirm identity for clarity
Result := Node;
end;
function TTypeChecker.VisitKeyword(const Node: IAstNode): IAstNode;
begin begin
Result := Node; Result := Node;
end; end;
function TTypeChecker.VisitKeyword(const Node: IKeywordNode): IAstNode; function TTypeChecker.VisitIdentifier(const Node: IAstNode): IAstNode;
begin
Result := Node;
end;
function TTypeChecker.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
var var
I: IIdentifierNode;
typ: IStaticType; typ: IStaticType;
adr: TResolvedAddress; adr: TResolvedAddress;
identity: INamedIdentity; identity: INamedIdentity;
begin begin
adr := Node.Address; I := Node.AsIdentifier;
identity := Node.Identity.AsNamed; adr := I.Address;
identity := I.Identity.AsNamed;
if adr.Kind = akUnresolved then if adr.Kind = akUnresolved then
begin begin
@@ -290,40 +322,45 @@ begin
Result := TAst.Identifier(identity, adr, typ); Result := TAst.Identifier(identity, adr, typ);
end; end;
function TTypeChecker.VisitRecurNode(const Node: IRecurNode): IAstNode; function TTypeChecker.VisitRecurNode(const Node: IAstNode): IAstNode;
var var
newArgs: TArray<IAstNode>; R: IRecurNode;
i: Integer; args: IArgumentList;
begin begin
SetLength(newArgs, Node.Arguments.Count); R := Node.AsRecur;
for i := 0 to Node.Arguments.Count - 1 do // Use inherited to transform arguments, then reconstruct with type Void
newArgs[i] := Accept(Node.Arguments[i]); // Note: inherited VisitRecurNode returns IAstNode which is a RecurNode.
// We can call Accept on arguments list directly to avoid intermediate node creation if desired,
// but relying on inherited logic keeps it consistent.
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity); // Efficient approach: Accept the arguments list directly (it's a child).
Result := TAst.Recur(Node.Identity, argList, TTypes.Void); args := Accept(R.Arguments).AsArgumentList;
Result := TAst.Recur(Node.Identity, args, TTypes.Void);
end; end;
function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TTypeChecker.VisitVariableDeclaration(const Node: IAstNode): IAstNode;
var var
V: IVariableDeclarationNode;
initType: IStaticType; initType: IStaticType;
newInitializer, newIdent: IAstNode; newInitializer, newIdent: IAstNode;
adr: TResolvedAddress; adr: TResolvedAddress;
identNode: IIdentifierNode; identNode: IIdentifierNode;
begin begin
identNode := Node.Target.AsIdentifier; V := Node.AsVariableDeclaration;
identNode := V.Target.AsIdentifier;
adr := identNode.Address; adr := identNode.Address;
initType := TTypes.Unknown; initType := TTypes.Unknown;
if adr.Kind = akUnresolved then if adr.Kind = akUnresolved then
begin begin
if Assigned(Node.Initializer) then if Assigned(V.Initializer) then
Accept(Node.Initializer); Accept(V.Initializer);
Result := Node; Result := Node;
Exit; Exit;
end; end;
if Assigned(Node.Initializer) then if Assigned(V.Initializer) then
newInitializer := Accept(Node.Initializer) newInitializer := Accept(V.Initializer)
else else
newInitializer := nil; newInitializer := nil;
@@ -334,29 +371,31 @@ begin
FCurrentContext.SetType(adr.SlotIndex, initType); FCurrentContext.SetType(adr.SlotIndex, initType);
newIdent := TAst.Identifier(identNode.Identity.AsNamed, adr, initType); newIdent := TAst.Identifier(identNode.Identity.AsNamed, adr, initType);
Result := TAst.VarDecl(Node.Identity, newIdent, newInitializer, initType, Node.IsBoxed); Result := TAst.VarDecl(Node.Identity, newIdent, newInitializer, initType, V.IsBoxed);
end; end;
function TTypeChecker.VisitAssignment(const Node: IAssignmentNode): IAstNode; function TTypeChecker.VisitAssignment(const Node: IAstNode): IAstNode;
var var
A: IAssignmentNode;
targetType, sourceType: IStaticType; targetType, sourceType: IStaticType;
newIdent, newValue: IAstNode; newIdent, newValue: IAstNode;
adr: TResolvedAddress; adr: TResolvedAddress;
identNode: IIdentifierNode; identNode: IIdentifierNode;
begin begin
identNode := Node.Target.AsIdentifier; A := Node.AsAssignment;
newIdent := Accept(Node.Target); identNode := A.Target.AsIdentifier;
newIdent := Accept(A.Target);
targetType := newIdent.AsTypedNode.StaticType; targetType := newIdent.AsTypedNode.StaticType;
adr := identNode.Address; adr := identNode.Address;
if adr.Kind = akUnresolved then if adr.Kind = akUnresolved then
begin begin
Accept(Node.Value); Accept(A.Value);
Result := Node; Result := Node;
Exit; Exit;
end; end;
newValue := Accept(Node.Value); newValue := Accept(A.Value);
sourceType := newValue.AsTypedNode.StaticType; sourceType := newValue.AsTypedNode.StaticType;
if (targetType.Kind <> stUnknown) and (sourceType.Kind <> stUnknown) then if (targetType.Kind <> stUnknown) and (sourceType.Kind <> stUnknown) then
@@ -378,27 +417,28 @@ begin
Result := TAst.Assign(Node.Identity, newIdent, newValue, targetType); Result := TAst.Assign(Node.Identity, newIdent, newValue, targetType);
end; end;
function TTypeChecker.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; function TTypeChecker.VisitBlockExpression(const Node: IAstNode): IAstNode;
var var
newBlock: IBlockExpressionNode;
blockType: IStaticType; blockType: IStaticType;
newExprs: TArray<IAstNode>; exprs: IExpressionList;
i: Integer;
begin begin
SetLength(newExprs, Node.Expressions.Count); // Inherited logic transforms all expressions in the list
for i := 0 to Node.Expressions.Count - 1 do newBlock := inherited VisitBlockExpression(Node).AsBlockExpression;
newExprs[i] := Accept(Node.Expressions[i]); exprs := newBlock.Expressions;
if Length(newExprs) > 0 then if exprs.Count > 0 then
blockType := newExprs[High(newExprs)].AsTypedNode.StaticType blockType := exprs[exprs.Count - 1].AsTypedNode.StaticType
else else
blockType := TTypes.Void; blockType := TTypes.Void;
var exprList := TExpressionList.Create(newExprs, Node.Expressions.Identity); // Return new block with calculated type
Result := TAst.Block(Node.Identity, exprList, blockType); Result := TAst.Block(Node.Identity, exprs, blockType);
end; end;
function TTypeChecker.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TTypeChecker.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var var
L: ILambdaExpressionNode;
newParams: TArray<IIdentifierNode>; newParams: TArray<IIdentifierNode>;
newBody: IAstNode; newBody: IAstNode;
bodyType, methodType: IStaticType; bodyType, methodType: IStaticType;
@@ -409,8 +449,10 @@ var
paramIdent: IIdentifierNode; paramIdent: IIdentifierNode;
injectedType: IStaticType; injectedType: IStaticType;
begin begin
L := Node.AsLambdaExpression;
// 1. Resolve Upvalue Types // 1. Resolve Upvalue Types
var upvalueAddrs := Node.Upvalues; var upvalueAddrs := L.Upvalues;
SetLength(upvalueTypes, Length(upvalueAddrs)); SetLength(upvalueTypes, Length(upvalueAddrs));
for i := 0 to High(upvalueAddrs) do for i := 0 to High(upvalueAddrs) do
@@ -425,14 +467,14 @@ begin
end; end;
// 2. Enter New Scope // 2. Enter New Scope
FCurrentContext := TTypeContext.Create(FCurrentContext, Node.Layout, upvalueTypes, nil); FCurrentContext := TTypeContext.Create(FCurrentContext, L.Layout, upvalueTypes, nil);
try try
SetLength(newParams, Node.Parameters.Count); SetLength(newParams, L.Parameters.Count);
SetLength(paramTypes, Node.Parameters.Count); SetLength(paramTypes, L.Parameters.Count);
for i := 0 to Node.Parameters.Count - 1 do for i := 0 to L.Parameters.Count - 1 do
begin begin
paramIdent := Node.Parameters[i]; paramIdent := L.Parameters[i];
// Check if there is already a type assigned (e.g. injected by Pipe Visitor) // Check if there is already a type assigned (e.g. injected by Pipe Visitor)
injectedType := paramIdent.AsTypedNode.StaticType; injectedType := paramIdent.AsTypedNode.StaticType;
@@ -448,52 +490,45 @@ begin
newParams[i] := TAst.Identifier(paramIdent.Identity.AsNamed, paramIdent.Address, paramTypes[i]); newParams[i] := TAst.Identifier(paramIdent.Identity.AsNamed, paramIdent.Address, paramTypes[i]);
end; end;
newBody := Accept(Node.Body); newBody := Accept(L.Body);
bodyType := newBody.AsTypedNode.StaticType; bodyType := newBody.AsTypedNode.StaticType;
methodType := TTypes.CreateMethod(paramTypes, bodyType); methodType := TTypes.CreateMethod(paramTypes, bodyType);
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types); finalDescriptor := TScope.CreateDescriptor(L.Layout, FCurrentContext.Types);
finally finally
var temp := FCurrentContext; var temp := FCurrentContext;
FCurrentContext := FCurrentContext.FParent; FCurrentContext := FCurrentContext.FParent;
temp.Free; temp.Free;
end; end;
var paramList := TParameterList.Create(newParams, Node.Parameters.Identity); var paramList := TParameterList.Create(newParams, L.Parameters.Identity);
Result := Result :=
TAst.LambdaExpr( TAst.LambdaExpr(Node.Identity, paramList, newBody, L.Layout, finalDescriptor, L.Upvalues, L.HasNestedLambdas, L.IsPure, methodType);
Node.Identity,
paramList,
newBody,
Node.Layout,
finalDescriptor,
Node.Upvalues,
Node.HasNestedLambdas,
Node.IsPure,
methodType
);
end; end;
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TTypeChecker.VisitFunctionCall(const Node: IAstNode): IAstNode;
var var
newCall: IFunctionCallNode;
calleeType, retType: IStaticType; calleeType, retType: IStaticType;
i, j: Integer; i, j: Integer;
newCallee: IAstNode;
newArgs: TArray<IAstNode>;
argTypes: TArray<IStaticType>; argTypes: TArray<IStaticType>;
hasUnknownArgs: Boolean; hasUnknownArgs: Boolean;
bestSig: IMethodSignature; bestSig: IMethodSignature;
match: Boolean; match: Boolean;
begin begin
newCallee := Accept(Node.Callee); // Use inherited to visit Callee and Arguments first
SetLength(newArgs, Node.Arguments.Count); newCall := inherited VisitFunctionCall(Node).AsFunctionCall;
SetLength(argTypes, Node.Arguments.Count);
// Now analyze types on the transformed children
var newCallee := newCall.Callee;
var newArgs := newCall.Arguments;
SetLength(argTypes, newArgs.Count);
hasUnknownArgs := False; hasUnknownArgs := False;
for i := 0 to Node.Arguments.Count - 1 do for i := 0 to newArgs.Count - 1 do
begin begin
newArgs[i] := Accept(Node.Arguments[i]);
argTypes[i] := newArgs[i].AsTypedNode.StaticType; argTypes[i] := newArgs[i].AsTypedNode.StaticType;
if argTypes[i].Kind = stUnknown then if argTypes[i].Kind = stUnknown then
hasUnknownArgs := True; hasUnknownArgs := True;
@@ -535,36 +570,35 @@ begin
if Assigned(FLog) then if Assigned(FLog) then
FLog.AddError(Format('Cannot invoke type %s as a function.', [calleeType.ToString]), Node); FLog.AddError(Format('Cannot invoke type %s as a function.', [calleeType.ToString]), Node);
var argList := TArgumentList.Create(newArgs, Node.Arguments.Identity); // Return new node with Types
Result := TAst.FunctionCall(Node.Identity, newCallee, argList, retType, Node.IsTailCall, nil, False); Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, retType, newCall.IsTailCall, nil, False);
end; end;
function TTypeChecker.VisitIfExpression(const Node: IIfExpressionNode): IAstNode; function TTypeChecker.VisitIfExpression(const Node: IAstNode): IAstNode;
var var
newCond, newThen, newElse: IAstNode; newIf: IIfExpressionNode;
resType: IStaticType; resType: IStaticType;
begin begin
newCond := Accept(Node.Condition); newIf := inherited VisitIfExpression(Node).AsIfExpression;
newThen := Accept(Node.ThenBranch);
newElse := Accept(Node.ElseBranch);
// Simple promotion logic if (newIf.ElseBranch <> nil) then
if (newElse <> nil) then resType := TTypeRules.Promote(newIf.ThenBranch.AsTypedNode.StaticType, newIf.ElseBranch.AsTypedNode.StaticType)
resType := TTypeRules.Promote(newThen.AsTypedNode.StaticType, newElse.AsTypedNode.StaticType)
else else
resType := TTypes.MakeOptional(newThen.AsTypedNode.StaticType); resType := TTypes.MakeOptional(newIf.ThenBranch.AsTypedNode.StaticType);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, resType); Result := TAst.IfExpr(Node.Identity, newIf.Condition, newIf.ThenBranch, newIf.ElseBranch, resType);
end; end;
function TTypeChecker.VisitIndexer(const Node: IIndexerNode): IAstNode; function TTypeChecker.VisitIndexer(const Node: IAstNode): IAstNode;
var var
I: IIndexerNode;
newBase, newIndex: IAstNode; newBase, newIndex: IAstNode;
baseType, elemType: IStaticType; baseType, elemType: IStaticType;
isOpt: Boolean; isOpt: Boolean;
begin begin
newBase := Accept(Node.Base); I := Node.AsIndexer;
newIndex := Accept(Node.Index); newBase := Accept(I.Base);
newIndex := Accept(I.Index);
baseType := PrepareBaseType(newBase, isOpt); baseType := PrepareBaseType(newBase, isOpt);
elemType := TTypes.Unknown; elemType := TTypes.Unknown;
@@ -577,20 +611,22 @@ begin
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType); Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
end; end;
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; function TTypeChecker.VisitMemberAccess(const Node: IAstNode): IAstNode;
var var
M: IMemberAccessNode;
newBase: IAstNode; newBase: IAstNode;
baseType, resType: IStaticType; baseType, resType: IStaticType;
idx: Integer; idx: Integer;
isOpt: Boolean; isOpt: Boolean;
begin begin
newBase := Accept(Node.Base); M := Node.AsMemberAccess;
newBase := Accept(M.Base);
baseType := PrepareBaseType(newBase, isOpt); baseType := PrepareBaseType(newBase, isOpt);
resType := TTypes.Unknown; resType := TTypes.Unknown;
if (baseType.Kind = stRecord) or (baseType.Kind = stRecordSeries) then if (baseType.Kind = stRecord) or (baseType.Kind = stRecordSeries) then
begin begin
idx := baseType.Definition.IndexOf(Node.Member.Value); idx := baseType.Definition.IndexOf(M.Member.Value);
if idx >= 0 then if idx >= 0 then
begin begin
var fieldType := TTypes.FromScalarKind(baseType.Definition.Items[idx].Value); var fieldType := TTypes.FromScalarKind(baseType.Definition.Items[idx].Value);
@@ -604,45 +640,40 @@ begin
end; end;
resType := ApplyOptionality(resType, isOpt); resType := ApplyOptionality(resType, isOpt);
Result := TAst.MemberAccess(Node.Identity, newBase, Node.Member, resType); Result := TAst.MemberAccess(Node.Identity, newBase, M.Member, resType);
end; end;
function TTypeChecker.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; function TTypeChecker.VisitRecordLiteral(const Node: IAstNode): IAstNode;
var var
R: IRecordLiteralNode;
i: Integer; i: Integer;
newFields: TArray<IRecordFieldNode>;
fieldTypes: TArray<TPair<IKeyword, IStaticType>>; fieldTypes: TArray<TPair<IKeyword, IStaticType>>;
scalarFieldTypes: TArray<TPair<IKeyword, TScalar.TKind>>; scalarFieldTypes: TArray<TPair<IKeyword, TScalar.TKind>>;
isScalar: Boolean; isScalar: Boolean;
valType: IStaticType; valType: IStaticType;
key: IKeyword; key: IKeyword;
visitedValue: IAstNode;
newFieldList: IRecordFieldList; newFieldList: IRecordFieldList;
begin begin
SetLength(newFields, Node.Fields.Count); R := Node.AsRecordLiteral;
SetLength(fieldTypes, Node.Fields.Count); // Transform fields using inherited recursion
SetLength(scalarFieldTypes, Node.Fields.Count); newFieldList := inherited VisitRecordFieldList(R.Fields).AsRecordFieldList;
// Now analyze the transformed fields
var count := newFieldList.Count;
SetLength(fieldTypes, count);
SetLength(scalarFieldTypes, count);
isScalar := True; isScalar := True;
for i := 0 to Node.Fields.Count - 1 do for i := 0 to count - 1 do
begin begin
var oldField := Node.Fields[i]; var field := newFieldList[i];
visitedValue := Accept(oldField.Value); key := field.Key.Value;
valType := field.Value.AsTypedNode.StaticType;
// Keys are guaranteed to be keywords by parser
key := oldField.Key.Value;
// Recreate the field node with the typed value
newFields[i] := TAst.RecordField(oldField.Identity, oldField.Key, visitedValue);
// Analyze Type
valType := visitedValue.AsTypedNode.StaticType;
// Check if strict scalar (no optionals allowed in packed ScalarRecord) // Check if strict scalar (no optionals allowed in packed ScalarRecord)
if (valType.Kind in [stOrdinal, stFloat, stBoolean, stDateTime, stKeyword]) and (not valType.IsOptional) then if (valType.Kind in [stOrdinal, stFloat, stBoolean, stDateTime, stKeyword]) and (not valType.IsOptional) then
begin begin
var kind: TScalar.TKind; var kind: TScalar.TKind;
// Map StaticType Kind to Scalar Kind
case valType.Kind of case valType.Kind of
stOrdinal: kind := TScalar.TKind.Ordinal; stOrdinal: kind := TScalar.TKind.Ordinal;
stFloat: kind := TScalar.TKind.Float; stFloat: kind := TScalar.TKind.Float;
@@ -650,7 +681,7 @@ begin
stDateTime: kind := TScalar.TKind.DateTime; stDateTime: kind := TScalar.TKind.DateTime;
stKeyword: kind := TScalar.TKind.Keyword; stKeyword: kind := TScalar.TKind.Keyword;
else else
kind := TScalar.TKind.Ordinal; // Should not happen given check above kind := TScalar.TKind.Ordinal;
end; end;
scalarFieldTypes[i] := TPair<IKeyword, TScalar.TKind>.Create(key, kind); scalarFieldTypes[i] := TPair<IKeyword, TScalar.TKind>.Create(key, kind);
end end
@@ -667,7 +698,7 @@ begin
var genericDef: IGenericRecordDefinition := nil; var genericDef: IGenericRecordDefinition := nil;
var resultType: IStaticType; var resultType: IStaticType;
if isScalar and (Length(newFields) > 0) then if isScalar and (count > 0) then
begin begin
scalarDef := TKeywordMappingRegistry<TScalar.TKind>.Intern(scalarFieldTypes); scalarDef := TKeywordMappingRegistry<TScalar.TKind>.Intern(scalarFieldTypes);
resultType := TTypes.CreateRecord(scalarDef); resultType := TTypes.CreateRecord(scalarDef);
@@ -678,16 +709,17 @@ begin
resultType := TTypes.CreateGenericRecord(genericDef); resultType := TTypes.CreateGenericRecord(genericDef);
end; end;
newFieldList := TRecordFieldList.Create(newFields, Node.Fields.Identity);
Result := TAst.RecordLiteral(Node.Identity, newFieldList, scalarDef, genericDef, resultType); Result := TAst.RecordLiteral(Node.Identity, newFieldList, scalarDef, genericDef, resultType);
end; end;
function TTypeChecker.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode; function TTypeChecker.VisitCreateSeries(const Node: IAstNode): IAstNode;
var var
C: ICreateSeriesNode;
elemType: IStaticType; elemType: IStaticType;
def: string; def: string;
begin begin
def := Node.Definition; C := Node.AsCreateSeries;
def := C.Definition;
// Simple heuristic for type // Simple heuristic for type
if def.StartsWith('[') then if def.StartsWith('[') then
elemType := TTypes.CreateRecord(nil) // Placeholder, normally parses JSON elemType := TTypes.CreateRecord(nil) // Placeholder, normally parses JSON
@@ -697,12 +729,12 @@ begin
Result := TAst.CreateSeries(Node.Identity.AsDefinition, TTypes.CreateSeries(elemType)); Result := TAst.CreateSeries(Node.Identity.AsDefinition, TTypes.CreateSeries(elemType));
end; end;
function TTypeChecker.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; function TTypeChecker.VisitSeriesLength(const Node: IAstNode): IAstNode;
begin begin
Result := TAst.SeriesLength(Node.Identity, Accept(Node.Series).AsIdentifier, TTypes.Ordinal); Result := TAst.SeriesLength(Node.Identity, Accept(Node.AsSeriesLength.Series).AsIdentifier, TTypes.Ordinal);
end; end;
function TTypeChecker.VisitNop(const Node: INopNode): IAstNode; function TTypeChecker.VisitNop(const Node: IAstNode): IAstNode;
begin begin
Result := TAst.Nop(Node.Identity, TTypes.Void); Result := TAst.Nop(Node.Identity, TTypes.Void);
end; end;
@@ -711,14 +743,15 @@ end;
// PIPE IMPLEMENTATION (Type Checking) // PIPE IMPLEMENTATION (Type Checking)
// ============================================================================= // =============================================================================
function TTypeChecker.VisitPipeInput(const Node: IPipeInputNode): IAstNode; function TTypeChecker.VisitPipeInput(const Node: IAstNode): IAstNode;
var var
P: IPipeInputNode;
newSource: IIdentifierNode; newSource: IIdentifierNode;
sourceType: IStaticType; sourceType: IStaticType;
newSelectors: TArray<IKeywordNode>;
i: Integer;
begin begin
newSource := Accept(Node.StreamSource).AsIdentifier; P := Node.AsPipeInput;
// Transform source identifier (resolves type)
newSource := Accept(P.StreamSource).AsIdentifier;
sourceType := newSource.AsTypedNode.StaticType; sourceType := newSource.AsTypedNode.StaticType;
// 1. Verify Source is a Series-compatible type // 1. Verify Source is a Series-compatible type
@@ -736,7 +769,7 @@ begin
begin begin
// 2. Verify Selectors exist in Record Definition // 2. Verify Selectors exist in Record Definition
var def := sourceType.Definition; var def := sourceType.Definition;
for var sel in Node.Selectors do for var sel in P.Selectors do
begin begin
if def.IndexOf(sel.Value) < 0 then if def.IndexOf(sel.Value) < 0 then
begin begin
@@ -747,16 +780,13 @@ begin
end; end;
end; end;
// Reconstruct list simply to pass through // Reuse selectors (they are just keywords, no type checking needed)
SetLength(newSelectors, Node.Selectors.Count); Result := TAst.PipeInput(newSource, P.Selectors, Node.Identity.Location);
for i := 0 to Node.Selectors.Count - 1 do
newSelectors[i] := Node.Selectors[i];
Result := TAst.PipeInput(newSource, TAst.PipeSelectorList(newSelectors, Node.Selectors.Identity.Location), Node.Identity.Location);
end; end;
function TTypeChecker.VisitPipe(const Node: IPipeNode): IAstNode; function TTypeChecker.VisitPipe(const Node: IAstNode): IAstNode;
var var
P: IPipeNode;
i, k: Integer; i, k: Integer;
inputNode: IPipeInputNode; inputNode: IPipeInputNode;
newInputs: TArray<IPipeInputNode>; newInputs: TArray<IPipeInputNode>;
@@ -766,13 +796,15 @@ var
newParams: TArray<IIdentifierNode>; newParams: TArray<IIdentifierNode>;
inferredType: IStaticType; inferredType: IStaticType;
begin begin
SetLength(newInputs, Node.Inputs.Count); P := Node.AsPipe;
SetLength(newInputs, P.Inputs.Count);
paramTypes := TList<IStaticType>.Create; paramTypes := TList<IStaticType>.Create;
try try
// 1. Visit Inputs and collect types for Lambda parameters // 1. Visit Inputs and collect types for Lambda parameters
for i := 0 to Node.Inputs.Count - 1 do for i := 0 to P.Inputs.Count - 1 do
begin begin
inputNode := Accept(Node.Inputs[i]).AsPipeInput; // Recurse on inputs to resolve their sources
inputNode := Accept(P.Inputs[i]).AsPipeInput;
newInputs[i] := inputNode; newInputs[i] := inputNode;
streamType := inputNode.StreamSource.AsTypedNode.StaticType; streamType := inputNode.StreamSource.AsTypedNode.StaticType;
@@ -803,7 +835,7 @@ begin
end; end;
// 2. Prepare Lambda with Inferred Types // 2. Prepare Lambda with Inferred Types
lambda := Node.Transformation; lambda := P.Transformation;
if lambda.Parameters.Count <> paramTypes.Count then if lambda.Parameters.Count <> paramTypes.Count then
begin begin
@@ -871,7 +903,7 @@ begin
pipeType := TTypes.Unknown; pipeType := TTypes.Unknown;
end; end;
Result := TAst.Pipe(Node.Identity, TPipeInputList.Create(newInputs, Node.Inputs.Identity), typedLambda, pipeType); Result := TAst.Pipe(Node.Identity, TPipeInputList.Create(newInputs, P.Inputs.Identity), typedLambda, pipeType);
finally finally
paramTypes.Free; paramTypes.Free;
end; end;
+104 -284
View File
@@ -11,6 +11,7 @@ uses
Myc.Ast.Nodes, Myc.Ast.Nodes,
Myc.Ast.Scope, Myc.Ast.Scope,
Myc.Ast, Myc.Ast,
Myc.Ast.Visitor,
Myc.Ast.Evaluator; Myc.Ast.Evaluator;
type type
@@ -19,41 +20,25 @@ type
FLog: TStrings; FLog: TStrings;
FIndentLevel: Integer; FIndentLevel: Integer;
FShowScope: Boolean; FShowScope: Boolean;
procedure Indent; procedure Indent;
procedure Unindent; procedure Unindent;
procedure AppendLine(const S: string); procedure AppendLine(const S: string);
procedure ShowScope; procedure ShowScope;
function GetNodeLogInfo(const Node: IAstNode): string;
protected protected
// Overridden to provide the debug-specific visitor factory for nested calls (Lambdas).
function CreateVisitorFactory: TEvaluatorFactory; override; function CreateVisitorFactory: TEvaluatorFactory; override;
// Central Interceptor for all node types
function Visit(const Node: IAstNode): TDataValue; override;
// Specific overrides for enhanced logging/state observation
function VisitVariableDeclaration(const N: IVariableDeclarationNode): TDataValue; override;
function VisitAssignment(const N: IAssignmentNode): TDataValue; override;
function VisitFunctionCall(const N: IFunctionCallNode): TDataValue; override;
function VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue; override;
public public
constructor Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0); constructor Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0);
// The logging overrides for Visit... methods
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
function VisitConstant(const Node: IConstantNode): TDataValue; override;
function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override;
function VisitKeyword(const Node: IKeywordNode): TDataValue; override;
function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override;
function VisitCondExpression(const Node: ICondExpressionNode): TDataValue; override; // Replaces Ternary
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override;
function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
function VisitIndexer(const Node: IIndexerNode): TDataValue; override;
function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override;
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
function VisitNop(const Node: INopNode): TDataValue; override;
// Pipe Support
function VisitPipeInput(const Node: IPipeInputNode): TDataValue; override;
function VisitPipeSelectorList(const Node: IPipeSelectorList): TDataValue; override;
function VisitPipeInputList(const Node: IPipeInputList): TDataValue; override;
function VisitPipe(const Node: IPipeNode): TDataValue; override;
end; end;
implementation implementation
@@ -64,26 +49,21 @@ uses
{ TDebugEvaluatorVisitor } { TDebugEvaluatorVisitor }
constructor TDebugEvaluatorVisitor.Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0); constructor TDebugEvaluatorVisitor.Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer);
begin begin
inherited Create(AScope); inherited Create(AScope);
Assert(Assigned(ALog));
FLog := ALog; FLog := ALog;
FIndentLevel := AInitialIndent; FIndentLevel := AInitialIndent;
FShowScope := AShowScope; FShowScope := AShowScope;
if FShowScope and (AInitialIndent = 0) then
ShowScope; ShowScope;
end; end;
function TDebugEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory; function TDebugEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory;
begin begin
// Return a closure that creates a new Debug visitor, ensuring the debug context (Log, Indent) is passed down.
// This is used by TEvaluatorVisitor.VisitLambdaExpression when executing the closure body.
// Capture current state
var currentLog := FLog; var currentLog := FLog;
var currentShowScope := FShowScope; var currentShowScope := FShowScope;
var currentIndent := FIndentLevel; // Pass current indent as base for new frame var currentIndent := FIndentLevel;
Result := Result :=
function(const AScope: IExecutionScope): IEvaluatorVisitor function(const AScope: IExecutionScope): IEvaluatorVisitor
begin begin
@@ -91,14 +71,98 @@ begin
end; end;
end; end;
procedure TDebugEvaluatorVisitor.Indent; function TDebugEvaluatorVisitor.Visit(const Node: IAstNode): TDataValue;
var
info: string;
begin begin
inc(FIndentLevel); info := GetNodeLogInfo(Node);
if info <> '' then
begin
AppendLine(info + ' {');
Indent;
end; end;
try
// Dispatch to inherited logic (which will call our specialized VisitXyz overrides)
Result := inherited Visit(Node);
finally
if info <> '' then
begin
Unindent;
var resStr :=
if Result.IsVoid then '(void)'
else Result.ToString;
AppendLine('} -> ' + resStr);
end;
end;
end;
// --- Enhanced Observation Overrides ---
function TDebugEvaluatorVisitor.VisitVariableDeclaration(const N: IVariableDeclarationNode): TDataValue;
begin
Result := inherited VisitVariableDeclaration(N);
if FShowScope then
ShowScope;
end;
function TDebugEvaluatorVisitor.VisitAssignment(const N: IAssignmentNode): TDataValue;
begin
Result := inherited VisitAssignment(N);
if FShowScope then
ShowScope;
end;
function TDebugEvaluatorVisitor.VisitFunctionCall(const N: IFunctionCallNode): TDataValue;
begin
// Log target purity if statically known
if Assigned(N.StaticTarget) and N.IsTargetPure then
AppendLine('[Pure Static Call]');
Result := inherited VisitFunctionCall(N);
end;
function TDebugEvaluatorVisitor.VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue;
begin
if Length(N.Upvalues) > 0 then
AppendLine(Format('[Capturing %d upvalues]', [Length(N.Upvalues)]));
Result := inherited VisitLambdaExpression(N);
end;
// --- Internal Helpers ---
function TDebugEvaluatorVisitor.GetNodeLogInfo(const Node: IAstNode): string;
begin
case Node.Kind of
akFunctionCall:
begin
var c := Node.AsFunctionCall;
var mode :=
if Assigned(c.StaticTarget) then 'STATIC'
else 'DYNAMIC';
Result := Format('Call (%s, Tail=%s)', [mode, c.IsTailCall.ToString(TUseBoolStrs.True)]);
end;
akIdentifier: Result := 'ID:' + Node.AsIdentifier.Name;
akVariableDeclaration: Result := 'DEF:' + Node.AsVariableDeclaration.Target.AsIdentifier.Name;
akAssignment: Result := 'ASSIGN:' + Node.AsAssignment.Target.AsIdentifier.Name;
akIfExpression: Result := 'IF';
akCondExpression: Result := 'COND';
akRecur: Result := 'RECUR';
akBlockExpression: Result := 'BLOCK';
akLambdaExpression: Result := 'FN';
akRecordLiteral: Result := 'RECORD';
akPipe: Result := 'PIPE';
else
Result := ''; // Silence leaf nodes like Constants and Keywords to reduce noise
end;
end;
procedure TDebugEvaluatorVisitor.Indent;
begin
Inc(FIndentLevel);
end;
procedure TDebugEvaluatorVisitor.Unindent; procedure TDebugEvaluatorVisitor.Unindent;
begin begin
dec(FIndentLevel); Dec(FIndentLevel);
end; end;
procedure TDebugEvaluatorVisitor.AppendLine(const S: string); procedure TDebugEvaluatorVisitor.AppendLine(const S: string);
@@ -114,255 +178,11 @@ end;
procedure TDebugEvaluatorVisitor.ShowScope; procedure TDebugEvaluatorVisitor.ShowScope;
var var
scopeDump: TArray<string>;
line: string; line: string;
begin begin
if FShowScope then AppendLine(' [Scope State]');
begin for line in Scope.Dump.Split([sLineBreak]) do
AppendLine('-- Scope --'); AppendLine(' ' + line);
// Scope.Dump logic has been updated in Myc.Ast.Scope to handle new architecture
scopeDump := Scope.Dump.Split([sLineBreak]);
for line in scopeDump do
begin
AppendLine(line);
end;
AppendLine('-----------');
end;
end;
// --- Visit Overrides ---
function TDebugEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
var
mode: string;
begin
if Assigned(Node.StaticTarget) then
mode := 'STATIC'
else
mode := 'DYNAMIC';
AppendLine(Format('FunctionCall (%s, Tail=%s) {', [mode, Node.IsTailCall.ToString(TUseBoolStrs.True)]));
Indent;
try
// ShowScope is called in Constructor of new Visitor (via CreateVisitorFactory) for the body,
// but for arguments evaluation here in the current scope, we might want to see it.
ShowScope;
Result := inherited VisitFunctionCall(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
begin
AppendLine('AddSeriesItem {');
Indent;
try
Result := inherited VisitAddSeriesItem(Node);
finally
Unindent;
end;
AppendLine('} -> (void)');
end;
function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
begin
AppendLine(Format('Assignment to "%s" {', [Node.Target.AsIdentifier.Name]));
Indent;
try
Result := inherited VisitAssignment(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
begin
Result := inherited VisitConstant(Node);
AppendLine(Format('Constant (%s)', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue;
begin
Result := inherited VisitKeyword(Node);
AppendLine(Format('Keyword (%s)', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
begin
AppendLine('CreateSeries {');
Indent;
try
Result := inherited VisitCreateSeries(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin
Result := inherited VisitIdentifier(Node);
AppendLine(Format('Identifier "%s" -> %s', [Node.Name, Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
begin
AppendLine('IfExpr {');
Indent;
try
Result := inherited VisitIfExpression(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
begin
AppendLine('Indexer {');
Indent;
try
Result := inherited VisitIndexer(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
begin
AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Value.Name]));
Indent;
try
Result := inherited VisitMemberAccess(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
begin
AppendLine('RecordLiteral {');
Indent;
try
Result := inherited VisitRecordLiteral(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitCondExpression(const Node: ICondExpressionNode): TDataValue;
begin
AppendLine(Format('CondExpr (%d pairs) {', [Length(Node.Pairs)]));
Indent;
try
// We just call inherited, which does the logic.
// We don't log every branch check here to avoid spamming output,
// but the recursive evaluation of conditions will show up in the log naturally.
Result := inherited VisitCondExpression(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
begin
AppendLine('Block {');
Indent;
try
// Delegates to VisitExpressionList via inherited
Result := inherited VisitBlockExpression(Node);
// Scope might have changed after block execution if vars were defined
ShowScope;
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
begin
AppendLine(Format('VarDecl %s :=', [Node.Target.AsIdentifier.Name]));
Indent;
try
Result := inherited VisitVariableDeclaration(Node);
finally
Unindent;
end;
end;
function TDebugEvaluatorVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
begin
AppendLine('SeriesLength {');
Indent;
try
Result := inherited VisitSeriesLength(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitNop(const Node: INopNode): TDataValue;
begin
AppendLine('Nop (void)');
Result := TDataValue.Void;
end;
// --- Pipe Support ---
function TDebugEvaluatorVisitor.VisitPipe(const Node: IPipeNode): TDataValue;
begin
AppendLine('Pipe {');
Indent;
try
Result := inherited VisitPipe(Node);
finally
Unindent;
end;
AppendLine(Format('} -> %s', [Result.ToString]));
end;
function TDebugEvaluatorVisitor.VisitPipeInput(const Node: IPipeInputNode): TDataValue;
begin
AppendLine(Format('PipeInput (Source: %s) {', [Node.StreamSource.Name]));
Indent;
try
Result := inherited VisitPipeInput(Node);
finally
Unindent;
end;
AppendLine('}');
end;
function TDebugEvaluatorVisitor.VisitPipeSelectorList(const Node: IPipeSelectorList): TDataValue;
begin
AppendLine('Selectors [');
Indent;
try
Result := inherited VisitPipeSelectorList(Node);
finally
Unindent;
end;
AppendLine(']');
end;
function TDebugEvaluatorVisitor.VisitPipeInputList(const Node: IPipeInputList): TDataValue;
begin
AppendLine('Inputs [');
Indent;
try
Result := inherited VisitPipeInputList(Node);
finally
Unindent;
end;
AppendLine(']');
end; end;
end. end.
+307 -231
View File
@@ -29,38 +29,47 @@ type
procedure LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil); overload; procedure LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil); overload;
function FormatAddress(const Addr: TResolvedAddress): string; function FormatAddress(const Addr: TResolvedAddress): string;
protected strict private
function VisitConstant(const Node: IConstantNode): TVoid; override; // Internal visit helpers with strict IAstNode signature
function VisitIdentifier(const Node: IIdentifierNode): TVoid; override; function VisitConstant(const Node: IAstNode): TVoid;
function VisitKeyword(const Node: IKeywordNode): TVoid; override; function VisitIdentifier(const Node: IAstNode): TVoid;
function VisitIfExpression(const Node: IIfExpressionNode): TVoid; override; function VisitKeyword(const Node: IAstNode): TVoid;
function VisitCondExpression(const Node: ICondExpressionNode): TVoid; override; // Replaced Ternary function VisitIfExpression(const Node: IAstNode): TVoid;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid; override; function VisitCondExpression(const Node: IAstNode): TVoid;
function VisitFunctionCall(const Node: IFunctionCallNode): TVoid; override; function VisitLambdaExpression(const Node: IAstNode): TVoid;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid; override; function VisitFunctionCall(const Node: IAstNode): TVoid;
function VisitRecurNode(const Node: IRecurNode): TVoid; override; function VisitMacroExpansionNode(const Node: IAstNode): TVoid;
function VisitBlockExpression(const Node: IBlockExpressionNode): TVoid; override; function VisitRecurNode(const Node: IAstNode): TVoid;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid; override; function VisitBlockExpression(const Node: IAstNode): TVoid;
function VisitAssignment(const Node: IAssignmentNode): TVoid; override; function VisitVariableDeclaration(const Node: IAstNode): TVoid;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid; override; function VisitAssignment(const Node: IAstNode): TVoid;
function VisitQuasiquote(const Node: IQuasiquoteNode): TVoid; override; function VisitMacroDefinition(const Node: IAstNode): TVoid;
function VisitUnquote(const Node: IUnquoteNode): TVoid; override; function VisitQuasiquote(const Node: IAstNode): TVoid;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid; override; function VisitUnquote(const Node: IAstNode): TVoid;
function VisitIndexer(const Node: IIndexerNode): TVoid; override; function VisitUnquoteSplicing(const Node: IAstNode): TVoid;
function VisitMemberAccess(const Node: IMemberAccessNode): TVoid; override; function VisitIndexer(const Node: IAstNode): TVoid;
function VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid; override; function VisitMemberAccess(const Node: IAstNode): TVoid;
function VisitCreateSeries(const Node: ICreateSeriesNode): TVoid; override; function VisitRecordLiteral(const Node: IAstNode): TVoid;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid; override; function VisitCreateSeries(const Node: IAstNode): TVoid;
function VisitSeriesLength(const Node: ISeriesLengthNode): TVoid; override; function VisitAddSeriesItem(const Node: IAstNode): TVoid;
function VisitNop(const Node: INopNode): TVoid; override; function VisitSeriesLength(const Node: IAstNode): TVoid;
function VisitNop(const Node: IAstNode): TVoid;
// List Visitors are handled implicitly by parent node iteration in Dumper, // List Visitors
// but we implement them empty/default to satisfy the abstract base class if called directly. function VisitParameterList(const Node: IAstNode): TVoid;
function VisitParameterList(const Node: IParameterList): TVoid; override; function VisitArgumentList(const Node: IAstNode): TVoid;
function VisitArgumentList(const Node: IArgumentList): TVoid; override; function VisitExpressionList(const Node: IAstNode): TVoid;
function VisitExpressionList(const Node: IExpressionList): TVoid; override; function VisitRecordFieldList(const Node: IAstNode): TVoid;
function VisitRecordFieldList(const Node: IRecordFieldList): TVoid; override; function VisitRecordField(const Node: IAstNode): TVoid;
function VisitRecordField(const Node: IRecordFieldNode): TVoid; override;
// Pipe Visitors
function VisitPipeInput(const Node: IAstNode): TVoid;
function VisitPipeSelectorList(const Node: IAstNode): TVoid;
function VisitPipeInputList(const Node: IAstNode): TVoid;
function VisitPipe(const Node: IAstNode): TVoid;
protected
procedure SetupHandlers; override;
public public
constructor Create(const AOutput: TStrings); constructor Create(const AOutput: TStrings);
@@ -81,7 +90,9 @@ var
dumper: TAstDumper; dumper: TAstDumper;
begin begin
if (not Assigned(Output)) or (not Assigned(RootNode)) then if (not Assigned(Output)) or (not Assigned(RootNode)) then
begin
exit; exit;
end;
Output.Clear; Output.Clear;
dumper := TAstDumper.Create(Output); dumper := TAstDumper.Create(Output);
@@ -99,10 +110,51 @@ begin
FIndent := 0; FIndent := 0;
end; end;
procedure TAstDumper.SetupHandlers;
begin
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
Register(akParameterList, VisitParameterList);
Register(akArgumentList, VisitArgumentList);
Register(akExpressionList, VisitExpressionList);
Register(akRecordFieldList, VisitRecordFieldList);
Register(akRecordField, VisitRecordField);
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akMacroExpansion, VisitMacroExpansionNode);
Register(akBlockExpression, VisitBlockExpression);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akMacroDefinition, VisitMacroDefinition);
Register(akQuasiquote, VisitQuasiquote);
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akCreateSeries, VisitCreateSeries);
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
procedure TAstDumper.Execute(const RootNode: IAstNode); procedure TAstDumper.Execute(const RootNode: IAstNode);
begin begin
if Assigned(RootNode) then if Assigned(RootNode) then
RootNode.Accept(Self); begin
Visit(RootNode);
end;
end; end;
procedure TAstDumper.Indent; procedure TAstDumper.Indent;
@@ -115,26 +167,28 @@ begin
dec(FIndent, 2); dec(FIndent, 2);
end; end;
procedure TAstDumper.Log(const Text: string; const Node: IAstNode = nil); procedure TAstDumper.Log(const Text: string; const Node: IAstNode);
var var
typeStr: string; typeStr: string;
typedNode: IAstTypedNode;
staticType: IStaticType; staticType: IStaticType;
begin begin
typeStr := ''; typeStr := '';
if Assigned(Node) and Node.IsTyped then if Assigned(Node) and Node.IsTyped then
begin begin
typedNode := Node.AsTypedNode; staticType := Node.AsTypedNode.StaticType;
staticType := typedNode.StaticType;
if Assigned(staticType) then if Assigned(staticType) then
typeStr := Format(' <Type: %s>', [staticType.ToString]) begin
typeStr := Format(' <Type: %s>', [staticType.ToString]);
end
else else
begin
typeStr := ' <Type: nil>'; typeStr := ' <Type: nil>';
end; end;
end;
FOutput.Add(StringOfChar(' ', FIndent) + Text + typeStr); FOutput.Add(StringOfChar(' ', FIndent) + Text + typeStr);
end; end;
procedure TAstDumper.LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil); procedure TAstDumper.LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode);
begin begin
Log(Format(Fmt, Args), Node); Log(Format(Fmt, Args), Node);
end; end;
@@ -150,402 +204,424 @@ begin
end; end;
end; end;
function TAstDumper.VisitConstant(const Node: IConstantNode): TVoid; function TAstDumper.VisitConstant(const Node: IAstNode): TVoid;
begin begin
LogFmt('Constant: %s', [Node.Value.ToString], Node); LogFmt('Constant: %s', [Node.AsConstant.Value.ToString], Node);
end; end;
function TAstDumper.VisitIdentifier(const Node: IIdentifierNode): TVoid; function TAstDumper.VisitIdentifier(const Node: IAstNode): TVoid;
var var
adr: TResolvedAddress; I: IIdentifierNode;
begin begin
adr := Node.Address; I := Node.AsIdentifier;
if adr.Kind <> akUnresolved then if I.Address.Kind <> akUnresolved then
LogFmt('Identifier: %s -> %s', [Node.Name, FormatAddress(adr)], Node) begin
LogFmt('Identifier: %s -> %s', [I.Name, FormatAddress(I.Address)], Node);
end
else else
LogFmt('Identifier: %s (unbound)', [Node.Name], Node); begin
LogFmt('Identifier: %s (unbound)', [I.Name], Node);
end;
end; end;
function TAstDumper.VisitKeyword(const Node: IKeywordNode): TVoid; function TAstDumper.VisitKeyword(const Node: IAstNode): TVoid;
begin begin
LogFmt('Keyword: :%s', [Node.Value.Name], Node); LogFmt('Keyword: :%s', [Node.AsKeyword.Value.Name], Node);
end; end;
function TAstDumper.VisitIfExpression(const Node: IIfExpressionNode): TVoid; function TAstDumper.VisitIfExpression(const Node: IAstNode): TVoid;
var
E: IIfExpressionNode;
begin begin
E := Node.AsIfExpression;
Log('IfExpression', Node); Log('IfExpression', Node);
Indent; Indent;
Log('Condition:'); Log('Condition:');
Node.Condition.Accept(Self); Visit(E.Condition);
Log('Then:'); Log('Then:');
Node.ThenBranch.Accept(Self); Visit(E.ThenBranch);
if Assigned(Node.ElseBranch) then if Assigned(E.ElseBranch) then
begin begin
Log('Else:'); Log('Else:');
Node.ElseBranch.Accept(Self); Visit(E.ElseBranch);
end; end;
Unindent; Unindent;
end; end;
function TAstDumper.VisitCondExpression(const Node: ICondExpressionNode): TVoid; function TAstDumper.VisitCondExpression(const Node: IAstNode): TVoid;
var var
E: ICondExpressionNode;
i: Integer; i: Integer;
begin begin
LogFmt('CondExpression (%d pairs)', [Length(Node.Pairs)], Node); E := Node.AsCondExpression;
LogFmt('CondExpression (%d pairs)', [Length(E.Pairs)], Node);
Indent; Indent;
for i := 0 to High(Node.Pairs) do for i := 0 to High(E.Pairs) do
begin begin
LogFmt('Pair %d:', [i]); LogFmt('Pair %d:', [i]);
Indent; Indent;
Log('Condition:'); Log('Condition:');
Node.Pairs[i].Condition.Accept(Self); Visit(E.Pairs[i].Condition);
Log('Branch:'); Log('Branch:');
Node.Pairs[i].Branch.Accept(Self); Visit(E.Pairs[i].Branch);
Unindent; Unindent;
end; end;
Log('Else:'); Log('Else:');
Node.ElseBranch.Accept(Self); Visit(E.ElseBranch);
Unindent; Unindent;
end; end;
function TAstDumper.VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid; function TAstDumper.VisitLambdaExpression(const Node: IAstNode): TVoid;
var var
upvalueAddr: TResolvedAddress; E: ILambdaExpressionNode;
addr: TResolvedAddress;
symbols: TArray<string>; symbols: TArray<string>;
layout: IScopeLayout; layout: IScopeLayout;
slot: Integer; slot: Integer;
typ: IStaticType; typ: IStaticType;
begin begin
E := Node.AsLambdaExpression;
LogFmt( LogFmt(
'LambdaExpression (HasNested: %s, IsPure: %s)', 'LambdaExpression (HasNested: %s, IsPure: %s)',
[Node.HasNestedLambdas.ToString(TUseBoolStrs.True), BoolToStr(Node.IsPure, True)], [E.HasNestedLambdas.ToString(TUseBoolStrs.True), E.IsPure.ToString(TUseBoolStrs.True)],
Node Node
); );
Indent; Indent;
// 1. Layout & Symbols if Assigned(E.Layout) then
if Assigned(Node.Layout) then
begin begin
LogFmt('Scope: Layout Slots=%d', [Node.Layout.SlotCount]); LogFmt('Scope: Layout Slots=%d', [E.Layout.SlotCount]);
if Assigned(Node.Descriptor) then if Assigned(E.Descriptor) then
begin begin
Log('Symbol Table:'); Log('Symbol Table:');
Indent; Indent;
layout := Node.Layout; layout := E.Layout;
symbols := layout.GetSymbols; symbols := layout.GetSymbols;
TArray.Sort<string>(symbols); TArray.Sort<string>(symbols);
for var name in symbols do for var name in symbols do
begin begin
slot := layout.FindSlot(name); slot := layout.FindSlot(name);
typ := Node.Descriptor.GetSymbolType(slot); typ := E.Descriptor.GetSymbolType(slot);
LogFmt('"%s" -> Slot %d (Type: %s)', [name, slot, typ.ToString]); LogFmt('"%s" -> Slot %d (Type: %s)', [name, slot, typ.ToString]);
end; end;
Unindent; Unindent;
end; end;
end end;
else
Log('Scope: No Layout (Raw)');
// 3. Parameters
Log('Parameters:'); Log('Parameters:');
Indent; Indent;
// Iterate over IParameterList Visit(E.Parameters);
for var param in Node.Parameters do
param.Accept(Self);
Unindent; Unindent;
// 4. Upvalues if Length(E.Upvalues) > 0 then
if Length(Node.Upvalues) > 0 then
begin begin
LogFmt('Captured Upvalues (%d):', [Length(Node.Upvalues)]); LogFmt('Captured Upvalues (%d):', [Length(E.Upvalues)]);
Indent; Indent;
for upvalueAddr in Node.Upvalues do for addr in E.Upvalues do
Log(FormatAddress(upvalueAddr)); begin
Log(FormatAddress(addr));
end;
Unindent; Unindent;
end; end;
// 5. Body
Log('Body:'); Log('Body:');
Node.Body.Accept(Self); Visit(E.Body);
Unindent; Unindent;
end; end;
function TAstDumper.VisitFunctionCall(const Node: IFunctionCallNode): TVoid; function TAstDumper.VisitFunctionCall(const Node: IAstNode): TVoid;
var var
arg: IAstNode; C: IFunctionCallNode;
staticStatus: string;
sigStr: string;
argTypes: TArray<string>; argTypes: TArray<string>;
i: Integer; i: Integer;
begin begin
sigStr := ''; C := Node.AsFunctionCall;
// Note: Node.Arguments is IArgumentList now.
if Assigned(Node.StaticTarget) then
begin
staticStatus := 'Assigned';
SetLength(argTypes, Node.Arguments.Count);
for i := 0 to Node.Arguments.Count - 1 do
begin
if Node.Arguments[i].IsTyped then
argTypes[i] := Node.Arguments[i].AsTypedNode.StaticType.ToString
else
argTypes[i] := 'Untyped';
end;
sigStr := Format(' <ResolvedSig: Method(%s): %s>', [string.Join(', ', argTypes), Node.StaticType.ToString]);
end
else
staticStatus := 'nil';
LogFmt( LogFmt(
'FunctionCall (IsTailCall: %s, StaticTarget: %s%s, IsTargetPure: %s)', 'FunctionCall (IsTailCall: %s, StaticTarget: %s, IsTargetPure: %s)',
[Node.IsTailCall.ToString(TUseBoolStrs.True), staticStatus, sigStr, BoolToStr(Node.IsTargetPure, True)], [
C.IsTailCall.ToString(TUseBoolStrs.True),
Assigned(C.StaticTarget).ToString(TUseBoolStrs.True),
C.IsTargetPure.ToString(TUseBoolStrs.True)
],
Node Node
); );
if Assigned(C.StaticTarget) then
begin
Indent; Indent;
Log('Callee:'); SetLength(argTypes, C.Arguments.Count);
Node.Callee.Accept(Self); for i := 0 to C.Arguments.Count - 1 do
LogFmt('Arguments (%d):', [Node.Arguments.Count]); begin
Indent; if C.Arguments[i].IsTyped then
// Iterate over IArgumentList argTypes[i] := C.Arguments[i].AsTypedNode.StaticType.ToString
for arg in Node.Arguments do else
arg.Accept(Self); argTypes[i] := 'Untyped';
Unindent; end;
LogFmt('ResolvedSig: Method(%s): %s', [string.Join(', ', argTypes), C.StaticType.ToString]);
Unindent; Unindent;
end; end;
function TAstDumper.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid; Indent;
Log('Callee:');
Visit(C.Callee);
LogFmt('Arguments (%d):', [C.Arguments.Count]);
Visit(C.Arguments);
Unindent;
end;
function TAstDumper.VisitMacroExpansionNode(const Node: IAstNode): TVoid;
var var
arg: IAstNode; M: IMacroExpansionNode;
begin begin
M := Node.AsMacroExpansion;
Log('MacroExpansion', Node); Log('MacroExpansion', Node);
Indent; Indent;
Log('Original Call:'); Log('Original Call:');
Indent; Visit(M.CallNode);
Log('Callee:');
Node.CallNode.Callee.Accept(Self);
LogFmt('Arguments (%d):', [Node.CallNode.Arguments.Count]);
Indent;
for arg in Node.CallNode.Arguments do
arg.Accept(Self);
Unindent;
Unindent;
Log('Expanded Body:'); Log('Expanded Body:');
Indent; Visit(M.ExpandedBody);
Node.ExpandedBody.Accept(Self);
Unindent;
Unindent; Unindent;
end; end;
function TAstDumper.VisitRecurNode(const Node: IRecurNode): TVoid; function TAstDumper.VisitRecurNode(const Node: IAstNode): TVoid;
var
arg: IAstNode;
begin begin
Log('Recur', Node); Log('Recur', Node);
Indent; Indent;
LogFmt('Arguments (%d):', [Node.Arguments.Count]); Visit(Node.AsRecur.Arguments);
Indent;
for arg in Node.Arguments do
arg.Accept(Self);
Unindent;
Unindent; Unindent;
end; end;
function TAstDumper.VisitBlockExpression(const Node: IBlockExpressionNode): TVoid; function TAstDumper.VisitBlockExpression(const Node: IAstNode): TVoid;
var
expr: IAstNode;
begin begin
Log('BlockExpression', Node); Log('BlockExpression', Node);
Indent; Indent;
for expr in Node.Expressions do Visit(Node.AsBlockExpression.Expressions);
expr.Accept(Self);
Unindent; Unindent;
end; end;
function TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid; function TAstDumper.VisitVariableDeclaration(const Node: IAstNode): TVoid;
var
V: IVariableDeclarationNode;
begin begin
LogFmt('VariableDeclaration (IsBoxed: %s)', [Node.IsBoxed.ToString(TUseBoolStrs.True)], Node); V := Node.AsVariableDeclaration;
LogFmt('VariableDeclaration (IsBoxed: %s)', [V.IsBoxed.ToString(TUseBoolStrs.True)], Node);
Indent; Indent;
Node.Target.Accept(Self); Visit(V.Target);
if Assigned(Node.Initializer) then if Assigned(V.Initializer) then
begin begin
Log('Initializer:'); Log('Initializer:');
Node.Initializer.Accept(Self); Visit(V.Initializer);
end; end;
Unindent; Unindent;
end; end;
function TAstDumper.VisitAssignment(const Node: IAssignmentNode): TVoid; function TAstDumper.VisitAssignment(const Node: IAstNode): TVoid;
var
A: IAssignmentNode;
begin begin
A := Node.AsAssignment;
Log('Assignment', Node); Log('Assignment', Node);
Indent; Indent;
Node.Target.Accept(Self); Visit(A.Target);
Log('Value:'); Log('Value:');
Node.Value.Accept(Self); Visit(A.Value);
Unindent; Unindent;
end; end;
function TAstDumper.VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid; function TAstDumper.VisitMacroDefinition(const Node: IAstNode): TVoid;
var
M: IMacroDefinitionNode;
begin begin
M := Node.AsMacroDefinition;
Log('MacroDefinition', Node); Log('MacroDefinition', Node);
Indent; Indent;
Log('Name:'); Log('Name:');
Indent; Visit(M.Name);
Node.Name.Accept(Self);
Unindent;
Log('Parameters:'); Log('Parameters:');
Indent; Visit(M.Parameters);
for var param in Node.Parameters do
param.Accept(Self);
Unindent;
Log('Body:'); Log('Body:');
Indent; Visit(M.Body);
Node.Body.Accept(Self);
Unindent;
Unindent; Unindent;
end; end;
function TAstDumper.VisitQuasiquote(const Node: IQuasiquoteNode): TVoid; function TAstDumper.VisitQuasiquote(const Node: IAstNode): TVoid;
begin begin
Log('Quasiquote', Node); Log('Quasiquote', Node);
Indent; Indent;
Node.Expression.Accept(Self); Visit(Node.AsQuasiquote.Expression);
Unindent; Unindent;
end; end;
function TAstDumper.VisitUnquote(const Node: IUnquoteNode): TVoid; function TAstDumper.VisitUnquote(const Node: IAstNode): TVoid;
begin begin
Log('Unquote', Node); Log('Unquote', Node);
Indent; Indent;
Node.Expression.Accept(Self); Visit(Node.AsUnquote.Expression);
Unindent; Unindent;
end; end;
function TAstDumper.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid; function TAstDumper.VisitUnquoteSplicing(const Node: IAstNode): TVoid;
begin begin
Log('UnquoteSplicing', Node); Log('UnquoteSplicing', Node);
Indent; Indent;
Node.Expression.Accept(Self); Visit(Node.AsUnquoteSplicing.Expression);
Unindent; Unindent;
end; end;
function TAstDumper.VisitIndexer(const Node: IIndexerNode): TVoid; function TAstDumper.VisitIndexer(const Node: IAstNode): TVoid;
var
I: IIndexerNode;
begin begin
I := Node.AsIndexer;
Log('Indexer', Node); Log('Indexer', Node);
Indent; Indent;
Log('Base:'); Log('Base:');
Node.Base.Accept(Self); Visit(I.Base);
Log('Index:'); Log('Index:');
Node.Index.Accept(Self); Visit(I.Index);
Unindent; Unindent;
end; end;
function TAstDumper.VisitMemberAccess(const Node: IMemberAccessNode): TVoid; function TAstDumper.VisitMemberAccess(const Node: IAstNode): TVoid;
var
M: IMemberAccessNode;
begin begin
M := Node.AsMemberAccess;
Log('MemberAccess', Node); Log('MemberAccess', Node);
Indent; Indent;
Log('Base:'); Log('Base:');
Node.Base.Accept(Self); Visit(M.Base);
Log('Member:'); Log('Member:');
Node.Member.Accept(Self); Visit(M.Member);
Unindent; Unindent;
end; end;
function TAstDumper.VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid; function TAstDumper.VisitRecordLiteral(const Node: IAstNode): TVoid;
var var
field: IRecordFieldNode; R: IRecordLiteralNode;
begin begin
LogFmt('RecordLiteral (%d fields)', [Node.Fields.Count], Node); R := Node.AsRecordLiteral;
LogFmt('RecordLiteral (%d fields)', [R.Fields.Count], Node);
Indent; Indent;
// Iterate over IRecordFieldList Visit(R.Fields);
for field in Node.Fields do
begin
LogFmt('Field :%s', [field.Key.Value.Name]);
Indent;
field.Value.Accept(Self);
Unindent;
end;
Unindent; Unindent;
end; end;
function TAstDumper.VisitCreateSeries(const Node: ICreateSeriesNode): TVoid; function TAstDumper.VisitCreateSeries(const Node: IAstNode): TVoid;
begin begin
LogFmt('CreateSeries: %s', [Node.Definition], Node); LogFmt('CreateSeries: %s', [Node.AsCreateSeries.Definition], Node);
end; end;
function TAstDumper.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid; function TAstDumper.VisitAddSeriesItem(const Node: IAstNode): TVoid;
var
A: IAddSeriesItemNode;
begin begin
A := Node.AsAddSeriesItem;
Log('AddSeriesItem', Node); Log('AddSeriesItem', Node);
Indent; Indent;
Log('Series:'); Log('Series:');
Node.Series.Accept(Self); Visit(A.Series);
Log('Value:'); Log('Value:');
Node.Value.Accept(Self); Visit(A.Value);
if Assigned(Node.Lookback) then if Assigned(A.Lookback) then
begin begin
Log('Lookback:'); Log('Lookback:');
Node.Lookback.Accept(Self); Visit(A.Lookback);
end; end;
Unindent; Unindent;
end; end;
function TAstDumper.VisitSeriesLength(const Node: ISeriesLengthNode): TVoid; function TAstDumper.VisitSeriesLength(const Node: IAstNode): TVoid;
begin begin
Log('SeriesLength', Node); Log('SeriesLength', Node);
Indent; Indent;
Log('Series:'); Visit(Node.AsSeriesLength.Series);
Node.Series.Accept(Self);
Unindent; Unindent;
end; end;
function TAstDumper.VisitNop(const Node: INopNode): TVoid; function TAstDumper.VisitNop(const Node: IAstNode): TVoid;
begin begin
Log('Nop', Node); Log('Nop', Node);
end; end;
// --- List Visitors Implementation --- { List Visitors }
function TAstDumper.VisitParameterList(const Node: IParameterList): TVoid; function TAstDumper.VisitParameterList(const Node: IAstNode): TVoid;
begin begin
// Usually handled by Parent Node iteration (Lambda), for var item in Node.AsParameterList do
// but if visited directly: Visit(item);
for var item in Node do
item.Accept(Self);
end; end;
function TAstDumper.VisitArgumentList(const Node: IArgumentList): TVoid; function TAstDumper.VisitArgumentList(const Node: IAstNode): TVoid;
begin begin
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitExpressionList(const Node: IExpressionList): TVoid;
begin
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitRecordFieldList(const Node: IRecordFieldList): TVoid;
begin
for var item in Node do
item.Accept(Self);
end;
function TAstDumper.VisitRecordField(const Node: IRecordFieldNode): TVoid;
begin
// If visited directly (e.g. inside a list)
LogFmt('Field :%s', [Node.Key.Value.Name]);
Indent; Indent;
Node.Value.Accept(Self); for var item in Node.AsArgumentList do
Visit(item);
Unindent;
end;
function TAstDumper.VisitExpressionList(const Node: IAstNode): TVoid;
begin
for var item in Node.AsExpressionList do
Visit(item);
end;
function TAstDumper.VisitRecordFieldList(const Node: IAstNode): TVoid;
begin
for var item in Node.AsRecordFieldList do
Visit(item);
end;
function TAstDumper.VisitRecordField(const Node: IAstNode): TVoid;
var
F: IRecordFieldNode;
begin
F := Node.AsRecordField;
LogFmt('Field :%s', [F.Key.Value.Name]);
Indent;
Visit(F.Value);
Unindent;
end;
{ Pipe Visitors }
function TAstDumper.VisitPipeInput(const Node: IAstNode): TVoid;
var
P: IPipeInputNode;
begin
P := Node.AsPipeInput;
Log('PipeInput', Node);
Indent;
Log('Source:');
Visit(P.StreamSource);
Log('Selectors:');
Visit(P.Selectors);
Unindent;
end;
function TAstDumper.VisitPipeSelectorList(const Node: IAstNode): TVoid;
begin
for var item in Node.AsPipeSelectorList do
Visit(item);
end;
function TAstDumper.VisitPipeInputList(const Node: IAstNode): TVoid;
begin
for var item in Node.AsPipeInputList do
Visit(item);
end;
function TAstDumper.VisitPipe(const Node: IAstNode): TVoid;
var
P: IPipeNode;
begin
P := Node.AsPipe;
Log('Pipe', Node);
Indent;
Log('Inputs:');
Visit(P.Inputs);
Log('Transformation:');
Visit(P.Transformation);
Unindent; Unindent;
end; end;
File diff suppressed because it is too large Load Diff
+543 -480
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -1715,6 +1715,10 @@ constructor TLambdaExpressionNode.Create(
); );
begin begin
inherited Create(AStaticType, AIdentity); inherited Create(AStaticType, AIdentity);
Assert(Assigned(AParameters));
Assert(Assigned(ABody));
FParameters := AParameters; FParameters := AParameters;
FBody := ABody; FBody := ABody;
FLayout := ALayout; FLayout := ALayout;
+179 -196
View File
@@ -13,47 +13,39 @@ uses
type type
// Removes a specific node from the AST. // Removes a specific node from the AST.
// - If the node is part of a list, it is removed from the list.
// - If the node is a mandatory child of a structure (e.g. If-Condition),
// it is replaced by a Nop node to maintain tree integrity.
TAstNodeRemover = class(TAstTransformer) TAstNodeRemover = class(TAstTransformer)
private private
FTarget: IAstNode; FTarget: IAstNode;
FRemoved: Boolean; FRemoved: Boolean;
// Generic helper to filter nil items from lists during transformation
function FilterList<T: IAstNode>(const Source: INodeList<T>): TArray<T>; function FilterList<T: IAstNode>(const Source: INodeList<T>): TArray<T>;
// Helper to ensure a mandatory slot is never nil (replaces with Nop)
function EnsureNode(const Node: IAstNode; const ContextNode: IAstNode): IAstNode; function EnsureNode(const Node: IAstNode; const ContextNode: IAstNode): IAstNode;
strict private
// List Containers (Filter Logic)
function VisitBlockExpression(const Node: IAstNode): IAstNode;
function VisitExpressionList(const Node: IAstNode): IAstNode;
function VisitArgumentList(const Node: IAstNode): IAstNode;
function VisitParameterList(const Node: IAstNode): IAstNode;
function VisitRecordFieldList(const Node: IAstNode): IAstNode;
// Structural Nodes (Replace Logic)
function VisitIfExpression(const Node: IAstNode): IAstNode;
function VisitCondExpression(const Node: IAstNode): IAstNode;
function VisitAssignment(const Node: IAstNode): IAstNode;
function VisitVariableDeclaration(const Node: IAstNode): IAstNode;
function VisitLambdaExpression(const Node: IAstNode): IAstNode;
function VisitFunctionCall(const Node: IAstNode): IAstNode;
function VisitIndexer(const Node: IAstNode): IAstNode;
function VisitMemberAccess(const Node: IAstNode): IAstNode;
function VisitAddSeriesItem(const Node: IAstNode): IAstNode;
protected protected
procedure SetupHandlers; override;
function Accept(const Node: IAstNode): IAstNode; override; function Accept(const Node: IAstNode): IAstNode; override;
// --- List Containers (Filter Logic) ---
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitExpressionList(const Node: IExpressionList): IAstNode; override;
function VisitArgumentList(const Node: IArgumentList): IAstNode; override;
function VisitParameterList(const Node: IParameterList): IAstNode; override;
function VisitRecordFieldList(const Node: IRecordFieldList): IAstNode; override;
// --- Structural Nodes (Replace Logic) ---
function VisitIfExpression(const Node: IIfExpressionNode): IAstNode; override;
function VisitCondExpression(const Node: ICondExpressionNode): IAstNode; override; // Replaces Ternary
function VisitAssignment(const Node: IAssignmentNode): IAstNode; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; override;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
function VisitIndexer(const Node: IIndexerNode): IAstNode; override;
function VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; override;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override;
public public
constructor Create(const ATarget: IAstNode); constructor Create(const ATarget: IAstNode);
// Tries to remove TargetNode from RootNode.
// Returns True if the node was found and removed/replaced.
// If successful, NewRootNode contains the modified AST.
// If not found, NewRootNode returns the original RootNode.
class function TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean; static; class function TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean; static;
end; end;
@@ -68,14 +60,35 @@ begin
FRemoved := False; FRemoved := False;
end; end;
procedure TAstNodeRemover.SetupHandlers;
begin
inherited SetupHandlers; // Defaults
// Filter Logic
Register(akBlockExpression, VisitBlockExpression);
Register(akExpressionList, VisitExpressionList);
Register(akArgumentList, VisitArgumentList);
Register(akParameterList, VisitParameterList);
Register(akRecordFieldList, VisitRecordFieldList);
// Replace Logic
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akAssignment, VisitAssignment);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akAddSeriesItem, VisitAddSeriesItem);
end;
class function TAstNodeRemover.TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean; class function TAstNodeRemover.TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean;
var var
Remover: TAstNodeRemover; Remover: TAstNodeRemover;
begin begin
// Edge case: Removing the root itself
if (RootNode = TargetNode) then if (RootNode = TargetNode) then
begin begin
// We replace the root with a Nop, as we cannot return 'nil' for a valid AST root expectation usually
NewRootNode := TAst.Nop(RootNode.Identity); NewRootNode := TAst.Nop(RootNode.Identity);
Result := True; Result := True;
Exit; Exit;
@@ -87,15 +100,9 @@ begin
Result := Remover.FRemoved; Result := Remover.FRemoved;
if not Result then if not Result then
begin NewRootNode := RootNode
// If nothing was removed, return the original node to ensure reference stability
NewRootNode := RootNode;
end
else if (NewRootNode = nil) then else if (NewRootNode = nil) then
begin
// Safety fallback: if traversal somehow resulted in nil (should be caught by EnsureNode logic usually)
NewRootNode := TAst.Nop(RootNode.Identity); NewRootNode := TAst.Nop(RootNode.Identity);
end;
finally finally
Remover.Free; Remover.Free;
end; end;
@@ -103,14 +110,11 @@ end;
function TAstNodeRemover.Accept(const Node: IAstNode): IAstNode; function TAstNodeRemover.Accept(const Node: IAstNode): IAstNode;
begin begin
// If we matched the target, flag it as removed and return nil.
// The parent visitor is responsible for handling the nil result (Filter or Replace).
if Node = FTarget then if Node = FTarget then
begin begin
FRemoved := True; FRemoved := True;
Exit(nil); Exit(nil);
end; end;
Result := inherited Accept(Node); Result := inherited Accept(Node);
end; end;
@@ -119,7 +123,6 @@ begin
if Assigned(Node) then if Assigned(Node) then
Result := Node Result := Node
else else
// Mandatory child was deleted -> Replace with Nop using parent's identity location
Result := TAst.Nop(ContextNode.Identity); Result := TAst.Nop(ContextNode.Identity);
end; end;
@@ -136,9 +139,6 @@ begin
begin begin
item := Source[i]; item := Source[i];
transformed := Accept(item); transformed := Accept(item);
// If transformed is not nil, keep it.
// If it is nil, it was the target, so we skip (delete) it.
if Assigned(transformed) then if Assigned(transformed) then
list.Add(T(transformed)); list.Add(T(transformed));
end; end;
@@ -152,107 +152,114 @@ end;
// List Visitors (Filtering) // List Visitors (Filtering)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function TAstNodeRemover.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; function TAstNodeRemover.VisitBlockExpression(const Node: IAstNode): IAstNode;
var var
B: IBlockExpressionNode;
newExprs: TArray<IAstNode>; newExprs: TArray<IAstNode>;
newList: IExpressionList; newList: IExpressionList;
changed: Boolean;
i: Integer;
begin begin
newExprs := FilterList<IAstNode>(Node.Expressions); B := Node.AsBlockExpression;
newExprs := FilterList<IAstNode>(B.Expressions);
// If counts differ, something was removed if Length(newExprs) = B.Expressions.Count then
if Length(newExprs) = Node.Expressions.Count then
begin begin
// If not marked as removed, we assume no change in this branch
if not FRemoved then if not FRemoved then
Exit(Node); Exit(Node);
changed := False;
// Deep check for replacement for i := 0 to High(newExprs) do
var changed := False; if newExprs[i] <> B.Expressions[i] then
for var i := 0 to High(newExprs) do
if newExprs[i] <> Node.Expressions[i] then
changed := True; changed := True;
if not changed then if not changed then
Exit(Node); Exit(Node);
end; end;
newList := TExpressionList.Create(newExprs, Node.Expressions.Identity); newList := TExpressionList.Create(newExprs, B.Expressions.Identity);
Result := TAst.Block(Node.Identity, newList, Node.StaticType); Result := TAst.Block(Node.Identity, newList, B.StaticType);
end; end;
function TAstNodeRemover.VisitExpressionList(const Node: IExpressionList): IAstNode; function TAstNodeRemover.VisitExpressionList(const Node: IAstNode): IAstNode;
var var
L: IExpressionList;
newExprs: TArray<IAstNode>; newExprs: TArray<IAstNode>;
changed: Boolean;
i: Integer;
begin begin
newExprs := FilterList<IAstNode>(Node); L := Node.AsExpressionList;
newExprs := FilterList<IAstNode>(L);
if Length(newExprs) = Node.Count then if Length(newExprs) = L.Count then
begin begin
var changed := False; changed := False;
for var i := 0 to High(newExprs) do for i := 0 to High(newExprs) do
if newExprs[i] <> Node[i] then if newExprs[i] <> L[i] then
changed := True; changed := True;
if not changed then if not changed then
Exit(Node); Exit(Node);
end; end;
Result := TExpressionList.Create(newExprs, Node.Identity); Result := TExpressionList.Create(newExprs, Node.Identity);
end; end;
function TAstNodeRemover.VisitArgumentList(const Node: IArgumentList): IAstNode; function TAstNodeRemover.VisitArgumentList(const Node: IAstNode): IAstNode;
var var
L: IArgumentList;
newArgs: TArray<IAstNode>; newArgs: TArray<IAstNode>;
changed: Boolean;
i: Integer;
begin begin
newArgs := FilterList<IAstNode>(Node); L := Node.AsArgumentList;
newArgs := FilterList<IAstNode>(L);
if Length(newArgs) = Node.Count then if Length(newArgs) = L.Count then
begin begin
var changed := False; changed := False;
for var i := 0 to High(newArgs) do for i := 0 to High(newArgs) do
if newArgs[i] <> Node[i] then if newArgs[i] <> L[i] then
changed := True; changed := True;
if not changed then if not changed then
Exit(Node); Exit(Node);
end; end;
Result := TArgumentList.Create(newArgs, Node.Identity); Result := TArgumentList.Create(newArgs, Node.Identity);
end; end;
function TAstNodeRemover.VisitParameterList(const Node: IParameterList): IAstNode; function TAstNodeRemover.VisitParameterList(const Node: IAstNode): IAstNode;
var var
L: IParameterList;
newParams: TArray<IIdentifierNode>; newParams: TArray<IIdentifierNode>;
changed: Boolean;
i: Integer;
begin begin
newParams := FilterList<IIdentifierNode>(Node); L := Node.AsParameterList;
newParams := FilterList<IIdentifierNode>(L);
if Length(newParams) = Node.Count then if Length(newParams) = L.Count then
begin begin
var changed := False; changed := False;
for var i := 0 to High(newParams) do for i := 0 to High(newParams) do
if newParams[i] <> Node[i] then if newParams[i] <> L[i] then
changed := True; changed := True;
if not changed then if not changed then
Exit(Node); Exit(Node);
end; end;
Result := TParameterList.Create(newParams, Node.Identity); Result := TParameterList.Create(newParams, Node.Identity);
end; end;
function TAstNodeRemover.VisitRecordFieldList(const Node: IRecordFieldList): IAstNode; function TAstNodeRemover.VisitRecordFieldList(const Node: IAstNode): IAstNode;
var var
L: IRecordFieldList;
newFields: TArray<IRecordFieldNode>; newFields: TArray<IRecordFieldNode>;
changed: Boolean;
i: Integer;
begin begin
newFields := FilterList<IRecordFieldNode>(Node); L := Node.AsRecordFieldList;
newFields := FilterList<IRecordFieldNode>(L);
if Length(newFields) = Node.Count then if Length(newFields) = L.Count then
begin begin
var changed := False; changed := False;
for var i := 0 to High(newFields) do for i := 0 to High(newFields) do
if newFields[i] <> Node[i] then if newFields[i] <> L[i] then
changed := True; changed := True;
if not changed then if not changed then
Exit(Node); Exit(Node);
end; end;
Result := TRecordFieldList.Create(newFields, Node.Identity); Result := TRecordFieldList.Create(newFields, Node.Identity);
end; end;
@@ -260,190 +267,166 @@ end;
// Structural Visitors (Replacement with Nop/Void) // Structural Visitors (Replacement with Nop/Void)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function TAstNodeRemover.VisitIfExpression(const Node: IIfExpressionNode): IAstNode; function TAstNodeRemover.VisitIfExpression(const Node: IAstNode): IAstNode;
var var
E: IIfExpressionNode;
newCond, newThen, newElse: IAstNode; newCond, newThen, newElse: IAstNode;
begin begin
newCond := Accept(Node.Condition); E := Node.AsIfExpression;
newThen := Accept(Node.ThenBranch); newCond := EnsureNode(Accept(E.Condition), Node);
newElse := Accept(Node.ElseBranch); newThen := EnsureNode(Accept(E.ThenBranch), Node);
newElse := Accept(E.ElseBranch); // Optional
// If condition or Then-branch is deleted, replace with Nop if (newCond = E.Condition) and (newThen = E.ThenBranch) and (newElse = E.ElseBranch) then
newCond := EnsureNode(newCond, Node);
newThen := EnsureNode(newThen, Node);
// Else branch is optional
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
Exit(Node); Exit(Node);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, E.StaticType);
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
end; end;
function TAstNodeRemover.VisitCondExpression(const Node: ICondExpressionNode): IAstNode; function TAstNodeRemover.VisitCondExpression(const Node: IAstNode): IAstNode;
var var
E: ICondExpressionNode;
i: Integer; i: Integer;
newPairs: TArray<TCondPair>; newPairs: TArray<TCondPair>;
newElse: IAstNode; newElse: IAstNode;
newCond, newBranch: IAstNode; newCond, newBranch: IAstNode;
hasChanged: Boolean; hasChanged: Boolean;
begin begin
SetLength(newPairs, Length(Node.Pairs)); E := Node.AsCondExpression;
SetLength(newPairs, Length(E.Pairs));
hasChanged := False; hasChanged := False;
for i := 0 to High(Node.Pairs) do for i := 0 to High(E.Pairs) do
begin begin
// Ensure mandatory children exist (replace with Nop if removed) newCond := EnsureNode(Accept(E.Pairs[i].Condition), Node);
newCond := EnsureNode(Accept(Node.Pairs[i].Condition), Node); newBranch := EnsureNode(Accept(E.Pairs[i].Branch), Node);
newBranch := EnsureNode(Accept(Node.Pairs[i].Branch), Node);
newPairs[i] := TCondPair.Create(newCond, newBranch); newPairs[i] := TCondPair.Create(newCond, newBranch);
if (newCond <> E.Pairs[i].Condition) or (newBranch <> E.Pairs[i].Branch) then
if (newCond <> Node.Pairs[i].Condition) or (newBranch <> Node.Pairs[i].Branch) then
hasChanged := True; hasChanged := True;
end; end;
// Else branch is effectively mandatory in the structure, even if it's Nop/Void newElse := EnsureNode(Accept(E.ElseBranch), Node);
newElse := EnsureNode(Accept(Node.ElseBranch), Node); if newElse <> E.ElseBranch then
if newElse <> Node.ElseBranch then
hasChanged := True; hasChanged := True;
if not hasChanged then if not hasChanged then
Exit(Node); Exit(Node);
Result := TAst.CondExpr(Node.Identity, newPairs, newElse, E.StaticType);
Result := TAst.CondExpr(Node.Identity, newPairs, newElse, Node.StaticType);
end; end;
function TAstNodeRemover.VisitAssignment(const Node: IAssignmentNode): IAstNode; function TAstNodeRemover.VisitAssignment(const Node: IAstNode): IAstNode;
var var
A: IAssignmentNode;
newTarget, newValue: IAstNode; newTarget, newValue: IAstNode;
begin begin
newTarget := EnsureNode(Accept(Node.Target), Node); A := Node.AsAssignment;
newValue := EnsureNode(Accept(Node.Value), Node); newTarget := EnsureNode(Accept(A.Target), Node);
newValue := EnsureNode(Accept(A.Value), Node);
if (newTarget = Node.Target) and (newValue = Node.Value) then if (newTarget = A.Target) and (newValue = A.Value) then
Exit(Node); Exit(Node);
Result := TAst.Assign(Node.Identity, newTarget, newValue, A.StaticType);
Result := TAst.Assign(Node.Identity, newTarget, newValue, Node.StaticType);
end; end;
function TAstNodeRemover.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode; function TAstNodeRemover.VisitVariableDeclaration(const Node: IAstNode): IAstNode;
var var
V: IVariableDeclarationNode;
newTarget, newInit: IAstNode; newTarget, newInit: IAstNode;
begin begin
newTarget := EnsureNode(Accept(Node.Target), Node); V := Node.AsVariableDeclaration;
newInit := Accept(Node.Initializer); // Initializer is optional newTarget := EnsureNode(Accept(V.Target), Node);
newInit := Accept(V.Initializer);
if (newTarget = Node.Target) and (newInit = Node.Initializer) then if (newTarget = V.Target) and (newInit = V.Initializer) then
Exit(Node); Exit(Node);
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, V.StaticType, V.IsBoxed);
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, Node.StaticType, Node.IsBoxed);
end; end;
function TAstNodeRemover.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode; function TAstNodeRemover.VisitLambdaExpression(const Node: IAstNode): IAstNode;
var var
newParams: IAstNode; L: ILambdaExpressionNode;
newBody: IAstNode; newParams, newBody: IAstNode;
begin begin
newParams := Accept(Node.Parameters); L := Node.AsLambdaExpression;
// If the parameter list itself was the target, we replace it with an empty list newParams := Accept(L.Parameters);
if newParams = nil then if newParams = nil then
newParams := TParameterList.Create([], Node.Parameters.Identity); newParams := TParameterList.Create([], L.Parameters.Identity);
newBody := EnsureNode(Accept(L.Body), Node);
newBody := EnsureNode(Accept(Node.Body), Node); if (newParams = L.Parameters) and (newBody = L.Body) then
if (newParams = Node.Parameters) and (newBody = Node.Body) then
Exit(Node); Exit(Node);
Result := Result :=
TAst.LambdaExpr( TAst.LambdaExpr(
Node.Identity, Node.Identity,
newParams.AsParameterList, newParams.AsParameterList,
newBody, newBody,
Node.Layout, L.Layout,
Node.Descriptor, L.Descriptor,
Node.Upvalues, L.Upvalues,
Node.HasNestedLambdas, L.HasNestedLambdas,
Node.IsPure, L.IsPure,
Node.StaticType L.StaticType
); );
end; end;
function TAstNodeRemover.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TAstNodeRemover.VisitFunctionCall(const Node: IAstNode): IAstNode;
var var
newCallee: IAstNode; C: IFunctionCallNode;
newArgs: IAstNode; newCallee, newArgs: IAstNode;
begin begin
newCallee := EnsureNode(Accept(Node.Callee), Node); C := Node.AsFunctionCall;
newCallee := EnsureNode(Accept(C.Callee), Node);
newArgs := Accept(Node.Arguments); newArgs := Accept(C.Arguments);
if newArgs = nil then if newArgs = nil then
newArgs := TArgumentList.Create([], Node.Arguments.Identity); newArgs := TArgumentList.Create([], C.Arguments.Identity);
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then if (newCallee = C.Callee) and (newArgs = C.Arguments) then
Exit(Node); Exit(Node);
Result := Result :=
TAst.FunctionCall( TAst.FunctionCall(Node.Identity, newCallee, newArgs.AsArgumentList, C.StaticType, C.IsTailCall, C.StaticTarget, C.IsTargetPure);
Node.Identity,
newCallee,
newArgs.AsArgumentList,
Node.StaticType,
Node.IsTailCall,
Node.StaticTarget,
Node.IsTargetPure
);
end; end;
function TAstNodeRemover.VisitIndexer(const Node: IIndexerNode): IAstNode; function TAstNodeRemover.VisitIndexer(const Node: IAstNode): IAstNode;
var var
I: IIndexerNode;
newBase, newIndex: IAstNode; newBase, newIndex: IAstNode;
begin begin
newBase := EnsureNode(Accept(Node.Base), Node); I := Node.AsIndexer;
newIndex := EnsureNode(Accept(Node.Index), Node); newBase := EnsureNode(Accept(I.Base), Node);
newIndex := EnsureNode(Accept(I.Index), Node);
if (newBase = Node.Base) and (newIndex = Node.Index) then if (newBase = I.Base) and (newIndex = I.Index) then
Exit(Node); Exit(Node);
Result := TAst.Indexer(Node.Identity, newBase, newIndex, I.StaticType);
Result := TAst.Indexer(Node.Identity, newBase, newIndex, Node.StaticType);
end; end;
function TAstNodeRemover.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode; function TAstNodeRemover.VisitMemberAccess(const Node: IAstNode): IAstNode;
var var
newBase: IAstNode; M: IMemberAccessNode;
newMember: IAstNode; newBase, newMember: IAstNode;
begin begin
newBase := EnsureNode(Accept(Node.Base), Node); M := Node.AsMemberAccess;
newBase := EnsureNode(Accept(M.Base), Node);
newMember := Accept(Node.Member); newMember := Accept(M.Member);
if newMember = nil then if newMember = nil then
begin Exit(TAst.Nop(Node.Identity)); // Member missing -> invalid access
// If member name is deleted, the access is invalid. Replace whole node with Nop.
Exit(TAst.Nop(Node.Identity));
end;
if (newBase = Node.Base) and (newMember = Node.Member) then if (newBase = M.Base) and (newMember = M.Member) then
Exit(Node); Exit(Node);
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, M.StaticType);
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, Node.StaticType);
end; end;
function TAstNodeRemover.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; function TAstNodeRemover.VisitAddSeriesItem(const Node: IAstNode): IAstNode;
var var
newSeries: IAstNode; A: IAddSeriesItemNode;
newValue: IAstNode; newSeries, newValue, newLookback: IAstNode;
newLookback: IAstNode;
begin begin
newSeries := Accept(Node.Series); A := Node.AsAddSeriesItem;
newSeries := Accept(A.Series);
if newSeries = nil then if newSeries = nil then
Exit(TAst.Nop(Node.Identity)); // Cannot exist without series identifier Exit(TAst.Nop(Node.Identity));
newValue := EnsureNode(Accept(A.Value), Node);
newLookback := Accept(A.Lookback);
newValue := EnsureNode(Accept(Node.Value), Node); if (newSeries = A.Series) and (newValue = A.Value) and (newLookback = A.Lookback) then
newLookback := Accept(Node.Lookback); // Optional
if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then
Exit(Node); Exit(Node);
Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, A.StaticType);
Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, Node.StaticType);
end; end;
end. end.
+537
View File
@@ -0,0 +1,537 @@
unit Myc.Ast.Script.Print;
interface
uses
System.SysUtils,
System.Classes,
Myc.Data.Value,
Myc.Data.Scalar,
Myc.Ast.Nodes,
Myc.Ast.Visitor,
Myc.Ast;
type
TPrettyPrintVisitor = class(TAstVisitor)
strict private
FBuilder: TStringBuilder;
FIndentLevel: Integer;
procedure Indent;
procedure Unindent;
procedure Append(const S: string);
procedure NewLine;
// Handlers now strictly accept IAstNode
function VisitConstant(const N: IAstNode): TVoid;
function VisitIdentifier(const N: IAstNode): TVoid;
function VisitKeyword(const N: IAstNode): TVoid;
function VisitParameterList(const N: IAstNode): TVoid;
function VisitArgumentList(const N: IAstNode): TVoid;
function VisitExpressionList(const N: IAstNode): TVoid;
function VisitRecordFieldList(const N: IAstNode): TVoid;
function VisitRecordField(const N: IAstNode): TVoid;
function VisitIfExpression(const N: IAstNode): TVoid;
function VisitCondExpression(const N: IAstNode): TVoid;
function VisitLambdaExpression(const N: IAstNode): TVoid;
function VisitMacroDefinition(const N: IAstNode): TVoid;
function VisitQuasiquote(const N: IAstNode): TVoid;
function VisitUnquote(const N: IAstNode): TVoid;
function VisitUnquoteSplicing(const N: IAstNode): TVoid;
function VisitFunctionCall(const N: IAstNode): TVoid;
function VisitMacroExpansionNode(const N: IAstNode): TVoid;
function VisitBlockExpression(const N: IAstNode): TVoid;
function VisitVariableDeclaration(const N: IAstNode): TVoid;
function VisitAssignment(const N: IAstNode): TVoid;
function VisitIndexer(const N: IAstNode): TVoid;
function VisitMemberAccess(const N: IAstNode): TVoid;
function VisitRecordLiteral(const N: IAstNode): TVoid;
function VisitCreateSeries(const N: IAstNode): TVoid;
function VisitAddSeriesItem(const N: IAstNode): TVoid;
function VisitSeriesLength(const N: IAstNode): TVoid;
function VisitRecurNode(const N: IAstNode): TVoid;
function VisitNop(const N: IAstNode): TVoid;
function VisitPipeInput(const N: IAstNode): TVoid;
function VisitPipeSelectorList(const N: IAstNode): TVoid;
function VisitPipeInputList(const N: IAstNode): TVoid;
function VisitPipe(const N: IAstNode): TVoid;
protected
procedure SetupHandlers; override;
public
constructor Create;
destructor Destroy; override;
function GetResult: string;
procedure Execute(const RootNode: IAstNode);
end;
implementation
{ TPrettyPrintVisitor }
constructor TPrettyPrintVisitor.Create;
begin
inherited Create;
FBuilder := TStringBuilder.Create;
FIndentLevel := 0;
end;
destructor TPrettyPrintVisitor.Destroy;
begin
FBuilder.Free;
inherited;
end;
procedure TPrettyPrintVisitor.SetupHandlers;
begin
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
Register(akParameterList, VisitParameterList);
Register(akArgumentList, VisitArgumentList);
Register(akExpressionList, VisitExpressionList);
Register(akRecordFieldList, VisitRecordFieldList);
Register(akRecordField, VisitRecordField);
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akMacroExpansion, VisitMacroExpansionNode);
Register(akBlockExpression, VisitBlockExpression);
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akMacroDefinition, VisitMacroDefinition);
Register(akQuasiquote, VisitQuasiquote);
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akRecordLiteral, VisitRecordLiteral);
Register(akCreateSeries, VisitCreateSeries);
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akSeriesLength, VisitSeriesLength);
Register(akRecur, VisitRecurNode);
Register(akNop, VisitNop);
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
function TPrettyPrintVisitor.GetResult: string;
begin
Result := FBuilder.ToString;
end;
procedure TPrettyPrintVisitor.Execute(const RootNode: IAstNode);
begin
if Assigned(RootNode) then
Visit(RootNode);
end;
procedure TPrettyPrintVisitor.Indent;
begin
Inc(FIndentLevel, 2);
end;
procedure TPrettyPrintVisitor.Unindent;
begin
Dec(FIndentLevel, 2);
end;
procedure TPrettyPrintVisitor.Append(const S: string);
begin
FBuilder.Append(S);
end;
procedure TPrettyPrintVisitor.NewLine;
begin
FBuilder.AppendLine;
FBuilder.Append(''.PadLeft(FIndentLevel));
end;
// --- Implementations (Clean & Typed) ---
function TPrettyPrintVisitor.VisitPipeInput(const N: IAstNode): TVoid;
var
P: IPipeInputNode;
begin
P := N.AsPipeInput;
Visit(P.StreamSource);
Append(' ');
Visit(P.Selectors);
end;
function TPrettyPrintVisitor.VisitPipeSelectorList(const N: IAstNode): TVoid;
var
L: IPipeSelectorList;
i: Integer;
begin
L := N.AsPipeSelectorList;
Append('[');
for i := 0 to L.Count - 1 do
begin
if i > 0 then
Append(' ');
Visit(L[i]);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitPipeInputList(const N: IAstNode): TVoid;
var
L: IPipeInputList;
i: Integer;
begin
L := N.AsPipeInputList;
Append('[');
for i := 0 to L.Count - 1 do
begin
if i > 0 then
Append(' ');
Visit(L[i]);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitPipe(const N: IAstNode): TVoid;
var
P: IPipeNode;
begin
P := N.AsPipe;
Append('(pipe ');
Visit(P.Inputs);
Indent;
NewLine;
Visit(P.Transformation);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitConstant(const N: IAstNode): TVoid;
var
C: IConstantNode;
begin
C := N.AsConstant;
if C.Value.Kind = vkText then
Append('"' + C.Value.AsText + '"')
else
Append(C.Value.ToString);
end;
function TPrettyPrintVisitor.VisitIdentifier(const N: IAstNode): TVoid;
begin
Append(N.AsIdentifier.Name);
end;
function TPrettyPrintVisitor.VisitKeyword(const N: IAstNode): TVoid;
begin
Append(':' + N.AsKeyword.Value.Name);
end;
function TPrettyPrintVisitor.VisitNop(const N: IAstNode): TVoid;
begin
Append('...');
end;
function TPrettyPrintVisitor.VisitParameterList(const N: IAstNode): TVoid;
var
L: IParameterList;
i: Integer;
begin
L := N.AsParameterList;
Append('[');
for i := 0 to L.Count - 1 do
begin
if i > 0 then
Append(' ');
Visit(L[i]);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitArgumentList(const N: IAstNode): TVoid;
var
L: IArgumentList;
i: Integer;
begin
L := N.AsArgumentList;
for i := 0 to L.Count - 1 do
begin
Append(' ');
Visit(L[i]);
end;
end;
function TPrettyPrintVisitor.VisitExpressionList(const N: IAstNode): TVoid;
var
L: IExpressionList;
item: IAstNode;
begin
L := N.AsExpressionList;
Indent;
for item in L do
begin
NewLine;
Visit(item);
end;
Unindent;
NewLine;
end;
function TPrettyPrintVisitor.VisitRecordFieldList(const N: IAstNode): TVoid;
var
L: IRecordFieldList;
item: IRecordFieldNode;
begin
L := N.AsRecordFieldList;
Indent;
for item in L do
begin
NewLine;
Visit(item);
end;
Unindent;
NewLine;
end;
function TPrettyPrintVisitor.VisitRecordField(const N: IAstNode): TVoid;
var
F: IRecordFieldNode;
begin
F := N.AsRecordField;
Visit(F.Key);
Append(' ');
Visit(F.Value);
end;
function TPrettyPrintVisitor.VisitIfExpression(const N: IAstNode): TVoid;
var
E: IIfExpressionNode;
begin
E := N.AsIfExpression;
Append('(if ');
Visit(E.Condition);
Indent;
NewLine;
Visit(E.ThenBranch);
if Assigned(E.ElseBranch) then
begin
NewLine;
Visit(E.ElseBranch);
end;
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitCondExpression(const N: IAstNode): TVoid;
var
E: ICondExpressionNode;
begin
E := N.AsCondExpression;
Append('(?');
Indent;
for var pair in E.Pairs do
begin
NewLine;
Visit(pair.Condition);
Append(' ');
Visit(pair.Branch);
end;
NewLine;
Visit(E.ElseBranch);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitLambdaExpression(const N: IAstNode): TVoid;
var
E: ILambdaExpressionNode;
begin
E := N.AsLambdaExpression;
Append('(fn ');
Visit(E.Parameters);
Indent;
NewLine;
Visit(E.Body);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitMacroDefinition(const N: IAstNode): TVoid;
var
M: IMacroDefinitionNode;
begin
M := N.AsMacroDefinition;
Append('(defmacro ');
Visit(M.Name);
Append(' ');
Visit(M.Parameters);
Indent;
NewLine;
Visit(M.Body);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitQuasiquote(const N: IAstNode): TVoid;
begin
Append('`');
Visit(N.AsQuasiquote.Expression);
end;
function TPrettyPrintVisitor.VisitUnquote(const N: IAstNode): TVoid;
begin
Append('~');
Visit(N.AsUnquote.Expression);
end;
function TPrettyPrintVisitor.VisitUnquoteSplicing(const N: IAstNode): TVoid;
begin
Append('~@');
Visit(N.AsUnquoteSplicing.Expression);
end;
function TPrettyPrintVisitor.VisitFunctionCall(const N: IAstNode): TVoid;
var
C: IFunctionCallNode;
begin
C := N.AsFunctionCall;
if (C.Callee.Kind = akIdentifier) and (C.Callee.AsIdentifier.Name = 'quote') and (C.Arguments.Count = 1) then
begin
Append('''');
Visit(C.Arguments[0]);
exit;
end;
Append('(');
Visit(C.Callee);
Visit(C.Arguments);
Append(')');
end;
function TPrettyPrintVisitor.VisitMacroExpansionNode(const N: IAstNode): TVoid;
begin
Visit(N.AsMacroExpansion.CallNode);
end;
function TPrettyPrintVisitor.VisitRecurNode(const N: IAstNode): TVoid;
begin
Append('(recur');
Visit(N.AsRecur.Arguments);
Append(')');
end;
function TPrettyPrintVisitor.VisitBlockExpression(const N: IAstNode): TVoid;
begin
Append('(do');
Visit(N.AsBlockExpression.Expressions);
Append(')');
end;
function TPrettyPrintVisitor.VisitVariableDeclaration(const N: IAstNode): TVoid;
var
V: IVariableDeclarationNode;
begin
V := N.AsVariableDeclaration;
Append('(def ');
Visit(V.Target);
if Assigned(V.Initializer) then
begin
Append(' ');
Visit(V.Initializer);
end;
Append(')');
end;
function TPrettyPrintVisitor.VisitAssignment(const N: IAstNode): TVoid;
var
A: IAssignmentNode;
begin
A := N.AsAssignment;
Append('(assign ');
Visit(A.Target);
Append(' ');
Visit(A.Value);
Append(')');
end;
function TPrettyPrintVisitor.VisitIndexer(const N: IAstNode): TVoid;
var
I: IIndexerNode;
begin
I := N.AsIndexer;
Append('(get ');
Visit(I.Base);
Append(' ');
Visit(I.Index);
Append(')');
end;
function TPrettyPrintVisitor.VisitMemberAccess(const N: IAstNode): TVoid;
var
M: IMemberAccessNode;
begin
M := N.AsMemberAccess;
Append('(.');
Visit(M.Member);
Append(' ');
Visit(M.Base);
Append(')');
end;
function TPrettyPrintVisitor.VisitRecordLiteral(const N: IAstNode): TVoid;
var
R: IRecordLiteralNode;
begin
R := N.AsRecordLiteral;
if R.Fields.Count = 0 then
begin
Append('{}');
exit;
end;
Append('{');
Visit(R.Fields);
Append('}');
end;
function TPrettyPrintVisitor.VisitCreateSeries(const N: IAstNode): TVoid;
begin
Append(Format('(new-series "%s")', [N.AsCreateSeries.Definition]));
end;
function TPrettyPrintVisitor.VisitAddSeriesItem(const N: IAstNode): TVoid;
var
A: IAddSeriesItemNode;
begin
A := N.AsAddSeriesItem;
Append('(add-item ');
Visit(A.Series);
Append(' ');
Visit(A.Value);
if Assigned(A.Lookback) then
begin
Append(' ');
Visit(A.Lookback);
end;
Append(')');
end;
function TPrettyPrintVisitor.VisitSeriesLength(const N: IAstNode): TVoid;
begin
Append('(count ');
Visit(N.AsSeriesLength.Series);
Append(')');
end;
end.
+2 -408
View File
@@ -6,7 +6,8 @@ uses
System.SysUtils, System.SysUtils,
Myc.Data.Value, Myc.Data.Value,
Myc.Ast.Nodes, Myc.Ast.Nodes,
Myc.Ast.Visitor; Myc.Ast.Visitor,
Myc.Ast.Script.Print;
type type
EParserException = class(EAstException) EParserException = class(EAstException)
@@ -116,57 +117,6 @@ type
function Parse: IAstNode; function Parse: IAstNode;
end; end;
TPrettyPrintVisitor = class(TAstVisitor)
private
FBuilder: TStringBuilder;
FIndentLevel: Integer;
procedure Indent;
procedure Unindent;
procedure Append(const S: string);
procedure NewLine;
public
constructor Create;
destructor Destroy; override;
function GetResult: string;
function Execute(const RootNode: IAstNode): TDataValue;
function VisitConstant(const Node: IConstantNode): TVoid; override;
function VisitIdentifier(const Node: IIdentifierNode): TVoid; override;
function VisitKeyword(const Node: IKeywordNode): TVoid; override;
function VisitParameterList(const Node: IParameterList): TVoid; override;
function VisitArgumentList(const Node: IArgumentList): TVoid; override;
function VisitExpressionList(const Node: IExpressionList): TVoid; override;
function VisitRecordFieldList(const Node: IRecordFieldList): TVoid; override;
function VisitRecordField(const Node: IRecordFieldNode): TVoid; override;
function VisitIfExpression(const Node: IIfExpressionNode): TVoid; override;
function VisitCondExpression(const Node: ICondExpressionNode): TVoid; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid; override;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid; override;
function VisitQuasiquote(const Node: IQuasiquoteNode): TVoid; override;
function VisitUnquote(const Node: IUnquoteNode): TVoid; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid; override;
function VisitFunctionCall(const Node: IFunctionCallNode): TVoid; override;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): TVoid; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid; override;
function VisitAssignment(const Node: IAssignmentNode): TVoid; override;
function VisitIndexer(const Node: IIndexerNode): TVoid; override;
function VisitMemberAccess(const Node: IMemberAccessNode): TVoid; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid; override;
function VisitCreateSeries(const Node: ICreateSeriesNode): TVoid; override;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid; override;
function VisitSeriesLength(const Node: ISeriesLengthNode): TVoid; override;
function VisitRecurNode(const Node: IRecurNode): TVoid; override;
function VisitNop(const Node: INopNode): TVoid; override;
function VisitPipeInput(const Node: IPipeInputNode): TVoid; override;
function VisitPipeSelectorList(const Node: IPipeSelectorList): TVoid; override;
function VisitPipeInputList(const Node: IPipeInputList): TVoid; override;
function VisitPipe(const Node: IPipeNode): TVoid; override;
end;
{ TTokenKindHelper } { TTokenKindHelper }
function TTokenKindHelper.ToString: String; function TTokenKindHelper.ToString: String;
begin begin
@@ -812,362 +762,6 @@ begin
Result := expr.Node; Result := expr.Node;
end; end;
{ TPrettyPrintVisitor }
constructor TPrettyPrintVisitor.Create;
begin
inherited Create;
FBuilder := TStringBuilder.Create;
FIndentLevel := 0;
end;
destructor TPrettyPrintVisitor.Destroy;
begin
FBuilder.Free;
inherited;
end;
function TPrettyPrintVisitor.GetResult: string;
begin
Result := FBuilder.ToString;
end;
procedure TPrettyPrintVisitor.Indent;
begin
Inc(FIndentLevel, 2);
end;
procedure TPrettyPrintVisitor.Unindent;
begin
Dec(FIndentLevel, 2);
end;
procedure TPrettyPrintVisitor.Append(const S: string);
begin
FBuilder.Append(S);
end;
procedure TPrettyPrintVisitor.NewLine;
begin
FBuilder.AppendLine;
FBuilder.Append(''.PadLeft(FIndentLevel));
end;
function TPrettyPrintVisitor.Execute(const RootNode: IAstNode): TDataValue;
begin
if Assigned(RootNode) then
RootNode.Accept(Self);
Result := TDataValue.Void;
end;
function TPrettyPrintVisitor.VisitPipeInput(const Node: IPipeInputNode): TVoid;
begin
Node.StreamSource.Accept(Self);
Append(' ');
// Printer Logic for Vector syntax
Node.Selectors.Accept(Self); // Selectors is IPipeSelectorList which is visited as list
end;
function TPrettyPrintVisitor.VisitPipeSelectorList(const Node: IPipeSelectorList): TVoid;
begin
Append('[');
for var i := 0 to Node.Count - 1 do
begin
if i > 0 then
Append(' ');
Node[i].Accept(Self);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitPipeInputList(const Node: IPipeInputList): TVoid;
begin
Append('[');
for var i := 0 to Node.Count - 1 do
begin
if i > 0 then
Append(' ');
Node[i].Accept(Self);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitPipe(const Node: IPipeNode): TVoid;
begin
Append('(pipe ');
Node.Inputs.Accept(Self);
Indent;
NewLine;
Node.Transformation.Accept(Self);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitConstant(const Node: IConstantNode): TVoid;
begin
if Node.Value.Kind = vkText then
Append('"' + Node.Value.AsText + '"')
else
Append(Node.Value.ToString);
end;
function TPrettyPrintVisitor.VisitIdentifier(const Node: IIdentifierNode): TVoid;
begin
Append(Node.Name);
end;
function TPrettyPrintVisitor.VisitKeyword(const Node: IKeywordNode): TVoid;
begin
Append(':' + Node.Value.Name);
end;
function TPrettyPrintVisitor.VisitNop(const Node: INopNode): TVoid;
begin
Append('...');
end;
function TPrettyPrintVisitor.VisitParameterList(const Node: IParameterList): TVoid;
begin
Append('[');
for var i := 0 to Node.Count - 1 do
begin
if i > 0 then
Append(' ');
Node[i].Accept(Self);
end;
Append(']');
end;
function TPrettyPrintVisitor.VisitArgumentList(const Node: IArgumentList): TVoid;
begin
for var i := 0 to Node.Count - 1 do
begin
Append(' ');
Node[i].Accept(Self);
end;
end;
function TPrettyPrintVisitor.VisitExpressionList(const Node: IExpressionList): TVoid;
begin
Indent;
for var item in Node do
begin
NewLine;
item.Accept(Self);
end;
Unindent;
NewLine;
end;
function TPrettyPrintVisitor.VisitRecordFieldList(const Node: IRecordFieldList): TVoid;
begin
Indent;
for var item in Node do
begin
NewLine;
item.Accept(Self);
end;
Unindent;
NewLine;
end;
function TPrettyPrintVisitor.VisitRecordField(const Node: IRecordFieldNode): TVoid;
begin
Node.Key.Accept(Self);
Append(' ');
Node.Value.Accept(Self);
end;
function TPrettyPrintVisitor.VisitIfExpression(const Node: IIfExpressionNode): TVoid;
begin
Append('(if ');
Node.Condition.Accept(Self);
Indent;
NewLine;
Node.ThenBranch.Accept(Self);
if Assigned(Node.ElseBranch) then
begin
NewLine;
Node.ElseBranch.Accept(Self);
end;
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitCondExpression(const Node: ICondExpressionNode): TVoid;
begin
Append('(?');
Indent;
for var pair in Node.Pairs do
begin
NewLine;
pair.Condition.Accept(Self);
Append(' ');
pair.Branch.Accept(Self);
end;
NewLine;
Node.ElseBranch.Accept(Self);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid;
begin
Append('(fn ');
Node.Parameters.Accept(Self);
Indent;
NewLine;
Node.Body.Accept(Self);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid;
begin
Append('(defmacro ');
Node.Name.Accept(Self);
Append(' ');
Node.Parameters.Accept(Self);
Indent;
NewLine;
Node.Body.Accept(Self);
Unindent;
NewLine;
Append(')');
end;
function TPrettyPrintVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): TVoid;
begin
Append('`');
Node.Expression.Accept(Self);
end;
function TPrettyPrintVisitor.VisitUnquote(const Node: IUnquoteNode): TVoid;
begin
Append('~');
Node.Expression.Accept(Self);
end;
function TPrettyPrintVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid;
begin
Append('~@');
Node.Expression.Accept(Self);
end;
function TPrettyPrintVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TVoid;
begin
if (Node.Callee.Kind = akIdentifier) and (Node.Callee.AsIdentifier.Name = 'quote') and (Node.Arguments.Count = 1) then
begin
Append('''');
Node.Arguments[0].Accept(Self);
exit;
end;
Append('(');
Node.Callee.Accept(Self);
Node.Arguments.Accept(Self);
Append(')');
end;
function TPrettyPrintVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid;
begin
VisitFunctionCall(Node.CallNode);
end;
function TPrettyPrintVisitor.VisitRecurNode(const Node: IRecurNode): TVoid;
begin
Append('(recur');
Node.Arguments.Accept(Self);
Append(')');
end;
function TPrettyPrintVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TVoid;
begin
Append('(do');
Node.Expressions.Accept(Self);
Append(')');
end;
function TPrettyPrintVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid;
begin
Append('(def ');
Node.Target.Accept(Self);
if Assigned(Node.Initializer) then
begin
Append(' ');
Node.Initializer.Accept(Self);
end;
Append(')');
end;
function TPrettyPrintVisitor.VisitAssignment(const Node: IAssignmentNode): TVoid;
begin
Append('(assign ');
Node.Target.Accept(Self);
Append(' ');
Node.Value.Accept(Self);
Append(')');
end;
function TPrettyPrintVisitor.VisitIndexer(const Node: IIndexerNode): TVoid;
begin
Append('(get ');
Node.Base.Accept(Self);
Append(' ');
Node.Index.Accept(Self);
Append(')');
end;
function TPrettyPrintVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TVoid;
begin
Append('(.');
Node.Member.Accept(Self);
Append(' ');
Node.Base.Accept(Self);
Append(')');
end;
function TPrettyPrintVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid;
begin
if Node.Fields.Count = 0 then
begin
Append('{}');
exit;
end;
Append('{');
Node.Fields.Accept(Self);
Append('}');
end;
function TPrettyPrintVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TVoid;
begin
Append(Format('(new-series "%s")', [Node.Definition]));
end;
function TPrettyPrintVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid;
begin
Append('(add-item ');
Node.Series.Accept(Self);
Append(' ');
Node.Value.Accept(Self);
if Assigned(Node.Lookback) then
begin
Append(' ');
Node.Lookback.Accept(Self);
end;
Append(')');
end;
function TPrettyPrintVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TVoid;
begin
Append('(count ');
Node.Series.Accept(Self);
Append(')');
end;
{ TAstScript } { TAstScript }
class function TAstScript.Parse(const ASource: string): IAstNode; class function TAstScript.Parse(const ASource: string): IAstNode;
File diff suppressed because it is too large Load Diff
+186 -120
View File
@@ -15,10 +15,11 @@ uses
type type
// Implementation of the Factory/Visitor for the Virtual Tree // Implementation of the Factory/Visitor for the Virtual Tree
// Uses TAstVisitor<TAstViewNode> which now uses closures for dispatch.
TAstVisualizer = class(TAstVisitor<TAstViewNode>, IAstVisualizer) TAstVisualizer = class(TAstVisitor<TAstViewNode>, IAstVisualizer)
private private
FExprDepth: Integer; FExprDepth: Integer;
FParentNode: TVisualNode; // Changed from TControl FParentNode: TVisualNode;
FWorkspace: TWorkspace; FWorkspace: TWorkspace;
function CallAccept(const Node: IAstNode): TAstViewNode; function CallAccept(const Node: IAstNode): TAstViewNode;
@@ -29,49 +30,58 @@ type
function GetWorkspace: TWorkspace; function GetWorkspace: TWorkspace;
protected protected
// --- Visitor Implementations --- // Setup registry using closures
function VisitConstant(const Node: IConstantNode): TAstViewNode; override; procedure SetupHandlers; override;
function VisitIdentifier(const Node: IIdentifierNode): TAstViewNode; override;
function VisitKeyword(const Node: IKeywordNode): TAstViewNode; override; // --- Visitor Implementations (Strict IAstNode signature) ---
function VisitConstant(const Node: IAstNode): TAstViewNode;
function VisitIdentifier(const Node: IAstNode): TAstViewNode;
function VisitKeyword(const Node: IAstNode): TAstViewNode;
// Lists // Lists
function VisitParameterList(const Node: IParameterList): TAstViewNode; override; function VisitParameterList(const Node: IAstNode): TAstViewNode;
function VisitArgumentList(const Node: IArgumentList): TAstViewNode; override; function VisitArgumentList(const Node: IAstNode): TAstViewNode;
function VisitExpressionList(const Node: IExpressionList): TAstViewNode; override; function VisitExpressionList(const Node: IAstNode): TAstViewNode;
function VisitRecordFieldList(const Node: IRecordFieldList): TAstViewNode; override; function VisitRecordFieldList(const Node: IAstNode): TAstViewNode;
function VisitRecordField(const Node: IRecordFieldNode): TAstViewNode; override; function VisitRecordField(const Node: IAstNode): TAstViewNode;
// Control Flow // Control Flow
function VisitIfExpression(const Node: IIfExpressionNode): TAstViewNode; override; function VisitIfExpression(const Node: IAstNode): TAstViewNode;
function VisitCondExpression(const Node: ICondExpressionNode): TAstViewNode; override; // Replaces Ternary function VisitCondExpression(const Node: IAstNode): TAstViewNode;
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstViewNode; override; function VisitBlockExpression(const Node: IAstNode): TAstViewNode;
function VisitRecurNode(const Node: IRecurNode): TAstViewNode; override; function VisitRecurNode(const Node: IAstNode): TAstViewNode;
function VisitNop(const Node: INopNode): TAstViewNode; override; function VisitNop(const Node: IAstNode): TAstViewNode;
// Functions // Functions
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstViewNode; override; function VisitLambdaExpression(const Node: IAstNode): TAstViewNode;
function VisitFunctionCall(const Node: IFunctionCallNode): TAstViewNode; override; function VisitFunctionCall(const Node: IAstNode): TAstViewNode;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TAstViewNode; override; function VisitMacroExpansionNode(const Node: IAstNode): TAstViewNode;
// Declarations // Declarations
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstViewNode; override; function VisitVariableDeclaration(const Node: IAstNode): TAstViewNode;
function VisitAssignment(const Node: IAssignmentNode): TAstViewNode; override; function VisitAssignment(const Node: IAstNode): TAstViewNode;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TAstViewNode; override; function VisitMacroDefinition(const Node: IAstNode): TAstViewNode;
// Metaprogramming // Metaprogramming
function VisitQuasiquote(const Node: IQuasiquoteNode): TAstViewNode; override; function VisitQuasiquote(const Node: IAstNode): TAstViewNode;
function VisitUnquote(const Node: IUnquoteNode): TAstViewNode; override; function VisitUnquote(const Node: IAstNode): TAstViewNode;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TAstViewNode; override; function VisitUnquoteSplicing(const Node: IAstNode): TAstViewNode;
// Data Access // Data Access
function VisitIndexer(const Node: IIndexerNode): TAstViewNode; override; function VisitIndexer(const Node: IAstNode): TAstViewNode;
function VisitMemberAccess(const Node: IMemberAccessNode): TAstViewNode; override; function VisitMemberAccess(const Node: IAstNode): TAstViewNode;
function VisitRecordLiteral(const Node: IRecordLiteralNode): TAstViewNode; override; function VisitRecordLiteral(const Node: IAstNode): TAstViewNode;
// Series // Series
function VisitCreateSeries(const Node: ICreateSeriesNode): TAstViewNode; override; function VisitCreateSeries(const Node: IAstNode): TAstViewNode;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstViewNode; override; function VisitAddSeriesItem(const Node: IAstNode): TAstViewNode;
function VisitSeriesLength(const Node: ISeriesLengthNode): TAstViewNode; override; function VisitSeriesLength(const Node: IAstNode): TAstViewNode;
// Pipes
function VisitPipeInput(const Node: IAstNode): TAstViewNode;
function VisitPipeSelectorList(const Node: IAstNode): TAstViewNode;
function VisitPipeInputList(const Node: IAstNode): TAstViewNode;
function VisitPipe(const Node: IAstNode): TAstViewNode;
public public
constructor Create(AWorkspace: TWorkspace; AParentNode: TVisualNode; AExprDepth: Integer); constructor Create(AWorkspace: TWorkspace; AParentNode: TVisualNode; AExprDepth: Integer);
@@ -87,42 +97,23 @@ type
implementation implementation
uses uses
System.StrUtils,
Myc.Fmx.AstEditor.Handlers, Myc.Fmx.AstEditor.Handlers,
Myc.Fmx.AstEditor.Handlers.Data, Myc.Fmx.AstEditor.Handlers.Data,
Myc.Fmx.AstEditor.Handlers.Control, Myc.Fmx.AstEditor.Handlers.Control,
Myc.Fmx.AstEditor.Handlers.Primitives, Myc.Fmx.AstEditor.Handlers.Primitives,
Myc.Fmx.AstEditor.Handlers.Lists; Myc.Fmx.AstEditor.Handlers.Lists;
// Helper to identify standard operators
function IsStandardOperator(const Name: string): Boolean; function IsStandardOperator(const Name: string): Boolean;
begin begin
// You can extend this list based on your RTL Result := MatchStr(Name, ['+', '-', '*', '/', 'div', 'mod', '=', '<>', '<', '>', '<=', '>=', 'and', 'or', 'xor', 'not', 'shl', 'shr']);
Result :=
(Name = '+')
or (Name = '-')
or (Name = '*')
or (Name = '/')
or (Name = 'div')
or (Name = 'mod')
or (Name = '=')
or (Name = '<>')
or (Name = '<')
or (Name = '>')
or (Name = '<=')
or (Name = '>=')
or (Name = 'and')
or (Name = 'or')
or (Name = 'xor')
or (Name = 'not')
or (Name = 'shl')
or (Name = 'shr');
end; end;
{ TAstVisualizer } { TAstVisualizer }
constructor TAstVisualizer.Create(AWorkspace: TWorkspace; AParentNode: TVisualNode; AExprDepth: Integer); constructor TAstVisualizer.Create(AWorkspace: TWorkspace; AParentNode: TVisualNode; AExprDepth: Integer);
begin begin
inherited Create; inherited Create; // Calls SetupHandlers
FWorkspace := AWorkspace; FWorkspace := AWorkspace;
FParentNode := AParentNode; FParentNode := AParentNode;
FExprDepth := AExprDepth; FExprDepth := AExprDepth;
@@ -133,6 +124,59 @@ begin
inherited; inherited;
end; end;
procedure TAstVisualizer.SetupHandlers;
begin
// Core Primitives
Register(akConstant, VisitConstant);
Register(akIdentifier, VisitIdentifier);
Register(akKeyword, VisitKeyword);
Register(akNop, VisitNop);
// Lists
Register(akParameterList, VisitParameterList);
Register(akArgumentList, VisitArgumentList);
Register(akExpressionList, VisitExpressionList);
Register(akRecordFieldList, VisitRecordFieldList);
Register(akRecordField, VisitRecordField);
// Control Flow
Register(akIfExpression, VisitIfExpression);
Register(akCondExpression, VisitCondExpression);
Register(akBlockExpression, VisitBlockExpression);
Register(akRecur, VisitRecurNode);
// Functions
Register(akLambdaExpression, VisitLambdaExpression);
Register(akFunctionCall, VisitFunctionCall);
Register(akMacroExpansion, VisitMacroExpansionNode);
// Declarations
Register(akVariableDeclaration, VisitVariableDeclaration);
Register(akAssignment, VisitAssignment);
Register(akMacroDefinition, VisitMacroDefinition);
// Metaprogramming
Register(akQuasiquote, VisitQuasiquote);
Register(akUnquote, VisitUnquote);
Register(akUnquoteSplicing, VisitUnquoteSplicing);
// Data Access
Register(akIndexer, VisitIndexer);
Register(akMemberAccess, VisitMemberAccess);
Register(akRecordLiteral, VisitRecordLiteral);
// Series
Register(akCreateSeries, VisitCreateSeries);
Register(akAddSeriesItem, VisitAddSeriesItem);
Register(akSeriesLength, VisitSeriesLength);
// Pipes
Register(akPipeInput, VisitPipeInput);
Register(akPipeSelectorList, VisitPipeSelectorList);
Register(akPipeInputList, VisitPipeInputList);
Register(akPipe, VisitPipe);
end;
function TAstVisualizer.CallAccept(const Node: IAstNode): TAstViewNode; function TAstVisualizer.CallAccept(const Node: IAstNode): TAstViewNode;
var var
dataValue: TDataValue; dataValue: TDataValue;
@@ -140,10 +184,9 @@ begin
if not Assigned(Node) then if not Assigned(Node) then
exit(nil); exit(nil);
// Dynamic dispatch via Visitor pattern
dataValue := Node.Accept(Self); dataValue := Node.Accept(Self);
if dataValue.Kind = TDataValueKind.vkGeneric then if dataValue.Kind = vkGeneric then
Result := dataValue.AsGeneric<TAstViewNode> Result := dataValue.AsGeneric<TAstViewNode>
else else
Result := nil; Result := nil;
@@ -169,180 +212,203 @@ begin
Result := FWorkspace; Result := FWorkspace;
end; end;
// --- Visitor Implementation Helpers --- // --- Visitor Implementations (Cast-heavy for efficiency) ---
function TAstVisualizer.VisitConstant(const Node: IConstantNode): TAstViewNode; function TAstVisualizer.VisitConstant(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TConstantNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TConstantNodeHandler.Create(Node.AsConstant));
end; end;
function TAstVisualizer.VisitIdentifier(const Node: IIdentifierNode): TAstViewNode; function TAstVisualizer.VisitIdentifier(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TIdentifierNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TIdentifierNodeHandler.Create(Node.AsIdentifier));
end; end;
function TAstVisualizer.VisitKeyword(const Node: IKeywordNode): TAstViewNode; function TAstVisualizer.VisitKeyword(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TKeywordNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TKeywordNodeHandler.Create(Node.AsKeyword));
end;
function TAstVisualizer.VisitNop(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end; end;
// Lists // Lists
function TAstVisualizer.VisitParameterList(const Node: IParameterList): TAstViewNode; function TAstVisualizer.VisitParameterList(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TParameterListHandler.Create(Node)); Result := TAstViewNode.Create(Self, TParameterListHandler.Create(Node.AsParameterList));
end; end;
function TAstVisualizer.VisitArgumentList(const Node: IArgumentList): TAstViewNode; function TAstVisualizer.VisitArgumentList(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TArgumentListHandler.Create(Node)); Result := TAstViewNode.Create(Self, TArgumentListHandler.Create(Node.AsArgumentList));
end; end;
function TAstVisualizer.VisitExpressionList(const Node: IExpressionList): TAstViewNode; function TAstVisualizer.VisitExpressionList(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TExpressionListHandler.Create(Node)); Result := TAstViewNode.Create(Self, TExpressionListHandler.Create(Node.AsExpressionList));
end; end;
function TAstVisualizer.VisitRecordFieldList(const Node: IRecordFieldList): TAstViewNode; function TAstVisualizer.VisitRecordFieldList(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TRecordFieldListHandler.Create(Node)); Result := TAstViewNode.Create(Self, TRecordFieldListHandler.Create(Node.AsRecordFieldList));
end; end;
function TAstVisualizer.VisitRecordField(const Node: IRecordFieldNode): TAstViewNode; function TAstVisualizer.VisitRecordField(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node)); Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node.AsRecordField));
end; end;
// Control Flow // Control Flow
function TAstVisualizer.VisitBlockExpression(const Node: IBlockExpressionNode): TAstViewNode; function TAstVisualizer.VisitBlockExpression(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TBlockExpressionNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TBlockExpressionNodeHandler.Create(Node.AsBlockExpression));
end; end;
function TAstVisualizer.VisitIfExpression(const Node: IIfExpressionNode): TAstViewNode; function TAstVisualizer.VisitIfExpression(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TIfExpressionNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TIfExpressionNodeHandler.Create(Node.AsIfExpression));
end; end;
function TAstVisualizer.VisitCondExpression(const Node: ICondExpressionNode): TAstViewNode; function TAstVisualizer.VisitCondExpression(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TCondExpressionNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TCondExpressionNodeHandler.Create(Node.AsCondExpression));
end; end;
function TAstVisualizer.VisitRecurNode(const Node: IRecurNode): TAstViewNode; function TAstVisualizer.VisitRecurNode(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TRecurNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TRecurNodeHandler.Create(Node.AsRecur));
end;
function TAstVisualizer.VisitNop(const Node: INopNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node));
end; end;
// Functions // Functions
function TAstVisualizer.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstViewNode; function TAstVisualizer.VisitLambdaExpression(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TLambdaExpressionNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TLambdaExpressionNodeHandler.Create(Node.AsLambdaExpression));
end; end;
function TAstVisualizer.VisitFunctionCall(const Node: IFunctionCallNode): TAstViewNode; function TAstVisualizer.VisitFunctionCall(const Node: IAstNode): TAstViewNode;
var var
C: IFunctionCallNode;
calleeName: string; calleeName: string;
isOp: Boolean; isOp: Boolean;
begin begin
C := Node.AsFunctionCall;
isOp := False; isOp := False;
// Check if callee is a simple identifier or keyword that matches an operator if C.Callee.Kind = akIdentifier then
if Node.Callee.Kind = akIdentifier then
begin begin
calleeName := Node.Callee.AsIdentifier.Name; calleeName := C.Callee.AsIdentifier.Name;
isOp := IsStandardOperator(calleeName); isOp := IsStandardOperator(calleeName);
end end
else if Node.Callee.Kind = akKeyword then else if C.Callee.Kind = akKeyword then
begin begin
calleeName := Node.Callee.AsKeyword.Value.Name; calleeName := C.Callee.AsKeyword.Value.Name;
isOp := IsStandardOperator(calleeName); isOp := IsStandardOperator(calleeName);
end; end;
if isOp then if isOp then
Result := TAstViewNode.Create(Self, TOperatorCallHandler.Create(Node)) Result := TAstViewNode.Create(Self, TOperatorCallHandler.Create(C))
else else
Result := TAstViewNode.Create(Self, TFunctionCallNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TFunctionCallNodeHandler.Create(C));
end; end;
function TAstVisualizer.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TAstViewNode; function TAstVisualizer.VisitMacroExpansionNode(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TMacroExpansionNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TMacroExpansionNodeHandler.Create(Node.AsMacroExpansion));
end; end;
// Declarations // Declarations
function TAstVisualizer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstViewNode; function TAstVisualizer.VisitVariableDeclaration(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TVariableDeclarationNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TVariableDeclarationNodeHandler.Create(Node.AsVariableDeclaration));
end; end;
function TAstVisualizer.VisitAssignment(const Node: IAssignmentNode): TAstViewNode; function TAstVisualizer.VisitAssignment(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TAssignmentNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TAssignmentNodeHandler.Create(Node.AsAssignment));
end; end;
function TAstVisualizer.VisitMacroDefinition(const Node: IMacroDefinitionNode): TAstViewNode; function TAstVisualizer.VisitMacroDefinition(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TMacroDefinitionNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TMacroDefinitionNodeHandler.Create(Node.AsMacroDefinition));
end; end;
// Metaprogramming // Metaprogramming
function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TAstViewNode; function TAstVisualizer.VisitQuasiquote(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TQuasiquoteNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TQuasiquoteNodeHandler.Create(Node.AsQuasiquote));
end; end;
function TAstVisualizer.VisitUnquote(const Node: IUnquoteNode): TAstViewNode; function TAstVisualizer.VisitUnquote(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TUnquoteNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TUnquoteNodeHandler.Create(Node.AsUnquote));
end; end;
function TAstVisualizer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TAstViewNode; function TAstVisualizer.VisitUnquoteSplicing(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TUnquoteSplicingNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TUnquoteSplicingNodeHandler.Create(Node.AsUnquoteSplicing));
end; end;
// Data Access // Data Access
function TAstVisualizer.VisitIndexer(const Node: IIndexerNode): TAstViewNode; function TAstVisualizer.VisitIndexer(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TIndexerNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TIndexerNodeHandler.Create(Node.AsIndexer));
end; end;
function TAstVisualizer.VisitMemberAccess(const Node: IMemberAccessNode): TAstViewNode; function TAstVisualizer.VisitMemberAccess(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TMemberAccessNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TMemberAccessNodeHandler.Create(Node.AsMemberAccess));
end; end;
function TAstVisualizer.VisitRecordLiteral(const Node: IRecordLiteralNode): TAstViewNode; function TAstVisualizer.VisitRecordLiteral(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TRecordLiteralNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TRecordLiteralNodeHandler.Create(Node.AsRecordLiteral));
end; end;
// Series // Series
function TAstVisualizer.VisitCreateSeries(const Node: ICreateSeriesNode): TAstViewNode; function TAstVisualizer.VisitCreateSeries(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TCreateSeriesNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TCreateSeriesNodeHandler.Create(Node.AsCreateSeries));
end; end;
function TAstVisualizer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstViewNode; function TAstVisualizer.VisitAddSeriesItem(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TAddSeriesItemNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TAddSeriesItemNodeHandler.Create(Node.AsAddSeriesItem));
end; end;
function TAstVisualizer.VisitSeriesLength(const Node: ISeriesLengthNode): TAstViewNode; function TAstVisualizer.VisitSeriesLength(const Node: IAstNode): TAstViewNode;
begin begin
Result := TAstViewNode.Create(Self, TSeriesLengthNodeHandler.Create(Node)); Result := TAstViewNode.Create(Self, TSeriesLengthNodeHandler.Create(Node.AsSeriesLength));
end;
// Pipes (Fallback to Nop/Placeholder visualization until specific handlers are implemented)
function TAstVisualizer.VisitPipeInput(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end;
function TAstVisualizer.VisitPipeSelectorList(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end;
function TAstVisualizer.VisitPipeInputList(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end;
function TAstVisualizer.VisitPipe(const Node: IAstNode): TAstViewNode;
begin
Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop));
end; end;
end. end.
+1
View File
@@ -93,6 +93,7 @@ type
implementation implementation
uses uses
System.Generics.Collections,
Myc.Fmx.AstEditor.Node; Myc.Fmx.AstEditor.Node;
{ TWorkspace } { TWorkspace }
+3
View File
@@ -110,6 +110,9 @@ type
implementation implementation
uses
Winapi.Windows;
{ TKeywordRegistry.TKeyword } { TKeywordRegistry.TKeyword }
constructor TKeywordRegistry.TKeyword.Create(const AName: string; AIdx: Integer); constructor TKeywordRegistry.TKeyword.Create(const AName: string; AIdx: Integer);
+3
View File
@@ -103,6 +103,9 @@ type
implementation implementation
uses
Winapi.Windows;
constructor TStreamSignal.Create(AKind: TSignalKind; ACycleID: Int64); constructor TStreamSignal.Create(AKind: TSignalKind; ACycleID: Int64);
begin begin
FKind := AKind; FKind := AKind;
+3
View File
@@ -37,6 +37,9 @@ procedure RegisterBroker(const Scope: IExecutionScope);
implementation implementation
uses
Myc.Data.Scalar;
type type
TMockBroker = class(TInterfacedObject, IMycBroker) TMockBroker = class(TInterfacedObject, IMycBroker)
strict private strict private
+1 -2
View File
@@ -16,6 +16,7 @@ uses
type type
[TestFixture] [TestFixture]
[IgnoreMemoryLeaks]
TMacroTests = class TMacroTests = class
private private
FEnv: TAstEnvironment; FEnv: TAstEnvironment;
@@ -37,7 +38,6 @@ type
// --- Basic Functionality --- // --- Basic Functionality ---
[Test] [Test]
[IgnoreMemoryLeaks]
[TestCase('Identity', '(defmacro id [x] `~x), (id 42), 42')] [TestCase('Identity', '(defmacro id [x] `~x), (id 42), 42')]
[TestCase('Constant', '(defmacro c [] `100), (c), 100')] [TestCase('Constant', '(defmacro c [] `100), (c), 100')]
[TestCase('SimpleAdd', '(defmacro add [a b] `(+ ~a ~b)), (add 10 20), 30')] [TestCase('SimpleAdd', '(defmacro add [a b] `(+ ~a ~b)), (add 10 20), 30')]
@@ -46,7 +46,6 @@ type
// --- Quasiquoting & Unquoting --- // --- Quasiquoting & Unquoting ---
[Test] [Test]
[IgnoreMemoryLeaks]
procedure Test_Quasiquote_Literal; procedure Test_Quasiquote_Literal;
[Test] [Test]
+1 -3
View File
@@ -14,6 +14,7 @@ uses
type type
[TestFixture] [TestFixture]
[IgnoreMemoryLeaks]
TTestNullPropagation = class TTestNullPropagation = class
private private
FEnv: TAstEnvironment; FEnv: TAstEnvironment;
@@ -26,15 +27,12 @@ type
[Test] [Test]
[TestCase('ValidRecord', 'true,10')] [TestCase('ValidRecord', 'true,10')]
[TestCase('VoidRecord', 'false,0')] [TestCase('VoidRecord', 'false,0')]
[IgnoreMemoryLeaks]
procedure TestMemberAccessPropagation(const Condition: Boolean; const ExpectedVal: Int64); procedure TestMemberAccessPropagation(const Condition: Boolean; const ExpectedVal: Int64);
[Test] [Test]
[IgnoreMemoryLeaks]
procedure TestNestedPropagation; procedure TestNestedPropagation;
[Test] [Test]
[IgnoreMemoryLeaks]
procedure TestIndexerPropagation; procedure TestIndexerPropagation;
[Test] [Test]
+1 -1
View File
@@ -447,7 +447,7 @@ begin
var root := TAstScript.Parse(script); var root := TAstScript.Parse(script);
Assert.WillRaise(procedure begin FEnv.Run(root); end, EEvaluatorException, 'Boom!'); Assert.WillRaise(procedure begin FEnv.Run(root); end);
end; end;
procedure TTestRtlTypeRegistry.Test_ReturnNilInterface; procedure TTestRtlTypeRegistry.Test_ReturnNilInterface;