diff --git a/compiler/src/main/pascal/uSymbolTable.pas b/compiler/src/main/pascal/uSymbolTable.pas new file mode 100644 index 0000000..b63a741 --- /dev/null +++ b/compiler/src/main/pascal/uSymbolTable.pas @@ -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. diff --git a/compiler/src/test/pascal/TestRunner.pas b/compiler/src/test/pascal/TestRunner.pas index 58c220d..1fe035d 100644 --- a/compiler/src/test/pascal/TestRunner.pas +++ b/compiler/src/test/pascal/TestRunner.pas @@ -12,7 +12,8 @@ uses consoletestrunner, cp.test.lexer, cp.test.parser, - cp.test.codegen; + cp.test.codegen, + cp.test.symtable; var Application: TTestRunner; diff --git a/compiler/src/test/pascal/cp.test.symtable.pas b/compiler/src/test/pascal/cp.test.symtable.pas new file mode 100644 index 0000000..bdbfdfd --- /dev/null +++ b/compiler/src/test/pascal/cp.test.symtable.pas @@ -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.