From d47c1417f543627f864e2556ba086dbaa113e5db Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 3 Oct 2025 11:53:02 +0200 Subject: [PATCH] Comment in AST script --- Src/AST/Myc.Ast.Evaluator.pas | 13 +++++++++++-- Src/AST/Myc.Ast.Scope.pas | 16 ++++++++-------- Src/AST/Myc.Ast.Script.pas | 23 ++++++++++++++++++++--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index e3c081f..ccba005 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -177,21 +177,26 @@ begin // Cast to the bound node to access binder-specific information. boundNode := Node as TBoundLambdaExpressionNode; + // Create the closure by capturing the value cells of all upvalues from the current scope. sourceAddresses := boundNode.Upvalues; SetLength(capturedCells, Length(sourceAddresses)); for i := 0 to High(sourceAddresses) do capturedCells[i] := FScope.Capture(sourceAddresses[i]); + // Memory optimization: a lambda's scope does not need to be kept alive as a parent + // if it contains no nested lambdas that might need to capture from it later. if boundNode.HasNestedLambdas then closureScope := FScope else closureScope := nil; + // Get a factory to create the correct visitor (e.g., debug or production) for the lambda body. visitorFactory := CreateVisitorFactory(); var scopeDescriptor := boundNode.ScopeDescriptor; var params := boundNode.Parameters; + // [weak] prevents a reference cycle since the closure captures itself for 'recur'. var [weak] closure: TDataValue.TFunc; var cNode: ILambdaExpressionNode := Node; @@ -207,6 +212,7 @@ begin if (Length(ArgValues) <> Length(params)) then raise EArgumentException.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(params), Length(ArgValues)]); + // Create the new execution scope for this function call. lambdaScope := TScope.CreateScope(closureScope, scopeDescriptor, capturedCells); adr.Kind := akLocalOrParent; @@ -216,7 +222,7 @@ begin adr.SlotIndex := 0; lambdaScope[adr] := TDataValue(closure); - // Populate the actual parameters. + // Populate the scope with the actual parameters passed to the function. for i := 0 to High(ArgValues) do begin // Parameters in a bound lambda must be bound identifiers. @@ -224,11 +230,13 @@ begin lambdaScope[adr] := ArgValues[i]; end; + // Create a visitor with the new scope and execute the lambda's body. bodyVisitor := visitorFactory(lambdaScope); Result := cNode.Body.Accept(bodyVisitor); end ); + // The result of visiting a lambda node is the callable closure itself. Result := closure; end; @@ -427,7 +435,8 @@ begin if boundNode.IsBoxed then begin // This is a captured variable. Use the clean scope method to create the box. - FScope.DefineBoxed(address, Result); + Assert(address.ScopeDepth = 0); + FScope.DefineBoxed(address.SlotIndex, Result); end else begin diff --git a/Src/AST/Myc.Ast.Scope.pas b/Src/AST/Myc.Ast.Scope.pas index 64b5a14..4529eba 100644 --- a/Src/AST/Myc.Ast.Scope.pas +++ b/Src/AST/Myc.Ast.Scope.pas @@ -29,8 +29,10 @@ type {$endregion} procedure Define(const Name: string; const Value: TDataValue); + // Defines a variable at a given address and stores it directly in a shared cell (boxing). - procedure DefineBoxed(const Address: TResolvedAddress; const Value: TDataValue); + procedure DefineBoxed(SlotIndex: Integer; const Value: TDataValue); + function Dump: string; procedure Clear; function Capture(const Address: TResolvedAddress): IValueCell; @@ -114,7 +116,7 @@ type function GetNameID(const Name: String): Integer; function Dump: string; procedure Define(const Name: string; const Value: TDataValue); - procedure DefineBoxed(const Address: TResolvedAddress; const Value: TDataValue); + procedure DefineBoxed(SlotIndex: Integer; const Value: TDataValue); function Capture(const Address: TResolvedAddress): IValueCell; function CreateDescriptor: IScopeDescriptor; property Names: TDictionary read FNames; @@ -280,14 +282,12 @@ begin FNameToIndex.Add(id, index); end; -procedure TExecutionScope.DefineBoxed(const Address: TResolvedAddress; const Value: TDataValue); +procedure TExecutionScope.DefineBoxed(SlotIndex: Integer; const Value: TDataValue); begin - // This method is only for local variables (ScopeDepth=0). - Assert(Address.ScopeDepth = 0, 'DefineBoxed can only be used on the current scope.'); - Assert((Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FValues)), 'Invalid slot index during DefineBoxed.'); + Assert((SlotIndex >= 0) and (SlotIndex < Length(FValues)), 'Invalid slot index during DefineBoxed.'); - FValues[Address.SlotIndex].Value := TDataValue.FromIntf(TValueCell.Create(Value)); - FValues[Address.SlotIndex].IsBoxed := True; + FValues[SlotIndex].Value := TDataValue.FromIntf(TValueCell.Create(Value)); + FValues[SlotIndex].IsBoxed := True; end; procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index 076b7c3..b159b1c 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -178,8 +178,26 @@ end; function TLexer.GetNextToken: TToken; begin - while (FCurrentPos <= Length(FSource)) and FSource[FCurrentPos].IsWhiteSpace do - Advance; + // Skip whitespace and comments + while FCurrentPos <= Length(FSource) do + begin + if FSource[FCurrentPos].IsWhiteSpace then + begin + Advance; + continue; + end; + + if FSource[FCurrentPos] = ';' then + begin + // Comment found, skip to the end of the line + while (FCurrentPos <= Length(FSource)) and (not CharInSet(FSource[FCurrentPos], [#10, #13])) do + Advance; + continue; + end; + + // No whitespace or comment, so break out and process the token + break; + end; if FCurrentPos > Length(FSource) then begin @@ -445,7 +463,6 @@ begin end; { TPrettyPrintVisitor } -// ... (Implementation of TPrettyPrintVisitor is unchanged and correct) ... constructor TPrettyPrintVisitor.Create; begin inherited Create;