diff --git a/newLang/demo/README.md b/newLang/demo/README.md new file mode 100644 index 0000000..8568530 --- /dev/null +++ b/newLang/demo/README.md @@ -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). diff --git a/newLang/demo/bad.n8 b/newLang/demo/bad.n8 new file mode 100644 index 0000000..2130a5e --- /dev/null +++ b/newLang/demo/bad.n8 @@ -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 diff --git a/newLang/demo/good.n8 b/newLang/demo/good.n8 new file mode 100644 index 0000000..45cdc3f --- /dev/null +++ b/newLang/demo/good.n8 @@ -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 diff --git a/newLang/demo/n8check.s b/newLang/demo/n8check.s new file mode 100644 index 0000000..398712d --- /dev/null +++ b/newLang/demo/n8check.s @@ -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 + ldy name_len + jsr diag_init ; scratches a stale NAME.ERR + lda #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+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" diff --git a/newLang/scripts/demo-write-check.ps1 b/newLang/scripts/demo-write-check.ps1 new file mode 100644 index 0000000..c1a54e1 --- /dev/null +++ b/newLang/scripts/demo-write-check.ps1 @@ -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/.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" +}