unit Myc.Ast.Binding; interface uses System.SysUtils, System.Classes, System.Generics.Collections, Myc.Data.Scalar, Myc.Data.Value, Myc.Ast.Nodes, Myc.Ast.Visitor, Myc.Ast.Scope, Myc.Ast.Analyzer, Myc.Ast; type IAstBinder = interface(IAstVisitor) function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; end; TAstBinder = class; // Forward declaration TEvaluateProc = reference to function(const Node: IAstNode): TDataValue; // This visitor handles the expansion of a single macro body (` `...`). TExpansionVisitor = class(TAstTransformer) private FEvaluate: TEvaluateProc; FMacroScope: IExecutionScope; function TransformAndSpliceNodes(const ANodes: TArray): TArray; protected function VisitUnquote(const Node: IUnquoteNode): TDataValue; override; function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override; function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override; function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override; public constructor Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc); class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode; end; TAstBinder = class(TAstTransformer, IAstBinder) private type TUpvalueMapping = class public Map: TDictionary; constructor Create; destructor Destroy; override; end; private FInitialScope: IExecutionScope; FCurrentDescriptor: IScopeDescriptor; FUpvalueStack: TStack; FNestedLambdaCount: Integer; FIsTailStack: TStack; FNextIsTail: Boolean; FBoxedDeclarations: THashSet; FEvaluatorFactory: TEvaluatorFactory; // Operator folding maps FBinaryOperators: TDictionary; FUnaryOperators: TDictionary; procedure EnterScope; procedure ExitScope; function IsValidIdentifier(const Name: string): Boolean; protected function Accept(const Node: IAstNode): TDataValue; override; function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override; function VisitAssignment(const Node: IAssignmentNode): TDataValue; override; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; override; function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override; function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; override; function VisitRecurNode(const Node: IRecurNode): TDataValue; override; function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override; function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override; function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override; function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override; function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override; public constructor Create(const AInitialScope: IExecutionScope; const AEvaluatorFactory: TEvaluatorFactory); destructor Destroy; override; function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; class function Bind( const InitialScope: IExecutionScope; const RootNode: IAstNode; out Descriptor: IScopeDescriptor; const EvaluatorFactory: TEvaluatorFactory ): IAstNode; static; end; TBoundIdentifierNode = class(TIdentifierNode) private FAddress: TResolvedAddress; public constructor Create(const AUnboundNode: IIdentifierNode; const AAddress: TResolvedAddress); property Address: TResolvedAddress read FAddress; end; TBoundVariableDeclarationNode = class(TVariableDeclarationNode) private FIsBoxed: Boolean; public constructor Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean); property IsBoxed: Boolean read FIsBoxed; end; TBoundLambdaExpressionNode = class(TLambdaExpressionNode) private FScopeDescriptor: IScopeDescriptor; FUpvalues: TArray; FHasNestedLambdas: Boolean; public constructor Create( const AUnboundNode: ILambdaExpressionNode; const ABody: IAstNode; const AParameters: TArray; const AScopeDescriptor: IScopeDescriptor; const AUpvalues: TArray; AHasNestedLambdas: Boolean ); property ScopeDescriptor: IScopeDescriptor read FScopeDescriptor; property Upvalues: TArray read FUpvalues; property HasNestedLambdas: Boolean read FHasNestedLambdas; end; TBoundFunctionCallNode = class(TFunctionCallNode) private FIsTailCall: Boolean; public constructor Create( const AUnboundNode: IFunctionCallNode; const ACallee: IAstNode; const AArguments: TArray; AIsTailCall: Boolean ); property IsTailCall: Boolean read FIsTailCall; end; implementation uses System.Generics.Defaults, System.Character; type TResolvedAddressComparer = class(TEqualityComparer) public function Equals(const Left, Right: TResolvedAddress): Boolean; override; function GetHashCode(const Value: TResolvedAddress): Integer; override; end; { TExpansionVisitor } constructor TExpansionVisitor.Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc); begin inherited Create; FMacroScope := AMacroScope; FEvaluate := AEvaluate; end; class function TExpansionVisitor.Expand( const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc ): IAstNode; begin var expander := TExpansionVisitor.Create(MacroScope, AEvaluate) as IAstTransformer; Result := expander.Execute(RootNode); end; function TExpansionVisitor.TransformAndSpliceNodes(const ANodes: TArray): TArray; var newList: TList; nodeToSplice: IAstNode; begin newList := TList.Create; try for var node in ANodes do begin if (node is TUnquoteSplicingNode) then begin var spliceExpr := (node as TUnquoteSplicingNode).Expression; var evaluatedSpliceValue := VisitUnquote(TAst.Unquote(spliceExpr)); if (not evaluatedSpliceValue.IsVoid) and (evaluatedSpliceValue.Kind = vkInterface) then begin nodeToSplice := evaluatedSpliceValue.AsIntf; if (nodeToSplice is TBlockExpressionNode) then newList.AddRange((nodeToSplice as TBlockExpressionNode).Expressions) else raise Exception.Create('Expression inside unquote-splicing (`~@`) must be a list of nodes (a block).'); end else raise Exception.Create('Expression inside unquote-splicing (`~@`) must evaluate to a list of nodes (a block).'); end else begin var transformedValue := node.Accept(self); if not transformedValue.IsVoid then newList.Add(transformedValue.AsIntf); end; end; Result := newList.ToArray; finally newList.Free; end; end; function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): TDataValue; var value: TDataValue; expr: IAstNode; addr: TResolvedAddress; begin expr := Node.Expression; if (expr is TIdentifierNode) then begin addr := FMacroScope.CreateDescriptor.FindSymbol((expr as TIdentifierNode).Name); if (addr.Kind = akLocalOrParent) and (addr.ScopeDepth = 0) then begin var argValue := FMacroScope.Values[addr]; if argValue.Kind = vkInterface then begin Result := argValue; exit; end; end; end; // externally evaluate the expression using the injected evaluator value := FEvaluate(expr); if value.Kind in [vkScalar, vkText, vkVoid] then Result := TDataValue.FromIntf(TAst.Constant(value)) else raise Exception.CreateFmt('Cannot unquote complex value of type %s at compile time.', [value.Kind.ToString]); end; function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; begin raise Exception.Create('Unquote-splicing (`~@`) can only appear inside a list form (e.g., a function call or a `do` block).'); end; function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; var newArgs: TArray; transformedCallee: IAstNode; begin transformedCallee := Self.Accept(Node.Callee).AsIntf; newArgs := TransformAndSpliceNodes(Node.Arguments); Result := TDataValue.FromIntf(TAst.FunctionCall(transformedCallee, newArgs)); end; function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; var newExprs: TArray; begin newExprs := TransformAndSpliceNodes(Node.Expressions); Result := TDataValue.FromIntf(TAst.Block(newExprs)); end; { TBoundIdentifierNode } constructor TBoundIdentifierNode.Create(const AUnboundNode: IIdentifierNode; const AAddress: TResolvedAddress); begin inherited Create(AUnboundNode.Name); FAddress := AAddress; end; { TBoundVariableDeclarationNode } constructor TBoundVariableDeclarationNode.Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean); begin inherited Create(AIdentifier, AInitializer); FIsBoxed := AIsBoxed; end; { TBoundLambdaExpressionNode } constructor TBoundLambdaExpressionNode.Create( const AUnboundNode: ILambdaExpressionNode; const ABody: IAstNode; const AParameters: TArray; const AScopeDescriptor: IScopeDescriptor; const AUpvalues: TArray; AHasNestedLambdas: Boolean ); begin inherited Create(AParameters, ABody); FScopeDescriptor := AScopeDescriptor; FUpvalues := AUpvalues; FHasNestedLambdas := AHasNestedLambdas; end; { TBoundFunctionCallNode } constructor TBoundFunctionCallNode.Create( const AUnboundNode: IFunctionCallNode; const ACallee: IAstNode; const AArguments: TArray; AIsTailCall: Boolean ); begin inherited Create(ACallee, AArguments); FIsTailCall := AIsTailCall; end; { TResolvedAddressComparer } function TResolvedAddressComparer.Equals(const Left, Right: TResolvedAddress): Boolean; begin Result := (Left = Right); end; function TResolvedAddressComparer.GetHashCode(const Value: TResolvedAddress): Integer; begin Result := 17; Result := Result * 23 + Ord(Value.Kind); Result := Result * 23 + Value.ScopeDepth; Result := Result * 23 + Value.SlotIndex; end; { TAstBinder.TUpvalueMapping } constructor TAstBinder.TUpvalueMapping.Create; begin inherited Create; Map := TDictionary.Create(TResolvedAddressComparer.Create); end; destructor TAstBinder.TUpvalueMapping.Destroy; begin Map.Free; inherited Destroy; end; { TAstBinder } constructor TAstBinder.Create(const AInitialScope: IExecutionScope; const AEvaluatorFactory: TEvaluatorFactory); var op: TScalar.TBinaryOp; begin inherited Create; Assert(Assigned(AInitialScope)); Assert(Assigned(AEvaluatorFactory)); FInitialScope := AInitialScope; FEvaluatorFactory := AEvaluatorFactory; FCurrentDescriptor := AInitialScope.CreateDescriptor; FUpvalueStack := TObjectStack.Create(True); FNestedLambdaCount := 0; FIsTailStack := TStack.Create; FNextIsTail := True; FBoxedDeclarations := nil; // Initialize operator folding maps FBinaryOperators := TDictionary.Create; for op := Low(TScalar.TBinaryOp) to High(TScalar.TBinaryOp) do FBinaryOperators.Add(op.ToString, op); FUnaryOperators := TDictionary.Create; FUnaryOperators.Add('not', TScalar.TUnaryOp.Not); // Note: '-' is handled as a special case in VisitFunctionCall end; destructor TAstBinder.Destroy; begin FUnaryOperators.Free; FBinaryOperators.Free; FIsTailStack.Free; FUpvalueStack.Free; FBoxedDeclarations.Free; inherited; end; function TAstBinder.Accept(const Node: IAstNode): TDataValue; begin if (not Assigned(Node)) or Done then exit; FIsTailStack.Push(FNextIsTail); try Result := inherited Accept(Node); finally FNextIsTail := FIsTailStack.Pop; end; end; class function TAstBinder.Bind( const InitialScope: IExecutionScope; const RootNode: IAstNode; out Descriptor: IScopeDescriptor; const EvaluatorFactory: TEvaluatorFactory ): IAstNode; begin var binder := TAstBinder.Create(InitialScope, EvaluatorFactory) as IAstBinder; Result := binder.Execute(RootNode, Descriptor); end; procedure TAstBinder.EnterScope; begin FCurrentDescriptor := TScope.CreateDescriptor(FCurrentDescriptor); end; function TAstBinder.Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; begin FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode, FCurrentDescriptor.Parent); try EnterScope; try var transformedValue := Accept(RootNode); if transformedValue.IsVoid then Result := TAst.Block([]) else Result := transformedValue.AsIntf; Descriptor := FCurrentDescriptor; finally ExitScope; end; finally // The binder now owns the hash set, which will be freed in the destructor. end; end; function TAstBinder.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; begin FCurrentDescriptor.DefineMacro(Node.Name.Name, Node); Result := TDataValue.Void; end; function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; var calleeIdentifier: TIdentifierNode; binaryOp: TScalar.TBinaryOp; unaryOp: TScalar.TUnaryOp; macroDef: IMacroDefinitionNode; begin if (Node.Callee is TIdentifierNode) then begin calleeIdentifier := Node.Callee as TIdentifierNode; // --- Optimization: Operator Folding --- // Try to fold binary operators if Length(Node.Arguments) = 2 then begin if FBinaryOperators.TryGetValue(calleeIdentifier.Name, binaryOp) then begin FNextIsTail := False; var left := Accept(Node.Arguments[0]).AsIntf; var right := Accept(Node.Arguments[1]).AsIntf; Result := TDataValue.FromIntf(TAst.BinaryExpr(left, binaryOp, right)); exit; end; end; // Try to fold unary operators if Length(Node.Arguments) = 1 then begin if FUnaryOperators.TryGetValue(calleeIdentifier.Name, unaryOp) then begin FNextIsTail := False; var right := Accept(Node.Arguments[0]).AsIntf; Result := TDataValue.FromIntf(TAst.UnaryExpr(unaryOp, right)); exit; end; // Special case for negation '-' if (calleeIdentifier.Name = '-') then begin FNextIsTail := False; var right := Accept(Node.Arguments[0]).AsIntf; Result := TDataValue.FromIntf(TAst.UnaryExpr(TScalar.TUnaryOp.Negate, right)); exit; end; end; // --- Macro Expansion --- macroDef := FCurrentDescriptor.FindMacro(calleeIdentifier.Name); if macroDef <> nil then begin var expansionScope := TAst.CreateScope(nil); var params := macroDef.Parameters; if Length(Node.Arguments) <> Length(params) then raise Exception.CreateFmt( 'Macro %s expects %d arguments, but got %d', [calleeIdentifier.Name, Length(params), Length(Node.Arguments)]); for var i := 0 to High(params) do expansionScope.Define(params[i].Name, TDataValue.FromIntf(Node.Arguments[i])); // expand var expandedBody := TExpansionVisitor.Expand( expansionScope, macroDef.Body.Expression, function(const Node: IAstNode): TDataValue var subDescriptor: IScopeDescriptor; begin // in place evaluator for expanded expressions var tempInitScope := TScope.CreateScope(FInitialScope.Parent, FCurrentDescriptor, nil); var boundSubAst := TAstBinder.Bind(tempInitScope, Node, subDescriptor, FEvaluatorFactory); var evalScope := subDescriptor.CreateScope(tempInitScope); var evaluator := FEvaluatorFactory(evalScope); Result := evaluator.Execute(boundSubAst); end ); // bind expanded body var boundExpandedBody := Self.Accept(expandedBody).AsIntf; // wrap in new expansion node var macroNode := TMacroExpansionNode.Create(Node, boundExpandedBody) as IMacroExpansionNode; // done exit(TDataValue.FromIntf(macroNode)); end; end; // --- Default: Bind as a standard function call --- var isTailCall := FIsTailStack.Peek; FNextIsTail := False; var callee := Accept(Node.Callee).AsIntf; var args := TransformNodes(Node.Arguments); var boundCall := TBoundFunctionCallNode.Create(Node, callee, args, isTailCall); Result := TDataValue.FromIntf(boundCall); end; function TAstBinder.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue; var boundCallee: IAstNode; boundArgs: TArray; boundExpandedBody: IAstNode; boundOriginalCall: IFunctionCallNode; begin boundCallee := Accept(Node.Callee).AsIntf; boundArgs := TransformNodes(Node.Arguments); boundExpandedBody := Accept(Node.ExpandedBody).AsIntf; boundOriginalCall := TAst.FunctionCall(boundCallee, boundArgs); var newMacroNode := TMacroExpansionNode.Create(boundOriginalCall, boundExpandedBody); Result := TDataValue.FromIntf(newMacroNode); end; procedure TAstBinder.ExitScope; begin FCurrentDescriptor := FCurrentDescriptor.Parent; end; function TAstBinder.IsValidIdentifier(const Name: string): Boolean; var c: Char; begin if Name.IsEmpty then exit(False); c := Name[1]; if not (c.IsLetter or (c = '_')) then exit(False); for c in Name do begin if not (c.IsLetterOrDigit or (c = '_') or (c = '-')) then exit(False); end; Result := True; end; function TAstBinder.VisitAssignment(const Node: IAssignmentNode): TDataValue; begin FNextIsTail := False; Result := inherited VisitAssignment(Node); end; function TAstBinder.VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; begin FNextIsTail := False; Result := inherited VisitBinaryExpression(Node); end; function TAstBinder.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; var exprs: TArray; i: Integer; isContextTail: Boolean; transformedValue: TDataValue; exprList: TList; begin isContextTail := FIsTailStack.Peek; exprList := TList.Create; try for i := 0 to High(Node.Expressions) do begin FNextIsTail := isContextTail and (i = High(Node.Expressions)); transformedValue := Accept(Node.Expressions[i]); if not transformedValue.IsVoid then exprList.Add(transformedValue.AsIntf); end; exprs := exprList.ToArray; finally exprList.Free; end; if (Length(exprs) = Length(Node.Expressions)) then begin var same := True; for i := 0 to High(exprs) do if exprs[i] <> Node.Expressions[i] then begin same := False; break; end; if same then begin Result := TDataValue.FromIntf(Node); exit; end; end; Result := TDataValue.FromIntf(TAst.Block(exprs)); end; function TAstBinder.VisitIfExpression(const Node: IIfExpressionNode): TDataValue; var isContextTail: Boolean; condition, thenBranch, elseBranch: IAstNode; begin isContextTail := FIsTailStack.Peek; FNextIsTail := False; condition := Accept(Node.Condition).AsIntf; FNextIsTail := isContextTail; thenBranch := Accept(Node.ThenBranch).AsIntf; elseBranch := Accept(Node.ElseBranch).AsIntf; if (condition <> Node.Condition) or (thenBranch <> Node.ThenBranch) or (elseBranch <> Node.ElseBranch) then Result := TDataValue.FromIntf(TAst.IfExpr(condition, thenBranch, elseBranch)) else Result := TDataValue.FromIntf(Node); end; function TAstBinder.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; var i: integer; boundParams: TArray; boundBody: IAstNode; lambdaScope: IScopeDescriptor; upvalues: TArray; hasNestedLambdas: Boolean; lastNestedLambdaCount: Integer; boundLambda: ILambdaExpressionNode; begin FUpvalueStack.Push(TUpvalueMapping.Create); try EnterScope; try FCurrentDescriptor.Define(''); SetLength(boundParams, Length(Node.Parameters)); for i := 0 to High(Node.Parameters) do begin var paramNode := Node.Parameters[i]; var slotIndex := FCurrentDescriptor.Define(paramNode.Name); var address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); boundParams[i] := TBoundIdentifierNode.Create(paramNode, address); end; lastNestedLambdaCount := FNestedLambdaCount; FNextIsTail := True; boundBody := Accept(Node.Body).AsIntf; hasNestedLambdas := FNestedLambdaCount > lastNestedLambdaCount; lambdaScope := FCurrentDescriptor; finally ExitScope; end; var upvalueMapping := FUpvalueStack.Peek; var sortedPairs := upvalueMapping.Map.ToArray; TArray.Sort>( sortedPairs, TComparer>.Construct( function(const Left, Right: TPair): Integer begin Result := Left.Value - Right.Value; end ) ); SetLength(upvalues, Length(sortedPairs)); for i := 0 to High(sortedPairs) do upvalues[i] := sortedPairs[i].Key; finally FUpvalueStack.Pop; end; inc(FNestedLambdaCount); boundLambda := TBoundLambdaExpressionNode.Create(Node, boundBody, boundParams, lambdaScope, upvalues, hasNestedLambdas); Result := TDataValue.FromIntf(boundLambda); end; function TAstBinder.VisitRecurNode(const Node: IRecurNode): TDataValue; begin if not FIsTailStack.Peek then raise Exception.Create('''recur'' can only be used in a tail position.'); FNextIsTail := False; Result := inherited VisitRecurNode(Node); end; function TAstBinder.VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; var isContextTail: Boolean; condition, thenBranch, elseBranch: IAstNode; begin isContextTail := FIsTailStack.Peek; FNextIsTail := False; condition := Accept(Node.Condition).AsIntf; FNextIsTail := isContextTail; thenBranch := Accept(Node.ThenBranch).AsIntf; elseBranch := Accept(Node.ElseBranch).AsIntf; if (condition <> Node.Condition) or (thenBranch <> Node.ThenBranch) or (elseBranch <> Node.ElseBranch) then Result := TDataValue.FromIntf(TAst.TernaryExpr(condition, thenBranch, elseBranch)) else Result := TDataValue.FromIntf(Node); end; function TAstBinder.VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; begin FNextIsTail := False; Result := inherited VisitUnaryExpression(Node); end; function TAstBinder.VisitIdentifier(const Node: IIdentifierNode): TDataValue; var adr: TResolvedAddress; boundNode: IIdentifierNode; begin adr := FCurrentDescriptor.FindSymbol(Node.Name); if adr.Kind = akLocalOrParent then begin if (adr.ScopeDepth > 0) and (FUpvalueStack.Count > 0) then begin var upvalue := FUpvalueStack.Peek; dec(adr.ScopeDepth); var upvalueIndex: Integer; if not upvalue.Map.TryGetValue(adr, upvalueIndex) then begin upvalueIndex := upvalue.Map.Count; upvalue.Map.Add(adr, upvalueIndex); end; boundNode := TBoundIdentifierNode.Create(Node, TResolvedAddress.Create(akUpvalue, 0, upvalueIndex)); end else boundNode := TBoundIdentifierNode.Create(Node, adr); Result := TDataValue.FromIntf(boundNode); end else raise Exception.CreateFmt('Undefined identifier: "%s"', [Node.Name]); end; function TAstBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; var initializer: IAstNode; slotIndex: Integer; address: TResolvedAddress; boundIdentifier: IIdentifierNode; isBoxed: Boolean; boundDecl: IVariableDeclarationNode; begin if not IsValidIdentifier(Node.Identifier.Name) then raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]); FNextIsTail := False; initializer := nil; if Node.Initializer <> nil then initializer := Accept(Node.Initializer).AsIntf; slotIndex := FCurrentDescriptor.Define(Node.Identifier.Name); address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); boundIdentifier := TBoundIdentifierNode.Create(Node.Identifier, address); isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node); boundDecl := TBoundVariableDeclarationNode.Create(boundIdentifier, initializer, isBoxed); Result := TDataValue.FromIntf(boundDecl); end; end.