This commit is contained in:
Michael Schimmel
2025-10-30 18:00:58 +01:00
parent dfe1f04333
commit 798aa08f02
12 changed files with 710 additions and 27 deletions
+109 -8
View File
@@ -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;
@@ -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<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;
+23 -2
View File
@@ -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{');
+26
View File
@@ -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]);
+62 -5
View File
@@ -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;
+74
View File
@@ -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
+38 -1
View File
@@ -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
View File
@@ -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>';
+26 -7
View File
@@ -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
+34
View File
@@ -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);
+70
View File
@@ -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);