classes/SymbolManager.ps1
|
class SymbolManager { [hashtable]$Symbols = [ordered]@{} ### $Symbols[$Pass][$Scope][$Name][$Instance].Property [int]$CurrentPass [Scope[]]$scopes [void] SetSymbol([SymbolEntry]$symbol) { # Write-Host "SetSymbol(symbol={name=$($symbol.Name), scopeId=$($symbol.ScopeId), pass=$($symbol.Pass), value=$($symbol.Value)})" -ForegroundColor Magenta $pass = $symbol.Pass # $pass = [string]$symbol.Pass $scope = $symbol.ScopeId $name = $symbol.Name if (-not $this.Symbols[$pass]) { # Write-Host " SetSymbol: Initializing Symbols for Pass $pass" -ForegroundColor Magenta $this.Symbols[$pass] = [ordered]@{} } if (-not $this.Symbols[$pass][$scope]) { # Write-Host " SetSymbol: Initializing Symbols for Scope '$scope' in Pass $pass" -ForegroundColor Magenta $this.Symbols[$pass][$scope] = [ordered]@{} } if (-not $this.Symbols[$pass][$scope][$name]) { # Write-Host " SetSymbol: Initializing Symbols for Name '$name' in Scope '$scope' in Pass $pass" -ForegroundColor Magenta $this.Symbols[$pass][$scope][$name] = [System.Collections.Generic.List[SymbolEntry]]::new() } # Write-Host " SetSymbol: Adding Symbol '$name' in Scope '$scope' in Pass $pass with Value $($symbol.Value)" -ForegroundColor Magenta $this.Symbols[$pass][$scope][$name].Add($symbol) } [void] AddUnscopedSymbol([string]$name) { $sym = [SymbolEntry]::new() $sym.Name = $name $sym.ScopeId = 'Unscoped' $this.SetSymbol($sym) } [void] AddUnresolvedSymbol([string]$name, [string]$scopeId, [int]$line, [int]$column) { $sym = [SymbolEntry]::new() $sym.Name = $name $sym.ScopeId = $scopeId $sym.Line = $line $sym.Column = $column $this.SetSymbol($sym) } [SymbolEntry] GetSymbol($name, [int]$callerScopeId, [int]$callerLine, [int]$callerColumn, [System.Management.Automation.InvocationInfo]$invocation) { $r = $this.ResolveNameAndScope($name, $callerScopeId) if (-not $r.Resolved) { throw "Unresolved symbol '$name'. Scope could not be resolved in line $($callerLine), column $($callerColumn)" } $name = $r.Name $scopeId = $r.ScopeId $scope = [string]$scopeId $line = $invocation.ScriptLineNumber $column = $invocation.OffsetInLine $numPasses = $this.Symbols.Count $currPass = $this.CurrentPass # $currentPass = $numPasses - 1 $previousPass = $currPass - 1 # Write-Host " GetSymbol: numPasses = $numPasses, currPass = $currPass" -ForegroundColor Magenta if ($this.CurrentPass -gt 0) { # Write-Host "NUMPASSES $numPasses CurrentPass $($this.currentPass) PREV PASS: $previousPass" # Write-Host "Symbol Count: $($this.Symbols.Count)" # Write-Host "Scope: $scope" # foreach ($e in $this.GetSymbolTable() ) { # Write-Host "SYMBOL: $($e.Scope).$($e.Name) = $($e.Value)" # } # Write-Host ($this.GetSymbolTable | %{$_ |Out-String}) if ($this.Symbols[$previousPass]?[$scope]) { if ($this.Symbols[$previousPass][$scope][$name]) { $numPreviousInstances = $this.Symbols[$previousPass][$scope][$name].Count if ($this.Symbols[$currPass]) { if ($this.Symbols[$currPass][$scope]) { if ($this.Symbols[$currPass][$scope][$name]) { $numCurrentInstances = $this.Symbols[$currPass][$scope][$name].Count $currentInstance = $numCurrentInstances - 1 if ($numCurrentInstances -lt $numPreviousInstances) { if ($callerLine -lt $this.Symbols[$currPass][$scope][$name][$currentInstance].Line -or ($callerLine -eq $this.Symbols[$currPass][$scope][$name][$currentInstance].Line -and $callerColumn -lt $this.Symbols[$currPass][$scope][$name][$currentInstance].Column)) { ### For forward references return $this.Symbols[$previousPass][$scope][$name][$currentInstance+1] } ### For backward references return $this.Symbols[$currPass][$scope][$name][$currentInstance] } else { return $this.Symbols[$currPass][$scope][$name][$currentInstance] } } else { return $this.Symbols[$previousPass][$scope][$name][0] } } else { return $this.Symbols[$previousPass][$scope][$name][0] } } else { return $this.Symbols[$previousPass][$scope][$name][0] } } else { throw "Unresolved symbol in scope '$scope'. Symbol '$name' not found in line $($callerLine), column $($callerColumn)" } } else { # return [SymbolEntry]::new() throw "Unresolved symbol '$name'. Scope '$scope' not found in line $($callerLine), column $($callerColumn)" } } else { throw "GetSymbol() called in pass 0. This should never happen!" } } [boolean] TestSymbol([string]$name, [int]$callerScopeId) { if ($this.ResolveNameAndScope($name, $callerScopeId).Resolved) { return $true } else { return $false } } [object] ResolveNameAndScope([string]$name, [int]$callerScopeId) { # Split dotted string, but keep leading dot if present $v = $name.Split('.') if ($v[0].Length -eq 0) { $v[1] = '.' + $v[1] $v = $v[1..($v.Count - 1)] } $names = $v $nameIsQualified = $names.Count -gt 1 $scopeId = $callerScopeId if ($nameIsQualified) { # --- Qualified lookup --- # Start by finding matching top-level scope while ($true) { $match = $this.scopes.Where({$_.ParentId -eq $this.scopes[$scopeId].ParentId -and $_.Name -eq $names[0]}) if ($match) { $scopeId = $match.Id; break } if ($scopeId -eq $this.scopes[$scopeId].ParentId) { break } # Reached root $scopeId = $this.scopes[$scopeId].ParentId } # Descend through subscopes - if more than two names.. otherwise we have found the scope already if ($names.count -gt 2) { foreach ($n in $names[1..($names.Count - 2)]) { $next = $this.scopes.Where({$_.ParentId -eq $scopeId -and $_.Name -eq $n}) if ($next.Count -gt 1) { throw "Ambiguous scope name '$n' in qualified symbol name '$name'" } if ($next) { $scopeId = $next.Id } else { return [PSCustomObject]@{Resolved = $false;ScopeId = $null;ScopeName = $null;Name = $null} } } } $finalName = $names[-1] if ($this.Symbols[0]?[[string]$scopeId]?[$finalName]) { return [PSCustomObject]@{Resolved = $true;ScopeId = $scopeId; ScopeName = $this.scopes[$ScopeId].Name;Name = $finalName} } else { return [PSCustomObject]@{Resolved = $false;ScopeId = $null;ScopeName = $null;Name = $null} } } else { # --- Unqualified lookup --- while ($true) { if ($this.Symbols[0]?[[string]$scopeId]?[$name]) { return [PSCustomObject]@{Resolved = $true;ScopeId = $scopeId;ScopeName = $this.scopes[$ScopeId].Name;Name = $name} } if ($scopeId -eq 0) { break } # stop after checking global scope $scopeId = $this.scopes[$scopeId].ParentId } return [PSCustomObject]@{Resolved = $false;ScopeId = $null;ScopeName = $null;Name = $null} } } [object[]] GetSymbolTable() { $table = foreach ($scopeId in $this.Symbols[$this.CurrentPass].Keys) { foreach ($name in $this.Symbols[$this.CurrentPass][$scopeId].Keys) { $sym = $this.Symbols[$this.CurrentPass][$scopeId][$name][-1] [pscustomobject]@{ Scope = $scopeId Name = $name Value = $sym.value } } } return $table } [object[]] GetFullSymbolTable() { $table = foreach ($pass in $this.Symbols.Keys) { foreach ($scope in $this.Symbols[$pass].Keys) { foreach ($name in $this.Symbols[$pass][$scope].Keys) { for ($instance=0; $instance -lt $this.Symbols[$pass][$scope][$name].Count; $instance++) { $symbol = $this.Symbols[$pass][$scope][$name][$instance] [PSCustomObject]@{ Pass = $pass Scope = $scope Name = $name Instance = $instance SymName = $symbol.Name SymScope = $symbol.ScopeId SymPass = $symbol.Pass Value = $symbol.Value Width = $symbol.Width Line = $symbol.Line Column = $symbol.Column } } } } } return $table | sort Pass, Scope, Name, Instance #| ft * -auto } } |