# Host reference lexer for Nova128 (E02-S02). Dot-source this file. # # An independent implementation of docs/language-spec.md 2.2 plus the token # rules documented at the top of src/compiler/lexer.s. Golden files must be # reproduced by BOTH this lexer and the C128 lexer. # # Token text format, one per line: # L:C KIND [raw-length] [detail] # IDENT detail = normalized name (PETSCII $41-$5A shown as A-Z) # NUMBER detail = decimal value # STRING detail = "text" (see Format-PetsciiText) # CHAR detail = $hh # ERROR detail = LEXERR name (no raw length) # EOL/EOF no length $script:Keywords = @{ "AND" = "KW_AND"; "AS" = "KW_AS"; "BOOLEAN" = "KW_BOOLEAN"; "BYTE" = "KW_BYTE" "CHAR" = "KW_CHAR"; "CLASS" = "KW_CLASS"; "DELETE" = "KW_DELETE"; "ELSE" = "KW_ELSE" "END" = "KW_END"; "FALSE" = "KW_FALSE"; "FOR" = "KW_FOR"; "IF" = "KW_IF" "IMPLEMENTS" = "KW_IMPLEMENTS"; "INTEGER" = "KW_INTEGER"; "INTERFACE" = "KW_INTERFACE" "METHOD" = "KW_METHOD"; "NEW" = "KW_NEW"; "NOT" = "KW_NOT"; "NULL" = "KW_NULL" "OR" = "KW_OR"; "PRINT" = "KW_PRINT"; "PRIVATE" = "KW_PRIVATE"; "PROGRAM" = "KW_PROGRAM" "PUBLIC" = "KW_PUBLIC"; "RETURN" = "KW_RETURN"; "SELF" = "KW_SELF"; "STEP" = "KW_STEP" "TO" = "KW_TO"; "TRUE" = "KW_TRUE"; "VAR" = "KW_VAR"; "WHILE" = "KW_WHILE"; "WORD" = "KW_WORD" } # Token kind numbers from src/common/tokens.inc (used to decode C128 dumps). $script:KindNames = @{ 0x00 = "EOF"; 0x01 = "EOL"; 0x02 = "IDENT"; 0x03 = "NUMBER"; 0x04 = "STRING" 0x05 = "CHAR"; 0x06 = "ERROR" 0x10 = "LPAREN"; 0x11 = "RPAREN"; 0x12 = "LBRACKET"; 0x13 = "RBRACKET"; 0x14 = "COMMA" 0x15 = "DOT"; 0x16 = "COLON"; 0x17 = "EQ"; 0x18 = "PLUS"; 0x19 = "MINUS"; 0x1A = "STAR" 0x1B = "SLASH"; 0x1C = "LT"; 0x1D = "GT"; 0x1E = "NE"; 0x1F = "LE"; 0x20 = "GE" } $i = 0x40 foreach ($kw in ($script:Keywords.Keys | Sort-Object)) { $script:KindNames[$i] = $script:Keywords[$kw]; $i++ } $script:ErrorNames = @{ 1 = "INVALID_CHAR"; 2 = "IDENT_TOO_LONG"; 3 = "NUMBER_RANGE"; 4 = "BAD_NUMBER" 5 = "UNTERMINATED_STRING"; 6 = "BAD_ESCAPE"; 7 = "BAD_CHAR_LITERAL" } $script:Punct = @{ 0x28 = "LPAREN"; 0x29 = "RPAREN"; 0x5B = "LBRACKET"; 0x5D = "RBRACKET"; 0x2C = "COMMA" 0x2E = "DOT"; 0x3A = "COLON"; 0x3D = "EQ"; 0x2B = "PLUS"; 0x2D = "MINUS"; 0x2A = "STAR" 0x2F = "SLASH"; 0x3C = "LT"; 0x3E = "GT" } function Format-PetsciiText([byte[]]$bytes) { $sb = New-Object System.Text.StringBuilder foreach ($b in $bytes) { if ($b -ge 0x41 -and $b -le 0x5A) { [void]$sb.Append([char]($b + 0x20)) } # a-z elseif ($b -ge 0xC1 -and $b -le 0xDA) { [void]$sb.Append([char]($b - 0x80)) } # A-Z elseif ($b -ge 0x20 -and $b -le 0x3F -and $b -ne 0x22) { [void]$sb.Append([char]$b) } else { [void]$sb.Append(('{' + $b.ToString("X2") + '}')) } } return '"' + $sb.ToString() + '"' } function Format-Name([byte[]]$bytes) { return -join ($bytes | ForEach-Object { [char]$_ }) # $41-$5A, digits, $5F } function Test-Letter([int]$b) { return ($b -ge 0x41 -and $b -le 0x5A) -or ($b -ge 0xC1 -and $b -le 0xDA) } function Test-Digit([int]$b) { return $b -ge 0x30 -and $b -le 0x39 } function Test-IdentPart([int]$b) { return (Test-Letter $b) -or (Test-Digit $b) -or $b -eq 0x5F } # Returns the token lines for one PETSCII source. function Get-ReferenceTokens([byte[]]$Source) { $out = New-Object System.Collections.Generic.List[string] $lines = New-Object System.Collections.Generic.List[byte[]] $start = 0 for ($k = 0; $k -lt $Source.Length; $k++) { if ($Source[$k] -eq 13) { $lines.Add([byte[]]$(if ($k -gt $start) { $Source[$start..($k - 1)] } else { @() })) $start = $k + 1 } } if ($start -lt $Source.Length) { $lines.Add([byte[]]$Source[$start..($Source.Length - 1)]) } for ($ln = 0; $ln -lt $lines.Count; $ln++) { $line = $lines[$ln]; $n = $line.Length; $lineNo = $ln + 1 $at = { param($p) if ($p -lt $n) { [int]$line[$p] } else { 13 } } $p = 0 while ($true) { while ((& $at $p) -eq 0x20 -or (& $at $p) -eq 0x09) { $p++ } $c = & $at $p if ($c -eq 13) { break } $col = $p + 1; $s = $p $emit = { param($kind, $detail) $out.Add(("{0}:{1} {2} {3}{4}" -f $lineNo, $col, $kind, ($p - $s), $detail)) } $err = $null; $errCol = $col if (Test-Letter $c) { $name = New-Object System.Collections.Generic.List[byte] while (Test-IdentPart (& $at $p)) { $b = & $at $p if (Test-Letter $b) { $b = $b -band 0x7F } $name.Add([byte]$b); $p++ } if ($name.Count -gt 16) { $err = "IDENT_TOO_LONG" } else { $text = Format-Name $name.ToArray() if ($script:Keywords.ContainsKey($text)) { & $emit $script:Keywords[$text] "" } else { & $emit "IDENT" (" " + $text) } } } elseif ((Test-Digit $c) -or $c -eq 0x24 -or $c -eq 0x25) { $value = [long]0; $count = 0 if ($c -eq 0x24) { $p++ while ($true) { $b = & $at $p; $d = -1 if (Test-Digit $b) { $d = $b - 0x30 } elseif ((Test-Letter $b) -and ($b -band 0x7F) -ge 0x41 -and ($b -band 0x7F) -le 0x46) { $d = ($b -band 0x7F) - 0x41 + 10 } if ($d -lt 0) { break } $value = $value * 16 + $d; $count++; $p++ } } elseif ($c -eq 0x25) { $p++ while ((& $at $p) -eq 0x30 -or (& $at $p) -eq 0x31) { $value = $value * 2 + ((& $at $p) - 0x30); $count++; $p++ } } else { while (Test-Digit (& $at $p)) { $value = $value * 10 + ((& $at $p) - 0x30); $count++; $p++ } } if ($count -eq 0 -or (Test-IdentPart (& $at $p))) { $err = "BAD_NUMBER" } elseif ($value -gt 65535) { $err = "NUMBER_RANGE" } else { & $emit "NUMBER" (" " + $value) } } elseif ($c -eq 0x22) { $p++; $text = New-Object System.Collections.Generic.List[byte] while ($null -eq $err) { $b = & $at $p if ($b -eq 13) { $err = "UNTERMINATED_STRING"; break } if ($b -eq 0x22) { $p++; break } if ($b -eq 0x5C) { $e = & $at ($p + 1) if ($e -eq 0x5C -or $e -eq 0x22) { $b = $e } elseif (($e -band 0x7F) -eq 0x4E -or ($e -band 0x7F) -eq 0x52) { $b = 0x0D } else { $err = "BAD_ESCAPE"; $errCol = $p + 1; break } $p++ } $text.Add([byte]$b); $p++ } if ($null -eq $err) { & $emit "STRING" (" " + (Format-PetsciiText $text.ToArray())) } } elseif ($c -eq 0x27) { $p++; $b = & $at $p if ($b -eq 13 -or $b -eq 0x27) { $err = "BAD_CHAR_LITERAL" } else { if ($b -eq 0x5C) { $e = & $at ($p + 1) if ($e -eq 0x5C -or $e -eq 0x27) { $b = $e } elseif (($e -band 0x7F) -eq 0x4E -or ($e -band 0x7F) -eq 0x52) { $b = 0x0D } else { $err = "BAD_ESCAPE"; $errCol = $p + 1 } $p++ } if ($null -eq $err) { $p++ if ((& $at $p) -ne 0x27) { $err = "BAD_CHAR_LITERAL" } else { $p++; & $emit "CHAR" (" `${0:X2}" -f $b) } } } } elseif ($script:Punct.ContainsKey($c)) { $next = & $at ($p + 1) if ($c -eq 0x2F -and $next -eq 0x2F) { break } # comment $p++ if ($c -eq 0x3C -and $next -eq 0x3E) { $p++; & $emit "NE" "" } elseif ($c -eq 0x3C -and $next -eq 0x3D) { $p++; & $emit "LE" "" } elseif ($c -eq 0x3E -and $next -eq 0x3D) { $p++; & $emit "GE" "" } else { & $emit $script:Punct[$c] "" } } else { $err = "INVALID_CHAR" } if ($null -ne $err) { $out.Add(("{0}:{1} ERROR {2}" -f $lineNo, $errCol, $err)) break # skip the line } } $out.Add(("{0}:{1} EOL" -f $lineNo, ($n + 1))) } $out.Add(("{0}:0 EOF" -f $lines.Count)) return ,$out } # Decode the C128 dump (tests/lexer/lexer-test.s) into per-fixture token lines. # Returns a list (in dump order) of @{ Index; Load; Tokens }. function ConvertFrom-TokenDump([byte[]]$Dump) { $result = New-Object System.Collections.Generic.List[object] $p = 0; $current = $null while ($p -lt $Dump.Length) { $kind = $Dump[$p] if ($kind -eq 0xFE) { break } if ($kind -eq 0xFF) { $current = [pscustomobject]@{ Index = [int]$Dump[$p + 1]; Load = $Dump[$p + 2] Tokens = New-Object System.Collections.Generic.List[string] } $result.Add($current) $p += 3; continue } $line = $Dump[$p + 1] + 256 * $Dump[$p + 2]; $col = $Dump[$p + 3]; $p += 4 $name = $script:KindNames[[int]$kind] if ($null -eq $name) { throw ("unknown token kind `${0:X2} in dump" -f $kind) } switch ($name) { "EOF" { $current.Tokens.Add("${line}:$col EOF") } "EOL" { $current.Tokens.Add("${line}:$col EOL") } "ERROR" { $current.Tokens.Add("${line}:$col ERROR " + $script:ErrorNames[[int]$Dump[$p]]); $p++ } { $_ -in "IDENT", "STRING" } { $len = $Dump[$p]; $tl = $Dump[$p + 1] $text = if ($tl -gt 0) { [byte[]]$Dump[($p + 2)..($p + 1 + $tl)] } else { [byte[]]@() } $detail = if ($name -eq "IDENT") { Format-Name $text } else { Format-PetsciiText $text } $current.Tokens.Add("${line}:$col $name $len $detail"); $p += 2 + $tl } "NUMBER" { $current.Tokens.Add("${line}:$col NUMBER $($Dump[$p]) $($Dump[$p + 1] + 256 * $Dump[$p + 2])"); $p += 3 } "CHAR" { $current.Tokens.Add(("{0}:{1} CHAR {2} `${3:X2}" -f $line, $col, $Dump[$p], $Dump[$p + 1])); $p += 2 } default { $current.Tokens.Add("${line}:$col $name $($Dump[$p])"); $p++ } } } return ,$result }