From ec76b78b39b9d18dcdfdd6ec9477fa538e9b9666 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Mon, 3 Nov 2025 00:35:23 +0100 Subject: [PATCH] Nop-Node --- ASTPlayground/MainForm.pas | 40 ++++++++---------- ASTPlayground/Myc.Fmx.AstEditor.Node.pas | 53 +++++++++++++++++++++++- Src/AST/Myc.Ast.Dumper.pas | 6 +++ Src/AST/Myc.Ast.Evaluator.pas | 34 ++++++++++++--- Src/AST/Myc.Ast.JSON.pas | 17 ++++++++ Src/AST/Myc.Ast.Nodes.pas | 11 ++++- Src/AST/Myc.Ast.Script.pas | 28 ++++++++++++- Src/AST/Myc.Ast.Visitor.pas | 25 +++++++++++ Src/AST/Myc.Ast.pas | 52 +++++++++++++++++++++++ 9 files changed, 234 insertions(+), 32 deletions(-) diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index 642e84a..d39ff95 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -845,32 +845,26 @@ begin end; procedure TForm1.Test1ButtonClick(Sender: TObject); -var - main, callAst: IAstNode; - result: TDataValue; - sw: TStopwatch; begin - Memo1.Lines.Clear; - Memo1.Lines.Add('--- Simple AST Execution ---'); - sw := TStopwatch.StartNew; - - main := - TAst.LambdaExpr( - [], - TAst.Block( - [ - TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(10)), - TAst.VarDecl(TAst.Identifier('b'), TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Multiply, TAst.Constant(2))), - TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Add, TAst.Identifier('b')) - ] - ) + FCurrUnboundAst := + TAst.Block( + [ + TAst.LambdaExpr( + [], + TAst.Block( + [ + TAst.VarDecl(TAst.Identifier('a'), TAst.Nop), + TAst.VarDecl( + TAst.Identifier('b'), + TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Multiply, TAst.Constant(2)) + ), + TAst.BinaryExpr(TAst.Identifier('a'), TScalar.TBinaryOp.Add, TAst.Identifier('b')) + ] + ) + ) + ] ); - callAst := TAst.FunctionCall(main, []); - result := ExecuteAst(callAst, FGScope); - - sw.Stop; - Memo1.Lines.Add(Format('Result: %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); UpdateScript; end; diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas index 6574dfe..f922fff 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas @@ -88,6 +88,7 @@ type function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAuraNode; override; function VisitSeriesLength(const Node: ISeriesLengthNode): TAuraNode; override; function VisitRecurNode(const Node: IRecurNode): TAuraNode; override; + function VisitNop(const Node: INopNode): TAuraNode; override; public constructor Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; AExprDepth: Integer); @@ -525,6 +526,17 @@ type property Node: ISeriesLengthNode read FNode; end; + TAuraNopNode = class(TAuraNode) + private + FNode: INopNode; + protected + procedure SetupNode; override; + public + constructor Create(const AVisualizer: IAstVisualizer; const ANode: INopNode); + function CreateAst: IAstNode; override; + property Node: INopNode read FNode; + end; + implementation { TAstVisualizer } @@ -716,6 +728,11 @@ begin Result := TAuraVariableDeclarationNode.Create(Self, Node); end; +function TAstVisualizer.VisitNop(const Node: INopNode): TAuraNode; +begin + Result := TAuraNopNode.Create(Self, Node); +end; + { TAutoFitControl } constructor TAutoFitControl.Create(AOwner: TComponent); @@ -1039,7 +1056,7 @@ begin Canvas.FillRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0); Canvas.Stroke.Kind := TBrushKind.Solid; Canvas.Stroke.Color := FBorderColor; - Canvas.Stroke.Thickness := 1; + Canvas.Stroke.Thickness := FBorderWidth; Canvas.Stroke.Dash := TStrokeDash.Dot; Canvas.DrawRect(TRectF.Create(0, 0, Width, Height), 0, 0, [], 1.0); end @@ -2222,4 +2239,38 @@ begin end; end; +{ TAuraNopNode } + +constructor TAuraNopNode.Create(const AVisualizer: IAstVisualizer; const ANode: INopNode); +begin + inherited Create(AVisualizer); + FNode := ANode; + + // Visual style for Nop: Dotted border, minimal size, clear color. + Frameless := true; + Alignment := TLayoutAlignment.laCenter; + Orientation := TLayoutOrientation.loHorizontal; +end; + +function TAuraNopNode.CreateAst: IAstNode; +begin + // The visual node returns the original Nop node, which the compiler pipeline must reject. + Result := FNode; +end; + +procedure TAuraNopNode.SetupNode; +var + lbl: TLabel; +begin + BeginUpdate; + try + // Add a placeholder text + lbl := AddLabel(Self, '...'); + lbl.Font.Style := lbl.Font.Style + [TFontStyle.fsBold]; + lbl.Margins.Rect := TRectF.Create(8, 4, 8, 4); + finally + EndUpdate; + end; +end; + end. diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index 3766b24..6c59d8d 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -56,6 +56,7 @@ type procedure VisitCreateSeries(const Node: ICreateSeriesNode); override; procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); override; procedure VisitSeriesLength(const Node: ISeriesLengthNode); override; + procedure VisitNop(const Node: INopNode); override; public // Creates a new instance of the AST dumper. @@ -461,6 +462,11 @@ begin Unindent; end; +procedure TAstDumper.VisitNop(const Node: INopNode); +begin + Log('Nop'); +end; + procedure TAstDumper.VisitSeriesLength(const Node: ISeriesLengthNode); begin Log('SeriesLength'); diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 8dad58a..1e8617d 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -47,6 +47,7 @@ type function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; virtual; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual; function VisitRecurNode(const Node: IRecurNode): TDataValue; virtual; + function VisitNop(const Node: INopNode): TDataValue; virtual; function IsTruthy(const AValue: TDataValue): Boolean; inline; @@ -445,6 +446,12 @@ var index: Int64; series: ISeries; recSeries: IRecordSeries; + i, fieldCount: Integer; + values: TArray; + key: IKeyword; + memberSeries: ISeries; + scalarValue: TScalar; + rec: TScalarRecord; begin baseValue := Node.Base.Accept(Self); indexValue := Node.Index.Accept(Self); @@ -467,12 +474,20 @@ begin recSeries := baseValue.AsRecordSeries; if (index < 0) or (index >= recSeries.TotalCount) then raise EArgumentException.CreateFmt('Index %d is out of bounds for series with %d elements.', [index, recSeries.TotalCount]); - // Accessing a record series by index materializes the TScalarRecord - var rec := TScalarRecord.Create(recSeries.Def, nil); // Nil fields, needs implementation - // TODO: This path (materializing a record from TScalarRecordSeries) is not fully implemented. - // We need to fetch the underlying TScalar.TValue array slice. - // For now, returning an empty record shell. - raise ENotSupportedException.Create('Indexing a RecordSeries to materialize a Record is not yet implemented.'); + + // Materialize the TScalarRecord by accessing each member series by index + fieldCount := Length(recSeries.Def.Fields); + SetLength(values, fieldCount); + + for i := 0 to fieldCount - 1 do + begin + key := recSeries.Def.Fields[i].Key; + memberSeries := recSeries[key]; + scalarValue := memberSeries[index]; + values[i] := scalarValue.Value; + end; + + rec := TScalarRecord.Create(recSeries.Def, values); Result := TDataValue.FromRecord(rec); end; else @@ -646,6 +661,13 @@ begin Result := expression.Accept(Self); end; +function TEvaluatorVisitor.VisitNop(const Node: INopNode): TDataValue; +begin + // This node should be rejected by the Binder/TypeChecker. + // If it reaches the evaluator, it's a compiler bug, but we treat it as Void. + Result := TDataValue.Void; +end; + function TEvaluatorVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; var seriesValue: TDataValue; diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas index bce60c7..5a8d62c 100644 --- a/Src/AST/Myc.Ast.JSON.pas +++ b/Src/AST/Myc.Ast.JSON.pas @@ -51,6 +51,8 @@ type function JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode; function JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode; function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode; + function JsonToNopNode(const AObj: TJSONObject): INopNode; + protected // IAstVisitor implementation for serialization (T = TJSONObject) function VisitConstant(const Node: IConstantNode): TJSONObject; override; @@ -77,6 +79,7 @@ type function VisitCreateSeries(const Node: ICreateSeriesNode): TJSONObject; override; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TJSONObject; override; function VisitSeriesLength(const Node: ISeriesLengthNode): TJSONObject; override; + function VisitNop(const Node: INopNode): TJSONObject; override; function Serialize(const RootNode: IAstNode): TJSONObject; function Deserialize(const AJson: TJSONObject): IAstNode; @@ -456,6 +459,13 @@ begin Result.AddPair('Series', seriesObj); end; +function TJsonAstConverter.VisitNop(const Node: INopNode): TJSONObject; +begin + Result := TJSONObject.Create; + Result.AddPair('NodeType', TJSONString.Create('Nop')); + // No other properties needed, as it is stateless +end; + { TJsonAstConverter - Deserialization } function TJsonAstConverter.JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue; @@ -761,6 +771,11 @@ begin Result := TAst.SeriesLength(JsonToIdentifierNode(AObj.GetValue('Series') as TJSONObject)); end; +function TJsonAstConverter.JsonToNopNode(const AObj: TJSONObject): INopNode; +begin + Result := TAst.Nop.AsNop; +end; + function TJsonAstConverter.JsonToNode(const AJson: TJSONValue; const ExpectedType: String = ''): IAstNode; var obj: TJSONObject; @@ -823,6 +838,8 @@ begin Result := JsonToAddSeriesItemNode(obj) else if nodeType = 'SeriesLength' then Result := JsonToSeriesLengthNode(obj) + else if nodeType = 'Nop' then // Added Nop Deserialization logic + Result := JsonToNopNode(obj) else raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s" for JSON deserialization.', [nodeType]); end; diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 759e84d..858272c 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -37,6 +37,7 @@ type IAddSeriesItemNode = interface; ISeriesLengthNode = interface; IRecurNode = interface; + INopNode = interface; // Added Nop // Defines the concrete kinds of AST nodes TAstNodeKind = ( @@ -63,7 +64,8 @@ type akCreateSeries, akAddSeriesItem, akSeriesLength, - akRecur + akRecur, + akNop // Added Nop ); // --- Concrete Type Definitions --- @@ -113,6 +115,7 @@ type function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; function VisitRecurNode(const Node: IRecurNode): TDataValue; + function VisitNop(const Node: INopNode): TDataValue; // Added Nop end; IAstNode = interface(IInterface) @@ -145,10 +148,16 @@ type function AsAddSeriesItem: IAddSeriesItemNode; function AsSeriesLength: ISeriesLengthNode; function AsRecur: IRecurNode; + function AsNop: INopNode; // Added Nop property Kind: TAstNodeKind read GetKind; end; + INopNode = interface(IAstNode) + // A placeholder node for the UI (e.g., drag target). + // This node is illegal during compilation. + end; + IConstantNode = interface(IAstNode) // Represents a constant value in the AST (Scalar, Text, or Void). {$region 'private'} diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index cd2716d..7f08c7b 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -9,6 +9,7 @@ uses var // This BNF should always be kept up to date and valid. It is used to comunicate with LLMs. + // (Nop isn't part of the language syntax.) BNF: String = ''' @@ -221,6 +222,7 @@ type procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); override; procedure VisitSeriesLength(const Node: ISeriesLengthNode); override; procedure VisitRecurNode(const Node: IRecurNode); override; + procedure VisitNop(const Node: INopNode); override; end; { TLexer } @@ -716,7 +718,24 @@ begin end; tkIdentifier: begin - Result.Node := TAst.Identifier(FCurrentToken.Text); + // Check for the NOP token ('...') + if FCurrentToken.Text = '...' then + begin + // The token '...' represents the Nop node, a non-compilable placeholder used exclusively + // for visual representations (e.g., UI drag targets). + // + // We handle it as a special case of tkIdentifier instead of introducing a dedicated tkNop + // TokenKind because of its **unofficial and transient status**. + // + // This allows the Parser to immediately apply the syntactic policy: mapping this + // specific identifier string directly to the non-standard TAst.Nop node, ensuring + // it bypasses the regular identifier creation and maintains its "placeholder" identity. + Result.Node := TAst.Nop; + end + else + begin + Result.Node := TAst.Identifier(FCurrentToken.Text); + end; NextToken; end; tkLeftParen: Result.Node := ParseList; @@ -1068,6 +1087,13 @@ begin Append(')'); end; +procedure TPrettyPrintVisitor.VisitNop(const Node: INopNode); +begin + // A placeholder node, usually for UI. + //TODO Nop soll durch ein spezielles ...-Token repräsentiert werden. Analysiere, ob dies syntaktisch möglich ist. Wenn ja, soll der Parser "inoffiziell" in der Lage sein Nops zu lesen und zu erzeugen. + Append('...'); +end; + procedure TPrettyPrintVisitor.VisitSeriesLength(const Node: ISeriesLengthNode); begin Append('(count '); diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 8eda77f..35126f9 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -37,6 +37,7 @@ type function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem; function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength; function IAstVisitor.VisitRecurNode = DoVisitRecurNode; + function IAstVisitor.VisitNop = DoVisitNop; // Added Nop // Private bridge method implementations function DoVisitConstant(const Node: IConstantNode): TDataValue; @@ -63,6 +64,7 @@ type function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; function DoVisitRecurNode(const Node: IRecurNode): TDataValue; + function DoVisitNop(const Node: INopNode): TDataValue; // Added Nop protected // Visit a node. @@ -92,6 +94,7 @@ type function VisitAddSeriesItem(const Node: IAddSeriesItemNode): T; virtual; abstract; function VisitSeriesLength(const Node: ISeriesLengthNode): T; virtual; abstract; function VisitRecurNode(const Node: IRecurNode): T; virtual; abstract; + function VisitNop(const Node: INopNode): T; virtual; abstract; // Added Nop end; TAstTransformer = class abstract(TAstVisitor) @@ -123,6 +126,7 @@ type function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override; function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override; function VisitRecurNode(const Node: IRecurNode): IAstNode; override; + function VisitNop(const Node: INopNode): IAstNode; override; // Added Nop end; TAstVisitor = class abstract(TInterfacedObject, IAstVisitor) @@ -152,6 +156,7 @@ type function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem; function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength; function IAstVisitor.VisitRecurNode = DoVisitRecurNode; + function IAstVisitor.VisitNop = DoVisitNop; // Added Nop // Private bridge method implementations function DoVisitConstant(const Node: IConstantNode): TDataValue; @@ -178,6 +183,7 @@ type function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; function DoVisitRecurNode(const Node: IRecurNode): TDataValue; + function DoVisitNop(const Node: INopNode): TDataValue; // Added Nop protected // Virtual procedures for descendants (Interpreters/Side-effects) to override @@ -205,6 +211,7 @@ type procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); virtual; abstract; procedure VisitSeriesLength(const Node: ISeriesLengthNode); virtual; abstract; procedure VisitRecurNode(const Node: IRecurNode); virtual; abstract; + procedure VisitNop(const Node: INopNode); virtual; abstract; // Added Nop end; implementation @@ -341,6 +348,12 @@ begin Result := TDataValue.FromGeneric(VisitRecurNode(Node)); end; +function TAstVisitor.DoVisitNop(const Node: INopNode): TDataValue; +begin + // Added Nop implementation + Result := TDataValue.FromGeneric(VisitNop(Node)); +end; + function TAstTransformer.AcceptParameters(const Nodes: TArray): TArray; var i: Integer; @@ -418,6 +431,12 @@ begin Result := Node; // Leaf node end; +function TAstTransformer.VisitNop(const Node: INopNode): IAstNode; +begin + // Added Nop implementation + Result := Node; // Leaf node +end; + function TAstTransformer.VisitBinaryExpression(const Node: IBinaryExpressionNode): IAstNode; var N: TBinaryExpressionNode; @@ -753,4 +772,10 @@ begin VisitRecurNode(Node); end; +function TAstVisitor.DoVisitNop(const Node: INopNode): TDataValue; +begin + // Added Nop implementation + VisitNop(Node); +end; + end. diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index fc826f9..e575f76 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -28,6 +28,10 @@ type class procedure RegisterLibrary(const AProc: TRegisterLibraryProc); static; class function CreateScope(Parent: IExecutionScope; const Descriptor: IScopeDescriptor = nil): IExecutionScope; static; + // A No-Operation node, used as a placeholder/stub (e.g., for UI drop targets). + // This node is illegal in the compiler (Binder/TypeChecker will reject it). + class function Nop: IAstNode; static; + // --- Factory functions --- class function Constant(const AValue: TDataValue): IConstantNode; overload; static; class function Constant(const AValue: String): IConstantNode; overload; static; @@ -103,6 +107,7 @@ type function AsAddSeriesItem: IAddSeriesItemNode; virtual; function AsSeriesLength: ISeriesLengthNode; virtual; function AsRecur: IRecurNode; virtual; + function AsNop: INopNode; virtual; // Added Nop property StaticType: IStaticType read FStaticType write SetStaticType; property Kind: TAstNodeKind read GetKind; @@ -474,6 +479,17 @@ implementation uses System.Generics.Defaults; +// Added Nop +type + TNopNode = class(TAstNode, INopNode) + private + function GetKind: TAstNodeKind; override; + public + constructor Create; + function Accept(const Visitor: IAstVisitor): TDataValue; override; + function AsNop: INopNode; override; + end; + { TAst } class function TAst.CreateScope(Parent: IExecutionScope; const Descriptor: IScopeDescriptor): IExecutionScope; @@ -603,6 +619,12 @@ begin Result := TMemberAccessNode.Create(ABase, AMember); end; +class function TAst.Nop: IAstNode; +begin + // Factory function for the new Nop node + Result := TNopNode.Create; +end; + class function TAst.Quasiquote(const AExpression: IAstNode): IQuasiquoteNode; begin Result := TQuasiquoteNode.Create(AExpression); @@ -650,6 +672,30 @@ begin Result := TVariableDeclarationNode.Create(AIdentifier, AInitializer); end; +{ TNopNode } + +constructor TNopNode.Create; +begin + inherited Create; + // Nop nodes are 'Void' by default, though TypeChecker will reject them anyway. + StaticType := TTypes.Void; +end; + +function TNopNode.Accept(const Visitor: IAstVisitor): TDataValue; +begin + Result := Visitor.VisitNop(Self); +end; + +function TNopNode.AsNop: INopNode; +begin + Result := Self; +end; + +function TNopNode.GetKind: TAstNodeKind; +begin + Result := akNop; +end; + { TAstNode } constructor TAstNode.Create; @@ -733,6 +779,12 @@ begin raise ETypeException.Create('Node is not a MemberAccess'); end; +function TAstNode.AsNop: INopNode; +begin + // Added Nop implementation + raise ETypeException.Create('Node is not a Nop'); +end; + function TAstNode.AsQuasiquote: IQuasiquoteNode; begin raise ETypeException.Create('Node is not a Quasiquote');