Keywords
This commit is contained in:
+109
-8
@@ -12,7 +12,7 @@ uses
|
||||
Myc.Ast.Visitor,
|
||||
Myc.Ast.Scope,
|
||||
Myc.Ast.Analyzer,
|
||||
Myc.Ast.Types, // Added
|
||||
Myc.Ast.Types,
|
||||
Myc.Ast;
|
||||
|
||||
type
|
||||
@@ -35,6 +35,7 @@ type
|
||||
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override;
|
||||
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
|
||||
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
|
||||
function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override;
|
||||
public
|
||||
constructor Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc);
|
||||
class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode;
|
||||
@@ -70,6 +71,7 @@ type
|
||||
protected
|
||||
function Accept(const Node: IAstNode): TDataValue; override;
|
||||
function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override;
|
||||
function VisitKeyword(const Node: IKeywordNode): TDataValue; override;
|
||||
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override;
|
||||
function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
|
||||
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override;
|
||||
@@ -82,10 +84,10 @@ type
|
||||
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override;
|
||||
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override;
|
||||
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override;
|
||||
// Added for type inference
|
||||
function VisitConstant(const Node: IConstantNode): TDataValue; override;
|
||||
function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override;
|
||||
function VisitIndexer(const Node: IIndexerNode): TDataValue; override;
|
||||
function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override;
|
||||
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override;
|
||||
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override;
|
||||
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
|
||||
@@ -151,11 +153,20 @@ type
|
||||
property IsTailCall: Boolean read FIsTailCall;
|
||||
end;
|
||||
|
||||
TBoundRecordLiteralNode = class(TRecordLiteralNode)
|
||||
private
|
||||
FDefinition: TScalarRecordDefinition;
|
||||
public
|
||||
constructor Create(const AFields: TArray<TRecordFieldLiteral>; const ADef: TScalarRecordDefinition);
|
||||
property Definition: TScalarRecordDefinition read FDefinition;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Generics.Defaults,
|
||||
System.Character;
|
||||
System.Character,
|
||||
Myc.Data.Keyword;
|
||||
|
||||
type
|
||||
TResolvedAddressComparer = class(TEqualityComparer<TResolvedAddress>)
|
||||
@@ -248,7 +259,7 @@ begin
|
||||
// externally evaluate the expression using the injected evaluator
|
||||
value := FEvaluate(expr);
|
||||
|
||||
if value.Kind in [vkScalar, vkText, vkVoid] then
|
||||
if value.Kind in [vkScalar, vkText, vkVoid, vkKeyword] then
|
||||
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value))
|
||||
else
|
||||
raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]);
|
||||
@@ -277,6 +288,13 @@ begin
|
||||
Result := TDataValue.FromIntf<IBlockExpressionNode>(TAst.Block(newExprs));
|
||||
end;
|
||||
|
||||
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
||||
begin
|
||||
// This node type does not support splicing, so we use the default
|
||||
// TAstTransformer implementation which just transforms child values.
|
||||
Result := inherited VisitRecordLiteral(Node);
|
||||
end;
|
||||
|
||||
{ TBoundIdentifierNode }
|
||||
constructor TBoundIdentifierNode.Create(const AUnboundNode: IIdentifierNode; const AAddress: TResolvedAddress);
|
||||
begin
|
||||
@@ -319,6 +337,13 @@ begin
|
||||
FIsTailCall := AIsTailCall;
|
||||
end;
|
||||
|
||||
{ TBoundRecordLiteralNode }
|
||||
constructor TBoundRecordLiteralNode.Create(const AFields: TArray<TRecordFieldLiteral>; const ADef: TScalarRecordDefinition);
|
||||
begin
|
||||
inherited Create(AFields);
|
||||
FDefinition := ADef;
|
||||
end;
|
||||
|
||||
{ TResolvedAddressComparer }
|
||||
function TResolvedAddressComparer.Equals(const Left, Right: TResolvedAddress): Boolean;
|
||||
begin
|
||||
@@ -474,6 +499,31 @@ var
|
||||
args: TArray<IAstNode>;
|
||||
i: Integer;
|
||||
begin
|
||||
// --- Transformation: Keyword-as-Function ---
|
||||
if (Node.Callee is TKeywordNode) then
|
||||
begin
|
||||
var keywordNode := (Node.Callee as TKeywordNode);
|
||||
var keywordName := keywordNode.Value.Name;
|
||||
|
||||
// 1. Validate argument count
|
||||
if Length(Node.Arguments) <> 1 then
|
||||
raise ETypeException
|
||||
.CreateFmt('Keyword :%s expects exactly one argument (the record), but got %d', [keywordName, Length(Node.Arguments)]);
|
||||
|
||||
// 2. Bind the base (the record)
|
||||
FNextIsTail := False; // Accessing a member is not a tail call
|
||||
var baseNode := Accept(Node.Arguments[0]).AsIntf<IAstNode>;
|
||||
|
||||
// 3. Create a synthetic IMemberAccessNode
|
||||
var memberIdentifier := TAst.Identifier(keywordName);
|
||||
var memberAccessNode := TAst.MemberAccess(baseNode, memberIdentifier);
|
||||
|
||||
// 4. Re-bind the synthetic node by calling Accept (which dispatches to VisitMemberAccess)
|
||||
// This ensures type checking and type inference for member access is done in one place.
|
||||
Result := Accept(memberAccessNode);
|
||||
exit;
|
||||
end;
|
||||
|
||||
if (Node.Callee is TIdentifierNode) then
|
||||
begin
|
||||
calleeIdentifier := Node.Callee as TIdentifierNode;
|
||||
@@ -592,10 +642,10 @@ begin
|
||||
// Check argument types (param types are not yet inferred, so skip check for now)
|
||||
// for i := 0 to High(args) do
|
||||
// begin
|
||||
// var argType := (args[i] as TAstNode).StaticType;
|
||||
// var paramType := signature.ParamTypes[i];
|
||||
// if not TTypeRules.CanAssign(paramType, argType) then
|
||||
// raise ETypeException.CreateFmt('Cannot assign argument %d (type %s) to parameter (type %s)', [i, argType.ToString, paramType.ToString]);
|
||||
// var argType := (args[i] as TAstNode).StaticType;
|
||||
// var paramType := signature.ParamTypes[i];
|
||||
// if not TTypeRules.CanAssign(paramType, argType) then
|
||||
// raise ETypeException.CreateFmt('Cannot assign argument %d (type %s) to parameter (type %s)', [i, argType.ToString, paramType.ToString]);
|
||||
// end;
|
||||
|
||||
boundCall := TBoundFunctionCallNode.Create(Node, callee, args, isTailCall);
|
||||
@@ -738,12 +788,19 @@ begin
|
||||
Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.FromScalarKind(Node.Value.AsScalar.Kind));
|
||||
TDataValueKind.vkText: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Text);
|
||||
TDataValueKind.vkVoid: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Void);
|
||||
TDataValueKind.vkKeyword: Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Keyword);
|
||||
else
|
||||
// Handle other constant types if they become supported
|
||||
Result := SetType(TDataValue.FromIntf<IConstantNode>(Node), TTypes.Unknown);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitKeyword(const Node: IKeywordNode): TDataValue;
|
||||
begin
|
||||
// Keywords are literals. Their type is set in TKeywordNode.Create.
|
||||
Result := TDataValue.FromIntf<IKeywordNode>(Node);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
|
||||
var
|
||||
elemType: IStaticType;
|
||||
@@ -892,6 +949,50 @@ begin
|
||||
Result := SetType(TDataValue.FromIntf<IMemberAccessNode>(boundNode), elemType);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
|
||||
var
|
||||
i: Integer;
|
||||
boundFields: TArray<TRecordFieldLiteral>;
|
||||
defFields: TArray<TScalarRecordField>;
|
||||
def: TScalarRecordDefinition;
|
||||
staticType: IStaticType;
|
||||
boundNode: IRecordLiteralNode;
|
||||
valNode: IAstNode;
|
||||
valType: IStaticType;
|
||||
begin
|
||||
FNextIsTail := False;
|
||||
SetLength(boundFields, Length(Node.Fields));
|
||||
SetLength(defFields, Length(Node.Fields));
|
||||
|
||||
for i := 0 to High(Node.Fields) do
|
||||
begin
|
||||
valNode := Accept(Node.Fields[i].Value).AsIntf<IAstNode>;
|
||||
valType := (valNode as TAstNode).StaticType;
|
||||
|
||||
// Records can only store scalar values
|
||||
if not (valType.Kind in [stOrdinal, stFloat]) then
|
||||
raise ETypeException.CreateFmt(
|
||||
'Record fields must be scalar (Ordinal or Float), but field "%s" is %s',
|
||||
[Node.Fields[i].Name, valType.ToString]);
|
||||
|
||||
boundFields[i] := TRecordFieldLiteral.Create(Node.Fields[i].Name, valNode);
|
||||
|
||||
var scalarKind: TScalar.TKind;
|
||||
if valType.Kind = stOrdinal then
|
||||
scalarKind := TScalar.TKind.Ordinal
|
||||
else
|
||||
scalarKind := TScalar.TKind.Float;
|
||||
|
||||
defFields[i] := TScalarRecordField.Create(Node.Fields[i].Name, scalarKind);
|
||||
end;
|
||||
|
||||
def := TScalarRecordDefinition.Create(defFields);
|
||||
staticType := TTypes.CreateRecord(def);
|
||||
boundNode := TBoundRecordLiteralNode.Create(boundFields, def);
|
||||
|
||||
Result := SetType(TDataValue.FromIntf<IRecordLiteralNode>(boundNode), staticType);
|
||||
end;
|
||||
|
||||
function TAstBinder.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
|
||||
var
|
||||
i: integer;
|
||||
|
||||
Reference in New Issue
Block a user