diff --git a/Src/AST/Myc.Ast.Analysis.Purity.pas b/Src/AST/Myc.Ast.Analysis.Purity.pas index 505713e..a7303f1 100644 --- a/Src/AST/Myc.Ast.Analysis.Purity.pas +++ b/Src/AST/Myc.Ast.Analysis.Purity.pas @@ -55,6 +55,8 @@ type function VisitMacroExpansionNode(const Node: IAstNode): Boolean; function VisitNop(const Node: IAstNode): Boolean; + function VisitTuple(const Node: IAstNode): Boolean; + // Pipe Support (Structural Check) function VisitPipeInput(const Node: IAstNode): Boolean; function VisitPipeSelectorList(const Node: IAstNode): Boolean; @@ -132,6 +134,8 @@ begin Register(akRecur, VisitRecurNode); Register(akNop, VisitNop); + Register(akTuple, VisitTuple); + // Pipes Register(akPipeInput, VisitPipeInput); Register(akPipeSelectorList, VisitPipeSelectorList); @@ -378,4 +382,17 @@ begin Result := IsNodePure(P.Inputs) and IsNodePure(P.Transformation); end; +function TPurityAnalyzer.VisitTuple(const Node: IAstNode): Boolean; +var + item: IAstNode; +begin + // Ein Tupel ist rein, wenn alle Elemente rein sind. + for item in Node.AsTuple.Elements do + begin + if not IsNodePure(item) then + exit(False); + end; + Result := True; +end; + end. diff --git a/Src/AST/Myc.Ast.Compiler.TCO.pas b/Src/AST/Myc.Ast.Compiler.TCO.pas index 97ba728..a6c9a1f 100644 --- a/Src/AST/Myc.Ast.Compiler.TCO.pas +++ b/Src/AST/Myc.Ast.Compiler.TCO.pas @@ -38,6 +38,7 @@ type function VisitRecurNode(const Node: IAstNode): IAstNode; function VisitMacroExpansionNode(const Node: IAstNode): IAstNode; function VisitFunctionCall(const Node: IAstNode): IAstNode; + function VisitTuple(const Node: IAstNode): IAstNode; protected procedure SetupHandlers; override; @@ -81,6 +82,7 @@ begin Register(akRecur, VisitRecurNode); Register(akMacroExpansion, VisitMacroExpansionNode); Register(akFunctionCall, VisitFunctionCall); + Register(akTuple, VisitTuple); end; class function TAstTCO.Optimize(const RootNode: IAstNode): IAstNode; @@ -314,4 +316,38 @@ begin Result := TAst.FunctionCall(Node.Identity, newCallee, newArgsList, C.StaticType, isTailCall, C.StaticTarget, C.IsTargetPure); end; +function TAstTCO.VisitTuple(const Node: IAstNode): IAstNode; +var + T: ITupleNode; + newElements: TArray; + i: Integer; + hasChanged: Boolean; + savedNextIsTail: Boolean; +begin + T := Node.AsTuple; + savedNextIsTail := FNextIsTail; // Zustand sichern (wahrscheinlich irrelevant, aber sauber) + + // Elemente in einem Tupel sind NIEMALS in Tail-Position + FNextIsTail := False; + + hasChanged := False; + SetLength(newElements, Length(T.Elements)); + + try + for i := 0 to High(T.Elements) do + begin + newElements[i] := Accept(T.Elements[i]); + if newElements[i] <> T.Elements[i] then + hasChanged := True; + end; + finally + FNextIsTail := savedNextIsTail; + end; + + if hasChanged then + Result := TAst.Tuple(Node.Identity, newElements, T.StaticType) + else + Result := Node; +end; + end. diff --git a/Src/AST/Myc.Ast.Compiler.TypeChecker.pas b/Src/AST/Myc.Ast.Compiler.TypeChecker.pas index 34e19b7..0da31fc 100644 --- a/Src/AST/Myc.Ast.Compiler.TypeChecker.pas +++ b/Src/AST/Myc.Ast.Compiler.TypeChecker.pas @@ -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; + elementTypes: TArray; + i: Integer; + + // Inference variables + firstType: IStaticType; + isHomogeneous: Boolean; + commonDim: TArray; + newDim: TArray; + + 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.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.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; diff --git a/Src/AST/Myc.Ast.Debugger.pas b/Src/AST/Myc.Ast.Debugger.pas index 22c5d57..f5b4273 100644 --- a/Src/AST/Myc.Ast.Debugger.pas +++ b/Src/AST/Myc.Ast.Debugger.pas @@ -151,6 +151,14 @@ begin akLambdaExpression: Result := 'FN'; akRecordLiteral: Result := 'RECORD'; akPipe: Result := 'PIPE'; + + // NEW: + akIndexer: Result := 'INDEXER'; + akMemberAccess: Result := 'MEMBER'; + akTuple: Result := 'TUPLE'; + akCreateSeries: Result := 'NEW-SERIES'; + akAddSeriesItem: Result := 'ADD-ITEM'; + else Result := ''; // Silence leaf nodes like Constants and Keywords to reduce noise end; diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas index f78c0a6..4402a19 100644 --- a/Src/AST/Myc.Ast.Dumper.pas +++ b/Src/AST/Myc.Ast.Dumper.pas @@ -62,6 +62,8 @@ type function VisitRecordFieldList(const Node: IAstNode): TVoid; function VisitRecordField(const Node: IAstNode): TVoid; + function VisitTuple(const Node: IAstNode): TVoid; + // Pipe Visitors function VisitPipeInput(const Node: IAstNode): TVoid; function VisitPipeSelectorList(const Node: IAstNode): TVoid; @@ -143,6 +145,8 @@ begin Register(akRecur, VisitRecurNode); Register(akNop, VisitNop); + Register(akTuple, VisitTuple); + Register(akPipeInput, VisitPipeInput); Register(akPipeSelectorList, VisitPipeSelectorList); Register(akPipeInputList, VisitPipeInputList); @@ -625,4 +629,20 @@ begin Unindent; end; +function TAstDumper.VisitTuple(const Node: IAstNode): TVoid; +var + T: ITupleNode; + i: Integer; +begin + T := Node.AsTuple; + LogFmt('Tuple (%d elements)', [Length(T.Elements)], Node); + Indent; + for i := 0 to High(T.Elements) do + begin + LogFmt('Element %d:', [i]); + Visit(T.Elements[i]); + end; + Unindent; +end; + end. diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 6aa51c1..df45568 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -21,13 +21,16 @@ type private FScope: IExecutionScope; protected + // Haupt-Dispatch-Methode function Visit(const Node: IAstNode): TDataValue; virtual; function CreateVisitorFactory: TEvaluatorFactory; virtual; - // Nur noch die für die Ausführung relevanten Methoden + // Besuchermethoden function VisitConstant(const N: IConstantNode): TDataValue; virtual; function VisitIdentifier(const N: IIdentifierNode): TDataValue; virtual; function VisitKeyword(const N: IKeywordNode): TDataValue; virtual; + function VisitTuple(const N: ITupleNode): TDataValue; virtual; // <--- WICHTIG + function VisitIfExpression(const N: IIfExpressionNode): TDataValue; virtual; function VisitCondExpression(const N: ICondExpressionNode): TDataValue; virtual; function VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue; virtual; @@ -89,10 +92,12 @@ end; function TEvaluatorVisitor.Visit(const Node: IAstNode): TDataValue; begin // Der Hot-Path: Direkter Dispatch ohne Umweg über ungenutzte Methoden. + // HIER fehlte wahrscheinlich akTuple! case Node.Kind of akConstant: Result := VisitConstant(Node.AsConstant); akIdentifier: Result := VisitIdentifier(Node.AsIdentifier); akKeyword: Result := VisitKeyword(Node.AsKeyword); + akTuple: Result := VisitTuple(Node.AsTuple); // <--- KORREKTUR akIfExpression: Result := VisitIfExpression(Node.AsIfExpression); akCondExpression: Result := VisitCondExpression(Node.AsCondExpression); akLambdaExpression: Result := VisitLambdaExpression(Node.AsLambdaExpression); @@ -173,6 +178,21 @@ begin Result := FScope[N.Address]; end; +function TEvaluatorVisitor.VisitTuple(const N: ITupleNode): TDataValue; +var + elements: TArray; + i: Integer; + nodes: TArray; +begin + nodes := N.Elements; + SetLength(elements, Length(nodes)); + for i := 0 to High(nodes) do + elements[i] := Visit(nodes[i]); + + // Uses the implicit operator: TArray -> TDataValue (vkTuple) + Result := elements; +end; + function TEvaluatorVisitor.VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue; var capturedCells: TArray; @@ -334,20 +354,30 @@ begin if base.IsVoid then exit(TDataValue.Void); idx := Visit(N.Index); - if (base.Kind = vkSeries) then - Result := TDataValue(base.AsSeries.Items[Integer(idx.AsScalar.Value.AsInt64)]) - else if (base.Kind = vkRecordSeries) then - begin - var rs := base.AsRecordSeries; - var vals: TArray; - SetLength(vals, rs.Def.Count); - var i64 := idx.AsScalar.Value.AsInt64; - for var k := 0 to rs.Def.Count - 1 do - vals[k] := rs.Fields[rs.Def.Keywords[k]].Items[Integer(i64)].Value; - Result := TScalarRecord.Create(rs.Def, vals); - end + + case base.Kind of + vkSeries: Result := TDataValue(base.AsSeries.Items[Integer(idx.AsScalar.Value.AsInt64)]); + vkRecordSeries: + begin + var rs := base.AsRecordSeries; + var vals: TArray; + SetLength(vals, rs.Def.Count); + var i64 := idx.AsScalar.Value.AsInt64; + for var k := 0 to rs.Def.Count - 1 do + vals[k] := rs.Fields[rs.Def.Keywords[k]].Items[Integer(i64)].Value; + Result := TScalarRecord.Create(rs.Def, vals); + end; + vkTuple: // <--- WICHTIG FÜR (get x 3) + begin + var tpl := base.AsTuple; + var i := idx.AsScalar.Value.AsInt64; + if (i < 0) or (i >= tpl.Count) then + raise EEvaluatorException.Create('Tuple index out of bounds'); + Result := tpl.Items[Integer(i)]; + end; else raise EEvaluatorException.Create('Indexer error'); + end; end; function TEvaluatorVisitor.VisitMemberAccess(const N: IMemberAccessNode): TDataValue; diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas index 5a53d9c..eca90d2 100644 --- a/Src/AST/Myc.Ast.JSON.pas +++ b/Src/AST/Myc.Ast.JSON.pas @@ -51,6 +51,7 @@ type function JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode; function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode; function JsonToNopNode(const AObj: TJSONObject): INopNode; + function JsonToTupleNode(const AObj: TJSONObject): ITupleNode; strict private // Serialization Visitors (IAstNode signature) @@ -85,6 +86,8 @@ type function VisitSeriesLength(const Node: IAstNode): TJSONObject; function VisitNop(const Node: IAstNode): TJSONObject; + function VisitTuple(const Node: IAstNode): TJSONObject; + function VisitPipeInput(const Node: IAstNode): TJSONObject; function VisitPipeSelectorList(const Node: IAstNode): TJSONObject; function VisitPipeInputList(const Node: IAstNode): TJSONObject; @@ -144,6 +147,8 @@ begin Register(akRecur, VisitRecurNode); Register(akNop, VisitNop); + Register(akTuple, VisitTuple); + Register(akPipeInput, VisitPipeInput); Register(akPipeSelectorList, VisitPipeSelectorList); Register(akPipeInputList, VisitPipeInputList); @@ -531,6 +536,24 @@ begin Result.AddPair('NodeType', TJSONString.Create('Nop')); end; +function TJsonAstConverter.VisitTuple(const Node: IAstNode): TJSONObject; +var + T: ITupleNode; + elemsArray: TJSONArray; + i: Integer; +begin + T := Node.AsTuple; + Result := TJSONObject.Create; + Result.AddPair('NodeType', TJSONString.Create('Tuple')); + + elemsArray := TJSONArray.Create; + for i := 0 to High(T.Elements) do + begin + elemsArray.Add(Visit(T.Elements[i])); + end; + Result.AddPair('Elements', elemsArray); +end; + function TJsonAstConverter.VisitParameterList(const Node: IAstNode): TJSONObject; begin Result := nil; @@ -548,6 +571,21 @@ begin Result := nil; end; +function TJsonAstConverter.JsonToTupleNode(const AObj: TJSONObject): ITupleNode; +var + elems: TArray; + elemsArray: TJSONArray; + i: Integer; +begin + elemsArray := AObj.GetValue('Elements'); + SetLength(elems, elemsArray.Count); + for i := 0 to elemsArray.Count - 1 do + begin + elems[i] := JsonToNode(elemsArray.Items[i]); + end; + Result := TAst.Tuple(elems); // Factory handles identity/type defaults +end; + function TJsonAstConverter.VisitPipeInput(const Node: IAstNode): TJSONObject; begin raise ENotSupportedException.Create('JSON Pipe Serialization not implemented.'); @@ -660,6 +698,8 @@ begin Result := JsonToSeriesLengthNode(obj) else if nodeType = 'Nop' then Result := JsonToNopNode(obj) + else if nodeType = 'Tuple' then + Result := JsonToTupleNode(obj) else raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s".', [nodeType]); end; diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index accfd59..885e93f 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -64,12 +64,6 @@ type property Errors: TArray read FErrors; end; - // Exception for the new visitor registry dispatch - ENoHandlerException = class(EAstException) - public - constructor Create(const KindName: string); - end; - TCompiledFunction = record public Func: TDataValue.TFunc; @@ -94,6 +88,7 @@ type IExpressionList = interface; IRecordFieldList = interface; IRecordFieldNode = interface; + ITupleNode = interface; // Control Flow & Structure IIfExpressionNode = interface; @@ -134,6 +129,9 @@ type akExpressionList, akRecordFieldList, akRecordField, + // Tuple + akTuple, + // Control Flow akIfExpression, akCondExpression, akLambdaExpression, @@ -195,6 +193,7 @@ type function AsExpressionList: IExpressionList; function AsRecordFieldList: IRecordFieldList; function AsRecordField: IRecordFieldNode; + function AsTuple: ITupleNode; function AsIfExpression: IIfExpressionNode; function AsCondExpression: ICondExpressionNode; @@ -298,6 +297,13 @@ type property Value: IKeyword read GetValue; end; + ITupleNode = interface(IAstTypedNode) + {$region 'private'} + function GetElements: TArray; + {$endregion} + property Elements: TArray read GetElements; + end; + IIfExpressionNode = interface(IAstTypedNode) {$region 'private'} function GetCondition: IAstNode; @@ -543,6 +549,7 @@ type function AsExpressionList: IExpressionList; virtual; function AsRecordFieldList: IRecordFieldList; virtual; function AsRecordField: IRecordFieldNode; virtual; + function AsTuple: ITupleNode; virtual; function AsIfExpression: IIfExpressionNode; virtual; function AsCondExpression: ICondExpressionNode; virtual; @@ -696,6 +703,17 @@ type function AsKeyword: IKeywordNode; override; end; + TTupleNode = class(TAstTypedNode, ITupleNode) + private + FElements: TArray; + function GetElements: TArray; + protected + function GetKind: TAstNodeKind; override; + public + constructor Create(const AElements: TArray; const AIdentity: IAstIdentity; const AStaticType: IStaticType); + function AsTuple: ITupleNode; override; + end; + TCreateSeriesNode = class(TAstTypedNode, ICreateSeriesNode) private FDefIdentity: IDefinitionIdentity; @@ -1207,13 +1225,6 @@ begin end; end; -{ ENoHandlerException } - -constructor ENoHandlerException.Create(const KindName: string); -begin - inherited CreateFmt('No visitor handler registered for AST node kind: %s', [KindName]); -end; - { TAstNode } constructor TAstNode.Create(const AIdentity: IAstIdentity); @@ -1280,6 +1291,11 @@ begin Result := nil; end; +function TAstNode.AsTuple: ITupleNode; +begin + Result := nil; +end; + function TAstNode.AsIfExpression: IIfExpressionNode; begin Result := nil; @@ -1608,6 +1624,29 @@ begin Result := FKeywordIdentity.Value; end; +{ TTupleNode } + +constructor TTupleNode.Create(const AElements: TArray; const AIdentity: IAstIdentity; const AStaticType: IStaticType); +begin + inherited Create(AStaticType, AIdentity); + FElements := AElements; +end; + +function TTupleNode.AsTuple: ITupleNode; +begin + Result := Self; +end; + +function TTupleNode.GetElements: TArray; +begin + Result := FElements; +end; + +function TTupleNode.GetKind: TAstNodeKind; +begin + Result := akTuple; +end; + { TCreateSeriesNode } constructor TCreateSeriesNode.Create(const AIdentity: IDefinitionIdentity; const AStaticType: IStaticType); diff --git a/Src/AST/Myc.Ast.Refactoring.Remove.pas b/Src/AST/Myc.Ast.Refactoring.Remove.pas index 8a6c2a5..2e2dc55 100644 --- a/Src/AST/Myc.Ast.Refactoring.Remove.pas +++ b/Src/AST/Myc.Ast.Refactoring.Remove.pas @@ -39,6 +39,7 @@ type function VisitIndexer(const Node: IAstNode): IAstNode; function VisitMemberAccess(const Node: IAstNode): IAstNode; function VisitAddSeriesItem(const Node: IAstNode): IAstNode; + function VisitTuple(const Node: IAstNode): IAstNode; protected procedure SetupHandlers; override; @@ -81,6 +82,8 @@ begin Register(akIndexer, VisitIndexer); Register(akMemberAccess, VisitMemberAccess); Register(akAddSeriesItem, VisitAddSeriesItem); + + Register(akTuple, VisitTuple); end; class function TAstNodeRemover.TryRemove(const RootNode, TargetNode: IAstNode; out NewRootNode: IAstNode): Boolean; @@ -429,4 +432,40 @@ begin Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, A.StaticType); end; +function TAstNodeRemover.VisitTuple(const Node: IAstNode): IAstNode; +var + T: ITupleNode; + newElements: TArray; + list: TList; + item, transformed: IAstNode; + i: Integer; + hasChanged: Boolean; +begin + T := Node.AsTuple; + list := TList.Create; + hasChanged := False; + try + for i := 0 to High(T.Elements) do + begin + item := T.Elements[i]; + transformed := Accept(item); + + if transformed <> item then + hasChanged := True; + + if Assigned(transformed) then + list.Add(transformed) + else + hasChanged := True; // Item removed + end; + + if not hasChanged then + Exit(Node); + + Result := TAst.Tuple(Node.Identity, list.ToArray, T.StaticType); + finally + list.Free; + end; +end; + end. diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index 97772fc..1e4a3b7 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -437,8 +437,8 @@ begin items.Add(ParseExpression.Node); end; - var id := TIdentities.List('[', ']', ' ', startLoc); - Result := TArgumentList.Create(items.ToArray, id); + // CHANGED: Use Tuple factory instead of ArgumentList + Result := TAst.Tuple(items.ToArray, startLoc); finally items.Free; end; @@ -447,15 +447,19 @@ end; function TParser.ExtractIdentifiers(const Node: IAstNode; const Context: string): TArray; var - list: IArgumentList; + list: TArray; i: Integer; begin - if Node.Kind <> akArgumentList then + // Support both Tuple (new syntax [...]) and ArgumentList (internal legacy) + if Node.Kind = akTuple then + list := Node.AsTuple.Elements + else if Node.Kind = akArgumentList then + list := Node.AsArgumentList.ToArray + else ErrorFmt('%s expects a vector [...]', [Context]); - list := Node.AsArgumentList; - SetLength(Result, list.Count); - for i := 0 to list.Count - 1 do + SetLength(Result, Length(list)); + for i := 0 to High(list) do begin if list[i].Kind <> akIdentifier then ErrorFmt('%s vector must contain only identifiers.', [Context]); @@ -536,16 +540,18 @@ begin if Length(tailNodes) <> 2 then Error('Syntax Error: ''pipe'' requires [inputs] vector and (fn) transformation.'); - if tailNodes[0].Kind <> akArgumentList then + // Input vector is now a TupleNode + if tailNodes[0].Kind <> akTuple then Error('Syntax Error: ''pipe'' first argument must be a vector [...].'); - var rawInputs := tailNodes[0].AsArgumentList; - if (rawInputs.Count mod 2) <> 0 then + + var rawInputs := tailNodes[0].AsTuple.Elements; + if (Length(rawInputs) mod 2) <> 0 then Error('Syntax Error: Pipe inputs must be pairs of Identifier and Selector Vector (e.g. [btc [:Close]]).'); var pipeInputs := TList.Create; try var k := 0; - while k < rawInputs.Count do + while k < Length(rawInputs) do begin if rawInputs[k].Kind <> akIdentifier then Error('Syntax Error: Pipe input stream must be an identifier.'); @@ -554,16 +560,16 @@ begin var streamId := rawInputs[k].AsIdentifier; var selKeywords: TArray; - // Strict format: Selector must be a vector [...] containing keywords - if selNode.Kind <> akArgumentList then + // Strict format: Selector must be a vector [...] (TupleNode) containing keywords + if selNode.Kind <> akTuple then Error('Syntax Error: Pipe selector must be a vector of keywords (e.g. [:Close :High]).'); - var selList := selNode.AsArgumentList; - if selList.Count = 0 then + var selList := selNode.AsTuple.Elements; + if Length(selList) = 0 then Error('Syntax Error: Selector list cannot be empty.'); - SetLength(selKeywords, selList.Count); - for var m := 0 to selList.Count - 1 do + SetLength(selKeywords, Length(selList)); + for var m := 0 to High(selList) do begin if selList[m].Kind <> akKeyword then Error('Syntax Error: Selector vector must contain only keywords.'); diff --git a/Src/AST/Myc.Ast.Types.pas b/Src/AST/Myc.Ast.Types.pas index cf30296..76e2118 100644 --- a/Src/AST/Myc.Ast.Types.pas +++ b/Src/AST/Myc.Ast.Types.pas @@ -558,7 +558,7 @@ end; function TSeriesType.ToString: string; begin - Result := 'Series<' + FElementType.ToString + '>'; + Result := inherited + '<' + FElementType.ToString + '>'; end; // --- Tuple --- @@ -651,14 +651,14 @@ var begin sb := TStringBuilder.Create; try - sb.Append('('); + sb.Append('['); for i := 0 to High(FElements) do begin sb.Append(FElements[i].ToString); if i < High(FElements) then sb.Append(', '); end; - sb.Append(')'); + sb.Append(']'); Result := sb.ToString; finally sb.Free; @@ -740,7 +740,7 @@ end; function TVectorType.ToString: string; begin - Result := Format('[%s; %d]', [FElementType.ToString, FCount]); + Result := inherited + Format('[%s; %d]', [FElementType.ToString, FCount]); end; // --- Matrix --- @@ -846,7 +846,7 @@ begin sb.Append('x'); end; sb.Append(']'); - Result := sb.ToString; + Result := inherited + sb.ToString; finally sb.Free; end; diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 30bfea5..10c43ef 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -83,6 +83,9 @@ type function VisitSeriesLength(const N: IAstNode): IAstNode; function VisitRecurNode(const N: IAstNode): IAstNode; + // Tuples + function VisitTuple(const N: IAstNode): IAstNode; + // Pipes function VisitPipeInput(const N: IAstNode): IAstNode; function VisitPipeSelectorList(const N: IAstNode): IAstNode; @@ -125,6 +128,8 @@ type function WalkSeriesLength(const N: IAstNode): TVoid; function WalkRecurNode(const N: IAstNode): TVoid; + function WalkTuple(const N: IAstNode): TVoid; + function WalkPipeInput(const N: IAstNode): TVoid; function WalkPipeSelectorList(const N: IAstNode): TVoid; function WalkPipeInputList(const N: IAstNode): TVoid; @@ -160,8 +165,8 @@ end; function TAstVisitor.DefaultSentinel(const Node: IAstNode): T; begin - // Controlled crash for missing handlers. - raise ENoHandlerException.Create(Node.Kind.ToString); + Assert(false, 'No visitor handler registered for AST node kind: ' + Node.Kind.ToString + ' in class ' + ClassName); + exit(Default(T)); end; procedure TAstVisitor.SetupHandlers; @@ -235,6 +240,8 @@ begin Register(akSeriesLength, VisitSeriesLength); Register(akRecur, VisitRecurNode); + Register(akTuple, VisitTuple); + Register(akPipeInput, VisitPipeInput); Register(akPipeSelectorList, VisitPipeSelectorList); Register(akPipeInputList, VisitPipeInputList); @@ -716,6 +723,33 @@ begin Result := TAst.Pipe(N.Identity, inp, trans, P.StaticType); end; +function TAstTransformer.VisitTuple(const N: IAstNode): IAstNode; +var + T: ITupleNode; + hasChanged: Boolean; + newElements: TArray; + item, newItem: IAstNode; + i: Integer; +begin + T := N.AsTuple; + hasChanged := False; + SetLength(newElements, Length(T.Elements)); + + for i := 0 to High(T.Elements) do + begin + item := T.Elements[i]; + newItem := Visit(item); + newElements[i] := newItem; + if item <> newItem then + hasChanged := True; + end; + + if not hasChanged then + Result := N + else + Result := TAst.Tuple(N.Identity, newElements, N.AsTypedNode.StaticType); +end; + { TAstVisitor (Walker) } procedure TAstVisitor.SetupHandlers; @@ -756,6 +790,8 @@ begin Register(akSeriesLength, WalkSeriesLength); Register(akRecur, WalkRecurNode); + Register(akTuple, WalkTuple); + // Pipes Register(akPipeInput, WalkPipeInput); Register(akPipeSelectorList, WalkPipeSelectorList); @@ -937,4 +973,12 @@ begin Visit(P.Transformation); end; +function TAstVisitor.WalkTuple(const N: IAstNode): TVoid; +var + item: IAstNode; +begin + for item in N.AsTuple.Elements do + Visit(item); +end; + end. diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 259876d..2030616 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -32,7 +32,6 @@ type ARegisterLibraries: Boolean = False ): IExecutionScope; static; - // ... (Keep existing methods: Identifier, Constant, Keyword, CreateSeries, Nop, IfExpr, CondExpr, LambdaExpr, MacroDef, Quotes, Call, etc.) ... // [Creation] Raw Name -> New INamedIdentity class function Identifier(const AName: string; const Loc: ISourceLocation = nil): IIdentifierNode; overload; static; class function Identifier( @@ -48,6 +47,13 @@ type class function Keyword(const AName: string; const Loc: ISourceLocation = nil): IKeywordNode; overload; static; class function Keyword(const Identity: IKeywordIdentity): IKeywordNode; overload; static; + class function Tuple(const Elements: TArray; const Loc: ISourceLocation = nil): ITupleNode; overload; static; + class function Tuple( + const Identity: IAstIdentity; + const Elements: TArray; + const AStaticType: IStaticType = nil + ): ITupleNode; overload; static; + class function CreateSeries(const ADefinition: String; const Loc: ISourceLocation = nil): ICreateSeriesNode; overload; static; class function CreateSeries( const Identity: IDefinitionIdentity; @@ -261,12 +267,10 @@ type const AStaticType: IStaticType = nil ): ISeriesLengthNode; overload; static; - // --- PIPES (Updated) --- + // --- PIPES --- - // Helper: Create a SelectorList from an array of KeywordNodes class function PipeSelectorList(const AKeywords: TArray; const Loc: ISourceLocation = nil): IPipeSelectorList; static; - // Updated PipeInput: Accepts IPipeSelectorList instead of single Keyword class function PipeInput( const AStream: IIdentifierNode; const ASelectors: IPipeSelectorList; @@ -389,6 +393,23 @@ begin Result := TKeywordNode.Create(Identity); end; +class function TAst.Tuple(const Elements: TArray; const Loc: ISourceLocation): ITupleNode; +begin + var id := TIdentities.List('[', ']', ' ', Loc); + Result := TTupleNode.Create(Elements, id, TTypes.Unknown); +end; + +class function TAst.Tuple(const Identity: IAstIdentity; const Elements: TArray; const AStaticType: IStaticType): ITupleNode; +begin + Result := + TTupleNode.Create( + Elements, + Identity, + if AStaticType <> nil then AStaticType + else TTypes.Unknown + ); +end; + class function TAst.CreateSeries(const ADefinition: String; const Loc: ISourceLocation): ICreateSeriesNode; begin var id := TIdentities.Definition(ADefinition, Loc); diff --git a/Src/AST/Myc.Fmx.AstEditor.Render.pas b/Src/AST/Myc.Fmx.AstEditor.Render.pas index b3abc16..3482087 100644 --- a/Src/AST/Myc.Fmx.AstEditor.Render.pas +++ b/Src/AST/Myc.Fmx.AstEditor.Render.pas @@ -228,6 +228,7 @@ end; procedure TVisualNode.AddChild(Child: TVisualNode); begin + Assert(Child <> nil); if Child.Parent <> nil then Child.Parent.RemoveChild(Child); diff --git a/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas b/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas index 5329d9b..82fa1c7 100644 --- a/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas +++ b/Src/AST/Myc.Fmx.AstEditor.Visualizer.pas @@ -45,6 +45,9 @@ type function VisitRecordFieldList(const Node: IAstNode): TAstViewNode; function VisitRecordField(const Node: IAstNode): TAstViewNode; + // Tuple + function VisitTuple(const Node: IAstNode): TAstViewNode; + // Control Flow function VisitIfExpression(const Node: IAstNode): TAstViewNode; function VisitCondExpression(const Node: IAstNode): TAstViewNode; @@ -138,6 +141,7 @@ begin Register(akExpressionList, VisitExpressionList); Register(akRecordFieldList, VisitRecordFieldList); Register(akRecordField, VisitRecordField); + Register(akTuple, VisitTuple); // <--- NEW // Control Flow Register(akIfExpression, VisitIfExpression); @@ -261,6 +265,11 @@ begin Result := TAstViewNode.Create(Self, TRecordFieldHandler.Create(Node.AsRecordField)); end; +function TAstVisualizer.VisitTuple(const Node: IAstNode): TAstViewNode; +begin + Result := TAstViewNode.Create(Self, TNopNodeHandler.Create(Node.AsNop)); +end; + // Control Flow function TAstVisualizer.VisitBlockExpression(const Node: IAstNode): TAstViewNode; diff --git a/Src/Data/Myc.Data.Tuple.pas b/Src/Data/Myc.Data.Tuple.pas index 3850289..cf96c54 100644 --- a/Src/Data/Myc.Data.Tuple.pas +++ b/Src/Data/Myc.Data.Tuple.pas @@ -12,6 +12,34 @@ type property Count: Integer read GetCount; end; + // Concrete generic implementation + TTupleImpl = class(TInterfacedObject, ITuple) + private + FItems: TArray; + function GetItems(Idx: Integer): T; + function GetCount: Integer; + public + constructor Create(const AItems: TArray); + end; + implementation +{ TTupleImpl } + +constructor TTupleImpl.Create(const AItems: TArray); +begin + inherited Create; + FItems := AItems; +end; + +function TTupleImpl.GetCount: Integer; +begin + Result := Length(FItems); +end; + +function TTupleImpl.GetItems(Idx: Integer): T; +begin + Result := FItems[Idx]; +end; + end. diff --git a/Src/Data/Myc.Data.Value.pas b/Src/Data/Myc.Data.Value.pas index 97c3146..03d67fc 100644 --- a/Src/Data/Myc.Data.Value.pas +++ b/Src/Data/Myc.Data.Value.pas @@ -76,6 +76,7 @@ type class operator Implicit(const AValue: String): TDataValue; overload; inline; class operator Implicit(const AValue: TDataValue.TFunc): TDataValue; overload; inline; class operator Implicit(const AValue: ITuple): TDataValue; overload; inline; + class operator Implicit(const AValue: TArray): TDataValue; overload; inline; class operator Implicit(const AValue: IKeywordMapping): TDataValue; overload; inline; class operator Implicit(const AValue: IKeywordMapping): TDataValue; overload; inline; class operator Implicit(const AValue: IWriteableScalarRecordSeries): TDataValue; overload; inline; @@ -507,6 +508,12 @@ begin Result.FInterface := AValue; end; +class operator TDataValue.Implicit(const AValue: TArray): TDataValue; +begin + Result.FKind := vkTuple; + Result.FInterface := TTupleImpl.Create(AValue) as ITuple; +end; + { TMapSeries } constructor TMapSeries.Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);