# E07-S03: create a Nova128 source in the Vim clone, prove the bytes on the disk # are the expected PETSCII/CR file, reopen it in a second Vim session, and # tokenize the disk file on C128 without a host conversion. [CmdletBinding()] param([int]$Port = 6541, [int]$SessionTimeoutSeconds = 300, [int]$MailboxSeconds = 180) $ErrorActionPreference = "Stop" $project = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent $repository = Split-Path $project -Parent Import-Module (Join-Path $project "scripts\lib\Nova128Test.psm1") -Force . (Join-Path $project "tests\lexer\reference-lexer.ps1") $root = Join-Path $project "build\vim-roundtrip" New-Item -ItemType Directory -Force -Path $root | Out-Null & (Join-Path $project "scripts\build-tokenize-seq.ps1") | Out-Host $x128 = Get-ViceTool "x128" $c1541 = Get-ViceTool "c1541" $sourceImage = Join-Path $repository "vim\vim-improved.d81" $editorPrg = Join-Path $repository "vim\vim128.prg" $work = Join-Path $root "work.d81" $roundtrip = Join-Path $root "roundtrip.pet" $copy = Join-Path $root "copy.pet" if (-not (Test-Path -LiteralPath $sourceImage -PathType Leaf)) { throw "editor image not found: $sourceImage" } if (-not (Test-Path -LiteralPath $editorPrg -PathType Leaf)) { throw "C128 editor program not found: $editorPrg" } $sourceHashBefore = (Get-FileHash -LiteralPath $sourceImage -Algorithm SHA256).Hash Copy-Item -LiteralPath $sourceImage -Destination $work -Force # The session's disk carries the C128 editor as well, so the machine can # reload it without the host. $injected = (& $c1541 $work -write $editorPrg "vim128" 2>&1) -join "`n" if ($LASTEXITCODE -ne 0) { throw "could not inject vim128 into the session disk:`n$injected" } # Both PETSCII letter ranges appear: lowercase identifiers and uppercase ones. $text = @( "program RoundTrip", "var alpha as byte", "var BETA as byte", "alpha = 1", "BETA = 2", "print alpha", "print BETA", "end program") -join "`n" # VICE keybuf uses a literal backslash-n for RETURN; \x03 is RUN/STOP, which # leaves the editor's insert mode. $typed = $text -split "`n" $insert = ($typed -join "\n") function Invoke-EditorSession { param( [Parameter(Mandatory = $true)][string]$Keys, [Parameter(Mandatory = $true)][string]$Screenshot ) if (Test-Path -LiteralPath $Screenshot) { Remove-Item -LiteralPath $Screenshot -Force } $psi = [Diagnostics.ProcessStartInfo]::new() $psi.FileName = $x128 # The C128 editor is an 80-column program, so the process starts in 80col # mode and the exit screenshot must be the plain option (VDC screen). It # is autostarted from the session disk: a host-loaded program gets no # device number, and vim128 then reports `written` without touching the # image. The machine runs at 1 MHz apart from autostart, so the wall-clock # key delay is also a cycle delay. $psi.WorkingDirectory = $root $psi.Arguments = @( "-80col", "-autostart", '"work.d81:vim128"', # A D81 needs a 1581 as drive 8, otherwise :wq fails with "cannot write". "-drive8type", "1581", "-8", "work.d81", "-autostart-warp", "-keybuf", ('"' + $Keys + '"'), "-keybuf-delay", "300", "-limitcycles", "120000000", "-exitscreenshot", ('"' + $Screenshot + '"'), "-sounddev", "dummy" ) -join ' ' $psi.UseShellExecute = $false $psi.CreateNoWindow = $true $process = [Diagnostics.Process]::Start($psi) if (-not $process.WaitForExit($SessionTimeoutSeconds * 1000)) { $process.Kill() throw "x64sc session exceeded $SessionTimeoutSeconds s" } if (-not (Test-Path -LiteralPath $Screenshot)) { throw "x64sc produced no exit screenshot; the session did not run" } } function Read-DiskSeq { param( [Parameter(Mandatory = $true)][string]$DiskName, [Parameter(Mandatory = $true)][string]$HostPath ) if (Test-Path -LiteralPath $HostPath) { Remove-Item -LiteralPath $HostPath -Force } $output = (& $c1541 $work -read "$DiskName,s" $HostPath 2>&1) -join "`n" if (-not (Test-Path -LiteralPath $HostPath -PathType Leaf)) { throw "could not read $DiskName from the session disk:`n$output" } return ,([IO.File]::ReadAllBytes($HostPath)) } function Compare-Bytes { param( [Parameter(Mandatory = $true)][byte[]]$Actual, [Parameter(Mandatory = $true)][byte[]]$Expected, [Parameter(Mandatory = $true)][string]$What ) $common = [Math]::Min($Actual.Length, $Expected.Length) for ($i = 0; $i -lt $common; $i++) { if ($Actual[$i] -ne $Expected[$i]) { throw ("{0}: byte {1} is `${2:X2}, expected `${3:X2} (actual {4} bytes, expected {5})" -f $What, $i, $Actual[$i], $Expected[$i], $Actual.Length, $Expected.Length) } } if ($Actual.Length -ne $Expected.Length) { throw ("{0}: length {1}, expected {2}" -f $What, $Actual.Length, $Expected.Length) } } # 1: create the file entirely in the editor. The name, the create answer, the # insert-mode text, RUN/STOP, and :wq all come from one keybuf string. Invoke-EditorSession -Keys ("roundtrip.n8\n" + "y" + "i" + $insert + "\x03:wq\n") ` -Screenshot (Join-Path $root "edit.png") $written = Read-DiskSeq -DiskName "roundtrip.n8" -HostPath $roundtrip $expected = ConvertTo-Nova128Petscii ($text + "`n") Compare-Bytes -Actual $written -Expected $expected -What "editor-written ROUNDTRIP.N8" $hasLow = @($written | Where-Object { $_ -ge 0x41 -and $_ -le 0x5A }) $hasHigh = @($written | Where-Object { $_ -ge 0xC1 -and $_ -le 0xDA }) if (-not $hasLow -or -not $hasHigh) { throw "the written file does not exercise both PETSCII letter ranges" } Write-Host ("editor file: {0} bytes, PETSCII ``$41-``$5A and ``$C1-``$DA both present" -f $written.Length) # 2: reopen the same file in a second session and write the buffer to another # name, which proves the editor restored the content it had saved. Invoke-EditorSession -Keys ("roundtrip.n8\n" + ":w copy.n8\n" + ":q\n") ` -Screenshot (Join-Path $root "reopen.png") $rewritten = Read-DiskSeq -DiskName "copy.n8" -HostPath $copy Compare-Bytes -Actual $rewritten -Expected $written -What "reopened COPY.N8" Write-Host "reopened in Vim and rewritten byte-identical" # 3: tokenize the editor-written file on C128 in x128. $disk = Join-Path $root "tokenize-seq.d81" $add = (& $c1541 $disk -write $roundtrip "roundtrip.n8,s" 2>&1) -join "`n" if ($LASTEXITCODE -ne 0) { throw "could not add the editor file to the test disk:`n$add" } $screenshot = Join-Path $root "tokenize.png" $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port -Screenshot $screenshot try { $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $MailboxSeconds $address = $mb[8] + 256 * $mb[9] $length = $mb[10] + 256 * $mb[11] $dump = Read-Nova128Memory $session $address $length } finally { Stop-Nova128X128 $session } [IO.File]::WriteAllBytes((Join-Path $root "token-dump.bin"), $dump) if ($mb[3] -ne 0xA5) { throw ("C128 reported FAIL (code `${0:X2})" -f $mb[4]) } $entries = ConvertFrom-TokenDump $dump if ($entries.Count -ne 1) { throw "token dump holds $($entries.Count) fixtures, expected 1" } if ($entries[0].Load -ne 0) { throw "src_load returned $($entries[0].Load) for the editor file" } $wanted = @((Get-ReferenceTokens $written) | ForEach-Object { $_ }) $got = @($entries[0].Tokens) if ($wanted.Count -ne $got.Count) { throw "C128 produced $($got.Count) tokens, the reference lexer $($wanted.Count)" } for ($i = 0; $i -lt $wanted.Count; $i++) { if ($wanted[$i] -ne $got[$i]) { throw ("token {0} differs: reference '{1}' vs C128 '{2}'" -f ($i + 1), $wanted[$i], $got[$i]) } } $sourceHashAfter = (Get-FileHash -LiteralPath $sourceImage -Algorithm SHA256).Hash if ($sourceHashAfter -ne $sourceHashBefore) { throw "the Vim source image changed during the round-trip" } Write-Host ("tokenized on C128: {0} tokens, no host conversion" -f $got.Count) Write-Host ("source image SHA-256 unchanged: {0}" -f $sourceHashAfter) Write-Host "vim source round-trip passed"