You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
11KB

  1. # Host reference lexer for Nova128 (E02-S02). Dot-source this file.
  2. #
  3. # An independent implementation of docs/language-spec.md 2.2 plus the token
  4. # rules documented at the top of src/compiler/lexer.s. Golden files must be
  5. # reproduced by BOTH this lexer and the C128 lexer.
  6. #
  7. # Token text format, one per line:
  8. # L:C KIND [raw-length] [detail]
  9. # IDENT detail = normalized name (PETSCII $41-$5A shown as A-Z)
  10. # NUMBER detail = decimal value
  11. # STRING detail = "text" (see Format-PetsciiText)
  12. # CHAR detail = $hh
  13. # ERROR detail = LEXERR name (no raw length)
  14. # EOL/EOF no length
  15. $script:Keywords = @{
  16. "AND" = "KW_AND"; "AS" = "KW_AS"; "BOOLEAN" = "KW_BOOLEAN"; "BYTE" = "KW_BYTE"
  17. "CHAR" = "KW_CHAR"; "CLASS" = "KW_CLASS"; "DELETE" = "KW_DELETE"; "ELSE" = "KW_ELSE"
  18. "END" = "KW_END"; "FALSE" = "KW_FALSE"; "FOR" = "KW_FOR"; "IF" = "KW_IF"
  19. "IMPLEMENTS" = "KW_IMPLEMENTS"; "INTEGER" = "KW_INTEGER"; "INTERFACE" = "KW_INTERFACE"
  20. "METHOD" = "KW_METHOD"; "NEW" = "KW_NEW"; "NOT" = "KW_NOT"; "NULL" = "KW_NULL"
  21. "OR" = "KW_OR"; "PRINT" = "KW_PRINT"; "PRIVATE" = "KW_PRIVATE"; "PROGRAM" = "KW_PROGRAM"
  22. "PUBLIC" = "KW_PUBLIC"; "RETURN" = "KW_RETURN"; "SELF" = "KW_SELF"; "STEP" = "KW_STEP"
  23. "TO" = "KW_TO"; "TRUE" = "KW_TRUE"; "VAR" = "KW_VAR"; "WHILE" = "KW_WHILE"; "WORD" = "KW_WORD"
  24. }
  25. # Token kind numbers from src/common/tokens.inc (used to decode C128 dumps).
  26. $script:KindNames = @{
  27. 0x00 = "EOF"; 0x01 = "EOL"; 0x02 = "IDENT"; 0x03 = "NUMBER"; 0x04 = "STRING"
  28. 0x05 = "CHAR"; 0x06 = "ERROR"
  29. 0x10 = "LPAREN"; 0x11 = "RPAREN"; 0x12 = "LBRACKET"; 0x13 = "RBRACKET"; 0x14 = "COMMA"
  30. 0x15 = "DOT"; 0x16 = "COLON"; 0x17 = "EQ"; 0x18 = "PLUS"; 0x19 = "MINUS"; 0x1A = "STAR"
  31. 0x1B = "SLASH"; 0x1C = "LT"; 0x1D = "GT"; 0x1E = "NE"; 0x1F = "LE"; 0x20 = "GE"
  32. }
  33. $i = 0x40
  34. foreach ($kw in ($script:Keywords.Keys | Sort-Object)) {
  35. $script:KindNames[$i] = $script:Keywords[$kw]; $i++
  36. }
  37. $script:ErrorNames = @{
  38. 1 = "INVALID_CHAR"; 2 = "IDENT_TOO_LONG"; 3 = "NUMBER_RANGE"; 4 = "BAD_NUMBER"
  39. 5 = "UNTERMINATED_STRING"; 6 = "BAD_ESCAPE"; 7 = "BAD_CHAR_LITERAL"
  40. }
  41. $script:Punct = @{
  42. 0x28 = "LPAREN"; 0x29 = "RPAREN"; 0x5B = "LBRACKET"; 0x5D = "RBRACKET"; 0x2C = "COMMA"
  43. 0x2E = "DOT"; 0x3A = "COLON"; 0x3D = "EQ"; 0x2B = "PLUS"; 0x2D = "MINUS"; 0x2A = "STAR"
  44. 0x2F = "SLASH"; 0x3C = "LT"; 0x3E = "GT"
  45. }
  46. function Format-PetsciiText([byte[]]$bytes) {
  47. $sb = New-Object System.Text.StringBuilder
  48. foreach ($b in $bytes) {
  49. if ($b -ge 0x41 -and $b -le 0x5A) { [void]$sb.Append([char]($b + 0x20)) } # a-z
  50. elseif ($b -ge 0xC1 -and $b -le 0xDA) { [void]$sb.Append([char]($b - 0x80)) } # A-Z
  51. elseif ($b -ge 0x20 -and $b -le 0x3F -and $b -ne 0x22) { [void]$sb.Append([char]$b) }
  52. else { [void]$sb.Append(("{{{0:X2}}}" -f $b)) }
  53. }
  54. return '"' + $sb.ToString() + '"'
  55. }
  56. function Format-Name([byte[]]$bytes) {
  57. return -join ($bytes | ForEach-Object { [char]$_ }) # $41-$5A, digits, $5F
  58. }
  59. function Test-Letter([int]$b) { return ($b -ge 0x41 -and $b -le 0x5A) -or ($b -ge 0xC1 -and $b -le 0xDA) }
  60. function Test-Digit([int]$b) { return $b -ge 0x30 -and $b -le 0x39 }
  61. function Test-IdentPart([int]$b) { return (Test-Letter $b) -or (Test-Digit $b) -or $b -eq 0x5F }
  62. # Returns the token lines for one PETSCII source.
  63. function Get-ReferenceTokens([byte[]]$Source) {
  64. $out = New-Object System.Collections.Generic.List[string]
  65. $lines = New-Object System.Collections.Generic.List[byte[]]
  66. $start = 0
  67. for ($k = 0; $k -lt $Source.Length; $k++) {
  68. if ($Source[$k] -eq 13) {
  69. $lines.Add([byte[]]$(if ($k -gt $start) { $Source[$start..($k - 1)] } else { @() }))
  70. $start = $k + 1
  71. }
  72. }
  73. if ($start -lt $Source.Length) { $lines.Add([byte[]]$Source[$start..($Source.Length - 1)]) }
  74. for ($ln = 0; $ln -lt $lines.Count; $ln++) {
  75. $line = $lines[$ln]; $n = $line.Length; $lineNo = $ln + 1
  76. $at = { param($p) if ($p -lt $n) { [int]$line[$p] } else { 13 } }
  77. $p = 0
  78. while ($true) {
  79. while ((& $at $p) -eq 0x20 -or (& $at $p) -eq 0x09) { $p++ }
  80. $c = & $at $p
  81. if ($c -eq 13) { break }
  82. $col = $p + 1; $s = $p
  83. $emit = { param($kind, $detail) $out.Add(("{0}:{1} {2} {3}{4}" -f $lineNo, $col, $kind, ($p - $s), $detail)) }
  84. $err = $null; $errCol = $col
  85. if (Test-Letter $c) {
  86. $name = New-Object System.Collections.Generic.List[byte]
  87. while (Test-IdentPart (& $at $p)) {
  88. $b = & $at $p
  89. if (Test-Letter $b) { $b = $b -band 0x7F }
  90. $name.Add([byte]$b); $p++
  91. }
  92. if ($name.Count -gt 16) { $err = "IDENT_TOO_LONG" }
  93. else {
  94. $text = Format-Name $name.ToArray()
  95. if ($script:Keywords.ContainsKey($text)) { & $emit $script:Keywords[$text] "" }
  96. else { & $emit "IDENT" (" " + $text) }
  97. }
  98. } elseif ((Test-Digit $c) -or $c -eq 0x24 -or $c -eq 0x25) {
  99. $value = [long]0; $count = 0
  100. if ($c -eq 0x24) {
  101. $p++
  102. while ($true) {
  103. $b = & $at $p; $d = -1
  104. if (Test-Digit $b) { $d = $b - 0x30 }
  105. elseif ((Test-Letter $b) -and ($b -band 0x7F) -ge 0x41 -and ($b -band 0x7F) -le 0x46) {
  106. $d = ($b -band 0x7F) - 0x41 + 10
  107. }
  108. if ($d -lt 0) { break }
  109. $value = $value * 16 + $d; $count++; $p++
  110. }
  111. } elseif ($c -eq 0x25) {
  112. $p++
  113. while ((& $at $p) -eq 0x30 -or (& $at $p) -eq 0x31) {
  114. $value = $value * 2 + ((& $at $p) - 0x30); $count++; $p++
  115. }
  116. } else {
  117. while (Test-Digit (& $at $p)) { $value = $value * 10 + ((& $at $p) - 0x30); $count++; $p++ }
  118. }
  119. if ($count -eq 0 -or (Test-IdentPart (& $at $p))) { $err = "BAD_NUMBER" }
  120. elseif ($value -gt 65535) { $err = "NUMBER_RANGE" }
  121. else { & $emit "NUMBER" (" " + $value) }
  122. } elseif ($c -eq 0x22) {
  123. $p++; $text = New-Object System.Collections.Generic.List[byte]
  124. while ($null -eq $err) {
  125. $b = & $at $p
  126. if ($b -eq 13) { $err = "UNTERMINATED_STRING"; break }
  127. if ($b -eq 0x22) { $p++; break }
  128. if ($b -eq 0x5C) {
  129. $e = & $at ($p + 1)
  130. if ($e -eq 0x5C -or $e -eq 0x22) { $b = $e }
  131. elseif (($e -band 0x7F) -eq 0x4E -or ($e -band 0x7F) -eq 0x52) { $b = 0x0D }
  132. else { $err = "BAD_ESCAPE"; $errCol = $p + 1; break }
  133. $p++
  134. }
  135. $text.Add([byte]$b); $p++
  136. }
  137. if ($null -eq $err) { & $emit "STRING" (" " + (Format-PetsciiText $text.ToArray())) }
  138. } elseif ($c -eq 0x27) {
  139. $p++; $b = & $at $p
  140. if ($b -eq 13 -or $b -eq 0x27) { $err = "BAD_CHAR_LITERAL" }
  141. else {
  142. if ($b -eq 0x5C) {
  143. $e = & $at ($p + 1)
  144. if ($e -eq 0x5C -or $e -eq 0x27) { $b = $e }
  145. elseif (($e -band 0x7F) -eq 0x4E -or ($e -band 0x7F) -eq 0x52) { $b = 0x0D }
  146. else { $err = "BAD_ESCAPE"; $errCol = $p + 1 }
  147. $p++
  148. }
  149. if ($null -eq $err) {
  150. $p++
  151. if ((& $at $p) -ne 0x27) { $err = "BAD_CHAR_LITERAL" }
  152. else { $p++; & $emit "CHAR" (" `${0:X2}" -f $b) }
  153. }
  154. }
  155. } elseif ($script:Punct.ContainsKey($c)) {
  156. $next = & $at ($p + 1)
  157. if ($c -eq 0x2F -and $next -eq 0x2F) { break } # comment
  158. $p++
  159. if ($c -eq 0x3C -and $next -eq 0x3E) { $p++; & $emit "NE" "" }
  160. elseif ($c -eq 0x3C -and $next -eq 0x3D) { $p++; & $emit "LE" "" }
  161. elseif ($c -eq 0x3E -and $next -eq 0x3D) { $p++; & $emit "GE" "" }
  162. else { & $emit $script:Punct[$c] "" }
  163. } else {
  164. $err = "INVALID_CHAR"
  165. }
  166. if ($null -ne $err) {
  167. $out.Add(("{0}:{1} ERROR {2}" -f $lineNo, $errCol, $err))
  168. break # skip the line
  169. }
  170. }
  171. $out.Add(("{0}:{1} EOL" -f $lineNo, ($n + 1)))
  172. }
  173. $out.Add(("{0}:0 EOF" -f $lines.Count))
  174. return ,$out
  175. }
  176. # Decode the C128 dump (tests/lexer/lexer-test.s) into per-fixture token lines.
  177. # Returns a list (in dump order) of @{ Index; Load; Tokens }.
  178. function ConvertFrom-TokenDump([byte[]]$Dump) {
  179. $result = New-Object System.Collections.Generic.List[object]
  180. $p = 0; $current = $null
  181. while ($p -lt $Dump.Length) {
  182. $kind = $Dump[$p]
  183. if ($kind -eq 0xFE) { break }
  184. if ($kind -eq 0xFF) {
  185. $current = [pscustomobject]@{ Index = [int]$Dump[$p + 1]; Load = $Dump[$p + 2]
  186. Tokens = New-Object System.Collections.Generic.List[string] }
  187. $result.Add($current)
  188. $p += 3; continue
  189. }
  190. $line = $Dump[$p + 1] + 256 * $Dump[$p + 2]; $col = $Dump[$p + 3]; $p += 4
  191. $name = $script:KindNames[[int]$kind]
  192. if ($null -eq $name) { throw ("unknown token kind `${0:X2} in dump" -f $kind) }
  193. switch ($name) {
  194. "EOF" { $current.Tokens.Add("${line}:$col EOF") }
  195. "EOL" { $current.Tokens.Add("${line}:$col EOL") }
  196. "ERROR" { $current.Tokens.Add("${line}:$col ERROR " + $script:ErrorNames[[int]$Dump[$p]]); $p++ }
  197. { $_ -in "IDENT", "STRING" } {
  198. $len = $Dump[$p]; $tl = $Dump[$p + 1]
  199. $text = if ($tl -gt 0) { [byte[]]$Dump[($p + 2)..($p + 1 + $tl)] } else { [byte[]]@() }
  200. $detail = if ($name -eq "IDENT") { Format-Name $text } else { Format-PetsciiText $text }
  201. $current.Tokens.Add("${line}:$col $name $len $detail"); $p += 2 + $tl
  202. }
  203. "NUMBER" { $current.Tokens.Add("${line}:$col NUMBER $($Dump[$p]) $($Dump[$p + 1] + 256 * $Dump[$p + 2])"); $p += 3 }
  204. "CHAR" { $current.Tokens.Add(("{0}:{1} CHAR {2} `${3:X2}" -f $line, $col, $Dump[$p], $Dump[$p + 1])); $p += 2 }
  205. default { $current.Tokens.Add("${line}:$col $name $($Dump[$p])"); $p++ }
  206. }
  207. }
  208. return ,$result
  209. }

Powered by TurnKey Linux.