|
- # build.ps1 - build a Power C program for the C64 on a fresh work disk, then
- # optionally leave it back in BASIC, loaded and running.
- #
- # powershell -NoProfile -File build.ps1
- # powershell -NoProfile -File build.ps1 -Run -Keys 'vim.c\njjj'
- # powershell -NoProfile -File build.ps1 -Shell 'l\n' (poke at the disk)
- #
- # Everything happens in ONE emulator session: compile each unit, link, then
- # "bye" out of the Power C shell, load the program from BASIC and run it. One
- # session is not just faster, it is required: two emulators both attached to
- # work.d81 write the image back over each other on exit and the files made by
- # the other one disappear.
- #
- # Power C is interactive - cc asks which disk to insert three times, the linker
- # wants one object file per ">" line, "^" for the libraries, an empty RETURN
- # and then a name - so every prompt is answered from the -keybuf string. VICE
- # paces those keystrokes in wall-clock milliseconds while the machine runs at
- # warp, so the delay has to be long enough that a step's answers arrive after
- # its prompt: cc, for one, clears the keyboard buffer when it starts.
-
- param(
- [string]$Prog = "vim",
- [string]$Keys = "",
- [string]$Shell = "",
- [int]$Delay = 900, # ms per keystroke; wall clock, machine is at warp
- [int]$Pad = 20, # spare RETURNs while a step is working
- [int]$Tail = 8, # spare RETURNs while the linker writes
- [int]$Settle = 0, # extra seconds to run after the last key (screen redraw)
- [switch]$Full, # ignore the stamps: fresh disk, recompile everything
- [switch]$Run
- )
-
- $ErrorActionPreference = "Stop"
-
- $vice = "C:\Program Files\GTK3VICE-3.10-win64\bin"
- $x64 = Join-Path $vice "x64sc.exe"
- $c1541 = Join-Path $vice "c1541.exe"
- $here = $PSScriptRoot
- $base = Join-Path $here "..\reference\POWERc.d81"
- $shots = Join-Path $here "shots"
-
- # VICE splits "image:file" on a colon, so keep the image name bare and run from
- # this folder.
- Set-Location $here
- $img = "work.d81"
-
- $units = @{
- vim = @("vim", "vscreen", "vview", "vedit", "vdir", "vml")
- mltest = @("mltest", "vml")
- }
- if (-not $units.ContainsKey($Prog)) { throw "unknown program '$Prog'" }
- $unitList = $units[$Prog]
-
- if (-not (Test-Path $x64)) { throw "x64sc.exe not found in $vice" }
- if (-not (Test-Path $base)) { throw "$base not found - is the repository complete?" }
- New-Item -ItemType Directory -Force -Path $shots | Out-Null
-
- # ---------------------------------------------------------------- petscii
-
- # Power C sources live on the disk as PETSCII SEQ files with CR line endings:
- # a-z -> $41-$5A, A-Z -> $C1-$DA, CRLF -> CR, and the four characters the manual
- # says are typed with shift/logo keys: { | } ~ -> $DB $DC $DD $DE.
- function ConvertTo-Petscii([string]$in, [string]$out) {
- $bytes = [IO.File]::ReadAllBytes($in)
- $start = 0
- if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and
- $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { $start = 3 }
- $o = New-Object System.Collections.Generic.List[byte]
- for ($i = $start; $i -lt $bytes.Length; $i++) {
- $b = $bytes[$i]
- if ($b -eq 13) { continue }
- if ($b -eq 10) { $o.Add([byte]13); continue }
- if ($b -ge 0x61 -and $b -le 0x7A) { $o.Add([byte]($b - 0x20)); continue }
- if ($b -ge 0x41 -and $b -le 0x5A) { $o.Add([byte]($b + 0x80)); continue }
- if ($b -ge 0x7B -and $b -le 0x7F) { $o.Add([byte]($b + 0x60)); continue }
- $o.Add([byte]$b)
- }
- [IO.File]::WriteAllBytes($out, $o.ToArray())
- return $o.Count
- }
-
- # ---------------------------------------------------------------- emulator
-
- # Quote one argument the way the Windows command line wants it. Start-Process's
- # -ArgumentList joins with spaces and does not quote anything, which silently
- # split "-keybuf cc vim.c\n" into two arguments, so the command line is built
- # here and handed to .NET as a single string.
- 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 + '"'
- }
-
- # Exactly one emulator, waited for, so nothing else touches the image.
- function Invoke-C64 {
- param(
- [string]$KeyBuf,
- [string]$Shot,
- [string]$Target = "shell",
- [int]$Cycles = 0,
- [int]$KeyDelay = 900
- )
- if (Test-Path $Shot) { Remove-Item $Shot -Force } # VICE won't overwrite
- if ($Cycles -le 0) {
- $wall = ($KeyBuf.Length * $KeyDelay / 1000.0) + 15 + $Settle
- $c = [long]($wall * 13000000)
- if ($c -lt 150000000) { $c = 150000000 }
- if ($c -gt 2000000000) { $c = 2000000000 }
- $Cycles = [int]$c
- }
- $argv = @(
- "-8", $img,
- "-autostart", ($img + ":" + $Target),
- "-autostart-warp", "-warp", "-sounddev", "dummy",
- "-keybuf-delay", "$KeyDelay",
- "-keybuf", $KeyBuf,
- "-limitcycles", "$Cycles",
- "-exitscreenshot", $Shot
- )
- $cmdline = ($argv | ForEach-Object { Quote-Arg $_ }) -join ' '
- $psi = New-Object System.Diagnostics.ProcessStartInfo
- $psi.FileName = $x64
- $psi.Arguments = $cmdline
- $psi.UseShellExecute = $false
- $pi = [System.Diagnostics.Process]::Start($psi)
- $pi.WaitForExit()
- Start-Sleep -Milliseconds 900 # let the image write settle
- }
-
- function Get-DiskDir {
- return (& $c1541 $img -list 2>$null) -join "`n"
- }
-
- # ---------------------------------------------------------------- raw shell
-
- if ($Shell -ne "") {
- if (-not (Test-Path $img)) { throw "$img not found - build something first" }
- Invoke-C64 -KeyBuf $Shell -Shot (Join-Path $shots "shell.png") -KeyDelay $Delay
- Write-Host "shots\shell.png"
- exit 0
- }
-
- # ---------------------------------------------------------------- fresh disk
-
- # work.stamp remembers the hash of each .c as it was last compiled onto the work
- # disk. Only units whose hash differs are converted, copied over and recompiled;
- # the others keep the .o already on the disk. The linker then runs again.
- $stampFile = Join-Path $here "work.stamp"
- $stamps = @{}
- if (Test-Path $stampFile) {
- foreach ($ln in (Get-Content $stampFile)) {
- $p = $ln -split '='
- if ($p.Count -eq 2) { $stamps[$p[0]] = $p[1] }
- }
- }
- if ($Full -or -not (Test-Path $img) -or -not (Test-Path $stampFile)) {
- Write-Host "== fresh work disk"
- Copy-Item $base $img -Force
- $stamps = @{}
- } else {
- Write-Host "== incremental build on $img"
- }
-
- $changed = @()
- foreach ($u in $unitList) {
- $src = Join-Path $here "$u.c"
- if (-not (Test-Path $src)) { throw "$src not found" }
- $hash = (Get-FileHash $src -Algorithm MD5).Hash
- if ($stamps[$u] -eq $hash) { continue }
- $changed += $u
- $pet = Join-Path $here "$u.pet"
- $n = ConvertTo-Petscii $src $pet
- Write-Host ("== {0}.c changed -> petscii ({1} bytes)" -f $u, $n)
- # cc and the linker refuse to overwrite, so scratch the stale copies first
- $null = & $c1541 $img -delete "$u.c" "$u.o" 2>$null
- $null = & $c1541 $img -write $pet "$u.c,s" 2>$null
- $stamps.Remove($u) # only re-stamped once its .o really exists
- }
- $haveProg = ((& $c1541 $img -list 2>$null) -join "`n") -match ('"' + [regex]::Escape($Prog) + '"\s+prg')
- $needLink = ($changed.Count -gt 0) -or (-not $haveProg)
- if ($needLink -and $haveProg) { $null = & $c1541 $img -delete $Prog 2>$null }
- if ($changed.Count -eq 0) { Write-Host "== no source changed" }
-
- # ---------------------------------------------------------------- one session
-
- # cc <unit> and RETURNs for its disk prompts, over and over, then the linker:
- # ">" object files, ">" up-arrow for the libraries, ">" empty to finish, the
- # output name. With -Run the session goes on to leave the shell with bye, load
- # the program from BASIC and type -Keys at whatever it puts on the screen.
- $kb = ""
- foreach ($u in $changed) {
- $kb += "cc $u.c\n" + ("\n" * 3) + ("\n" * $Pad)
- }
- if ($needLink) {
- $kb += "link -s\n"
- foreach ($u in $unitList) { $kb += "$u.o\n" }
- $kb += "^\n\n" + $Prog + "\n" + ("\n" * $Tail)
- }
- if ((-not $Run) -and ($kb -eq "")) {
- Write-Host "build ok - nothing to do"
- exit 0
- }
- if ($Run) {
- $kb += "bye\n" + ("\n" * 6) # let the shell hand back to BASIC
- $kb += 'load"' + $Prog + '",8,1' + "\n" + ("\n" * 10) # let the load finish
- $kb += "run\n"
- $kb += $Keys
- }
-
- $shot = if ($Run) { Join-Path $shots "run.png" } else { Join-Path $shots "build.png" }
- Write-Host ("== one session: cc [{0}], {1}{2}" -f ($changed -join ' '),
- $(if ($needLink) { 'link' } else { 'no link' }),
- $(if ($Run) { ', bye, load, run' } else { '' }))
- Invoke-C64 -KeyBuf $kb -Shot $shot -KeyDelay $Delay
-
- $dir = Get-DiskDir
- $found = $false
- foreach ($line in ($dir -split "`n")) {
- if ($line -match ('"' + [regex]::Escape($Prog) + '"\s+prg')) { $found = $true }
- if ($line -match ('"(' + ($unitList -join '|') + ')\.[co]"')) {
- Write-Host " $line"
- }
- }
-
- # stamp a unit only once its object file is really on the disk
- foreach ($u in $changed) {
- if ($dir -match ('"' + [regex]::Escape($u) + '\.o"')) {
- $stamps[$u] = (Get-FileHash (Join-Path $here "$u.c") -Algorithm MD5).Hash
- } else {
- Write-Host " $u.c produced no object - read $shot"
- }
- }
- ($stamps.GetEnumerator() | ForEach-Object { "{0}={1}" -f $_.Key, $_.Value }) | Set-Content $stampFile
- if (-not $found) { throw "'$Prog' was not created - read $shot" }
-
- if ($Run) {
- Write-Host (" {0}" -f (Split-Path $shot -Leaf))
- } else {
- Write-Host "build ok - 'build.ps1 -Run -Keys ...' to try it"
- }
|