{"id":13341518,"url":"https://github.com/yiransheng/basic_rs","last_synced_at":"2025-04-14T12:06:31.454Z","repository":{"id":145408292,"uuid":"160492461","full_name":"yiransheng/basic_rs","owner":"yiransheng","description":"Original Dartmouth BASIC Interpreter/Compiler","archived":false,"fork":false,"pushed_at":"2019-01-17T21:00:02.000Z","size":557,"stargazers_count":38,"open_issues_count":4,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-14T12:06:27.264Z","etag":null,"topics":["basic","compiler","interpreter","relooper","rust","vm","wasm","web-assembly"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yiransheng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-05T09:21:00.000Z","updated_at":"2024-03-31T13:16:25.000Z","dependencies_parsed_at":"2023-06-03T17:15:14.158Z","dependency_job_id":null,"html_url":"https://github.com/yiransheng/basic_rs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Fbasic_rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Fbasic_rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Fbasic_rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Fbasic_rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yiransheng","download_url":"https://codeload.github.com/yiransheng/basic_rs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248877985,"owners_count":21176243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["basic","compiler","interpreter","relooper","rust","vm","wasm","web-assembly"],"created_at":"2024-07-29T19:25:28.913Z","updated_at":"2025-04-14T12:06:31.431Z","avatar_url":"https://github.com/yiransheng.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `basic_rs` : a BASIC Interpreter/Compiler for the Original Dartmouth Version\n\n[![Build Status](https://travis-ci.org/yiransheng/basic_rs.svg?branch=master)](https://travis-ci.org/yiransheng)\n\nA BASIC language interpreter written in `rust`. This project is motivated and inspired by Peter Norvig's [BASIC interpreter in python](http://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/BASIC.ipynb), reading that notebook helped me tremendously.\n\n## Overview\n\nThe repo contains an interpreter and a couple compilers of the original Dartmouth BASIC language.\n\n* Main crate `basic_rs` implements the frontend of BASIC (scanner, parser, ast), and a VM-based interpreter\n* Crate `basic2wasm` compiles BASIC to Web Assembly using [binaryen](https://github.com/WebAssembly/binaryen) (`INPUT` statement only works with console output, due to lack of blocking IO in browser environment)\n  * example: wasm  [Game of Life](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/master/ipynb/BASIC.ipynb#Longer-Program:-Life) from BASIC source, see it running [here](http://subdued-afternoon.surge.sh/)\n  * [README](./basic2wasm/README.md)\n* Crate `basic2js` compiles BASIC to JavaScript (using generator functions for async `INPUT` handling)\n  * example: vintage [**batnum**](https://www.atariarchives.org/basicgames/showpage.php?page=14) game, see it running [here](http://batnum.surge.sh/)\n  * [README](./basic2js/README.md)\n* Crate `basic2rs`, compiles BASIC to `rust` source code, and to subsequently native code with `rustc` (click below for BASIC to rs in action)\n\n[![asciicast](https://asciinema.org/a/O8HlDhmjjtkRqTz1nCuTtZ49u.svg)](https://asciinema.org/a/O8HlDhmjjtkRqTz1nCuTtZ49u)\n\n\n\nCalling the last two \"compilers\" is a bit disingenuous, as they are source-to-source transpilers, and produce strings rather than any kind of runnable machine code. This is primarily a learning project, to get myself familiar with compiler constructions and optimizations. However, I will continue to add tests and bug fixes and strive to make this a solid BASIC implementation.\n\n## Features and Limitations\n\nMatches first version of [Dartmouth Basic](https://en.wikipedia.org/wiki/Dartmouth_BASIC) closely: reference manual [here](http://web.archive.org/web/20120716185629/http://www.bitsavers.org/pdf/dartmouth/BASIC_Oct64.pdf), which means this implementation inherits all its limitations.\n\n* No input support other than `DATA` statements in source program\n  * [**Update**] Added `INPUT` statement support in #28\n  * Not sure what the official syntax for `INPUT` is,  but statements like `10 INPUT \"Prompt\" X, Y` works fine\n  * This makes at least some vintage BASIC using only number inputs playable\n* No string / boolean value types, only value type is `f64`\n* Variable names restricted to `[A-Z]\\d?` \n* Function names restricted to `FN[A-Z]`\n* List and table dimension restrictions\n  * [**Update**] Restriction since has been removed\n* Otherwise supports all 15 types of statements: `LET`, `READ`, `DATA`, `PRINT`, `GOTO`, `IF`, `FOR`, `NEXT`, `END`, `STOP`, `DEF`, `GOSUB`, `RETURN`, `DIM` and `REM`, in addition, added `INPUT`\n\n\n\n## Run Program\n\n```shell\nbasic_rs ./my_program.bas \n```\n\nOptionally, `-d` flag disassembles compiled VM byte code:\n\n```\nbasic_rs -d ./debug.bas\n```\n\n\n\n## Note on `INPUT`\n\nOriginal BASIC does not have `INPUT` statement, and its syntax in different implementations of BASIC later varies. I have chosen the following:\n\n```\ninputStatement := (label | \";\" | \",\")* variable (\",\" variable)*\nvariable       := ident ( \"(\" expr \")\" | \"(\" expr \",\" expr \")\" )?\n```\n\nEssentially, an input statement is some optional prompts followed by one or more of variables (allowing array subscripting) separated by commas.\n\nAt runtime, each line is consider a single value, and empty lines are treated as 0.\n\n## Implementation Details\n\nCompared to Norvig's implementation, the flavor of this project is more of no-hack, from-scratch approach (Norvig's version leveraged many of Python's powerful, dynamic features to get things done fast and cleverly). My goal was trying to learn how to implement a simple language as principled as I could manage.\n\n\n\nBASIC source code is _lexed_, _parsed_ and _compiled_ into a custom stack based VM byte code (consist of instructions I made up somewhat arbitrarily along the way instead of properly designed).\n\n\n\n### VM features:\n\n* standard stack based arithmetics\n* global variables and arrays\n* function call with 0 or 1 argument\n  * the former is used for subroutine calls\n  * the latter is used for user defined function calls `FNA` - `FNZ`\n* custom IO instructions to match BASIC's weird print semantics\n\n\n\nSome sample disassembler output. (Source of this program is `sample_programs/func_redefine.bas`).\n\n```\n10    0000    decl.loc   2\n15    0002    const      0\n |    0005    set.loc    $0\n20    0008    const      0\n |    0011    set.loc    $1\n25    0014    bind.fn    FNZ \u003ccompiled function 0\u003e\n30    0019    bind.fn    FNA \u003ccompiled function 1\u003e\n |    0024    const      10\n |    0027    get.fn     FNA\n |    0030    call_      args: 1\n |    0032    set.loc    $0\n |    0035    prt.lab    \"FNA(10) =\"\n |    0038    prt;      \n40    0039    get.loc    $0\n |    0042    prt.expr  \n45    0043    prt\\n     \n50    0044    bind.fn    FNA \u003ccompiled function 2\u003e\n |    0049    const      10\n |    0052    get.fn     FNA\n |    0055    call_      args: 1\n |    0057    set.loc    $1\n |    0060    prt.lab    \"FNA(10) =\"\n |    0063    prt;      \n |    0064    get.loc    $0\n |    0067    get.loc    $1\n |    0070    eq        \n |    0071    not       \n |    0072    jmp.t      80\n70    0075    prt.lab    \"FAILED\"\n |    0078    prt\\n     \n |    0079    ret       \n100   0080    prt.lab    \"Ok\"\n |    0083    prt\\n     \n |    0084    ret       \n\nChunk: \u003ccompiled function 0\u003e\n\n15    0000    decl.loc   0\n |    0002    get.loc    $0\n |    0005    ret.val   \n\nChunk: \u003ccompiled function 2\u003e\n\n40    0000    decl.loc   0\n |    0002    get.loc    $0\n |    0005    get.fn     FNZ\n |    0008    call_      args: 1\n |    0010    const      1\n |    0013    sub       \n |    0014    ret.val   \n\nChunk: \u003ccompiled function 1\u003e\n\n20    0000    decl.loc   0\n |    0002    const      1\n |    0005    get.loc    $0\n |    0008    add       \n |    0009    ret.val   \n```\n\n\n\n## WASM\n\nSee [README](./basic2wasm/README.md) for `basic2wasm` crate.\n\n\n\n## Performance\n\nA simple BASIC program using `RND` to estimate PI via Monte Carlo method is used for benchmarking (using 1000 iterations), and compared to these implementations:\n\n* `python`  (benches/pi.py)\n* `nodejs` (benches/pi.js)\n* `rust` (`fn` embedded in benchmark file)\n* `rust` compiled from BASIC source\n* `js` compiled from BASIC source\n\n\nHere are the results:\n\n```\ninterpreter:pi.bas      time:   [388.12 us 389.08 us 390.18 us]                        \n\nextern:pi.py            time:   [202.06 us 203.38 us 205.06 us]                         \n\nextern:pi.js            time:   [19.374 ns 19.605 ns 19.869 ns]                         \n\n(*fastest) rust:pi      time:   [296.27 ps 296.78 ps 297.42 ps]\n\nbas:compiled_rust       time:   [1.1920 us 1.1988 us 1.2064 us]\n\nbas:compiled_js         time:   [18.245 ns 18.317 ns 18.410 ns] \n```\n\n\n\nIt is only 2 times slower than `python`. Nodejs is four magnitude (10000 times compared to python) faster, and `rust` (with `target-cpu = \"native\"`) is pretty much on a different level. There are some minor penalties to node and python versions due to communicating via stdin/stdout. However, the pattern still holds if iteration is increased from 1000 to 1000_000 and without IO barrier.\n\n\n\nThe `pi.bas` program compiled to `rust` , runs in 1.2us, slower than nodejs(60x slower) and handwritten `rust` version (6000x slower) by quite a bit - mostly due to:\n\n* Using `f64` for loop counter\n* Inefficient control flow generated by `Relooper` (using runtime variable to encode otherwise statically known branching), potentially made it harder to `rustc` and `LLVM` to optimize\n\nIt does run much faster than than interpreter version and a non-JIT-ed interpreted Python code.\n\n\n\nSomewhat surprisingly, the compiled js version rust the same as hand written js version. And it is faster than the compiled rust version - even though the generated control flow is identical, I guess due to the V8's JIT magic.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyiransheng%2Fbasic_rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyiransheng%2Fbasic_rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyiransheng%2Fbasic_rs/lists"}