Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

283 linhas
14KB

  1. # E03-S01: run the parser over every fixture in x128 and check the ERR files
  2. # and the recorded top-level types against the fixtures' expectations.
  3. [CmdletBinding()]
  4. param([int]$Port = 6535, [int]$TimeoutSeconds = 300)
  5. $ErrorActionPreference = "Stop"
  6. $project = Split-Path $PSScriptRoot -Parent
  7. Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
  8. . (Join-Path $PSScriptRoot "build-parser-test.ps1") # defines $ParserFixtures
  9. $out = Join-Path $project "build\parser-test"
  10. $disk = Join-Path $out "parser-test.d81"
  11. # Catalog: symbol -> number.
  12. $codes = @{}
  13. foreach ($line in Get-Content (Join-Path $project "src\common\diagcat.inc")) {
  14. $m = [regex]::Match($line, '^\s*DIAGNOSTIC\s+(\w+),\s*(\d+),')
  15. if ($m.Success) { $codes[$m.Groups[1].Value] = [int]$m.Groups[2].Value }
  16. }
  17. $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
  18. -Screenshot (Join-Path $out "parser-test-vicii.png")
  19. try {
  20. $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
  21. $dump = Read-Nova128Memory $session ($mb[8] + 256 * $mb[9]) ($mb[10] + 256 * $mb[11])
  22. } finally {
  23. Stop-Nova128X128 $session
  24. }
  25. if ($mb[3] -ne 0xA5) { throw ("C128 reported FAIL (code `${0:X2})" -f $mb[4]) }
  26. # ------------------------------------------------------------ decode dump
  27. function ConvertFrom-Spelling([byte[]]$bytes) {
  28. return -join ($bytes | ForEach-Object {
  29. if ($_ -ge 0x41 -and $_ -le 0x5A) { [char]($_ + 0x20) } elseif ($_ -ge 0xC1 -and $_ -le 0xDA) { [char]($_ - 0x80) } else { [char]$_ } })
  30. }
  31. # A name is a length and its spelling; a type is base, name, length lo/hi.
  32. # Both are rendered lowercase, as the oracle below writes them.
  33. function Read-DumpName {
  34. $len = $dump[$script:p]; $script:p++
  35. if ($len -eq 0) { return "" }
  36. $s = ConvertFrom-Spelling $dump[$script:p..($script:p + $len - 1)]
  37. $script:p += $len
  38. return $s.ToLower()
  39. }
  40. function Read-DumpType {
  41. $base = $dump[$script:p]; $script:p++
  42. $name = Read-DumpName
  43. $n = $dump[$script:p] + 256 * $dump[$script:p + 1]; $script:p += 2
  44. $t = @("none", "boolean", "byte", "char", "integer", "word", $name)[$base]
  45. if ($n) { $t += "[$n]" }
  46. return $t
  47. }
  48. $records = @(); $script:p = 0
  49. while ($dump[$p] -ne 0xFE) {
  50. if ($dump[$p] -ne 0xFF) { throw "dump out of sync at $p" }
  51. $r = [pscustomobject]@{ Index = $dump[$p + 1]; Load = $dump[$p + 2]; Count = $dump[$p + 3]
  52. MinSp = $dump[$p + 4]; Types = @(); Members = @(); Layout = @(); Trace = $null }
  53. $p += 5
  54. while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFC -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFA) {
  55. $len = $dump[$p + 3]
  56. $spell = ConvertFrom-Spelling $dump[($p + 4)..($p + 3 + $len)]
  57. $r.Types += ("{0} {1} {2}" -f @("", "interface", "class", "program")[$dump[$p]], $spell, ($dump[$p + 1] + 256 * $dump[$p + 2]))
  58. $p += 4 + $len
  59. }
  60. if ($dump[$p] -eq 0xFC) {
  61. $p++
  62. while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB -and $dump[$p] -ne 0xFA) {
  63. $owner = Read-DumpName
  64. $name = Read-DumpName
  65. $flags = $dump[$p]; $p++
  66. $type = Read-DumpType
  67. $count = $dump[$p]; $p++
  68. $params = @(for ($k = 0; $k -lt $count; $k++) { Read-DumpType })
  69. $r.Members += ("{0}.{1} {2} {3} {4} ({5})" -f $owner, $name,
  70. $(if ($flags -band 1) { "method" } else { "field" }),
  71. $(if ($flags -band 2) { "private" } else { "public" }), $type, ($params -join ","))
  72. }
  73. if ($dump[$p] -eq 0xFA) {
  74. $p++
  75. while ($dump[$p] -ne 0xFD -and $dump[$p] -ne 0xFB) {
  76. $owner = Read-DumpName
  77. $size = $dump[$p] + 256 * $dump[$p + 1]; $p += 2
  78. $count = $dump[$p]; $p++
  79. $r.Layout += ("{0} size {1}" -f $owner, $size)
  80. for ($k = 0; $k -lt $count; $k++) {
  81. $fname = Read-DumpName
  82. $off = $dump[$p] + 256 * $dump[$p + 1]; $p += 2
  83. $r.Layout += ("{0}.{1} offset {2}" -f $owner, $fname, $off)
  84. }
  85. }
  86. }
  87. if ($dump[$p] -eq 0xFB) {
  88. $n = $dump[$p + 1] + 256 * $dump[$p + 2]
  89. $r.Trace = if ($n) { [byte[]]$dump[($p + 3)..($p + 2 + $n)] } else { [byte[]]@() }
  90. $p += 3 + $n
  91. }
  92. }
  93. $p++
  94. $records += $r
  95. }
  96. # Expression trace -> "line: postfix" strings. Operands are named by the
  97. # source text at their column; operators by their symbol (parser.inc TR_*).
  98. $traceOps = @{ 0x17 = "="; 0x1E = "<>"; 0x1C = "<"; 0x1D = ">"; 0x1F = "<="; 0x20 = ">="
  99. 0x18 = "+"; 0x19 = "-"; 0x1A = "*"; 0x1B = "/"; 0x40 = "and"; 0x53 = "or" }
  100. function ConvertFrom-Trace([byte[]]$trace, [string[]]$lines) {
  101. $out = @(); $k = 0; $cur = $null
  102. while ($k -lt $trace.Length) {
  103. $b = $trace[$k]
  104. if ($b -eq 0xFF) {
  105. if ($cur) { $out += $cur }
  106. $line = $trace[$k + 1] + 256 * $trace[$k + 2]; $k += 3
  107. $cur = "${line}:"; $src = $lines[$line - 1]; continue
  108. }
  109. if ($b -eq 0xF0) { $cur += " neg" }
  110. elseif ($b -eq 0xF1) { $cur += " not" }
  111. elseif ($b -ge 0x80) { $cur += " " + $traceOps[$b - 0x80] }
  112. else {
  113. $m = [regex]::Match($src.Substring($b - 1), '^(\w+|"[^"]*"|''.'')')
  114. $cur += " " + $m.Value.ToLower()
  115. }
  116. $k++
  117. }
  118. if ($cur) { $out += $cur }
  119. return ,$out
  120. }
  121. # Members the source declares, derived independently by regex: per class or
  122. # interface in order; access defaults (fields private, methods public);
  123. # a second declaration of a name in the same namespace is not recorded.
  124. $typeRx = '(boolean|byte|char|integer|word|\w+)(\[(\d+)\])?'
  125. function Format-SourceType([string]$base, [string]$n) {
  126. $t = $base.ToLower(); if ($n) { $t += "[$n]" }; return $t
  127. }
  128. function Get-SourceMembers([string]$path) {
  129. $members = @(); $seen = @{}; $owner = $null; $inBody = $false
  130. foreach ($line in Get-Content -LiteralPath $path) {
  131. if ($line -match '^\s*(interface|class)\s+(\w+)') { $ownerKind = $Matches[1]; $owner = $Matches[2].ToLower(); continue }
  132. if ($line -match '^\s*end\s+(interface|class)\b') { $owner = $null; continue }
  133. if ($line -match '^\s*end\s+method\b') { $inBody = $false; continue }
  134. if (-not $owner -or $inBody) { continue }
  135. $m = [regex]::Match($line, "^\s*(public|private)?\s*method\s+(\w+)\s*\((.*)\)\s*(as\s+$typeRx)?\s*$")
  136. if ($m.Success) {
  137. $kind = "method"; $name = $m.Groups[2].Value.ToLower()
  138. $access = if ($m.Groups[1].Value -eq "private") { "private" } else { "public" }
  139. $type = if ($m.Groups[4].Success) { Format-SourceType $m.Groups[5].Value $m.Groups[7].Value } else { "none" }
  140. $params = @(foreach ($part in ($m.Groups[3].Value -split ',' | Where-Object { $_.Trim() })) {
  141. $pm = [regex]::Match($part.Trim(), "^\w+\s+as\s+$typeRx$")
  142. Format-SourceType $pm.Groups[1].Value $pm.Groups[3].Value })
  143. $inBody = ($ownerKind -eq "class") # interface methods have no body
  144. } else {
  145. $m = [regex]::Match($line, "^\s*(public|private)?\s*$typeRx\s+(\w+)\s*$")
  146. if (-not $m.Success) { continue }
  147. $kind = "field"; $name = $m.Groups[5].Value.ToLower()
  148. $access = if ($m.Groups[1].Value -eq "public") { "public" } else { "private" }
  149. $type = Format-SourceType $m.Groups[2].Value $m.Groups[4].Value
  150. $params = @()
  151. }
  152. $key = "$owner.$kind.$name"
  153. if ($seen[$key]) { continue }
  154. $seen[$key] = $true
  155. $members += ("{0}.{1} {2} {3} {4} ({5})" -f $owner, $name, $kind, $access, $type, ($params -join ","))
  156. }
  157. return ,$members
  158. }
  159. # Fixtures whose member tables are compared (headers all well formed).
  160. $memberChecked = @("members.n8", "lifecycle.n8", "scopes.n8", "private.n8")
  161. $c1541 = Get-ViceTool "c1541"
  162. $listing = (& $c1541 -attach $disk -list 2>&1) | Out-String
  163. function Get-ErrLines([string]$base) {
  164. $dest = Join-Path $out "$base.err.bin"
  165. if (Test-Path $dest) { Remove-Item $dest -Force }
  166. if ($listing -notmatch ('"' + [regex]::Escape("$base.err") + '"')) { return $null }
  167. & $c1541 -attach $disk -read "$base.err,s" $dest 2>&1 | Out-Null
  168. # The leading comma keeps a one-line file an array (PowerShell unrolls it).
  169. # Names keep their source spelling: shifted PETSCII letters ($C1-$DA) are
  170. # capitals; messages are compared case-insensitively.
  171. $text = -join ([IO.File]::ReadAllBytes($dest) | ForEach-Object {
  172. if ($_ -ge 0xC1 -and $_ -le 0xDA) { [char]($_ - 0x80) } else { [char]$_ } })
  173. return ,@(($text.TrimEnd("`r")) -split "`r")
  174. }
  175. function Parse-Err([string]$line) {
  176. $m = [regex]::Match($line, '^E(\d{3}) \S+ (\d+) (\d+) (.*)$')
  177. return [pscustomobject]@{ Code = [int]$m.Groups[1].Value; Line = [int]$m.Groups[2].Value
  178. Col = [int]$m.Groups[3].Value; Message = $m.Groups[4].Value }
  179. }
  180. # ---------------------------------------------------------------- check
  181. $failures = @(); $i = 0; $minSp = 0xFF
  182. foreach ($name in $ParserFixtures.Keys) {
  183. $src, $group, $expect = $ParserFixtures[$name]
  184. $base = $name -replace '\.n8$', ''
  185. $r = $records[$i]; $i++
  186. $errLines = Get-ErrLines $base
  187. $problems = @()
  188. if ($r.Load -ne 0) { $problems += "src_load returned $($r.Load)" }
  189. switch ($group) {
  190. { $_ -eq "accepted" -or $_ -eq "layout" } {
  191. if ($r.Count -ne 0) { $problems += "$($r.Count) diagnostic(s): $($errLines -join ' | ')" }
  192. if ($null -ne $errLines) { $problems += "an ERR file exists" }
  193. $want = @()
  194. $n = 0
  195. foreach ($line in Get-Content -LiteralPath $src) {
  196. $n++
  197. $m = [regex]::Match($line, '^\s*(interface|class|program)\s+(\w+)')
  198. if ($m.Success) { $want += ("{0} {1} {2}" -f $m.Groups[1].Value, $m.Groups[2].Value, $n) }
  199. }
  200. if ((($r.Types | Sort-Object) -join ';') -ne (($want | Sort-Object) -join ';')) {
  201. $problems += "types [$($r.Types -join '; ')] expected [$($want -join '; ')]"
  202. }
  203. $detail = "{0} types" -f $r.Types.Count
  204. }
  205. "rejected" {
  206. $text = Get-Content -Raw -LiteralPath $expect
  207. $sym = [regex]::Match($text, '(?m)^diagnostic: (\w+)').Groups[1].Value
  208. $wantLine = [int][regex]::Match($text, '(?m)^line: (\d+)').Groups[1].Value
  209. $colMatch = [regex]::Match($text, '(?m)^column: (\d+)')
  210. if ($null -eq $errLines) { $problems += "no ERR file" }
  211. else {
  212. $first = Parse-Err $errLines[0]
  213. if ($first.Code -ne $codes[$sym] -or $first.Line -ne $wantLine -or
  214. ($colMatch.Success -and $first.Col -ne [int]$colMatch.Groups[1].Value)) {
  215. $problems += "first diagnostic '$($errLines[0])' expected $sym (E$($codes[$sym])) at line $wantLine"
  216. }
  217. }
  218. $detail = if ($errLines) { $errLines[0] } else { "" }
  219. }
  220. "exact" {
  221. # SYMBOL line column [message]; a message is compared when given.
  222. $rows = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") } | ForEach-Object {
  223. $f = $_ -split ' ', 4
  224. [pscustomobject]@{ Key = "{0} {1} {2}" -f $codes[$f[0]], $f[1], $f[2]; Message = $f[3] } })
  225. $want = @($rows | ForEach-Object { $_.Key })
  226. $errs = @($errLines | Where-Object { $_ } | ForEach-Object { Parse-Err $_ })
  227. $got = @($errs | ForEach-Object { "{0} {1} {2}" -f $_.Code, $_.Line, $_.Col })
  228. if (($got -join ';') -ne ($want -join ';')) {
  229. $problems += "diagnostics [$($got -join '; ')] expected [$($want -join '; ')]"
  230. } else {
  231. for ($k = 0; $k -lt $rows.Count; $k++) {
  232. if ($rows[$k].Message -and $rows[$k].Message.ToUpper() -ne $errs[$k].Message.ToUpper()) {
  233. $problems += "E$($errs[$k].Code) at $($errs[$k].Line):$($errs[$k].Col) says '$($errs[$k].Message)', expected '$($rows[$k].Message)'"
  234. }
  235. }
  236. }
  237. $detail = "{0} diagnostics" -f $got.Count
  238. }
  239. }
  240. if ($group -in @("accepted", "layout") -or $memberChecked -contains $name) {
  241. $wantMembers = Get-SourceMembers $src
  242. if (($r.Members -join ';') -ne ($wantMembers -join ';')) {
  243. $problems += "members [$($r.Members -join '; ')] expected [$($wantMembers -join '; ')]"
  244. }
  245. $detail += ", {0} members" -f $r.Members.Count
  246. }
  247. if ($group -eq "accepted" -and $expect) { # expression trace golden
  248. $want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") })
  249. if ($null -eq $r.Trace) { $problems += "no expression trace" }
  250. else {
  251. $got = ConvertFrom-Trace $r.Trace @(Get-Content -LiteralPath $src)
  252. for ($k = 0; $k -lt [Math]::Max($got.Count, $want.Count); $k++) {
  253. if ($got[$k] -ne $want[$k]) { $problems += "trace '$($got[$k])' expected '$($want[$k])'" }
  254. }
  255. $detail += ", {0} traced statements" -f $got.Count
  256. }
  257. }
  258. if ($group -eq "layout") {
  259. $want = @(Get-Content -LiteralPath $expect | Where-Object { $_ -and -not $_.StartsWith("//") })
  260. if (($r.Layout -join ';') -ne ($want -join ';')) {
  261. $problems += "layout [$($r.Layout -join '; ')] expected [$($want -join '; ')]"
  262. }
  263. $detail += ", {0} layout lines" -f $r.Layout.Count
  264. }
  265. if ($r.MinSp -ne 0xFF -and $r.MinSp -lt $minSp) { $minSp = $r.MinSp }
  266. $status = if ($problems) { "FAIL" } else { "ok" }
  267. Write-Host (" {0,-13} {1,-9} {2} {3}" -f $name, $group, $status, $detail)
  268. if ($problems) { $failures += "${name}: " + ($problems -join "; ") }
  269. }
  270. # The expression bound must leave hardware stack to spare (KERNAL calls and
  271. # interrupts run below the parser).
  272. if ($minSp -lt 0x60) { $failures += ("expression parsing took the stack down to `${0:X2}" -f $minSp) }
  273. if ($failures) { throw ("parser verification failed:`n " + ($failures -join "`n ")) }
  274. Write-Host ("verified parser: {0} fixtures; lowest stack pointer in expressions `${1:X2}" -f $ParserFixtures.Count, $minSp)

Powered by TurnKey Linux.