diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj index 31f0c45..a738789 100644 --- a/ASTPlayground/ASTPlayground.dproj +++ b/ASTPlayground/ASTPlayground.dproj @@ -5,9 +5,9 @@ FMX True Debug - Win32 + Win64 ASTPlayground - 3 + 2 Application ASTPlayground.dpr @@ -1142,7 +1142,7 @@ - True + False True diff --git a/ASTPlayground/ASTPlayground.res b/ASTPlayground/ASTPlayground.res index 29a1295..8e77859 100644 Binary files a/ASTPlayground/ASTPlayground.res and b/ASTPlayground/ASTPlayground.res differ diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index f93d9c8..e1ae840 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -88,6 +88,7 @@ begin FWorkspace := TAuraWorkspace.Create(Panel2); FWorkspace.Parent := Panel2; FWorkspace.Align := TAlignLayout.Client; + FWorkspace.ClipChildren := true; FWorkspace.OnMouseDown := WorkspaceMouseDown; diff --git a/ASTPlayground/Myc.Ast.Visualizer.pas b/ASTPlayground/Myc.Ast.Visualizer.pas index 0575dcb..ff6215e 100644 --- a/ASTPlayground/Myc.Ast.Visualizer.pas +++ b/ASTPlayground/Myc.Ast.Visualizer.pas @@ -16,21 +16,33 @@ uses DraggablePanel; type - // Record to store a connection between two pins + // ... TPinConnection, TPinShape, TPinAlignment, TAuraWorkspace ... TPinConnection = record OutputPin: TControl; InputPin: TControl; constructor Create(AOutputPin, AInputPin: TControl); end; - // Defines the shape of a pin. TPinShape = (psCircle, psTriangle); - // Defines the horizontal alignment of a pin on a node. TPinAlignment = (paLeft, paRight); TAuraWorkspace = class; TAstToAuraNodeVisitor = class(TInterfacedObject, IAstVisitor) + const + // Pin Visuals + cPinSize = 10; + cDataPinColor = TAlphaColors.Dodgerblue; + cExecPinColor = TAlphaColors.Lightgreen; + + // Node Layout + cHorizontalPadding = 25; // Padding inside the node title bar + cPinOffsetY = 18; // Vertical distance between pins + cVerticalPadding = 20; // General vertical padding inside container nodes + + // Specific Node Heights + cDefaultNodeHeight = 45; // Default height for simple nodes like BinaryExpr + cIfNodeHeight = 60; // Taller height for If nodes with more pins private FWorkspace: TAuraWorkspace; FParentControl: TControl; @@ -46,9 +58,15 @@ type AColor: TAlphaColor; const ATag: string; AOffsetY: Single = 0 - ): TControl; + ): TShape; + + function CreateInput(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; + function CreateOutput(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; + function CreateEntry(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; + function CreateExit(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; + function FindPinByTag(AParentNode: TAuraNode; const ATag: string): TControl; - function ConnectPins(InputPin: TControl; OutputNode: TAuraNode; const Tag: string): TControl; + function ConnectData(InputPin: TControl; OutputNode: TAuraNode): TControl; public constructor Create( AWorkspace: TAuraWorkspace; @@ -148,17 +166,39 @@ begin FConnections := AConnections; end; -function TAstToAuraNodeVisitor.ConnectPins(InputPin: TControl; OutputNode: TAuraNode; const Tag: string): TControl; +function TAstToAuraNodeVisitor.ConnectData(InputPin: TControl; OutputNode: TAuraNode): TControl; begin - Result := FindPinByTag(OutputNode, Tag); + Result := FindPinByTag(OutputNode, 'data.out'); if Assigned(Result) then FConnections.Add(TPinConnection.Create(Result, InputPin)); end; +function TAstToAuraNodeVisitor.CreateInput(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; +begin + Result := CreatePin(AParentNode, psCircle, paLeft, cDataPinColor, 'data.in.' + ACaption, AOffsetY); + Result.Hint := ACaption; + Result.ShowHint := ACaption <> ''; +end; + +function TAstToAuraNodeVisitor.CreateOutput(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; +begin + Result := CreatePin(AParentNode, psCircle, paRight, cDataPinColor, 'data.out', AOffsetY); +end; + +function TAstToAuraNodeVisitor.CreateEntry(AParentNode: TAuraNode; AOffsetY: Single = 0): TControl; +begin + Result := CreatePin(AParentNode, psTriangle, paLeft, cExecPinColor, 'exec.in', AOffsetY); +end; + +function TAstToAuraNodeVisitor.CreateExit(AParentNode: TAuraNode; const ACaption: string; AOffsetY: Single = 0): TControl; +begin + var cap := ACaption; + if cap <> '' then + cap := '.' + cap; + Result := CreatePin(AParentNode, psTriangle, paRight, cExecPinColor, 'exec.out' + cap, AOffsetY); +end; + function TAstToAuraNodeVisitor.CreateNodeControl(const ATitle, ADetails: string): TAuraNode; -const - // Horizontal padding on each side of the title to ensure text and pins are fully visible. - cHorizontalPadding = 25; var LFullTitle: string; LTextWidth: Single; @@ -199,10 +239,8 @@ function TAstToAuraNodeVisitor.CreatePin( AAlignment: TPinAlignment; AColor: TAlphaColor; const ATag: string; - AOffsetY: Single -): TControl; -const - cPinSize = 10; + AOffsetY: Single = 0 +): TShape; var LPosX, LPosY: Single; begin @@ -240,7 +278,7 @@ begin // Common properties Result.Parent := AParentNode; Result.SetBounds(LPosX, LPosY, cPinSize, cPinSize); - Result.HitTest := False; + Result.HitTest := true; // Use TagString to identify the control as a pin and describe its function. Result.TagString := ATag; @@ -265,9 +303,6 @@ begin end; function TAstToAuraNodeVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; -const - cExecPinColor = TAlphaColors.Lightgreen; - cDataPinColor = TAlphaColors.Dodgerblue; var LAssignmentNode, LValueNode: TAuraNode; LInputPin: TControl; @@ -285,12 +320,12 @@ begin // Create the assignment node and its pins LAssignmentNode := CreateNodeControl('Assignment', Node.Identifier.Name); - CreatePin(LAssignmentNode, psTriangle, paLeft, cExecPinColor, 'Pin.Exec.In', -8); - CreatePin(LAssignmentNode, psTriangle, paRight, cExecPinColor, 'Pin.Exec.Out'); - LInputPin := CreatePin(LAssignmentNode, psCircle, paLeft, cDataPinColor, 'Pin.Data.In', 8); + CreateEntry(LAssignmentNode, 0); + CreateExit(LAssignmentNode, '', 0); + LInputPin := CreateInput(LAssignmentNode, Node.Identifier.Name, 0); // Connect the value to the assignment - ConnectPins(LInputPin, LValueNode, 'Pin.Data.Out'); + ConnectData(LInputPin, LValueNode); // Finalize visitor state FCurrentPos.X := LStartPosition.X; @@ -301,9 +336,6 @@ begin end; function TAstToAuraNodeVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue; -const - cDataPinColor = TAlphaColors.Dodgerblue; - cDefaultHeight = 45; var LBinaryExprNode, LLeftNode, LRightNode: TAuraNode; LInputPin1, LInputPin2: TControl; @@ -329,20 +361,21 @@ begin // Vertically center the binary expression node between its children var LTotalChildHeight := LRightEndY - LStartPosition.Y; var LNodeCenterY := LStartPosition.Y + (LTotalChildHeight / 2); - FCurrentPos.Y := LNodeCenterY - (cDefaultHeight / 2); + FCurrentPos.Y := LNodeCenterY - (cDefaultNodeHeight / 2); LBinaryExprNode := CreateNodeControl(Node.Operator.ToString, ''); + LBinaryExprNode.TitleFont.Size := 20; // Create pins for the binary expression node - LInputPin1 := CreatePin(LBinaryExprNode, psCircle, paLeft, cDataPinColor, 'Pin.Data.In.1', -8); - LInputPin2 := CreatePin(LBinaryExprNode, psCircle, paLeft, cDataPinColor, 'Pin.Data.In.2', 8); - CreatePin(LBinaryExprNode, psCircle, paRight, cDataPinColor, 'Pin.Data.Out', 0); + LInputPin1 := CreateInput(LBinaryExprNode, LLeftNode.Name, -8); + LInputPin2 := CreateInput(LBinaryExprNode, LRightNode.Name, 8); + CreateOutput(LBinaryExprNode, 0); // Connect the output of the left child to the first input of the binary node - ConnectPins(LInputPin1, LLeftNode, 'Pin.Data.Out'); + ConnectData(LInputPin1, LLeftNode); // Connect the output of the right child to the second input of the binary node - ConnectPins(LInputPin2, LRightNode, 'Pin.Data.Out'); + ConnectData(LInputPin2, LRightNode); // Finalize visitor state for the next node at this level FCurrentPos.X := LStartPosition.X; @@ -404,67 +437,109 @@ begin end; function TAstToAuraNodeVisitor.VisitConstant(const Node: IConstantNode): TAstValue; -const - cDataPinColor = TAlphaColors.Dodgerblue; var LConstantNode: TAuraNode; begin // A constant has a data output pin. // Data outputs are round and on the right side of the box. LConstantNode := CreateNodeControl('Constant', Node.Value.ToString); - CreatePin(LConstantNode, psCircle, paRight, cDataPinColor, 'Pin.Data.Out', 0); + CreateOutput(LConstantNode, 0); Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; var - Arg: IExpressionNode; + LFuncCallNode: TAuraNode; + LInputResultNodes: TList; + LInputNode: IExpressionNode; + LStartPosition: TPointF; + LEndY, LMaxChildWidth: Single; + i: Integer; + LCalleePin: TControl; + LArgPin: array of TControl; + LCurrentInputOffsetY, LCurrentOutputOffsetY: Single; begin - CreateNodeControl('FunctionCall', ''); - FCurrentPos.X := FCurrentPos.X + FSpacing.X; + LStartPosition := FCurrentPos; + LInputResultNodes := TList.Create; try - // Callee - CreateNodeControl('Callee', ''); - FCurrentPos.X := FCurrentPos.X + FSpacing.X; - try - Node.Callee.Accept(Self); - finally - FCurrentPos.X := FCurrentPos.X - FSpacing.X; + // 1. Visit all input nodes first (Callee + Arguments) and collect their visual representations. + Node.Callee.Accept(Self); + LInputResultNodes.Add(FLastNode); + + for LInputNode in Node.Arguments do + begin + LInputNode.Accept(Self); + LInputResultNodes.Add(FLastNode); + end; + LEndY := FCurrentPos.Y; + + // 2. Determine the layout bounds of the input nodes. + LMaxChildWidth := 0; + for var LNode in LInputResultNodes do + LMaxChildWidth := Max(LMaxChildWidth, LNode.Position.X + LNode.Width); + + // 3. Position and create the 'FunctionCall' node to the right of the inputs. + FCurrentPos.X := LMaxChildWidth + FSpacing.X; + + // Calculate dynamic height based on the number of pins + var LNumInputPins := 2 + Node.Arguments.Count; + var LNumOutputPins := 2; + var LMaxPins := Max(LNumInputPins, LNumOutputPins); + var LNodeHeight := cVerticalPadding + (LMaxPins - 1) * cPinOffsetY; + + var LTotalInputHeight := LEndY - LStartPosition.Y; + var LNodeCenterY := LStartPosition.Y + (LTotalInputHeight / 2); + FCurrentPos.Y := LNodeCenterY - (LNodeHeight / 2); + + LFuncCallNode := CreateNodeControl('FunctionCall', ''); + LFuncCallNode.Height := LNodeHeight; + + // 4. Create all necessary pins on the 'FunctionCall' node with proper spacing. + // Left side (Inputs) + LCurrentInputOffsetY := -(LNumInputPins - 1) / 2.0 * cPinOffsetY; + CreateEntry(LFuncCallNode, LCurrentInputOffsetY); + LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY; + LCalleePin := CreateInput(LFuncCallNode, LInputResultNodes[0].Name, LCurrentInputOffsetY); + LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY; + SetLength(LArgPin, Node.Arguments.Count); + for i := 0 to Node.Arguments.Count - 1 do + begin + LArgPin[i] := CreateInput(LFuncCallNode, LInputResultNodes[i + 1].Name + i.ToString, LCurrentInputOffsetY); + LCurrentInputOffsetY := LCurrentInputOffsetY + cPinOffsetY; end; - // Arguments - if Node.Arguments.Count > 0 then - begin - CreateNodeControl('Arguments', ''); - FCurrentPos.X := FCurrentPos.X + FSpacing.X; - try - for Arg in Node.Arguments do - Arg.Accept(Self); - finally - FCurrentPos.X := FCurrentPos.X - FSpacing.X; - end; - end; + // Right side (Outputs) + LCurrentOutputOffsetY := -(LNumOutputPins - 1) / 2.0 * cPinOffsetY; + CreateExit(LFuncCallNode, '', LCurrentOutputOffsetY); + LCurrentOutputOffsetY := LCurrentOutputOffsetY + cPinOffsetY; + CreateOutput(LFuncCallNode, LCurrentOutputOffsetY); + + // 5. Connect the inputs. + ConnectData(LCalleePin, LInputResultNodes[0]); + for i := 0 to Node.Arguments.Count - 1 do + ConnectData(LArgPin[i], LInputResultNodes[i + 1]); + finally - FCurrentPos.X := FCurrentPos.X - FSpacing.X; + LInputResultNodes.Free; end; + + // 6. Finalize visitor state. + FCurrentPos.X := LStartPosition.X; + FCurrentPos.Y := Max(LEndY, LFuncCallNode.Position.Y + LFuncCallNode.Height + FSpacing.Y); + FLastNode := LFuncCallNode; + Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue; -const - cDataPinColor = TAlphaColors.Dodgerblue; begin CreateNodeControl('Identifier', Node.Name); // Add a data output pin on the right side. - CreatePin(FLastNode, psCircle, paRight, cDataPinColor, 'Pin.Data.Out'); + CreateOutput(FLastNode, 0); Result := TAstValue.Void; end; function TAstToAuraNodeVisitor.VisitIfExpression(const Node: IIfExpressionNode): TAstValue; -const - cExecPinColor = TAlphaColors.Lightgreen; - cDataPinColor = TAlphaColors.Dodgerblue; - cDefaultHeight = 60; // Taller to accommodate more pins var LIfNode, LConditionNode, LThenNode, LElseNode: TAuraNode; LConditionInputPin, LThenOutputPin, LElseOutputPin: TControl; @@ -481,20 +556,20 @@ begin // Position and create the 'If' node to the right of the condition's subtree. LMaxChildWidth := LConditionNode.Position.X + LConditionNode.Width; FCurrentPos.X := LMaxChildWidth + FSpacing.X; - FCurrentPos.Y := LStartPosition.Y + (LConditionNode.Position.Y + LConditionNode.Height / 2) - LStartPosition.Y - (cDefaultHeight / 2); + FCurrentPos.Y := LStartPosition.Y + (LConditionNode.Position.Y + LConditionNode.Height / 2) - LStartPosition.Y - (cIfNodeHeight / 2); LIfNode := CreateNodeControl('If', ''); - LIfNode.Height := cDefaultHeight; - CreatePin(LIfNode, psTriangle, paLeft, cExecPinColor, 'Pin.Exec.In', -12); - LConditionInputPin := CreatePin(LIfNode, psCircle, paLeft, cDataPinColor, 'Pin.Data.In.Condition', 12); - LThenOutputPin := CreatePin(LIfNode, psTriangle, paRight, cExecPinColor, 'Pin.Exec.Out.Then', -12); + LIfNode.Height := cIfNodeHeight; + CreateEntry(LIfNode, -12); + LConditionInputPin := CreateInput(LIfNode, 'Condition', 12); + LThenOutputPin := CreateExit(LIfNode, 'Then', -12); if Assigned(Node.ElseBranch) then - LElseOutputPin := CreatePin(LIfNode, psTriangle, paRight, cExecPinColor, 'Pin.Exec.Out.Else', 12) + LElseOutputPin := CreateExit(LIfNode, 'Else', 12) else LElseOutputPin := nil; // Connect the condition's output to the 'If' node's data input. - ConnectPins(LConditionInputPin, LConditionNode, 'Pin.Data.Out'); + ConnectData(LConditionInputPin, LConditionNode); // Visit the 'Then' and 'Else' execution branches, placing them to the right of the 'If' node. var LBranchStartX := LIfNode.Position.X + LIfNode.Width + FSpacing.X; @@ -505,7 +580,7 @@ begin Node.ThenBranch.Accept(Self); LThenNode := FLastNode; LThenEndY := FCurrentPos.Y; - // ConnectPins(FindPinByTag(LThenNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Then'); + // ConnectData(FindPinByTag(LThenNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Then'); // Else branch (if it exists) LElseEndY := LThenEndY; @@ -516,7 +591,7 @@ begin Node.ElseBranch.Accept(Self); LElseNode := FLastNode; LElseEndY := FCurrentPos.Y; - // ConnectPins(FindPinByTag(LElseNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Else'); + // ConnectData(FindPinByTag(LElseNode, 'Pin.Exec.In'), LIfNode, 'Pin.Exec.Out.Else'); end; // Finalize visitor state. @@ -528,9 +603,6 @@ begin end; function TAstToAuraNodeVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; -const - cExecPinColor = TAlphaColors.Lightgreen; - cDataPinColor = TAlphaColors.Dodgerblue; var ParamStr: String; LLambdaNode: TAuraNode; @@ -578,7 +650,7 @@ begin LLambdaNode.Height := Max(LLambdaNode.Height, LMaxBottom + FSpacing.Y); // Add a data output pin for the resulting closure. - CreatePin(LLambdaNode, psCircle, paRight, cDataPinColor, 'Pin.Data.Out'); + CreateOutput(LLambdaNode, 0); // Update the current Y position of the main visitor to be below the now correctly sized lambda node. FCurrentPos.Y := LLambdaNode.Position.Y + LLambdaNode.Height + FSpacing.Y; @@ -599,10 +671,6 @@ begin end; function TAstToAuraNodeVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; -const - cExecPinColor = TAlphaColors.Lightgreen; - cDataPinColor = TAlphaColors.Dodgerblue; - cInitializerTreeWidth = 150; var LVarDeclNode, LInitializerNode: TAuraNode; LStartPosition: TPointF; @@ -619,20 +687,27 @@ begin LInitializerNode := FLastNode; LInitializerEndY := FCurrentPos.Y; - FCurrentPos.X := LStartPosition.X + cInitializerTreeWidth + FSpacing.X; + + // Position the VarDecl node dynamically to the right of the initializer's visual tree. + FCurrentPos.X := LInitializerNode.Position.X + LInitializerNode.Width + FSpacing.X; FCurrentPos.Y := LStartPosition.Y; end; LVarDeclNode := CreateNodeControl('VarDecl', Node.Identifier.Name); LVarDeclNodeEndY := FCurrentPos.Y; - CreatePin(LVarDeclNode, psTriangle, paLeft, cExecPinColor, 'Pin.Exec.In', -8); - CreatePin(LVarDeclNode, psTriangle, paRight, cExecPinColor, 'Pin.Exec.Out', -8); + CreateEntry(LVarDeclNode, -8); + CreateExit(LVarDeclNode, '', -8); if Assigned(Node.Initializer) then begin - LInputPin := CreatePin(LVarDeclNode, psCircle, paLeft, cDataPinColor, 'Pin.Data.In', 8); - ConnectPins(LInputPin, LInitializerNode, 'Pin.Data.Out'); + LInputPin := CreateInput(LVarDeclNode, LInitializerNode.Name, 8); + ConnectData(LInputPin, LInitializerNode); + + // Vertically center the VarDecl node relative to the initializer's visual tree. + var LInitializerHeight := LInitializerEndY - LStartPosition.Y; + LVarDeclNode.Position.Y := LStartPosition.Y + (LInitializerHeight / 2) - (LVarDeclNode.Height / 2); + LVarDeclNodeEndY := LVarDeclNode.Position.Y + LVarDeclNode.Height + FSpacing.Y; FCurrentPos.Y := Max(LInitializerEndY, LVarDeclNodeEndY); FCurrentPos.X := LStartPosition.X; @@ -693,7 +768,7 @@ begin LPath := TPathData.Create; try LDeltaX := LEndPoint.X - LStartPoint.X; - LControlOffset := Max(50, Abs(LDeltaX) / 2); + LControlOffset := Abs(LDeltaX) / 2; LControlPoint1 := TPointF.Create(LStartPoint.X + LControlOffset, LStartPoint.Y); LControlPoint2 := TPointF.Create(LEndPoint.X - LControlOffset, LEndPoint.Y); diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index ec362cf..1ea3340 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -273,9 +273,9 @@ begin end; if boolResult then - Result := TAstValue.FromScalar(TScalar.FromInt64(1)) + Result := TAstValue.FromScalar(TScalar.FromBoolean(true)) else - Result := TAstValue.FromScalar(TScalar.FromInt64(0)); + Result := TAstValue.FromScalar(TScalar.FromBoolean(false)); end; function TEvaluatorVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue; diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index b900dfd..9de58c1 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -313,7 +313,7 @@ begin case Self of boAdd: Result := '+'; boSubtract: Result := '-'; - boMultiply: Result := '*'; + boMultiply: Result := #$2A2F; boDivide: Result := '/'; boEqual: Result := '=='; boNotEqual: Result := '!=';