Pipe Parameter is now a Tuple

This commit is contained in:
Michael Schimmel
2026-01-05 00:13:51 +01:00
parent 242ec9a56e
commit 264314cd93
15 changed files with 322 additions and 1244 deletions
+111 -92
View File
@@ -73,7 +73,6 @@ type
function VisitTuple(const Node: IAstNode): IAstNode;
// Pipe Support
function VisitPipeInput(const Node: IAstNode): IAstNode;
function VisitPipe(const Node: IAstNode): IAstNode;
protected
@@ -242,7 +241,6 @@ begin
Register(akTuple, VisitTuple);
// Pipe Support
Register(akPipeInput, VisitPipeInput);
Register(akPipe, VisitPipe);
end;
@@ -328,8 +326,6 @@ begin
for i := 0 to count - 1 do
begin
newElements[i] := Accept(T.Items[i]);
// FIX: Ensure the node is typed before accessing StaticType.
// Structural nodes (like RecordField) are NOT typed and might crash or return garbage.
if newElements[i].IsTyped then
elementTypes[i] := newElements[i].AsTypedNode.StaticType
else
@@ -347,9 +343,6 @@ begin
firstType := elementTypes[0];
isHomogeneous := True;
// Check for Homogeneity (Exact type equality of all elements)
// Note: If any element is Unknown, we assume Heterogeneous (Tuple) unless all are unknown?
// For now, Unknown breaks homogeneity.
for i := 1 to count - 1 do
begin
if not firstType.IsEqual(elementTypes[i]) then
@@ -361,20 +354,15 @@ begin
if isHomogeneous and (firstType.Kind <> stUnknown) 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...]
// Vector of Matrices -> Higher dimensional Matrix
commonDim := firstType.AsMatrix.Dimensions;
SetLength(newDim, Length(commonDim) + 1);
@@ -518,30 +506,22 @@ var
exprs: ITupleNode;
i: Integer;
begin
// 1. Transform children via inherited logic
// This delegates to TAstTransformer.VisitBlockExpression, which calls Visit(B.Expressions).
// B.Expressions is a Tuple, so it routes to TTypeChecker.VisitTuple.
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
begin
// Check for NIL entries which indicate failed transformation of children
for i := 0 to exprs.Count - 1 do
begin
if exprs.Items[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.Items[exprs.Count - 1].AsTypedNode.StaticType;
end
else
@@ -783,12 +763,6 @@ var
newFields: ITupleNode;
begin
R := Node.AsRecordLiteral;
// FIX: Do NOT use inherited VisitRecordLiteral() here, because it expects an IRecordLiteralNode
// but R.Fields is an ITupleNode. Using inherited would dispatch to VisitRecordLiteral with wrong type
// or trigger recursive confusion.
// Instead, directly visit the fields Tuple. This routes to TTypeChecker.VisitTuple.
newFields := Visit(R.Fields).AsTuple;
var count := newFields.Count;
@@ -798,10 +772,6 @@ begin
for i := 0 to count - 1 do
begin
// Note: Elements of the fields tuple are RecordFieldNodes.
// RecordFieldNodes are NOT Typed (IAstTypedNode), they are structural.
// So we cannot check .StaticType on the field itself.
// We must check the .Value of the field.
var field := newFields.Items[i].AsRecordField;
key := field.Key.Value;
valType := field.Value.AsTypedNode.StaticType;
@@ -876,89 +846,133 @@ end;
// PIPE IMPLEMENTATION (Type Checking)
// =============================================================================
function TTypeChecker.VisitPipeInput(const Node: IAstNode): IAstNode;
var
P: IPipeInputNode;
newSource: IIdentifierNode;
sourceType: IStaticType;
begin
P := Node.AsPipeInput;
newSource := Accept(P.StreamSource).AsIdentifier;
sourceType := newSource.AsTypedNode.StaticType;
if (sourceType.Kind <> stUnknown) then
begin
if not ((sourceType.Kind = stSeries) or (sourceType.Kind = stRecordSeries)) then
begin
if Assigned(FLog) then
FLog.AddError(
Format('Pipe input "%s" must be a Series or RecordSeries, but got %s.', [newSource.Name, sourceType.ToString]),
Node
);
end
else if (sourceType.Kind = stRecordSeries) then
begin
var def := sourceType.AsRecord.Definition;
for var sel in P.Selectors do
begin
if def.IndexOf(sel.Value) < 0 then
begin
if Assigned(FLog) then
FLog.AddError(Format('Field ":%s" not found in stream "%s".', [sel.Value.Name, newSource.Name]), sel);
end;
end;
end;
end;
Result := TAst.PipeInput(newSource, P.Selectors, Node.Identity.Location);
end;
function TTypeChecker.VisitPipe(const Node: IAstNode): IAstNode;
var
P: IPipeNode;
rawInputs: ITupleNode;
i, k: Integer;
inputNode: IPipeInputNode;
newInputs: TArray<IPipeInputNode>;
streamType: IStaticType;
newInputs: TList<IAstNode>;
paramTypes: TList<IStaticType>;
lambda: ILambdaExpressionNode;
newParams: TArray<IIdentifierNode>;
newParamsAsNodes: TArray<IAstNode>;
inferredType: IStaticType;
// Iteration vars
inputPair: IAstNode;
rawTuple: ITupleNode;
streamNode: IIdentifierNode;
selectorsNode: ITupleNode;
newSelectors: TList<IAstNode>;
streamType: IStaticType;
begin
P := Node.AsPipe;
SetLength(newInputs, P.Inputs.Count);
rawInputs := P.Inputs; // This is the Tuple of Tuples [[id [sel]] ...]
newInputs := TList<IAstNode>.Create;
paramTypes := TList<IStaticType>.Create;
try
for i := 0 to P.Inputs.Count - 1 do
// 1. Iterate over input definitions
for i := 0 to rawInputs.Count - 1 do
begin
inputNode := Accept(P.Inputs[i]).AsPipeInput;
newInputs[i] := inputNode;
inputPair := rawInputs.Items[i];
streamType := inputNode.StreamSource.AsTypedNode.StaticType;
for var sel in inputNode.Selectors do
// Validate Structure: [StreamSource, [Selectors]]
if inputPair.Kind <> akTuple then
begin
inferredType := TTypes.Unknown;
if streamType.Kind = stRecordSeries then
if Assigned(FLog) then
FLog.AddError('Pipe input must be a vector: [Source [Selectors]].', inputPair);
continue;
end;
rawTuple := inputPair.AsTuple;
if rawTuple.Count <> 2 then
begin
if Assigned(FLog) then
FLog.AddError('Pipe input vector must have exactly 2 elements.', inputPair);
continue;
end;
// Element 0: Stream Identifier
if rawTuple.Items[0].Kind <> akIdentifier then
begin
if Assigned(FLog) then
FLog.AddError('Pipe source must be an identifier.', rawTuple.Items[0]);
continue;
end;
// Resolve Stream Identifier (Type Binding)
streamNode := Accept(rawTuple.Items[0]).AsIdentifier;
streamType := streamNode.StaticType;
// Validate Stream Type
if (streamType.Kind <> stUnknown) then
begin
if not ((streamType.Kind = stSeries) or (streamType.Kind = stRecordSeries)) then
begin
var def := streamType.AsRecord.Definition;
var idx := def.IndexOf(sel.Value);
if idx >= 0 then
inferredType := TTypes.FromScalarKind(def[idx]);
end
else if streamType.Kind = stSeries then
if Assigned(FLog) then
FLog.AddError(
Format('Pipe input "%s" must be a Series or RecordSeries, but got %s.', [streamNode.Name, streamType.ToString]),
streamNode
);
end;
end;
// Element 1: Selector Vector
if rawTuple.Items[1].Kind <> akTuple then
begin
if Assigned(FLog) then
FLog.AddError('Pipe selectors must be a vector of keywords.', rawTuple.Items[1]);
continue;
end;
selectorsNode := rawTuple.Items[1].AsTuple;
newSelectors := TList<IAstNode>.Create;
try
// Iterate Selectors
for k := 0 to selectorsNode.Count - 1 do
begin
if Assigned(streamType.AsSeries.ElementType) then
inferredType := streamType.AsSeries.ElementType
else
inferredType := TTypes.Ordinal;
var selItem := selectorsNode.Items[k];
if selItem.Kind <> akKeyword then
begin
if Assigned(FLog) then
FLog.AddError('Selector must be a keyword.', selItem);
continue;
end;
var kw := selItem.AsKeyword;
newSelectors.Add(kw); // Keywords don't need 'Accept' as they are constant, but we keep them structurally
// Infer Type for Lambda Parameter
inferredType := TTypes.Unknown;
if streamType.Kind = stRecordSeries then
begin
var def := streamType.AsRecord.Definition;
var idx := def.IndexOf(kw.Value);
if idx >= 0 then
inferredType := TTypes.FromScalarKind(def[idx])
else if Assigned(FLog) then
FLog.AddError(Format('Field ":%s" not found in stream "%s".', [kw.Value.Name, streamNode.Name]), selItem);
end
else if streamType.Kind = stSeries then
begin
if Assigned(streamType.AsSeries.ElementType) then
inferredType := streamType.AsSeries.ElementType
else
inferredType := TTypes.Ordinal;
end;
paramTypes.Add(inferredType);
end;
paramTypes.Add(inferredType);
// Reconstruct the typed Input Tuple [StreamIdent, [Selectors]]
var typedSelectorTuple := TAst.Tuple(selectorsNode.Identity, newSelectors.ToArray, TTypes.Unknown);
var typedInputPair := TAst.Tuple(rawTuple.Identity, [streamNode, typedSelectorTuple], TTypes.Unknown);
newInputs.Add(typedInputPair);
finally
newSelectors.Free;
end;
end;
// 2. Process Transformation Lambda
lambda := P.Transformation;
if lambda.Parameters.Count <> paramTypes.Count then
@@ -1004,6 +1018,7 @@ begin
var typedLambda := Accept(preTypedLambda).AsLambdaExpression;
// 3. Determine Result Type
var lambdaRetType := typedLambda.AsTypedNode.StaticType.AsMethod.Signatures[0].ReturnType;
var pipeType: IStaticType;
@@ -1024,8 +1039,12 @@ begin
pipeType := TTypes.Unknown;
end;
Result := TAst.Pipe(Node.Identity, TPipeInputList.Create(newInputs, P.Inputs.Identity), typedLambda, pipeType);
// Reconstruct the Pipe Node with typed Inputs tuple and typed Lambda
var typedInputsTuple := TAst.Tuple(rawInputs.Identity, newInputs.ToArray, TTypes.Unknown);
Result := TAst.Pipe(Node.Identity, typedInputsTuple, typedLambda, pipeType);
finally
newInputs.Free;
paramTypes.Free;
end;
end;