A BASIC interpreter written in golang.


Go Report Card license Release

05 PRINT "Index"


This repository contains a naive implementation of BASIC, written in Golang.

If you'd prefer to see a more "real" interpreted language, implemented in Go, you might prefer monkey.

The implementation is simple for two main reasons:

  • There is no UI, which means any and all graphics-primitives are ruled out.
    • However the embedded sample, described later in this file, demonstrates using BASIC to create a PNG image.
    • There is also a HTTP-based BASIC server, also described later, which allows you to create images "interactively".
  • I didn't implement the full BASIC set of primitives.
    • Although most of the commands available to the ZX Spectrum are implemented. I only excluded things relating to tape, PEEK, POKE, etc.
    • If you want to add new BASIC keywords this is easy, and the samples mentioned above do that.

The following obvious primitives work as you'd expect:

  • DIM
  • END
    • Exit the program.
  • GOTO
    • Jump to the given line.
    • Used to call the subroutines at the specified line.
  • IF / THEN / ELSE
    • Conditional execution.
    • Allow reading a string, or number (see later note about types).
  • LET
    • Assign a value to a variable, creating it if necessary.
  • FOR & NEXT
    • Looping constructs.
    • Print a string, an integer, or variable.
    • Multiple arguments may be separated by commas.
  • REM
    • A single-line comment (BASIC has no notion of multi-line comments).
  • SWAP
  • DEF FN & FN

Most of the maths-related primitives I'm familiar with are also present, for example SIN, COS, PI, ABS, along with the similar string-related primitives:

    • Returns the length of a string "STEVE" (5).
  • LEFT$ "STEVE", 2
    • Returns the left-most 2 characters of "STEVE" ("ST").
  • RIGHT$ "STEVE", 2
    • Returns the right-most 2 characters of "STEVE" ("VE").
  • CHR$ 42
    • Converts the integer 42 to a character (*). (i.e. ASCII value.)
  • CODE " "
    • Converts the given character to the integer value (32).

20 PRINT "Limitations"

This project was started as a weekend-project, although it has subsequently been improved and extended.

The code has near-total test-coverage, and has been hammered with multiple days of fuzz-testing (i.e. Feeding random programs into the interpreter to see if it will die - see FUZZING.md for more details on that.)

That said there are some (obvious) limitations:

  • Only a single statement is allowed upon each line.
  • Only a subset of the language is implemented.
    • If there are specific primitives you miss, then please report a bug.
      • The project is open to suggestions, but do bear in mind the project goals listed later on.
  • When it comes to types only floating-point and string values are permitted.
    • There is support for arrays but only one or two dimensional ones.


Arrays are used just like normal variables, but they need to be declared using the DIM statement. Individual elements are accessed using the offsets in brackets after the variable name:

10 DIM a(10,10)
20 LET a[1,1]=10
30 PRINT a[1,1], "\n"

Arrays are indexed from 0-N, so with an array size of ten you can access eleven elements:

 10 DIM a(10)
 20 a[0] = 0
 30 a[1] = 1
 40 ..
 90 a[9] = 9
100 a[10] = 10

ZX Spectrum BASIC indexed arrays from 1, denying the ability to use the zeroth element, which I've long considered a mistake.

Line Numbers

Line numbers are mostly optional, for example the following program is valid and correct:

 10 READ a
 20 IF a = 999 THEN GOTO 100
 30 PRINT a, "\n"
 40 GOTO 10
100 END
    DATA 1, 2, 3, 4, 999

The main reason you need line-numbers is for the GOTO and GOSUB functions, if you prefer to avoid them then you're welcome to do so.

IF Statement

The handling of the IF statement is perhaps a little unusual, since I'm used to the BASIC provided by the ZX Spectrum which had no ELSE clause. The general form of the IF statement I've implemented is:


Only a single statement is permitted between "THEN" and "ELSE", and again between "ELSE" and NEWLINE. These are valid IF statements:


In that second example you'll see that ":" was used to terminate the PRINT statement, which otherwise would have tried to consume all input until it hit a newline.

The set of comparison functions probably includes everything you need:

  • IF a < b THEN ..
  • IF a > b THEN ..
  • IF a <= b THEN ..
  • IF a >= b THEN ..
  • IF a = b THEN ..
  • IF a <> b THEN ..
  • IF a THEN ..
    • This passes if a is a number which is not zero.
    • This passes if a is a string which is non-empty.

You can see several examples of the IF statement in use in the example examples/70-if.bas.

DATA / READ Statements

The READ statement allows you to read the next value from the data stored in the program, via DATA. There is no support for the RESTORE function, so once your data is read it cannot be re-read.

Builtin Functions

You'll also notice that the primitives which are present all suffer from the flaw that they don't allow brackets around their arguments. So this is valid:

10 PRINT RND 100

But this is not:

10 PRINT RND(100)

This particular problem could be fixed, but I've not considered it significant.


There are no type restrictions on variable names vs. their contents, so these statements are each valid:

  • LET a = "steve"
  • LET a = 3.2
  • LET a% = ""
  • LET a$ = "steve"
  • LET a$ = 17 + 3
  • LET a% = "string"

The sole exception relates to the INPUT statement. The INPUT statement prompts a user for input, and returns it as a value - it doesn't know whether to return a "string" or a "number". So it returns a string if it sees a $ in the variable name.

This means this reads a string:

10 INPUT "Enter a string", a$

But this prompts for a number:

10 INPUT "Enter a number", a

This seemed better than trying to return a string, unless the input looked like a number (i.e. the input matched /^([0-9\.]+)$/ we could store a number, otherwise a string).

30 PRINT "Installation"

Build without Go Modules (Go before 1.11)

Providing you have a working go-installation you should be able to install this software by running:

go get -u github.com/skx/gobasic

NOTE This will only install the command-line driver, rather than the HTTP-server, or the embedded example code.

Build with Go Modules (Go 1.11 or higher)

git clone https://github.com/skx/gobasic ;# make sure to clone outside of GOPATH
cd gobasic
go install

If you don't have a golang environment setup you should be able to download various binaries from the github release page:

40 PRINT "Usage"

gobasic is very simple, and just requires the name of a BASIC-program to execute. Write your input in a file and invoke gobasic with the path.

For example the following program was useful to test my implementation of the GOTO primitive:

 10 GOTO 80
 20 GOTO 70
 30 GOTO 60
 40 PRINT "Hello, world!\n"
 50 END
 60 GOTO 40
 70 GOTO 30
 80 GOTO 20

Execute it like this:

$ gobasic examples/10-goto.bas

NOTE: I feel nostalgic seeing keywords in upper-case, but PRINT and print are treated identically.

50 PRINT "Implementation"

A traditional interpreter for a scripting/toy language would have a series of well-defined steps:

  • Split the input into a series of tokens ("lexing").
  • Parse those tokens and build an abstract syntax tree (AST).
  • Walk that tree, evaluating as you go.

As is common with early 8-bit home-computers this implementation is a little more BASIC:

  • We parse the input into a series of tokens, defined in token/token.go
  • We then directly execute those tokens.
    • The execution happens in eval/eval.go with a couple of small helpers:
      • eval/for_loop.go holds a simple data-structure for handling FOR/NEXT loops.
      • eval/stack.go holds a call-stack to handle GOSUB/RETURN
      • eval/vars.go holds all our variable references.
      • We have a facility to allow golang code to be made available to BASIC programs, and we use that facility to implement a bunch of our functions as "builtins".
        • Our builtin-functions are implemented beneath builtin/.
  • Because we support both strings and ints/floats in our BASIC scripts we use a wrapper to hold them on the golang-side. This can be found in object/object.go.

As there is no AST step errors cannot be detected prior to the execution of programs - because we only hit them after we've started running.

60 PRINT "Sample Code"

There are a small number of sample-programs located beneath examples/. These were written in an adhoc fashion to test various parts of the implementation.

Perhaps the best demonstration of the code are the following two samples:

  • examples/90-stars.bas
    • Prompt the user for their name and the number of stars to print.
    • Then print them. Riveting! Fascinating! A program for the whole family!
  • examples/55-game.bas
    • A classic game where you guess the random number the computer has thought of.

70 PRINT "Embedding"

The interpreter is designed to be easy to embed into your application(s) if you're crazy enough to want to do that!

You can see an example in the file embed/main.go.

The example defines several new functions which can be called by BASIC:

  • PEEK
  • POKE
  • PLOT
  • SAVE

When the script runs it does some BASIC variable manipulation and it also creates a PNG file - the PLOT function allows your script to set a pixel and the CIRCLE primitive draws an outline of a circle. Finally the SAVE function writes out the result.

Extending this example to draw filled circles, boxes, etc, is left as an exercise ;)

Hopefully this example shows that making your own functions available to BASIC scripts is pretty simple. (This is how SIN, COS, etc are implemented in the standalone interpreter.)

80 PRINT "Visual BASIC!"

Building upon the code in the embedded-example I've also implemented a simple HTTP-server which will accept BASIC code, and render images!

To run this:

cd goserver ; go build . ; ./goserver

Once running open your browser at the URL:

The view will have an area of entering code, and once you run it the result will be shown in the bottom of the screen. Something like this:

alt text

There are several included examples which you can load/launch by clicking upon them.

90 PRINT "Bugs?"

It is probable that bugs exist in this interpreter, but I've tried to do as much testing as I can. If you spot anything that seems wrong please do report an issue.

  • If the interpreter segfaults that is a bug.
    • Even if the program is invalid, bogus, or malformed the interpreter should cope with it.
  • If a valid program produces the wrong output then that is also a bug.

The project contain a number of test-cases, which you can execute like so:

$ go test ./...

Finally if our test-coverage drops beneath 95% that is a bug. The test coverage of most of our packages is 100%, unfortunately the main eval/ package is not yet completely covered.

You can see the global coverage via:

$ ./test-coverage

In addition to the test-cases which have been manually written the interpreter has also been fuzz-tested, which has resulted in some significant improvements.

See FUZZING.md for details of how to run the fuzz-tests.

100 PRINT "Project Goals / Links"

It is never the intention of this project to support all things that are possible in the various dialects of BASIC.

There are facilities which will make porting programs useful, such as the ability to use WHILE/END loops, functions with named-parameters, and primitives such as SLEEP, BEEP, & etc.

Above all else this project is supposed to be fun, for me. Which means if there are two ways of implementing something I'll pick the way I remember back when I was 12 and computers were .. fun!

If there are feature-requests which seem less fun, and less immediately useful to me - with my biased memories of coding on a ZX Spectrum - I will tag them "wontfix". If you contribute a pull-request to support them I will accept them, but I'm probably not likely to work upon them directly.

That said there are cases where I can be persuaded, and there are a lot of other BASIC intepreters out there, so I won't feel bad if this particular project doesn't suit your needs.

One project, slightly related to this, which might be worth checking up on is this one:

Github Setup

This repository is configured to run tests upon every commit, and when pull-requests are created/updated. The testing is carried out via .github/run-tests.sh which is used by the github-action-tester action.

Releases are automated in a similar fashion via .github/build, and the github-action-publish-binaries action.


  • LET should be optional

    LET should be optional

    I think LET should be optional.

    $ more let.bas 
    10 let a=1 : print a
    20 b=2 : print b
    $ gobasic let.bas 
    1Error running program:
    	Line 20 : Object{Type:error, Value:The variable 'b' doesn't exist}
    opened by udhos 6
  • Allow string-multiplication

    Allow string-multiplication

    I saw a bug report in a related project, relating to "string multiplication":

    • https://github.com/richpl/PyBasic/issues/49

    Supporting this would be nice and simple, and I recall it was always useful in Perl.

    Something like the following should work as expected:

    10 LET a = "STEVE "
    20 LET b = 4
    40 LET c = a * b
    50 PRINT c
    enhancement good first issue 
    opened by skx 5
  • Expected identifier after DATA

    Expected identifier after DATA

    Can anyone spot the problem causing the error below?

    $ gobasic bubble.bas 
    Error running program:
    	Line 130 : Expected identifier after DATA - found Token{Type:( Value:(}
    $ more bubble.bas 
    100 DATA "HELLO", "WORLD", "BASIC", "BUBBLE", "SORT", "DEMO", "GO"
    110 LET J=7
    120 FOR N=1 TO J
    130 READ A$(N)
    140 NEXT
    160 GOSUB 1000
    180 PRINT
    190 FOR N=1 TO J
    200 PRINT A$(N)
    210 NEXT
    220 END
    1040 REM
    1050 LET FLIPS=1
    1060 WHILE FLIPS
    1070 LET FLIPS=0
    1080 FOR N=1 TO J-1
    1090 IF A$(N)>A$(N+1) THEN SWAP A$(N), A$(N+1): LET FLIPS=1
    1100 NEXT N
    1110 WEND
    1120 RETURN
    opened by udhos 5
  • Improve test-coverage of the eval/eval.go file.

    Improve test-coverage of the eval/eval.go file.

    The core of our interpreter is implemented in eval/eval.go, and currently boasts 85% test-coverage.

    Unfortunately the bits that are lacking coverage are tricky to test, and the tests we've got defined cover random things unrelated to the core - specifically they cover the builtins, from before they were moved to their own package (in #58, #59, #60).

    I will accept 95% coverage - because I assume handling INPUT will be a pain - but nothing less.

    Once this pull-request is complete this will be achieved, and our test-cases (and no doubt improved implementation) will be much cleaner and more tightly focused - we'll close #70 at that time.

    opened by skx 4
  • Fantasy Basic: GOSUB by name?

    Fantasy Basic: GOSUB by name?

    I could imagine this, to allow you to GOTO/GOSUB via ident:

    10 LET a = 3000
    20 GOSUB a

    However neither helps if you want to renumber your programs. (Well I guess they add clarity.) However this would:

    10 GOSUB steve
    100 LABEL steve
    110  RETURN

    Do we want to add such things? Maybe. Filed as a fantasy for the moment.

    opened by skx 4
  • Support for SWAP

    Support for SWAP

    gobasic should support SWAP.

    Documentation: http://www.antonis.de/qbebooks/gwbasman/swap.html

    $ gobasic swap.bas 
    Error running program:
    	Line 30 : Object{Type:error, Value:The variable 'swap' doesn't exist}
    $ more swap.bas 
    10 let a=1
    20 let b=2
    30 swap a,b
    40 print a
    50 print b
    enhancement good first issue fantasy BUILTINS 
    opened by udhos 3
  • Troubleshoot recursive GOSUB program

    Troubleshoot recursive GOSUB program

    For some reason, gobasic can't properly execute this recursive factorial program. It doesn't ever end. Why?

    $ gobasic fact.bas 
    This program calculates factorial recursively using GOSUB
    Enter number: 7
    Calculating factorial for  7 !
    $ more fact.bas 
    10 print "This program calculates factorial recursively using GOSUB\n"
    20 input "Enter number: ", x
    30 print "Calculating factorial for ", x, "!\n"
    40 gosub 100
    50 print "Done: ", x, "! = ", y, "\n"
    60 end
    100 rem Factorial for x is returned in y
    110 rem Input: x
    120 rem Output: y
    130 if x < 2 then y = 1
    135 if x < 2 then return
    140 x = x - 1
    150 gosub 100
    160 x = x + 1
    170 y = y * x 
    180 return
    opened by udhos 3
  • Conditionals Seem Limited

    Conditionals Seem Limited

    This works as expected: IF 1=1 THEN PRINT "TEST" This, however, fails

     Error running program:
           Line  : factor() - unhandled token: Token{Type:PRINT Value:PRINT}

    It seems to work fine in other basic interpreters I tried.

    opened by krschwab 3
  • Handled built-ins as expressions.

    Handled built-ins as expressions.

    This works:

    10 PRINT RND 4

    These fails:

     10 PRINT RND RND 4
     10 COLOR RND 255, RND 255, RND 255

    The reason for this is interesting. We store variables as {token.IDENT }, and we added special code to first of all look for a built-in every time we get the value of a variable. However for that to work we need to skip tokens more than we do.

    The solution to this problem is to treat builtins as something that can be handled by expr. Which means we stop faking the invokation of builtins, but actually do it for real. This will be easier to implement than describe!

    • Add a new token.type token.BUILTIN
    • When calling RegisterBuiltin scan our program and rewrite it, in place.
    • When a type is token.BUILTIN call expr() to get the value.
    • Done.
    opened by skx 3
  • Compiling BASIC to Go

    Compiling BASIC to Go


    Inspired by your gobasic tool, I am sketching a BASIC-to-Go compiler just for fun.

    I managed to compile an adapted version of your example 99-game.bas. This is the game.bas version that my compiler currently can handle: https://github.com/udhos/basgo/blob/master/examples/game.bas

    The compiler works like this:

    basgo-build < examples/game.bas > game.go
    go run game.go

    You might want to have a look at the compiler: https://github.com/udhos/basgo Let me know what you think.

    One question: should I report minor gobasic issues that I am finding? For example, gobasic requires LET in order to create a new variable, and I think it should be optional.

    Best regards, Everton

    opened by udhos 2
  • Improve the handling of impossible FOR loops

    Improve the handling of impossible FOR loops

    The following example code was emailed to me:

    10 FOR I = 1 TO -20 
    20 PRINT I, "\n"
    30 NEXT I

    Output? Constant numbers.

    Expected output? zero numbers. There shouldn't even be a single iteration.

    A similar situation is:

    10 FOR I = 1 TO 10 STEP 7
    20 PRINT I, "\n"
    30 NEXT I

    On my system that never terminates. On a real spectrum it produces:

    opened by skx 2
  • Subtraction not processed

    Subtraction not processed

    gobasic version v1.8 under windows 7 x64 Code: LET a="Hello, World!" LET b=LEN a PRINT "b=",b,"\n" b=b-1 PRINT "b=",b,"\n"

    Output: b= 13 b= 13

    opened by eotect 4
  • ZXBasic string slicing

    ZXBasic string slicing

    Found a funny feature called string slicing in ZXBasic:

    There is a notation called slicing for describing substrings, and this can be applied to arbitrary string expressions.

    "abcdef"(2 TO 5)="bcde"


    opened by udhos 0
  • Error in DEF FN: Expected FN after DEF

    Error in DEF FN: Expected FN after DEF

    gobasic is reporting error for this program. How can I find the root cause?

    [email protected]:~$ gobasic basgo/examples/trek/superstartrek.bas
    Error constructing interpreter:
            Error in DEF FN: Expected FN after DEF
    [email protected]:~$

    Full source code: https://github.com/udhos/basgo/blob/master/examples/trek/superstartrek.bas

    opened by udhos 3
  • Suppose for NEXT without variable

    Suppose for NEXT without variable

    gobasic should support NEXT without a variable.

    The NEXT variable should be optional unless for BASIC code collapsing multiple FOR loops into the same NEXT (10 for a=1 to 2: for b=3 to 5: next b,a).

    Documentation: https://hwiegman.home.xs4all.nl/gw-man/FORNEXT.html

    $ gobasic next.bas 
    1Error running program:
    	Line 30 : Expected IDENT after NEXT in FOR loop, got Token{Type:NEWLINE Value:\n}
    $ more next.bas 
    10 for i=1 to 3
    20 print i
    30 next
    enhancement wontfix fantasy 
    opened by udhos 1
  • Support for WHILE/WEND

    Support for WHILE/WEND

    gobasic should support WHILE/WEND.

    Documentation: http://www.antonis.de/qbebooks/gwbasman/whilewend.html

    $ gobasic while.bas 
    Error running program:
    	Line 20 : Object{Type:error, Value:The variable 'while' doesn't exist}
    $ more while.bas 
    10 let a=1
    20 while a<=10
    30 print a
    40 let a=a+1
    50 wend
    wontfix fantasy 
    opened by udhos 2
  • INPUT prompt string should be optional

    INPUT prompt string should be optional

    I think INPUT without prompt string should be supported.

    $ more input.bas
    10 input a : print a
    $ gobasic input.bas
    Error running program:
    	Line 10 : ERROR: INPUT should be : INPUT "prompt",var
    wontfix fantasy 
    opened by udhos 3
gpython is a python interpreter written in go "batteries not included"

gpython gpython is a part re-implementation / part port of the Python 3.4 interpreter to the Go language, "batteries not included". It includes: runti

go-python 640 Nov 29, 2022
A JavaScript interpreter in Go (golang)

otto -- import "github.com/robertkrimen/otto" Package otto is a JavaScript parser and interpreter written natively in Go. http://godoc.org/github.com/

Robert Krimen 7k Dec 2, 2022
wagon, a WebAssembly-based Go interpreter, for Go.

wagon wagon is a WebAssembly-based interpreter in Go, for Go. As of 2020/05/11 Wagon is in read-only mode, and looking for a maintainer. You may want

Go Interpreter 898 Nov 16, 2022
Yaegi is Another Elegant Go Interpreter

Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go

Traefik Labs 5k Nov 30, 2022
Lisp Interpreter

golisp Lisp Interpreter Usage $ golisp < foo.lisp Installation $ go get github.com/mattn/golisp/cmd/golisp Features Call Go functions. Print random in

mattn 120 Nov 12, 2022
A simple interpreter

类型: 基础类型: 整形,浮点,字符串,布尔,空值(nil) 符合类型: 数组,只读数组(元组),字典 支持语句: if, elif, else, for, foreach, break, continue, return 支持类型定义: class, func 语法格式: 赋值语句---> ```

null 19 Aug 10, 2022
High-performance PHP application server, load-balancer and process manager written in Golang

RoadRunner is an open-source (MIT licensed) high-performance PHP application server, load balancer, and process manager. It supports running as a serv

Spiral Scout 6.9k Nov 27, 2022
A compiler for the ReCT programming language written in Golang

ReCT-Go-Compiler A compiler for the ReCT programming language written in Golang

null 7 Nov 20, 2022
A Lua 5.3 VM and compiler written in Go.

DCLua - Go Lua Compiler and VM: This is a Lua 5.3 VM and compiler written in Go. This is intended to allow easy embedding into Go programs, with minim

Milo Christiansen 901 Nov 28, 2022
PHP parser written in Go

PHP Parser written in Go This project uses goyacc and ragel tools to create PHP parser. It parses source code into AST. It can be used to write static

Vadym Slizov 897 Nov 20, 2022
An interpreted languages written in Go

Monkey My changes 1. Installation Source Installation go <= 1.11 Source installation go >= 1.12 Binary Releases 1.1 Usage 2 Syntax 2.1 Definitions 2.2

Steve Kemp 185 Nov 21, 2022
Transpiling fortran code to golang code

f4go Example of use > # Install golang > # Compile f4go > go get -u github.com/Konstantin8105/f4go > cd $GOPATH/src/github.com/Konstantin8105/f4go > g

Konstantin 34 Sep 26, 2022
Golang->Haxe->CPP/CSharp/Java/JavaScript transpiler

TARDIS Go -> Haxe transpiler Haxe -> C++ / C# / Java / JavaScript Project status: a non-working curiosity, development currently on-ice The advent of

TARDIS Go 423 Nov 27, 2022
PHP bindings for the Go programming language (Golang)

PHP bindings for Go This package implements support for executing PHP scripts, exporting Go variables for use in PHP contexts, attaching Go method rec

Alex Palaistras 878 Nov 22, 2022
High-performance PHP-to-Golang IPC bridge

High-performance PHP-to-Golang IPC bridge Goridge is high performance PHP-to-Golang codec library which works over native PHP sockets and Golang net/r

Spiral Scout 1.1k Nov 23, 2022
Expression evaluation in golang

Gval Gval (Go eVALuate) provides support for evaluating arbitrary expressions, in particular Go-like expressions. Evaluate Gval can evaluate expressio

null 559 Nov 30, 2022
golang AST matcher

goastch (GO AST matCH) Introduction Inspired by ast matcher. There are four different basic categories of matchers: Node Matchers: Matchers that match

Helloyi He 13 Nov 11, 2022
Arbitrary expression evaluation for golang

govaluate Provides support for evaluating arbitrary C-like artithmetic/string expressions. Why can't you just write these expressions in code? Sometim

George Lester 2.8k Nov 29, 2022
hotbuild - a cross platform hot compilation tool for golang

hotbuild A cross platform hot compilation tool By monitoring the modification of the project directory file, the recompilation and running are automat

wander 188 Nov 22, 2022