Initial commit.

This commit is contained in:
Scott Duensing 2026-02-21 18:51:40 -06:00
commit a0b6f078f8
23 changed files with 9630 additions and 0 deletions

View file

@ -0,0 +1,92 @@
{
"permissions": {
"allow": [
"Bash(cc:*)",
"Bash(./basic2c)",
"Bash(./basic2c:*)",
"Bash(./test_output)",
"Bash(./test_classic:*)",
"Bash(./test_redim:*)",
"Bash(/tmp/t1)",
"Bash(/tmp/t2)",
"Bash(/tmp/t3)",
"Bash(./test_big)",
"Bash(/tmp/tbig:*)",
"Bash(/tmp/ttypes:*)",
"Bash(/tmp/ttest:*)",
"Bash(/tmp/tclassic:*)",
"Bash(/tmp/tredim:*)",
"Bash(/tmp/tfileio:*)",
"Bash(/tmp/tdata)",
"Bash(/tmp/test_labels:*)",
"Bash(for f in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_data.bas)",
"Bash(do echo \"=== $f ===\")",
"Bash(if [ $? -ne 0 ])",
"Bash(then echo \"FAIL: $f\")",
"Bash(else echo \"OK\")",
"Bash(fi)",
"Bash(done)",
"Bash(/tmp/test_data)",
"Bash(for f in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_labels.bas)",
"Bash(do echo -n \"$f: \")",
"Bash(for f in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_data.bas test_labels.bas)",
"Bash(for f in test_classic.bas test_data.bas test_labels.bas test_fileio.bas)",
"Bash(do echo \"===== $f =====\")",
"Bash(/tmp/out)",
"Bash(/tmp/test_multidim:*)",
"Bash(for f in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_data.bas test_labels.bas test_multidim.bas)",
"Bash(echo:*)",
"Bash(/tmp/out_udt)",
"Bash(for f in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_data.bas test_labels.bas test_multidim.bas test_udt.bas)",
"Bash(failed=0)",
"Bash(for bas in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_data.bas test_labels.bas test_multidim.bas test_udt.bas)",
"Bash(do:*)",
"Bash(then echo \"FAIL compile: $bas\")",
"Bash(failed=1)",
"Bash(else /tmp/test_out)",
"Bash(then echo \"FAIL run: $bas\")",
"Bash(else echo \"PASS \\(debug\\): $bas\")",
"Bash(if [ $failed -eq 0 ])",
"Bash(then echo \"All debug tests passed\")",
"Bash(else echo \"PASS \\(release\\): $bas\")",
"Bash(then echo \"All release tests passed\")",
"Bash(then echo \"FAIL compile \\($mode\\): $bas\")",
"Bash(then echo \"FAIL run \\($mode\\): $bas\")",
"Bash(then echo \"All 20 tests passed \\(10 debug + 10 release\\)\")",
"Bash(printf:*)",
"Bash(/tmp/test_sincos:*)",
"Bash(then echo \"FAIL: $bas\")",
"Bash(else /tmp/t)",
"Bash({ echo \"FAIL run: $bas\")",
"Bash(})",
"Bash([ $failed -eq 0 ])",
"Bash(python3:*)",
"Bash({ echo \"FAIL run \\($mode\\): $bas\")",
"Bash(for bas in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_labels.bas test_data.bas test_multidim.bas test_udt.bas)",
"Bash(for bas in test.bas test_big.bas)",
"Bash(/tmp/test_prog:*)",
"Bash(/tmp/test_nf)",
"Bash(for bas in test.bas test_classic.bas test_redim.bas test_big.bas test_types.bas test_fileio.bas test_labels.bas test_data.bas test_multidim.bas test_udt.bas test_newfeatures.bas)",
"Bash(/tmp/inc_test)",
"Bash(for:*)",
"Bash(/tmp/test_continue)",
"Bash(/tmp/test_print:*)",
"Bash(/tmp/test2)",
"Bash(/tmp/test_tab)",
"Bash(/tmp/test_tab2)",
"Bash(/tmp/test_all)",
"Bash(/tmp/test_rnd)",
"Bash(/tmp/test_rnd2)",
"Bash(/tmp/test_spc:*)",
"Bash(/tmp/test_spc2:*)",
"Bash(./test_extern:*)",
"Bash(./test_timer:*)",
"Bash(cat:*)",
"Bash(./test_final)",
"Bash(./test_using)",
"Bash(./test_debug:*)",
"Bash(./test_using2)",
"Bash(/tmp/testproj/test:*)"
]
}
}

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*~
basic2c
*.o

951
README.md Normal file
View file

@ -0,0 +1,951 @@
# basic2c
A BASIC-to-C transpiler. Translates BASIC source code into equivalent C source
code with an embedded runtime library.
## Build
```
cc -Wall -o basic2c basic2c.c -lm
```
## Usage
```
basic2c [--release|-r] input.bas [output.c]
```
- If `output.c` is omitted, C code is written to stdout.
- `--release` (or `-r`) selects the release runtime (see [Runtime Modes](#runtime-modes)).
Compile the generated C:
```
cc -Wall -o program output.c -lm
```
## Architecture
The transpiler is a single-file C program with three phases:
1. **Lexer** — tokenizes BASIC source (case-insensitive keywords)
2. **Parser** — recursive descent, builds an AST
3. **Codegen** — walks the AST, emits C source with a small runtime library
## Data Types
| BASIC Type | C Type | Suffix | Notes |
|----------------|------------|--------|------------------------------|
| `BYTE` | `uint8_t` | | Unsigned 8-bit |
| `INTEGER` | `int16_t` | `%` | Signed 16-bit |
| `LONG` | `int32_t` | | Signed 32-bit |
| `FLOAT` | `float` | `!` | Single precision |
| `DOUBLE` | `double` | `#` | Double precision (default numeric) |
| `STRING` | `char*` | `$` | Dynamic, heap-allocated |
Type suffixes on variable names are recognized: `name$` is STRING, `count%` is
INTEGER, `total#` is DOUBLE, `rate!` is FLOAT. Variables without a suffix or
explicit type declaration default to DOUBLE.
Numeric types follow a promotion hierarchy: BYTE < INTEGER < LONG < FLOAT <
DOUBLE. Mixed-type expressions promote to the higher-ranked type.
## Variables and Arrays
### Declaration
```basic
DIM x AS DOUBLE
DIM name AS STRING
DIM count AS INTEGER
```
Variables can also be used without declaration — they are implicitly declared
based on their type suffix or as DOUBLE by default.
### Arrays
```basic
DIM arr(10) AS INTEGER ' 1D array, indices 0..10
DIM matrix(3, 4) AS DOUBLE ' 2D array, indices 0..3 x 0..4
DIM cube(2, 3, 4) AS INTEGER ' 3D array
```
Arrays are zero-based. The dimension value is the upper bound (inclusive), so
`DIM arr(10)` allocates 11 elements (0 through 10).
### REDIM
```basic
REDIM arr(20) AS INTEGER ' Resize array (contents reset to zero)
REDIM matrix(5, 5) AS DOUBLE ' Resize multidimensional array
```
`REDIM` frees the previous allocation and creates a new zero-initialized array.
## Operators
### Arithmetic
| Operator | Description |
|----------|----------------------|
| `+` | Addition |
| `-` | Subtraction / unary negation |
| `*` | Multiplication |
| `/` | Division |
| `\` | Integer division |
| `MOD` | Modulo |
| `^` | Exponentiation |
### Comparison
| Operator | Description |
|----------|----------------------|
| `=` | Equal |
| `<>` | Not equal |
| `<` | Less than |
| `>` | Greater than |
| `<=` | Less than or equal |
| `>=` | Greater than or equal|
### Bitwise / Logical
| Operator | Description |
|----------|----------------------|
| `AND` | Bitwise AND |
| `OR` | Bitwise OR |
| `NOT` | Bitwise NOT |
| `XOR` | Bitwise XOR |
These operators work as both bitwise and logical operators. When used with
comparisons (which return 0 or 1), they behave logically: `x > 5 AND y < 10`.
When used with integers, they operate on individual bits: `15 AND 9` gives `9`.
### String
| Operator | Description |
|----------|----------------------|
| `+` | Concatenation (when operands are strings) |
| `&` | Concatenation (explicit) |
## Control Flow
### IF / THEN / ELSE
Single-line:
```basic
IF x > 0 THEN PRINT "positive" ELSE PRINT "non-positive"
```
Multi-line:
```basic
IF x > 0 THEN
PRINT "positive"
ELSEIF x = 0 THEN
PRINT "zero"
ELSE
PRINT "negative"
END IF
```
### FOR / NEXT
```basic
FOR i = 1 TO 10
PRINT i
NEXT i
FOR i = 10 TO 0 STEP -2
PRINT i
NEXT i
```
### WHILE / WEND
```basic
WHILE x > 0
x = x - 1
WEND
```
### DO / LOOP
```basic
DO
x = x + 1
LOOP UNTIL x >= 10
DO WHILE x < 100
x = x * 2
LOOP
```
### SELECT CASE
```basic
SELECT CASE grade
CASE 90 TO 100
PRINT "A"
CASE 80 TO 89
PRINT "B"
CASE 70 TO 79
PRINT "C"
CASE IS < 60
PRINT "F"
CASE ELSE
PRINT "D"
END SELECT
```
CASE values support single values (`CASE 1`), comma-separated values
(`CASE 1, 2, 3`), ranges (`CASE 5 TO 10`), comparisons (`CASE IS > 100`),
and a default (`CASE ELSE`). Works with both numeric and string expressions.
### EXIT
```basic
EXIT FOR
EXIT WHILE
EXIT DO
EXIT SUB
EXIT FUNCTION
```
### CONTINUE
```basic
CONTINUE FOR
CONTINUE WHILE
CONTINUE DO
```
Skips the rest of the current loop iteration and jumps to the next iteration.
### GOTO
```basic
GOTO 100 ' Jump to line number
GOTO myLabel ' Jump to named label
```
### GOSUB / RETURN
```basic
GOSUB 200
GOSUB myRoutine
' ...
200 PRINT "in subroutine"
RETURN
myRoutine:
PRINT "named routine"
RETURN
```
GOSUB uses a compile-time dispatch mechanism — each GOSUB site gets a unique
return-point ID, and RETURN uses a switch statement to jump back.
### ON GOTO / ON GOSUB
```basic
ON choice GOTO label1, label2, label3
ON choice GOSUB routine1, routine2, routine3
```
Branches to the Nth label based on the expression value (1-based). If the
value is out of range, execution continues at the next statement.
### Labels
Both classic line numbers and named labels are supported:
```basic
10 PRINT "line 10"
20 GOTO 10
myLabel:
PRINT "named label"
GOTO myLabel
```
## Constants
```basic
CONST PI = 3.14159
CONST MAX_SIZE = 100
CONST GREETING$ = "Hello"
```
Constants are evaluated at compile time and substituted directly into
expressions. They cannot be reassigned.
## SWAP
```basic
SWAP a, b
SWAP s1$, s2$
```
Exchanges the values of two variables of the same type.
## Procedures
### SUB
```basic
SUB greet(name AS STRING)
PRINT "Hello, "; name
END SUB
CALL greet("World")
greet "World" ' CALL keyword is optional
```
### FUNCTION
```basic
FUNCTION square(x AS DOUBLE) AS DOUBLE
square = x * x
END FUNCTION
PRINT square(5)
```
Functions return values by assigning to the function name or using `RETURN expr`.
### Parameter Passing
```basic
SUB increment(BYREF x AS INTEGER)
x = x + 1
END SUB
SUB display(BYVAL x AS INTEGER)
PRINT x
END SUB
```
- `BYREF` (default) — passes a pointer; changes affect the caller's variable
- `BYVAL` — passes a copy; changes are local to the procedure
### LOCAL and STATIC
```basic
SUB counter()
STATIC count AS INTEGER
LOCAL temp AS INTEGER
count = count + 1
temp = count
PRINT temp
END SUB
```
- `LOCAL` — declares a variable scoped to the procedure
- `STATIC` — declares a variable that persists across calls
## User-Defined Types
### TYPE / END TYPE
```basic
TYPE PersonRecord
firstName AS STRING * 20
lastName AS STRING * 30
age AS INTEGER
salary AS DOUBLE
END TYPE
DIM person AS PersonRecord
person.firstName = "John"
person.lastName = "Doe"
person.age = 30
person.salary = 55000.50
```
String fields in TYPE definitions require a fixed length (`STRING * N`). Dynamic
strings (`AS STRING` without a length) are not permitted in TYPE definitions
because struct copy would produce dangling pointers.
Supported field types: `BYTE`, `INTEGER`, `LONG`, `FLOAT`, `DOUBLE`,
`STRING * N`, and other user-defined types (nesting).
### Nested UDTs
```basic
TYPE Vec2
x AS DOUBLE
y AS DOUBLE
END TYPE
TYPE Circle
center AS Vec2
radius AS DOUBLE
END TYPE
DIM c AS Circle
c.center.x = 10.0
c.center.y = 20.0
c.radius = 5.0
```
Nesting depth is unlimited. Chained dot-access works for both reads and writes.
### UDT Arrays
```basic
DIM points(10) AS Vec2
points(0).x = 1.5
points(0).y = 2.5
```
### UDT Assignment
Whole-struct copy via assignment:
```basic
DIM a AS Vec2
DIM b AS Vec2
a.x = 1.0
a.y = 2.0
b = a ' Copies all fields
```
Sub-struct copy also works:
```basic
DIM saved AS Vec2
saved = c.center ' Copy nested struct out
c.center = saved ' Copy nested struct in
```
Array element copy:
```basic
circles(0) = circles(2)
```
### SIZEOF
```basic
DIM sz AS LONG
sz = SIZEOF(PersonRecord)
```
Returns the byte size of a user-defined type. Used primarily with random-access
file I/O to specify record length.
## Built-in Functions
### String Functions
| Function | Description |
|-----------------------|------------------------------------------------|
| `LEN(s$)` | Length of string |
| `MID$(s$, start, len)` | Substring (1-based start position) |
| `LEFT$(s$, n)` | First n characters |
| `RIGHT$(s$, n)` | Last n characters |
| `CHR$(n)` | Character from ASCII code |
| `ASC(s$)` | ASCII code of first character |
| `STR$(n)` | Convert number to string |
| `VAL(s$)` | Convert string to number |
| `UCASE$(s$)` | Convert to uppercase |
| `LCASE$(s$)` | Convert to lowercase |
| `INSTR(haystack$, needle$)` | Find substring position (1-based, 0 if not found) |
| `STRING$(n, char$)` | Repeat a character n times |
| `LTRIM$(s$)` | Remove leading spaces |
| `RTRIM$(s$)` | Remove trailing spaces |
| `TRIM$(s$)` | Remove leading and trailing spaces |
| `SPACE$(n)` | String of n spaces |
| `HEX$(n)` | Hexadecimal string representation |
| `OCT$(n)` | Octal string representation |
### MID$ Assignment
```basic
DIM s AS STRING
s = "Hello World"
MID$(s, 7, 5) = "BASIC" ' s is now "Hello BASIC"
```
Replaces characters in a string starting at a 1-based position. The length
parameter limits how many characters are replaced.
### Math Functions
| Function | Description |
|------------|------------------------------------------|
| `ABS(n)` | Absolute value |
| `INT(n)` | Truncate to integer |
| `SQR(n)` | Square root |
| `SIN(n)` | Sine (radians) |
| `COS(n)` | Cosine (radians) |
| `TAN(n)` | Tangent (radians) |
| `ATN(n)` | Arctangent (returns radians) |
| `LOG(n)` | Natural logarithm |
| `EXP(n)` | e raised to the power n |
| `SGN(n)` | Sign: -1, 0, or 1 |
| `RND` | Random number between 0 and 1 |
Numeric expressions also support `^` for exponentiation (emitted as `pow()`).
### Print Formatting Functions
| Function | Description |
|------------|------------------------------------------|
| `TAB(n)` | Output spaces to reach column n |
| `SPC(n)` | Output exactly n spaces |
These functions are used within PRINT statements:
```basic
PRINT "Name"; TAB(20); "Value"
PRINT "A"; SPC(5); "B" ' Outputs "A B"
```
`RND` can be called with or without parentheses, and accepts an optional argument
(which is ignored) for compatibility with other BASIC dialects. Use `RANDOMIZE`
to seed the random number generator:
```basic
RANDOMIZE ' Seed from system clock
RANDOMIZE 12345 ' Seed with specific value
x = RND ' Random double 0..1
x = RND(1) ' Same as RND (argument ignored)
```
### Array Functions
| Function | Description |
|---------------|----------------------------------------------|
| `LBOUND(arr)` | Lower bound of array (always 0) |
| `UBOUND(arr)` | Upper bound of array |
### I/O Functions
| Function | Description |
|--------------|--------------------------------------------------|
| `EOF(n)` | Returns true (-1) if at end of file n |
| `LOF(n)` | Returns byte length of file n |
| `FREEFILE()` | Returns the next available file number |
## Console I/O
### PRINT
```basic
PRINT "Hello, World!"
PRINT "x = "; x
PRINT x; " "; y ' Semicolon suppresses newline between items
PRINT x, y ' Comma advances to next tab stop
PRINT "no newline"; ' Trailing semicolon suppresses final newline
? "shortcut" ' ? is a shortcut for PRINT
```
The `?` character can be used as a shortcut for `PRINT`, for compatibility with
classic BASIC dialects and interactive use.
### PRINT USING
```basic
PRINT USING "###.##"; 123.456 ' Outputs: 123.46
PRINT USING "$$#,###.##"; 1234.56 ' Outputs: $1,234.56
PRINT USING "+###.##"; -45.6 ' Outputs: -45.60
PRINT USING "**###.##"; 9.99 ' Outputs: ****9.99
PRINT USING "!"; "Hello" ' Outputs: H
PRINT USING "&"; "World" ' Outputs: World
PRINT USING "\ \"; "Testing" ' Outputs: Testin (6 chars)
```
Format specifiers for numbers:
| Format | Description |
|--------|-------------|
| `#` | Digit placeholder |
| `.` | Decimal point position |
| `,` | Thousands separator (in format, not output) |
| `+` | Show sign (+ or -) at start |
| `-` | Trailing minus for negative numbers |
| `$$` | Floating dollar sign |
| `**` | Fill leading spaces with asterisks |
Format specifiers for strings:
| Format | Description |
|--------|-------------|
| `!` | First character only |
| `&` | Entire string |
| `\ \` | Fixed width (spaces between backslashes + 2) |
Multiple values can be formatted with one format string:
```basic
PRINT USING "### + ### = ###"; 10; 20; 30
' Outputs: 10 + 20 = 30
```
### INPUT
```basic
INPUT "Enter name: "; name$
INPUT x
```
### LINE INPUT
```basic
LINE INPUT "Enter text: "; line$
```
Reads an entire line including commas and spaces.
## File I/O
### Sequential Files
```basic
' Write
OPEN "data.txt" FOR OUTPUT AS #1
PRINT #1, "Hello"
PRINT #1, 42
CLOSE #1
' Read
OPEN "data.txt" FOR INPUT AS #1
LINE INPUT #1, text$
INPUT #1, value
CLOSE #1
' Append
OPEN "log.txt" FOR APPEND AS #1
PRINT #1, "new entry"
CLOSE #1
```
### WRITE #
```basic
WRITE #1, name$, age, salary
```
Outputs CSV-style: strings are quoted, values are comma-separated, terminated
with a newline.
### Binary Files
```basic
OPEN "file.dat" FOR BINARY AS #1
```
### Random-Access Files
```basic
TYPE Record
name AS STRING * 20
value AS DOUBLE
END TYPE
DIM rec AS Record
rec.name = "test"
rec.value = 3.14
OPEN "data.dat" FOR RANDOM AS #1 LEN = SIZEOF(Record)
PUT #1, 1, rec ' Write record at position 1 (1-based)
GET #1, 1, rec ' Read record at position 1
CLOSE #1
```
Random-access uses `GET` and `PUT` with 1-based record numbers. The `LEN`
clause specifies record size in bytes. Records can be read and written in any
order.
### File Modes
| Mode | C Mode | Description |
|----------|--------|--------------------------------------|
| `INPUT` | `"r"` | Read sequential text |
| `OUTPUT` | `"w"` | Write sequential text (truncates) |
| `APPEND` | `"a"` | Append sequential text |
| `BINARY` | `"rb"` | Binary read |
| `RANDOM` | `"r+b"`| Random access (creates if not found) |
## DATA / READ / RESTORE
```basic
DATA 10, 20, 30, "hello"
DIM x AS INTEGER
DIM s AS STRING
READ x ' x = 10
READ x ' x = 20
READ x ' x = 30
READ s ' s = "hello"
RESTORE ' Reset read pointer to beginning
READ x ' x = 10 again
```
`DATA` statements define a pool of literal values. `READ` consumes them in
order. `RESTORE` resets the read pointer (optionally to a specific line number).
## Comments
```basic
' This is a comment
REM This is also a comment
x = 5 ' Inline comment
```
## $INCLUDE Metacommand
```basic
'$INCLUDE: 'helpers.bas'
```
The `$INCLUDE` metacommand inserts the contents of another file at the point
of the directive, before lexing and parsing. The directive is placed inside a
comment (the leading `'` makes it invisible to editors that don't understand it).
### Syntax
The filename is enclosed in single quotes after `'$INCLUDE:`. The keyword is
case-insensitive. Any amount of whitespace may appear between the colon and the
opening quote.
### Nested Includes
Included files may themselves contain `$INCLUDE` directives:
```basic
' main.bas
'$INCLUDE: 'math_lib.bas'
'$INCLUDE: 'string_lib.bas'
```
```basic
' math_lib.bas — can include further files
'$INCLUDE: 'constants.bas'
FUNCTION Square(x AS DOUBLE) AS DOUBLE
Square = x * x
END FUNCTION
```
### Path Resolution
Filenames are resolved relative to the **including file's directory**, not the
working directory. If `src/main.bas` includes `'lib/util.bas'`, the transpiler
looks for `src/lib/util.bas`.
### Error Reporting
When `$INCLUDE` is used, error messages show the originating file and line:
```
Error (math_lib.bas:12): undeclared variable 'q'
```
Without includes, the format is the same but shows the input filename:
```
Error (main.bas:5): type mismatch
```
### Circular Include Detection
If file A includes file B which includes file A, the transpiler reports a fatal
error rather than looping infinitely:
```
Error: Circular include detected: main.bas
```
## Extensible Functions
The transpiler supports two mechanisms for defining additional functions:
### Built-in Functions (builtins.def)
The `builtins.def` file is compiled into basic2c and provides functions that are
always available. To add permanent built-in functions, edit `builtins.def` and
recompile basic2c.
Default built-ins include:
**Math functions:**
| Function | Description |
|----------|-------------|
| `SQR(n)` | Square root |
| `SIN(n)` | Sine (radians) |
| `COS(n)` | Cosine (radians) |
| `TAN(n)` | Tangent (radians) |
| `ATN(n)` | Arctangent (returns radians) |
| `LOG(n)` | Natural logarithm |
| `EXP(n)` | e raised to power n |
| `SGN(n)` | Sign: -1, 0, or 1 |
| `RND()` | Random number 0 to 1 |
| `CEIL(n)` | Round up to integer |
| `FLOOR(n)` | Round down to integer |
| `ROUND(n)` | Round to nearest integer |
| `FIX(n)` | Truncate toward zero |
| `FRAC(n)` | Fractional part |
| `HYPOT(x, y)` | Hypotenuse (sqrt(x² + y²)) |
| `MAX(a, b)` | Maximum of two values |
| `MIN(a, b)` | Minimum of two values |
**String functions:**
| Function | Description |
|----------|-------------|
| `CHR$(n)` | Character from ASCII code |
| `STR$(n)` | Convert number to string |
| `UCASE$(s)` | Convert to uppercase |
| `LCASE$(s)` | Convert to lowercase |
| `LTRIM$(s)` | Remove leading spaces |
| `RTRIM$(s)` | Remove trailing spaces |
| `TRIM$(s)` | Remove leading and trailing spaces |
| `SPACE$(n)` | String of n spaces |
| `HEX$(n)` | Hexadecimal representation |
| `OCT$(n)` | Octal representation |
| `TAB(n)` | Spaces to reach column n |
| `SPC(n)` | Output n spaces |
| `ENVIRON$(name)` | Get environment variable |
**System:**
| Function | Description |
|----------|-------------|
| `TIMER()` | Seconds since program start |
### External Functions (functions.def)
The `functions.def` file is loaded at runtime from two locations (both if present):
1. The directory containing the `basic2c` binary (global extensions)
2. The directory containing the input `.bas` file (project-specific)
Functions from the input file's directory are loaded second, allowing project-specific
definitions to supplement or override earlier ones.
### Definition Format
Both `builtins.def` and `functions.def` use the same format:
```
# Comment lines start with #
# Format: name : type : c_template
SQUARE : double : ((%) * (%))
CUBE : double : ((%) * (%) * (%))
```
Each line defines:
- **name** — The BASIC function name (case-insensitive)
- **type** — Return type: `byte`, `integer`, `long`, `float`, `double`, or `string`
- **c_template** — C code with argument placeholders
### Argument Placeholders
- `%` or `%1` — First argument
- `%2` — Second argument
- `%3` — Third argument (and so on)
Arguments are substituted directly, so use parentheses in templates to ensure
correct precedence: `((%) * (%2))` not `% * %2`.
### Usage
```basic
PRINT CEIL(3.7) ' Outputs: 4
PRINT MAX(5, 10) ' Outputs: 10
t = TIMER() ' Get elapsed time
PRINT ENVIRON$("HOME") ' Print home directory
```
Extensible functions require parentheses, even with no arguments: `TIMER()` not `TIMER`.
## Runtime Modes
The transpiler supports two runtime modes selected at transpile time:
### Debug Mode (default)
The debug runtime includes error checking and diagnostics:
- NULL guards on string function arguments
- `malloc`/`calloc` failure checks with error messages
- File number bounds checking
- `fopen` failure reporting with filename
- GOSUB stack overflow/underflow detection
- All errors print to stderr and call `exit(1)`
### Release Mode (`--release` or `-r`)
The release runtime strips all diagnostic checks for minimal generated code:
- No NULL guards on string functions
- No malloc failure checks
- No file number bounds checking
- No GOSUB stack overflow/underflow checks
- ~8% fewer lines of generated C code
Functional guards are preserved in release mode to prevent crashes:
- `EOF()` returns true (-1) for NULL file handles (enables file existence checks)
- `LOF()` returns 0 for NULL file handles
- `CLOSE` is a no-op for NULL file handles
- `LINE INPUT` is a no-op for NULL file handles
- Temp string pool management (`_bfree_temps`, `_btmp`)
- String variable management (`_bstr_assign`)
## Limits
| Resource | Maximum |
|------------------------|---------|
| Token length | 4096 |
| Identifier length | 128 |
| Parameters per procedure | 32 |
| Symbol table entries | 2048 |
| GOSUB return sites | 512 |
| Line number labels | 4096 |
| AST nodes | 65536 |
| Arguments per call | 64 |
| User-defined types | 64 |
| Fields per type | 32 |
| Constants | 256 |
| Include nesting depth | 16 |
| Included files | 64 |
| Total source lines | 65536 |
## Example
```basic
TYPE Item
name AS STRING * 20
price AS DOUBLE
END TYPE
DIM items(2) AS Item
items(0).name = "Widget"
items(0).price = 9.99
items(1).name = "Gadget"
items(1).price = 24.95
items(2).name = "Doohickey"
items(2).price = 4.50
DIM i AS INTEGER
DIM total AS DOUBLE
total = 0
FOR i = 0 TO 2
PRINT items(i).name; " $"; items(i).price
total = total + items(i).price
NEXT i
PRINT "Total: $"; total
```
Transpile and run:
```
./basic2c example.bas example.c
cc -Wall -o example example.c -lm
./example
```

5571
basic2c.c Normal file

File diff suppressed because it is too large Load diff

45
builtins.def Normal file
View file

@ -0,0 +1,45 @@
// Built-in function definitions for basic2c
// Format: BUILTIN(name, return_type, c_template)
// This file is #included into basic2c.c with BUILTIN macro defined
//
// Template placeholders: % or %1 = first arg, %2 = second arg, etc.
// Return types: TYPE_BYTE, TYPE_INT, TYPE_LONG, TYPE_FLOAT, TYPE_DBL, TYPE_STR
// Standard math functions
BUILTIN("SQR", TYPE_DBL, "sqrt(%)")
BUILTIN("SIN", TYPE_DBL, "sin(%)")
BUILTIN("COS", TYPE_DBL, "cos(%)")
BUILTIN("TAN", TYPE_DBL, "tan(%)")
BUILTIN("ATN", TYPE_DBL, "atan(%)")
BUILTIN("LOG", TYPE_DBL, "log(%)")
BUILTIN("EXP", TYPE_DBL, "exp(%)")
BUILTIN("SGN", TYPE_INT, "((%) > 0 ? 1 : ((%) < 0 ? -1 : 0))")
BUILTIN("RND", TYPE_DBL, "((double)rand() / (double)RAND_MAX)")
// Extended math functions
BUILTIN("CEIL", TYPE_DBL, "ceil(%)")
BUILTIN("FLOOR", TYPE_DBL, "floor(%)")
BUILTIN("ROUND", TYPE_DBL, "round(%)")
BUILTIN("FRAC", TYPE_DBL, "((%) - floor(%))")
BUILTIN("FIX", TYPE_DBL, "trunc(%)")
BUILTIN("HYPOT", TYPE_DBL, "hypot(%, %2)")
BUILTIN("MAX", TYPE_DBL, "((double)((%) > (%2) ? (%) : (%2)))")
BUILTIN("MIN", TYPE_DBL, "((double)((%) < (%2) ? (%) : (%2)))")
// System/Time
BUILTIN("TIMER", TYPE_DBL, "((double)clock() / CLOCKS_PER_SEC)")
// String functions
BUILTIN("CHR$", TYPE_STR, "_bchr((int)(%))")
BUILTIN("STR$", TYPE_STR, "_bstr_of_int(%)")
BUILTIN("UCASE$", TYPE_STR, "_bucase(%)")
BUILTIN("LCASE$", TYPE_STR, "_blcase(%)")
BUILTIN("LTRIM$", TYPE_STR, "_bltrim(%)")
BUILTIN("RTRIM$", TYPE_STR, "_brtrim(%)")
BUILTIN("TRIM$", TYPE_STR, "_btrim(%)")
BUILTIN("SPACE$", TYPE_STR, "_bspace((int)(%))")
BUILTIN("HEX$", TYPE_STR, "_bhex((int)(%))")
BUILTIN("OCT$", TYPE_STR, "_boct((int)(%))")
BUILTIN("TAB", TYPE_STR, "_btab((int)(%))")
BUILTIN("SPC", TYPE_STR, "_bspace((int)(%))")
BUILTIN("ENVIRON$", TYPE_STR, "_bgetenv(%)")

12
functions.def Normal file
View file

@ -0,0 +1,12 @@
# External function definitions for basic2c
# Format: name : type : c_template
# Types: byte, integer, long, float, double, string
# Template: % or %1 = first arg, %2 = second arg, etc.
#
# Note: Common functions like CEIL, FLOOR, MAX, MIN, TIMER, ENVIRON$
# are now built into basic2c via builtins.def. This file is for
# user-defined extensions that supplement or override the built-ins.
#
# Example custom functions:
# SQUARE : double : ((%) * (%))
# CUBE : double : ((%) * (%) * (%))

202
test.bas Normal file
View file

@ -0,0 +1,202 @@
' ============================================
' Test program for the basic2c transpiler
' Tests all major features
' ============================================
' --- Variable declarations ---
DIM x AS INTEGER
DIM y AS INTEGER
DIM pi AS DOUBLE
DIM greeting AS STRING
DIM name$ AS STRING
' --- Assignments ---
x = 10
y = 20
pi = 3.14159
greeting = "Hello, World!"
name$ = "BASIC"
' --- PRINT with various separators ---
PRINT greeting
PRINT "x = "; x; " y = "; y
PRINT "PI is approximately "; pi
PRINT "Name: "; name$
' --- Arithmetic expressions ---
DIM result AS INTEGER
result = x + y * 2 - 5
PRINT "x + y * 2 - 5 = "; result
DIM quotient AS DOUBLE
quotient = x / 3
PRINT "x / 3 = "; quotient
DIM remainder AS INTEGER
remainder = y MOD 3
PRINT "y MOD 3 = "; remainder
' --- String operations ---
DIM full$ AS STRING
full$ = greeting + " from " + name$
PRINT full$
PRINT "Length of greeting: "; LEN(greeting)
PRINT "First 5 chars: "; LEFT$(greeting, 5)
PRINT "Last 6 chars: "; RIGHT$(greeting, 6)
PRINT "Middle: "; MID$(greeting, 3, 5)
PRINT "Upper: "; UCASE$(name$)
PRINT "Lower: "; LCASE$(greeting)
' --- IF / ELSEIF / ELSE ---
IF x > 15 THEN
PRINT "x is greater than 15"
ELSEIF x > 5 THEN
PRINT "x is between 6 and 15"
ELSE
PRINT "x is 5 or less"
END IF
' --- Single-line IF ---
IF y = 20 THEN PRINT "y is twenty"
' --- FOR loop ---
PRINT "Counting 1 to 5:"
DIM i AS INTEGER
FOR i = 1 TO 5
PRINT i;
NEXT i
PRINT ""
' --- WHILE loop ---
DIM count AS INTEGER
count = 5
PRINT "Countdown:"
WHILE count > 0
PRINT count;
count = count - 1
WEND
PRINT " Go!"
' --- DO LOOP WHILE (bottom test) ---
DIM n AS INTEGER
n = 1
PRINT "DO LOOP WHILE:"
DO
PRINT n;
n = n + 1
LOOP WHILE n <= 5
PRINT ""
' --- DO WHILE LOOP (top test) ---
n = 10
PRINT "DO WHILE LOOP:"
DO WHILE n > 5
PRINT n;
n = n - 1
LOOP
PRINT ""
' --- DO UNTIL ---
n = 1
PRINT "DO UNTIL:"
DO UNTIL n > 5
PRINT n;
n = n + 1
LOOP
PRINT ""
' --- Dynamic arrays ---
DIM arr(10) AS INTEGER
FOR i = 0 TO 10
arr(i) = i * i
NEXT i
PRINT "Array squares:"
FOR i = 0 TO 10
PRINT arr(i);
NEXT i
PRINT ""
' --- FUNCTION with BYVAL ---
FUNCTION Square(BYVAL n AS INTEGER) AS INTEGER
Square = n * n
END FUNCTION
FUNCTION Factorial(BYVAL n AS INTEGER) AS INTEGER
IF n <= 1 THEN
Factorial = 1
ELSE
Factorial = n * Factorial(n - 1)
END IF
END FUNCTION
PRINT "Square(7) = "; Square(7)
PRINT "Factorial(6) = "; Factorial(6)
' --- FUNCTION returning DOUBLE ---
FUNCTION CircleArea(BYVAL radius AS DOUBLE) AS DOUBLE
CircleArea = 3.14159 * radius * radius
END FUNCTION
PRINT "Area of circle r=5: "; CircleArea(5.0)
' --- SUB with BYREF (modifies caller's variables) ---
SUB Swap(BYREF a AS INTEGER, BYREF b AS INTEGER)
LOCAL temp AS INTEGER
temp = a
a = b
b = temp
END SUB
DIM p AS INTEGER
DIM q AS INTEGER
p = 100
q = 200
PRINT "Before swap: p="; p; " q="; q
CALL Swap(p, q)
PRINT "After swap: p="; p; " q="; q
' --- SUB with BYVAL ---
SUB ShowMessage(BYVAL msg AS STRING)
PRINT ">>> "; msg; " <<<"
END SUB
CALL ShowMessage("This is a test message")
' --- STATIC variable in a function ---
FUNCTION Counter() AS INTEGER
STATIC c AS INTEGER
c = c + 1
Counter = c
END FUNCTION
PRINT "Counter: "; Counter()
PRINT "Counter: "; Counter()
PRINT "Counter: "; Counter()
' --- Nested IF ---
DIM score AS INTEGER
score = 85
IF score >= 90 THEN
PRINT "Grade: A"
ELSEIF score >= 80 THEN
PRINT "Grade: B"
ELSEIF score >= 70 THEN
PRINT "Grade: C"
ELSE
PRINT "Grade: F"
END IF
' --- Boolean expressions ---
IF x > 5 AND y < 30 THEN
PRINT "x>5 AND y<30 is TRUE"
END IF
IF x > 100 OR y = 20 THEN
PRINT "x>100 OR y=20 is TRUE"
END IF
IF NOT (x = 5) THEN
PRINT "NOT (x=5) is TRUE"
END IF
PRINT "Done!"

1049
test_big.bas Normal file

File diff suppressed because it is too large Load diff

17
test_classic.bas Normal file
View file

@ -0,0 +1,17 @@
' Classic BASIC with line numbers
' Tests GOTO, GOSUB/RETURN, and line-numbered code
10 DIM x AS INTEGER
20 x = 1
30 PRINT "Start"
40 GOSUB 100
50 PRINT "Back from first gosub, x="; x
60 GOSUB 100
70 PRINT "Back from second gosub, x="; x
80 GOTO 200
90 PRINT "This should not print"
100 x = x + 10
110 PRINT "In subroutine, x="; x
120 RETURN
200 PRINT "After GOTO, x="; x
210 PRINT "Classic BASIC done!"

46
test_continue.bas Normal file
View file

@ -0,0 +1,46 @@
' Test CONTINUE statement
PRINT "==== CONTINUE Test ===="
' CONTINUE FOR — skip even numbers
PRINT "Odd numbers 1-10:"
DIM i AS INTEGER
FOR i = 1 TO 10
IF i MOD 2 = 0 THEN CONTINUE FOR
PRINT i; " ";
NEXT i
PRINT ""
' CONTINUE WHILE — skip multiples of 3
PRINT "Non-multiples of 3 (1-12):"
DIM w AS INTEGER
w = 0
WHILE w < 12
w = w + 1
IF w MOD 3 = 0 THEN CONTINUE WHILE
PRINT w; " ";
WEND
PRINT ""
' CONTINUE DO — skip value 5
PRINT "1-8 without 5:"
DIM d AS INTEGER
d = 0
DO
d = d + 1
IF d = 5 THEN CONTINUE DO
PRINT d; " ";
LOOP UNTIL d >= 8
PRINT ""
' CONTINUE DO with WHILE form
PRINT "DO WHILE skip 3:"
DIM e AS INTEGER
e = 0
DO WHILE e < 6
e = e + 1
IF e = 3 THEN CONTINUE DO
PRINT e; " ";
LOOP
PRINT ""
PRINT "CONTINUE test complete!"

173
test_data.bas Normal file
View file

@ -0,0 +1,173 @@
' ============================================================
' Test program for DATA/READ/RESTORE statements
' All DATA items form a single global pool, read sequentially.
' ============================================================
' All DATA statements (pool order matters)
DATA 10, 20, 30
DATA "Hello", "World", "BASIC"
DATA 42, "Alice", 3.14, "Bob", 100
DATA -5, -10, -3.14
DATA 1.5, 2.7, 3.9, 4.1
PRINT "==== DATA/READ/RESTORE Tests ===="
' ---- Test 1: Simple integer DATA/READ ----
PRINT ""
PRINT "---- Test 1: Integer DATA/READ ----"
DIM a AS INTEGER
DIM b AS INTEGER
DIM c AS INTEGER
READ a, b, c
PRINT "Read: "; a; " "; b; " "; c
' ---- Test 2: String DATA/READ ----
PRINT ""
PRINT "---- Test 2: String DATA/READ ----"
DIM s1$ AS STRING
DIM s2$ AS STRING
DIM s3$ AS STRING
READ s1$, s2$, s3$
PRINT "Read: "; s1$; " "; s2$; " "; s3$
' ---- Test 3: Mixed types ----
PRINT ""
PRINT "---- Test 3: Mixed types ----"
DIM n AS INTEGER
DIM name$ AS STRING
DIM pi AS DOUBLE
DIM name2$ AS STRING
DIM m AS INTEGER
READ n, name$, pi, name2$, m
PRINT "Int: "; n
PRINT "Str: "; name$
PRINT "Dbl: "; pi
PRINT "Str: "; name2$
PRINT "Int: "; m
' ---- Test 4: Negative numbers ----
PRINT ""
PRINT "---- Test 4: Negative numbers ----"
DIM neg1 AS INTEGER
DIM neg2 AS INTEGER
DIM neg3 AS DOUBLE
READ neg1, neg2, neg3
PRINT "Negatives: "; neg1; " "; neg2; " "; neg3
' ---- Test 5: Double values ----
PRINT ""
PRINT "---- Test 5: Double values ----"
DIM f1 AS DOUBLE
DIM f2 AS DOUBLE
DIM f3 AS DOUBLE
DIM f4 AS DOUBLE
READ f1, f2, f3, f4
PRINT "Doubles: "; f1; " "; f2; " "; f3; " "; f4
' ---- Test 6: RESTORE (reset to beginning) ----
PRINT ""
PRINT "---- Test 6: RESTORE ----"
RESTORE
DIM ra AS INTEGER
DIM rb AS INTEGER
DIM rc AS INTEGER
READ ra, rb, rc
PRINT "After RESTORE: "; ra; " "; rb; " "; rc
' ---- Test 7: READ in a loop ----
PRINT ""
PRINT "---- Test 7: READ in loop ----"
RESTORE
DIM val AS INTEGER
DIM i AS INTEGER
PRINT "First 3 via loop: ";
FOR i = 1 TO 3
READ val
PRINT val; " ";
NEXT i
PRINT ""
' ---- Test 8: RESTORE then skip with multiple READs ----
PRINT ""
PRINT "---- Test 8: Skip and read ----"
RESTORE
' Skip first 3 integers
DIM skip AS INTEGER
READ skip, skip, skip
' Now read the 3 strings
DIM rs1$ AS STRING
DIM rs2$ AS STRING
DIM rs3$ AS STRING
READ rs1$, rs2$, rs3$
PRINT "Skipped to strings: "; rs1$; " "; rs2$; " "; rs3$
' ---- Test 9: RESTORE with line number ----
PRINT ""
PRINT "---- Test 9: RESTORE with line number ----"
9000 DATA 111, 222, 333
9010 DATA 444, 555, 666
RESTORE 9010
DIM r1 AS INTEGER
DIM r2 AS INTEGER
DIM r3 AS INTEGER
READ r1, r2, r3
PRINT "RESTORE 9010: "; r1; " "; r2; " "; r3
RESTORE 9000
READ r1, r2, r3
PRINT "RESTORE 9000: "; r1; " "; r2; " "; r3
' ---- Test 10: Typed variables ----
PRINT ""
PRINT "---- Test 10: Typed variables ----"
DATA 200, 1000, 100000, 1.5
RESTORE
' Skip to the typed-variable DATA at the end.
' The pool has: 10,20,30, Hello,World,BASIC, 42,Alice,3.14,Bob,100,
' -5,-10,-3.14, 1.5,2.7,3.9,4.1, 111,222,333, 444,555,666, 200,1000,100000,1.5
' Items 23-26 are 200,1000,100000,1.5
' Use a known restore point instead: just re-read after RESTORE
' Actually, let's just read in order after a targeted DATA
9020 DATA 255, 32000, 100000, 2.5
RESTORE 9020
DIM bval AS BYTE
DIM ival AS INTEGER
DIM lval AS LONG
DIM fval AS FLOAT
READ bval, ival, lval, fval
PRINT "BYTE: "; bval
PRINT "INTEGER: "; ival
PRINT "LONG: "; lval
PRINT "FLOAT: "; fval
' ---- Test 11: Re-read same data ----
PRINT ""
PRINT "---- Test 11: Re-read same data ----"
RESTORE 9020
READ bval, ival, lval, fval
PRINT "Re-read BYTE: "; bval
PRINT "Re-read INTEGER: "; ival
' ---- Test 12: RESTORE with named label ----
PRINT ""
PRINT "---- Test 12: RESTORE with named label ----"
myData: DATA 777, 888, 999
moreData: DATA 50, 60, 70
RESTORE myData
DIM nd1 AS INTEGER
DIM nd2 AS INTEGER
DIM nd3 AS INTEGER
READ nd1, nd2, nd3
PRINT "RESTORE myData: "; nd1; " "; nd2; " "; nd3
RESTORE moreData
READ nd1, nd2, nd3
PRINT "RESTORE moreData: "; nd1; " "; nd2; " "; nd3
' Re-read from named label again
RESTORE myData
READ nd1
PRINT "Re-read first from myData: "; nd1
PRINT ""
PRINT "==== ALL DATA/READ/RESTORE TESTS COMPLETE ===="

190
test_fileio.bas Normal file
View file

@ -0,0 +1,190 @@
' ============================================================
' Test program for File I/O features
' OPEN, CLOSE, PRINT #, INPUT #, LINE INPUT #, WRITE #,
' EOF(), LOF(), FREEFILE()
' ============================================================
PRINT "==== File I/O Tests ===="
' ---- Test 1: Basic OPEN/PRINT #/CLOSE (write a file) ----
PRINT ""
PRINT "---- Test 1: Write file with PRINT # ----"
OPEN "/tmp/basic_test1.txt" FOR OUTPUT AS #1
PRINT #1, "Hello, File World!"
PRINT #1, "Line two"
PRINT #1, "Line three"
CLOSE #1
PRINT "Wrote 3 lines to /tmp/basic_test1.txt"
' ---- Test 2: Read file back with LINE INPUT # ----
PRINT ""
PRINT "---- Test 2: Read file with LINE INPUT # ----"
DIM line$ AS STRING
OPEN "/tmp/basic_test1.txt" FOR INPUT AS #1
DIM lineCount AS INTEGER
lineCount = 0
DO WHILE NOT EOF(1)
LINE INPUT #1, line$
lineCount = lineCount + 1
PRINT "Line "; lineCount; ": "; line$
LOOP
CLOSE #1
PRINT "Read "; lineCount; " lines"
' ---- Test 3: FREEFILE ----
PRINT ""
PRINT "---- Test 3: FREEFILE ----"
DIM f AS INTEGER
f = FREEFILE()
PRINT "First free file number: "; f
OPEN "/tmp/basic_test2.txt" FOR OUTPUT AS #f
PRINT #f, "Written using FREEFILE"
CLOSE #f
PRINT "Wrote using file #"; f
' Read it back to verify
OPEN "/tmp/basic_test2.txt" FOR INPUT AS #1
LINE INPUT #1, line$
CLOSE #1
PRINT "Read back: "; line$
' ---- Test 4: Numeric data with PRINT # and INPUT # ----
PRINT ""
PRINT "---- Test 4: Numeric I/O ----"
DIM x AS DOUBLE
DIM y AS DOUBLE
DIM n AS INTEGER
x = 3.14159
y = 2.71828
n = 42
OPEN "/tmp/basic_test3.txt" FOR OUTPUT AS #1
PRINT #1, x
PRINT #1, y
PRINT #1, n
CLOSE #1
DIM rx AS DOUBLE
DIM ry AS DOUBLE
DIM rn AS INTEGER
OPEN "/tmp/basic_test3.txt" FOR INPUT AS #1
INPUT #1, rx
INPUT #1, ry
INPUT #1, rn
CLOSE #1
PRINT "Read x = "; rx
PRINT "Read y = "; ry
PRINT "Read n = "; rn
' ---- Test 5: WRITE # (CSV-style output) ----
PRINT ""
PRINT "---- Test 5: WRITE # (CSV) ----"
DIM name$ AS STRING
DIM age AS INTEGER
DIM score AS DOUBLE
name$ = "Alice"
age = 30
score = 95.5
OPEN "/tmp/basic_test4.csv" FOR OUTPUT AS #1
WRITE #1, name$, age, score
name$ = "Bob"
age = 25
score = 88.3
WRITE #1, name$, age, score
CLOSE #1
' Read back and display
OPEN "/tmp/basic_test4.csv" FOR INPUT AS #1
DO WHILE NOT EOF(1)
LINE INPUT #1, line$
PRINT "CSV: "; line$
LOOP
CLOSE #1
' ---- Test 6: LOF (file length) ----
PRINT ""
PRINT "---- Test 6: LOF ----"
OPEN "/tmp/basic_test1.txt" FOR INPUT AS #1
DIM fsize AS LONG
fsize = LOF(1)
PRINT "File size of test1.txt: "; fsize; " bytes"
CLOSE #1
' ---- Test 7: APPEND mode ----
PRINT ""
PRINT "---- Test 7: APPEND mode ----"
OPEN "/tmp/basic_test1.txt" FOR APPEND AS #1
PRINT #1, "Appended line four"
PRINT #1, "Appended line five"
CLOSE #1
' Read all lines back
OPEN "/tmp/basic_test1.txt" FOR INPUT AS #1
lineCount = 0
DO WHILE NOT EOF(1)
LINE INPUT #1, line$
lineCount = lineCount + 1
PRINT "Line "; lineCount; ": "; line$
LOOP
CLOSE #1
PRINT "Total lines after append: "; lineCount
' ---- Test 8: PRINT # with semicolons (no newline) ----
PRINT ""
PRINT "---- Test 8: PRINT # with separators ----"
OPEN "/tmp/basic_test5.txt" FOR OUTPUT AS #1
PRINT #1, "A"; "B"; "C"
PRINT #1, 10; " "; 20; " "; 30
CLOSE #1
OPEN "/tmp/basic_test5.txt" FOR INPUT AS #1
DO WHILE NOT EOF(1)
LINE INPUT #1, line$
PRINT " "; line$
LOOP
CLOSE #1
' ---- Test 9: Multiple files open simultaneously ----
PRINT ""
PRINT "---- Test 9: Multiple files ----"
OPEN "/tmp/basic_multi1.txt" FOR OUTPUT AS #1
OPEN "/tmp/basic_multi2.txt" FOR OUTPUT AS #2
PRINT #1, "File one content"
PRINT #2, "File two content"
CLOSE #2
CLOSE #1
OPEN "/tmp/basic_multi1.txt" FOR INPUT AS #1
LINE INPUT #1, line$
PRINT "File 1: "; line$
CLOSE #1
OPEN "/tmp/basic_multi2.txt" FOR INPUT AS #2
LINE INPUT #2, line$
PRINT "File 2: "; line$
CLOSE #2
' ---- Test 10: File I/O in a FUNCTION ----
PRINT ""
PRINT "---- Test 10: File I/O in FUNCTION ----"
FUNCTION CountLines(BYVAL fname AS STRING) AS INTEGER
LOCAL count AS INTEGER
LOCAL buf$ AS STRING
count = 0
OPEN fname FOR INPUT AS #3
DO WHILE NOT EOF(3)
LINE INPUT #3, buf$
count = count + 1
LOOP
CLOSE #3
CountLines = count
END FUNCTION
DIM cnt AS INTEGER
cnt = CountLines("/tmp/basic_test1.txt")
PRINT "CountLines result: "; cnt
PRINT ""
PRINT "==== ALL FILE I/O TESTS COMPLETE ===="

6
test_inc_b.bas Normal file
View file

@ -0,0 +1,6 @@
' Level B - includes C
'$INCLUDE: 'test_inc_c.bas'
SUB FromB()
PRINT "Hello from file B (depth 2)"
END SUB

6
test_inc_c.bas Normal file
View file

@ -0,0 +1,6 @@
' Level C - deepest include
CONST DEPTH_C = 3
SUB FromC()
PRINT "Hello from file C (depth 3)"
END SUB

23
test_include.bas Normal file
View file

@ -0,0 +1,23 @@
' Test $INCLUDE metacommand
PRINT "==== $INCLUDE Test ===="
' Include a library file
'$INCLUDE: 'test_include_lib.bas'
PRINT "Library version: "; LIB_VERSION
' Call functions from included file
LibGreet "World"
PRINT "3 + 4 = "; LibAdd(3, 4)
' Include a file that itself has no further includes
'$INCLUDE: 'test_include_nested.bas'
NestedGreet
' Test nested include chain: this includes B which includes C
'$INCLUDE: 'test_inc_b.bas'
FromB
FromC
PRINT "Depth C constant: "; DEPTH_C
PRINT "Include test complete!"

10
test_include_lib.bas Normal file
View file

@ -0,0 +1,10 @@
' Library file for $INCLUDE testing
CONST LIB_VERSION = 1
SUB LibGreet(name AS STRING)
PRINT "Hello from library, "; name; "!"
END SUB
FUNCTION LibAdd(a AS DOUBLE, b AS DOUBLE) AS DOUBLE
LibAdd = a + b
END FUNCTION

4
test_include_nested.bas Normal file
View file

@ -0,0 +1,4 @@
' Nested include file (no further includes)
SUB NestedGreet()
PRINT "Hello from nested include!"
END SUB

93
test_labels.bas Normal file
View file

@ -0,0 +1,93 @@
' ============================================================
' Test program for named labels with GOTO and GOSUB
' ============================================================
PRINT "==== Named Label Tests ===="
' ---- Test 1: Simple GOTO with named label ----
PRINT ""
PRINT "---- Test 1: GOTO named label ----"
GOTO skipSection
PRINT "ERROR: This should be skipped"
skipSection:
PRINT "Jumped to skipSection"
' ---- Test 2: GOSUB with named label ----
PRINT ""
PRINT "---- Test 2: GOSUB named label ----"
GOSUB mySubroutine
PRINT "Returned from GOSUB"
GOTO afterSub
mySubroutine:
PRINT "Inside mySubroutine"
RETURN
afterSub:
' ---- Test 3: Labels with underscores ----
PRINT ""
PRINT "---- Test 3: Labels with underscores ----"
GOTO my_label_here
PRINT "ERROR: should not print"
my_label_here:
PRINT "Reached my_label_here"
' ---- Test 4: Mixed numeric and named labels ----
PRINT ""
PRINT "---- Test 4: Mixed numeric and named ----"
GOTO 100
PRINT "ERROR: should not print"
100 PRINT "At line 100"
GOTO namedAfter100
PRINT "ERROR: should not print"
namedAfter100:
PRINT "At namedAfter100"
' ---- Test 5: Label with statement on same line ----
PRINT ""
PRINT "---- Test 5: Label with trailing statement ----"
GOTO inlineLabel
PRINT "ERROR: should not print"
inlineLabel: PRINT "Statement on same line as label"
' ---- Test 6: GOSUB to named label, then GOTO past RETURN ----
PRINT ""
PRINT "---- Test 6: GOSUB named + flow control ----"
DIM count AS INTEGER
count = 0
loopStart:
count = count + 1
IF count <= 3 THEN
GOSUB doWork
GOTO loopStart
END IF
PRINT "Loop done, count = "; count
GOTO afterWork
doWork:
PRINT " Working, count = "; count
RETURN
afterWork:
' ---- Test 7: Multiple GOSUBs to different named labels ----
PRINT ""
PRINT "---- Test 7: Multiple named GOSUBs ----"
GOSUB subA
GOSUB subB
GOSUB subA
GOTO afterSubs
subA:
PRINT "In subA"
RETURN
subB:
PRINT "In subB"
RETURN
afterSubs:
PRINT ""
PRINT "==== ALL NAMED LABEL TESTS COMPLETE ===="

176
test_multidim.bas Normal file
View file

@ -0,0 +1,176 @@
' ============================================================
' Test program for multidimensional arrays
' ============================================================
PRINT "==== Multidimensional Array Tests ===="
' ---- Test 1: 2D integer array ----
PRINT ""
PRINT "---- Test 1: 2D integer array ----"
DIM matrix(3, 4) AS INTEGER
DIM i AS INTEGER
DIM j AS INTEGER
' Fill with i*10 + j
FOR i = 0 TO 3
FOR j = 0 TO 4
matrix(i, j) = i * 10 + j
NEXT j
NEXT i
' Read back specific elements
PRINT "matrix(0,0) = "; matrix(0, 0)
PRINT "matrix(1,2) = "; matrix(1, 2)
PRINT "matrix(3,4) = "; matrix(3, 4)
PRINT "matrix(2,3) = "; matrix(2, 3)
' ---- Test 2: 2D string array ----
PRINT ""
PRINT "---- Test 2: 2D string array ----"
DIM grid$(2, 2) AS STRING
grid$(0, 0) = "NW"
grid$(0, 1) = "N"
grid$(0, 2) = "NE"
grid$(1, 0) = "W"
grid$(1, 1) = "C"
grid$(1, 2) = "E"
grid$(2, 0) = "SW"
grid$(2, 1) = "S"
grid$(2, 2) = "SE"
FOR i = 0 TO 2
FOR j = 0 TO 2
PRINT grid$(i, j); " ";
NEXT j
PRINT ""
NEXT i
' ---- Test 3: 3D array ----
PRINT ""
PRINT "---- Test 3: 3D array ----"
DIM cube(2, 3, 4) AS INTEGER
DIM k AS INTEGER
' Fill with i*100 + j*10 + k
FOR i = 0 TO 2
FOR j = 0 TO 3
FOR k = 0 TO 4
cube(i, j, k) = i * 100 + j * 10 + k
NEXT k
NEXT j
NEXT i
PRINT "cube(0,0,0) = "; cube(0, 0, 0)
PRINT "cube(1,2,3) = "; cube(1, 2, 3)
PRINT "cube(2,3,4) = "; cube(2, 3, 4)
PRINT "cube(2,0,1) = "; cube(2, 0, 1)
' ---- Test 4: 2D with expressions ----
PRINT ""
PRINT "---- Test 4: 2D with expressions ----"
DIM r AS INTEGER
DIM c AS INTEGER
r = 5
c = 3
DIM table(r, c) AS DOUBLE
table(1, 1) = 3.14
table(4, 2) = 2.71
table(r, c) = 9.99
PRINT "table(1,1) = "; table(1, 1)
PRINT "table(4,2) = "; table(4, 2)
PRINT "table(5,3) = "; table(r, c)
' ---- Test 5: Loop over 2D array ----
PRINT ""
PRINT "---- Test 5: Loop over 2D ----"
DIM small(2, 3) AS INTEGER
DIM sum AS INTEGER
sum = 0
FOR i = 0 TO 2
FOR j = 0 TO 3
small(i, j) = (i + 1) * (j + 1)
sum = sum + small(i, j)
NEXT j
NEXT i
PRINT "Sum of multiplication table: "; sum
' Print the table
FOR i = 0 TO 2
FOR j = 0 TO 3
PRINT small(i, j); " ";
NEXT j
PRINT ""
NEXT i
' ---- Test 6: REDIM 2D ----
PRINT ""
PRINT "---- Test 6: REDIM 2D ----"
DIM resizable(2, 2) AS INTEGER
resizable(0, 0) = 1
resizable(1, 1) = 5
resizable(2, 2) = 9
PRINT "Before REDIM: "; resizable(1, 1)
REDIM resizable(4, 4) AS INTEGER
PRINT "After REDIM (0,0): "; resizable(0, 0)
resizable(3, 3) = 42
PRINT "After REDIM (3,3): "; resizable(3, 3)
' ---- Test 7: Mixed 1D and 2D ----
PRINT ""
PRINT "---- Test 7: Mixed 1D and 2D ----"
DIM arr1d(5) AS INTEGER
DIM arr2d(3, 3) AS INTEGER
FOR i = 0 TO 5
arr1d(i) = i * 2
NEXT i
FOR i = 0 TO 3
FOR j = 0 TO 3
arr2d(i, j) = arr1d(i) + arr1d(j)
NEXT j
NEXT i
PRINT "arr1d(3) = "; arr1d(3)
PRINT "arr2d(2,3) = "; arr2d(2, 3)
PRINT "arr2d(1,2) = "; arr2d(1, 2)
' ---- Test 8: 2D array in SUB (BYREF) ----
PRINT ""
PRINT "---- Test 8: 2D array element BYREF ----"
DIM val AS INTEGER
val = 100
DIM testArr(3, 3) AS INTEGER
testArr(1, 2) = 50
GOSUB addTen
PRINT "val after GOSUB: "; val
GOTO afterAddTen
addTen:
val = val + 10
RETURN
afterAddTen:
' ---- Test 9: 2D DOUBLE array ----
PRINT ""
PRINT "---- Test 9: 2D DOUBLE array ----"
DIM dbl2d(2, 2) AS DOUBLE
dbl2d(0, 0) = 1.1
dbl2d(0, 1) = 2.2
dbl2d(0, 2) = 3.3
dbl2d(1, 0) = 4.4
dbl2d(1, 1) = 5.5
dbl2d(1, 2) = 6.6
dbl2d(2, 0) = 7.7
dbl2d(2, 1) = 8.8
dbl2d(2, 2) = 9.9
DIM dsum AS DOUBLE
dsum = 0
FOR i = 0 TO 2
FOR j = 0 TO 2
dsum = dsum + dbl2d(i, j)
NEXT j
NEXT i
PRINT "Sum of doubles: "; dsum
PRINT ""
PRINT "==== ALL MULTIDIMENSIONAL TESTS COMPLETE ===="

192
test_newfeatures.bas Normal file
View file

@ -0,0 +1,192 @@
' Test file for all new features
' ============================================================
' === CONST ===
PRINT "==== CONST ===="
CONST PI = 3.14159
CONST MAX_SIZE = 100
CONST GREETING$ = "Hello"
PRINT "PI = "; PI
PRINT "MAX_SIZE = "; MAX_SIZE
PRINT "GREETING$ = "; GREETING$
' === New Math Functions ===
PRINT ""
PRINT "==== Math Functions ===="
PRINT "TAN(0.7854) = "; TAN(0.7854)
PRINT "ATN(1) = "; ATN(1)
PRINT "LOG(2.71828) = "; LOG(2.71828)
PRINT "EXP(1) = "; EXP(1)
PRINT "SGN(-5) = "; SGN(-5)
PRINT "SGN(0) = "; SGN(0)
PRINT "SGN(42) = "; SGN(42)
' === RND / RANDOMIZE ===
PRINT ""
PRINT "==== RND / RANDOMIZE ===="
RANDOMIZE 12345
DIM r AS DOUBLE
r = RND
PRINT "RND (with seed 12345): "; r
r = RND()
PRINT "RND() again: "; r
RANDOMIZE
PRINT "RANDOMIZE (time-based) done"
' === New String Functions ===
PRINT ""
PRINT "==== String Functions ===="
PRINT "LTRIM$(' hello') = '"; LTRIM$(" hello"); "'"
PRINT "RTRIM$('hello ') = '"; RTRIM$("hello "); "'"
PRINT "TRIM$(' hello ') = '"; TRIM$(" hello "); "'"
PRINT "SPACE$(5) = '"; SPACE$(5); "'"
PRINT "HEX$(255) = "; HEX$(255)
PRINT "HEX$(16) = "; HEX$(16)
PRINT "OCT$(8) = "; OCT$(8)
PRINT "OCT$(255) = "; OCT$(255)
PRINT "STRING$(5, '*') = "; STRING$(5, "*")
PRINT "STRING$(3, 'AB') = "; STRING$(3, "AB")
' === SWAP ===
PRINT ""
PRINT "==== SWAP ===="
DIM a AS INTEGER
DIM b AS INTEGER
a = 10
b = 20
PRINT "Before: a="; a; " b="; b
SWAP a, b
PRINT "After: a="; a; " b="; b
DIM s1 AS STRING
DIM s2 AS STRING
s1 = "first"
s2 = "second"
PRINT "Before: s1="; s1; " s2="; s2
SWAP s1, s2
PRINT "After: s1="; s1; " s2="; s2
' === LBOUND / UBOUND ===
PRINT ""
PRINT "==== LBOUND / UBOUND ===="
DIM arr(10) AS INTEGER
PRINT "LBOUND(arr) = "; LBOUND(arr)
PRINT "UBOUND(arr) = "; UBOUND(arr)
' === Bitwise Operators ===
PRINT ""
PRINT "==== Bitwise Operators ===="
DIM x AS INTEGER
DIM y AS INTEGER
x = 15
y = 9
PRINT "15 AND 9 = "; x AND y
PRINT "15 OR 9 = "; x OR y
PRINT "15 XOR 9 = "; x XOR y
PRINT "NOT 0 = "; NOT 0
PRINT "NOT -1 = "; NOT -1
' Logical-style usage (with comparisons)
DIM c AS INTEGER
c = 5
IF c > 3 AND c < 10 THEN
PRINT "5 is between 3 and 10"
END IF
IF c > 10 OR c < 6 THEN
PRINT "5 > 10 OR 5 < 6 is TRUE"
END IF
IF NOT (c = 10) THEN
PRINT "NOT (5 = 10) is TRUE"
END IF
' === SELECT CASE ===
PRINT ""
PRINT "==== SELECT CASE ===="
DIM grade AS INTEGER
grade = 85
SELECT CASE grade
CASE 90 TO 100
PRINT "Grade A"
CASE 80 TO 89
PRINT "Grade B"
CASE 70 TO 79
PRINT "Grade C"
CASE ELSE
PRINT "Grade F"
END SELECT
DIM val AS INTEGER
val = 3
SELECT CASE val
CASE 1
PRINT "One"
CASE 2, 3
PRINT "Two or Three"
CASE IS > 5
PRINT "Greater than 5"
CASE ELSE
PRINT "Something else"
END SELECT
' String SELECT CASE
DIM color AS STRING
color = "red"
SELECT CASE color
CASE "red"
PRINT "Color is RED"
CASE "blue"
PRINT "Color is BLUE"
CASE ELSE
PRINT "Unknown color"
END SELECT
' === ON GOTO ===
PRINT ""
PRINT "==== ON GOTO ===="
DIM choice AS INTEGER
choice = 2
ON choice GOTO opt1, opt2, opt3
GOTO skipOpts
opt1:
PRINT "Option 1"
GOTO skipOpts
opt2:
PRINT "Option 2"
GOTO skipOpts
opt3:
PRINT "Option 3"
skipOpts:
' === ON GOSUB ===
PRINT ""
PRINT "==== ON GOSUB ===="
choice = 1
ON choice GOSUB sub1, sub2, sub3
choice = 3
ON choice GOSUB sub1, sub2, sub3
GOTO skipSubs
sub1:
PRINT "Subroutine 1"
RETURN
sub2:
PRINT "Subroutine 2"
RETURN
sub3:
PRINT "Subroutine 3"
RETURN
skipSubs:
' === MID$ Assignment ===
PRINT ""
PRINT "==== MID$ Assignment ===="
DIM msg AS STRING
msg = "Hello World"
PRINT "Before: "; msg
MID$(msg, 7, 5) = "BASIC"
PRINT "After: "; msg
MID$(msg, 1, 5) = "Howdy"
PRINT "After2: "; msg
PRINT ""
PRINT "All new feature tests complete!"

70
test_redim.bas Normal file
View file

@ -0,0 +1,70 @@
' Test dynamic arrays and REDIM
DIM arr(5) AS INTEGER
DIM i AS INTEGER
' Fill initial array
FOR i = 0 TO 5
arr(i) = i * 10
NEXT i
PRINT "Before REDIM:"
FOR i = 0 TO 5
PRINT arr(i);
NEXT i
PRINT ""
' Resize array larger
REDIM arr(10) AS INTEGER
arr(8) = 88
arr(10) = 100
PRINT "After REDIM:"
FOR i = 0 TO 10
PRINT arr(i);
NEXT i
PRINT ""
' Test string array
DIM names(3) AS STRING
names(0) = "Alice"
names(1) = "Bob"
names(2) = "Charlie"
names(3) = "Diana"
PRINT "Names:"
FOR i = 0 TO 3
PRINT names(i)
NEXT i
' Test FOR with STEP
PRINT "Even numbers 0 to 10:"
FOR i = 0 TO 10 STEP 2
PRINT i;
NEXT i
PRINT ""
' Test negative step
PRINT "Countdown by 2:"
FOR i = 10 TO 0 STEP -2
PRINT i;
NEXT i
PRINT ""
' Test integer division and MOD
DIM a AS INTEGER
DIM b AS INTEGER
a = 17
b = 5
PRINT "17 \ 5 = "; a \ b
PRINT "17 MOD 5 = "; a MOD b
' Test nested loops with EXIT
PRINT "Nested loop with EXIT FOR:"
DIM j AS INTEGER
FOR i = 1 TO 5
FOR j = 1 TO 5
IF j = 3 THEN EXIT FOR
PRINT i * 10 + j;
NEXT j
PRINT "";
NEXT i

338
test_types.bas Normal file
View file

@ -0,0 +1,338 @@
' ============================================================
' Test program for extended data types
' BYTE -> uint8_t, INTEGER -> int16_t, LONG -> int32_t,
' FLOAT -> float, DOUBLE -> double
' ============================================================
' ---- BYTE (uint8_t): range 0 to 255 ----
PRINT "==== BYTE (uint8_t) ===="
DIM b1 AS BYTE
DIM b2 AS BYTE
b1 = 100
b2 = 200
PRINT "100 + 200 (byte) = "; b1 + b2
b1 = 255
PRINT "Max uint8_t = "; b1
b2 = b1 + 1
PRINT "255 + 1 overflows to "; b2
' BYTE arithmetic
b1 = 50
b2 = 30
PRINT "50 - 30 = "; b1 - b2
PRINT "50 * 3 = "; b1 * 3
' BYTE in array
DIM byteArr(3) AS BYTE
DIM bi AS BYTE
FOR bi = 0 TO 3
byteArr(bi) = bi * 10
NEXT bi
PRINT "BYTE array: ";
FOR bi = 0 TO 3
PRINT byteArr(bi); " ";
NEXT bi
PRINT ""
' BYTE + INTEGER promotion
DIM bv AS BYTE
DIM iv AS INTEGER
bv = 100
iv = 1000
PRINT "byte + int16: "; bv + iv
' BYTE function
FUNCTION ByteMax(BYVAL x AS BYTE, BYVAL y AS BYTE) AS BYTE
IF x > y THEN
ByteMax = x
ELSE
ByteMax = y
END IF
END FUNCTION
PRINT "ByteMax(100, 200) = "; ByteMax(100, 200)
' ---- INTEGER (int16_t): range -32768 to 32767 ----
PRINT ""
PRINT "==== INTEGER (int16_t) ===="
DIM i AS INTEGER
DIM j AS INTEGER
i = 100
j = 200
PRINT "100 + 200 = "; i + j
i = 32000
PRINT "32000 as INTEGER = "; i
' Demonstrate int16_t overflow wrapping
i = 32767
PRINT "Max int16_t = "; i
j = i + 1
PRINT "32767 + 1 overflows to "; j
' Integer suffix %
DIM count% AS INTEGER
count% = 42
PRINT "count% = "; count%
' ---- LONG (int32_t): range -2147483648 to 2147483647 ----
PRINT ""
PRINT "==== LONG (int32_t) ===="
DIM a AS LONG
DIM b AS LONG
DIM big AS LONG
a = 100000
b = 200000
PRINT "100000 + 200000 = "; a + b
big = 1000000
big = big * 1000
PRINT "1000000 * 1000 = "; big
' LONG can hold values far beyond int16_t range
big = 32767
big = big * 100
PRINT "32767 * 100 = "; big
' Arithmetic with LONGs
a = 123456
b = 789012
PRINT "123456 + 789012 = "; a + b
PRINT "789012 - 123456 = "; b - a
PRINT "123456 * 2 = "; a * 2
' LONG division and modulo
a = 1000000
b = 7
PRINT "1000000 \\ 7 = "; a \ b
PRINT "1000000 MOD 7 = "; a MOD b
' ---- FLOAT (float: ~7 digits precision) ----
PRINT ""
PRINT "==== FLOAT ===="
DIM f1 AS FLOAT
DIM f2 AS FLOAT
DIM fResult AS FLOAT
f1 = 3.14
f2 = 2.71
PRINT "3.14 + 2.71 = "; f1 + f2
PRINT "3.14 * 2.71 = "; f1 * f2
PRINT "3.14 / 2.71 = "; f1 / f2
' Float suffix !
DIM pi! AS FLOAT
pi! = 3.14159
PRINT "pi! = "; pi!
' Float precision test
f1 = 1.0
f2 = 3.0
fResult = f1 / f2
PRINT "1.0 / 3.0 (float) = "; fResult
f1 = 123456.789
PRINT "123456.789 as float = "; f1
' ---- DOUBLE (double: ~15 digits precision) ----
PRINT ""
PRINT "==== DOUBLE ===="
DIM d1 AS DOUBLE
DIM d2 AS DOUBLE
DIM dResult AS DOUBLE
d1 = 3.14159265358979
d2 = 2.71828182845904
PRINT "pi = "; d1
PRINT "e = "; d2
PRINT "pi + e = "; d1 + d2
PRINT "pi * e = "; d1 * d2
' Double suffix #
DIM exact# AS DOUBLE
exact# = 1.0 / 3.0
PRINT "1/3 (double) = "; exact#
' ---- Type promotion in expressions ----
PRINT ""
PRINT "==== Type Promotion ===="
' INTEGER + LONG -> LONG
DIM si AS INTEGER
DIM sl AS LONG
si = 100
sl = 100000
PRINT "int16 + int32: "; si + sl
' INTEGER + FLOAT -> FLOAT
DIM sf AS FLOAT
sf = 3.14
PRINT "int16 + float: "; si + sf
' INTEGER + DOUBLE -> DOUBLE
DIM sd AS DOUBLE
sd = 3.14159265358979
PRINT "int16 + double: "; si + sd
' LONG + FLOAT -> FLOAT
PRINT "int32 + float: "; sl + sf
' LONG + DOUBLE -> DOUBLE
PRINT "int32 + double: "; sl + sd
' FLOAT + DOUBLE -> DOUBLE
PRINT "float + double: "; sf + sd
' ---- Mixed type functions ----
PRINT ""
PRINT "==== Functions with New Types ===="
FUNCTION AddLongs(BYVAL x AS LONG, BYVAL y AS LONG) AS LONG
AddLongs = x + y
END FUNCTION
FUNCTION MultiplyFloat(BYVAL x AS FLOAT, BYVAL y AS FLOAT) AS FLOAT
MultiplyFloat = x * y
END FUNCTION
FUNCTION LongFactorial(BYVAL n AS LONG) AS LONG
IF n <= 1 THEN
LongFactorial = 1
ELSE
LongFactorial = n * LongFactorial(n - 1)
END IF
END FUNCTION
FUNCTION FloatSqrt(BYVAL x AS FLOAT) AS FLOAT
FloatSqrt = SQR(x)
END FUNCTION
PRINT "AddLongs(100000, 200000) = "; AddLongs(100000, 200000)
PRINT "MultiplyFloat(3.14, 2.0) = "; MultiplyFloat(3.14, 2.0)
PRINT "LongFactorial(12) = "; LongFactorial(12)
PRINT "FloatSqrt(2.0) = "; FloatSqrt(2.0)
' ---- BYREF with new types ----
PRINT ""
PRINT "==== BYREF with New Types ===="
SUB SwapLongs(BYREF x AS LONG, BYREF y AS LONG)
LOCAL t AS LONG
t = x
x = y
y = t
END SUB
SUB ScaleFloat(BYREF val AS FLOAT, BYVAL factor AS FLOAT)
val = val * factor
END SUB
DIM lx AS LONG
DIM ly AS LONG
lx = 100000
ly = 999999
PRINT "Before SwapLongs: "; lx; " "; ly
CALL SwapLongs(lx, ly)
PRINT "After SwapLongs: "; lx; " "; ly
DIM fv AS FLOAT
fv = 2.5
PRINT "Before ScaleFloat: "; fv
CALL ScaleFloat(fv, 4.0)
PRINT "After ScaleFloat(2.5, 4.0): "; fv
' ---- Arrays with new types ----
PRINT ""
PRINT "==== Arrays with New Types ===="
DIM longArr(5) AS LONG
DIM fltArr(5) AS FLOAT
DIM idx AS INTEGER
FOR idx = 0 TO 5
longArr(idx) = (idx + 1) * 100000
fltArr(idx) = (idx + 1) * 1.5
NEXT idx
PRINT "LONG array: ";
FOR idx = 0 TO 5
PRINT longArr(idx); " ";
NEXT idx
PRINT ""
PRINT "FLOAT array: ";
FOR idx = 0 TO 5
PRINT fltArr(idx); " ";
NEXT idx
PRINT ""
' ---- STATIC with new types ----
PRINT ""
PRINT "==== STATIC with New Types ===="
FUNCTION LongCounter() AS LONG
STATIC lc AS LONG
lc = lc + 100000
LongCounter = lc
END FUNCTION
FUNCTION FloatAccum(BYVAL v AS FLOAT) AS FLOAT
STATIC fs AS FLOAT
fs = fs + v
FloatAccum = fs
END FUNCTION
PRINT "LongCounter: ";
FOR idx = 1 TO 5
PRINT LongCounter(); " ";
NEXT idx
PRINT ""
PRINT "FloatAccum: ";
DIM ftemp AS FLOAT
ftemp = FloatAccum(1.1)
PRINT ftemp; " ";
ftemp = FloatAccum(2.2)
PRINT ftemp; " ";
ftemp = FloatAccum(3.3)
PRINT ftemp; " ";
PRINT ""
' ---- Mixing classic line numbers with new types ----
PRINT ""
PRINT "==== Classic Lines with New Types ===="
DIM counter AS LONG
counter = 0
2000 counter = counter + 50000
2010 IF counter >= 250000 THEN GOTO 2030
2020 GOTO 2000
2030 PRINT "LONG counter reached: "; counter
' ---- Range demonstration ----
PRINT ""
PRINT "==== Type Ranges ===="
DIM maxInt AS INTEGER
DIM maxLong AS LONG
maxInt = 32767
maxLong = 2147483647
PRINT "Max INTEGER (int16_t): "; maxInt
PRINT "Max LONG (int32_t): "; maxLong
' Show that LONG can handle what overflows INTEGER
DIM intVal AS INTEGER
DIM longVal AS LONG
intVal = 200
longVal = 200
intVal = intVal * intVal
longVal = longVal * longVal
PRINT "200 * 200 as INTEGER: "; intVal
PRINT "200 * 200 as LONG: "; longVal
PRINT ""
PRINT "==== ALL TYPE TESTS COMPLETE ===="

361
test_udt.bas Normal file
View file

@ -0,0 +1,361 @@
' ============================================================
' Test program for user-defined types and random-access file I/O
' ============================================================
PRINT "==== User-Defined Type Tests ===="
' ---- Test 1: Basic TYPE definition and field access ----
PRINT ""
PRINT "---- Test 1: Basic TYPE and field access ----"
TYPE PersonRecord
firstName AS STRING * 20
lastName AS STRING * 30
age AS INTEGER
salary AS DOUBLE
END TYPE
DIM person AS PersonRecord
person.firstName = "John"
person.lastName = "Doe"
person.age = 30
person.salary = 55000.50
PRINT "Name: "; person.firstName; " "; person.lastName
PRINT "Age: "; person.age
PRINT "Salary: "; person.salary
' ---- Test 2: Multiple TYPE definitions ----
PRINT ""
PRINT "---- Test 2: Multiple TYPE definitions ----"
TYPE Point2D
x AS DOUBLE
y AS DOUBLE
END TYPE
TYPE Rectangle
width AS DOUBLE
height AS DOUBLE
END TYPE
DIM pt AS Point2D
pt.x = 3.0
pt.y = 4.0
PRINT "Point: ("; pt.x; ", "; pt.y; ")"
DIM rect AS Rectangle
rect.width = 10.5
rect.height = 20.3
PRINT "Rect: "; rect.width; " x "; rect.height
' ---- Test 3: UDT with integer fields ----
PRINT ""
PRINT "---- Test 3: UDT integer fields ----"
TYPE Color
r AS INTEGER
g AS INTEGER
b AS INTEGER
END TYPE
DIM c AS Color
c.r = 255
c.g = 128
c.b = 64
PRINT "Color: ("; c.r; ", "; c.g; ", "; c.b; ")"
' ---- Test 4: Array of UDTs ----
PRINT ""
PRINT "---- Test 4: Array of UDTs ----"
DIM points(3) AS Point2D
DIM i AS INTEGER
FOR i = 0 TO 3
points(i).x = i * 1.5
points(i).y = i * 2.5
NEXT i
FOR i = 0 TO 3
PRINT "points("; i; "): ("; points(i).x; ", "; points(i).y; ")"
NEXT i
' ---- Test 5: UDT field expressions ----
PRINT ""
PRINT "---- Test 5: UDT field expressions ----"
DIM p1 AS Point2D
DIM p2 AS Point2D
p1.x = 1.0
p1.y = 2.0
p2.x = 4.0
p2.y = 6.0
DIM dist AS DOUBLE
dist = SQR((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y))
PRINT "Distance: "; dist
' ---- Test 6: SIZEOF ----
PRINT ""
PRINT "---- Test 6: SIZEOF ----"
DIM szPerson AS LONG
szPerson = SIZEOF(PersonRecord)
PRINT "SIZEOF(PersonRecord) = "; szPerson
DIM szPoint AS LONG
szPoint = SIZEOF(Point2D)
PRINT "SIZEOF(Point2D) = "; szPoint
DIM szColor AS LONG
szColor = SIZEOF(Color)
PRINT "SIZEOF(Color) = "; szColor
' ---- Test 7: Random-access file I/O ----
PRINT ""
PRINT "---- Test 7: Random-access file I/O ----"
TYPE Item
name AS STRING * 16
price AS DOUBLE
qty AS INTEGER
END TYPE
DIM item1 AS Item
DIM item2 AS Item
DIM item3 AS Item
item1.name = "Widget"
item1.price = 9.99
item1.qty = 100
item2.name = "Gadget"
item2.price = 24.95
item2.qty = 50
item3.name = "Doohickey"
item3.price = 4.50
item3.qty = 200
' Write records
OPEN "/tmp/test_items.dat" FOR RANDOM AS #1 LEN = SIZEOF(Item)
PUT #1, 1, item1
PUT #1, 2, item2
PUT #1, 3, item3
CLOSE #1
' Read records back in different order
DIM loaded AS Item
OPEN "/tmp/test_items.dat" FOR RANDOM AS #1 LEN = SIZEOF(Item)
GET #1, 2, loaded
PRINT "Record 2: "; loaded.name; " $"; loaded.price; " qty="; loaded.qty
GET #1, 1, loaded
PRINT "Record 1: "; loaded.name; " $"; loaded.price; " qty="; loaded.qty
GET #1, 3, loaded
PRINT "Record 3: "; loaded.name; " $"; loaded.price; " qty="; loaded.qty
CLOSE #1
' ---- Test 8: Overwrite and re-read record ----
PRINT ""
PRINT "---- Test 8: Overwrite and re-read ----"
DIM updated AS Item
updated.name = "NewWidget"
updated.price = 19.99
updated.qty = 75
OPEN "/tmp/test_items.dat" FOR RANDOM AS #1 LEN = SIZEOF(Item)
PUT #1, 1, updated
GET #1, 1, loaded
PRINT "Updated record 1: "; loaded.name; " $"; loaded.price; " qty="; loaded.qty
' Verify record 3 is unchanged
GET #1, 3, loaded
PRINT "Record 3 unchanged: "; loaded.name; " $"; loaded.price; " qty="; loaded.qty
CLOSE #1
' ---- Test 9: UDT with mixed field types ----
PRINT ""
PRINT "---- Test 9: Mixed field types ----"
TYPE MixedRecord
label AS STRING * 10
byteVal AS BYTE
intVal AS INTEGER
longVal AS LONG
floatVal AS FLOAT
dblVal AS DOUBLE
END TYPE
DIM m AS MixedRecord
m.label = "TestMixed"
m.byteVal = 42
m.intVal = -1000
m.longVal = 100000
m.floatVal = 3.14
m.dblVal = 2.71828
PRINT "Label: "; m.label
PRINT "Byte: "; m.byteVal
PRINT "Int: "; m.intVal
PRINT "Long: "; m.longVal
PRINT "Double: "; m.dblVal
' ---- Test 10: Nested UDTs ----
PRINT ""
PRINT "---- Test 10: Nested UDTs ----"
TYPE Vec2
x AS DOUBLE
y AS DOUBLE
END TYPE
TYPE Circle
center AS Vec2
radius AS DOUBLE
END TYPE
DIM circ AS Circle
circ.center.x = 10.0
circ.center.y = 20.0
circ.radius = 5.5
PRINT "Circle center: ("; circ.center.x; ", "; circ.center.y; ")"
PRINT "Circle radius: "; circ.radius
' ---- Test 11: Nested UDT read in expressions ----
PRINT ""
PRINT "---- Test 11: Nested UDT expressions ----"
DIM circ2 AS Circle
circ2.center.x = 30.0
circ2.center.y = 40.0
circ2.radius = 2.0
DIM dx AS DOUBLE
DIM dy AS DOUBLE
dx = circ2.center.x - circ.center.x
dy = circ2.center.y - circ.center.y
PRINT "dx = "; dx
PRINT "dy = "; dy
' ---- Test 12: UDT whole-struct copy ----
PRINT ""
PRINT "---- Test 12: UDT copy ----"
DIM orig AS Vec2
orig.x = 42.0
orig.y = 99.0
DIM copy AS Vec2
copy = orig
PRINT "copy.x = "; copy.x
PRINT "copy.y = "; copy.y
' Modify original, verify copy is independent
orig.x = 0.0
PRINT "After modify orig, copy.x = "; copy.x
' ---- Test 13: Nested UDT sub-struct copy ----
PRINT ""
PRINT "---- Test 13: Nested UDT sub-struct copy ----"
DIM savedCenter AS Vec2
savedCenter = circ.center
PRINT "savedCenter: ("; savedCenter.x; ", "; savedCenter.y; ")"
' Assign sub-struct to nested field
DIM circ3 AS Circle
DIM newCenter AS Vec2
newCenter.x = 77.0
newCenter.y = 88.0
circ3.center = newCenter
circ3.radius = 3.0
PRINT "circ3 center: ("; circ3.center.x; ", "; circ3.center.y; ")"
PRINT "circ3 radius: "; circ3.radius
' ---- Test 14: Array of UDTs with nested types ----
PRINT ""
PRINT "---- Test 14: Array of nested UDTs ----"
DIM circles(2) AS Circle
circles(0).center.x = 1.0
circles(0).center.y = 2.0
circles(0).radius = 10.0
circles(1).center.x = 3.0
circles(1).center.y = 4.0
circles(1).radius = 20.0
circles(2).center.x = 5.0
circles(2).center.y = 6.0
circles(2).radius = 30.0
DIM ci AS INTEGER
FOR ci = 0 TO 2
PRINT "circles("; ci; "): ("; circles(ci).center.x; ", "; circles(ci).center.y; ") r="; circles(ci).radius
NEXT ci
' ---- Test 15: Copy between array elements ----
PRINT ""
PRINT "---- Test 15: Array element copy ----"
circles(0) = circles(2)
PRINT "After copy, circles(0).center.x = "; circles(0).center.x
PRINT "After copy, circles(0).radius = "; circles(0).radius
' ---- Test 16: Three-level nesting ----
PRINT ""
PRINT "---- Test 16: Three-level nesting ----"
TYPE LineSegment
start AS Vec2
finish AS Vec2
END TYPE
TYPE Shape
outline AS LineSegment
label AS STRING * 16
END TYPE
DIM shape AS Shape
shape.outline.start.x = 1.0
shape.outline.start.y = 2.0
shape.outline.finish.x = 10.0
shape.outline.finish.y = 20.0
shape.label = "MyLine"
PRINT "Shape: "; shape.label
PRINT " from ("; shape.outline.start.x; ", "; shape.outline.start.y; ")"
PRINT " to ("; shape.outline.finish.x; ", "; shape.outline.finish.y; ")"
' ---- Test 17: Nested UDT file I/O ----
PRINT ""
PRINT "---- Test 17: Nested UDT file I/O ----"
DIM c1 AS Circle
DIM c2 AS Circle
c1.center.x = 111.0
c1.center.y = 222.0
c1.radius = 333.0
c2.center.x = 444.0
c2.center.y = 555.0
c2.radius = 666.0
OPEN "/tmp/test_circles.dat" FOR RANDOM AS #1 LEN = SIZEOF(Circle)
PUT #1, 1, c1
PUT #1, 2, c2
CLOSE #1
DIM ld AS Circle
OPEN "/tmp/test_circles.dat" FOR RANDOM AS #1 LEN = SIZEOF(Circle)
GET #1, 2, ld
PRINT "Loaded circle 2: ("; ld.center.x; ", "; ld.center.y; ") r="; ld.radius
GET #1, 1, ld
PRINT "Loaded circle 1: ("; ld.center.x; ", "; ld.center.y; ") r="; ld.radius
CLOSE #1
PRINT ""
PRINT "==== ALL UDT TESTS COMPLETE ===="