Compiler errors

This commit is contained in:
Michael Schimmel
2025-11-25 18:11:04 +01:00
parent 0a1df4e9fe
commit 85ef043b04
5 changed files with 464 additions and 103 deletions
+57 -16
View File
@@ -16,9 +16,6 @@ uses
Myc.Ast;
type
// Exception specific to the binding phase (e.g. undefined symbols, invalid identifiers)
EBinderException = class(EAstException);
IAstBinder = interface(IAstVisitor)
function Execute(const RootNode: IAstNode; out Layout: IScopeLayout): IAstNode;
end;
@@ -40,6 +37,7 @@ type
FArgTypes: TArray<IStaticType>;
FFunctionRegistry: IFunctionDefinitionRegistry;
FBoxedDeclarations: THashSet<IVariableDeclarationNode>;
FLog: ICompilerLog;
// Counts lambdas encountered to determine if a lambda has nested children
FLambdaCounter: Integer;
@@ -63,7 +61,8 @@ type
constructor Create(
const AParentLayout: IScopeLayout;
const AFunctionRegistry: IFunctionDefinitionRegistry;
const AArgTypes: TArray<IStaticType>
const AArgTypes: TArray<IStaticType>;
const ALog: ICompilerLog
);
destructor Destroy; override;
@@ -73,6 +72,7 @@ type
const ParentLayout: IScopeLayout;
const RootNode: IAstNode;
out Layout: IScopeLayout;
const ALog: ICompilerLog;
const AFunctionRegistry: IFunctionDefinitionRegistry = nil;
const AArgTypes: TArray<IStaticType> = nil
): IAstNode; static;
@@ -110,7 +110,8 @@ end;
constructor TAstBinder.Create(
const AParentLayout: IScopeLayout;
const AFunctionRegistry: IFunctionDefinitionRegistry;
const AArgTypes: TArray<IStaticType>
const AArgTypes: TArray<IStaticType>;
const ALog: ICompilerLog
);
begin
inherited Create;
@@ -120,6 +121,7 @@ begin
FFunctionRegistry := AFunctionRegistry;
FArgTypes := AArgTypes;
FLog := ALog;
FBoxedDeclarations := nil;
FLambdaCounter := 0;
end;
@@ -135,11 +137,12 @@ class function TAstBinder.Bind(
const ParentLayout: IScopeLayout;
const RootNode: IAstNode;
out Layout: IScopeLayout;
const ALog: ICompilerLog;
const AFunctionRegistry: IFunctionDefinitionRegistry = nil;
const AArgTypes: TArray<IStaticType> = nil
): IAstNode;
begin
var binder := TAstBinder.Create(ParentLayout, AFunctionRegistry, AArgTypes);
var binder := TAstBinder.Create(ParentLayout, AFunctionRegistry, AArgTypes, ALog);
try
Result := binder.Execute(RootNode, Layout);
finally
@@ -239,7 +242,13 @@ begin
if Node.Address.Kind = akUnresolved then
begin
if not ResolveSymbol(Node.Name, physAddr) then
raise EBinderException.CreateFmt('Undefined identifier: "%s"', [Node.Name]);
begin
// Log error but don't crash. Return unresolved node (Poison Pill).
if Assigned(FLog) then
FLog.AddError(Format('Undefined identifier: "%s"', [Node.Name]), Node);
Result := Node;
Exit;
end;
if physAddr.ScopeDepth > 0 then
begin
@@ -264,12 +273,28 @@ var
isBoxed: Boolean;
begin
var identifier := Node.Target.AsIdentifier;
if not IsValidIdentifier(identifier.Name) then
raise EBinderException.CreateFmt('Invalid identifier name: "%s".', [identifier.Name]);
// Check if the identifier is already defined in the current scope builder to avoid Scope Asserts.
if not IsValidIdentifier(identifier.Name) then
begin
if Assigned(FLog) then
FLog.AddError(Format('Invalid identifier name: "%s".', [identifier.Name]), Node);
// Continue to try processing the initializer
end;
// Check for duplicates before defining
if FCurrentBuilder.FindSlot(identifier.Name) >= 0 then
raise EBinderException.CreateFmt('Variable "%s" is already defined in this scope.', [identifier.Name]);
begin
if Assigned(FLog) then
FLog.AddError(Format('Variable "%s" is already defined in this scope.', [identifier.Name]), Node);
// Do NOT define the slot to avoid scope corruption.
// Visit initializer to catch errors there, then return original node or dummy.
if Assigned(Node.Initializer) then
Accept(Node.Initializer);
Result := Node;
Exit;
end;
slot := FCurrentBuilder.Define(identifier.Name);
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
@@ -300,6 +325,7 @@ begin
if Assigned(FFunctionRegistry) and (newValue <> nil) and (newValue.Kind = akLambdaExpression) then
begin
// Only register if it resolved correctly
if newIdent.AsIdentifier.Address.Kind = akLocalOrParent then
FFunctionRegistry.Register(newIdent.AsIdentifier.Address, newValue.AsLambdaExpression);
end;
@@ -338,18 +364,33 @@ begin
SetLength(newParams, Length(Node.Parameters));
for i := 0 to High(Node.Parameters) do
begin
// Parameter names must also be checked for uniqueness, though usually guaranteed by Parser/AST construction
if FCurrentBuilder.FindSlot(Node.Parameters[i].Name) >= 0 then
raise EBinderException.CreateFmt('Duplicate parameter name "%s".', [Node.Parameters[i].Name]);
var paramName := Node.Parameters[i].Name;
// Check duplicate parameters
if FCurrentBuilder.FindSlot(paramName) >= 0 then
begin
if Assigned(FLog) then
FLog.AddError(Format('Duplicate parameter name "%s".', [paramName]), Node);
// Skip defining this parameter to avoid scope crash, but create a dummy binding
// so index alignment isn't completely broken if we were to continue harder.
// Here we just skip definition.
end
else
FCurrentBuilder.Define(paramName);
// Note: We still create a valid (or attempted) identifier node mapping
// even if definition failed, to keep AST shape consistent.
slot := FCurrentBuilder.FindSlot(paramName); // might return <0 or previous slot if duplicate
if slot < 0 then
slot := 0; // Fallback for duplicates
slot := FCurrentBuilder.Define(Node.Parameters[i].Name);
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
paramType := TTypes.Unknown;
if (parentBuilder.Parent = nil) and (FArgTypes <> nil) and (i < Length(FArgTypes)) then
paramType := FArgTypes[i];
newParams[i] := TAst.Identifier(Node.Parameters[i].Name, addr, paramType);
newParams[i] := TAst.Identifier(paramName, addr, paramType);
end;
newBody := Accept(Node.Body);