Keywords
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<string, TAuraNode>; // 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<TAuraNode>(TAuraIdentifierNode.Create(Self, Node));
|
||||
end;
|
||||
|
||||
function TAstVisualizer.VisitKeyword(const Node: IKeywordNode): TDataValue;
|
||||
begin
|
||||
Result := TDataValue.FromGeneric<TAuraNode>(TAuraKeywordNode.Create(Self, Node));
|
||||
end;
|
||||
|
||||
function TAstVisualizer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
|
||||
begin
|
||||
Result := TDataValue.FromGeneric<TAuraNode>(TAuraBinaryExpressionNode.Create(Self, Node));
|
||||
@@ -643,6 +677,11 @@ begin
|
||||
Result := TDataValue.FromGeneric<TAuraNode>(TAuraMemberAccessNode.Create(Self, Node));
|
||||
end;
|
||||
|
||||
function TAstVisualizer.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
||||
begin
|
||||
Result := TDataValue.FromGeneric<TAuraNode>(TAuraRecordLiteralNode.Create(Self, Node));
|
||||
end;
|
||||
|
||||
function TAstVisualizer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue;
|
||||
begin
|
||||
Result := TDataValue.FromGeneric<TAuraNode>(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<string, TAuraNode>.Create;
|
||||
end;
|
||||
|
||||
destructor TAuraRecordLiteralNode.Destroy;
|
||||
begin
|
||||
FFieldNodes.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TAuraRecordLiteralNode.CreateAst: IAstNode;
|
||||
var
|
||||
fields: TArray<TRecordFieldLiteral>;
|
||||
i: Integer;
|
||||
valueNode: TAuraNode;
|
||||
pair: TPair<string, TAuraNode>;
|
||||
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);
|
||||
|
||||
+105
-4
@@ -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<TRecordFieldLiteral>; 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<TResolvedAddress>)
|
||||
@@ -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<IAstNode>(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<IBlockExpressionNode>(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<TRecordFieldLiteral>; 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<IAstNode>;
|
||||
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<IAstNode>;
|
||||
|
||||
// 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;
|
||||
@@ -738,12 +788,19 @@ begin
|
||||
Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.FromScalarKind(Node.Value.AsScalar.Kind));
|
||||
TDataValueKind.vkText: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Text);
|
||||
TDataValueKind.vkVoid: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Void);
|
||||
TDataValueKind.vkKeyword: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Keyword);
|
||||
else
|
||||
// Handle other constant types if they become supported
|
||||
Result := SetType(TDataValue.FromIntf<IConstantNode>(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<IKeywordNode>(Node);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
||||
var
|
||||
elemType: IStaticType;
|
||||
@@ -892,6 +949,50 @@ begin
|
||||
Result := SetType(TDataValue.FromIntf<IMemberAccessNode>(boundNode), elemType);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
||||
var
|
||||
i: Integer;
|
||||
boundFields: TArray<TRecordFieldLiteral>;
|
||||
defFields: TArray<TScalarRecordField>;
|
||||
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<IAstNode>;
|
||||
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<IRecordLiteralNode>(boundNode), staticType);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
|
||||
var
|
||||
i: integer;
|
||||
|
||||
@@ -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{');
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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<TScalar.TValue>;
|
||||
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;
|
||||
|
||||
@@ -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<string>('Value');
|
||||
end
|
||||
else if SameText(kindStr, 'Keyword') then
|
||||
begin
|
||||
Result := TDataValue.FromKeyword(TKeywordRegistry.Intern(valObj.GetValue<string>('Value')));
|
||||
end
|
||||
else if SameText(kindStr, 'Void') then
|
||||
begin
|
||||
Result := TDataValue.Void;
|
||||
@@ -640,6 +688,11 @@ begin
|
||||
Result := TAst.Identifier(AObj.GetValue<string>('Name'));
|
||||
end;
|
||||
|
||||
function TJsonAstConverter.JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode;
|
||||
begin
|
||||
Result := TAst.Keyword(AObj.GetValue<string>('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<TRecordFieldLiteral>;
|
||||
fieldsArray: TJSONArray;
|
||||
fieldObj: TJSONObject;
|
||||
i: Integer;
|
||||
begin
|
||||
fieldsArray := AObj.GetValue<TJSONArray>('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<string>('Name'), JsonToNode(fieldObj.GetValue('Value')));
|
||||
end;
|
||||
Result := TAst.RecordLiteral(fields);
|
||||
end;
|
||||
|
||||
function TJsonAstConverter.JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode;
|
||||
begin
|
||||
Result := TAst.CreateSeries(AObj.GetValue<string>('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
|
||||
|
||||
@@ -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<TRecordFieldLiteral>;
|
||||
{$endregion}
|
||||
property Fields: TArray<TRecordFieldLiteral> 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.
|
||||
|
||||
+102
-1
@@ -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<IIdentifierNode>;
|
||||
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<TRecordFieldLiteral>;
|
||||
fieldName: string;
|
||||
fieldValue: IAstNode;
|
||||
begin
|
||||
Consume(tkLeftBrace);
|
||||
fields := TList<TRecordFieldLiteral>.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 := '<identifier>';
|
||||
tkKeyword: Result := '<keyword>';
|
||||
tkNumber: Result := '<number>';
|
||||
tkString: Result := '<string>';
|
||||
tkEOF: Result := '<EOF>';
|
||||
|
||||
@@ -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<IStaticType>; 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
|
||||
|
||||
@@ -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<IIdentifierNode>(Node);
|
||||
end;
|
||||
|
||||
function TAstTransformer.VisitKeyword(const Node: IKeywordNode): TDataValue;
|
||||
begin
|
||||
// Keywords are interned and immutable
|
||||
Result := TDataValue.FromIntf<IKeywordNode>(Node);
|
||||
end;
|
||||
|
||||
function TAstTransformer.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue;
|
||||
begin
|
||||
var left := Accept(Node.Left).AsIntf<IAstNode>;
|
||||
@@ -316,6 +326,30 @@ begin
|
||||
Result := TDataValue.FromIntf<IMemberAccessNode>(TAst.MemberAccess(base, member));
|
||||
end;
|
||||
|
||||
function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
||||
var
|
||||
i: Integer;
|
||||
hasChanged: Boolean;
|
||||
newFields: TArray<TRecordFieldLiteral>;
|
||||
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<IAstNode>;
|
||||
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<IRecordLiteralNode>(TAst.RecordLiteral(newFields))
|
||||
else
|
||||
Result := TDataValue.FromIntf<IRecordLiteralNode>(Node);
|
||||
end;
|
||||
|
||||
function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
||||
begin
|
||||
Result := TDataValue.FromIntf<ICreateSeriesNode>(Node);
|
||||
|
||||
@@ -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<TRecordFieldLiteral>): 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<TRecordFieldLiteral>;
|
||||
function GetFields: TArray<TRecordFieldLiteral>;
|
||||
public
|
||||
constructor Create(const AFields: TArray<TRecordFieldLiteral>);
|
||||
function Accept(const Visitor: IAstVisitor): TDataValue; override;
|
||||
property Fields: TArray<TRecordFieldLiteral> 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<TRecordFieldLiteral>);
|
||||
begin
|
||||
inherited Create;
|
||||
FFields := AFields;
|
||||
end;
|
||||
|
||||
function TRecordLiteralNode.Accept(const Visitor: IAstVisitor): TDataValue;
|
||||
begin
|
||||
Result := Visitor.VisitRecordLiteral(Self);
|
||||
end;
|
||||
|
||||
function TRecordLiteralNode.GetFields: TArray<TRecordFieldLiteral>;
|
||||
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<IIdentifierNode>; 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<TRecordFieldLiteral>): IRecordLiteralNode;
|
||||
begin
|
||||
Result := TRecordLiteralNode.Create(AFields);
|
||||
end;
|
||||
|
||||
class function TAst.SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode;
|
||||
begin
|
||||
Result := TSeriesLengthNode.Create(ASeries);
|
||||
|
||||
Reference in New Issue
Block a user