Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion .github/workflows/TestingCI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,68 @@ jobs:
& ./target/release/xbasic64.exe $_.FullName -o example.exe
if ($LASTEXITCODE -ne 0) { throw "compile failed: $($_.Name)" }
& ./example.exe | Out-Null
if ($LASTEXITCODE -ne 0) { throw "run failed: $($_.Name)" }
# Report the code, not just the failure. Windows says what went
# wrong in it, and nothing else here can: 1 is the compiler's own
# diagnosed abort, while 0xC0000005 (access violation), 0xC0000409
# (stack buffer overrun) and 0xC0000374 (heap corruption) each name
# a different defect, and none of the three can be reproduced on
# the Linux job.
if ($LASTEXITCODE -ne 0) {
# `-f` with X8 renders a negative Int32 as its two's-complement
# hex on its own. Masking first needed no cast and got one: in
# PowerShell 0xFFFFFFFF is Int32 -1, so the -band was a no-op and
# [uint32] then threw on the very value it was there to show.
throw ("run failed: {0} (exit {1}, 0x{2:X8})" -f $_.Name, $LASTEXITCODE, $LASTEXITCODE)
}
}

# An example is a whole program, so a crash in one names the program and
# not the statement. These are one statement each, and they are the only
# coverage the console statements have anywhere: the test suite runs on
# Linux, and CLS, LOCATE and COLOR are precisely the helpers whose two
# implementations differ most.
#
# Every probe runs, and the step reports the whole table before failing,
# so one CI run says which statements are broken rather than the first.
- name: Probe each console statement
if: always()
shell: pwsh
run: |
$probes = [ordered]@{
'CLS' = 'CLS'
'CLS after PRINT' = "PRINT `"x`"`nCLS"
'CLS twice' = "CLS`nCLS"
'PRINT alone' = 'PRINT "x"'
'COLOR' = 'COLOR 14, 1'
'LOCATE' = 'LOCATE 2, 5'
'LOCATE row only' = 'LOCATE 3'
'POS' = 'PRINT POS(0)'
'TIMER' = 'PRINT TIMER'
'RANDOMIZE n' = 'RANDOMIZE 42'
'RANDOMIZE TIMER' = 'RANDOMIZE TIMER'
'RND' = 'PRINT RND'
'RND(0)' = 'PRINT RND(0)'
'BEEP' = 'BEEP'
'DATE$' = 'PRINT DATE$'
'TIME$' = 'PRINT TIME$'
'FRE' = 'PRINT FRE(0)'
'DEFINT' = "DEFINT A-Z`nX = 3`nPRINT X"
'EQV' = 'PRINT 1 EQV 1'
'ERASE' = "DIM A(3)`nERASE A"
'SYSTEM' = 'SYSTEM'
}
$failed = @()
foreach ($p in $probes.GetEnumerator()) {
Set-Content -Path probe.bas -Value $p.Value
& ./target/release/xbasic64.exe probe.bas -o probe.exe | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host ("{0,-20} DID NOT COMPILE" -f $p.Key)
$failed += $p.Key
continue
}
& ./probe.exe | Out-Null
$code = $LASTEXITCODE
Write-Host ("{0,-20} exit {1} (0x{2:X8})" -f $p.Key, $code, $code)
if ($code -ne 0) { $failed += $p.Key }
}
if ($failed.Count -gt 0) { throw "crashed: $($failed -join ', ')" }
9 changes: 8 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,20 @@ Integration tests organized by feature area:
- **Runtime checks on by default**: `--unsafe` removes them
- **Two runtimes in lockstep**: `runtime/sysv/` and `runtime/win64-native/` export
the same `.globl` names; a new helper must be added to both
- **Two assemblers, not just two runtimes**: `main.rs` assembles with GNU `as`
on Linux and `clang -c` on Windows, so the Win64 tree is the one built by the
assembler no test here runs. They disagree about `.equ len, . - label`: GNU
`as` folds it to an immediate, and an assembler that cannot prove the symbol
absolute assembles `mov reg, len` as a *load from that address* instead.
`CLS` did that on every Windows run. Bracket data with an `_end` label and
subtract at run time
- **A runtime helper takes at most four arguments**: Win64 passes only four in
registers, so `arg_reg(4)` panics there while System V accepts six. A helper
that needs a fifth is invisible on Linux and breaks every Windows compile;
fold an argument away instead (see `_rt_file_open_random`)
- **Unsupported GW-BASIC names are refused, not ignored**: an unrecognised name
is otherwise just a new variable, so `UNSUPPORTED` in `sema.rs` names the
keywords this compiler does not provide and why. See [NONGOALS.md](NONGOALS.md)
keywords this compiler does not provide and why
- **String builtins returning a static buffer must copy**: two calls in one
expression would otherwise alias, which is why `_rt_str`, `_rt_chr`, `_rt_hex`,
`_rt_oct` and `_rt_mk` end in `_rt_strdup`
Expand Down
119 changes: 104 additions & 15 deletions LANGREF.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,24 @@ xbasic64 supports five data types, indicated by suffix characters:

### Default Type

**Unsuffixed numeric variables default to DOUBLE (`#`).**
**Unsuffixed numeric variables default to DOUBLE (`#`)** unless a `DEF*`
statement says otherwise.

### DEFINT, DEFLNG, DEFSNG, DEFDBL, DEFSTR

These set the default type for names beginning with the given letters, so a
listing need not suffix every variable:

```basic
DEFINT A-Z ' every unsuffixed name is an INTEGER
DEFSTR S ' except those starting with S, which are strings
DEFINT A, C-E ' single letters and ranges, comma separated
```

A suffix always wins over the default, and the two spellings of one name are
the same variable: after `DEFINT A`, `A` and `A%` share storage. The default
applies to the whole program rather than from the statement onwards, which is
where these are written in practice.

```basic
X = 3.14159 ' X is Double
Expand Down Expand Up @@ -269,6 +286,8 @@ case-sensitive and a prefix sorts before the longer string (`"ab" < "abc"`).
| `OR` | Bitwise/logical OR |
| `XOR` | Bitwise/logical XOR |
| `NOT` | Bitwise/logical NOT |
| `EQV` | Bitwise equivalence |
| `IMP` | Bitwise implication |

These operate bitwise on integers, allowing both logical tests and bit
manipulation. Their operands are converted to integers first, and the result is
Expand Down Expand Up @@ -302,6 +321,8 @@ From highest to lowest:
6. `NOT`
7. `AND`
8. `OR`, `XOR`
9. `EQV`
10. `IMP`

Because `^` binds tighter than unary negation, `-2 ^ 2` is `-(2 ^ 2)` = -4.

Expand Down Expand Up @@ -663,6 +684,46 @@ Clear screen:
CLS
```

### BEEP, ERASE and SYSTEM

```basic
BEEP ' Ring the terminal bell
DIM Scores(10)
ERASE Scores ' Release it, so it may be DIMed again
DIM Scores(50)
SYSTEM ' End the program, as END does
```

`ERASE` is recognised only before a name, so `Erase` remains usable as a label
or a variable elsewhere. Its argument must be an array that is DIMed somewhere
-- a name that is not one is a mistake, not a statement that does nothing.

### LOCATE and COLOR

Console control, written as ANSI escape sequences:

```basic
CLS
LOCATE 5, 10 ' Row 5, column 10; both count from 1
LOCATE 3 ' Row only; the column is left alone
LOCATE , 20 ' Column only
COLOR 14, 1 ' Bright yellow on blue
COLOR 7 ' Foreground only
PRINT "positioned"
```

Colours are GW-BASIC's 0-15, where 8-15 are the bright half. GW-BASIC's third
`COLOR` argument sets the border, which a terminal has no equivalent for, and is
refused rather than ignored. `LOCATE` with no row asks for row 1, since the row
is not tracked the way the column is.

`POS(0)` gives the column the next character will be written to, counting from 1:

```basic
PRINT "abc";
PRINT POS(0) ' 4
```

### SWAP

Exchange two values of the same type, including array elements:
Expand Down Expand Up @@ -847,23 +908,41 @@ STOP ' Terminate (historically for debugging)
| `EXP(x)` | e raised to power x |
| `LOG(x)` | Natural logarithm |
| `RND` | Random number 0 ≤ r < 1 |
| `FRE(x)` | Free memory; a large constant here |

**Numeric output:** `PRINT` writes the shortest decimal that reads back as the
same value, so a `DOUBLE` shows its full precision (`PRINT 1 / 3` gives
`0.3333333333333333`) and a `SINGLE` shows only the ~7 digits it carries.

**RND behavior:**
**RND behavior:** the argument selects between three behaviours.

```basic
X = RND ' Next random number
X = RND(0) ' Same as RND
X = RND(-1) ' Reseed with system time (implementation-defined)
X = RND(1) ' Next random number; any positive value does this
X = RND(0) ' The previous number again
X = RND(-7) ' Reseed from -7, then return the next number
```

The generator starts from a fixed seed, so a program that never reseeds replays
the same numbers on every run -- which is useful while debugging and wrong for a
game. `RANDOMIZE` is how a program chooses:

```basic
RANDOMIZE ' Seed from the clock: a different run every time
RANDOMIZE TIMER ' The same thing, written out
RANDOMIZE 42 ' A fixed seed: the same run every time
```

GW-BASIC's bare `RANDOMIZE` asks the operator for a seed. A compiled program has
nobody to ask, so it takes the clock.

### String Functions

| Function | Description |
|-----------------------|------------------------------------------------|
| `LEN(s$)` | Length of string |
| `DATE$` | Current date, as `MM-DD-YYYY` |
| `TIME$` | Current time, as `HH:MM:SS` |
| `LEFT$(s$, n)` | Leftmost n characters |
| `RIGHT$(s$, n)` | Rightmost n characters |
| `MID$(s$, start, len)`| Substring (1-based index) |
Expand Down Expand Up @@ -1283,20 +1362,30 @@ Each of these is practical on both Linux and Windows and simply has not been
written. Programs using them are refused today.

- **Error trapping** -- `ON ERROR GOTO`, `RESUME`, `RESUME NEXT`, `ERR`, `ERL`, `ERROR`
- **Console control** -- `LOCATE`, `COLOR`, `WIDTH`, `CSRLIN`, `POS`, `VIEW PRINT`, `INKEY$`, `BEEP`, `SLEEP`
- **Date and time** -- `DATE$`, `TIME$`
- **Console control** -- `WIDTH`, `CSRLIN`, `VIEW PRINT`, `INKEY$`, `BEEP`, `SLEEP`
- **Operating system** -- `SHELL`, `ENVIRON$`, `KILL`, `NAME`, `FILES`, `CHDIR`, `MKDIR`, `RMDIR`
- **Odds and ends** -- `RANDOMIZE`, `ERASE`, `INPUT$`, `FRE`, `SHARED`, `STATIC`
- **`DEFINT` and friends** -- `DEFINT`, `DEFLNG`, `DEFSNG`, `DEFDBL`, `DEFSTR`; use a
type suffix or `DIM ... AS`
- **Odds and ends** -- `INPUT$`, `SHARED`, `STATIC`

### Out of scope

**Graphics** -- `SCREEN`, `PSET`, `PRESET`, `LINE` in its graphics form,
`CIRCLE`, `DRAW`, `PAINT`, `POINT`, `VIEW`, `WINDOW`, `PALETTE`, `PMAP`. These
need a display this compiler does not provide.

**The 8086's machine** -- `PEEK`, `POKE`, `DEF SEG`, `VARPTR`, `VARPTR$`, `USR`,
`INP`, `OUT`, `WAIT`, `BLOAD`, `BSAVE`. There is no fixed address worth naming
in a 64-bit hosted program: the video buffer is not memory, addresses are
randomised, and the pages are protected. A `POKE` that appeared to work would be
the worst outcome available, so these are refused rather than emulated.

**Commands to the interpreter** -- `RUN`, `LIST`, `LOAD`, `SAVE`, `MERGE`,
`NEW`, `EDIT`, `RENUM`, `AUTO`, `CONT`, `DELETE`, `TRON`, `TROFF`, `CLEAR`.
These operate on program text that a compiled program no longer has.

### Never
**Program chaining** -- `CHAIN` and `COMMON` need separate compilation.

Graphics, sound, joysticks, light pens, direct memory access, port I/O, the
line printer, and the interpreter's own commands (`RUN`, `LIST`, `CHAIN`, ...)
are permanent non-goals: they describe a machine and a way of working that a
compiled 64-bit program does not have. **[NONGOALS.md](NONGOALS.md)** gives the
full list and the reasoning.
Everything else GW-BASIC provides that is missing here is listed above as not
yet implemented. Each refused name says which of the two it is.

### Structural

Expand Down
Loading
Loading