Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
Basic.java
Benchmark1.java
DlgBasic.java
LICENSE
README.md
basic.go
basic.pod
basic.py
benchmark1.bas
benchmark1.go
benchmark1.py

README.md

Tinycat BASIC

Tinycat BASIC is a line-number BASIC dialect that can be implemented in less than a thousand lines of Java or Go (barring extensions to the core). It achieves that by relying on direct interpretation and having no type system. Its name is meant to honor the classic dialect Tiny BASIC as inspiration and ancestry.

Rationale

While the hardware limitations that made Tiny BASIC desirable are largely a thing of the past, having programming language dialects that can be trivially implemented remains a good idea. It reduces software dependencies and black box components.

Features

Tinycat BASIC has a number of useful additions on top of its model:

  • floating point numbers;
  • arbitrary variable names;
  • control structures like DO ... LOOP and FOR ... NEXT;
  • the logical operators: AND, OR, NOT;
  • power and flooring division operators;
  • functions, both built-in and user-defined*;
  • random number generation.

*) Note: the new Go implementation lacks DEF FN.

Some features would not be trivial to add, and therefore outside the scope of this project:

  • arrays;
  • string variables.

Others can be added easily, but would cause more trouble than it's worth:

  • multiple statements per line; they require littering the source code with special cases, and lower performance for little benefit.

Performance

Note: the figures below have changed repeatedly as the interpreters were improved and flaws were fixed in the benchmark itself. Consider them a rough indication of relative performance.

The reference Python implementation is 113 times slower than the host language. Conversely, the Go implementation is 47 times slower than native code, within limits for this type of interpreter.

The Java implementation proved harder to benchmark, as a long-running interpreter runs progressively faster. That said, it seems to be roughly 47 times slower than pure Java on a fresh start (7 times slower than the Go implementation in absolute numbers), but ends up only 2.6 times slower than a compiled Java program. That's almost as if the interpreter wasn't in the way anymore, and actually three times faster than the Go implementation!

Overall, Java seems the best suited for interpreting another language. Or at least this interpreter architecture happens to suit Java unusually well.

Embedding Tinycat BASIC

All three editions of the interpreter can be embedded, with some caveats:

  • the Python edition will only have one shared context;
  • only the Java edition has independent per-context random number generators;
  • the Go edition has a (trivial) main() function that must be replaced.

Both the Go and Java interpreters support I/O redirection, but only the latter does it per-context.

Extending Tinycat BASIC

The Java implementation is fully extensible: by subclassing the interpreter, you can add more statements, built-in functions and even expression kinds!

The Python implementation can be extended with new statements or functions.

In the Go implementation, you can only add more built-in functions without changing the source code.

Supported commands

LIST
RUN
CONTINUE
CLEAR
NEW
DELETE line-number
LOAD "filename"
SAVE "filename"
BYE

Commands are only available at the built-in command prompt. It is assumed that a program embedding the interpreter will provide its own alternatives.

The BYE command leaves the command loop and returns to the host application (which simply closes a stand-alone interpreter). You can also press Ctrl-D to send an end-of-file character.

Supported statements

LET name "=" expression
IF expression THEN statement
GOTO expression
PRINT (string | expression)? ("," (string | expression))* ";"?
INPUT (string ",")? name ("," name)?
FOR name = expression TO expression (STEP expression)?
NEXT name
GOSUB expression
RETURN
DO
LOOP (WHILE | UNTIL) expression
REM text
DEF FN name "(" (name ("," name)?)? ")" "=" expression**
RANDOMIZE expression?
STOP
END

**) Note: absent in the Go edition.

Built-in functions

TIMER()
RND()
PI()
INT(n)
ABS(n)
SQR(n)
SIN(n)
COS(n)
RAD(n)
DEG(n)
MIN(a, b)
MAX(a, b)
MOD(a, b)
HYPOT2(a, b)
HYPOT3(a, b, c)
IIF(a, b, c)

Beware that the IIF function doesn't short-circuit (and neither do the logical operators).

Expression syntax

expression ::= disjunction
disjunction ::= conjunction ("or" conjunction)*
conjunction ::= negation ("and" negation)*
negation ::= "not"? comparison
comparison ::= math_expr (comp_oper math_expr)?
comp_oper ::= "<" | ">" | "<=" | ">=" | "<>" | "="

math_expr ::= term (("+"|"-") term)*
term ::= factor (("*" | "/" | "\") factor)*
factor ::= ("+"|"-")? (number | name | funcall | "(" expression ")")
funcall ::= name ("(" expr_list? ")")?
expr_list ::= expression ("," expression)*

Bugs and caveats

Java always displays numbers with at least 6 digits of precision. It seems to be a bug in String.format().

Python always displays numbers with at most six digits of precision -- the exact opposite behavior!

For portability, programs making use of randomness should call RANDOMIZE near the beginning. Not all implementations do that by default.