Skip to content

Commit

Permalink
async for *INPUT*, *FRAME* in subroutines (one calling level); some c…
Browse files Browse the repository at this point in the history
…ode cleanup; testPage improved
  • Loading branch information
benchmarko committed Dec 23, 2024
1 parent d65866c commit 1a6bca0
Show file tree
Hide file tree
Showing 10 changed files with 1,060 additions and 375 deletions.
285 changes: 202 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,180 @@
# LocoBasic - Light version of Locomotive BASIC

LocoBasic is a streamlined adaptation of Locomotive BASIC, designed primarily for calculations.
It is lightweight and can run either in a browser or on the command line using Node.js.
It has **NO GOTO** but supports a subroutine style with *GOSUB*.
Line numbers are optional and only needed to start a subroutine or a *DATA* line for *RESTORE*

LocoBasic Links:
[LocoBasic](https://benchmarko.github.io/LocoBasic/),
[Source code](https://github.com/benchmarko/LocoBasic/),
[HTML Readme](https://github.com/benchmarko/LocoBasic/#readme),

## Getting Started

### Running in a Browser

1. Open [LocoBasic](https://benchmarko.github.io/LocoBasic/) in any modern web browser.
2. Select an example with the select box or input your own BASIC code.
The code is automatically compiled to JavaScript and executed unless you switch off the "auto" checkbox.

### Running with Node.js

1. Install Node.js if you don’t already have it.
2. Clone this repository and navigate to its directory.
3. Run the following commands to install:

```bash
npm i
npm run build
```

4. Run some of the following command to execute:

```bash
node dist/locobasic.js example=euler
node dist/locobasic.js input='print "Hello!"'
node dist/locobasic.js input="?3 + 5 * (2 - 8)"
```

## LocoBasic Language Description

### Control Structures

- **Supported:**
- `IF...ELSE`
- Loops: `FOR` and `WHILE`
- These structures are directly converted to JavaScript for execution.

### Subroutines

- Use `GOSUB` and `ON GOSUB`
- `GOTO` and `ON GOTO` are **not supported**

- **Subroutine Style:**
- A line starting with `GOSUB <line>` marks the beginning of a subroutine
- Subroutines must end with a single `RETURN` on its own line
- **Important:** Subroutines cannot be nested

### Variable Types

- Usually number
- Use `$` to denote a string variable
- Variable markers like `!` and `%` are **not supported**
- **No automatic rounding:**
- Integer parameters are not automatically rounded
- Computations follow JavaScript precision
- Operator arity and precedence match those of Locomotive BASIC

### Special Notes

- **Endless Loops:**
- Not trapped automatically. Restarting the browser window may be required to recover.
- **STOP and END:**
- These halt execution only at the top level. Within subroutines, they simply return.

### Operators

- `AND`, `NOT`, `OR`, `XOR`
- `number MOD number` compute the modulus
- Comparisons: =, <>, <, <=, >, >=
- +, -, *, /, \ (integer div), (...)
- &hexValue, &xBinaryValue
- String concatenation: +

### Supported Commands and Functions

- `ABS(number)` Returns the absolute value of *number*
- `ASC(character)` Returns the ASCII number of *character*
- `ÀTN(number)` Returns the arcustangens of *number* in radians
- `BIN$(number [, padding])` Converts a number to its binary representation
- `CHR$(number)` Returns the character for the ASCII code *number*
- `CINT(number)` Returns the integer part of *number*
- same as *INT*
- `CLS` Clears the output window
- `COS(number)` Returns the cosine of *number* given in radians
- `DATA ["string", number,...]` Defines string or numerical data to be read by *READ*
- Separated by commas ","
- Strings must be quoted
- Numbers (including hex and binary) are unquoted and can only be read numerically
- `DEF FNname[(arg1, ...)] = expression` Defines a function *FNname*
- Can be used as `FNname()`
- No space between *FN* and *name* is allowed
- If there are no arguments, do not use parentheses
- `DIM arrayVariable(dim1 [, dim2, ...])` Initializes an array
- Can be multi-dimensional
- Will be initialized to 0 or "" depending on the variable type
- `END` Ends execution
- currently the same as `STOP`
- `ERASE variable, [variable,...]` Erases array variables
- Specify variable name without indices
- `ERROR number` Throws error with *number*
- `EXP(number)` Returns e function raised to the power of *number*
- `FIX(number)` Truncates *number*
- `FOR variable = start to end [STEP increment]` Control structure
- *increment* can also be negative,, in which case *start* must be greater than *end*
- **Endless Loops:** Not trapped
- `FRAME` Pauses execution for ~50ms intervals for synchronization.
- `GOSUB line` Calls subroutine starting at *line*
- Subroutines must end with a single `RETURN` on its own line
- `HEX$(number [, padding])` Converts a number to its hexadecimal representation
- `IF expression THEN statements [ELSE statements]` control structure (in one line)
- `INPUT [message;] variable` Prompts the user for input (string or numeric)
- `INSTR(string1, string2)` Returns the first positon of *string2* in *string1*
- **Limitations:** No support for *start position* as first argument
- `INT(number)` Returns the integer part of *number*
- `LEFT$(string, number)` Returns *number* characters from the left in *string*
- `LEN(string)` Returns rthe length of the string
- LocoBasic has no limitaton on the length
- `LOG(number)` Returns natural logarithm for *number* (based on e)
- `LOG10(number)` Returns logarithm for *number* based on 10
- `LOWER$(string)` Returns string in lowercase
- `MAX(number [,number,...])` Returns the maximum of the given numbers
- `MID$(string, first [, length])` Returns a substring starting at positon *first* with *length*
- `MIN(number [,number,...])` Returns the minimum of the given numbers
- `MODE number` Sets screen mode
- Currently the same as *CLS* with the mode *number* ignored
- `NEXT` Closes a *FOR* loop
- `ON index GOSUB line1 [,line2...]` Calls subroutine at position *index* in the list
- Check `GOSUB` for how to define a subroutine
- **Limitations:** There must be a subroutine at position *index* in the list
- `PI` Returns the value of 'pi'
- `PRINT argument1 [; argument2; ...]` Outputs text and numbers
- Arguments must be separated by `;`
- Numbers are padded with trailinng space, and leading space for positive numbers
- **Limitations:** No support for `TAB()`, `SPC()`, or `USING` formatting
- `READ variable` Reads the next value from a `DATA` statement into *variable*
- `REM` A comment until end of line, same as `
- `RESTORE [line]` Resets the `DATA` pointer to a specified *line* number
- `RETURN` Returns from a subroutine.
- See *GOSUB*, *ON... GOSUB*
- `RIGHT$(string, number)` Returns *number* characters from the right in *string*
- `RND(number)` Returns the next pseudo-random number
- Parameter *number* is ignored
- `ROUND(number [, decimalPlaces])` Rounds a number to a specified number of decimal places
- Rounding not exactly the same as with Locomotive BASIC
- `SGN(number)` Returns the signum of a number (-1, 0 or 1)
- `SIN(number)` Returns the sine of *number* given in radians
- `SPACE$(number)` Returns *number* spaces
- `SQR(number)` Returns the square root of *number*
- `STOP` Halts the execution
- Within subroutines, it functions as a *RETURN*
- Similar to *END*
- `STR$(number)` Converts a number to its string representation
- A positive number is passed with a space
- `STRING$(number, character)` Returns *character* repeated *number* times
- requires the second parameter to be a character
- `TAN(number)` Returns the tangens of *number* given in radians
- `TIME` Returns the current system time in 1/300 sec
- `UPPER$(string)` Returns string in uppercase
- `VAL(string)` Converts a string to a number
- supports hexadecimal and binary formats
- `WEND` Ends a *WHILE* loop
- `WHILE expression` Control structure: repeat until *expression* is false
- `number XOR number` In Expresions: exclusive-OR

### Misc

- LocoBasic is mainly used for calculations. It runs in a Browser or on the command line with node.js
- Control structures like IF...ELSE, and FOR and WHILE loops are directly converted to JaveScript
- GOTO or ON GOTO are not suppoered. Use GOSUB, ON GOSUB instead. The GOSUB line is interpreted as subroutine start.
Expand All @@ -13,36 +188,19 @@
- STRING$(): second parameter must be a character
- DATA: strings must be quoted; numbers (including hex, bin) are unquoted and can only be read numerical

## Examples

```basic
5 ' examples:
10 ' associativity
15 ? "7 =" 12 xor 5+6 "=" 12 xor (5+6), (12 xor 5)+6
20 ? "3 =" 7 mod 5+1 "=" (7 mod 5)+1, 7 mod (5+1)
30 ? "0 =" 10>5>4 "=" (10>5)>4, 10>(5>4)
40 ? not 1234555
50 ? 12 \ 5
100 DIM a$(10):?"<";a$(10);">"
110 DIM a2$(10,1):?"<";a2$(10,1);">"
120 a=1:b=7:c=4:de=68:f=2:g=5:hi=93:PRINT a*10+b;"*";c;"=";de;" / ";de;"+";f*10+g;"=";hi
130 a=7:?"<";str$(a);">"
````
## Misc
### TODO

- DATA lines are compiled to empty lines, remove them?
- numbers with exponential notation
- command line tool should output a stand alone running JS file for node, so include dimArray() on-demand in the compiled source?
- TIME: *300/1000 ?
- mid$ as assign? a$="abcde":mid$(a$,3,2)="w":?a$ ?
- `MID$` as assign? `a$="abcde": MID$(a$,3,2)="w": ?a$`
- command line tool should output a stand alone running JS file for node
- Do we want keywords all uppercase? And variables all lowercase?
And maybe new features with capital letter? E.g. If...Then...Else...Endif on multiple lines?
- Create syntax highlighting for BASIC for CodeMirror, maybe similar to the[amstradbasic-vscode](https://github.com/dfreniche/amstradbasic-vscode/blob/master/syntaxes/amstradbasic.tmLanguage.json) or [CPCReady](https://marketplace.visualstudio.com/items?itemName=CPCReady.basic-language-extension) extension

### DONE
### Done

- numbers with exponential notation
- *dim* and other more complex commands are included on-demand in the compiled JavaScript
- TIME: *300/1000
- DIM, NEXT with multiple arguments
- DATA, READ, RESTORE
- comments in IF: 107 IF zoom<3 THEN zoom=3: 'zoom=12
Expand All @@ -53,74 +211,35 @@
- ?hex$("3") => array hex$["3"]
- load examples.js separately (not as examples.ts in the package)
- separate UI from core (UI not needed for node), maybe two packages
- ERASE <var> or <strVar> sets var=0, strVar=" ", not really needed, just to run such programs
### Resources (unsorted)
```javaScript
return async function() {
_o.print(Date.now()+"\n");
return new Promise(resolve => setTimeout(() => resolve('Hello after 1 second!'), 1000));
//return await 'stop';
}();
return (async function() { return await Promise.resolve('Hello, Async World!'); })();
async function _input(msg) {
return new Promise(resolve => setTimeout(() => resolve(prompt(msg)), 0));
}
- `ERASE var | strVar` sets `var=0; strVar=""`, not really needed, just to run such programs

var name = await _input("What is your name?");
```

```javaScript
let ls = []; Object.keys(allTests).forEach((c) => { ls.push("' " + c + "\n" + Object.keys(allTests[c]).join("\n")); }); console.log(ls.join("\n"));
```

<https://github.com/ohmjs/ohm/blob/main/examples/math/index.html>

<https://nextjournal.com/pangloss/ohm-parsing-made-easy>

<https://stackoverflow.com/questions/60857610/grammar-for-expression-language>

<https://github.com/Gamadril/led-basic-vscode>

<https://ohmjs.org/editor/>

<https://ohmjs.org/docs/releases/ohm-js-16.0#default-semantic-actions>

<https://stackoverflow.com/questions/69762570/rollup-umd-output-format-doesnt-work-however-es-does>
### Not implemented

<https://github.com/beautifier/js-beautify>
after auto border break call cat chain clear cog closein closeout cont copychr
creal cursor dec defint defreal defstr deg delete derr di draw drawr edit ei eof erl err every fill fre
goto graphics himem ink inkey-$ inp joy key let line list load locate mask memory merge move mover new
on openin openout origin out paper peek pen plot plotr poke pos rad randomize release remain renum resume run
save sound spc speed sq swap symbol tab tag tagoff test testr troff tron unt using vpos wait width window write xpos ypos zone

<https://jsonformatter.org/javascript-pretty-print>
### Resources

<https://dev.to/cantem/how-to-write-a-debounce-function-1bdf>
- [Ohm](https://ohmjs.org/) JavaScript parsing toolkit - [Source code](https://github.com/ohmjs/ohm)

//<https://light-basic-interpreter.soft112.com/>
- [CodeMirror](https://codemirror.net/) code editor used for the Locobasic UI -
[Source code](https://github.com/codemirror/dev/) -
[Libraries](https://cdnjs.com/libraries/codemirror)

<https://lume.ufrgs.br/bitstream/handle/10183/190184/001088757.pdf?sequence=1>
- [CPCBasicTS](https://benchmarko.github.io/CPCBasicTS/) CPCBasicTS Unchained - Run CPC BASIC in a Browser - [Source code](https://github.com/benchmarko/CPCBasicTS/)

<https://cdnjs.com/libraries/codemirror>
<https://codemirror.net/5/doc/manual.html#operation>
<https://codemirror.net/5/mode/>
<https://codemirror.net/5/mode/diff/diff.js>
<https://codemirror.net/5/demo/simplemode.html>
<https://github.com/codemirror/legacy-modes/blob/main/mode/pascal.js>
- [CPCBasic](https://benchmarko.github.io/CPCBasic/) CPCBasic Unchained - Run CPC BASIC in a Browser - [Source code](https://github.com/benchmarko/CPCBasic/)

<https://github.com/dfreniche/amstradbasic-vscode/blob/master/syntaxes/amstradbasic.tmLanguage.json>
<https://marketplace.visualstudio.com/items?itemName=CPCReady.basic-language-extension>
<https://retroprogramming.iwashere.eu/ugbasic:user>
<https://ugbasic.iwashere.eu/target/cpc#examples>
- [CPCemu](http://www.cpc-emu.org/) - CPC Emulator, since version 2.0 with very accurate emulation

<https://www.unpkg.com/browse/codemirror@6.65.7/lib/>
- [Amstrad CPC 6128 User Instructions](http://www.cpcwiki.eu/manuals/AmstradCPC6128-hypertext-en-Sinewalker.pdf), or:
[Schneider CPC 6128 Benutzerhandbuch](https://acpc.me/ACME/MANUELS/[DEU]DEUTSCH(GERMAN)/CPC6128_SCHNEIDER[DEU]_Erste_Ausgabe_1985[OCR].pdf)

### Not implemented
- [Locomotive BASIC](https://www.cpcwiki.eu/index.php/Locomotive_BASIC) - Description of the CPC Basic Dialect

after auto border break call cat chain clear cog closein closeout cos cont copychr
creal cursor data dec def defint defreal defstr deg delete derr di draw drawr edit ei eof erl err error every fill fn frame fre
gosub goto graphics himem ink inkey-$ inp input instr joy key let line list load locate mask memory merge mode move mover new
on openin openout origin out paper peek pen plot plotr poke pos rad randomize read release remain renum restore resume return round run
save sgn sound spc speed sq swap symbol tab tag tagoff test testr troff tron unt using vpos wait width window write xpos ypos zone
- Another BASIC compiler: [ugBASIC](https://ugbasic.iwashere.eu/target/cpc#examples)

--
### **mv, 12/2024**
Loading

0 comments on commit 1a6bca0

Please sign in to comment.