diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index fe3c821..f73759e 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -800,7 +800,8 @@ begin [ TAst.VarDecl( TAst.Identifier('closeColumn'), - TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close')) + TAst.FunctionCall(TAst.Keyword('Close'), [TAst.Identifier('ohlcvSeries')]) + // TAst.MemberAccess(TAst.Identifier('ohlcvSeries'), TAst.Identifier('Close')) ), TAst.Indexer(TAst.Identifier('closeColumn'), TAst.Constant(1)) ] @@ -1319,7 +1320,7 @@ end; procedure TForm1.UpdateScript; begin - PrintScript(FCurrAst); + PrintScript(FCurrUnboundAst); FWorkspace.DeleteChildren; ShowVizualization(14, 14); diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas index 4399689..75e51bb 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas @@ -64,6 +64,7 @@ type // Visitor implementations for each AST node type. function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -80,6 +81,7 @@ type function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): 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; @@ -242,6 +244,17 @@ type property Node: IIdentifierNode read FNode; end; + TAuraKeywordNode = class(TAuraNode) + private + FNode: IKeywordNode; + protected + procedure SetupNode; override; + public + constructor Create(const AVisualizer: IAstVisualizer; const ANode: IKeywordNode); + function CreateAst: IAstNode; override; + property Node: IKeywordNode read FNode; + end; + TAuraBinaryExpressionNode = class(TAuraNode) private FNode: IBinaryExpressionNode; @@ -462,6 +475,19 @@ type property Node: IMemberAccessNode read FNode; end; + TAuraRecordLiteralNode = class(TAuraNode) + private + FNode: IRecordLiteralNode; + FFieldNodes: TDictionary; // Map KeyName -> ValueNode + protected + procedure SetupNode; override; + public + constructor Create(const AVisualizer: IAstVisualizer; const ANode: IRecordLiteralNode); + destructor Destroy; override; + function CreateAst: IAstNode; override; + property Node: IRecordLiteralNode read FNode; + end; + TAuraCreateSeriesNode = class(TAuraNode) private FNode: ICreateSeriesNode; @@ -499,6 +525,9 @@ type implementation +uses + Myc.Data.Keyword; + { TAstVisualizer } constructor TAstVisualizer.Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; AExprDepth: Integer); @@ -588,6 +617,11 @@ begin Result := TDataValue.FromGeneric(TAuraIdentifierNode.Create(Self, Node)); end; +function TAstVisualizer.VisitKeyword(const Node: IKeywordNode): TDataValue; +begin + Result := TDataValue.FromGeneric(TAuraKeywordNode.Create(Self, Node)); +end; + function TAstVisualizer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; begin Result := TDataValue.FromGeneric(TAuraBinaryExpressionNode.Create(Self, Node)); @@ -643,6 +677,11 @@ begin Result := TDataValue.FromGeneric(TAuraMemberAccessNode.Create(Self, Node)); end; +function TAstVisualizer.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +begin + Result := TDataValue.FromGeneric(TAuraRecordLiteralNode.Create(Self, Node)); +end; + function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; begin Result := TDataValue.FromGeneric(TAuraQuasiquoteNode.Create(Self, Node)); @@ -1201,6 +1240,32 @@ begin end; end; +{ TAuraKeywordNode } + +constructor TAuraKeywordNode.Create(const AVisualizer: IAstVisualizer; const ANode: IKeywordNode); +begin + inherited Create(AVisualizer); + FNode := ANode; +end; + +function TAuraKeywordNode.CreateAst: IAstNode; +begin + Result := FNode; // Keywords are literals, return original node +end; + +procedure TAuraKeywordNode.SetupNode; +begin + BeginUpdate; + try + Frameless := true; + var lbl := AddLabel(Self, ':' + FNode.Value.Name); + lbl.StyledSettings := lbl.StyledSettings - [TStyledSetting.FontColor]; + lbl.FontColor := TAlphaColors.Mediumvioletred; + finally + EndUpdate; + end; +end; + { TAuraBinaryExpressionNode } constructor TAuraBinaryExpressionNode.Create(const AVisualizer: IAstVisualizer; const ANode: IBinaryExpressionNode); @@ -1883,7 +1948,7 @@ begin leftBracket.Padding.Right := 0; leftBracket.Margins.Right := 0; - visu.CallAccept(FNode.Expression); + FExpressionNode := visu.CallAccept(FNode.Expression); var rightBracket := AddLabel(Self, '>'); rightBracket.Padding.Left := 0; @@ -1990,6 +2055,83 @@ begin end; end; +{ TAuraRecordLiteralNode } + +constructor TAuraRecordLiteralNode.Create(const AVisualizer: IAstVisualizer; const ANode: IRecordLiteralNode); +begin + inherited Create(AVisualizer); + FNode := ANode; + FFieldNodes := TDictionary.Create; +end; + +destructor TAuraRecordLiteralNode.Destroy; +begin + FFieldNodes.Free; + inherited Destroy; +end; + +function TAuraRecordLiteralNode.CreateAst: IAstNode; +var + fields: TArray; + i: Integer; + valueNode: TAuraNode; + pair: TPair; +begin + SetLength(fields, FFieldNodes.Count); + i := 0; + // Note: TDictionary iteration order is not guaranteed, + // but TScalarRecordDefinition is looked up by name, so order doesn't matter. + for pair in FFieldNodes do + begin + valueNode := pair.Value; + fields[i] := TRecordFieldLiteral.Create(pair.Key, valueNode.CreateAst); + inc(i); + end; + Result := TAst.RecordLiteral(fields); +end; + +procedure TAuraRecordLiteralNode.SetupNode; +var + field: TRecordFieldLiteral; + fieldContainer: TAutoFitControl; + valueNode: TAuraNode; + keyLabel: TLabel; +begin + BeginUpdate; + try + Frameless := False; + Orientation := loVertical; + Alignment := laFlush; // Align fields to the left + + if Length(FNode.Fields) = 0 then + begin + AddLabel(Self, '{}'); + end + else + begin + for field in FNode.Fields do + begin + // Create a horizontal container for the key-value pair + fieldContainer := AddContainer(Self, loHorizontal, laCenter); + + // Key label + keyLabel := AddLabel(fieldContainer, ':' + field.Name); + keyLabel.Font.Style := keyLabel.Font.Style + [TFontStyle.fsBold]; + keyLabel.StyledSettings := keyLabel.StyledSettings - [TStyledSetting.FontColor]; + keyLabel.FontColor := TAlphaColors.Mediumvioletred; + + // Value node + valueNode := FVisualizer.Clone(fieldContainer, FVisualizer.ExprDepth + 1).CallAccept(field.Value); + + // Store for CreateAst + FFieldNodes.Add(field.Name, valueNode); + end; + end; + finally + EndUpdate; + end; +end; + { TAuraCreateSeriesNode } constructor TAuraCreateSeriesNode.Create(const AVisualizer: IAstVisualizer; const ANode: ICreateSeriesNode); diff --git a/Src/AST/Myc.Ast.Binding.pas b/Src/AST/Myc.Ast.Binding.pas index b05a105..7f638f2 100644 --- a/Src/AST/Myc.Ast.Binding.pas +++ b/Src/AST/Myc.Ast.Binding.pas @@ -12,7 +12,7 @@ uses Myc.Ast.Visitor, Myc.Ast.Scope, Myc.Ast.Analyzer, - Myc.Ast.Types, // Added + Myc.Ast.Types, Myc.Ast; type @@ -35,6 +35,7 @@ type function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override; function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override; function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override; + function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override; public constructor Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc); class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode; @@ -70,6 +71,7 @@ type protected function Accept(const Node: IAstNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override; function VisitAssignment(const Node: IAssignmentNode): TDataValue; override; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override; @@ -82,10 +84,10 @@ type function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; - // Added for type inference function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override; function VisitIndexer(const Node: IIndexerNode): 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; @@ -151,11 +153,20 @@ type property IsTailCall: Boolean read FIsTailCall; end; + TBoundRecordLiteralNode = class(TRecordLiteralNode) + private + FDefinition: TScalarRecordDefinition; + public + constructor Create(const AFields: TArray; const ADef: TScalarRecordDefinition); + property Definition: TScalarRecordDefinition read FDefinition; + end; + implementation uses System.Generics.Defaults, - System.Character; + System.Character, + Myc.Data.Keyword; type TResolvedAddressComparer = class(TEqualityComparer) @@ -248,7 +259,7 @@ begin // externally evaluate the expression using the injected evaluator value := FEvaluate(expr); - if value.Kind in [vkScalar, vkText, vkVoid] then + if value.Kind in [vkScalar, vkText, vkVoid, vkKeyword] then Result := TDataValue.FromIntf(TAst.Constant(value)) else raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]); @@ -277,6 +288,13 @@ begin Result := TDataValue.FromIntf(TAst.Block(newExprs)); end; +function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +begin + // This node type does not support splicing, so we use the default + // TAstTransformer implementation which just transforms child values. + Result := inherited VisitRecordLiteral(Node); +end; + { TBoundIdentifierNode } constructor TBoundIdentifierNode.Create(const AUnboundNode: IIdentifierNode; const AAddress: TResolvedAddress); begin @@ -319,6 +337,13 @@ begin FIsTailCall := AIsTailCall; end; +{ TBoundRecordLiteralNode } +constructor TBoundRecordLiteralNode.Create(const AFields: TArray; const ADef: TScalarRecordDefinition); +begin + inherited Create(AFields); + FDefinition := ADef; +end; + { TResolvedAddressComparer } function TResolvedAddressComparer.Equals(const Left, Right: TResolvedAddress): Boolean; begin @@ -474,6 +499,31 @@ var args: TArray; i: Integer; begin + // --- Transformation: Keyword-as-Function --- + if (Node.Callee is TKeywordNode) then + begin + var keywordNode := (Node.Callee as TKeywordNode); + var keywordName := keywordNode.Value.Name; + + // 1. Validate argument count + if Length(Node.Arguments) <> 1 then + raise ETypeException + .CreateFmt('Keyword :%s expects exactly one argument (the record), but got %d', [keywordName, Length(Node.Arguments)]); + + // 2. Bind the base (the record) + FNextIsTail := False; // Accessing a member is not a tail call + var baseNode := Accept(Node.Arguments[0]).AsIntf; + + // 3. Create a synthetic IMemberAccessNode + var memberIdentifier := TAst.Identifier(keywordName); + var memberAccessNode := TAst.MemberAccess(baseNode, memberIdentifier); + + // 4. Re-bind the synthetic node by calling Accept (which dispatches to VisitMemberAccess) + // This ensures type checking and type inference for member access is done in one place. + Result := Accept(memberAccessNode); + exit; + end; + if (Node.Callee is TIdentifierNode) then begin calleeIdentifier := Node.Callee as TIdentifierNode; @@ -592,10 +642,10 @@ begin // Check argument types (param types are not yet inferred, so skip check for now) // for i := 0 to High(args) do // begin - // var argType := (args[i] as TAstNode).StaticType; - // var paramType := signature.ParamTypes[i]; - // if not TTypeRules.CanAssign(paramType, argType) then - // raise ETypeException.CreateFmt('Cannot assign argument %d (type %s) to parameter (type %s)', [i, argType.ToString, paramType.ToString]); + // var argType := (args[i] as TAstNode).StaticType; + // var paramType := signature.ParamTypes[i]; + // if not TTypeRules.CanAssign(paramType, argType) then + // raise ETypeException.CreateFmt('Cannot assign argument %d (type %s) to parameter (type %s)', [i, argType.ToString, paramType.ToString]); // end; boundCall := TBoundFunctionCallNode.Create(Node, callee, args, isTailCall); @@ -738,12 +788,19 @@ begin Result := SetType(TDataValue.FromIntf(Node), TTypes.FromScalarKind(Node.Value.AsScalar.Kind)); TDataValueKind.vkText: Result := SetType(TDataValue.FromIntf(Node), TTypes.Text); TDataValueKind.vkVoid: Result := SetType(TDataValue.FromIntf(Node), TTypes.Void); + TDataValueKind.vkKeyword: Result := SetType(TDataValue.FromIntf(Node), TTypes.Keyword); else // Handle other constant types if they become supported Result := SetType(TDataValue.FromIntf(Node), TTypes.Unknown); end; end; +function TAstBinder.VisitKeyword(const Node: IKeywordNode): TDataValue; +begin + // Keywords are literals. Their type is set in TKeywordNode.Create. + Result := TDataValue.FromIntf(Node); +end; + function TAstBinder.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; var elemType: IStaticType; @@ -892,6 +949,50 @@ begin Result := SetType(TDataValue.FromIntf(boundNode), elemType); end; +function TAstBinder.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +var + i: Integer; + boundFields: TArray; + defFields: TArray; + def: TScalarRecordDefinition; + staticType: IStaticType; + boundNode: IRecordLiteralNode; + valNode: IAstNode; + valType: IStaticType; +begin + FNextIsTail := False; + SetLength(boundFields, Length(Node.Fields)); + SetLength(defFields, Length(Node.Fields)); + + for i := 0 to High(Node.Fields) do + begin + valNode := Accept(Node.Fields[i].Value).AsIntf; + valType := (valNode as TAstNode).StaticType; + + // Records can only store scalar values + if not (valType.Kind in [stOrdinal, stFloat]) then + raise ETypeException.CreateFmt( + 'Record fields must be scalar (Ordinal or Float), but field "%s" is %s', + [Node.Fields[i].Name, valType.ToString]); + + boundFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, valNode); + + var scalarKind: TScalar.TKind; + if valType.Kind = stOrdinal then + scalarKind := TScalar.TKind.Ordinal + else + scalarKind := TScalar.TKind.Float; + + defFields[i] := TScalarRecordField.Create(Node.Fields[i].Name, scalarKind); + end; + + def := TScalarRecordDefinition.Create(defFields); + staticType := TTypes.CreateRecord(def); + boundNode := TBoundRecordLiteralNode.Create(boundFields, def); + + Result := SetType(TDataValue.FromIntf(boundNode), staticType); +end; + function TAstBinder.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; var i: integer; diff --git a/Src/AST/Myc.Ast.Debugger.pas b/Src/AST/Myc.Ast.Debugger.pas index 0f318c7..d419da3 100644 --- a/Src/AST/Myc.Ast.Debugger.pas +++ b/Src/AST/Myc.Ast.Debugger.pas @@ -35,6 +35,7 @@ type // The logging overrides for other Visit... methods function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -44,6 +45,7 @@ type 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; @@ -52,7 +54,8 @@ type implementation uses - System.TypInfo; + System.TypInfo, + Myc.Data.Keyword; { TDebugEvaluatorVisitor } @@ -156,8 +159,14 @@ end; function TDebugEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue; begin - AppendLine(Format('Constant (%s)', [Node.Value.ToString])); 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; @@ -238,6 +247,18 @@ begin 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.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; begin AppendLine('TernaryExpr{'); diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index e7a63e2..71515d1 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -31,6 +31,7 @@ type protected function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -48,6 +49,7 @@ type function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): 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; @@ -65,6 +67,7 @@ type implementation uses + Myc.Data.Keyword, Myc.Ast.Binding; // For TBound...Node classes { TAstDumper } @@ -144,6 +147,12 @@ begin Result := TDataValue.Void; end; +function TAstDumper.VisitKeyword(const Node: IKeywordNode): TDataValue; +begin + LogFmt('Keyword: :%s', [Node.Value.Name]); + Result := TDataValue.Void; +end; + function TAstDumper.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; begin LogFmt('BinaryExpression: %s', [Node.Operator.ToString]); @@ -432,6 +441,23 @@ begin Result := TDataValue.Void; end; +function TAstDumper.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +var + field: TRecordFieldLiteral; +begin + LogFmt('RecordLiteral (%d fields)', [Length(Node.Fields)]); + Indent; + for field in Node.Fields do + begin + LogFmt('Field :%s', [field.Name]); + Indent; + field.Value.Accept(Self); + Unindent; + end; + Unindent; + Result := TDataValue.Void; +end; + function TAstDumper.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; begin LogFmt('CreateSeries: %s', [Node.Definition]); diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 3b320c7..af81dc5 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -23,6 +23,7 @@ type protected function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -39,6 +40,7 @@ type 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; @@ -65,6 +67,7 @@ implementation uses System.TypInfo, System.Generics.Defaults, + Myc.Data.Keyword, Myc.Data.Decimal, Myc.Data.Series, Myc.Data.Scalar.JSON; @@ -363,6 +366,12 @@ begin Result := Node.Value; end; +function TEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue; +begin + // Return the shared, interned IKeyword interface as a TDataValue + Result := TDataValue.FromKeyword(Node.Value); +end; + function TEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; var def: string; @@ -441,6 +450,30 @@ begin end; end; +function TEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +var + boundNode: TBoundRecordLiteralNode; + values: TArray; + i: Integer; + valData: TDataValue; +begin + // The node must be a bound node from the binder + boundNode := Node as TBoundRecordLiteralNode; + SetLength(values, Length(boundNode.Fields)); + + for i := 0 to High(boundNode.Fields) do + begin + // Evaluate the value expression for this field + valData := boundNode.Fields[i].Value.Accept(Self); + // The binder already validated this is a scalar + values[i] := valData.AsScalar.Value; + end; + + // Create the TScalarRecord using the definition from the binder + var rec := TScalarRecord.Create(boundNode.Definition, values); + Result := TDataValue.FromRecord(rec); +end; + function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; var address: TResolvedAddress; @@ -473,22 +506,46 @@ end; function TEvaluatorVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; var leftValue, rightValue: TDataValue; + resScalar: TScalar; begin leftValue := Node.Left.Accept(Self); rightValue := Node.Right.Accept(Self); - if (leftValue.Kind <> vkScalar) or (rightValue.Kind <> vkScalar) then - raise ENotSupportedException.Create('Binary operations are only supported for scalar types.'); + // Handle Keyword equality + if (leftValue.Kind = vkKeyword) or (rightValue.Kind = vkKeyword) then + begin + if not (leftValue.Kind = rightValue.Kind) then + begin + // Comparing keyword to non-keyword + if Node.Operator = TScalar.TBinaryOp.NotEqual then + Result := TScalar.FromInt64(1) + else + Result := TScalar.FromInt64(0); + exit; + end; - var res: TScalar; - if not TScalar.TryBinaryOperation(Node.Operator, leftValue.AsScalar, rightValue.AsScalar, res) then + // Both are keywords, compare interfaces + case Node.Operator of + TScalar.TBinaryOp.Equal: Result := TScalar.FromInt64(Ord(leftValue.AsKeyword = rightValue.AsKeyword)); + TScalar.TBinaryOp.NotEqual: Result := TScalar.FromInt64(Ord(leftValue.AsKeyword <> rightValue.AsKeyword)); + else + raise ENotSupportedException.Create('Only = and <> operations are supported for Keywords.'); + end; + exit; + end; + + // Standard scalar operations + if (leftValue.Kind <> vkScalar) or (rightValue.Kind <> vkScalar) then + raise ENotSupportedException.Create('Binary operations are only supported for scalar or keyword types.'); + + if not TScalar.TryBinaryOperation(Node.Operator, leftValue.AsScalar, rightValue.AsScalar, resScalar) then raise ENotSupportedException.Create( 'Binary operation not supported for scalar types ' + leftValue.AsScalar.Kind.ToString + ' and ' + rightValue.AsScalar.Kind.ToString + ' .'); - Result := res; + Result := resScalar; end; function TEvaluatorVisitor.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas index 9562e89..a3071cb 100644 --- a/Src/AST/Myc.Ast.JSON.pas +++ b/Src/AST/Myc.Ast.JSON.pas @@ -29,6 +29,7 @@ type function JsonToConstantNode(const AObj: TJSONObject): IConstantNode; function JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode; + function JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode; function JsonToBinaryExprNode(const AObj: TJSONObject): IBinaryExpressionNode; function JsonToUnaryExprNode(const AObj: TJSONObject): IUnaryExpressionNode; function JsonToIfExprNode(const AObj: TJSONObject): IIfExpressionNode; @@ -46,6 +47,7 @@ type function JsonToAssignmentNode(const AObj: TJSONObject): IAssignmentNode; function JsonToIndexerNode(const AObj: TJSONObject): IIndexerNode; function JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode; + function JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode; function JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode; function JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode; function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode; @@ -53,6 +55,7 @@ type // IAstVisitor implementation for serialization function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -70,6 +73,7 @@ type 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; @@ -83,6 +87,9 @@ type implementation +uses + Myc.Data.Keyword; + { TJsonAstConverter } constructor TJsonAstConverter.Create; @@ -123,6 +130,7 @@ begin valObj.AddPair('Value', scalarObj); end; vkText: valObj.AddPair('Value', TJSONString.Create(AValue.AsText)); + vkKeyword: valObj.AddPair('Value', TJSONString.Create(AValue.AsKeyword.Name)); vkVoid:; // No value to add else raise ENotSupportedException.Create('Unsupported TDataValue kind for constant serialization.'); @@ -153,6 +161,17 @@ begin Result := TDataValue.Void; end; +function TJsonAstConverter.VisitKeyword(const Node: IKeywordNode): TDataValue; +var + obj: TJSONObject; +begin + obj := TJSONObject.Create; + obj.AddPair('NodeType', TJSONString.Create('Keyword')); + obj.AddPair('Name', TJSONString.Create(Node.Value.Name)); + FJsonObjectStack.Push(obj); + Result := TDataValue.Void; +end; + function TJsonAstConverter.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; var obj, leftObj, rightObj: TJSONObject; @@ -544,6 +563,31 @@ begin Result := TDataValue.Void; end; +function TJsonAstConverter.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +var + obj, fieldObj: TJSONObject; + fieldsArray: TJSONArray; + field: TRecordFieldLiteral; +begin + obj := TJSONObject.Create; + obj.AddPair('NodeType', TJSONString.Create('RecordLiteral')); + + fieldsArray := TJSONArray.Create; + for field in Node.Fields do + begin + field.Value.Accept(Self); // This pushes the value JSON onto the stack + + fieldObj := TJSONObject.Create; + fieldObj.AddPair('Name', TJSONString.Create(field.Name)); + fieldObj.AddPair('Value', FJsonObjectStack.Pop); + fieldsArray.Add(fieldObj); + end; + + obj.AddPair('Fields', fieldsArray); + FJsonObjectStack.Push(obj); + Result := TDataValue.Void; +end; + function TJsonAstConverter.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; var obj: TJSONObject; @@ -622,6 +666,10 @@ begin begin Result := valObj.GetValue('Value'); end + else if SameText(kindStr, 'Keyword') then + begin + Result := TDataValue.FromKeyword(TKeywordRegistry.Intern(valObj.GetValue('Value'))); + end else if SameText(kindStr, 'Void') then begin Result := TDataValue.Void; @@ -640,6 +688,11 @@ begin Result := TAst.Identifier(AObj.GetValue('Name')); end; +function TJsonAstConverter.JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode; +begin + Result := TAst.Keyword(AObj.GetValue('Name')); +end; + function TJsonAstConverter.JsonToBinaryExprNode(const AObj: TJSONObject): IBinaryExpressionNode; var opStr: string; @@ -850,6 +903,23 @@ begin Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToIdentifierNode(AObj.GetValue('Member') as TJSONObject)); end; +function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode; +var + fields: TArray; + fieldsArray: TJSONArray; + fieldObj: TJSONObject; + i: Integer; +begin + fieldsArray := AObj.GetValue('Fields'); + SetLength(fields, fieldsArray.Count); + for i := 0 to fieldsArray.Count - 1 do + begin + fieldObj := fieldsArray.Items[i] as TJSONObject; + fields[i] := TRecordFieldLiteral.Create(fieldObj.GetValue('Name'), JsonToNode(fieldObj.GetValue('Value'))); + end; + Result := TAst.RecordLiteral(fields); +end; + function TJsonAstConverter.JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode; begin Result := TAst.CreateSeries(AObj.GetValue('Definition')); @@ -891,6 +961,8 @@ begin Result := JsonToConstantNode(obj) else if nodeType = 'Identifier' then Result := JsonToIdentifierNode(obj) + else if nodeType = 'Keyword' then + Result := JsonToKeywordNode(obj) else if nodeType = 'BinaryExpr' then Result := JsonToBinaryExprNode(obj) else if nodeType = 'UnaryExpr' then @@ -925,6 +997,8 @@ begin Result := JsonToIndexerNode(obj) else if nodeType = 'MemberAccess' then Result := JsonToMemberAccessNode(obj) + else if nodeType = 'RecordLiteral' then + Result := JsonToRecordLiteralNode(obj) else if nodeType = 'CreateSeries' then Result := JsonToCreateSeriesNode(obj) else if nodeType = 'AddSeriesItem' then diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 0c8360f..2fff938 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -6,7 +6,8 @@ uses System.SysUtils, System.Generics.Collections, Myc.Data.Scalar, - Myc.Data.Value; + Myc.Data.Value, + Myc.Data.Keyword; type // --- Forward Declarations to break cycles --- @@ -14,6 +15,7 @@ type IAstNode = interface; IIdentifierNode = interface; IConstantNode = interface; + IKeywordNode = interface; IBinaryExpressionNode = interface; IUnaryExpressionNode = interface; IIfExpressionNode = interface; @@ -30,6 +32,7 @@ type IUnquoteSplicingNode = interface; IIndexerNode = interface; IMemberAccessNode = interface; + IRecordLiteralNode = interface; ICreateSeriesNode = interface; IAddSeriesItemNode = interface; ISeriesLengthNode = interface; @@ -50,9 +53,17 @@ type class operator Equal(const A: TResolvedAddress; B: TResolvedAddress): Boolean; end; + // A single field in a record literal + TRecordFieldLiteral = record + Name: string; // The field name (from keyword :name) + Value: IAstNode; + constructor Create(const AName: string; 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 VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; @@ -69,6 +80,7 @@ type 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; @@ -94,6 +106,15 @@ type property Name: string read GetName; end; + // Represents a keyword literal in the AST (e.g., :foo) + IKeywordNode = interface(IAstNode) + {$region 'private'} + function GetValue: IKeyword; + {$endregion} + // The interned keyword object + property Value: IKeyword read GetValue; + end; + IBinaryExpressionNode = interface(IAstNode) {$region 'private'} function GetLeft: IAstNode; @@ -249,6 +270,14 @@ type property Member: IIdentifierNode read GetMember; end; + // Represents a record literal, e.g., {:a 1 :b 2} + IRecordLiteralNode = interface(IAstNode) + {$region 'private'} + function GetFields: TArray; + {$endregion} + property Fields: TArray read GetFields; + end; + ICreateSeriesNode = interface(IAstNode) {$region 'private'} function GetDefinition: String; @@ -304,4 +333,12 @@ begin Dest.SlotIndex := -1; end; +{ TRecordFieldLiteral } + +constructor TRecordFieldLiteral.Create(const AName: string; const AValue: IAstNode); +begin + Name := AName; + Value := AValue; +end; + end. diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index a9a701b..63bb2b4 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -24,6 +24,7 @@ uses System.Math, Myc.Data.Scalar, Myc.Data.Value, + Myc.Data.Keyword, Myc.Ast; type @@ -35,11 +36,14 @@ type tkRightParen, // ) tkLeftBracket, // [ tkRightBracket, // ] + tkLeftBrace, // { + tkRightBrace, // } tkQuote, // ' tkBacktick, // ` tkTilde, // ~ tkAt, // @ tkIdentifier, + tkKeyword, tkNumber, tkString, tkEOF, @@ -82,6 +86,7 @@ type procedure Consume(AExpectedKind: TTokenKind); procedure NextToken; function ParseList: IAstNode; + function ParseRecordLiteral: IAstNode; function ParseParameterList: TArray; function ParseExpression: TExpr; public @@ -108,6 +113,7 @@ type function Execute(const RootNode: IAstNode): TDataValue; function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -124,6 +130,7 @@ type 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; @@ -158,7 +165,7 @@ var begin startPos := FCurrentPos; // Reader macro characters are now also delimiters for identifiers. - while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']', '''', '`', '~']))) do + while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']', '{', '}', '''', '`', '~']))) do Advance; Result := Copy(FSource, startPos, FCurrentPos - startPos); end; @@ -237,6 +244,16 @@ begin Result.Kind := tkRightBracket; Advance; end; + '{': + begin + Result.Kind := tkLeftBrace; + Advance; + end; + '}': + begin + Result.Kind := tkRightBrace; + Advance; + end; '''': begin Result.Kind := tkQuote; @@ -262,6 +279,14 @@ begin Result.Kind := tkString; Result.Text := ReadString; end; + ':': + begin + Advance; // Skip : + Result.Kind := tkKeyword; + Result.Text := ReadIdentifier; + if Result.Text.IsEmpty then + Result.Kind := tkError; + end; else if c.IsDigit or ((c = '-') and (FCurrentPos < Length(FSource)) and FSource[FCurrentPos + 1].IsDigit) then begin @@ -326,6 +351,41 @@ begin Consume(tkRightBracket); end; +function TParser.ParseRecordLiteral: IAstNode; +var + fields: TList; + fieldName: string; + fieldValue: IAstNode; +begin + Consume(tkLeftBrace); + fields := TList.Create; + try + while FCurrentToken.Kind <> tkRightBrace do + begin + if FCurrentToken.Kind = tkEOF then + raise Exception.Create('Syntax Error: Unexpected end of file in record literal.'); + + // Key must be a keyword + if FCurrentToken.Kind <> tkKeyword then + raise Exception.Create('Syntax Error: Expected keyword (e.g., :key) as field name in record literal.'); + fieldName := FCurrentToken.Text; + NextToken; + + // Value can be any expression + if FCurrentToken.Kind = tkRightBrace then + raise Exception.Create('Syntax Error: Missing value for key ' + fieldName + ' in record literal.'); + fieldValue := ParseExpression.Node; + + fields.Add(TRecordFieldLiteral.Create(fieldName, fieldValue)); + end; + + Result := TAst.RecordLiteral(fields.ToArray); + finally + fields.Free; + end; + Consume(tkRightBrace); +end; + function TParser.ParseList: IAstNode; var @@ -504,6 +564,11 @@ begin Result.Node := TAst.Constant(FCurrentToken.Text); NextToken; end; + tkKeyword: + begin + Result.Node := TAst.Keyword(FCurrentToken.Text); + NextToken; + end; tkIdentifier: begin Result.Node := TAst.Identifier(FCurrentToken.Text); @@ -511,6 +576,7 @@ begin end; tkLeftParen: Result.Node := ParseList; tkLeftBracket: Result.Params := ParseParameterList; + tkLeftBrace: Result.Node := ParseRecordLiteral; tkEOF: {nop}; else raise Exception.CreateFmt('Syntax Error: Unexpected token %s', [FCurrentToken.Kind.ToString]); @@ -590,6 +656,12 @@ begin Result := TDataValue.Void; end; +function TPrettyPrintVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue; +begin + Append(':' + Node.Value.Name); + Result := TDataValue.Void; +end; + function TPrettyPrintVisitor.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; begin Append('(' + Node.Operator.ToString); @@ -829,6 +901,31 @@ begin Result := TDataValue.Void; end; +function TPrettyPrintVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +var + field: TRecordFieldLiteral; +begin + if Length(Node.Fields) = 0 then + begin + Append('{}'); + exit(TDataValue.Void); + end; + + Append('{'); + Indent; + for field in Node.Fields do + begin + NewLine; + Append(':' + field.Name); + Append(' '); + field.Value.Accept(Self); + end; + Unindent; + NewLine; + Append('}'); + Result := TDataValue.Void; +end; + function TPrettyPrintVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; begin Append(Format('(new-series "%s")', [Node.Definition])); @@ -896,10 +993,14 @@ begin tkRightParen: Result := ')'; tkLeftBracket: Result := '['; tkRightBracket: Result := ']'; + tkLeftBrace: Result := '{'; + tkRightBrace: Result := '}'; tkQuote: Result := ''''; tkBacktick: Result := '`'; tkTilde: Result := '~'; + tkAt: Result := '@'; tkIdentifier: Result := ''; + tkKeyword: Result := ''; tkNumber: Result := ''; tkString: Result := ''; tkEOF: Result := ''; diff --git a/Src/AST/Myc.Ast.Types.pas b/Src/AST/Myc.Ast.Types.pas index 6c3273b..428ea62 100644 --- a/Src/AST/Myc.Ast.Types.pas +++ b/Src/AST/Myc.Ast.Types.pas @@ -5,7 +5,8 @@ interface uses System.SysUtils, System.Generics.Collections, - Myc.Data.Scalar; + Myc.Data.Scalar, + Myc.Data.Keyword; type IStaticType = interface; @@ -17,6 +18,7 @@ type stOrdinal, // Int64 stFloat, // Double stText, + stKeyword, stMethod, stSeries, stRecord, @@ -77,6 +79,8 @@ type FFloat: IStaticType; class var FText: IStaticType; + class var + FKeyword: IStaticType; class constructor Create; public // Flyweight accessors for simple types @@ -85,6 +89,7 @@ type class property Ordinal: IStaticType read FOrdinal; class property Float: IStaticType read FFloat; class property Text: IStaticType read FText; + class property Keyword: IStaticType read FKeyword; // Factory functions for complex types class function CreateSeries(const AElementType: IStaticType): IStaticType; static; @@ -124,6 +129,7 @@ begin stOrdinal: Result := 'Ordinal'; stFloat: Result := 'Float'; stText: Result := 'Text'; + stKeyword: Result := 'Keyword'; stMethod: Result := 'Method'; stSeries: Result := 'Series'; stRecord: Result := 'Record'; @@ -411,6 +417,7 @@ begin FOrdinal := TSimpleStaticType.Create(stOrdinal); FFloat := TSimpleStaticType.Create(stFloat); FText := TSimpleStaticType.Create(stText); + FKeyword := TSimpleStaticType.Create(stKeyword); end; class function TTypes.CreateMethod(const AParamTypes: TArray; const AReturnType: IStaticType): IStaticType; @@ -541,12 +548,24 @@ begin Result := TTypes.Float; end; - TScalar.TBinaryOp.Equal, - TScalar.TBinaryOp.NotEqual, - TScalar.TBinaryOp.Less, - TScalar.TBinaryOp.Greater, - TScalar.TBinaryOp.LessOrEqual, - TScalar.TBinaryOp.GreaterOrEqual: + TScalar.TBinaryOp.Equal, TScalar.TBinaryOp.NotEqual: + begin + // Allow equality checks for Keywords + if (promotedKind = stKeyword) then + begin + Result := TTypes.Ordinal; + exit; + end; + + // Comparison requires Ordinal or Float, but *always* results in Ordinal (boolean) + if not (promotedKind in [stOrdinal, stFloat]) then + raise ETypeException.CreateFmt( + 'Comparison operator %s requires Ordinal, Float, or Keyword, but got %s after promotion', + [Op.ToString, promotedType.ToString]); + Result := TTypes.Ordinal; + end; + + TScalar.TBinaryOp.Less, TScalar.TBinaryOp.Greater, TScalar.TBinaryOp.LessOrEqual, TScalar.TBinaryOp.GreaterOrEqual: begin // Comparison requires Ordinal or Float, but *always* results in Ordinal (boolean) if not (promotedKind in [stOrdinal, stFloat]) then diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 6c7b831..fe0ccbf 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -15,6 +15,7 @@ type protected function VisitConstant(const Node: IConstantNode): TDataValue; virtual; abstract; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; virtual; abstract; + function VisitKeyword(const Node: IKeywordNode): TDataValue; virtual; abstract; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; virtual; abstract; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; virtual; abstract; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; virtual; abstract; @@ -31,6 +32,7 @@ type function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; virtual; abstract; function VisitIndexer(const Node: IIndexerNode): TDataValue; virtual; abstract; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; virtual; abstract; + function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; virtual; abstract; function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; virtual; abstract; function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; virtual; abstract; function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual; abstract; @@ -51,6 +53,7 @@ type // These virtual methods implement the default identity-transformation (cloning) behavior. function VisitConstant(const Node: IConstantNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; + function VisitKeyword(const Node: IKeywordNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; @@ -67,6 +70,7 @@ type function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): 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; @@ -126,6 +130,12 @@ begin Result := TDataValue.FromIntf(Node); end; +function TAstTransformer.VisitKeyword(const Node: IKeywordNode): TDataValue; +begin + // Keywords are interned and immutable + Result := TDataValue.FromIntf(Node); +end; + function TAstTransformer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; begin var left := Accept(Node.Left).AsIntf; @@ -316,6 +326,30 @@ begin Result := TDataValue.FromIntf(TAst.MemberAccess(base, member)); end; +function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; +var + i: Integer; + hasChanged: Boolean; + newFields: TArray; + newValue: IAstNode; +begin + hasChanged := False; + SetLength(newFields, Length(Node.Fields)); + + for i := 0 to High(Node.Fields) do + begin + newValue := Accept(Node.Fields[i].Value).AsIntf; + if newValue <> Node.Fields[i].Value then + hasChanged := True; + newFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, newValue); + end; + + if hasChanged then + Result := TDataValue.FromIntf(TAst.RecordLiteral(newFields)) + else + Result := TDataValue.FromIntf(Node); +end; + function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; begin Result := TDataValue.FromIntf(Node); diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 676a59b..cedb13e 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -9,6 +9,7 @@ uses System.Generics.Defaults, Myc.Data.Scalar, Myc.Data.Value, + Myc.Data.Keyword, Myc.Ast.Nodes, Myc.Ast.Types, Myc.Ast.Scope; @@ -31,6 +32,7 @@ type // --- Factory functions --- class function Constant(const AValue: TDataValue): IConstantNode; overload; static; class function Constant(const AValue: String): IConstantNode; overload; static; + class function Keyword(const AName: string): IKeywordNode; static; class function Identifier(AName: string): IIdentifierNode; static; class function BinaryExpr(ALeft: IAstNode; AOperator: TScalar.TBinaryOp; ARight: IAstNode): IBinaryExpressionNode; static; class function UnaryExpr(const AOperator: TScalar.TUnaryOp; const ARight: IAstNode): IUnaryExpressionNode; static; @@ -57,6 +59,7 @@ type class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated; class function Indexer(const ABase: IAstNode; const AIndex: IAstNode): IIndexerNode; static; class function MemberAccess(const ABase: IAstNode; const AMember: IIdentifierNode): IMemberAccessNode; static; + class function RecordLiteral(const AFields: TArray): IRecordLiteralNode; static; class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static; class function AddSeriesItem( const ASeries: IIdentifierNode; @@ -96,6 +99,16 @@ type property Name: string read FName; end; + TKeywordNode = class(TAstNode, IKeywordNode) + private + FValue: IKeyword; + function GetValue: IKeyword; + public + constructor Create(const AName: string); + function Accept(const Visitor: IAstVisitor): TDataValue; override; + property Value: IKeyword read FValue; + end; + TBinaryExpressionNode = class(TAstNode, IBinaryExpressionNode) private FLeft: IAstNode; @@ -287,6 +300,16 @@ type function Accept(const Visitor: IAstVisitor): TDataValue; override; end; + TRecordLiteralNode = class(TAstNode, IRecordLiteralNode) + private + FFields: TArray; + function GetFields: TArray; + public + constructor Create(const AFields: TArray); + function Accept(const Visitor: IAstVisitor): TDataValue; override; + property Fields: TArray read FFields; + end; + TCreateSeriesNode = class(TAstNode, ICreateSeriesNode) private FDefinition: String; @@ -367,6 +390,25 @@ begin Result := FName; end; +{ TKeywordNode } + +constructor TKeywordNode.Create(const AName: string); +begin + inherited Create; + FValue := TKeywordRegistry.Intern(AName); + StaticType := TTypes.Keyword; +end; + +function TKeywordNode.Accept(const Visitor: IAstVisitor): TDataValue; +begin + Result := Visitor.VisitKeyword(Self); +end; + +function TKeywordNode.GetValue: IKeyword; +begin + Result := FValue; +end; + { TBinaryExpressionNode } constructor TBinaryExpressionNode.Create(const ALeft: IAstNode; AOperator: TScalar.TBinaryOp; const ARight: IAstNode); @@ -768,6 +810,24 @@ begin Result := FMember; end; +{ TRecordLiteralNode } + +constructor TRecordLiteralNode.Create(const AFields: TArray); +begin + inherited Create; + FFields := AFields; +end; + +function TRecordLiteralNode.Accept(const Visitor: IAstVisitor): TDataValue; +begin + Result := Visitor.VisitRecordLiteral(Self); +end; + +function TRecordLiteralNode.GetFields: TArray; +begin + Result := FFields; +end; + { TCreateSeriesNode } constructor TCreateSeriesNode.Create(const ADefinition: String); @@ -930,6 +990,11 @@ begin Result := TIndexerNode.Create(ABase, AIndex); end; +class function TAst.Keyword(const AName: string): IKeywordNode; +begin + Result := TKeywordNode.Create(AName); +end; + class function TAst.LambdaExpr(const AParameters: TArray; const ABody: IAstNode): ILambdaExpressionNode; begin Result := TLambdaExpressionNode.Create(AParameters, ABody); @@ -965,6 +1030,11 @@ begin Result := TRecurNode.Create(args); end; +class function TAst.RecordLiteral(const AFields: TArray): IRecordLiteralNode; +begin + Result := TRecordLiteralNode.Create(AFields); +end; + class function TAst.SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; begin Result := TSeriesLengthNode.Create(ASeries);