diff --git a/Src/AST/Myc.Ast.Compiler.Lowering.pas b/Src/AST/Myc.Ast.Compiler.Lowering.pas index 3c9b951..70108ec 100644 --- a/Src/AST/Myc.Ast.Compiler.Lowering.pas +++ b/Src/AST/Myc.Ast.Compiler.Lowering.pas @@ -8,13 +8,16 @@ uses System.Generics.Collections, Myc.Data.Scalar, Myc.Data.Value, - Myc.Ast.Nodes, + Myc.Ast.Nodes, // Provides EAstException Myc.Ast.Visitor, Myc.Ast.Scope, Myc.Ast.Types, Myc.Ast; type + // Exception specific to AST lowering/rewriting errors + ELoweringException = class(EAstException); + IAstLowerer = interface(IAstVisitor) function Execute(const RootNode: IAstNode): IAstNode; end; @@ -27,6 +30,8 @@ type FBinaryOperators: TDictionary; FUnaryOperators: TDictionary; protected + // Implement Visit methods here when specific lowering logic is added + // (e.g. converting operator function calls to specific node types if added in future) public constructor Create; destructor Destroy; override; @@ -47,7 +52,7 @@ constructor TAstLowerer.Create; begin inherited Create; - // Operator folding maps + // Operator folding maps (Reserved for future static optimization) FBinaryOperators := TDictionary.Create; FBinaryOperators.Add('+', TScalar.TBinaryOp.Add); @@ -76,12 +81,20 @@ end; class function TAstLowerer.Lower(const RootNode: IAstNode): IAstNode; begin var lowerer := TAstLowerer.Create as IAstLowerer; - Result := lowerer.Execute(RootNode); + try + Result := lowerer.Execute(RootNode); + except + on E: Exception do + raise ELoweringException.Create('Internal Error during AST Lowering: ' + E.Message); + end; end; function TAstLowerer.Execute(const RootNode: IAstNode): IAstNode; begin - Result := Accept(RootNode); // Use IAstNode-returning Accept + if not Assigned(RootNode) then + exit(nil); + + Result := Accept(RootNode); if not Assigned(Result) then Result := TAst.Block([]); end; diff --git a/Src/AST/Myc.Ast.Compiler.Macros.pas b/Src/AST/Myc.Ast.Compiler.Macros.pas index 6f97ef8..dd1d5a1 100644 --- a/Src/AST/Myc.Ast.Compiler.Macros.pas +++ b/Src/AST/Myc.Ast.Compiler.Macros.pas @@ -15,6 +15,9 @@ uses Myc.Ast; type + // Exception specific to macro expansion errors + EMacroException = class(EAstException); + IAstMacroExpander = interface(IAstVisitor) function Execute(const RootNode: IAstNode): IAstNode; end; @@ -306,13 +309,13 @@ begin if value.Kind in [vkScalar, vkText, vkVoid] then Result := TAst.Constant(value) else - raise Exception.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]); + raise EMacroException.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]); end; function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; begin // ~@ can only be handled within a list context (TransformAndSpliceNodes) - raise Exception.Create('Unquote-splicing (`~@`) can only appear inside a list form.'); + raise EMacroException.Create('Unquote-splicing (`~@`) can only appear inside a list form.'); end; function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; @@ -443,7 +446,7 @@ begin var params := macroDef.Parameters; if Length(Node.Arguments) <> Length(params) then - raise Exception.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]); + raise EMacroException.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]); // 2. Populate scope with UN-EVALUATED AST nodes (Code as Data) for i := 0 to High(params) do @@ -470,12 +473,12 @@ end; function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode; begin // Unquotes outside of a macro definition/expansion phase are invalid syntax - raise Exception.Create('Unquote (`~`) can only be used inside a quasiquote.'); + raise EMacroException.Create('Unquote (`~`) can only be used inside a quasiquote.'); end; function TMacroExpander.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; begin - raise Exception.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.'); + raise EMacroException.Create('Unquote-splicing (`~@`) can only be used inside a quasiquote.'); end; { TMacroRegistry } diff --git a/Src/AST/Myc.Ast.Compiler.Specializer.pas b/Src/AST/Myc.Ast.Compiler.Specializer.pas index d62ee64..0f05206 100644 --- a/Src/AST/Myc.Ast.Compiler.Specializer.pas +++ b/Src/AST/Myc.Ast.Compiler.Specializer.pas @@ -16,6 +16,9 @@ uses Myc.Ast.Compiler.Binder; type + // Exception specific to specialization errors (e.g. internal compilation failures) + ESpecializerException = class(EAstException); + IAstSpecializer = interface(IAstVisitor) function Execute(const RootNode: IAstNode): IAstNode; end; @@ -90,6 +93,12 @@ constructor TStaticSpecializer.Create( ); begin inherited Create; + if not Assigned(AMonomorphCache) then + raise ESpecializerException.Create('MonomorphCache cannot be nil.'); + if not Assigned(AFunctionRegistry) then + raise ESpecializerException.Create('FunctionRegistry cannot be nil.'); + // CompileFunc can be nil if no recursion is supported, but usually required. + FMonomorphCache := AMonomorphCache; FFunctionRegistry := AFunctionRegistry; FCompileFunc := ACompileFunc; @@ -178,7 +187,6 @@ begin if FMonomorphCache.TryGetFunction(key, specializedMethod) then begin // 4a. Cache Hit (Environment) - // Propagate IsPure flag from cache to AST node Result := TAst.FunctionCall( newCallee, @@ -198,7 +206,6 @@ begin // 5a. Cache Hit (RTL) FMonomorphCache.Add(key, specializedMethod); - // Propagate IsPure flag from RTL definition to AST node Result := TAst.FunctionCall( newCallee, @@ -228,17 +235,21 @@ begin end; // 6a. Compile func with KNOWN TYPES - // This recursively triggers Bind -> Check -> Specialize -> Purity Inference for the callee body! + if not Assigned(FCompileFunc) then + raise ESpecializerException.Create('Cannot specialize user function: Compiler callback is missing.'); + var compiled := FCompileFunc(funcDef, argTypes); + // Safety check: The recursive compiler MUST produce a valid function pointer + if not Assigned(compiled.Func) then + raise ESpecializerException.CreateFmt('Internal Error: Failed to compile specialization for "%s"', [funcName]); + // 6b. Store in cache - // Get the return type from the *full function type* returned by Compile var returnType := compiled.StaticType.Signatures[0].ReturnType; specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType, compiled.IsPure); FMonomorphCache.Add(key, specializedMethod); // 6c. Return the new node - // Propagate the inferred IsPure flag to the AST node Result := TAst.FunctionCall(newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure); exit; end; diff --git a/Src/AST/Myc.Ast.Compiler.TCO.pas b/Src/AST/Myc.Ast.Compiler.TCO.pas index 968f384..ae184f7 100644 --- a/Src/AST/Myc.Ast.Compiler.TCO.pas +++ b/Src/AST/Myc.Ast.Compiler.TCO.pas @@ -14,6 +14,9 @@ uses Myc.Ast; type + // Exception specific to optimization/TCO errors + EOptimizerException = class(EAstException); + IAstTCO = interface(IAstVisitor) function Execute(const RootNode: IAstNode): IAstNode; end; @@ -180,7 +183,6 @@ begin begin // Rebuild using factory. // IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker). - // (* FIX: Pass Node.IsPure to preserve it *) Result := TAst.LambdaExpr( newParams, @@ -200,7 +202,7 @@ var newArgs: TArray; begin if not FIsTailStack.Peek then - raise Exception.Create('''recur'' can only be used in a tail position.'); + raise EOptimizerException.Create('''recur'' can only be used in a tail position.'); // Arguments are not in tail position FNextIsTail := False; @@ -254,7 +256,7 @@ begin Node.StaticType, isTailCall, Node.StaticTarget, // Preserve the static target from Specializer - Node.IsTargetPure // (* FIX: Preserve the purity flag from Specializer! *) + Node.IsTargetPure // Preserve purity flag ); end; diff --git a/Src/AST/Myc.Ast.Scope.pas b/Src/AST/Myc.Ast.Scope.pas index 39b42e3..752b407 100644 --- a/Src/AST/Myc.Ast.Scope.pas +++ b/Src/AST/Myc.Ast.Scope.pas @@ -11,6 +11,9 @@ uses Myc.Ast.Types; type + // Exception specific to scope resolution and definition errors + EScopeException = class(Exception); + IExecutionScope = interface; IScopeDescriptor = interface; IScopeLayout = interface; @@ -357,7 +360,7 @@ function TScopeBuilder.Define(const Name: string): Integer; begin // Rule: Shadowing / Redefinition in the same scope is forbidden. if FMap.ContainsKey(Name) then - raise Exception.CreateFmt('Variable "%s" is already defined in this scope.', [Name]); + raise EScopeException.CreateFmt('Variable "%s" is already defined in this scope.', [Name]); // Allocate a new slot Result := FNextSlot; @@ -598,7 +601,7 @@ begin end; end; else - raise EInvalidOpException.Create('Cannot capture an unresolved address.'); + raise EScopeException.Create('Cannot capture an unresolved address.'); end; end; @@ -613,7 +616,7 @@ begin // NOTE: Runtime redefinition is generally restricted in this dynamic Define // (typically used for Root/Global definitions). if FNameToIndex.ContainsKey(id) then - raise Exception.CreateFmt('Variable "%s" is already defined in this scope.', [Name]); + raise EScopeException.CreateFmt('Variable "%s" is already defined in this scope.', [Name]); index := Length(FValues); SetLength(FValues, index + 1); @@ -734,7 +737,7 @@ begin Result := item.Value; end; else - raise EInvalidOpException.Create('Cannot get value for an unresolved address.'); + raise EScopeException.Create('Cannot get value for an unresolved address.'); end; end; @@ -810,7 +813,7 @@ begin pItem.Value := Value; end; else - raise EInvalidOpException.Create('Cannot set value for an unresolved address.'); + raise EScopeException.Create('Cannot set value for an unresolved address.'); end; end; diff --git a/Src/AST/Myc.Ast.Types.pas b/Src/AST/Myc.Ast.Types.pas index 0d8d224..c134b28 100644 --- a/Src/AST/Myc.Ast.Types.pas +++ b/Src/AST/Myc.Ast.Types.pas @@ -9,6 +9,10 @@ uses Myc.Data.Keyword; type + // Exception for atomic type errors (promotion failures, invalid operations) + // Inherits from Exception directly to keep Types unit independent of Nodes/AST. + ETypeException = class(Exception); + IStaticType = interface; // Defines the categories of types available in the language. @@ -79,8 +83,6 @@ type function GetHashCode: Integer; end; - ETypeException = class(Exception); - // Factory and Flyweight access for static types. // Use this record to create or access all IStaticType instances. TTypes = record diff --git a/Test/AST/Test.Myc.Ast.Compiler.Macros.pas b/Test/AST/Test.Myc.Ast.Compiler.Macros.pas index 119a7a7..41b766d 100644 --- a/Test/AST/Test.Myc.Ast.Compiler.Macros.pas +++ b/Test/AST/Test.Myc.Ast.Compiler.Macros.pas @@ -11,7 +11,8 @@ uses Myc.Ast.Script, Myc.Ast.Environment, Myc.Data.Value, - Myc.Data.Scalar; + Myc.Data.Scalar, + Myc.Ast.Compiler.Macros; type [TestFixture] @@ -239,12 +240,12 @@ end; procedure TMacroTests.Test_Error_Unquote_Outside_Quasiquote; begin - Assert.WillRaise(procedure begin Run('(print ~1)'); end, Exception, 'Unquote outside quasiquote should raise exception'); + Assert.WillRaise(procedure begin Run('(print ~1)'); end, EMacroException, 'Unquote outside quasiquote should raise exception'); end; procedure TMacroTests.Test_Macro_Argument_Count_Mismatch; begin - Assert.WillRaise(procedure begin Run('(do (defmacro two [a b] `(+ ~a ~b)) (two 1))'); end, Exception); + Assert.WillRaise(procedure begin Run('(do (defmacro two [a b] `(+ ~a ~b)) (two 1))'); end, EMacroException); end; end. diff --git a/Test/AST/Test.Myc.Ast.Script.pas b/Test/AST/Test.Myc.Ast.Script.pas index bd2715d..a2af179 100644 --- a/Test/AST/Test.Myc.Ast.Script.pas +++ b/Test/AST/Test.Myc.Ast.Script.pas @@ -355,7 +355,6 @@ end; procedure TTestMycAstScript.Parser_Error_UnbalancedParens_MissingRight; begin - // FIX 2: Pass explicit Exception type to WillRaise Assert.WillRaise(procedure begin Parse('(a b'); end, EParserException); end;