Skip to content

RDMachinery/Commodore64ToJavaByteCodeCompiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 

Repository files navigation

C64 BASIC Compiler

A complete Commodore 64 BASIC to JVM bytecode compiler written in Java by Mario Gianota.

Compiles C64 BASIC source programs (.bas) into standard Java .class files that run on any JVM. Every compiled program is a single self-contained .class file with no external runtime dependency — no classpath configuration, no JAR bundling, no library files to distribute.

java BasicC fizzbuzz.bas
java Fizzbuzz

Language coverage

The compiler implements the complete Commodore 64 BASIC V2 specification.

Variables and types

Type Syntax JVM mapping
Double-precision numeric A, BC, X1 static double field
Integer numeric A%, I% static int field
String A$, NAME$ static String field
1-D numeric array DIM A(10) static double[] field
1-D integer array DIM A%(10) static int[] field
1-D string array DIM N$(20) static String[] field
2-D numeric array DIM A(5,5) static double[][] field

All variables are global — C64 BASIC semantics faithfully preserved.

Statements

Statement Description
LET var = expr Assignment (LET is optional)
PRINT expr [; expr] [, expr] Output — ; suppresses newline, , tabs to next zone
PRINT# n, expr Write to logical file n
INPUT [prompt;] var Read from keyboard
INPUT# n, var Read from logical file n
GET var$ Read single character without Enter
IF expr THEN stmt Conditional — expr, line number, or statement
GOTO n Unconditional jump to line n
GOSUB n / RETURN Subroutine call and return
ON expr GOTO n1, n2, ... Computed goto
ON expr GOSUB n1, n2, ... Computed gosub
FOR var = start TO limit [STEP s] Loop initialisation
NEXT [var] Loop increment and test
DIM var(n) / DIM var(m,n) Array declaration
DATA val, val, ... Compile-time data pool
READ var [, var ...] Read next DATA value
RESTORE Reset DATA read pointer
DEF FN name(param) = expr Define single-argument function
OPEN n, device, channel, "filename" Open logical file
CLOSE n Close logical file
CMD n Redirect PRINT output to logical file n
POKE addr, val Write byte to 64KB memory map
SYS addr Call machine code address
REM text Comment
END / STOP Terminate program

Functions

Mathematical: ABS ATN COS EXP INT LOG RND SGN SIN SQR TAN

String: ASC CHR$ LEFT$ LEN MID$ RIGHT$ STR$ VAL

Memory: PEEK(addr) — reads byte from 64KB memory map as unsigned double (0–255)

User-defined: FN name(expr) — calls a function defined with DEF FN

File I/O filename convention

OPEN 1, 8, 1, "DATA.TXT,S,W"   ' sequential write (create/overwrite)
OPEN 1, 8, 1, "DATA.TXT,S,A"   ' sequential append
OPEN 2, 8, 2, "DATA.TXT,S,R"   ' sequential read
OPEN 2, 8, 2, "DATA.TXT"       ' sequential read (default)

PEEK / POKE / SYS

The compiler maintains a 65,536-byte memory array simulating the C64 address space. Special addresses trigger side effects on the terminal:

Address Effect
POKE 53281, n Set screen background colour (ANSI escape)
POKE 53280, n Set border colour (ANSI background)
POKE 646, n Set text/cursor colour (ANSI foreground)
SYS 64738 Cold reset → System.exit(0)
SYS 58634 Clear screen → ANSI ESC[2J

Architecture

The compiler is a two-pass design targeting the Jasmin Java assembler as its backend.

BASIC source (.bas)
        │
        ▼
    ┌───────┐
    │  Lexer │  Case-insensitive tokeniser. Handles REM, string literals,
    └───────┘  string-function keywords (CHR$, LEFT$...), two-char operators.
        │  List<Token>
        ▼
    ┌────────┐
    │ Parser │  Pass 1 — one forward scan builds six tables:
    └────────┘
        │         LineNumberTable  — every line number → its token sequence
        │         VariableTable    — all scalar names and types
        │         ArrayTable       — all array names, dimensions, element types
        │         DataTable        — all DATA values in program order
        │         FnTable          — all DEF FN definitions
        │         GosubTable       — all GOSUB target line numbers
        ▼
    ┌─────────────────────┐
    │ JasminCodeGenerator │  Pass 2 — generates Jasmin assembly (.j file)
    └─────────────────────┘
        │  .j file
        ▼
    ┌──────────────────┐
    │  Jasmin assembler │  External tool (jasmin.jar)
    └──────────────────┘
        │  .class file
        ▼
    java ClassName

Why two passes?

Pass 1 sees the entire program before a single byte of output is generated. This eliminates forward-reference problems entirely — GOTO 500 on line 10 resolves cleanly even if line 500 comes later, because the complete LineNumberTable exists before code generation begins.

Generated class structure

ClassName.class
├── static double/int/String fields    (one per scalar variable)
├── static array fields                (one per DIM'd array)
├── static byte[]    __MEM             (65,536-byte C64 memory map)
├── static PrintWriter[]  __FILE_OUT   (file output table, 16 slots)
├── static BufferedReader[] __FILE_IN  (file input table, 16 slots)
├── static int       __CMD_FILE        (CMD redirect target, -1 = screen)
├── static String[]  __DATA            (DATA pool, if DATA statements present)
├── static int       __DATA_PTR        (DATA read pointer)
│
├── <clinit>()V          initialises all arrays, memory map, DATA pool
├── main(String[])V      compiled BASIC program body
├── sub_N()V             one method per GOSUB target line number
├── fn_X(D)D             one method per DEF FN definition
│
└── bas_*()              runtime library (30+ private static methods)
     bas_print_d / bas_print_s / bas_println   (CMD-aware)
     bas_print_comma / bas_println_file
     bas_input_d / bas_input_s
     bas_get_s
     bas_open / bas_close / bas_cmd
     bas_print_file_s / bas_print_file_d / bas_println_file
     bas_input_file_s / bas_input_file_d
     bas_read_d / bas_read_s
     bas_abs / bas_atn / bas_cos / bas_exp / bas_int
     bas_log / bas_rnd / bas_sgn / bas_sin / bas_sqr / bas_tan
     bas_len / bas_asc / bas_chr / bas_left / bas_right
     bas_mid / bas_str / bas_val / bas_not
     bas_peek / bas_poke / bas_sys / bas_c64_colour

Performance note

Every BASIC program compiles into a single main method. The JVM JIT compiler treats this as one hot compilation unit and applies its most aggressive optimisations across the entire program. There is no object allocation, no garbage collection pressure, no virtual dispatch — all variable access is getstatic/putstatic on static fields and all calls are invokestatic. Compiled C64 BASIC programs may run faster than equivalent Java spread across many methods and objects.


Source files

File Lines Purpose
TokenType.java ~90 Enum of every token category
Token.java ~110 Immutable token value object
Lexer.java ~380 Tokeniser
Tables.java ~420 Six Pass 1 data structures
Parser.java ~320 Pass 1 — builds all six tables
ExpressionParser.java ~380 Recursive descent expression parser
RuntimeLibrary.java ~1000 Jasmin source templates for all bas_* methods
JasminCodeGenerator.java ~1700 Pass 2 — generates Jasmin assembly
BasicCompiler.java ~130 Coordinates all stages
BasicC.java ~190 Command-line driver
CompilerDemo.java ~250 Test suite — 25 programs

Requirements


Installation

git clone https://github.com/yourusername/c64-basic-compiler.git
cd c64-basic-compiler
javac *.java

Place jasmin.jar in the same directory, or set the JASMIN_JAR environment variable.


Usage

Command-line driver

java BasicC program.bas
java Program

The class name is derived from the filename — hello-world.bas becomes HelloWorld.

Flags:

java BasicC <file.bas> [--jasmin path/to/jasmin.jar] [--keep-j]

  --jasmin <path>   Path to jasmin.jar
  --keep-j          Retain the .j intermediate file after assembly

Jasmin is located automatically:

  1. --jasmin flag
  2. JASMIN_JAR environment variable
  3. jasmin.jar in the current directory
  4. lib/jasmin.jar or ~/bin/jasmin.jar

Manual pipeline

java CompilerDemo          # compile all 25 test programs to .j files
java -jar jasmin.jar Test5FizzBuzz.j
java Test5FizzBuzz

Example programs

Hello World

10 PRINT "HELLO, WORLD!"
20 END

FizzBuzz

10 FOR I = 1 TO 100
20 IF I/3 = INT(I/3) AND I/5 = INT(I/5) THEN PRINT "FIZZBUZZ" : GOTO 60
30 IF I/3 = INT(I/3) THEN PRINT "FIZZ" : GOTO 60
40 IF I/5 = INT(I/5) THEN PRINT "BUZZ" : GOTO 60
50 PRINT I
60 NEXT I

Bubble sort

10 DIM A(4)
20 A(0)=5 : A(1)=3 : A(2)=8 : A(3)=1 : A(4)=4
30 FOR I = 0 TO 3
40 FOR J = 0 TO 3 - I
50 IF A(J) <= A(J+1) THEN GOTO 80
60 LET T = A(J) : A(J) = A(J+1) : A(J+1) = T
80 NEXT J
90 NEXT I
100 FOR I = 0 TO 4 : PRINT A(I) : NEXT I

DATA and READ

10 DATA "MERCURY","VENUS","EARTH","MARS","JUPITER"
20 FOR I = 1 TO 5
30 READ P$
40 PRINT P$
50 NEXT I

User-defined functions

10 DEF FN SQ(X) = X * X
20 DEF FN CUBE(X) = X * X * X
30 FOR I = 1 TO 5
40 PRINT FN SQ(I), FN CUBE(I)
50 NEXT I

File I/O — write and read back

10 OPEN 1, 8, 1, "PLANETS.TXT,S,W"
20 DATA "MERCURY","VENUS","EARTH","MARS"
30 FOR I = 1 TO 4
40 READ P$
50 PRINT# 1, P$
60 NEXT I
70 CLOSE 1
80 OPEN 2, 8, 2, "PLANETS.TXT,S,R"
90 FOR I = 1 TO 4
100 INPUT# 2, P$
110 PRINT P$
120 NEXT I
130 CLOSE 2

ON GOSUB dispatch table

10 FOR X = 1 TO 3
20 ON X GOSUB 100, 200, 300
30 NEXT X
40 END
100 PRINT "ONE" : RETURN
200 PRINT "TWO" : RETURN
300 PRINT "THREE" : RETURN

Licence

Copyright © 2024–2026 Mario Gianota. All rights reserved. Contact: mariogianota@protonmail.com

Non-commercial use is free, provided this copyright notice and the following attribution are preserved in all copies and derivative works:

Original author: Mario Gianota (mariogianota@protonmail.com)

Commercial use requires a written licence agreement. No fee is payable until annual gross revenue attributable to this software exceeds USD $10,000.

See LICENCE for the complete terms and conditions.

About

A Commodore 64 to Java ByteCode compiler system.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors