Skip to content

mhaupt/basicode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BASICODE

This is an implementation of a BASIC interpreter providing a BASICODE runtime environment independent of any emulator (unless you count the Java VM as one). The point of this project is to have such a thing, rather than providing a highly optimised BASICODE run-time environment. Frankly, the JVM, off the shelf, will do a good enough job at delivering run-time performance. This is also why the BASICODE implementation does not sport any translation to Java bytecodes. The whole thing should be simple, first and foremost.

Building and Running

If you're not interested in building the project before trying it, you can download an executable JAR file (basicode.jar) from the release page.

The project was built using Java 21 and Maven. Once those are installed, just running mvn package on the command line should be enough to build everything and run the tests.

The mvn package run will generate an executable JAR file named basicode.jar in the project root. This file includes all relevant dependencies, so it can be used as a standalone "executable" for BASICODE as long as you still have Java 21 installed.

To run a BASIC file from the command line at the project root, run this (the -hold argument will make the BASICODE window stay after the program has run, until you press a key):

$ java -jar basicode.jar -hold examples/hello.bas 

This works for other BASICODE programs, as long as you point to the right basicode.jar and BASIC source file. If no BASIC source file is given, a dialogue will open that lets you choose one.

In case the interpreter should display a splash screen before running the actual program, use the -intro argument. The splash screen will show until a key is pressed.

Slowing Down Execution

Some programs of yore kind of relay on the hardware being inherently slower than today's machines. This is particularly true for games with a high amount of interaction. These will typically run so fast on this BASICODE interpreter that they become unplayable.

If you want programs like these to be usable, or if you just want to experience the feeling of 8-bit hardware execution speeds again, you can use the -slow command line argument. If this argument is given, the interpreter will pause execution after each statement for a multiple of 5 ms.

The actual slowdown depends on the number of o characters given in the -slow argument. For example, just passing -slow will pause execution for 1 ms after each statement; passing -slooow will pause execution for 3 ms after each statement.

Overview of Available Command Line Arguments

In addition to -hold, -intro, and -slow (see above), you can use the following command line arguments:

  • -nowait: suppress waiting. If this argument is given, the GOSUB 450 subroutine will have no effect; execution will simply continue without delays.
  • -nosound: play no sounds at all.
  • -enforceBoundaries: do run-time checks that ensure the HO and VE coordinates in graphics mode are always within the allowed 0..1 range.
  • -showMapKeys: display debugging information when keys are pressed and mapped.

Implementation Notes

The BASICODE implementation is meant to be simple, and to be as "standalone" as possible. Therefore, the implementation relies on out-of-the box components of the JDK as much as possible. For the GUI, the standard AWT and Swing packages are used.

Grammar

The BASIC grammar is located in the file basic.jj in the de.haupz.basicode.grammar package. This file is there only for illustration and for historical reasons: the parser used to be generated. It is now a handwritten recursive-descent parser in the de.haupz.basicode.parser package.

While there are numerous BASIC dialects that do not use line numbers (or make them optional), the BASICODE implementation here is meant to be compatible with the BASICODE sources "out there". Therefore, the grammar here enforces the use of line numbers.

Several other restrictions the BASICODE conventions impose - e.g., the maximum length of a source code line being 60 characters - are not enforced.

The grammar, during parsing, generates an abstract syntax tree (AST) of the program. The nodes of this AST are directly used by the interpreter (see below).

Interpreter

The AST nodes generated by the parser contain all the logic that is needed to run the program they represent. The BASIC interpreter used here thus embodies an AST interpreter design.

The main interpreter loop is contained in the ProgramNode class, which can be found in the de.haupz.basicode.ast package. Many of the AST node come with a piece of JavaDoc commentary that describes how they work, so I'll refer to those for further details.

Files and Printing

BASICODE supports reading from and writing to files on different kinds of media that were in use at the time when it was invented. These include cassette tapes, floppy disks, and microdrives. Since this BASICODE implementation would typically not have access to such media, it defaults to always opening files on the file system the implementation is running on.

If file names are given without any directory prefix (which would be unusual in BASICODE to begin with), files will be expected to be located, and can be expected to be created, in the directory the java command to start BASICODE was issued.

Similarly to file I/O, printer output is also emulated. The BASICODE implementation will open a file named BASICODE-printer.txt that will contain any output sent to the printer.

Tests

The grammar and basic functionality of the BASICODE implementation are covered in a decent amount of unit tests, which can be found in the test directory. The mvn test command will run them after building the project, and building the standalone JAR using mvn package will run the tests as well.

Examples

Some BASICODE example programs can be found in the examples directory. They are mostly simple, and serve to test particular aspects of the BASICODE conventions.

A vast set of examples can be found on Rob Hagemans' BASICODE in the browser page, or in his GitHub repository dedicated to BASICODE examples.

Extensions

This BASICODE implementation takes advantage of the fact that it's written in Java and there are multiple unused line numbers below 1000 that can be used for additional subroutines.

Dump All (Selected) Variable Values and Array Contents: GOSUB 960 (961)

Call these subroutines to dump all (960) or a selection of (961) variable values and array contents to the Java console. For GOSUB 961, the OD$() array controls what is dumped. Each element of that array should be the name of a variable or array the values or contents of which should be displayed. The OD$() array must be declared and properly dimensioned before the first use of this subroutine. If it is not present or unusable, all variables and arrays will be dumped.

Call Stack: GOSUB 962

Whenever this subroutine is called during execution, the current BASICODE call stack will be printed to the Java console. Execution will not be paused as would be the case with a breakpoint (see below).

Breakpoints: GOSUB 963

Use this to trigger a breakpoint during execution. When this subroutine is called, a dialogue box will open that displays the current call stack and the values of all variables and contents of all arrays. If, at the time the subroutine is called, the string OC$ contains a BASICODE condition that evaluates to false, the breakpoint will not be triggered. If the string is undefined or empty at that time, that counts as the condition being true.

Breakpoints With Selective Value Display: GOSUB 964

Similarly to GOSUB 963, this will trigger a breakpoint during execution. The variable values and array contents displayed will however be governed by what is contained in the OD$() array. Each element of that array should be the name of a variable or array the values or contents of which should be displayed. The OD$() array must be declared and properly dimensioned before the first use of this subroutine. If it is not present or unusable, all variables and arrays will be displayed.

If, at the time the subroutine is called, the string OC$ contains a BASICODE condition that evaluates to false, the breakpoint will not be triggered. If the string is undefined or empty at that time, that counts as the condition being true.

Watchpoints: GOSUB 965

Calling this subroutine registers a watchpoint. It will be triggered when a condition flips from "unmet" to "met". The condition is expressed in BASICODE syntax in the OC$ variable. After execution of the subroutine, the variable OP will contain a running number of the watchpoint. Numbering starts at 1. A value of -1 indicates an error during registration; in that case, OE$ will also contain an error message.

The watchpoint will be triggered whenever the condition flips from "unmet" to "met" after the execution of a statement. It will honour the contents of the OD$() array for selective display of variable values and array contents, as described above.

Programmatically Setting Breakpoints: GOSUB 966

Call this subroutine to register a breakpoint that will trigger without an explicit call to GOSUB 963. Before calling this subroutine, set OL to the line where the breakpoint should be triggered, and OS to the number of the statement (starting at 0) before the execution of which the breakpoint should be triggered.

Optionally, pass OD$ to control which variable values and array contents should be displayed when the breakpoint is triggered; and use OC$ to define a condition. See above for details on both of these.

After the invocation of the subroutine, OP contains the running number of the registered breakpoint (starting at 1), or -1 in case anything has gone wrong. In the latter case, OE$ will also contain an error message.

Selectively (De)activating Breakpoints: GOSUB 967 (968)

Any breakpoint registered with GOSUB 966 can be selectively (de)activated by calling one of these two subroutines. They both expect the breakpoint in question to be identified by the OP variable. A call to GOSUB 967 will activate the breakpoint, and a call to GOSUB 968 will deactivate it. In case of success, OP will be unchanged after the subroutine call. In case of any kind of error, OP will be set to -1 and OE$ will contain an error message.

Contributions

If you're interested in contributing to this project, please find details in CONTRIBUTING.md.

Licence Information

This project is under the MIT Licence, non-contagious, well-intentioned, and harmless.

The font used in the emulated console is "Amstrad CPC464 Regular" by Wesley Clarke. It's included here thanks to being licenced with CC BY-SA 3.0.

Credits

I'm immensely grateful to Thomas Rademacher of basicode.de for introducing me to BASICODE, and for contributing the splash screen. Many thanks to Bernd Bock and other members of Joyce-User-AG for encouragement and bug reports.

About

a BASICODE interpreter written in Java

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published