Compiler exceptions
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user