classes/SemanticParser.ps1
|
class SemanticParser { [System.Collections.Generic.List[object]]$inTokens [System.Collections.Generic.List[object]]$outTokens [SymbolManager]$symbolManager [ScopeManager]$scopeManager [System.Collections.Generic.List[object]]$Macros [Tokenizer]$tokenizer [int]$LineCounter [hashtable]$LineMap [MultiLevelCounter]$hashTableCounter SemanticParser([InputFileStack]$FileStack) { $this.LineCounter = 0 ### Line Number in outTokens, see ParseToken() for handling $this.LineMap = @{} $this.tokenizer = [Tokenizer]::new($FileStack) $this.inTokens = $this.tokenizer.tokens $this.outTokens = [System.Collections.Generic.List[Token]]::new() $this.symbolManager = [SymbolManager]::new() $this.scopeManager = [ScopeManager]::new() $this.Macros = [System.Collections.Generic.List[PSCustomObject]]::new() $this.hashTableCounter = [MultiLevelCounter]::new(2) $this.MapLabels() ### This must be done before mapping scopes and symbols! $this.MapScopes() ### This must be done before mapping symbols! $this.MapSymbols() ### This must be done before parsing tokens! $this.ParseAllTokens() } SemanticParser() {} [void] AddToken([string]$value) { $this.outTokens.Add([Token]::new([TokenType]::Unknown, $value)) } [void] InsertToken([int]$index, [string]$value) { $this.outTokens.Insert($index, [Token]::new([TokenType]::Unknown, $value)) } [int] SkipWhitespace([int]$tokenIndex) { while($this.inTokens[$tokenIndex].Type -eq [TokenType]::WhiteSpace) {$tokenIndex++} return $tokenIndex } [int] SkipWhitespaceBackwards([int]$tokenIndex) { while($this.inTokens[$tokenIndex].Type -eq [TokenType]::WhiteSpace) {$tokenIndex--} return $tokenIndex } [bool] IsNextToken([int]$tokenIndex, [TokenType]$tokenType) { $i = $this.SkipWhitespace($tokenIndex + 1) return $this.inTokens[$i].Type -eq $tokenType } [bool] IsNextToken([int]$tokenIndex, [TokenType[]]$tokenTypes) { $i = $this.SkipWhitespace($tokenIndex + 1) return $this.inTokens[$i].Type -in $tokenTypes } [int] SkipToNextToken([int]$tokenIndex) { $tokenIndex++ while($this.inTokens[$tokenIndex].Type -eq [TokenType]::WhiteSpace) {$tokenIndex++} return $tokenIndex } [int] SkipToNextToken([int]$tokenIndex, [TokenType]$tokenType) { $tokenIndex++ while($this.inTokens[$tokenIndex].Type -ne $tokenType) {$tokenIndex++} return $tokenIndex } [int] SkipToNextToken([int]$tokenIndex, [TokenType[]]$tokenTypes) { $tokenIndex++ while($this.inTokens[$tokenIndex].Type -notin $tokenTypes) {$tokenIndex++} return $tokenIndex } [int] SkipToPrevToken([int]$tokenIndex, [TokenType]$tokenType) { $tokenIndex-- while($this.inTokens[$tokenIndex].Type -ne $tokenType) {$tokenIndex--} return $tokenIndex } [int] SkipToPrevToken([int]$tokenIndex, [TokenType[]]$tokenTypes) { $tokenIndex-- while($this.inTokens[$tokenIndex].Type -notin $tokenTypes) {$tokenIndex--} return $tokenIndex } [int] ParseUntilNextToken([int]$tokenIndex, [TokenType]$tokenType) { $tokenIndex++ while ($tokenIndex -lt $this.inTokens.Count -and $this.inTokens[$tokenIndex].Type -ne $tokenType) { $tokenIndex = $this.ParseToken($tokenIndex) } return $tokenIndex } [int] ParseUntilNextToken([int]$tokenIndex, [TokenType[]]$tokenTypes) { $tokenIndex++ while ($tokenIndex -lt $this.inTokens.Count -and $this.inTokens[$tokenIndex].Type -notin $tokenTypes) { $tokenIndex = $this.ParseToken($tokenIndex) } return $tokenIndex } [int] ParseUntilAfterNextToken([int]$tokenIndex, [TokenType]$tokenType) { while ($tokenIndex -lt $this.inTokens.Count -and $this.inTokens[$tokenIndex-1].Type -ne $tokenType) { $tokenIndex = $this.ParseToken($tokenIndex) } return $tokenIndex } [bool] IsPrevToken([int]$tokenIndex, [TokenType]$tokenType) { $i = $this.SkipWhitespaceBackwards($tokenIndex - 1) return $this.inTokens[$i].Type -eq $tokenType } [bool] IsPrevToken([int]$tokenIndex, [TokenType[]]$tokenTypes) { $i = $this.SkipWhitespaceBackwards($tokenIndex - 1) return $this.inTokens[$i].Type -in $tokenTypes } [bool] IsPrevTokenValue([int]$tokenIndex, [string]$tokenValue) { $i = $this.SkipWhitespaceBackwards($tokenIndex - 1) return $this.inTokens[$i].Value -match $tokenValue } hidden [int] LookBackForToken([int]$startIndex, [TokenType[]]$stopTypes, [TokenType[]]$matchTypes, [bool]$skipParenthesis) { $i = 1 $depth = 0 while ($startIndex - $i -ge 0) { $token = $this.inTokens[$startIndex - $i] if ($skipParenthesis) { if ($token.Type -eq [TokenType]::RParen) { $depth++ $i++ continue } elseif ($token.Type -eq [TokenType]::LParen) { if ($depth -gt 0) { $depth-- $i++ continue } } } # Only consider stopTypes when outside parentheses if ($depth -eq 0 -and $token.Type -in $stopTypes) { break } # Only check for matchTypes outside parentheses if ($depth -eq 0 -and $token.Type -in $matchTypes) { return $startIndex - $i } $i++ } return -1 } [void] MapLabels() { ### This updates the Value property of inTokens directly! # Remove trailing : from labels and create unique names for anonymous labels $labels = $this.inTokens.Where({$_.Type -eq [TokenType]::Label}).ForEach({$_.Value = $_.Value.Trim(':');$_}) $anonymousLabels = $this.inTokens.Where({$_.Type -eq [TokenType]::AnonymousLabel}).ForEach({$_.Value = "ANON_L$($_.Line)_C$($_.Column)";$_}) # Resolve anonymous references to the corresponding anonymous labels # This updates the Value property of the reference token to the resolved label name $anonymousReferences = foreach($ref in $this.inTokens.Where({$_.Type -eq [TokenType]::AnonymousReference})) { if($ref.Value[1] -eq '+') { $r = ($anonymousLabels.Where({$_.Index -gt $ref.Index}) | Sort-Object -Property {$_.Index})[$ref.Length-2] } else { $r = ($anonymousLabels.Where({$_.Index -lt $ref.Index}) | Sort-Object -Descending -Property {$_.Index})[$ref.Length-2] } $ref.Value = $r.Value $ref } } [void] MapScopes() { for ($tokenIndex = 0; $tokenindex -lt $this.inTokens.Count; $tokenIndex++) { $token = $this.inTokens[$tokenIndex] switch($token.Type) { ([TokenType]::LCurly) { $matchedIndex = $this.LookBackForToken($tokenIndex, @([TokenType]::LCurly, [TokenType]::RCurly, [TokenType]::Pipe), @([TokenType]::Label, [TokenType]::AnonymousLabel, [TokenType]::Identifier, [TokenType]::PSClassMethod), $true) if ($matchedIndex -ge 0) { $this.scopeManager.EnterNewScope($this.inTokens[$matchedIndex].Value, $tokenIndex, $token.Line, $token.Column) } else { $this.scopeManager.EnterNewScope($tokenIndex, $token.Line, $token.Column) } } ([TokenType]::RCurly) { $this.scopeManager.ExitNewScope($tokenIndex, $token.Line, $token.Column) } ([TokenType]::EOF) { $this.scopeManager.ExitNewScope($tokenIndex, $token.Line, $token.Column) } } } $this.symbolManager.scopes = $this.scopeManager.scopes } ### To support empty parameter lists for macros both in definition and calls, we need to know all macro names beforehand ### This allows to define a macro like .macro myMacro() {...} and call it like myMacro() ### We also need to know macro names, to assign them to scopes... [void] MapSymbols() { for ($tokenIndex = 0; $tokenindex -lt $this.inTokens.Count; $tokenIndex++) { $token = $this.inTokens[$tokenIndex] $scopeid = $this.scopeManager.GetScopeByIndex($tokenIndex).Id switch($token.Type) { ([TokenType]::Directive) { if ($token.Value -match '\.mac(ro)?') { if ($this.IsNextToken($tokenIndex, [TokenType[]]@([TokenType]::Identifier,[TokenType]::Directive))) { $ti = $this.SkipToNextToken($tokenIndex) $this.Macros.Add([pscustomobject]@{ScopeID = $scopeid;Name = $this.inTokens[$ti].Value}) $this.symbolManager.AddUnresolvedSymbol($this.inTokens[$ti].Value, $scopeId, $this.inTokens[$ti].Line, $this.inTokens[$ti].Column) } else { throw "Macro definition at line $($token.Line), column $($token.Column) missing name" } } } ([TokenType]::Label) { $this.symbolManager.AddUnresolvedSymbol($token.Value, $scopeId, $token.Line, $token.Column) } ([TokenType]::AnonymousLabel) { $this.symbolManager.AddUnresolvedSymbol($token.Value, $scopeId, $token.Line, $token.Column) } ([TokenType]::AnonymousReference) { # $this.symbolManager.AddUnresolvedSymbol($token.Value, $scopeId, $token.Line, $token.Column) } } } } [int] ParseToken([int]$tokenIndex) { if ($tokenIndex -gt $this.inTokens.Count) { throw "Parser error: Token index $tokenIndex out of range" } $nextTokenIndex = $tokenIndex + 1 $token = $this.inTokens[$tokenIndex] if ($this.LineCounter -eq 0) { $this.LineMap.Add(++$this.LineCounter, @{File = $token.Filename; Line = ($token.Line)}) } switch($token.Type) { ([TokenType]::Label) { $symbolName = $token.Value.Trim(':') $this.AddToken(".label -name $symbolName -scopeId $($this.scopeManager.GetCurrentScope());") $this.symbolManager.AddUnresolvedSymbol($symbolName, $this.scopeManager.GetCurrentScope(), $token.Line, $token.Column) if ($this.IsNextToken($tokenIndex, [TokenType]::LCurly)) { $this.AddToken("&") } } ([TokenType]::AnonymousLabel) { $symbolName = "ANON_L$($token.Line)_C$($token.Column)" $this.AddToken(".label -name $symbolName -scopeId $($this.scopeManager.GetCurrentScope());") $this.symbolManager.AddUnresolvedSymbol($symbolName, $this.scopeManager.GetCurrentScope(), $token.Line, $token.Column) } ([TokenType]::AnonymousReference) { $this.AddToken("(_getSymbol '$($token.Value)' $($this.scopeManager.GetCurrentScope()) $($token.Line) $($token.Column))") } ([TokenType]::Directive) { $this.AddToken($token.Value) if ($token.Value -match '\.mac(ro)?') { $nextTokenIndex = $this.ParseUntilNextToken($tokenIndex, [TokenType[]]@([TokenType]::SemiColon, [TokenType]::NewLine)) $this.AddToken(" -ScopeID $($this.scopeManager.GetCurrentScope());") } if ($token.Value -in '.byte', '.word', '.text', '.txt', '.petscii', '.ascii', '.fill', '.align') { $this.AddToken(" -InvocationFile '$($token.Filename)' -InvocationLine $($token.Line)") } } ([TokenType]::Identifier) { $tval = $token.Value $ti = $tokenIndex # Build qualified name if identifier has members (e.g. myLabel.part1.part2) while ($this.inTokens[$ti+1].Type -eq [TokenType]::Member) { $ti++ $tval += $this.inTokens[$ti].Value } if ($this.hashTableCounter.Counters[0] -gt 0 -and $this.hashTableCounter.Counters[1] -eq 1) { # We're in a hashtable so identifier is a key, return as is and let PowerShell handle it $this.AddToken($token.Value) break } if (-not $this.IsPrevToken($tokenIndex, [TokenType]::ColonColon)) { # Check if next token is '=' (assignment) - if so, convert to .label call if ($this.IsNextToken($ti, [TokenType]::Equals)) { $this.symbolManager.AddUnresolvedSymbol($tval, $this.scopeManager.GetCurrentScope(), $token.Line, $token.Column) $this.AddToken(".label -name $tval -scopeId $($this.scopeManager.GetCurrentScope()) -addr (") # Skip the Equal sign $ti = $this.SkipToNextToken($ti, [TokenType]::Equals) # Parse nested expression until semicolon or newline $nextTokenIndex = $this.ParseUntilNextToken($ti, [TokenType[]]@([TokenType]::SemiColon, [TokenType]::NewLine)) $this.AddToken(");") break } if($this.symbolManager.TestSymbol($tval, $this.scopeManager.GetCurrentScope())) { # Get the macro name without any scope qualification, but keep the leading . if $tval is not qualified. $mname = $tval -replace '^(?!\.[^.]+$).*\.', '' if ($mname -in $this.Macros.Name -and -not ($this.IsPrevTokenValue($ti, '\.mac(ro)?'))) { $this.AddToken("_invokeMacro -name '$tval' -ScopeID $($this.scopeManager.GetCurrentScope()) -MacroArgs @(") # Parse nested expression until semicolon or newline $nextTokenIndex = $this.ParseUntilNextToken($ti, [TokenType[]]@([TokenType]::SemiColon, [TokenType]::NewLine, [TokenType]::RCurly)) $this.AddToken(")") break } if (-not ($mname -in $this.Macros.Name)) { # This is shit... If a minus is immediately preceding the identifier, I assume it's a parameter to a function # The correct solution is to keep track of if we are in a function call or not... # Maybe even do this in the Tokenizer, since there is a token type PSFunctionParameter, where i btw also make assumtions I should not. # For now I guess if you need to subtract a label/identifier from something, you just need to put a space between the minus and the identifier. if ($this.inTokens[$ti-1].Type -ne [TokenType]::Minus) { $this.AddToken('(_getSymbol "'+$($tval)+'" '+$($this.scopeManager.GetCurrentScope())+' '+$($token.Line)+' '+$($token.Column)+')') $nextTokenIndex = $ti+1 break } } } } $this.AddToken($token.Value) } ([TokenType]::CStyleBlockComment) { # $s = $token.Value # $s = $s -replace '/\*','<#' # $s = $s -replace '\*/','#>' # $this.AddToken($s) } ([TokenType]::CStyleLineComment) { # $this.AddToken(($token.Value -replace '//',' #')) } ([TokenType]::NumericLiteral) { $s = $token.Value if($s[0] -eq '$') {$s=$s -replace '[$]','0x'} if($s[0] -eq '%') {$s=$s -replace '[%]','0b'} $this.AddToken($s) } ([TokenType]::LAngle) { ### Commenting the following lines out, to always translate > to _hiByte - this breaks PowerShell file redirection operators. # $j=1 # while($tokenIndex-$j -ge 0 -and $this.inTokens[$tokenIndex-$j].Type -notin $null, [TokenType]::SemiColon, [TokenType]::NewLine){ # if($this.inTokens[$tokenIndex-$j++].Type -in [TokenType]::Mnemonic, [TokenType]::Directive) { $this.AddToken("_loByte ") break # } # } } ([TokenType]::RAngle) { ### Commenting the following lines out, to always translate > to _hiByte - this breaks PowerShell file redirection operators. # $j=1 # while($tokenIndex-$j -ge 0 -and $this.inTokens[$tokenIndex-$j].Type -notin $null, [TokenType]::SemiColon, [TokenType]::NewLine){ # if($this.inTokens[$tokenIndex-$j++].Type -in [TokenType]::Mnemonic, [TokenType]::Directive) { $this.AddToken("_hiByte ") break # } # } } ([TokenType]::LCurly) { if ($this.hashTableCounter.Counters[0] -gt 0) { $this.hashTableCounter.Inc(1) } $this.scopeManager.EnterScope($tokenIndex) $this.AddToken($token.Value) $ti = $tokenIndex+1 while ($this.inTokens[$ti].Type -notin $null, [TokenType]::RCurly) { $ti = $this.ParseToken($ti) } $ti = $this.ParseToken($ti) # Parse the closing curly brace to avoid stack backtracking in the while loop above $nextTokenIndex = $ti } ([TokenType]::RCurly) { if ($this.hashTableCounter.Counters[0] -gt 0) { $this.hashTableCounter.Dec(1) if ($this.hashTableCounter.Counters[1] -eq 0) { $this.hashTableCounter.Dec(0) } } $this.scopeManager.ExitScope() $this.AddToken($token.Value) } ([TokenType]::LParen) { if ($this.IsNextToken($tokenIndex, [TokenType]::RParen)) { # fucking power fuckshell and its inconsistent early type inference... why the fuck do I need to cast the TokenType array here?!?!??!!??!???? if ($this.IsPrevToken($tokenIndex, [TokenType[]]@([TokenType]::Identifier, [TokenType]::Member))) { $ti = $this.SkipToPrevToken($tokenIndex, [TokenType[]]@([TokenType]::Identifier, [TokenType]::Member)) $tval = $this.inTokens[$ti].Type -eq [TokenType]::Member ? $this.inTokens[$ti].Value.Substring(1) : $this.inTokens[$ti].Value if ($tval -in $this.Macros.Name) { $nextTokenIndex = $this.SkipToNextToken($tokenIndex, [TokenType]::RParen) + 1 break } if ($this.IsPrevToken($ti, [TokenType]::Directive) -and $this.IsPrevTokenValue($ti, '\.mac(ro)?')) { $nextTokenIndex = $this.SkipToNextToken($tokenIndex, [TokenType]::RParen) + 1 break } } } $this.AddToken($token.Value) } ([TokenType]::Asterisk) { if ($this.IsPrevToken($tokenIndex, [TokenType[]]@([TokenType]::Equals, [TokenType]::Comma, [TokenType]::Divide, [TokenType]::Minus, [TokenType]::Modulo, [TokenType]::Plus, [TokenType]::Asterisk, [TokenType]::LAngle, [TokenType]::RAngle, [TokenType]::LParen, [TokenType]::Mnemonic, [TokenType]::Directive))) { $this.AddToken("(.pc)") break; } if ($this.IsNextToken($tokenIndex, [TokenType[]]@([TokenType]::Equals, [TokenType]::Comma, [TokenType]::Divide, [TokenType]::Minus, [TokenType]::Modulo, [TokenType]::Plus, [TokenType]::Asterisk, [TokenType]::RParen))) { $this.AddToken("(.pc)") break; } $this.AddToken($token.Value) } ([TokenType]::AtSymbol) { if ($this.IsNextToken($tokenIndex, [TokenType]::LCurly)) { $this.hashTableCounter.Inc(0) } $this.AddToken($token.Value) } ([TokenType]::Mnemonic) { $mne=$token.Value $instStartIndex = $tokenIndex+1 $instEndIndex = $instStartIndex $addressingMode = $null ### Find end of Instruction $lcurl=0 while($this.inTokens[$instEndIndex].Type -notin [TokenType]::CStyleLineComment, [TokenType]::PSLineComment, [TokenType]::Newline, [TokenType]::SemiColon, [TokenType]::EOF) { if ($this.inTokens[$instEndIndex].Type -eq [TokenType]::LCurly) { $lcurl++ } if ($this.inTokens[$instEndIndex].Type -eq [TokenType]::RCurly) { $lcurl-- } if ($lcurl -lt 0) { break } $instEndIndex++ } ### Backtrack whitespaces, to keep trailing whitespace out of Operator parameter while($this.inTokens[$instEndIndex-1].Type -eq [TokenType]::Whitespace) { $instEndIndex-- } ### Skip whitespace after mnemonic $tokenIndex = $this.Skipwhitespace(++$tokenIndex) ### Addressing mode detection state machine enum State {Init; Immediate; Absolute; AbsoluteIndexed; AbsoluteIndexedX; AbsoluteIndexedY; Indirect; IndirectAbsolute; IndirectIndexed; IndirectIndexedY; Indexed; IndexedX; IndexedXIndirect; Relative} if($mne -in 'BCC','BCS','BEQ','BMI','BNE','BPL','BVC','BVS') { $state = [State]::Relative } else { $state = [State]::Init } $operandTokensIndex=@() $parenCount=0 for($i=$tokenIndex;$i -lt $instEndIndex;$i++) { $tk = $this.inTokens[$i] switch($tk.Type) { ([TokenType]::Whitespace) {break} ([TokenType]::Label) { # Support label definitions at operand position, e.g. "lda myLabel:#0; inc myLabel;" $symbolName = $tk.Value $scopeId = $this.scopeManager.GetCurrentScope() $this.AddToken(".label -name $($symbolName) -scopeId $($scopeId) -addr ((.pc) + 1);") $this.symbolManager.AddUnresolvedSymbol($symbolName, $scopeId, $tk.Line, $tk.Column) break } ([TokenType]::AnonymousLabel) { # Support anonymous label definitions at operand position, e.g. "lda :#0; inc :-;" $symbolName = "ANON_L$($tk.Line)_C$($tk.Column)" $scopeId = $this.scopeManager.GetCurrentScope() $this.AddToken(".label -name $($symbolName) -scopeId $($scopeId) -addr ((.pc) + 1);") $this.symbolManager.AddUnresolvedSymbol($symbolName, $scopeId, $tk.Line, $tk.Column) break } ([TokenType]::Hash) { if($state -eq [state]::Init) {$state = [state]::Immediate; break} } ([TokenType]::LParen) { $parenCount++ if($state -eq [state]::Init) {$state = [state]::Indirect; break} $operandTokensIndex+=$i } ([TokenType]::RParen) { if(--$parenCount -eq 0) { if($state -eq [state]::Indirect) {$state = [state]::IndirectAbsolute; break} if($state -eq [state]::IndexedX) {$state = [state]::IndexedXIndirect; break} } $operandTokensIndex+=$i } ([TokenType]::Comma) { if($state -eq [state]::Absolute) {$state = [state]::AbsoluteIndexed; break} if($state -eq [state]::Indirect) {$state = [state]::Indexed; break} if($state -eq [state]::IndirectAbsolute) {$state = [state]::IndirectIndexed; break} $operandTokensIndex+=$i } {$tk.Value -eq 'x'} { if($state -eq [state]::Indexed) {$state = [state]::IndexedX; break} if($state -eq [state]::AbsoluteIndexed) {$state = [state]::AbsoluteIndexedX; break} $operandTokensIndex+=$i } {$tk.Value -eq 'y'} { if($state -eq [state]::IndirectIndexed) {$state = [state]::IndirectIndexedY; break} if($state -eq [state]::AbsoluteIndexed) {$state = [state]::AbsoluteIndexedY; break} $operandTokensIndex+=$i } default { if($state -eq [state]::Init) {$state = [state]::Absolute} $operandTokensIndex+=$i } } } $this.AddToken(".inst -Mnemonic $($mne)") if($state -eq [state]::Init) { $addressingMode = [MOS6502AddressingMode]::Implied $this.AddToken(" -AddressingMode $($addressingMode)") } else { $addressingMode = [MOS6502AddressingMode]$state.ToString() $this.AddToken(" -AddressingMode $($addressingMode) -Operand (") $startIndex = $this.outTokens.Count # Go through operand token indices. for($i=0;$i -lt $operandTokensIndex.Count;$i++) { $ti = $this.ParseToken($operandTokensIndex[$i]) - 1 # Each time I parse one, skip over any indices that fall inside that parsed range so I don’t process them twice. while($i -lt $operandTokensIndex.Count -and $ti -ge $operandTokensIndex[$i+1]) {$i++} } $endIndex = $this.outTokens.Count if ($startIndex -eq $endIndex -or (($this.outTokens[$startIndex..($endIndex-1)].Value -join '') -match '^(?s:\s|<#.*?#>|#.*$|//.*$)*$')) { # If no operand was found or if it only contains whitespace/comments, throw an error throw "Parser error: No operand found for instruction '$mne' in addressing mode '$($addressingMode)' at line $($token.Line), column $($token.Column)" } $this.AddToken(")") } $this.AddToken(" -InvocationFile '$($token.Filename)' -InvocationLine $($token.Line)") $nextTokenIndex = $instEndIndex } ([TokenType]::Newline) { $this.LineMap.Add(++$this.LineCounter, @{File = $token.Filename; Line = ($token.Line+1)}) $this.AddToken($token.Value) } ([TokenType]::EOF) { # $this.AddToken($token.Value) } default { $this.AddToken($token.Value) } } return $nextTokenIndex } [int] ParseTokens([int]$tokenIndex, [int]$count) { for($i=0;$i -lt $count;$i++) { $i = $this.ParseToken($tokenIndex+$i) - $tokenIndex - 1 } return $tokenIndex+$i } # Public entry point — call this to parse everything and populate outTokens [void] ParseAllTokens() { $null = $this.ParseTokens(0, $this.inTokens.Count) } } |