Keywords
This commit is contained in:
+30
-19
@@ -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<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
|
||||
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<IAstNode>;
|
||||
|
||||
// 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<IAstNode>;
|
||||
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<TRecordFieldLiteral>;
|
||||
defFields: TArray<TScalarRecordField>;
|
||||
defFields: TArray<TScalarRecordDefinition.TField>;
|
||||
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<IAstNode>;
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string>('Name'), JsonToNode(fieldObj.GetValue('Value')));
|
||||
fields[i] :=
|
||||
TRecordFieldLiteral.Create(
|
||||
TKeywordNode.Create(TKeywordRegistry.Intern(fieldObj.GetValue<string>('Name'))),
|
||||
JsonToNode(fieldObj.GetValue('Value'))
|
||||
);
|
||||
end;
|
||||
Result := TAst.RecordLiteral(fields);
|
||||
end;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -319,7 +319,7 @@ end;
|
||||
function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
|
||||
begin
|
||||
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
|
||||
Result := TDataValue.FromIntf<IMemberAccessNode>(Node)
|
||||
else
|
||||
@@ -331,6 +331,7 @@ var
|
||||
i: Integer;
|
||||
hasChanged: Boolean;
|
||||
newFields: TArray<TRecordFieldLiteral>;
|
||||
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<IKeywordNode>;
|
||||
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;
|
||||
newFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, newValue);
|
||||
|
||||
newFields[i] := TRecordFieldLiteral.Create(newKey, newValue);
|
||||
end;
|
||||
|
||||
if hasChanged then
|
||||
|
||||
+15
-13
@@ -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<TRecordFieldLiteral>): 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<IIdentifierNode>; 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;
|
||||
|
||||
Reference in New Issue
Block a user