Add symbol table with scope nesting and built-in types
TSymbolTable manages a scope stack with case-insensitive lookup. Each TScope holds a sorted TStringList (names → TSymbol) and chains to its parent for identifier resolution. Built-in primitives (Integer, Int64, UInt32, Byte, Boolean, string) and I/O procedures (Write, WriteLn) are pre-registered in the global scope. 29 FPCUnit tests, all passing.
This commit is contained in:
parent
95e1ea58af
commit
a561525ebd
335
compiler/src/main/pascal/uSymbolTable.pas
Normal file
335
compiler/src/main/pascal/uSymbolTable.pas
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
unit uSymbolTable;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, contnrs;
|
||||
|
||||
type
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Type descriptors }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
TTypeKind = (
|
||||
tyInteger, { Int32 — QBE 'w' }
|
||||
tyInt64, { Int64 — QBE 'l' }
|
||||
tyUInt32, { UInt32 — QBE 'w' unsigned }
|
||||
tyByte, { Byte — QBE 'b' }
|
||||
tyBoolean, { Boolean — stored as byte, 0/1 only }
|
||||
tyString, { ARC-managed UTF-8 string }
|
||||
tyRecord, { Stack-allocated aggregate (Phase 2) }
|
||||
tyClass, { Heap-allocated, single-inheritance (Phase 2) }
|
||||
tyVoid { No value — used as procedure return type }
|
||||
);
|
||||
|
||||
TTypeDesc = class
|
||||
public
|
||||
Kind: TTypeKind;
|
||||
Name: string;
|
||||
function IsNumeric: Boolean;
|
||||
function IsString: Boolean;
|
||||
function IsOrdinal: Boolean;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Symbols }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
TSymbolKind = (
|
||||
skVariable,
|
||||
skType,
|
||||
skProcedure,
|
||||
skFunction,
|
||||
skParameter
|
||||
);
|
||||
|
||||
TParamDesc = class
|
||||
public
|
||||
Name: string;
|
||||
TypeDesc: TTypeDesc; { not owned }
|
||||
IsConst: Boolean;
|
||||
IsVar: Boolean;
|
||||
end;
|
||||
|
||||
TSymbol = class
|
||||
public
|
||||
Name: string;
|
||||
Kind: TSymbolKind;
|
||||
TypeDesc: TTypeDesc; { not owned; nil for procedures }
|
||||
Params: TObjectList; { owned TParamDesc; populated for procedures/functions }
|
||||
constructor Create(const AName: string; AKind: TSymbolKind; AType: TTypeDesc);
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Scope }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
TScope = class
|
||||
private
|
||||
FParent: TScope;
|
||||
FSymbols: TObjectList; { owns all TSymbol in this scope }
|
||||
FKeys: TStringList; { sorted, case-insensitive; Objects[] = TSymbol (not owned) }
|
||||
public
|
||||
constructor Create(AParent: TScope);
|
||||
destructor Destroy; override;
|
||||
property Parent: TScope read FParent;
|
||||
{ Returns False if name already defined in this scope; caller must free ASymbol. }
|
||||
function Define(ASymbol: TSymbol): Boolean;
|
||||
function LookupLocal(const AName: string): TSymbol;
|
||||
function Lookup(const AName: string): TSymbol;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Symbol table }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
TSymbolTable = class
|
||||
private
|
||||
FScopeStack: TObjectList; { owned TScope, index 0 = global }
|
||||
FAllTypes: TObjectList; { owned TTypeDesc }
|
||||
|
||||
FTypeInteger: TTypeDesc;
|
||||
FTypeInt64: TTypeDesc;
|
||||
FTypeUInt32: TTypeDesc;
|
||||
FTypeByte: TTypeDesc;
|
||||
FTypeBoolean: TTypeDesc;
|
||||
FTypeString: TTypeDesc;
|
||||
FTypeVoid: TTypeDesc;
|
||||
|
||||
function GetCurrentScope: TScope;
|
||||
function GetScopeDepth: Integer;
|
||||
function NewType(AKind: TTypeKind; const AName: string): TTypeDesc;
|
||||
procedure RegisterBuiltins;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
{ Scope management }
|
||||
function PushScope: TScope;
|
||||
procedure PopScope;
|
||||
property CurrentScope: TScope read GetCurrentScope;
|
||||
property ScopeDepth: Integer read GetScopeDepth;
|
||||
|
||||
{ Symbol management — owns ASymbol on success; caller must free on False }
|
||||
function Define(ASymbol: TSymbol): Boolean;
|
||||
function Lookup(const AName: string): TSymbol;
|
||||
|
||||
{ Type lookup — case-insensitive, returns nil if not found }
|
||||
function FindType(const AName: string): TTypeDesc;
|
||||
|
||||
{ Convenience type accessors }
|
||||
property TypeInteger: TTypeDesc read FTypeInteger;
|
||||
property TypeInt64: TTypeDesc read FTypeInt64;
|
||||
property TypeUInt32: TTypeDesc read FTypeUInt32;
|
||||
property TypeByte: TTypeDesc read FTypeByte;
|
||||
property TypeBoolean: TTypeDesc read FTypeBoolean;
|
||||
property TypeString: TTypeDesc read FTypeString;
|
||||
property TypeVoid: TTypeDesc read FTypeVoid;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ TTypeDesc }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
function TTypeDesc.IsNumeric: Boolean;
|
||||
begin
|
||||
Result := Kind in [tyInteger, tyInt64, tyUInt32, tyByte];
|
||||
end;
|
||||
|
||||
function TTypeDesc.IsString: Boolean;
|
||||
begin
|
||||
Result := Kind = tyString;
|
||||
end;
|
||||
|
||||
function TTypeDesc.IsOrdinal: Boolean;
|
||||
begin
|
||||
Result := Kind in [tyInteger, tyInt64, tyUInt32, tyByte, tyBoolean];
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ TSymbol }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
constructor TSymbol.Create(const AName: string; AKind: TSymbolKind; AType: TTypeDesc);
|
||||
begin
|
||||
inherited Create;
|
||||
Name := AName;
|
||||
Kind := AKind;
|
||||
TypeDesc := AType;
|
||||
Params := TObjectList.Create(True);
|
||||
end;
|
||||
|
||||
destructor TSymbol.Destroy;
|
||||
begin
|
||||
Params.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ TScope }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
constructor TScope.Create(AParent: TScope);
|
||||
begin
|
||||
inherited Create;
|
||||
FParent := AParent;
|
||||
FSymbols := TObjectList.Create(True);
|
||||
FKeys := TStringList.Create;
|
||||
FKeys.Sorted := True;
|
||||
FKeys.CaseSensitive := False;
|
||||
FKeys.Duplicates := dupIgnore; { we check manually before inserting }
|
||||
end;
|
||||
|
||||
destructor TScope.Destroy;
|
||||
begin
|
||||
FKeys.Free;
|
||||
FSymbols.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TScope.Define(ASymbol: TSymbol): Boolean;
|
||||
var
|
||||
Idx: Integer;
|
||||
begin
|
||||
if FKeys.Find(ASymbol.Name, Idx) then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
FSymbols.Add(ASymbol);
|
||||
{ Store the TSymbol pointer directly in the string list's Objects slot. }
|
||||
FKeys.AddObject(ASymbol.Name, ASymbol);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TScope.LookupLocal(const AName: string): TSymbol;
|
||||
var
|
||||
Idx: Integer;
|
||||
begin
|
||||
if FKeys.Find(AName, Idx) then
|
||||
Result := TSymbol(FKeys.Objects[Idx])
|
||||
else
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
function TScope.Lookup(const AName: string): TSymbol;
|
||||
var
|
||||
S: TScope;
|
||||
begin
|
||||
S := Self;
|
||||
while S <> nil do
|
||||
begin
|
||||
Result := S.LookupLocal(AName);
|
||||
if Result <> nil then
|
||||
Exit;
|
||||
S := S.FParent;
|
||||
end;
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ TSymbolTable }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
constructor TSymbolTable.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FScopeStack := TObjectList.Create(True);
|
||||
FAllTypes := TObjectList.Create(True);
|
||||
{ Global scope — parent = nil }
|
||||
FScopeStack.Add(TScope.Create(nil));
|
||||
RegisterBuiltins;
|
||||
end;
|
||||
|
||||
destructor TSymbolTable.Destroy;
|
||||
begin
|
||||
FScopeStack.Free;
|
||||
FAllTypes.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TSymbolTable.NewType(AKind: TTypeKind; const AName: string): TTypeDesc;
|
||||
begin
|
||||
Result := TTypeDesc.Create;
|
||||
Result.Kind := AKind;
|
||||
Result.Name := AName;
|
||||
FAllTypes.Add(Result);
|
||||
end;
|
||||
|
||||
procedure TSymbolTable.RegisterBuiltins;
|
||||
var
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
{ Primitive types }
|
||||
FTypeInteger := NewType(tyInteger, 'Integer');
|
||||
FTypeInt64 := NewType(tyInt64, 'Int64');
|
||||
FTypeUInt32 := NewType(tyUInt32, 'UInt32');
|
||||
FTypeByte := NewType(tyByte, 'Byte');
|
||||
FTypeBoolean := NewType(tyBoolean, 'Boolean');
|
||||
FTypeString := NewType(tyString, 'string');
|
||||
FTypeVoid := NewType(tyVoid, 'void');
|
||||
|
||||
{ Register type names as skType symbols in global scope }
|
||||
Define(TSymbol.Create('Integer', skType, FTypeInteger));
|
||||
Define(TSymbol.Create('Int64', skType, FTypeInt64));
|
||||
Define(TSymbol.Create('UInt32', skType, FTypeUInt32));
|
||||
Define(TSymbol.Create('Byte', skType, FTypeByte));
|
||||
Define(TSymbol.Create('Boolean', skType, FTypeBoolean));
|
||||
Define(TSymbol.Create('string', skType, FTypeString));
|
||||
|
||||
{ Built-in I/O procedures }
|
||||
Sym := TSymbol.Create('Write', skProcedure, nil);
|
||||
Define(Sym);
|
||||
Sym := TSymbol.Create('WriteLn', skProcedure, nil);
|
||||
Define(Sym);
|
||||
end;
|
||||
|
||||
function TSymbolTable.GetCurrentScope: TScope;
|
||||
begin
|
||||
Result := TScope(FScopeStack[FScopeStack.Count - 1]);
|
||||
end;
|
||||
|
||||
function TSymbolTable.GetScopeDepth: Integer;
|
||||
begin
|
||||
Result := FScopeStack.Count;
|
||||
end;
|
||||
|
||||
function TSymbolTable.PushScope: TScope;
|
||||
begin
|
||||
Result := TScope.Create(CurrentScope);
|
||||
FScopeStack.Add(Result);
|
||||
end;
|
||||
|
||||
procedure TSymbolTable.PopScope;
|
||||
begin
|
||||
if FScopeStack.Count > 1 then
|
||||
FScopeStack.Delete(FScopeStack.Count - 1);
|
||||
end;
|
||||
|
||||
function TSymbolTable.Define(ASymbol: TSymbol): Boolean;
|
||||
begin
|
||||
Result := CurrentScope.Define(ASymbol);
|
||||
end;
|
||||
|
||||
function TSymbolTable.Lookup(const AName: string): TSymbol;
|
||||
begin
|
||||
Result := CurrentScope.Lookup(AName);
|
||||
end;
|
||||
|
||||
function TSymbolTable.FindType(const AName: string): TTypeDesc;
|
||||
var
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
Sym := CurrentScope.Lookup(AName);
|
||||
if (Sym <> nil) and (Sym.Kind = skType) then
|
||||
Result := Sym.TypeDesc
|
||||
else
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -12,7 +12,8 @@ uses
|
|||
consoletestrunner,
|
||||
cp.test.lexer,
|
||||
cp.test.parser,
|
||||
cp.test.codegen;
|
||||
cp.test.codegen,
|
||||
cp.test.symtable;
|
||||
|
||||
var
|
||||
Application: TTestRunner;
|
||||
|
|
|
|||
532
compiler/src/test/pascal/cp.test.symtable.pas
Normal file
532
compiler/src/test/pascal/cp.test.symtable.pas
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
unit cp.test.symtable;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, fpcunit, testregistry,
|
||||
uSymbolTable;
|
||||
|
||||
type
|
||||
TSymbolTableTests = class(TTestCase)
|
||||
published
|
||||
{ Built-in primitive types are pre-registered }
|
||||
procedure TestBuiltin_Type_Integer;
|
||||
procedure TestBuiltin_Type_Int64;
|
||||
procedure TestBuiltin_Type_UInt32;
|
||||
procedure TestBuiltin_Type_Byte;
|
||||
procedure TestBuiltin_Type_Boolean;
|
||||
procedure TestBuiltin_Type_String;
|
||||
|
||||
{ Type descriptor properties }
|
||||
procedure TestTypeDesc_Integer_IsNumeric;
|
||||
procedure TestTypeDesc_Boolean_IsNotNumeric;
|
||||
procedure TestTypeDesc_String_IsString;
|
||||
procedure TestTypeDesc_Integer_IsNotString;
|
||||
|
||||
{ FindType is case-insensitive (Pascal identifiers are) }
|
||||
procedure TestFindType_CaseInsensitive;
|
||||
procedure TestFindType_Unknown_ReturnsNil;
|
||||
|
||||
{ Symbol definition }
|
||||
procedure TestDefine_Variable;
|
||||
procedure TestDefine_Procedure;
|
||||
procedure TestDefine_DuplicateInSameScope_ReturnsFalse;
|
||||
procedure TestDefine_SameNameDiffScope_IsAllowed;
|
||||
|
||||
{ Symbol lookup }
|
||||
procedure TestLookup_FindsInCurrentScope;
|
||||
procedure TestLookup_NotFound_ReturnsNil;
|
||||
procedure TestLookup_CaseInsensitive;
|
||||
|
||||
{ Scope nesting }
|
||||
procedure TestScope_InnerSeesOuter;
|
||||
procedure TestScope_OuterCannotSeeInner;
|
||||
procedure TestScope_InnerShadowsOuter;
|
||||
procedure TestScope_AfterPop_InnerSymbolsGone;
|
||||
procedure TestScope_DepthAfterPushPop;
|
||||
|
||||
{ Built-in procedures }
|
||||
procedure TestBuiltin_WriteLn_Exists;
|
||||
procedure TestBuiltin_Write_Exists;
|
||||
procedure TestBuiltin_WriteLn_IsProcedure;
|
||||
|
||||
{ Symbol properties }
|
||||
procedure TestSymbol_Variable_HasType;
|
||||
procedure TestSymbol_Procedure_HasVoidReturn;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Helpers }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
function MakeVar(const AName: string; AType: TTypeDesc): TSymbol;
|
||||
begin
|
||||
Result := TSymbol.Create(AName, skVariable, AType);
|
||||
end;
|
||||
|
||||
function MakeProc(const AName: string): TSymbol;
|
||||
begin
|
||||
Result := TSymbol.Create(AName, skProcedure, nil);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Built-in types }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Type_Integer;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('Integer type', ST.FindType('Integer'));
|
||||
AssertEquals('Integer kind', Ord(tyInteger), Ord(ST.FindType('Integer').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Type_Int64;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('Int64 type', ST.FindType('Int64'));
|
||||
AssertEquals('Int64 kind', Ord(tyInt64), Ord(ST.FindType('Int64').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Type_UInt32;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('UInt32 type', ST.FindType('UInt32'));
|
||||
AssertEquals('UInt32 kind', Ord(tyUInt32), Ord(ST.FindType('UInt32').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Type_Byte;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('Byte type', ST.FindType('Byte'));
|
||||
AssertEquals('Byte kind', Ord(tyByte), Ord(ST.FindType('Byte').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Type_Boolean;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('Boolean type', ST.FindType('Boolean'));
|
||||
AssertEquals('Boolean kind', Ord(tyBoolean), Ord(ST.FindType('Boolean').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Type_String;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('string type', ST.FindType('string'));
|
||||
AssertEquals('string kind', Ord(tyString), Ord(ST.FindType('string').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Type descriptor properties }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestTypeDesc_Integer_IsNumeric;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertTrue('Integer is numeric', ST.FindType('Integer').IsNumeric);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestTypeDesc_Boolean_IsNotNumeric;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertFalse('Boolean is not numeric', ST.FindType('Boolean').IsNumeric);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestTypeDesc_String_IsString;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertTrue('string IsString', ST.FindType('string').IsString);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestTypeDesc_Integer_IsNotString;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertFalse('Integer IsString', ST.FindType('Integer').IsString);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ FindType }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestFindType_CaseInsensitive;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('integer lowercase', ST.FindType('integer'));
|
||||
AssertNotNull('INTEGER uppercase', ST.FindType('INTEGER'));
|
||||
AssertNotNull('String mixed', ST.FindType('String'));
|
||||
AssertSame('Same descriptor', ST.FindType('Integer'), ST.FindType('integer'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestFindType_Unknown_ReturnsNil;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNull('unknown type', ST.FindType('Foobar'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Symbol definition }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestDefine_Variable;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Sym := MakeVar('x', ST.FindType('Integer'));
|
||||
AssertTrue('Define succeeds', ST.Define(Sym));
|
||||
AssertSame('Lookup finds it', Sym, ST.Lookup('x'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestDefine_Procedure;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Sym := MakeProc('Greet');
|
||||
AssertTrue('Define succeeds', ST.Define(Sym));
|
||||
AssertEquals('Kind', Ord(skProcedure), Ord(ST.Lookup('Greet').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestDefine_DuplicateInSameScope_ReturnsFalse;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
S1, S2: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
S1 := MakeVar('x', ST.FindType('Integer'));
|
||||
S2 := MakeVar('x', ST.FindType('string'));
|
||||
AssertTrue('First define ok', ST.Define(S1));
|
||||
AssertFalse('Duplicate fails', ST.Define(S2));
|
||||
S2.Free;
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestDefine_SameNameDiffScope_IsAllowed;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
S1, S2: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
S1 := MakeVar('n', ST.FindType('Integer'));
|
||||
AssertTrue('Outer define ok', ST.Define(S1));
|
||||
ST.PushScope;
|
||||
S2 := MakeVar('n', ST.FindType('string'));
|
||||
AssertTrue('Inner define ok — shadowing allowed', ST.Define(S2));
|
||||
ST.PopScope;
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Symbol lookup }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestLookup_FindsInCurrentScope;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Sym := MakeVar('result', ST.FindType('Integer'));
|
||||
ST.Define(Sym);
|
||||
AssertSame('Found', Sym, ST.Lookup('result'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestLookup_NotFound_ReturnsNil;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNull('Missing symbol', ST.Lookup('doesNotExist'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestLookup_CaseInsensitive;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Sym := MakeVar('MyVar', ST.FindType('Integer'));
|
||||
ST.Define(Sym);
|
||||
AssertSame('lowercase', Sym, ST.Lookup('myvar'));
|
||||
AssertSame('uppercase', Sym, ST.Lookup('MYVAR'));
|
||||
AssertSame('mixed', Sym, ST.Lookup('MyVar'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Scope nesting }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestScope_InnerSeesOuter;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Outer: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Outer := MakeVar('x', ST.FindType('Integer'));
|
||||
ST.Define(Outer);
|
||||
ST.PushScope;
|
||||
AssertSame('Inner sees outer x', Outer, ST.Lookup('x'));
|
||||
ST.PopScope;
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestScope_OuterCannotSeeInner;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Inner: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
ST.PushScope;
|
||||
Inner := MakeVar('local', ST.FindType('Integer'));
|
||||
ST.Define(Inner);
|
||||
ST.PopScope;
|
||||
AssertNull('Outer cannot see inner local', ST.Lookup('local'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestScope_InnerShadowsOuter;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Outer, Inner: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Outer := MakeVar('n', ST.FindType('Integer'));
|
||||
ST.Define(Outer);
|
||||
ST.PushScope;
|
||||
Inner := MakeVar('n', ST.FindType('string'));
|
||||
ST.Define(Inner);
|
||||
AssertSame('Inner n shadows outer n', Inner, ST.Lookup('n'));
|
||||
ST.PopScope;
|
||||
AssertSame('After pop, outer n visible again', Outer, ST.Lookup('n'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestScope_AfterPop_InnerSymbolsGone;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Inner: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
ST.PushScope;
|
||||
Inner := MakeVar('temp', ST.FindType('Integer'));
|
||||
ST.Define(Inner);
|
||||
AssertNotNull('Present before pop', ST.Lookup('temp'));
|
||||
ST.PopScope;
|
||||
AssertNull('Gone after pop', ST.Lookup('temp'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestScope_DepthAfterPushPop;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertEquals('Depth 1 initially', 1, ST.ScopeDepth);
|
||||
ST.PushScope;
|
||||
AssertEquals('Depth 2 after push', 2, ST.ScopeDepth);
|
||||
ST.PushScope;
|
||||
AssertEquals('Depth 3', 3, ST.ScopeDepth);
|
||||
ST.PopScope;
|
||||
AssertEquals('Depth 2 after pop', 2, ST.ScopeDepth);
|
||||
ST.PopScope;
|
||||
AssertEquals('Depth 1 restored', 1, ST.ScopeDepth);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Built-in procedures }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_WriteLn_Exists;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('WriteLn exists', ST.Lookup('WriteLn'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_Write_Exists;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertNotNull('Write exists', ST.Lookup('Write'));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestBuiltin_WriteLn_IsProcedure;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
AssertEquals('WriteLn kind',
|
||||
Ord(skProcedure), Ord(ST.Lookup('WriteLn').Kind));
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Symbol properties }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TSymbolTableTests.TestSymbol_Variable_HasType;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Sym := MakeVar('s', ST.FindType('string'));
|
||||
ST.Define(Sym);
|
||||
AssertSame('TypeDesc', ST.FindType('string'), ST.Lookup('s').TypeDesc);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSymbolTableTests.TestSymbol_Procedure_HasVoidReturn;
|
||||
var
|
||||
ST: TSymbolTable;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
ST := TSymbolTable.Create;
|
||||
try
|
||||
Sym := MakeProc('DoSomething');
|
||||
ST.Define(Sym);
|
||||
AssertNull('Procedure has no return type', ST.Lookup('DoSomething').TypeDesc);
|
||||
finally
|
||||
ST.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TSymbolTableTests);
|
||||
|
||||
end.
|
||||
Loading…
Reference in a new issue