Compiler exceptions

This commit is contained in:
Michael Schimmel
2025-11-25 15:27:57 +01:00
parent d84509c034
commit 4e508d90a5
8 changed files with 62 additions and 28 deletions
+17 -4
View File
@@ -8,13 +8,16 @@ uses
System.Generics.Collections, System.Generics.Collections,
Myc.Data.Scalar, Myc.Data.Scalar,
Myc.Data.Value, Myc.Data.Value,
Myc.Ast.Nodes, Myc.Ast.Nodes, // Provides EAstException
Myc.Ast.Visitor, Myc.Ast.Visitor,
Myc.Ast.Scope, Myc.Ast.Scope,
Myc.Ast.Types, Myc.Ast.Types,
Myc.Ast; Myc.Ast;
type type
// Exception specific to AST lowering/rewriting errors
ELoweringException = class(EAstException);
IAstLowerer = interface(IAstVisitor) IAstLowerer = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode; function Execute(const RootNode: IAstNode): IAstNode;
end; end;
@@ -27,6 +30,8 @@ type
FBinaryOperators: TDictionary<string, TScalar.TBinaryOp>; FBinaryOperators: TDictionary<string, TScalar.TBinaryOp>;
FUnaryOperators: TDictionary<string, TScalar.TUnaryOp>; FUnaryOperators: TDictionary<string, TScalar.TUnaryOp>;
protected 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 public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
@@ -47,7 +52,7 @@ constructor TAstLowerer.Create;
begin begin
inherited Create; inherited Create;
// Operator folding maps // Operator folding maps (Reserved for future static optimization)
FBinaryOperators := TDictionary<string, TScalar.TBinaryOp>.Create; FBinaryOperators := TDictionary<string, TScalar.TBinaryOp>.Create;
FBinaryOperators.Add('+', TScalar.TBinaryOp.Add); FBinaryOperators.Add('+', TScalar.TBinaryOp.Add);
@@ -76,12 +81,20 @@ end;
class function TAstLowerer.Lower(const RootNode: IAstNode): IAstNode; class function TAstLowerer.Lower(const RootNode: IAstNode): IAstNode;
begin begin
var lowerer := TAstLowerer.Create as IAstLowerer; 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; end;
function TAstLowerer.Execute(const RootNode: IAstNode): IAstNode; function TAstLowerer.Execute(const RootNode: IAstNode): IAstNode;
begin begin
Result := Accept(RootNode); // Use IAstNode-returning Accept if not Assigned(RootNode) then
exit(nil);
Result := Accept(RootNode);
if not Assigned(Result) then if not Assigned(Result) then
Result := TAst.Block([]); Result := TAst.Block([]);
end; end;
+8 -5
View File
@@ -15,6 +15,9 @@ uses
Myc.Ast; Myc.Ast;
type type
// Exception specific to macro expansion errors
EMacroException = class(EAstException);
IAstMacroExpander = interface(IAstVisitor) IAstMacroExpander = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode; function Execute(const RootNode: IAstNode): IAstNode;
end; end;
@@ -306,13 +309,13 @@ begin
if value.Kind in [vkScalar, vkText, vkVoid] then if value.Kind in [vkScalar, vkText, vkVoid] then
Result := TAst.Constant(value) Result := TAst.Constant(value)
else 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; end;
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
begin begin
// ~@ can only be handled within a list context (TransformAndSpliceNodes) // ~@ 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; end;
function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
@@ -443,7 +446,7 @@ begin
var params := macroDef.Parameters; var params := macroDef.Parameters;
if Length(Node.Arguments) <> Length(params) then 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) // 2. Populate scope with UN-EVALUATED AST nodes (Code as Data)
for i := 0 to High(params) do for i := 0 to High(params) do
@@ -470,12 +473,12 @@ end;
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode; function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode;
begin begin
// Unquotes outside of a macro definition/expansion phase are invalid syntax // 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; end;
function TMacroExpander.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; function TMacroExpander.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
begin 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; end;
{ TMacroRegistry } { TMacroRegistry }
+16 -5
View File
@@ -16,6 +16,9 @@ uses
Myc.Ast.Compiler.Binder; Myc.Ast.Compiler.Binder;
type type
// Exception specific to specialization errors (e.g. internal compilation failures)
ESpecializerException = class(EAstException);
IAstSpecializer = interface(IAstVisitor) IAstSpecializer = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode; function Execute(const RootNode: IAstNode): IAstNode;
end; end;
@@ -90,6 +93,12 @@ constructor TStaticSpecializer.Create(
); );
begin begin
inherited Create; 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; FMonomorphCache := AMonomorphCache;
FFunctionRegistry := AFunctionRegistry; FFunctionRegistry := AFunctionRegistry;
FCompileFunc := ACompileFunc; FCompileFunc := ACompileFunc;
@@ -178,7 +187,6 @@ begin
if FMonomorphCache.TryGetFunction(key, specializedMethod) then if FMonomorphCache.TryGetFunction(key, specializedMethod) then
begin begin
// 4a. Cache Hit (Environment) // 4a. Cache Hit (Environment)
// Propagate IsPure flag from cache to AST node
Result := Result :=
TAst.FunctionCall( TAst.FunctionCall(
newCallee, newCallee,
@@ -198,7 +206,6 @@ begin
// 5a. Cache Hit (RTL) // 5a. Cache Hit (RTL)
FMonomorphCache.Add(key, specializedMethod); FMonomorphCache.Add(key, specializedMethod);
// Propagate IsPure flag from RTL definition to AST node
Result := Result :=
TAst.FunctionCall( TAst.FunctionCall(
newCallee, newCallee,
@@ -228,17 +235,21 @@ begin
end; end;
// 6a. Compile func with KNOWN TYPES // 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); 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 // 6b. Store in cache
// Get the return type from the *full function type* returned by Compile
var returnType := compiled.StaticType.Signatures[0].ReturnType; var returnType := compiled.StaticType.Signatures[0].ReturnType;
specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType, compiled.IsPure); specializedMethod := TSpecializedMethod.Create(compiled.Func, returnType, compiled.IsPure);
FMonomorphCache.Add(key, specializedMethod); FMonomorphCache.Add(key, specializedMethod);
// 6c. Return the new node // 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); Result := TAst.FunctionCall(newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure);
exit; exit;
end; end;
+5 -3
View File
@@ -14,6 +14,9 @@ uses
Myc.Ast; Myc.Ast;
type type
// Exception specific to optimization/TCO errors
EOptimizerException = class(EAstException);
IAstTCO = interface(IAstVisitor) IAstTCO = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): IAstNode; function Execute(const RootNode: IAstNode): IAstNode;
end; end;
@@ -180,7 +183,6 @@ begin
begin begin
// Rebuild using factory. // Rebuild using factory.
// IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker). // IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker).
// (* FIX: Pass Node.IsPure to preserve it *)
Result := Result :=
TAst.LambdaExpr( TAst.LambdaExpr(
newParams, newParams,
@@ -200,7 +202,7 @@ var
newArgs: TArray<IAstNode>; newArgs: TArray<IAstNode>;
begin begin
if not FIsTailStack.Peek then 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 // Arguments are not in tail position
FNextIsTail := False; FNextIsTail := False;
@@ -254,7 +256,7 @@ begin
Node.StaticType, Node.StaticType,
isTailCall, isTailCall,
Node.StaticTarget, // Preserve the static target from Specializer Node.StaticTarget, // Preserve the static target from Specializer
Node.IsTargetPure // (* FIX: Preserve the purity flag from Specializer! *) Node.IsTargetPure // Preserve purity flag
); );
end; end;
+8 -5
View File
@@ -11,6 +11,9 @@ uses
Myc.Ast.Types; Myc.Ast.Types;
type type
// Exception specific to scope resolution and definition errors
EScopeException = class(Exception);
IExecutionScope = interface; IExecutionScope = interface;
IScopeDescriptor = interface; IScopeDescriptor = interface;
IScopeLayout = interface; IScopeLayout = interface;
@@ -357,7 +360,7 @@ function TScopeBuilder.Define(const Name: string): Integer;
begin begin
// Rule: Shadowing / Redefinition in the same scope is forbidden. // Rule: Shadowing / Redefinition in the same scope is forbidden.
if FMap.ContainsKey(Name) then 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 // Allocate a new slot
Result := FNextSlot; Result := FNextSlot;
@@ -598,7 +601,7 @@ begin
end; end;
end; end;
else else
raise EInvalidOpException.Create('Cannot capture an unresolved address.'); raise EScopeException.Create('Cannot capture an unresolved address.');
end; end;
end; end;
@@ -613,7 +616,7 @@ begin
// NOTE: Runtime redefinition is generally restricted in this dynamic Define // NOTE: Runtime redefinition is generally restricted in this dynamic Define
// (typically used for Root/Global definitions). // (typically used for Root/Global definitions).
if FNameToIndex.ContainsKey(id) then 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); index := Length(FValues);
SetLength(FValues, index + 1); SetLength(FValues, index + 1);
@@ -734,7 +737,7 @@ begin
Result := item.Value; Result := item.Value;
end; end;
else else
raise EInvalidOpException.Create('Cannot get value for an unresolved address.'); raise EScopeException.Create('Cannot get value for an unresolved address.');
end; end;
end; end;
@@ -810,7 +813,7 @@ begin
pItem.Value := Value; pItem.Value := Value;
end; end;
else else
raise EInvalidOpException.Create('Cannot set value for an unresolved address.'); raise EScopeException.Create('Cannot set value for an unresolved address.');
end; end;
end; end;
+4 -2
View File
@@ -9,6 +9,10 @@ uses
Myc.Data.Keyword; Myc.Data.Keyword;
type 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; IStaticType = interface;
// Defines the categories of types available in the language. // Defines the categories of types available in the language.
@@ -79,8 +83,6 @@ type
function GetHashCode: Integer; function GetHashCode: Integer;
end; end;
ETypeException = class(Exception);
// Factory and Flyweight access for static types. // Factory and Flyweight access for static types.
// Use this record to create or access all IStaticType instances. // Use this record to create or access all IStaticType instances.
TTypes = record TTypes = record
+4 -3
View File
@@ -11,7 +11,8 @@ uses
Myc.Ast.Script, Myc.Ast.Script,
Myc.Ast.Environment, Myc.Ast.Environment,
Myc.Data.Value, Myc.Data.Value,
Myc.Data.Scalar; Myc.Data.Scalar,
Myc.Ast.Compiler.Macros;
type type
[TestFixture] [TestFixture]
@@ -239,12 +240,12 @@ end;
procedure TMacroTests.Test_Error_Unquote_Outside_Quasiquote; procedure TMacroTests.Test_Error_Unquote_Outside_Quasiquote;
begin 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; end;
procedure TMacroTests.Test_Macro_Argument_Count_Mismatch; procedure TMacroTests.Test_Macro_Argument_Count_Mismatch;
begin 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;
end. end.
-1
View File
@@ -355,7 +355,6 @@ end;
procedure TTestMycAstScript.Parser_Error_UnbalancedParens_MissingRight; procedure TTestMycAstScript.Parser_Error_UnbalancedParens_MissingRight;
begin begin
// FIX 2: Pass explicit Exception type to WillRaise
Assert.WillRaise(procedure begin Parse('(a b'); end, EParserException); Assert.WillRaise(procedure begin Parse('(a b'); end, EParserException);
end; end;