Tuples
This commit is contained in:
@@ -70,6 +70,7 @@ type
|
||||
function VisitRecordLiteral(const Node: IAstNode): IAstNode;
|
||||
function VisitConstant(const Node: IAstNode): IAstNode;
|
||||
function VisitKeyword(const Node: IAstNode): IAstNode;
|
||||
function VisitTuple(const Node: IAstNode): IAstNode;
|
||||
|
||||
// Pipe Support
|
||||
function VisitPipeInput(const Node: IAstNode): IAstNode;
|
||||
@@ -238,6 +239,7 @@ begin
|
||||
Register(akRecordLiteral, VisitRecordLiteral);
|
||||
Register(akConstant, VisitConstant);
|
||||
Register(akKeyword, VisitKeyword);
|
||||
Register(akTuple, VisitTuple);
|
||||
|
||||
// Pipe Support
|
||||
Register(akPipeInput, VisitPipeInput);
|
||||
@@ -292,7 +294,6 @@ end;
|
||||
|
||||
function TTypeChecker.VisitConstant(const Node: IAstNode): IAstNode;
|
||||
begin
|
||||
// Base implementation already returns Node, but here we explicitly confirm identity for clarity
|
||||
Result := Node;
|
||||
end;
|
||||
|
||||
@@ -301,6 +302,98 @@ begin
|
||||
Result := Node;
|
||||
end;
|
||||
|
||||
function TTypeChecker.VisitTuple(const Node: IAstNode): IAstNode;
|
||||
var
|
||||
T: ITupleNode;
|
||||
newElements: TArray<IAstNode>;
|
||||
elementTypes: TArray<IStaticType>;
|
||||
i: Integer;
|
||||
|
||||
// Inference variables
|
||||
firstType: IStaticType;
|
||||
isHomogeneous: Boolean;
|
||||
commonDim: TArray<Integer>;
|
||||
newDim: TArray<Integer>;
|
||||
|
||||
finalType: IStaticType;
|
||||
begin
|
||||
T := Node.AsTuple;
|
||||
var count := Length(T.Elements);
|
||||
|
||||
SetLength(newElements, count);
|
||||
SetLength(elementTypes, count);
|
||||
|
||||
// 1. Visit Children
|
||||
// Recursively type-check all elements first to determine their static types.
|
||||
for i := 0 to count - 1 do
|
||||
begin
|
||||
newElements[i] := Accept(T.Elements[i]);
|
||||
elementTypes[i] := newElements[i].AsTypedNode.StaticType;
|
||||
end;
|
||||
|
||||
// 2. Inference Logic: Tuple vs. Vector vs. Matrix
|
||||
if count = 0 then
|
||||
begin
|
||||
// Empty Tuple -> stTuple (safest fallback, effectively Void)
|
||||
finalType := TTypes.CreateTuple([]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
firstType := elementTypes[0];
|
||||
isHomogeneous := True;
|
||||
|
||||
// Check for Homogeneity (Exact type equality of all elements)
|
||||
for i := 1 to count - 1 do
|
||||
begin
|
||||
if not firstType.IsEqual(elementTypes[i]) then
|
||||
begin
|
||||
isHomogeneous := False;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
|
||||
if isHomogeneous then
|
||||
begin
|
||||
// It is at least a Vector.
|
||||
// Check if it should be promoted to a Matrix (i.e., elements are Vectors or Matrices).
|
||||
|
||||
if firstType.Kind = stVector then
|
||||
begin
|
||||
// Vector of Vectors -> Matrix (2D)
|
||||
// New Dimensions = [OuterCount, InnerCount]
|
||||
newDim := [count, firstType.AsVector.Count];
|
||||
finalType := TTypes.CreateMatrix(firstType.AsVector.ElementType, newDim);
|
||||
end
|
||||
else if firstType.Kind = stMatrix then
|
||||
begin
|
||||
// Vector of Matrices -> Higher dimensional Matrix (N+1)
|
||||
// New Dimensions = [OuterCount, Dim0, Dim1...]
|
||||
commonDim := firstType.AsMatrix.Dimensions;
|
||||
SetLength(newDim, Length(commonDim) + 1);
|
||||
|
||||
newDim[0] := count;
|
||||
for i := 0 to High(commonDim) do
|
||||
newDim[i + 1] := commonDim[i];
|
||||
|
||||
finalType := TTypes.CreateMatrix(firstType.AsMatrix.ElementType, newDim);
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Base case: Homogeneous Scalars/Records -> Vector (1D)
|
||||
finalType := TTypes.CreateVector(firstType, count);
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Heterogeneous types -> Standard Tuple
|
||||
finalType := TTypes.CreateTuple(elementTypes);
|
||||
end;
|
||||
end;
|
||||
|
||||
// 3. Return the new node with the inferred type definition
|
||||
Result := TAst.Tuple(Node.Identity, newElements, finalType);
|
||||
end;
|
||||
|
||||
function TTypeChecker.VisitIdentifier(const Node: IAstNode): IAstNode;
|
||||
var
|
||||
I: IIdentifierNode;
|
||||
@@ -328,12 +421,6 @@ var
|
||||
args: IArgumentList;
|
||||
begin
|
||||
R := Node.AsRecur;
|
||||
// Use inherited to transform arguments, then reconstruct with type Void
|
||||
// Note: inherited VisitRecurNode returns IAstNode which is a RecurNode.
|
||||
// We can call Accept on arguments list directly to avoid intermediate node creation if desired,
|
||||
// but relying on inherited logic keeps it consistent.
|
||||
|
||||
// Efficient approach: Accept the arguments list directly (it's a child).
|
||||
args := Accept(R.Arguments).AsArgumentList;
|
||||
Result := TAst.Recur(Node.Identity, args, TTypes.Void);
|
||||
end;
|
||||
@@ -422,17 +509,37 @@ var
|
||||
newBlock: IBlockExpressionNode;
|
||||
blockType: IStaticType;
|
||||
exprs: IExpressionList;
|
||||
i: Integer;
|
||||
begin
|
||||
// Inherited logic transforms all expressions in the list
|
||||
newBlock := inherited VisitBlockExpression(Node).AsBlockExpression;
|
||||
// 1. Transform children via inherited logic
|
||||
var transformedNode := inherited VisitBlockExpression(Node);
|
||||
|
||||
// Safety check: Did the transformer return a node?
|
||||
if not Assigned(transformedNode) then
|
||||
raise ECompilationFailed.Create([TCompilerError.Create(elError, 'Internal Error: Block transformation returned nil.', Node)]);
|
||||
|
||||
newBlock := transformedNode.AsBlockExpression;
|
||||
exprs := newBlock.Expressions;
|
||||
|
||||
// 2. Validate expressions
|
||||
if exprs.Count > 0 then
|
||||
blockType := exprs[exprs.Count - 1].AsTypedNode.StaticType
|
||||
else
|
||||
blockType := TTypes.Void;
|
||||
begin
|
||||
// Check for NIL entries which indicate failed transformation of children
|
||||
for i := 0 to exprs.Count - 1 do
|
||||
begin
|
||||
if exprs[i] = nil then
|
||||
raise ECompilationFailed.Create(
|
||||
[TCompilerError.Create(elError, Format('Internal Error: Block expression #%d transformed to nil.', [i]), Node)]);
|
||||
end;
|
||||
|
||||
// The type of the block is the type of the last expression
|
||||
blockType := exprs[exprs.Count - 1].AsTypedNode.StaticType;
|
||||
end
|
||||
else
|
||||
begin
|
||||
blockType := TTypes.Void;
|
||||
end;
|
||||
|
||||
// Return new block with calculated type
|
||||
Result := TAst.Block(Node.Identity, exprs, blockType);
|
||||
end;
|
||||
|
||||
@@ -451,7 +558,6 @@ var
|
||||
begin
|
||||
L := Node.AsLambdaExpression;
|
||||
|
||||
// 1. Resolve Upvalue Types
|
||||
var upvalueAddrs := L.Upvalues;
|
||||
SetLength(upvalueTypes, Length(upvalueAddrs));
|
||||
|
||||
@@ -466,7 +572,6 @@ begin
|
||||
upvalueTypes[i] := FCurrentContext.LookupType(lookupAddr);
|
||||
end;
|
||||
|
||||
// 2. Enter New Scope
|
||||
FCurrentContext := TTypeContext.Create(FCurrentContext, L.Layout, upvalueTypes, nil);
|
||||
try
|
||||
SetLength(newParams, L.Parameters.Count);
|
||||
@@ -475,8 +580,6 @@ begin
|
||||
for i := 0 to L.Parameters.Count - 1 do
|
||||
begin
|
||||
paramIdent := L.Parameters[i];
|
||||
|
||||
// Check if there is already a type assigned (e.g. injected by Pipe Visitor)
|
||||
injectedType := paramIdent.AsTypedNode.StaticType;
|
||||
|
||||
if injectedType.Kind = stUnknown then
|
||||
@@ -517,10 +620,7 @@ var
|
||||
bestSig: IMethodSignature;
|
||||
match: Boolean;
|
||||
begin
|
||||
// Use inherited to visit Callee and Arguments first
|
||||
newCall := inherited VisitFunctionCall(Node).AsFunctionCall;
|
||||
|
||||
// Now analyze types on the transformed children
|
||||
var newCallee := newCall.Callee;
|
||||
var newArgs := newCall.Arguments;
|
||||
|
||||
@@ -570,7 +670,6 @@ begin
|
||||
if Assigned(FLog) then
|
||||
FLog.AddError(Format('Cannot invoke type %s as a function.', [calleeType.ToString]), Node);
|
||||
|
||||
// Return new node with Types
|
||||
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, retType, newCall.IsTailCall, nil, False);
|
||||
end;
|
||||
|
||||
@@ -605,7 +704,18 @@ begin
|
||||
if baseType.Kind = stSeries then
|
||||
elemType := baseType.AsSeries.ElementType
|
||||
else if baseType.Kind = stRecordSeries then
|
||||
elemType := TTypes.CreateRecord(baseType.AsRecord.Definition);
|
||||
elemType := TTypes.CreateRecord(baseType.AsRecord.Definition)
|
||||
else if baseType.Kind = stTuple then
|
||||
begin
|
||||
// NEW: Tuple Indexing (requires constant index for strong typing!)
|
||||
if (newIndex.Kind = akConstant) and (newIndex.AsConstant.Value.Kind = vkScalar) then
|
||||
begin
|
||||
var idx := newIndex.AsConstant.Value.AsScalar.Value.AsInt64;
|
||||
var tpl := baseType.AsTuple;
|
||||
if (idx >= 0) and (idx < tpl.Count) then
|
||||
elemType := tpl.Elements[Integer(idx)];
|
||||
end;
|
||||
end;
|
||||
|
||||
elemType := ApplyOptionality(elemType, isOpt);
|
||||
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
|
||||
@@ -655,10 +765,8 @@ var
|
||||
newFieldList: IRecordFieldList;
|
||||
begin
|
||||
R := Node.AsRecordLiteral;
|
||||
// Transform fields using inherited recursion
|
||||
newFieldList := inherited VisitRecordFieldList(R.Fields).AsRecordFieldList;
|
||||
|
||||
// Now analyze the transformed fields
|
||||
var count := newFieldList.Count;
|
||||
SetLength(fieldTypes, count);
|
||||
SetLength(scalarFieldTypes, count);
|
||||
@@ -670,7 +778,6 @@ begin
|
||||
key := field.Key.Value;
|
||||
valType := field.Value.AsTypedNode.StaticType;
|
||||
|
||||
// Check if strict scalar (no optionals allowed in packed ScalarRecord)
|
||||
if (valType.Kind in [stOrdinal, stFloat, stBoolean, stDateTime, stKeyword]) and (not valType.IsOptional) then
|
||||
begin
|
||||
var kind: TScalar.TKind;
|
||||
@@ -693,7 +800,6 @@ begin
|
||||
fieldTypes[i] := TPair<IKeyword, IStaticType>.Create(key, valType);
|
||||
end;
|
||||
|
||||
// Build Definition
|
||||
var scalarDef: IScalarRecordDefinition := nil;
|
||||
var genericDef: IGenericRecordDefinition := nil;
|
||||
var resultType: IStaticType;
|
||||
@@ -720,9 +826,8 @@ var
|
||||
begin
|
||||
C := Node.AsCreateSeries;
|
||||
def := C.Definition;
|
||||
// Simple heuristic for type
|
||||
if def.StartsWith('[') then
|
||||
elemType := TTypes.CreateRecord(nil) // Placeholder, normally parses JSON
|
||||
elemType := TTypes.CreateRecord(nil)
|
||||
else
|
||||
elemType := TTypes.FromScalarKind(TScalar.StringToKind(def));
|
||||
|
||||
@@ -750,11 +855,9 @@ var
|
||||
sourceType: IStaticType;
|
||||
begin
|
||||
P := Node.AsPipeInput;
|
||||
// Transform source identifier (resolves type)
|
||||
newSource := Accept(P.StreamSource).AsIdentifier;
|
||||
sourceType := newSource.AsTypedNode.StaticType;
|
||||
|
||||
// 1. Verify Source is a Series-compatible type
|
||||
if (sourceType.Kind <> stUnknown) then
|
||||
begin
|
||||
if not ((sourceType.Kind = stSeries) or (sourceType.Kind = stRecordSeries)) then
|
||||
@@ -767,7 +870,6 @@ begin
|
||||
end
|
||||
else if (sourceType.Kind = stRecordSeries) then
|
||||
begin
|
||||
// 2. Verify Selectors exist in Record Definition
|
||||
var def := sourceType.AsRecord.Definition;
|
||||
for var sel in P.Selectors do
|
||||
begin
|
||||
@@ -780,7 +882,6 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Reuse selectors (they are just keywords, no type checking needed)
|
||||
Result := TAst.PipeInput(newSource, P.Selectors, Node.Identity.Location);
|
||||
end;
|
||||
|
||||
@@ -800,22 +901,18 @@ begin
|
||||
SetLength(newInputs, P.Inputs.Count);
|
||||
paramTypes := TList<IStaticType>.Create;
|
||||
try
|
||||
// 1. Visit Inputs and collect types for Lambda parameters
|
||||
for i := 0 to P.Inputs.Count - 1 do
|
||||
begin
|
||||
// Recurse on inputs to resolve their sources
|
||||
inputNode := Accept(P.Inputs[i]).AsPipeInput;
|
||||
newInputs[i] := inputNode;
|
||||
|
||||
streamType := inputNode.StreamSource.AsTypedNode.StaticType;
|
||||
|
||||
// Flatten logic: One lambda param per selector
|
||||
for var sel in inputNode.Selectors do
|
||||
begin
|
||||
inferredType := TTypes.Unknown;
|
||||
if streamType.Kind = stRecordSeries then
|
||||
begin
|
||||
// Extract field type from definition
|
||||
var def := streamType.AsRecord.Definition;
|
||||
var idx := def.IndexOf(sel.Value);
|
||||
if idx >= 0 then
|
||||
@@ -823,18 +920,16 @@ begin
|
||||
end
|
||||
else if streamType.Kind = stSeries then
|
||||
begin
|
||||
// If simple series, use its element type
|
||||
if Assigned(streamType.AsSeries.ElementType) then
|
||||
inferredType := streamType.AsSeries.ElementType
|
||||
else
|
||||
inferredType := TTypes.Ordinal; // Fallback default
|
||||
inferredType := TTypes.Ordinal;
|
||||
end;
|
||||
|
||||
paramTypes.Add(inferredType);
|
||||
end;
|
||||
end;
|
||||
|
||||
// 2. Prepare Lambda with Inferred Types
|
||||
lambda := P.Transformation;
|
||||
|
||||
if lambda.Parameters.Count <> paramTypes.Count then
|
||||
@@ -857,11 +952,9 @@ begin
|
||||
if k < paramTypes.Count then paramTypes[k]
|
||||
else TTypes.Unknown;
|
||||
|
||||
// Create new identifier with the inferred type!
|
||||
newParams[k] := TAst.Identifier(oldP.Identity.AsNamed, oldP.Address, typeToInject);
|
||||
end;
|
||||
|
||||
// Recreate Lambda NODE with Typed Parameters
|
||||
var preTypedLambda :=
|
||||
TAst.LambdaExpr(
|
||||
lambda.Identity,
|
||||
@@ -872,24 +965,20 @@ begin
|
||||
lambda.Upvalues,
|
||||
lambda.HasNestedLambdas,
|
||||
lambda.IsPure,
|
||||
TTypes.Unknown // Will be recalculated in Accept
|
||||
TTypes.Unknown
|
||||
);
|
||||
|
||||
// 3. Visit the Lambda (This will now type-check the Body using the types we just injected)
|
||||
var typedLambda := Accept(preTypedLambda).AsLambdaExpression;
|
||||
|
||||
// 4. Infer Pipe Return Type & Validate Strictness
|
||||
var lambdaRetType := typedLambda.AsTypedNode.StaticType.AsMethod.Signatures[0].ReturnType;
|
||||
var pipeType: IStaticType;
|
||||
|
||||
if lambdaRetType.Kind = stRecord then
|
||||
begin
|
||||
// Valid: Record -> RecordSeries
|
||||
pipeType := TTypes.CreateRecordSeries(lambdaRetType.AsRecord.Definition);
|
||||
end
|
||||
else
|
||||
begin
|
||||
// STRICT CHECK: Scalars are NOT allowed.
|
||||
if (lambdaRetType.Kind <> stUnknown) and (lambdaRetType.Kind <> stVoid) then
|
||||
begin
|
||||
if Assigned(FLog) then
|
||||
@@ -898,8 +987,6 @@ begin
|
||||
lambda
|
||||
);
|
||||
end;
|
||||
|
||||
// If Void or Unknown, or Error case:
|
||||
pipeType := TTypes.Unknown;
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user