unit Myc.Ast.Nodes; interface uses System.SysUtils, System.Generics.Collections, Myc.Data.Scalar, Myc.Data.Value, Myc.Data.Keyword, Myc.Ast.Scope, Myc.Ast.Types; type // --- Forward Declarations --- IAstNode = interface; // --- Identity & Provenance --- /// Represents the immutable identity or provenance of an AST node. /// This allows tracking where a node came from (Parser, Generator, Macro, etc.) /// without coupling the AST to specific source formats. IAstIdentity = interface ['{5C496755-721D-4793-9526-7E2E63357719}'] function ToString: string; end; /// Specific identity for nodes originating from source code text. ISourceLocation = interface(IAstIdentity) ['{98226687-F323-49B0-8D38-61803B225973}'] function GetLine: Integer; function GetCol: Integer; property Line: Integer read GetLine; property Col: Integer read GetCol; end; // --- Error Handling Infrastructure --- TCompilerErrorLevel = (elHint, elWarning, elError); TCompilerError = record Level: TCompilerErrorLevel; Message: string; Node: IAstNode; // Context node where the error occurred constructor Create(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode = nil); function ToString: string; end; ICompilerLog = interface ['{8E5F2C10-4B3A-49F1-9C2D-7A8B5E6F4D3C}'] procedure Add(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode = nil); procedure AddError(const AMessage: string; const ANode: IAstNode = nil); procedure AddWarning(const AMessage: string; const ANode: IAstNode = nil); function HasErrors: Boolean; function GetEntryCount: Integer; function GetEntries: TArray; property Entries: TArray read GetEntries; end; // Standard implementation of the log TCompilerLog = class(TInterfacedObject, ICompilerLog) private FEntries: TList; public constructor Create; destructor Destroy; override; procedure Add(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode = nil); procedure AddError(const AMessage: string; const ANode: IAstNode = nil); procedure AddWarning(const AMessage: string; const ANode: IAstNode = nil); function HasErrors: Boolean; function GetEntryCount: Integer; function GetEntries: TArray; end; // Base class for all Compiler related exceptions. EAstException = class(Exception); // The concrete exception thrown at the END of a compilation pass if errors occurred. ECompilationFailed = class(EAstException) private FErrors: TArray; public constructor Create(const AErrors: TArray); function ToString: string; override; property Errors: TArray read FErrors; end; // --- AST Interfaces --- IAstVisitor = interface; IFunctionDefinition = interface; IIdentifierNode = interface; IConstantNode = interface; IKeywordNode = interface; IIfExpressionNode = interface; ITernaryExpressionNode = interface; ILambdaExpressionNode = interface; IFunctionCallNode = interface; IMacroExpansionNode = interface; IBlockExpressionNode = interface; IVariableDeclarationNode = interface; IAssignmentNode = interface; IMacroDefinitionNode = interface; IQuasiquoteNode = interface; IUnquoteNode = interface; IUnquoteSplicingNode = interface; IIndexerNode = interface; IMemberAccessNode = interface; IRecordLiteralNode = interface; ICreateSeriesNode = interface; IAddSeriesItemNode = interface; ISeriesLengthNode = interface; IRecurNode = interface; INopNode = interface; IAstTypedNode = interface; // Defines the concrete kinds of AST nodes TAstNodeKind = ( akConstant, akIdentifier, akKeyword, akIfExpression, akTernaryExpression, akLambdaExpression, akFunctionCall, akMacroExpansion, akBlockExpression, akVariableDeclaration, akAssignment, akMacroDefinition, akQuasiquote, akUnquote, akUnquoteSplicing, akIndexer, akMemberAccess, akRecordLiteral, akCreateSeries, akAddSeriesItem, akSeriesLength, akRecur, akNop ); TAstNodeKindHelper = record helper for TAstNodeKind public function ToString: string; end; // --- Concrete Type Definitions --- TRecordFieldLiteral = record Key: IKeywordNode; Value: IAstNode; constructor Create(const AKey: IKeywordNode; const AValue: IAstNode); end; IAstVisitor = interface function VisitConstant(const Node: IConstantNode): TDataValue; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; function VisitKeyword(const Node: IKeywordNode): TDataValue; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; function VisitAssignment(const Node: IAssignmentNode): TDataValue; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; function VisitUnquote(const Node: IUnquoteNode): TDataValue; function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; function VisitIndexer(const Node: IIndexerNode): TDataValue; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; function VisitRecurNode(const Node: IRecurNode): TDataValue; function VisitNop(const Node: INopNode): TDataValue; end; IAstNode = interface {$region 'private'} function GetKind: TAstNodeKind; function GetIsTyped: Boolean; function GetIdentity: IAstIdentity; {$endregion} function Accept(const Visitor: IAstVisitor): TDataValue; function AsConstant: IConstantNode; function AsIdentifier: IIdentifierNode; function AsKeyword: IKeywordNode; function AsIfExpression: IIfExpressionNode; function AsTernaryExpression: ITernaryExpressionNode; function AsLambdaExpression: ILambdaExpressionNode; function AsFunctionCall: IFunctionCallNode; function AsMacroExpansion: IMacroExpansionNode; function AsBlockExpression: IBlockExpressionNode; function AsVariableDeclaration: IVariableDeclarationNode; function AsAssignment: IAssignmentNode; function AsMacroDefinition: IMacroDefinitionNode; function AsQuasiquote: IQuasiquoteNode; function AsUnquote: IUnquoteNode; function AsUnquoteSplicing: IUnquoteSplicingNode; function AsIndexer: IIndexerNode; function AsMemberAccess: IMemberAccessNode; function AsRecordLiteral: IRecordLiteralNode; function AsCreateSeries: ICreateSeriesNode; function AsAddSeriesItem: IAddSeriesItemNode; function AsSeriesLength: ISeriesLengthNode; function AsRecur: IRecurNode; function AsNop: INopNode; function AsTypedNode: IAstTypedNode; property IsTyped: Boolean read GetIsTyped; property Kind: TAstNodeKind read GetKind; property Identity: IAstIdentity read GetIdentity; end; IAstTypedNode = interface(IAstNode) {$region 'private'} function GetStaticType: IStaticType; {$endregion} property StaticType: IStaticType read GetStaticType; end; IFunctionDefinition = interface(IAstTypedNode) {$region 'private'} function GetBody: IAstNode; function GetParameters: TArray; function GetIsPure: Boolean; {$endregion} property Body: IAstNode read GetBody; property Parameters: TArray read GetParameters; property IsPure: Boolean read GetIsPure; end; INopNode = interface(IAstTypedNode) end; IConstantNode = interface(IAstTypedNode) {$region 'private'} function GetValue: TDataValue; {$endregion} property Value: TDataValue read GetValue; end; IIdentifierNode = interface(IAstTypedNode) {$region 'private'} function GetAddress: TResolvedAddress; function GetName: string; {$endregion} property Address: TResolvedAddress read GetAddress; property Name: string read GetName; end; IKeywordNode = interface(IAstTypedNode) {$region 'private'} function GetValue: IKeyword; {$endregion} property Value: IKeyword read GetValue; end; IIfExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetCondition: IAstNode; function GetThenBranch: IAstNode; function GetElseBranch: IAstNode; {$endregion} property Condition: IAstNode read GetCondition; property ThenBranch: IAstNode read GetThenBranch; property ElseBranch: IAstNode read GetElseBranch; end; ITernaryExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetCondition: IAstNode; function GetThenBranch: IAstNode; function GetElseBranch: IAstNode; {$endregion} property Condition: IAstNode read GetCondition; property ThenBranch: IAstNode read GetThenBranch; property ElseBranch: IAstNode read GetElseBranch; end; ILambdaExpressionNode = interface(IFunctionDefinition) {$region 'private'} function GetParameters: TArray; function GetBody: IAstNode; function GetLayout: IScopeLayout; function GetDescriptor: IScopeDescriptor; function GetUpvalues: TArray; function GetHasNestedLambdas: Boolean; {$endregion} property Parameters: TArray read GetParameters; property Body: IAstNode read GetBody; property Layout: IScopeLayout read GetLayout; property Descriptor: IScopeDescriptor read GetDescriptor; property Upvalues: TArray read GetUpvalues; property HasNestedLambdas: Boolean read GetHasNestedLambdas; end; IFunctionCallNode = interface(IAstTypedNode) {$region 'private'} function GetCallee: IAstNode; function GetArguments: TArray; function GetIsTailCall: Boolean; function GetStaticTarget: TDataValue.TFunc; function GetIsTargetPure: Boolean; {$endregion} property Callee: IAstNode read GetCallee; property Arguments: TArray read GetArguments; property IsTailCall: Boolean read GetIsTailCall; property StaticTarget: TDataValue.TFunc read GetStaticTarget; property IsTargetPure: Boolean read GetIsTargetPure; end; IMacroExpansionNode = interface(IAstTypedNode) {$region 'private'} function GetCallNode: IFunctionCallNode; function GetExpandedBody: IAstNode; {$endregion} property CallNode: IFunctionCallNode read GetCallNode; property ExpandedBody: IAstNode read GetExpandedBody; end; IRecurNode = interface(IAstTypedNode) {$region 'private'} function GetArguments: TArray; {$endregion} property Arguments: TArray read GetArguments; end; IBlockExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetExpressions: TArray; {$endregion} property Expressions: TArray read GetExpressions; end; IVariableDeclarationNode = interface(IAstTypedNode) {$region 'private'} function GetTarget: IAstNode; function GetInitializer: IAstNode; function GetIsBoxed: Boolean; {$endregion} property Target: IAstNode read GetTarget; property Initializer: IAstNode read GetInitializer; property IsBoxed: Boolean read GetIsBoxed; end; IAssignmentNode = interface(IAstTypedNode) {$region 'private'} function GetTarget: IAstNode; function GetValue: IAstNode; {$endregion} property Target: IAstNode read GetTarget; property Value: IAstNode read GetValue; end; IMacroDefinitionNode = interface(IAstTypedNode) {$region 'private'} function GetName: IIdentifierNode; function GetParameters: TArray; function GetBody: IAstNode; {$endregion} property Name: IIdentifierNode read GetName; property Parameters: TArray read GetParameters; property Body: IAstNode read GetBody; end; IQuasiquoteNode = interface(IAstNode) {$region 'private'} function GetExpression: IAstNode; {$endregion} property Expression: IAstNode read GetExpression; end; IUnquoteNode = interface(IAstNode) {$region 'private'} function GetExpression: IAstNode; {$endregion} property Expression: IAstNode read GetExpression; end; IUnquoteSplicingNode = interface(IAstNode) {$region 'private'} function GetExpression: IQuasiquoteNode; {$endregion} property Expression: IQuasiquoteNode read GetExpression; end; IIndexerNode = interface(IAstTypedNode) {$region 'private'} function GetBase: IAstNode; function GetIndex: IAstNode; {$endregion} property Base: IAstNode read GetBase; property Index: IAstNode read GetIndex; end; IMemberAccessNode = interface(IAstTypedNode) {$region 'private'} function GetBase: IAstNode; function GetMember: IKeywordNode; {$endregion} property Base: IAstNode read GetBase; property Member: IKeywordNode read GetMember; end; IRecordLiteralNode = interface(IAstTypedNode) {$region 'private'} function GetFields: TArray; function GetGenericDefinition: IGenericRecordDefinition; function GetScalarDefinition: IScalarRecordDefinition; {$endregion} property Fields: TArray read GetFields; property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition; property ScalarDefinition: IScalarRecordDefinition read GetScalarDefinition; end; ICreateSeriesNode = interface(IAstTypedNode) {$region 'private'} function GetDefinition: String; {$endregion} property Definition: String read GetDefinition; end; IAddSeriesItemNode = interface(IAstTypedNode) {$region 'private'} function GetSeries: IIdentifierNode; function GetValue: IAstNode; function GetLookback: IAstNode; {$endregion} property Series: IIdentifierNode read GetSeries; property Value: IAstNode read GetValue; property Lookback: IAstNode read GetLookback; end; ISeriesLengthNode = interface(IAstTypedNode) {$region 'private'} function GetSeries: IIdentifierNode; {$endregion} property Series: IIdentifierNode read GetSeries; end; IEvaluatorVisitor = interface(IAstVisitor) function Execute(const RootNode: IAstNode): TDataValue; end; TEvaluatorFactory = reference to function(const Scope: IExecutionScope): IEvaluatorVisitor; implementation uses System.Classes, System.Rtti, System.StrUtils; { TAstNodeKindHelper } function TAstNodeKindHelper.ToString: string; begin Result := TRttiEnumerationType.GetName(Self); Assert(StartsText('ak', Result)); Result := Result.Substring(2); end; { TRecordFieldLiteral } constructor TRecordFieldLiteral.Create(const AKey: IKeywordNode; const AValue: IAstNode); begin Key := AKey; Value := AValue; end; { TCompilerError } constructor TCompilerError.Create(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode); begin Level := ALevel; Message := AMessage; Node := ANode; end; function TCompilerError.ToString: string; var location: string; loc: ISourceLocation; begin // 1. Try to extract location from Node Identity location := ''; if Assigned(Node) and Assigned(Node.Identity) then begin if Supports(Node.Identity, ISourceLocation, loc) then location := Format('[Line %d, Col %d] ', [loc.Line, loc.Col]) else location := Format('[%s] ', [Node.Identity.ToString]); end; // 2. Prefix with Level case Level of elHint: Result := '[Hint] '; elWarning: Result := '[Warning] '; elError: Result := '[Error] '; end; // 3. Assemble Result := Result + location + Message; end; { TCompilerLog } constructor TCompilerLog.Create; begin inherited Create; FEntries := TList.Create; end; destructor TCompilerLog.Destroy; begin FEntries.Free; inherited; end; procedure TCompilerLog.Add(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode); begin FEntries.Add(TCompilerError.Create(ALevel, AMessage, ANode)); end; procedure TCompilerLog.AddError(const AMessage: string; const ANode: IAstNode); begin Add(elError, AMessage, ANode); end; procedure TCompilerLog.AddWarning(const AMessage: string; const ANode: IAstNode); begin Add(elWarning, AMessage, ANode); end; function TCompilerLog.HasErrors: Boolean; var entry: TCompilerError; begin Result := False; for entry in FEntries do if entry.Level = elError then Exit(True); end; function TCompilerLog.GetEntryCount: Integer; begin Result := FEntries.Count; end; function TCompilerLog.GetEntries: TArray; begin Result := FEntries.ToArray; end; { ECompilationFailed } constructor ECompilationFailed.Create(const AErrors: TArray); var msg: string; begin if Length(AErrors) > 0 then msg := Format('Compilation failed with %d error(s). First: %s', [Length(AErrors), AErrors[0].Message]) else msg := 'Compilation failed with unspecified errors.'; inherited Create(msg); FErrors := AErrors; end; function ECompilationFailed.ToString: string; var sb: TStringBuilder; err: TCompilerError; begin sb := TStringBuilder.Create; try sb.AppendLine(Message); for err in FErrors do begin sb.Append(' - '); sb.AppendLine(err.ToString); end; Result := sb.ToString; finally sb.Free; end; end; end.