Skip to content


Folders and files

Last commit message
Last commit date

Latest commit



45 Commits

Repository files navigation


C64 BASIC, with some lil’ conveniences: no line numbers required, and some other helpful transformations. This tool converts .ebas files to standard C64 BASIC as a .bas file.

What is an .ebas file? It’s a thing I made up, ‘cause I am making a game which requires me to write a lot of C64 BASIC, and I got annoyed at re-numbering things. Then I got carried away adding more features. Unless you are me, you likely will have no use for this script.

Here’s an example of some “eBASIC”:

# Position sprites
for n=4 to 9:
  poke v+n,n*0x14:

# Print some text
print "{clear}"
 poke 0x286,n:
 print "coding is basic ...  ";
 goto ~top

Running ebas2bas generates BASIC:

10 for n=4 to 9:poke v+n, n*20:next
20 print "{clear}"
30 poke 646,n:n=n+1
40 print "coding is basic ...  ";
50 goto 30


node ebas2bas.js <filename>

You can omit the .ebas extension. The output will be the the same name, but with a .bas extension.


Line numbers & labels

print "i am the best ";
goto ~top

compiles to:

10 print "i am the best ";
20 goto 10

Support for hex and binary numbers

Converts hex and binary numbers to decimal

0x1f -> 31
0b111 -> 7
0b1000_0001 -> 129


Comments with “#” are removed from the source code (You can still use REM comments if you want them to stay).

poke 0xD020,1 # Make border white -> poke 53280,1

Multi line source

Most BASIC programs jam many statements onto a single line. To keep the source readable, you can use multiple lines - then any ending with “:” will be concatenated.

for n=0 to 62 :
  read q :
  poke 0x340+n,q :

compiles to:

10 for n=0 to 62 :read q :poke 832+n,q :next

Include files

External files can be included into the current source with {{ "./path/to/file" }}. The contents of the file will be dumped in the first pass, so all further transformations (hex, macros, etc) will be applied to the included contents. Include files can also contain other includes (with no circular dependency testing, so becareful!).

As the include file is dumped directly, it can also be assembly code, or macros. If it’s assembly, remember to wrap the include tag in an inline-assembler tag (see section inline assembler) if needed. Eg: {{! {{ "./test.asm" }} !}}.


Macros are javascript functions that return BASIC source code that is injected in the final listing. If the return value is an array, multiple lines will be injected. To define a macro, wrap a javascript function in {! and !} tags:

{! rainbow = (str) => {
  const cols = COLS.slice(1); // COLS = Magic array of color tokens
  return str
    .map((ch,i) => "{" + cols[i % cols.length] + "}" + ch)
} !}

This defines a macro called rainbow that splits an input string into characters, then changes the color of each character using the standard basic color codes (eg {wht},{red}). To call a macro, wrap a function call in {{ and }} tags:

print "hello, {{ rainbow("world") }}!"
REM compiles to:
10 print "hello, {wht}w{red}o{cyn}r{pur}l{grn}d!"

Inline assembler

Inline assembler can be compiled with KickAssembler, and the bytes are then injected into the BASIC listing via POKES and run with a SYS call.

{{!"set screen colors via asm"
  lda #0
  sta $d020
  sta $d021

Compiles to:

10 rem set screen colors via asm
20 for za = 0 to 8:read zb:poke 49152+za,zb:next za
30 sys 49152
40 data 169,0,141,32,208,141,33,208,96

The title string is optional, and will be included in a REM statement if present. If you don’t specify a base memory location (with the KickAssembler definition (eg, *=$c000) it will use the default (I think it’s $801? Which is not good for BASIC!). But if you specify the base in your first inline asm block, subsequent blocks can continue on from the last address by putting a * after the opening tag.

For example, the above asm is located from $c000 to $c008 (49152-49160 decimal). If you start the next asm block with {{!*, it will POKE the bytes into location $c009 (49161 decimal).


Sorry, no CLI options yet. Settings are defined in ebas_config.json:

  "OUTFILE_PATH": "./",
  "KICKASSEMBLER_PATH": "/usr/lib/KickAssembler/",
  • LINE_SPACING is how to sequentially number your source code. Default is 10, but you might just want 1 (or 5, or whatever).
  • OUTFILE_PATH indicates where to dump the output file. Default is in the current directory.
  • KICKASSEMBLER_PATH is the path to the KickAssembler compiler jar file if you want to do inline assembler.

Running .bas files on a C64

My use case is to convert the .ebas file to plain C64 BASIC, then compile that into an optimised C64 .prg file with Egon Olsen’s fantastic BASICv2 mospeed java command line tool:

./ -target=test.prg test.bas

The .prg file can then be loaded into Vice or another emulator (or, you know, run on a real Commodore 64!).