diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas index f73759e..2120bbd 100644 --- a/ASTPlayground/MainForm.pas +++ b/ASTPlayground/MainForm.pas @@ -694,6 +694,7 @@ begin result := ExecuteAst(root, fibScope); sw.Stop; Memo1.Lines.Add(Format('Result: fib(25) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds])); + UpdateScript; end; @@ -920,7 +921,7 @@ begin [ TAst.VarDecl( TAst.Identifier('closeSeries'), - TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Identifier('Close')) + TAst.MemberAccess(TAst.Identifier('ohlcv'), TAst.Keyword('Close')) ), TAst.VarDecl( TAst.Identifier('currentClose'), diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas index 75e51bb..9e7d32a 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Node.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Node.pas @@ -18,6 +18,7 @@ uses FMX.StdCtrls, Myc.Data.Scalar, Myc.Data.Value, + Myc.Data.Keyword, Myc.Ast.Nodes, Myc.Ast.Visitor, Myc.Ast, @@ -478,7 +479,7 @@ type TAuraRecordLiteralNode = class(TAuraNode) private FNode: IRecordLiteralNode; - FFieldNodes: TDictionary; // Map KeyName -> ValueNode + FFieldNodes: TDictionary; // Map KeyName -> ValueNode protected procedure SetupNode; override; public @@ -525,9 +526,6 @@ type implementation -uses - Myc.Data.Keyword; - { TAstVisualizer } constructor TAstVisualizer.Create(AWorkspace: TAuraWorkspace; AParentControl: TControl; AExprDepth: Integer); @@ -2032,7 +2030,7 @@ end; function TAuraMemberAccessNode.CreateAst: IAstNode; begin // 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; procedure TAuraMemberAccessNode.SetupNode; @@ -2049,7 +2047,7 @@ begin AddLabel(Self, '.'); // Add member name as a label - AddLabel(Self, FNode.Member.Name); + AddLabel(Self, FNode.Member.Value.Name); finally EndUpdate; end; @@ -2061,7 +2059,7 @@ constructor TAuraRecordLiteralNode.Create(const AVisualizer: IAstVisualizer; con begin inherited Create(AVisualizer); FNode := ANode; - FFieldNodes := TDictionary.Create; + FFieldNodes := TDictionary.Create; end; destructor TAuraRecordLiteralNode.Destroy; @@ -2075,7 +2073,7 @@ var fields: TArray; i: Integer; valueNode: TAuraNode; - pair: TPair; + pair: TPair; begin SetLength(fields, FFieldNodes.Count); i := 0; @@ -2084,7 +2082,7 @@ begin for pair in FFieldNodes do begin valueNode := pair.Value; - fields[i] := TRecordFieldLiteral.Create(pair.Key, valueNode.CreateAst); + fields[i] := TRecordFieldLiteral.Create(TKeywordNode.Create(pair.Key), valueNode.CreateAst); inc(i); end; Result := TAst.RecordLiteral(fields); @@ -2115,7 +2113,7 @@ begin fieldContainer := AddContainer(Self, loHorizontal, laCenter); // Key label - keyLabel := AddLabel(fieldContainer, ':' + field.Name); + keyLabel := AddLabel(fieldContainer, ':' + field.Key.Value.Name); keyLabel.Font.Style := keyLabel.Font.Style + [TFontStyle.fsBold]; keyLabel.StyledSettings := keyLabel.StyledSettings - [TStyledSetting.FontColor]; keyLabel.FontColor := TAlphaColors.Mediumvioletred; @@ -2124,7 +2122,7 @@ begin valueNode := FVisualizer.Clone(fieldContainer, FVisualizer.ExprDepth + 1).CallAccept(field.Value); // Store for CreateAst - FFieldNodes.Add(field.Name, valueNode); + FFieldNodes.Add(field.Key.Value, valueNode); end; end; finally diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas index 4fec989..aca8548 100644 --- a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas +++ b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas @@ -209,7 +209,7 @@ var baseStr: string; begin baseStr := Node.Base.Accept(Self).AsText; - Result := Format('%s.%s', [baseStr, Node.Member.Name]); + Result := Format('%s.%s', [baseStr, Node.Member.Value.Name]); end; function TAstToTextVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; diff --git a/Src/AST/Myc.Ast.Binding.pas b/Src/AST/Myc.Ast.Binding.pas index 7f638f2..99fb596 100644 --- a/Src/AST/Myc.Ast.Binding.pas +++ b/Src/AST/Myc.Ast.Binding.pas @@ -259,8 +259,15 @@ begin // externally evaluate the expression using the injected evaluator value := FEvaluate(expr); + // Allow unquoting keywords if value.Kind in [vkScalar, vkText, vkVoid, vkKeyword] then - Result := TDataValue.FromIntf(TAst.Constant(value)) + begin + // vkKeyword needs to be handled differently than other constants + if value.Kind = vkKeyword then + Result := TDataValue.FromIntf(TAst.Keyword(value.AsKeyword.Name)) + else + Result := TDataValue.FromIntf(TAst.Constant(value)); + end else raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]); end; @@ -290,8 +297,8 @@ 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. + // Record literals do not support splicing. + // We just use the default TAstTransformer implementation which visits child values. Result := inherited VisitRecordLiteral(Node); end; @@ -500,6 +507,7 @@ var i: Integer; begin // --- Transformation: Keyword-as-Function --- + // Check if the callee is a keyword literal if (Node.Callee is TKeywordNode) then begin var keywordNode := (Node.Callee as TKeywordNode); @@ -508,18 +516,17 @@ begin // 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)]); + .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 var baseNode := Accept(Node.Arguments[0]).AsIntf; // 3. Create a synthetic IMemberAccessNode - var memberIdentifier := TAst.Identifier(keywordName); - var memberAccessNode := TAst.MemberAccess(baseNode, memberIdentifier); + var memberAccessNode := TAst.MemberAccess(baseNode, TAst.Keyword(keywordName)); // 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); exit; end; @@ -925,19 +932,19 @@ var begin baseNode := Accept(Node.Base).AsIntf; baseType := (baseNode as TAstNode).StaticType; - var memberName := Node.Member.Name; elemType := TTypes.Unknown; if (baseType.Kind <> TStaticTypeKind.stUnknown) then begin 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 - 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 elemType := fieldType @@ -953,7 +960,7 @@ function TAstBinder.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataVal var i: Integer; boundFields: TArray; - defFields: TArray; + defFields: TArray; def: TScalarRecordDefinition; staticType: IStaticType; boundNode: IRecordLiteralNode; @@ -964,18 +971,21 @@ begin SetLength(boundFields, 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 begin valNode := Accept(Node.Fields[i].Value).AsIntf; 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 raise ETypeException.CreateFmt( - 'Record fields must be scalar (Ordinal or Float), but field "%s" is %s', - [Node.Fields[i].Name, valType.ToString]); + 'Record fields must be scalar (Ordinal or Float), but field ":%s" is %s', + [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; if valType.Kind = stOrdinal then @@ -983,7 +993,8 @@ begin else 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; def := TScalarRecordDefinition.Create(defFields); diff --git a/Src/AST/Myc.Ast.Debugger.pas b/Src/AST/Myc.Ast.Debugger.pas index d419da3..e8565fc 100644 --- a/Src/AST/Myc.Ast.Debugger.pas +++ b/Src/AST/Myc.Ast.Debugger.pas @@ -237,7 +237,7 @@ end; function TDebugEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; begin - AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Name])); + AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Value.Name])); Indent; try Result := inherited VisitMemberAccess(Node); diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index 71515d1..3206818 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -449,7 +449,7 @@ begin Indent; for field in Node.Fields do begin - LogFmt('Field :%s', [field.Name]); + LogFmt('Field :%s', [field.Key.Value.Name]); Indent; field.Value.Accept(Self); Unindent; diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index af81dc5..f7935d7 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -437,14 +437,12 @@ end; function TEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; var baseValue: TDataValue; - memberName: string; begin baseValue := Node.Base.Accept(Self); - memberName := Node.Member.Name; case baseValue.Kind of - vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.CreateMemberSeries(memberName)); - vkRecord: Result := baseValue.AsRecord.Items[memberName]; + vkRecordSeries: Result := TDataValue.FromSeries(baseValue.AsRecordSeries.CreateMemberSeries(Node.Member.Value)); + vkRecord: Result := baseValue.AsRecord[Node.Member.Value]; else raise EArgumentException.Create('Member access operator `.` is not supported for this value type.'); end; diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas index a3071cb..e5ec185 100644 --- a/Src/AST/Myc.Ast.JSON.pas +++ b/Src/AST/Myc.Ast.JSON.pas @@ -578,7 +578,7 @@ 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('Name', TJSONString.Create(field.Key.Value.Name)); fieldObj.AddPair('Value', FJsonObjectStack.Pop); fieldsArray.Add(fieldObj); end; @@ -900,7 +900,7 @@ end; function TJsonAstConverter.JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode; 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; function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode; @@ -915,7 +915,11 @@ begin for i := 0 to fieldsArray.Count - 1 do begin fieldObj := fieldsArray.Items[i] as TJSONObject; - fields[i] := TRecordFieldLiteral.Create(fieldObj.GetValue('Name'), JsonToNode(fieldObj.GetValue('Value'))); + fields[i] := + TRecordFieldLiteral.Create( + TKeywordNode.Create(TKeywordRegistry.Intern(fieldObj.GetValue('Name'))), + JsonToNode(fieldObj.GetValue('Value')) + ); end; Result := TAst.RecordLiteral(fields); end; diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 2fff938..ec27a4a 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -55,9 +55,9 @@ type // A single field in a record literal TRecordFieldLiteral = record - Name: string; // The field name (from keyword :name) + Key: IKeywordNode; Value: IAstNode; - constructor Create(const AName: string; const AValue: IAstNode); + constructor Create(const AKey: IKeywordNode; const AValue: IAstNode); end; IAstVisitor = interface @@ -264,10 +264,10 @@ type IMemberAccessNode = interface(IAstNode) {$region 'private'} function GetBase: IAstNode; - function GetMember: IIdentifierNode; + function GetMember: IKeywordNode; {$endregion} property Base: IAstNode read GetBase; - property Member: IIdentifierNode read GetMember; + property Member: IKeywordNode read GetMember; end; // Represents a record literal, e.g., {:a 1 :b 2} @@ -335,9 +335,9 @@ end; { TRecordFieldLiteral } -constructor TRecordFieldLiteral.Create(const AName: string; const AValue: IAstNode); +constructor TRecordFieldLiteral.Create(const AKey: IKeywordNode; const AValue: IAstNode); begin - Name := AName; + Key := AKey; Value := AValue; end; diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index 63bb2b4..3b8ab73 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -376,7 +376,7 @@ begin raise Exception.Create('Syntax Error: Missing value for key ' + fieldName + ' in record literal.'); fieldValue := ParseExpression.Node; - fields.Add(TRecordFieldLiteral.Create(fieldName, fieldValue)); + fields.Add(TRecordFieldLiteral.Create(TKeywordNode.Create(TKeywordRegistry.Intern(fieldName)), fieldValue)); end; Result := TAst.RecordLiteral(fields.ToArray); @@ -490,7 +490,7 @@ begin else if SameText(head.Token.Text, 'get') then Result := TAst.Indexer(tailNodes[0], tailNodes[1]) 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; // If no special form matched, treat it as a generic function call. @@ -916,7 +916,7 @@ begin for field in Node.Fields do begin NewLine; - Append(':' + field.Name); + Append(':' + field.Key.Value.Name); Append(' '); field.Value.Accept(Self); end; diff --git a/Src/AST/Myc.Ast.Types.pas b/Src/AST/Myc.Ast.Types.pas index 428ea62..d7a0e7e 100644 --- a/Src/AST/Myc.Ast.Types.pas +++ b/Src/AST/Myc.Ast.Types.pas @@ -386,7 +386,8 @@ begin for i := 0 to High(Self.FDefinition.Fields) do 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); end; @@ -400,7 +401,7 @@ begin Result := GetKind.ToString + '{'; for i := 0 to High(FDefinition.Fields) do 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 Result := Result + ', '; end; diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index fe0ccbf..4ee26c5 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -319,7 +319,7 @@ end; function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; begin var base := Accept(Node.Base).AsIntf; - var member := Node.Member; + var member := Accept(Node.Member).AsIntf; if (base = Node.Base) and (member = Node.Member) then Result := TDataValue.FromIntf(Node) else @@ -331,6 +331,7 @@ var i: Integer; hasChanged: Boolean; newFields: TArray; + newKey: IKeywordNode; newValue: IAstNode; begin hasChanged := False; @@ -338,10 +339,13 @@ begin for i := 0 to High(Node.Fields) do begin + newKey := Accept(Node.Fields[i].Key).AsIntf; newValue := Accept(Node.Fields[i].Value).AsIntf; - if newValue <> Node.Fields[i].Value then + + if (newKey <> Node.Fields[i].Key) or (newValue <> Node.Fields[i].Value) then hasChanged := True; - newFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, newValue); + + newFields[i] := TRecordFieldLiteral.Create(newKey, newValue); end; if hasChanged then diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index cedb13e..91f41aa 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -58,7 +58,7 @@ type class function Assign(const AIdentifier: IIdentifierNode; const AValue: IAstNode): IAssignmentNode; static; 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 MemberAccess(const ABase: IAstNode; const AMember: IKeywordNode): IMemberAccessNode; static; class function RecordLiteral(const AFields: TArray): IRecordLiteralNode; static; class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static; class function AddSeriesItem( @@ -104,7 +104,7 @@ type FValue: IKeyword; function GetValue: IKeyword; public - constructor Create(const AName: string); + constructor Create(const AValue: IKeyword); function Accept(const Visitor: IAstVisitor): TDataValue; override; property Value: IKeyword read FValue; end; @@ -292,11 +292,11 @@ type TMemberAccessNode = class(TAstNode, IMemberAccessNode) private FBase: IAstNode; - FMember: IIdentifierNode; + FMember: IKeywordNode; function GetBase: IAstNode; - function GetMember: IIdentifierNode; + function GetMember: IKeywordNode; public - constructor Create(const ABase: IAstNode; const AMember: IIdentifierNode); + constructor Create(const ABase: IAstNode; const AMember: IKeywordNode); function Accept(const Visitor: IAstVisitor): TDataValue; override; end; @@ -349,14 +349,15 @@ constructor TConstantNode.Create(const AValue: TDataValue); begin inherited Create; // Validate that only allowed constant kinds are used. - if not (AValue.Kind in [vkScalar, vkText, vkVoid]) then - raise EArgumentException.Create('IConstantNode only supports Scalar, Text, and Void values.'); + if not (AValue.Kind in [vkScalar, vkText, vkVoid, vkKeyword]) then + raise EArgumentException.Create('IConstantNode only supports Scalar, Text, Void and Keyword values.'); FValue := AValue; case AValue.Kind of vkScalar: StaticType := TTypes.FromScalarKind(AValue.AsScalar.Kind); vkText: StaticType := TTypes.Text; vkVoid: StaticType := TTypes.Void; + vkKeyword: StaticType := TTypes.Keyword; else StaticType := TTypes.Unknown; // Should not happen due to validation above end; @@ -392,11 +393,12 @@ end; { TKeywordNode } -constructor TKeywordNode.Create(const AName: string); +constructor TKeywordNode.Create(const AValue: IKeyword); begin inherited Create; - FValue := TKeywordRegistry.Intern(AName); + FValue := AValue; StaticType := TTypes.Keyword; + FValue := AValue; end; function TKeywordNode.Accept(const Visitor: IAstVisitor): TDataValue; @@ -788,7 +790,7 @@ end; { TMemberAccessNode } -constructor TMemberAccessNode.Create(const ABase: IAstNode; const AMember: IIdentifierNode); +constructor TMemberAccessNode.Create(const ABase: IAstNode; const AMember: IKeywordNode); begin inherited Create; FBase := ABase; @@ -805,7 +807,7 @@ begin Result := FBase; end; -function TMemberAccessNode.GetMember: IIdentifierNode; +function TMemberAccessNode.GetMember: IKeywordNode; begin Result := FMember; end; @@ -992,7 +994,7 @@ end; class function TAst.Keyword(const AName: string): IKeywordNode; begin - Result := TKeywordNode.Create(AName); + Result := TKeywordNode.Create(TKeywordRegistry.Intern(AName)); end; class function TAst.LambdaExpr(const AParameters: TArray; const ABody: IAstNode): ILambdaExpressionNode; @@ -1009,7 +1011,7 @@ begin Result := TMacroDefinitionNode.Create(AName, AParameters, ABody); end; -class function TAst.MemberAccess(const ABase: IAstNode; const AMember: IIdentifierNode): IMemberAccessNode; +class function TAst.MemberAccess(const ABase: IAstNode; const AMember: IKeywordNode): IMemberAccessNode; begin Result := TMemberAccessNode.Create(ABase, AMember); end; diff --git a/Src/Data/Myc.Data.Keyword.pas b/Src/Data/Myc.Data.Keyword.pas index 0c854ee..9d3092e 100644 --- a/Src/Data/Myc.Data.Keyword.pas +++ b/Src/Data/Myc.Data.Keyword.pas @@ -11,7 +11,9 @@ uses type // The runtime representation of an interned keyword IKeyword = interface(IInterface) + function GetIdx: Integer; function GetName: string; + property Idx: Integer read GetIdx; property Name: string read GetName; end; @@ -22,9 +24,11 @@ type TKeyword = class(TInterfacedObject, IKeyword) private FName: string; + FIdx: Integer; function GetName: string; + function GetIdx: Integer; public - constructor Create(const AName: string); + constructor Create(const AName: string; AIdx: Integer); end; strict private class var @@ -38,14 +42,38 @@ type class function Intern(const AName: string): IKeyword; static; end; + TKeywordMapping = record + type + TField = record + Key: IKeyword; + Value: T; + public + constructor Create(const AKey: IKeyword; const AValue: T); + end; + private + FMap: TArray; + FFields: TArray; + FFirst, FLast: Integer; + public + constructor Create(const AFields: TArray); + function IndexOf(const Key: IKeyword): Integer; + property Fields: TArray read FFields; + end; + implementation { TKeywordRegistry.TKeyword } -constructor TKeywordRegistry.TKeyword.Create(const AName: string); +constructor TKeywordRegistry.TKeyword.Create(const AName: string; AIdx: Integer); begin inherited Create; FName := AName; + FIdx := AIdx; +end; + +function TKeywordRegistry.TKeyword.GetIdx: Integer; +begin + Result := FIdx; end; function TKeywordRegistry.TKeyword.GetName: string; @@ -72,7 +100,7 @@ begin try if not FRegistry.TryGetValue(AName, Result) then begin - Result := TKeyword.Create(AName); + Result := TKeyword.Create(AName, FRegistry.Count); FRegistry.Add(AName, Result); end; finally @@ -80,4 +108,47 @@ begin end; end; +{ TKeywordMapping } + +constructor TKeywordMapping.Create(const AFields: TArray); +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.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.TField.Create(const AKey: IKeyword; const AValue: T); +begin + Key := AKey; + Value := AValue; +end; + end. diff --git a/Src/Data/Myc.Data.Scalar.JSON.pas b/Src/Data/Myc.Data.Scalar.JSON.pas index 2bbd06b..289f6f0 100644 --- a/Src/Data/Myc.Data.Scalar.JSON.pas +++ b/Src/Data/Myc.Data.Scalar.JSON.pas @@ -24,8 +24,9 @@ type implementation uses + System.Generics.Collections, Myc.Data.Decimal, - System.Generics.Collections; + Myc.Data.Keyword; { TRttiAstHelper } @@ -35,7 +36,8 @@ var fieldsArray: TJSONArray; i: Integer; fieldObj: TJSONObject; - kindStr: string; + kindStr, fieldName: string; + fieldKey: IKeyword; fields: TArray; begin jsonValue := TJSONObject.ParseJSONValue(AJson); @@ -55,9 +57,11 @@ begin if not Assigned(fieldObj) then continue; - fields[i].Name := fieldObj.GetValue('name'); + fieldName := fieldObj.GetValue('name'); + fieldKey := TKeywordRegistry.Intern(fieldName); kindStr := fieldObj.GetValue('kind'); - fields[i].Kind := TScalar.TKind(GetEnumValue(TypeInfo(TScalar.TKind), kindStr)); + + fields[i] := TScalarRecordField.Create(fieldKey, TScalar.TKind(GetEnumValue(TypeInfo(TScalar.TKind), kindStr))); end; Result := TScalarRecordDefinition.Create(fields); finally diff --git a/Src/Data/Myc.Data.Scalar.pas b/Src/Data/Myc.Data.Scalar.pas index 90e2438..fd6ffe4 100644 --- a/Src/Data/Myc.Data.Scalar.pas +++ b/Src/Data/Myc.Data.Scalar.pas @@ -5,7 +5,8 @@ interface uses System.SysUtils, Myc.Data.Decimal, - Myc.Data.Series; + Myc.Data.Series, + Myc.Data.Keyword; {$SCOPEDENUMS ON} @@ -91,20 +92,8 @@ type TScalarTuple = TArray; // A field definition for a scalar record. - TScalarRecordField = record - Name: String; - Kind: TScalar.TKind; - constructor Create(const AName: String; AKind: TScalar.TKind); - end; - - TScalarRecordDefinition = record - private - FFields: TArray; - public - constructor Create(const AFields: TArray); - function IndexOf(const Name: String): Integer; - property Fields: TArray read FFields; - end; + TScalarRecordField = TKeywordMapping.TField; + TScalarRecordDefinition = TKeywordMapping; // A record of scalar values, based on a definition. TScalarRecord = record @@ -112,10 +101,10 @@ type constructor Create(const ADef: TScalarRecordDefinition; const AFields: TArray); function GetDef: TScalarRecordDefinition; inline; function GetFields: TArray; inline; - function GetItems(const Name: String): TScalar; + function GetKeys(const Key: IKeyword): TScalar; property Def: TScalarRecordDefinition read GetDef; property Fields: TArray read GetFields; - property Items[const Name: String]: TScalar read GetItems; default; + property Keys[const Key: IKeyword]: TScalar read GetKeys; default; strict private FDef: TScalarRecordDefinition; FFields: TArray; @@ -158,7 +147,7 @@ type function GetTotalCount: Int64; {$endregion} 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 Def: TScalarRecordDefinition read GetDef; property Items[Idx: Integer]: TScalarRecord read GetItems; default; @@ -192,7 +181,7 @@ type public constructor Create(const ADef: TScalarRecordDefinition); 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 Def: TScalarRecordDefinition read GetDef; property Items[Idx: Integer]: TScalarRecord read GetItems; default; @@ -528,14 +517,6 @@ begin Items := AItems; end; -{ TScalarRecordField } - -constructor TScalarRecordField.Create(const AName: String; AKind: TScalar.TKind); -begin - Name := AName; - Kind := AKind; -end; - { TScalarRecord } constructor TScalarRecord.Create(const ADef: TScalarRecordDefinition; const AFields: TArray); @@ -555,34 +536,15 @@ begin Result := FFields; end; -function TScalarRecord.GetItems(const Name: String): TScalar; +function TScalarRecord.GetKeys(const Key: IKeyword): TScalar; var idx: Integer; begin - idx := FDef.IndexOf(Name); + idx := FDef.IndexOf(Key); 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]); -end; - -{ TScalarRecordDefinition } - -constructor TScalarRecordDefinition.Create(const AFields: TArray); -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; + Result.Create(FDef.Fields[idx].Value, FFields[idx]); end; { TScalarRecordSeries } @@ -600,11 +562,11 @@ begin inc(FTotalCount); end; -function TScalarRecordSeries.CreateMemberSeries(const Field: String): ISeries; +function TScalarRecordSeries.CreateMemberSeries(const Key: IKeyword): ISeries; begin - var elem := FDef.IndexOf(Field); + var elem := FDef.IndexOf(Key); 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); end; @@ -686,7 +648,7 @@ begin inherited Create; FRecordSeries := ARecordSeries; FRecordSeries._AddRef; - FKind := FRecordSeries.FDef.FFields[AElementIdx].Kind; + FKind := FRecordSeries.FDef.Fields[AElementIdx].Value; FOffset := sizeof(TScalar.TValue) * AElementIdx; end; diff --git a/Src/Data/Myc.Data.Value.pas b/Src/Data/Myc.Data.Value.pas index 872f66f..da8c5ed 100644 --- a/Src/Data/Myc.Data.Value.pas +++ b/Src/Data/Myc.Data.Value.pas @@ -82,12 +82,12 @@ type class function FromRecordSeries(const AValue: IRecordSeries): TDataValue; static; inline; class function FromRecord(const [ref] AValue: TScalarRecord): 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 AsMethod: TFunc; inline; function AsText: String; inline; - function AsKeyword: IKeyword; inline; // Added + function AsKeyword: IKeyword; inline; function AsRecordSeries: IRecordSeries; inline; function AsRecord: TScalarRecord; inline; function AsSeries: ISeries; inline; @@ -264,7 +264,7 @@ begin TInterlocked.CompareExchange(FScalar.Value.AsInt64, NewValue.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; end; end; @@ -369,7 +369,7 @@ begin end; 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 Result.FKind := FKind; Result.FInterface := IntfExchange(FInterface, NewValue.FInterface); @@ -386,7 +386,7 @@ begin case FKind of vkScalar: Result := FScalar.ToString; vkText: Result := '"' + AsText + '"'; - vkKeyword: Result := ':' + AsKeyword.Name; // Added + vkKeyword: Result := ':' + AsKeyword.Name; vkSeries: begin var series := AsSeries; @@ -404,7 +404,7 @@ begin begin if not first then sb.Append(','); - sb.Append(field.Name); + sb.Append(field.Key.Name); first := False; end; sb.Append('}'); @@ -425,7 +425,7 @@ begin begin if not first then sb.Append(','); - sb.Append(field.Name); + sb.Append(field.Key.Name); first := False; end; sb.Append('}'); @@ -490,7 +490,7 @@ begin vkVoid: Result := 'Void'; vkScalar: Result := 'Scalar'; vkText: Result := 'Text'; - vkKeyword: Result := 'Keyword'; // Added + vkKeyword: Result := 'Keyword'; vkSeries: Result := 'Series'; vkRecordSeries: Result := 'RecordSeries'; vkRecord: Result := 'Record';