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