Basic.js Hero

Basic.js

Basic.js is a modernized implementation of the BASIC programming language for JavaScript. It is comprised of three major parts: the lexer, the parser, and the BASIN runtime which is its own separate project used in several of my domain-specific languages.

GitHub Repo: https://github.com/DanFessler/basic.js

Motivation

My first programming language was BASIC which I taught myself through a software released by Interplay called Learn to Program Basic in 1998. My nostalgia for BASIC and interest in interpreters led me to make my own implementation.

Originally it was intended to be a faithful implementation of Interplay’s version, however, as the project progressed, it turned into my own modern flavor. Notable differences include:

Example program

for i: 1 to 100
  if i % 15 = 0
    print "FizzBuzz"
  elseif i % 5 = 0
    print "Buzz"
  elseif i % 3 = 0
    print "Fizz"
  else
    print i
  endif
next

Usage

Install directly from the GitHub repo:

npm i https://github.com/DanFessler/basic.js

Then import the package, import any necessary library plugins, and run your program.

import basic from "jsbasic";

basic.import({
  newFunction: () => console.log("this is an example plugin function"),
});

basic.run(`print "Hello World!"`);

Lexer

I wrote a custom Lexer for Basic.js which was very simple, only ~150 lines. At a high level, it follows these steps:

  1. Reads through the program character by character, adding to the current token lexeme until it no longer matches a rule or reaches the end of the program
  2. Determines the type for the token based on matching its lexeme to a list of keywords, operators, or literals.
  3. If there was no matching rule or the type wasn’t determined, throw a Syntax Error

Parser

Rather than using a parser generator or other context-free grammar solution, the parser was also written from scratch for Basic.js. This was an educational project after all.

At its core it’s a Recursive Descent Parser, with some notable differences:

Plugins

Plugins to extend the language with additional libraries can be imported with basic.import() which are then accessible to Basic.js through regular function calls. A plugin is provided as a lookup table of named functions.

Below is an example plugin which adds canvas drawing and mouse input functions. This particular example is instantiated from a class as it needs to track internal state.

export default class Plugin {
  constructor(canvasID) {
    this.canvas = document.getElementById(canvasID);
    this.ctx = this.canvas.getContext("2d");

    this.mouseState = {
      x: 0,
      y: 0,
      button: 0,
    };

    window.onmousemove = (e) => {
      let rect = this.canvas.getBoundingClientRect();
      this.mouseState.x = e.clientX - rect.left;
      this.mouseState.y = e.clientY - rect.top;
    };

    this.canvas.onmousedown = (e) => {
      this.mouseState.button = e.buttons;
    };

    this.canvas.onmouseup = (e) => {
      this.mouseState.button = e.buttons;
    };
  }

  getFunctions() {
    return {
      drawCircle: (x, y, r, color) => {
        this.ctx.fillStyle = color;
        this.ctx.beginPath();
        this.ctx.arc(x, y, r, 0, 360);
        this.ctx.fill();
      },

      fillCanvas: (color) => {
        this.ctx.fillStyle = color;
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
      },

      clearCanvas: () => {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      },

      getMouseX: () => {
        return this.mouseState.x;
      },

      getMouseY: () => {
        return this.mouseState.y;
      },

      getMouseButton: () => {
        return this.mouseState.button;
      },
    };
  }
}

Live Demo

I put together a playground which extends the language with canvas drawing operations and a few other features. The default program draws an animated grid of circles that follows the mouse cursor.

Basic.js playground screenshot