10 January 2010

Listing: symboltable.pb


; symboltable.pb
; Simple symbol table
; Scope separator is ":"
; So a local variable X in the function Func would be Func:X.
; A global variable X would be X.

Structure SSymbol
Name.s
Kind.i
EndStructure

Enumeration ; Symbol kind
#SKind_Variable
#SKind_Function
EndEnumeration

Structure SVariable Extends SSymbol
Type.i
RefKind.i ; Global, local, parameter
RefOffset.i ; Currently not needed for globals
EndStructure

Enumeration ; Ref kind
#Ref_Global
#Ref_Local
#Ref_Parameter
EndEnumeration

Structure SFunction Extends SSymbol
Type.i ; Return type
Ref.s ; Entry point
ParamCount.i
ParamTypes.i[10] ; We allow at most 10 parameters
EndStructure

Enumeration ; Symbol type
#TyLong
EndEnumeration

; Prototype for callback that is called once for each
; symbol in the table (for listing all symbols)
Prototype ProtoSymbolListEnum(ScopedName.s, *Symbol.SSymbol)

; Map of pointers to SSymbol
Global NewMap *Symbols.SSymbol()

; Linked list of the scopes we're in (for use with local variables)
; When looking up symbols we check innermost scopes first
; Innermost scopes will be first in the list
Global NewList CurrentScopes.S()
AddElement(CurrentScopes()) ; Add the global scope

Procedure EnterScope(Scope.s)
FirstElement(CurrentScopes())
Scope = CurrentScopes() + ":" + Scope
ResetList(CurrentScopes())
AddElement(CurrentScopes())
CurrentScopes() = Scope
EndProcedure

Procedure LeaveScope()
FirstElement(CurrentScopes())
DeleteElement(CurrentScopes())
EndProcedure

; Lookup a symbol without scope resolution
; Valid parameters would be "Foo" (global), "Bar:Glass" (local)
Procedure LookupSymbolNoScopeResolve(Name.s)
ProcedureReturn *Symbols(":"+Name)
EndProcedure

; Lookup a symbol using the scope resolution rules of our language.
; For example, I have decided that global variables are accessible
; in functions, however, local variables have the priority in case
; of name clashes, so we test for local names first.
Procedure LookupSymbol(Name.s)
Protected *S
; Try all scopes in order
ForEach CurrentScopes()
*S = *Symbols(CurrentScopes() + ":" + Name)
If *S
ProcedureReturn *S
EndIf
Next
ProcedureReturn 0
EndProcedure

; Lookup a symbol, but look only in the current scope
Procedure LookupSymbolCurrentScope(Name.s)
FirstElement(CurrentScopes())
ProcedureReturn *Symbols(CurrentScopes() + ":" + Name)
EndProcedure

; Add a local or global symbol.
; Note: when in the global scope, added symbols will
; be global even when added with Glbal = 0
Procedure AddSymbol(*Sym.SSymbol, Glbal = 0)
Protected N.s = *Sym\Name
If Not Glbal
; Not explicitly global, add it in the current scope
FirstElement(CurrentScopes())
*Symbols(CurrentScopes()+":"+N) = *Sym
Else
; Global, add without extra scope
*Symbols(N) = *Sym
EndIf
EndProcedure

; List all symbols in a custom callback
Procedure EnumerateSymbols(Callback.ProtoSymbolListEnum)
ForEach *Symbols()
Callback(MapKey(*Symbols()), *Symbols())
Next
EndProcedure

; Convert the type name into a type number.
; For now we have only one type: #TyLong
Procedure TypeFromString(TypeS.s)
Select TypeS
Case "long": ProcedureReturn #TyLong
Default
Error("Invalid type name: " + TypeS)
EndSelect
EndProcedure

Procedure AddVariable(Name.s, TypeS.s, RefKind.i, RefOffset.i)
; Check if it's already declared in this scope
Protected *V.SVariable
*V = LookupSymbolCurrentScope(Name)
If *V
Error("Variable declared twice: " + Name)
EndIf

; Add the variable to the symbol table
*V = AllocateMemory(SizeOf(SVariable))
*V\Name = Name
*V\Kind = #SKind_Variable
*V\Type = TypeFromString(TypeS)
*V\RefKind = RefKind
*V\RefOffset = RefOffset
AddSymbol(*V)
ProcedureReturn *V
EndProcedure

Procedure AddFunction(Name.s, Ref.s = "")
; Check if it's already declared in the global scope
Protected *F.SFunction
*F = LookupSymbolNoScopeResolve(Name)
If *F
Error("Function declared twice: " + Name)
EndIf

; Add the function to the symbol table
*F = AllocateMemory(SizeOf(SFunction))
*F\Name = Name
*F\Kind = #SKind_Function
*F\Type = #TyLong
*F\ParamCount = 0
If Ref = ""
*F\Ref = "_" + Name
Else
*F\Ref = Ref
EndIf
AddSymbol(*F)
ProcedureReturn *F
EndProcedure

;----------------

CompilerIf 0

Procedure ListSymbols(ScopedName.s, *V.SSymbol)
Debug ScopedName + ", " + *V\Name
EndProcedure

N.SVariable
N\Name = "Apple"
N\Kind = #SKind_Variable
N\RefKind = #Ref_Global
N\RefOffset = 0

AddSymbol(@N)

EnterScope("Blah")

O.SVariable
O\Name = "Orange"
O\Kind = #SKind_Variable
O\RefKind = #Ref_Local
O\RefOffset = 0

AddSymbol(@O)

Debug LookupSymbol("Orange")
Debug LookupSymbol("Apple")
LeaveScope()
Debug "--"
Debug LookupSymbol("Orange")
Debug LookupSymbol("Apple")
Debug LookupSymbol("Blah:Orange")

; EnumerateSymbols(@ListSymbols())

CompilerEndIf

No comments: