This commit is contained in:
Michael Schimmel
2025-10-30 20:27:44 +01:00
parent 798aa08f02
commit 0526ec8a24
17 changed files with 193 additions and 137 deletions
+2 -1
View File
@@ -694,6 +694,7 @@ begin
result := ExecuteAst(root, fibScope); result := ExecuteAst(root, fibScope);
sw.Stop; sw.Stop;
Memo1.Lines.Add(Format('Result: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); Memo1.Lines.Add(Format('Result: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
UpdateScript; UpdateScript;
end; end;
@@ -920,7 +921,7 @@ begin
[ [
TAst.VarDecl( TAst.VarDecl(
TAst.Identifier('closeSeries'), TAst.Identifier('closeSeries'),
TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Identifier('Close')) TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Keyword('Close'))
), ),
TAst.VarDecl( TAst.VarDecl(
TAst.Identifier('currentClose'), TAst.Identifier('currentClose'),
+9 -11
View File
@@ -18,6 +18,7 @@ uses
FMX.StdCtrls, FMX.StdCtrls,
Myc.Data.Scalar, Myc.Data.Scalar,
Myc.Data.Value, Myc.Data.Value,
Myc.Data.Keyword,
Myc.Ast.Nodes, Myc.Ast.Nodes,
Myc.Ast.Visitor, Myc.Ast.Visitor,
Myc.Ast, Myc.Ast,
@@ -478,7 +479,7 @@ type
TAuraRecordLiteralNode = class(TAuraNode) TAuraRecordLiteralNode = class(TAuraNode)
private private
FNode: IRecordLiteralNode; FNode: IRecordLiteralNode;
FFieldNodes: TDictionary<string, TAuraNode>; // Map KeyName -> ValueNode FFieldNodes: TDictionary<IKeyword, TAuraNode>; // Map KeyName -> ValueNode
protected protected
procedure SetupNode; override; procedure SetupNode; override;
public public
@@ -525,9 +526,6 @@ type
implementation implementation
uses
Myc.Data.Keyword;
{ TAstVisualizer } { TAstVisualizer }
constructor TAstVisualizer.Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; AExprDepth: Integer); constructor TAstVisualizer.Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; AExprDepth: Integer);
@@ -2032,7 +2030,7 @@ end;
function TAuraMemberAccessNode.CreateAst: IAstNode; function TAuraMemberAccessNode.CreateAst: IAstNode;
begin begin
// Re-create identifier from FNode // Re-create identifier from FNode
Result := TAst.MemberAccess(FBaseNode.CreateAst, TAst.Identifier(FNode.Member.Name)); Result := TAst.MemberAccess(FBaseNode.CreateAst, TAst.Keyword(FNode.Member.Value.Name));
end; end;
procedure TAuraMemberAccessNode.SetupNode; procedure TAuraMemberAccessNode.SetupNode;
@@ -2049,7 +2047,7 @@ begin
AddLabel(Self, '.'); AddLabel(Self, '.');
// Add member name as a label // Add member name as a label
AddLabel(Self, FNode.Member.Name); AddLabel(Self, FNode.Member.Value.Name);
finally finally
EndUpdate; EndUpdate;
end; end;
@@ -2061,7 +2059,7 @@ constructor TAuraRecordLiteralNode.Create(const AVisualizer: IAstVisualizer; con
begin begin
inherited Create(AVisualizer); inherited Create(AVisualizer);
FNode := ANode; FNode := ANode;
FFieldNodes := TDictionary<string, TAuraNode>.Create; FFieldNodes := TDictionary<IKeyword, TAuraNode>.Create;
end; end;
destructor TAuraRecordLiteralNode.Destroy; destructor TAuraRecordLiteralNode.Destroy;
@@ -2075,7 +2073,7 @@ var
fields: TArray<TRecordFieldLiteral>; fields: TArray<TRecordFieldLiteral>;
i: Integer; i: Integer;
valueNode: TAuraNode; valueNode: TAuraNode;
pair: TPair<string, TAuraNode>; pair: TPair<IKeyword, TAuraNode>;
begin begin
SetLength(fields, FFieldNodes.Count); SetLength(fields, FFieldNodes.Count);
i := 0; i := 0;
@@ -2084,7 +2082,7 @@ begin
for pair in FFieldNodes do for pair in FFieldNodes do
begin begin
valueNode := pair.Value; valueNode := pair.Value;
fields[i] := TRecordFieldLiteral.Create(pair.Key, valueNode.CreateAst); fields[i] := TRecordFieldLiteral.Create(TKeywordNode.Create(pair.Key), valueNode.CreateAst);
inc(i); inc(i);
end; end;
Result := TAst.RecordLiteral(fields); Result := TAst.RecordLiteral(fields);
@@ -2115,7 +2113,7 @@ begin
fieldContainer := AddContainer(Self, loHorizontal, laCenter); fieldContainer := AddContainer(Self, loHorizontal, laCenter);
// Key label // Key label
keyLabel := AddLabel(fieldContainer, ':' + field.Name); keyLabel := AddLabel(fieldContainer, ':' + field.Key.Value.Name);
keyLabel.Font.Style := keyLabel.Font.Style + [TFontStyle.fsBold]; keyLabel.Font.Style := keyLabel.Font.Style + [TFontStyle.fsBold];
keyLabel.StyledSettings := keyLabel.StyledSettings - [TStyledSetting.FontColor]; keyLabel.StyledSettings := keyLabel.StyledSettings - [TStyledSetting.FontColor];
keyLabel.FontColor := TAlphaColors.Mediumvioletred; keyLabel.FontColor := TAlphaColors.Mediumvioletred;
@@ -2124,7 +2122,7 @@ begin
valueNode := FVisualizer.Clone(fieldContainer, FVisualizer.ExprDepth + 1).CallAccept(field.Value); valueNode := FVisualizer.Clone(fieldContainer, FVisualizer.ExprDepth + 1).CallAccept(field.Value);
// Store for CreateAst // Store for CreateAst
FFieldNodes.Add(field.Name, valueNode); FFieldNodes.Add(field.Key.Value, valueNode);
end; end;
end; end;
finally finally
+1 -1
View File
@@ -209,7 +209,7 @@ var
baseStr: string; baseStr: string;
begin begin
baseStr := Node.Base.Accept(Self).AsText; baseStr := Node.Base.Accept(Self).AsText;
Result := Format('%s.%s', [baseStr, Node.Member.Name]); Result := Format('%s.%s', [baseStr, Node.Member.Value.Name]);
end; end;
function TAstToTextVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; function TAstToTextVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue;
+30 -19
View File
@@ -259,8 +259,15 @@ begin
// externally evaluate the expression using the injected evaluator // externally evaluate the expression using the injected evaluator
value := FEvaluate(expr); value := FEvaluate(expr);
// Allow unquoting keywords
if value.Kind in [vkScalar, vkText, vkVoid, vkKeyword] then if value.Kind in [vkScalar, vkText, vkVoid, vkKeyword] then
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value)) begin
// vkKeyword needs to be handled differently than other constants
if value.Kind = vkKeyword then
Result := TDataValue.FromIntf<IAstNode>(TAst.Keyword(value.AsKeyword.Name))
else
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value));
end
else else
raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]); raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]);
end; end;
@@ -290,8 +297,8 @@ end;
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
begin begin
// This node type does not support splicing, so we use the default // Record literals do not support splicing.
// TAstTransformer implementation which just transforms child values. // We just use the default TAstTransformer implementation which visits child values.
Result := inherited VisitRecordLiteral(Node); Result := inherited VisitRecordLiteral(Node);
end; end;
@@ -500,6 +507,7 @@ var
i: Integer; i: Integer;
begin begin
// --- Transformation: Keyword-as-Function --- // --- Transformation: Keyword-as-Function ---
// Check if the callee is a keyword literal
if (Node.Callee is TKeywordNode) then if (Node.Callee is TKeywordNode) then
begin begin
var keywordNode := (Node.Callee as TKeywordNode); var keywordNode := (Node.Callee as TKeywordNode);
@@ -508,18 +516,17 @@ begin
// 1. Validate argument count // 1. Validate argument count
if Length(Node.Arguments) <> 1 then if Length(Node.Arguments) <> 1 then
raise ETypeException raise ETypeException
.CreateFmt('Keyword :%s expects exactly one argument (the record), but got %d', [keywordName, Length(Node.Arguments)]); .CreateFmt('Keyword :%s expects exactly one argument (the record/map), but got %d', [keywordName, Length(Node.Arguments)]);
// 2. Bind the base (the record) // 2. Bind the base (the record/map)
FNextIsTail := False; // Accessing a member is not a tail call FNextIsTail := False; // Accessing a member is not a tail call
var baseNode := Accept(Node.Arguments[0]).AsIntf<IAstNode>; var baseNode := Accept(Node.Arguments[0]).AsIntf<IAstNode>;
// 3. Create a synthetic IMemberAccessNode // 3. Create a synthetic IMemberAccessNode
var memberIdentifier := TAst.Identifier(keywordName); var memberAccessNode := TAst.MemberAccess(baseNode, TAst.Keyword(keywordName));
var memberAccessNode := TAst.MemberAccess(baseNode, memberIdentifier);
// 4. Re-bind the synthetic node by calling Accept (which dispatches to VisitMemberAccess) // 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. // This ensures type checking and type inference for member access is centralized.
Result := Accept(memberAccessNode); Result := Accept(memberAccessNode);
exit; exit;
end; end;
@@ -925,19 +932,19 @@ var
begin begin
baseNode := Accept(Node.Base).AsIntf<IAstNode>; baseNode := Accept(Node.Base).AsIntf<IAstNode>;
baseType := (baseNode as TAstNode).StaticType; baseType := (baseNode as TAstNode).StaticType;
var memberName := Node.Member.Name;
elemType := TTypes.Unknown; elemType := TTypes.Unknown;
if (baseType.Kind <> TStaticTypeKind.stUnknown) then if (baseType.Kind <> TStaticTypeKind.stUnknown) then
begin begin
if (baseType.Kind <> TStaticTypeKind.stRecord) and (baseType.Kind <> TStaticTypeKind.stRecordSeries) then if (baseType.Kind <> TStaticTypeKind.stRecord) and (baseType.Kind <> TStaticTypeKind.stRecordSeries) then
raise ETypeException.CreateFmt('Member access `.` requires a record or record series, but got %s', [baseType.ToString]); raise ETypeException.CreateFmt('Member access requires a record or record series, but got %s', [baseType.ToString]);
fieldIndex := baseType.Definition.IndexOf(memberName); // Use IndexOf(string) which correctly delegates to IndexOf(IKeyword)
fieldIndex := baseType.Definition.IndexOf(Node.Member.Value);
if fieldIndex < 0 then if fieldIndex < 0 then
raise ETypeException.CreateFmt('Member "%s" not found in type %s', [memberName, baseType.ToString]); raise ETypeException.CreateFmt('Member "%s" not found in type %s', [Node.Member.Value.Name, baseType.ToString]);
var fieldType := TTypes.FromScalarKind(baseType.Definition.Fields[fieldIndex].Kind); var fieldType := TTypes.FromScalarKind(baseType.Definition.Fields[fieldIndex].Value);
if baseType.Kind = TStaticTypeKind.stRecord then if baseType.Kind = TStaticTypeKind.stRecord then
elemType := fieldType elemType := fieldType
@@ -953,7 +960,7 @@ function TAstBinder.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataVal
var var
i: Integer; i: Integer;
boundFields: TArray<TRecordFieldLiteral>; boundFields: TArray<TRecordFieldLiteral>;
defFields: TArray<TScalarRecordField>; defFields: TArray<TScalarRecordDefinition.TField>;
def: TScalarRecordDefinition; def: TScalarRecordDefinition;
staticType: IStaticType; staticType: IStaticType;
boundNode: IRecordLiteralNode; boundNode: IRecordLiteralNode;
@@ -964,18 +971,21 @@ begin
SetLength(boundFields, Length(Node.Fields)); SetLength(boundFields, Length(Node.Fields));
SetLength(defFields, Length(Node.Fields)); SetLength(defFields, Length(Node.Fields));
// We assume this is a TScalarRecord (Path A) until proven otherwise.
// The "dual path" logic for stDictionary is not yet implemented.
for i := 0 to High(Node.Fields) do for i := 0 to High(Node.Fields) do
begin begin
valNode := Accept(Node.Fields[i].Value).AsIntf<IAstNode>; valNode := Accept(Node.Fields[i].Value).AsIntf<IAstNode>;
valType := (valNode as TAstNode).StaticType; valType := (valNode as TAstNode).StaticType;
// Records can only store scalar values // Path A: Records can only store scalar values
if not (valType.Kind in [stOrdinal, stFloat]) then if not (valType.Kind in [stOrdinal, stFloat]) then
raise ETypeException.CreateFmt( raise ETypeException.CreateFmt(
'Record fields must be scalar (Ordinal or Float), but field "%s" is %s', 'Record fields must be scalar (Ordinal or Float), but field ":%s" is %s',
[Node.Fields[i].Name, valType.ToString]); [Node.Fields[i].Key.Value.Name, valType.ToString]);
boundFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, valNode); boundFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Key, valNode);
var scalarKind: TScalar.TKind; var scalarKind: TScalar.TKind;
if valType.Kind = stOrdinal then if valType.Kind = stOrdinal then
@@ -983,7 +993,8 @@ begin
else else
scalarKind := TScalar.TKind.Float; scalarKind := TScalar.TKind.Float;
defFields[i] := TScalarRecordField.Create(Node.Fields[i].Name, scalarKind); // Create the definition field using the Keyword's name
defFields[i] := TScalarRecordField.Create(Node.Fields[i].Key.Value, scalarKind);
end; end;
def := TScalarRecordDefinition.Create(defFields); def := TScalarRecordDefinition.Create(defFields);
+1 -1
View File
@@ -237,7 +237,7 @@ end;
function TDebugEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; function TDebugEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
begin begin
AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Name])); AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Value.Name]));
Indent; Indent;
try try
Result := inherited VisitMemberAccess(Node); Result := inherited VisitMemberAccess(Node);
+1 -1
View File
@@ -449,7 +449,7 @@ begin
Indent; Indent;
for field in Node.Fields do for field in Node.Fields do
begin begin
LogFmt('Field :%s', [field.Name]); LogFmt('Field :%s', [field.Key.Value.Name]);
Indent; Indent;
field.Value.Accept(Self); field.Value.Accept(Self);
Unindent; Unindent;
+2 -4
View File
@@ -437,14 +437,12 @@ end;
function TEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; function TEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
var var
baseValue: TDataValue; baseValue: TDataValue;
memberName: string;
begin begin
baseValue := Node.Base.Accept(Self); baseValue := Node.Base.Accept(Self);
memberName := Node.Member.Name;
case baseValue.Kind of case baseValue.Kind of
vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.CreateMemberSeries(memberName)); vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.CreateMemberSeries(Node.Member.Value));
vkRecord: Result := baseValue.AsRecord.Items[memberName]; vkRecord: Result := baseValue.AsRecord[Node.Member.Value];
else else
raise EArgumentException.Create('Member access operator `.` is not supported for this value type.'); raise EArgumentException.Create('Member access operator `.` is not supported for this value type.');
end; end;
+7 -3
View File
@@ -578,7 +578,7 @@ begin
field.Value.Accept(Self); // This pushes the value JSON onto the stack field.Value.Accept(Self); // This pushes the value JSON onto the stack
fieldObj := TJSONObject.Create; fieldObj := TJSONObject.Create;
fieldObj.AddPair('Name', TJSONString.Create(field.Name)); fieldObj.AddPair('Name', TJSONString.Create(field.Key.Value.Name));
fieldObj.AddPair('Value', FJsonObjectStack.Pop); fieldObj.AddPair('Value', FJsonObjectStack.Pop);
fieldsArray.Add(fieldObj); fieldsArray.Add(fieldObj);
end; end;
@@ -900,7 +900,7 @@ end;
function TJsonAstConverter.JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode; function TJsonAstConverter.JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode;
begin begin
Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToIdentifierNode(AObj.GetValue('Member') as TJSONObject)); Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToKeywordNode(AObj.GetValue('Member') as TJSONObject));
end; end;
function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode; function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode;
@@ -915,7 +915,11 @@ begin
for i := 0 to fieldsArray.Count - 1 do for i := 0 to fieldsArray.Count - 1 do
begin begin
fieldObj := fieldsArray.Items[i] as TJSONObject; fieldObj := fieldsArray.Items[i] as TJSONObject;
fields[i] := TRecordFieldLiteral.Create(fieldObj.GetValue<string>('Name'), JsonToNode(fieldObj.GetValue('Value'))); fields[i] :=
TRecordFieldLiteral.Create(
TKeywordNode.Create(TKeywordRegistry.Intern(fieldObj.GetValue<string>('Name'))),
JsonToNode(fieldObj.GetValue('Value'))
);
end; end;
Result := TAst.RecordLiteral(fields); Result := TAst.RecordLiteral(fields);
end; end;
+6 -6
View File
@@ -55,9 +55,9 @@ type
// A single field in a record literal // A single field in a record literal
TRecordFieldLiteral = record TRecordFieldLiteral = record
Name: string; // The field name (from keyword :name) Key: IKeywordNode;
Value: IAstNode; Value: IAstNode;
constructor Create(const AName: string; const AValue: IAstNode); constructor Create(const AKey: IKeywordNode; const AValue: IAstNode);
end; end;
IAstVisitor = interface IAstVisitor = interface
@@ -264,10 +264,10 @@ type
IMemberAccessNode = interface(IAstNode) IMemberAccessNode = interface(IAstNode)
{$region 'private'} {$region 'private'}
function GetBase: IAstNode; function GetBase: IAstNode;
function GetMember: IIdentifierNode; function GetMember: IKeywordNode;
{$endregion} {$endregion}
property Base: IAstNode read GetBase; property Base: IAstNode read GetBase;
property Member: IIdentifierNode read GetMember; property Member: IKeywordNode read GetMember;
end; end;
// Represents a record literal, e.g., {:a 1 :b 2} // Represents a record literal, e.g., {:a 1 :b 2}
@@ -335,9 +335,9 @@ end;
{ TRecordFieldLiteral } { TRecordFieldLiteral }
constructor TRecordFieldLiteral.Create(const AName: string; const AValue: IAstNode); constructor TRecordFieldLiteral.Create(const AKey: IKeywordNode; const AValue: IAstNode);
begin begin
Name := AName; Key := AKey;
Value := AValue; Value := AValue;
end; end;
+3 -3
View File
@@ -376,7 +376,7 @@ begin
raise Exception.Create('Syntax Error: Missing value for key ' + fieldName + ' in record literal.'); raise Exception.Create('Syntax Error: Missing value for key ' + fieldName + ' in record literal.');
fieldValue := ParseExpression.Node; fieldValue := ParseExpression.Node;
fields.Add(TRecordFieldLiteral.Create(fieldName, fieldValue)); fields.Add(TRecordFieldLiteral.Create(TKeywordNode.Create(TKeywordRegistry.Intern(fieldName)), fieldValue));
end; end;
Result := TAst.RecordLiteral(fields.ToArray); Result := TAst.RecordLiteral(fields.ToArray);
@@ -490,7 +490,7 @@ begin
else if SameText(head.Token.Text, 'get') then else if SameText(head.Token.Text, 'get') then
Result := TAst.Indexer(tailNodes[0], tailNodes[1]) Result := TAst.Indexer(tailNodes[0], tailNodes[1])
else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then
Result := TAst.MemberAccess(tailNodes[0], TAst.Identifier(head.Token.Text.Substring(1))); Result := TAst.MemberAccess(tailNodes[0], TAst.Keyword(head.Token.Text.Substring(1)));
end; end;
// If no special form matched, treat it as a generic function call. // If no special form matched, treat it as a generic function call.
@@ -916,7 +916,7 @@ begin
for field in Node.Fields do for field in Node.Fields do
begin begin
NewLine; NewLine;
Append(':' + field.Name); Append(':' + field.Key.Value.Name);
Append(' '); Append(' ');
field.Value.Accept(Self); field.Value.Accept(Self);
end; end;
+3 -2
View File
@@ -386,7 +386,8 @@ begin
for i := 0 to High(Self.FDefinition.Fields) do for i := 0 to High(Self.FDefinition.Fields) do
begin begin
if (Self.FDefinition.Fields[i].Name <> otherDef.Fields[i].Name) or (Self.FDefinition.Fields[i].Kind <> otherDef.Fields[i].Kind) then // Use fast interface comparison for Keys
if (Self.FDefinition.Fields[i].Key <> otherDef.Fields[i].Key) or (Self.FDefinition.Fields[i].Value <> otherDef.Fields[i].Value) then
exit(False); exit(False);
end; end;
@@ -400,7 +401,7 @@ begin
Result := GetKind.ToString + '{'; Result := GetKind.ToString + '{';
for i := 0 to High(FDefinition.Fields) do for i := 0 to High(FDefinition.Fields) do
begin begin
Result := Result + FDefinition.Fields[i].Name + ': ' + FDefinition.Fields[i].Kind.ToString; Result := Result + FDefinition.Fields[i].Key.Name + ': ' + FDefinition.Fields[i].Value.ToString;
if i < High(FDefinition.Fields) then if i < High(FDefinition.Fields) then
Result := Result + ', '; Result := Result + ', ';
end; end;
+7 -3
View File
@@ -319,7 +319,7 @@ end;
function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
begin begin
var base := Accept(Node.Base).AsIntf<IAstNode>; var base := Accept(Node.Base).AsIntf<IAstNode>;
var member := Node.Member; var member := Accept(Node.Member).AsIntf<IKeywordNode>;
if (base = Node.Base) and (member = Node.Member) then if (base = Node.Base) and (member = Node.Member) then
Result := TDataValue.FromIntf<IMemberAccessNode>(Node) Result := TDataValue.FromIntf<IMemberAccessNode>(Node)
else else
@@ -331,6 +331,7 @@ var
i: Integer; i: Integer;
hasChanged: Boolean; hasChanged: Boolean;
newFields: TArray<TRecordFieldLiteral>; newFields: TArray<TRecordFieldLiteral>;
newKey: IKeywordNode;
newValue: IAstNode; newValue: IAstNode;
begin begin
hasChanged := False; hasChanged := False;
@@ -338,10 +339,13 @@ begin
for i := 0 to High(Node.Fields) do for i := 0 to High(Node.Fields) do
begin begin
newKey := Accept(Node.Fields[i].Key).AsIntf<IKeywordNode>;
newValue := Accept(Node.Fields[i].Value).AsIntf<IAstNode>; newValue := Accept(Node.Fields[i].Value).AsIntf<IAstNode>;
if newValue <> Node.Fields[i].Value then
if (newKey <> Node.Fields[i].Key) or (newValue <> Node.Fields[i].Value) then
hasChanged := True; hasChanged := True;
newFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, newValue);
newFields[i] := TRecordFieldLiteral.Create(newKey, newValue);
end; end;
if hasChanged then if hasChanged then
+15 -13
View File
@@ -58,7 +58,7 @@ type
class function Assign(const AIdentifier: IIdentifierNode; const AValue: IAstNode): IAssignmentNode; static; class function Assign(const AIdentifier: IIdentifierNode; const AValue: IAstNode): IAssignmentNode; static;
class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated; class function AssignResult(const AValue: IAstNode): IAssignmentNode; static; deprecated;
class function Indexer(const ABase: IAstNode; const AIndex: IAstNode): IIndexerNode; static; class function Indexer(const ABase: IAstNode; const AIndex: IAstNode): IIndexerNode; static;
class function MemberAccess(const ABase: IAstNode; const AMember: IIdentifierNode): IMemberAccessNode; static; class function MemberAccess(const ABase: IAstNode; const AMember: IKeywordNode): IMemberAccessNode; static;
class function RecordLiteral(const AFields: TArray<TRecordFieldLiteral>): IRecordLiteralNode; static; class function RecordLiteral(const AFields: TArray<TRecordFieldLiteral>): IRecordLiteralNode; static;
class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static; class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static;
class function AddSeriesItem( class function AddSeriesItem(
@@ -104,7 +104,7 @@ type
FValue: IKeyword; FValue: IKeyword;
function GetValue: IKeyword; function GetValue: IKeyword;
public public
constructor Create(const AName: string); constructor Create(const AValue: IKeyword);
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
property Value: IKeyword read FValue; property Value: IKeyword read FValue;
end; end;
@@ -292,11 +292,11 @@ type
TMemberAccessNode = class(TAstNode, IMemberAccessNode) TMemberAccessNode = class(TAstNode, IMemberAccessNode)
private private
FBase: IAstNode; FBase: IAstNode;
FMember: IIdentifierNode; FMember: IKeywordNode;
function GetBase: IAstNode; function GetBase: IAstNode;
function GetMember: IIdentifierNode; function GetMember: IKeywordNode;
public public
constructor Create(const ABase: IAstNode; const AMember: IIdentifierNode); constructor Create(const ABase: IAstNode; const AMember: IKeywordNode);
function Accept(const Visitor: IAstVisitor): TDataValue; override; function Accept(const Visitor: IAstVisitor): TDataValue; override;
end; end;
@@ -349,14 +349,15 @@ constructor TConstantNode.Create(const AValue: TDataValue);
begin begin
inherited Create; inherited Create;
// Validate that only allowed constant kinds are used. // Validate that only allowed constant kinds are used.
if not (AValue.Kind in [vkScalar, vkText, vkVoid]) then if not (AValue.Kind in [vkScalar, vkText, vkVoid, vkKeyword]) then
raise EArgumentException.Create('IConstantNode only supports Scalar, Text, and Void values.'); raise EArgumentException.Create('IConstantNode only supports Scalar, Text, Void and Keyword values.');
FValue := AValue; FValue := AValue;
case AValue.Kind of case AValue.Kind of
vkScalar: StaticType := TTypes.FromScalarKind(AValue.AsScalar.Kind); vkScalar: StaticType := TTypes.FromScalarKind(AValue.AsScalar.Kind);
vkText: StaticType := TTypes.Text; vkText: StaticType := TTypes.Text;
vkVoid: StaticType := TTypes.Void; vkVoid: StaticType := TTypes.Void;
vkKeyword: StaticType := TTypes.Keyword;
else else
StaticType := TTypes.Unknown; // Should not happen due to validation above StaticType := TTypes.Unknown; // Should not happen due to validation above
end; end;
@@ -392,11 +393,12 @@ end;
{ TKeywordNode } { TKeywordNode }
constructor TKeywordNode.Create(const AName: string); constructor TKeywordNode.Create(const AValue: IKeyword);
begin begin
inherited Create; inherited Create;
FValue := TKeywordRegistry.Intern(AName); FValue := AValue;
StaticType := TTypes.Keyword; StaticType := TTypes.Keyword;
FValue := AValue;
end; end;
function TKeywordNode.Accept(const Visitor: IAstVisitor): TDataValue; function TKeywordNode.Accept(const Visitor: IAstVisitor): TDataValue;
@@ -788,7 +790,7 @@ end;
{ TMemberAccessNode } { TMemberAccessNode }
constructor TMemberAccessNode.Create(const ABase: IAstNode; const AMember: IIdentifierNode); constructor TMemberAccessNode.Create(const ABase: IAstNode; const AMember: IKeywordNode);
begin begin
inherited Create; inherited Create;
FBase := ABase; FBase := ABase;
@@ -805,7 +807,7 @@ begin
Result := FBase; Result := FBase;
end; end;
function TMemberAccessNode.GetMember: IIdentifierNode; function TMemberAccessNode.GetMember: IKeywordNode;
begin begin
Result := FMember; Result := FMember;
end; end;
@@ -992,7 +994,7 @@ end;
class function TAst.Keyword(const AName: string): IKeywordNode; class function TAst.Keyword(const AName: string): IKeywordNode;
begin begin
Result := TKeywordNode.Create(AName); Result := TKeywordNode.Create(TKeywordRegistry.Intern(AName));
end; end;
class function TAst.LambdaExpr(const AParameters: TArray<IIdentifierNode>; const ABody: IAstNode): ILambdaExpressionNode; class function TAst.LambdaExpr(const AParameters: TArray<IIdentifierNode>; const ABody: IAstNode): ILambdaExpressionNode;
@@ -1009,7 +1011,7 @@ begin
Result := TMacroDefinitionNode.Create(AName, AParameters, ABody); Result := TMacroDefinitionNode.Create(AName, AParameters, ABody);
end; end;
class function TAst.MemberAccess(const ABase: IAstNode; const AMember: IIdentifierNode): IMemberAccessNode; class function TAst.MemberAccess(const ABase: IAstNode; const AMember: IKeywordNode): IMemberAccessNode;
begin begin
Result := TMemberAccessNode.Create(ABase, AMember); Result := TMemberAccessNode.Create(ABase, AMember);
end; end;
+74 -3
View File
@@ -11,7 +11,9 @@ uses
type type
// The runtime representation of an interned keyword // The runtime representation of an interned keyword
IKeyword = interface(IInterface) IKeyword = interface(IInterface)
function GetIdx: Integer;
function GetName: string; function GetName: string;
property Idx: Integer read GetIdx;
property Name: string read GetName; property Name: string read GetName;
end; end;
@@ -22,9 +24,11 @@ type
TKeyword = class(TInterfacedObject, IKeyword) TKeyword = class(TInterfacedObject, IKeyword)
private private
FName: string; FName: string;
FIdx: Integer;
function GetName: string; function GetName: string;
function GetIdx: Integer;
public public
constructor Create(const AName: string); constructor Create(const AName: string; AIdx: Integer);
end; end;
strict private strict private
class var class var
@@ -38,14 +42,38 @@ type
class function Intern(const AName: string): IKeyword; static; class function Intern(const AName: string): IKeyword; static;
end; end;
TKeywordMapping<T> = record
type
TField = record
Key: IKeyword;
Value: T;
public
constructor Create(const AKey: IKeyword; const AValue: T);
end;
private
FMap: TArray<Integer>;
FFields: TArray<TField>;
FFirst, FLast: Integer;
public
constructor Create(const AFields: TArray<TField>);
function IndexOf(const Key: IKeyword): Integer;
property Fields: TArray<TField> read FFields;
end;
implementation implementation
{ TKeywordRegistry.TKeyword } { TKeywordRegistry.TKeyword }
constructor TKeywordRegistry.TKeyword.Create(const AName: string); constructor TKeywordRegistry.TKeyword.Create(const AName: string; AIdx: Integer);
begin begin
inherited Create; inherited Create;
FName := AName; FName := AName;
FIdx := AIdx;
end;
function TKeywordRegistry.TKeyword.GetIdx: Integer;
begin
Result := FIdx;
end; end;
function TKeywordRegistry.TKeyword.GetName: string; function TKeywordRegistry.TKeyword.GetName: string;
@@ -72,7 +100,7 @@ begin
try try
if not FRegistry.TryGetValue(AName, Result) then if not FRegistry.TryGetValue(AName, Result) then
begin begin
Result := TKeyword.Create(AName); Result := TKeyword.Create(AName, FRegistry.Count);
FRegistry.Add(AName, Result); FRegistry.Add(AName, Result);
end; end;
finally finally
@@ -80,4 +108,47 @@ begin
end; end;
end; end;
{ TKeywordMapping }
constructor TKeywordMapping<T>.Create(const AFields: TArray<TField>);
begin
FFields := AFields;
FFirst := 0;
FLast := -1;
if Length(FFields) = 0 then
exit;
FFirst := FFields[0].Key.Idx;
FLast := FFirst;
for var i := 1 to High(FFields) do
begin
var idx := FFields[i].Key.Idx;
if FFirst > idx then
FFirst := idx;
if FLast < idx then
FLast := idx;
end;
SetLength(FMap, FLast - FFirst + 1);
for var i := 0 to High(FMap) do
FMap[i] := -1;
for var i := 0 to High(FFields) do
FMap[FFirst + FFields[i].Key.Idx] := i;
end;
function TKeywordMapping<T>.IndexOf(const Key: IKeyword): Integer;
begin
var idx := Key.Idx - FFirst;
if (idx < 0) or (Idx > High(FMap)) then
exit(-1);
Result := FMap[idx];
end;
constructor TKeywordMapping<T>.TField.Create(const AKey: IKeyword; const AValue: T);
begin
Key := AKey;
Value := AValue;
end;
end. end.
+8 -4
View File
@@ -24,8 +24,9 @@ type
implementation implementation
uses uses
System.Generics.Collections,
Myc.Data.Decimal, Myc.Data.Decimal,
System.Generics.Collections; Myc.Data.Keyword;
{ TRttiAstHelper } { TRttiAstHelper }
@@ -35,7 +36,8 @@ var
fieldsArray: TJSONArray; fieldsArray: TJSONArray;
i: Integer; i: Integer;
fieldObj: TJSONObject; fieldObj: TJSONObject;
kindStr: string; kindStr, fieldName: string;
fieldKey: IKeyword;
fields: TArray<TScalarRecordField>; fields: TArray<TScalarRecordField>;
begin begin
jsonValue := TJSONObject.ParseJSONValue(AJson); jsonValue := TJSONObject.ParseJSONValue(AJson);
@@ -55,9 +57,11 @@ begin
if not Assigned(fieldObj) then if not Assigned(fieldObj) then
continue; continue;
fields[i].Name := fieldObj.GetValue<string>('name'); fieldName := fieldObj.GetValue<string>('name');
fieldKey := TKeywordRegistry.Intern(fieldName);
kindStr := fieldObj.GetValue<string>('kind'); kindStr := fieldObj.GetValue<string>('kind');
fields[i].Kind := TScalar.TKind(GetEnumValue(TypeInfo(TScalar.TKind), kindStr));
fields[i] := TScalarRecordField.Create(fieldKey, TScalar.TKind(GetEnumValue(TypeInfo(TScalar.TKind), kindStr)));
end; end;
Result := TScalarRecordDefinition.Create(fields); Result := TScalarRecordDefinition.Create(fields);
finally finally
+16 -54
View File
@@ -5,7 +5,8 @@ interface
uses uses
System.SysUtils, System.SysUtils,
Myc.Data.Decimal, Myc.Data.Decimal,
Myc.Data.Series; Myc.Data.Series,
Myc.Data.Keyword;
{$SCOPEDENUMS ON} {$SCOPEDENUMS ON}
@@ -91,20 +92,8 @@ type
TScalarTuple = TArray<TScalar>; TScalarTuple = TArray<TScalar>;
// A field definition for a scalar record. // A field definition for a scalar record.
TScalarRecordField = record TScalarRecordField = TKeywordMapping<TScalar.TKind>.TField;
Name: String; TScalarRecordDefinition = TKeywordMapping<TScalar.TKind>;
Kind: TScalar.TKind;
constructor Create(const AName: String; AKind: TScalar.TKind);
end;
TScalarRecordDefinition = record
private
FFields: TArray<TScalarRecordField>;
public
constructor Create(const AFields: TArray<TScalarRecordField>);
function IndexOf(const Name: String): Integer;
property Fields: TArray<TScalarRecordField> read FFields;
end;
// A record of scalar values, based on a definition. // A record of scalar values, based on a definition.
TScalarRecord = record TScalarRecord = record
@@ -112,10 +101,10 @@ type
constructor Create(const ADef: TScalarRecordDefinition; const AFields: TArray<TScalar.TValue>); constructor Create(const ADef: TScalarRecordDefinition; const AFields: TArray<TScalar.TValue>);
function GetDef: TScalarRecordDefinition; inline; function GetDef: TScalarRecordDefinition; inline;
function GetFields: TArray<TScalar.TValue>; inline; function GetFields: TArray<TScalar.TValue>; inline;
function GetItems(const Name: String): TScalar; function GetKeys(const Key: IKeyword): TScalar;
property Def: TScalarRecordDefinition read GetDef; property Def: TScalarRecordDefinition read GetDef;
property Fields: TArray<TScalar.TValue> read GetFields; property Fields: TArray<TScalar.TValue> read GetFields;
property Items[const Name: String]: TScalar read GetItems; default; property Keys[const Key: IKeyword]: TScalar read GetKeys; default;
strict private strict private
FDef: TScalarRecordDefinition; FDef: TScalarRecordDefinition;
FFields: TArray<TScalar.TValue>; FFields: TArray<TScalar.TValue>;
@@ -158,7 +147,7 @@ type
function GetTotalCount: Int64; function GetTotalCount: Int64;
{$endregion} {$endregion}
procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1); procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1);
function CreateMemberSeries(const Field: String): ISeries; function CreateMemberSeries(const Key: IKeyword): ISeries;
property Count: Int64 read GetCount; property Count: Int64 read GetCount;
property Def: TScalarRecordDefinition read GetDef; property Def: TScalarRecordDefinition read GetDef;
property Items[Idx: Integer]: TScalarRecord read GetItems; default; property Items[Idx: Integer]: TScalarRecord read GetItems; default;
@@ -192,7 +181,7 @@ type
public public
constructor Create(const ADef: TScalarRecordDefinition); constructor Create(const ADef: TScalarRecordDefinition);
procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1); procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1);
function CreateMemberSeries(const Field: String): ISeries; function CreateMemberSeries(const Key: IKeyword): ISeries;
property Count: Int64 read GetCount; property Count: Int64 read GetCount;
property Def: TScalarRecordDefinition read GetDef; property Def: TScalarRecordDefinition read GetDef;
property Items[Idx: Integer]: TScalarRecord read GetItems; default; property Items[Idx: Integer]: TScalarRecord read GetItems; default;
@@ -528,14 +517,6 @@ begin
Items := AItems; Items := AItems;
end; end;
{ TScalarRecordField }
constructor TScalarRecordField.Create(const AName: String; AKind: TScalar.TKind);
begin
Name := AName;
Kind := AKind;
end;
{ TScalarRecord } { TScalarRecord }
constructor TScalarRecord.Create(const ADef: TScalarRecordDefinition; const AFields: TArray<TScalar.TValue>); constructor TScalarRecord.Create(const ADef: TScalarRecordDefinition; const AFields: TArray<TScalar.TValue>);
@@ -555,34 +536,15 @@ begin
Result := FFields; Result := FFields;
end; end;
function TScalarRecord.GetItems(const Name: String): TScalar; function TScalarRecord.GetKeys(const Key: IKeyword): TScalar;
var var
idx: Integer; idx: Integer;
begin begin
idx := FDef.IndexOf(Name); idx := FDef.IndexOf(Key);
if idx < 0 then if idx < 0 then
raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); raise EArgumentException.CreateFmt('Field ":%s" not found in record definition.', [Key.Name]);
Result.Create(FDef.Fields[idx].Kind, FFields[idx]); Result.Create(FDef.Fields[idx].Value, FFields[idx]);
end;
{ TScalarRecordDefinition }
constructor TScalarRecordDefinition.Create(const AFields: TArray<TScalarRecordField>);
begin
FFields := AFields;
end;
function TScalarRecordDefinition.IndexOf(const Name: String): Integer;
var
i: Integer;
begin
for i := 0 to High(FFields) do
begin
if (CompareText(FFields[i].Name, Name) = 0) then
exit(i);
end;
Result := -1;
end; end;
{ TScalarRecordSeries } { TScalarRecordSeries }
@@ -600,11 +562,11 @@ begin
inc(FTotalCount); inc(FTotalCount);
end; end;
function TScalarRecordSeries.CreateMemberSeries(const Field: String): ISeries; function TScalarRecordSeries.CreateMemberSeries(const Key: IKeyword): ISeries;
begin begin
var elem := FDef.IndexOf(Field); var elem := FDef.IndexOf(Key);
if elem < 0 then if elem < 0 then
raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Field]); raise EArgumentException.CreateFmt('Field ":%s" not found in record definition.', [Key.Name]);
Result := TMemberSeries.Create(Self, elem); Result := TMemberSeries.Create(Self, elem);
end; end;
@@ -686,7 +648,7 @@ begin
inherited Create; inherited Create;
FRecordSeries := ARecordSeries; FRecordSeries := ARecordSeries;
FRecordSeries._AddRef; FRecordSeries._AddRef;
FKind := FRecordSeries.FDef.FFields[AElementIdx].Kind; FKind := FRecordSeries.FDef.Fields[AElementIdx].Value;
FOffset := sizeof(TScalar.TValue) * AElementIdx; FOffset := sizeof(TScalar.TValue) * AElementIdx;
end; end;
+8 -8
View File
@@ -82,12 +82,12 @@ type
class function FromRecordSeries(const AValue: IRecordSeries): TDataValue; static; inline; class function FromRecordSeries(const AValue: IRecordSeries): TDataValue; static; inline;
class function FromRecord(const [ref] AValue: TScalarRecord): TDataValue; static; inline; class function FromRecord(const [ref] AValue: TScalarRecord): TDataValue; static; inline;
class function FromSeries(const AValue: ISeries): TDataValue; static; inline; class function FromSeries(const AValue: ISeries): TDataValue; static; inline;
class function FromKeyword(const AValue: IKeyword): TDataValue; static; inline; // Added class function FromKeyword(const AValue: IKeyword): TDataValue; static; inline;
function AsScalar: TScalar; inline; function AsScalar: TScalar; inline;
function AsMethod: TFunc; inline; function AsMethod: TFunc; inline;
function AsText: String; inline; function AsText: String; inline;
function AsKeyword: IKeyword; inline; // Added function AsKeyword: IKeyword; inline;
function AsRecordSeries: IRecordSeries; inline; function AsRecordSeries: IRecordSeries; inline;
function AsRecord: TScalarRecord; inline; function AsRecord: TScalarRecord; inline;
function AsSeries: ISeries; inline; function AsSeries: ISeries; inline;
@@ -264,7 +264,7 @@ begin
TInterlocked.CompareExchange(FScalar.Value.AsInt64, NewValue.AsScalar.Value.AsInt64, Expected.AsScalar.Value.AsInt64) TInterlocked.CompareExchange(FScalar.Value.AsInt64, NewValue.AsScalar.Value.AsInt64, Expected.AsScalar.Value.AsInt64)
= Expected.AsScalar.Value.AsInt64; = Expected.AsScalar.Value.AsInt64;
vkText, vkKeyword, vkSeries, vkRecordSeries, vkRecord, vkManagedObject, vkMethod, vkInterface, vkGeneric: // Added vkKeyword vkText, vkKeyword, vkSeries, vkRecordSeries, vkRecord, vkManagedObject, vkMethod, vkInterface, vkGeneric:
Result := IntfCompareExchange(FInterface, NewValue.FInterface, Expected.FInterface) = Expected.FInterface; Result := IntfCompareExchange(FInterface, NewValue.FInterface, Expected.FInterface) = Expected.FInterface;
end; end;
end; end;
@@ -369,7 +369,7 @@ begin
end; end;
vkPointer: Result := TInterlocked.Exchange(FScalar.Value.AsInt64, NewValue.AsScalar.Value.AsInt64); vkPointer: Result := TInterlocked.Exchange(FScalar.Value.AsInt64, NewValue.AsScalar.Value.AsInt64);
vkText, vkKeyword, vkSeries, vkRecordSeries, vkRecord, vkManagedObject, vkMethod, vkInterface, vkGeneric: // Added vkKeyword vkText, vkKeyword, vkSeries, vkRecordSeries, vkRecord, vkManagedObject, vkMethod, vkInterface, vkGeneric:
begin begin
Result.FKind := FKind; Result.FKind := FKind;
Result.FInterface := IntfExchange(FInterface, NewValue.FInterface); Result.FInterface := IntfExchange(FInterface, NewValue.FInterface);
@@ -386,7 +386,7 @@ begin
case FKind of case FKind of
vkScalar: Result := FScalar.ToString; vkScalar: Result := FScalar.ToString;
vkText: Result := '"' + AsText + '"'; vkText: Result := '"' + AsText + '"';
vkKeyword: Result := ':' + AsKeyword.Name; // Added vkKeyword: Result := ':' + AsKeyword.Name;
vkSeries: vkSeries:
begin begin
var series := AsSeries; var series := AsSeries;
@@ -404,7 +404,7 @@ begin
begin begin
if not first then if not first then
sb.Append(','); sb.Append(',');
sb.Append(field.Name); sb.Append(field.Key.Name);
first := False; first := False;
end; end;
sb.Append('}'); sb.Append('}');
@@ -425,7 +425,7 @@ begin
begin begin
if not first then if not first then
sb.Append(','); sb.Append(',');
sb.Append(field.Name); sb.Append(field.Key.Name);
first := False; first := False;
end; end;
sb.Append('}'); sb.Append('}');
@@ -490,7 +490,7 @@ begin
vkVoid: Result := 'Void'; vkVoid: Result := 'Void';
vkScalar: Result := 'Scalar'; vkScalar: Result := 'Scalar';
vkText: Result := 'Text'; vkText: Result := 'Text';
vkKeyword: Result := 'Keyword'; // Added vkKeyword: Result := 'Keyword';
vkSeries: Result := 'Series'; vkSeries: Result := 'Series';
vkRecordSeries: Result := 'RecordSeries'; vkRecordSeries: Result := 'RecordSeries';
vkRecord: Result := 'Record'; vkRecord: Result := 'Record';