scripts/demo-write-check.ps1 types each demo program into the Vim clone on x64sc (fresh copy of vim/work.d81), confirms the saved SEQ file matches its ASCII mirror, then runs demo/n8check.s on x128: a DEMO driver (not N128C) that asks for a file name and runs the current front end (source reader, lexer, names, parser) with on-screen diagnostics and NAME.ERR. good.n8 checks clean; bad.n8 reports its four intended errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
| @@ -0,0 +1,26 @@ | |||
| # Write-and-check demo | |||
| Shows the Nova128 workflow as far as it exists today: a program is typed | |||
| into the Vim clone on the C64, saved to disk, and checked on the C128 by | |||
| the compiler front end. No code is generated yet (epic E04), so nothing | |||
| runs. | |||
| ```powershell | |||
| pwsh -File scripts/demo-write-check.ps1 # good.n8 and bad.n8 | |||
| pwsh -File scripts/demo-write-check.ps1 -Programs bad.n8 | |||
| ``` | |||
| | File | Purpose | | |||
| |---|---| | |||
| | `good.n8` | Valid program: interface, class with private field, `init`, interface call | | |||
| | `bad.n8` | Four errors: `init` with a result, `destroy` with a parameter, private field used from the program, undeclared name | | |||
| | `n8check.s` | DEMO driver (not `N128C`): asks for a file name, runs source reader, lexer, names, and parser, shows diagnostics, writes `NAME.ERR` | | |||
| The script works on `build/demo/demo.d81`, a fresh copy of `vim/work.d81` | |||
| (the original is never written). x64sc and x128 run one after the other, | |||
| never together. Demo sources must be lowercase with no quotes or | |||
| backslashes, because they are typed through VICE's `-keybuf`. | |||
| Outputs in `build/demo/`: `NAME-vim.png` (the editor after `:w`), | |||
| `NAME-check.png` (the C128 screen), `NAME.seq` (the file Vim wrote), and | |||
| `NAME.err.txt` (the error file, when there are errors). | |||
| @@ -0,0 +1,15 @@ | |||
| class square | |||
| private word side | |||
| public method init(s as word) as word | |||
| self.side = s | |||
| end method | |||
| public method destroy(x as byte) | |||
| end method | |||
| end class | |||
| program main | |||
| var sq as square | |||
| sq = new square(4) | |||
| print sq.side | |||
| print total | |||
| end program | |||
| @@ -0,0 +1,22 @@ | |||
| interface shape | |||
| method area() as word | |||
| end interface | |||
| class square implements shape | |||
| private word side | |||
| public method init(s as word) | |||
| self.side = s | |||
| end method | |||
| public method area() as word | |||
| return self.side * self.side | |||
| end method | |||
| end class | |||
| program main | |||
| var sq as square | |||
| var s as shape | |||
| sq = new square(4) | |||
| s = sq | |||
| print s.area() | |||
| delete s | |||
| end program | |||
| @@ -0,0 +1,248 @@ | |||
| ; n8check.s - DEMO driver: check one Nova128 source file on the C128. | |||
| ; | |||
| ; This is not the Nova128 compiler (N128C, still to come). It runs the | |||
| ; front end that exists today - source reader, lexer, name table, and the | |||
| ; declaration/member/scope parser (E02, E03-S01, E03-S02) - on a file named | |||
| ; at the prompt, shows any diagnostics on screen, and writes NAME.ERR. It | |||
| ; generates no code: the code generator is epic E04. | |||
| ; | |||
| ; Mailbox: producer $02, status PASS (no errors) or FAIL, code = errors. | |||
| .setcpu "6502" | |||
| .include "../src/common/c128.inc" | |||
| .include "../src/common/mailbox.inc" | |||
| .include "../src/common/source.inc" | |||
| .include "../src/common/errors.inc" | |||
| .import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline | |||
| .import src_load, src_err_line, src_err_col, src_dos_code | |||
| .import names_init | |||
| .import diag_init, diag_report, diag_finish, diag_line, diag_col | |||
| .import diag_arg1, diag_arg1_len | |||
| .import parse_file | |||
| NAME_MAX = 16 | |||
| .segment "LOADADDR" | |||
| .word $1C01 | |||
| .segment "EXEHDR" | |||
| .word basic_end | |||
| .word 10 | |||
| .byte $9E | |||
| .byte "7181" | |||
| .byte $00 | |||
| basic_end: | |||
| .word $0000 | |||
| .segment "MAILBOX" | |||
| .res MB_SIZE | |||
| .segment "STARTUP" | |||
| entry: | |||
| php | |||
| sei | |||
| ldx #MB_SIZE - 1 | |||
| lda #$00 | |||
| @clr: sta MB_BASE,x | |||
| dex | |||
| bpl @clr | |||
| lda #MB_MAGIC0_VALUE | |||
| sta MB_MAGIC0 | |||
| lda #MB_MAGIC1_VALUE | |||
| sta MB_MAGIC1 | |||
| lda #MB_PROD_COMPILER | |||
| sta MB_PRODUCER | |||
| lda #MB_ST_RUNNING | |||
| sta MB_STATUS | |||
| jsr mmu_init | |||
| jsr mmu_bank0 | |||
| jsr main | |||
| jsr mmu_shutdown | |||
| plp | |||
| rts | |||
| .segment "CODE" | |||
| main: ldx #msg_title - msgs | |||
| jsr print | |||
| ldx #msg_prompt - msgs | |||
| jsr print | |||
| jsr read_name | |||
| lda name_len | |||
| bne :+ | |||
| rts ; empty name: nothing to do | |||
| : ldx #msg_checking - msgs | |||
| jsr print | |||
| jsr names_init | |||
| lda #<name_buf | |||
| ldx #>name_buf | |||
| ldy name_len | |||
| jsr diag_init ; scratches a stale NAME.ERR | |||
| lda #<name_buf | |||
| ldx #>name_buf | |||
| ldy name_len | |||
| jsr src_load | |||
| bcc @parse | |||
| jsr report_load ; A = SRC_ERR_* | |||
| jmp @done | |||
| @parse: jsr parse_file | |||
| @done: jsr diag_finish ; A = errors reported | |||
| sta errors | |||
| sta MB_CODE | |||
| cmp #$00 | |||
| bne @bad | |||
| ldx #msg_ok - msgs | |||
| jsr print | |||
| lda #MB_ST_PASS | |||
| sta MB_STATUS | |||
| rts | |||
| @bad: lda errors | |||
| jsr print_dec | |||
| ldx #msg_errors - msgs | |||
| jsr print | |||
| ldx #$00 ; base name, then .ERR | |||
| @base: cpx base_len | |||
| beq :+ | |||
| lda name_buf,x | |||
| stx save_x | |||
| jsr chrout | |||
| ldx save_x | |||
| inx | |||
| bne @base | |||
| : ldx #msg_errfile - msgs | |||
| jsr print | |||
| lda #MB_ST_FAIL | |||
| sta MB_STATUS | |||
| rts | |||
| ; Map a source-reader failure (A = SRC_ERR_*) to its diagnostic. | |||
| report_load: | |||
| cmp #SRC_ERR_DISK | |||
| bne @limit | |||
| lda src_dos_code ; "DOS nn" | |||
| ldx #$00 | |||
| @tens: cmp #10 | |||
| bcc :+ | |||
| sbc #10 | |||
| inx | |||
| bne @tens | |||
| : ora #'0' | |||
| sta dos_text+1 | |||
| txa | |||
| ora #'0' | |||
| sta dos_text | |||
| lda #<dos_text | |||
| sta diag_arg1 | |||
| lda #>dos_text | |||
| sta diag_arg1+1 | |||
| lda #2 | |||
| sta diag_arg1_len | |||
| lda #$00 | |||
| sta diag_line | |||
| sta diag_line+1 | |||
| sta diag_col | |||
| lda #E_DSK_SOURCE_UNREADABLE | |||
| jmp diag_report | |||
| @limit: tax ; 2/3/4 -> E002/E001/E003 | |||
| lda load_codes-2,x | |||
| pha | |||
| lda src_err_line | |||
| sta diag_line | |||
| lda src_err_line+1 | |||
| sta diag_line+1 | |||
| lda src_err_col | |||
| sta diag_col | |||
| pla | |||
| jmp diag_report | |||
| ; Read a line from the keyboard into name_buf (screen editor input). | |||
| read_name: | |||
| lda #$00 | |||
| sta name_len | |||
| @key: jsr kcall_inline | |||
| .word CHRIN | |||
| cmp #$0D | |||
| beq @end | |||
| ldx name_len | |||
| cpx #NAME_MAX | |||
| bcs @key | |||
| sta name_buf,x | |||
| inc name_len | |||
| bne @key | |||
| @end: lda #$0D | |||
| jsr chrout | |||
| lda name_len ; base_len = length without ".N8" | |||
| sta base_len | |||
| cmp #3 | |||
| bcc @out | |||
| tax | |||
| lda name_buf-3,x | |||
| cmp #'.' | |||
| bne @out | |||
| lda name_buf-2,x | |||
| and #$7F | |||
| cmp #'N' | |||
| bne @out | |||
| lda name_buf-1,x | |||
| cmp #'8' | |||
| bne @out | |||
| dex | |||
| dex | |||
| dex | |||
| stx base_len | |||
| @out: rts | |||
| ; Print the $00-terminated message at msgs+X. | |||
| print: lda msgs,x | |||
| beq @done | |||
| stx save_x | |||
| jsr chrout | |||
| ldx save_x | |||
| inx | |||
| bne print | |||
| @done: rts | |||
| ; Print A (0-99) in decimal; at most 21 errors are ever reported. | |||
| print_dec: | |||
| ldx #$00 | |||
| @t: cmp #10 | |||
| bcc @units | |||
| sbc #10 | |||
| inx | |||
| bne @t | |||
| @units: pha | |||
| txa | |||
| beq :+ | |||
| ora #'0' | |||
| jsr chrout | |||
| : pla | |||
| ora #'0' | |||
| jmp chrout | |||
| chrout: jsr kcall_inline | |||
| .word CHROUT | |||
| rts | |||
| .segment "RODATA" | |||
| load_codes: | |||
| .byte E_SRC_FILE_TOO_LARGE, E_SRC_LINE_TOO_LONG, E_SRC_TOO_MANY_LINES | |||
| msgs: | |||
| msg_title: .byte $0D, "NOVA128 CHECK (DEMO: FRONT END ONLY)", $0D, $00 | |||
| msg_prompt: .byte "SOURCE FILE?", $0D, $00 | |||
| msg_checking: .byte "CHECKING...", $0D, $00 | |||
| msg_ok: .byte "NO ERRORS: DECLARATIONS, MEMBERS,", $0D | |||
| .byte "SCOPES AND ACCESS ARE VALID.", $0D | |||
| .byte "(NO CODE YET - CODE GENERATION IS E04)", $0D, $00 | |||
| msg_errors: .byte " ERROR(S); LIST IN ", $00 | |||
| msg_errfile: .byte ".ERR", $0D, $00 | |||
| .segment "BSS" | |||
| name_buf: .res NAME_MAX | |||
| name_len: .res 1 | |||
| base_len: .res 1 | |||
| errors: .res 1 | |||
| save_x: .res 1 | |||
| .segment "DATA" | |||
| dos_text: .byte "00" | |||
| @@ -0,0 +1,135 @@ | |||
| # DEMO: write a Nova128 program in the Vim clone (x64sc, C64 mode), then check | |||
| # it on the C128 (x128) with the compiler front end as it exists today. | |||
| # | |||
| # pwsh -File scripts/demo-write-check.ps1 (good.n8 and bad.n8) | |||
| # pwsh -File scripts/demo-write-check.ps1 -Programs good.n8 | |||
| # | |||
| # For each demo/<name>.n8 (an ASCII mirror): | |||
| # 1. x64sc autostarts VIM from a fresh copy of vim/work.d81 and the program | |||
| # is TYPED into it through the keyboard buffer: file name, `y` to create, | |||
| # `i`, the text, RUN/STOP, `:w`. Vim writes the PETSCII SEQ file and | |||
| # stays on screen for the exit screenshot. | |||
| # 2. The SEQ file is read back with c1541 and compared with the mirror. | |||
| # 3. x128 autostarts N8CHECK from the same disk (after x64sc has exited), | |||
| # the file name is typed at its prompt, and it parses the file. | |||
| # 4. The screen and NAME.ERR, if any, are shown. | |||
| # Screenshots go to build/demo/. No code is generated yet (epic E04). | |||
| [CmdletBinding()] | |||
| param([string[]]$Programs = @("good.n8", "bad.n8"), [int]$KeyDelay = 120) | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| $repository = Split-Path $project -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| $out = Join-Path $project "build\demo" | |||
| New-Item -ItemType Directory -Force -Path $out | Out-Null | |||
| $c1541 = Get-ViceTool "c1541" | |||
| $x64 = Get-ViceTool "x64sc" | |||
| $x128 = Get-ViceTool "x128" | |||
| # ------------------------------------------------------------- build | |||
| $link = Invoke-Nova128Link -Name "n8check" -BuildDir $out ` | |||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||
| (Join-Path $project "src\compiler\mmu.s"), | |||
| (Join-Path $project "src\compiler\diskio.s"), | |||
| (Join-Path $project "src\compiler\source.s"), | |||
| (Join-Path $project "src\compiler\lexer.s"), | |||
| (Join-Path $project "src\compiler\names.s"), | |||
| (Join-Path $project "src\compiler\diagnostics.s"), | |||
| (Join-Path $project "src\compiler\parser.s"), | |||
| (Join-Path $project "demo\n8check.s")) | |||
| # A fresh copy: the Vim work disk itself is never written. | |||
| $disk = Join-Path $out "demo.d81" | |||
| if (Test-Path $disk) { Remove-Item $disk -Force } | |||
| Copy-Item (Join-Path $repository "vim\work.d81") $disk | |||
| & $c1541 $disk -write $link.Program "n8check" 2>&1 | Out-Null | |||
| foreach ($p in $Programs) { # stale copies, if any | |||
| & $c1541 $disk -delete $p ($p -replace '\.n8$', '.err') 2>&1 | Out-Null | |||
| } | |||
| function Quote-Arg([string]$a) { | |||
| if ($a -ne "" -and $a -notmatch '[\s"]') { return $a } | |||
| $e = $a -replace '(\\*)"', '$1$1\"' | |||
| $e = $e -replace '(\\+)$', '$1$1' | |||
| return '"' + $e + '"' | |||
| } | |||
| # One emulator at a time, run from build/demo so "demo.d81:prog" has no drive | |||
| # letter colon, and waited for so nothing else touches the image. | |||
| function Invoke-Vice([string]$exe, [string[]]$argv) { | |||
| $psi = [Diagnostics.ProcessStartInfo]::new() | |||
| $psi.FileName = $exe | |||
| $psi.WorkingDirectory = $out | |||
| $psi.Arguments = ($argv | ForEach-Object { Quote-Arg $_ }) -join ' ' | |||
| $psi.UseShellExecute = $false | |||
| $psi.RedirectStandardOutput = $true # VICE's log is noise here | |||
| $psi.RedirectStandardError = $true | |||
| $proc = [Diagnostics.Process]::Start($psi) | |||
| $stdout = $proc.StandardOutput.ReadToEndAsync() | |||
| $stderr = $proc.StandardError.ReadToEndAsync() | |||
| $proc.WaitForExit() | |||
| [void]$stdout.Result; [void]$stderr.Result | |||
| Start-Sleep -Milliseconds 900 # let the image write settle | |||
| } | |||
| function Get-Cycles([int]$keys, [int]$extraSeconds) { | |||
| $wall = $keys * $KeyDelay / 1000.0 + $extraSeconds | |||
| return [long][Math]::Min([Math]::Max($wall * 13000000, 150000000), 2000000000) | |||
| } | |||
| function ConvertFrom-PetsciiText([byte[]]$bytes) { | |||
| return -join ($bytes | ForEach-Object { | |||
| if ($_ -eq 13) { "`n" } | |||
| elseif ($_ -ge 0x41 -and $_ -le 0x5A) { [char]($_ + 0x20) } | |||
| elseif ($_ -ge 0xC1 -and $_ -le 0xDA) { [char]($_ - 0x80) } | |||
| else { [char]$_ } }) | |||
| } | |||
| foreach ($name in $Programs) { | |||
| $base = $name -replace '\.n8$', '' | |||
| $mirror = (Get-Content -Raw (Join-Path $project "demo\$name")) -replace "`r", "" | |||
| $text = $mirror.TrimEnd("`n") | |||
| if ($text -cmatch '[A-Z"\\]') { throw "$name must be lowercase without quotes or backslashes (typed via -keybuf)" } | |||
| Write-Host "" | |||
| Write-Host "=== $name" | |||
| # 1. type it into Vim on the C64 | |||
| $keys = "$name\n" + "y" + "i" + ($text -replace "`n", "\n") + "\x03" + ":w\n" | |||
| $shot = Join-Path $out "$base-vim.png" | |||
| if (Test-Path $shot) { Remove-Item $shot -Force } | |||
| Write-Host " x64sc: typing $($text.Length) characters into VIM..." | |||
| Invoke-Vice $x64 @("-drive8type", "1581", "-8", "demo.d81", "-autostart", "demo.d81:vim", | |||
| "-autostart-warp", "-warp", "-sounddev", "dummy", | |||
| "-keybuf-delay", "$KeyDelay", "-keybuf", $keys, | |||
| "-limitcycles", "$(Get-Cycles $keys.Length 25)", "-exitscreenshot", $shot) | |||
| # 2. read back what Vim wrote | |||
| $seq = Join-Path $out "$base.seq" | |||
| if (Test-Path $seq) { Remove-Item $seq -Force } | |||
| & $c1541 $disk -read "$name,s" $seq 2>&1 | Out-Null | |||
| if (-not (Test-Path $seq)) { throw "VIM did not write $name (see $shot)" } | |||
| $saved = (ConvertFrom-PetsciiText ([IO.File]::ReadAllBytes($seq))).TrimEnd("`n") | |||
| if ($saved -ne $text) { throw "$name on disk differs from demo\$name (see $shot)" } | |||
| Write-Host " VIM saved $name as a SEQ file: $(([IO.File]::ReadAllBytes($seq)).Length) PETSCII bytes, identical to the mirror" | |||
| # 3. check it on the C128 | |||
| $shot128 = Join-Path $out "$base-check.png" | |||
| if (Test-Path $shot128) { Remove-Item $shot128 -Force } | |||
| Write-Host " x128: N8CHECK $name ..." | |||
| Invoke-Vice $x128 @("-drive8type", "1581", "-8", "demo.d81", "-autostart", "demo.d81:n8check", | |||
| "-autostart-warp", "-warp", "-sounddev", "dummy", | |||
| "-keybuf-delay", "$KeyDelay", "-keybuf", "$name\n", | |||
| "-limitcycles", "$(Get-Cycles 10 40)", "-exitscreenshotvicii", $shot128) | |||
| # 4. results | |||
| $err = Join-Path $out "$base.err.txt" | |||
| if (Test-Path $err) { Remove-Item $err -Force } | |||
| & $c1541 $disk -read "$base.err,s" $err 2>&1 | Out-Null | |||
| if (Test-Path $err) { | |||
| Write-Host " $base.err:" | |||
| foreach ($line in ([Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($err)).TrimEnd("`r") -split "`r")) { | |||
| Write-Host " $line" | |||
| } | |||
| } else { | |||
| Write-Host " no $base.err: the program has no errors" | |||
| } | |||
| Write-Host " screenshots: $shot, $shot128" | |||
| } | |||
Powered by TurnKey Linux.