aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexis Lockwood2021-06-27 20:57:57 -0400
committerAlexis Lockwood2021-06-27 20:57:57 -0400
commit2c4f7bf097283c707bd7a6ff8a12cd55913d0f1c (patch)
tree92a9855359fae1659a9731a589d0933f3fb5f9ff
parent5c3b9c51ffd1819a68e075bb5be78a598a31fa4c (diff)
ok ok i'll use a markdown readme
-rw-r--r--README435
-rw-r--r--README.md355
2 files changed, 355 insertions, 435 deletions
diff --git a/README b/README
deleted file mode 100644
index d9b73d5..0000000
--- a/README
+++ /dev/null
@@ -1,435 +0,0 @@
-╒═════════════════════════════════════════════════════════════════════════════╕
-│ THIS PROJECT IS NEW and the code is UNFINISHED. DON'T USE IT │
-╘═════════════════════════════════════════════════════════════════════════════╛
-
-LittleScript is a BASIC-like lightweight scripting language for
-microcontrollers. It's meant for adding scriptability to your firmware, not
-for writing your entire code in. LS has some restrictions to make it more
-embedded-friendly:
-
-- Identifiers capped at 6 characters
-- Integer arithmetic by default
-- No garbage collection (strings work like C strings)
-- Allocation from a pool of fixed blocks
-- Code is not stored all at once in RAM - can execute in place from slow
- external devices with some caching, etc.
-
-┌─────────────────┐
-│ Copying and use │
-└─────────────────┘
-
-This software disclaims copyright. Do what you want with it. Be gay, do crime.
-Originally written by Alexis Lockwood in 2021. Ⓐ
-
-┌──────────┐
-│ Building │
-└──────────┘
-
-Dependencies (for all builds):
-
- Python 3
-
-Dependencies (command-line tools only):
-
- libexplain (apt install libexplain-dev)
-
-Building locally, running tests, and cleaning:
-
- make
- make check
- make clean
-
-Cross-compiling only the library:
-
- make CC=avr-gcc EXTRA_FLAGS=-fshort-enums
-
-┌──────────────────────┐
-│ So, how small is it? │
-└──────────────────────┘
-
-The short answer: about 15 kB of flash and a bit over 1 kB of RAM.
-
-*That's not so small!*
-
-Well, it's Littlescript, not Microscript. Not so small that it's impractical to
-use. My target platforms are RAM-limited but have plenty of flash (e.g. larger
-8-bit microcontrollers like AVR-Dx and ATxmega). The flash usage is pretty
-high, around 15 kB (TODO update this when closer to a release). The RAM usage,
-however, is much smaller than a typical BASIC. For example, here is an
-estimation of RAM usage on an 8-bit platform with 16-bit addressing like AVR:
-
- 64x 15B pool entries 960
- Main ls_t object 57
- 2x 64 byte buffers 128
- ---------------------------
- Total usage 1145
-
-All data is allocated from the pool, which can be arbitrarily sized (up to
-64k entries). The 64-byte buffers listed are for storing source code, and are
-not needed if the entire source is already in RAM or ROM.
-
-The following things use pool entries:
-
-- The global scope (1)
-- Named labels, which are stored as they are found (1 each)
-- Variables (1 each for integers, 2 each for other types, plus as many as
- required for the data inside containers: each holds one list element or
- 8 string characters)
-- Expression evaluation (number determined by the expression complexity,
- typically 2-4, these are freed when the expression is finished being
- evaluated)
-- GOSUB calls, to save the execution context (1, plus 1 per argument for the
- new scope)
-
-┌───────────────────────────┐
-│ What's implemented so far │
-└───────────────────────────┘
-
-- Basic high-level API
-- Main lex-parse loop
-- Expression evaluator
-- Variable storage
-- LET, PRINT, GOTO, GOSUB, RETURN keywords
-
-┌───────────┐
-│ Todo list │
-└───────────┘
-
-- Other keywords
-- Non-integer variable types
- - String will always be supported
- - Floating point as optional compile-in
-- Better testing
-- Compile-time options for:
- - Internal asserts for better LS_INTERNAL_ERROR info
- - Hardwiring the ls object to a static, this should reduce code size a lot
- - Disabling features not needed, including
- - Hooks
- - Label cache
- - Fetcher (always execute in RAM)
-- OPER_* should be LS_OPER_*
-
-╒════════════════════════╕
-│ THE SCRIPTING LANGUAGE │
-╘════════════════════════╛
-
-┌───────────┐
-│ Positions │
-└───────────┘
-
-Unlike BASIC which uses line numbers, LittleScript uses a system of named
-labels and numbered small directional jumps. These are inspired by GNU
-Assembler jump targets:
-
- REM examples/positions.bas
- GOSUB hello
- PRINT "Returned"
- END
-
- hello: PRINT "Hello, world"
- GOTO +1
- PRINT "This line is skipped"
- 1: RETURN
-
-There is no requirement of declaration order. If the target of a GOSUB, GOTO,
-or any other line number argument is an identifier that has not been seen yet,
-the interpreter will scan forward until it finds it.
-
-When a number is used, it must be prefixed with + or -, and refers to the next
-line forward or backward matching that number.
-
-
-┌───────┐
-│ Types │
-└───────┘
-
-LittleScript currently has no floating or fixed point support; all numbers are
-integers and this is the default type. As with most BASIC variants, the $
-suffix can be used for string variables:
-
- S$ = "Hello, world"
- PRINT S$
-
-Strings are interpreted as ASCII and can contain all byte values including 0.
-This allows them to be used to contain arbitrary binary data. Internally,
-they are represented as a list of eight-character chunks.
-
-Lists are single-dimensional only, and are implemented using linked lists
-rather than arrays. No DIM is necessary to set them up since new elements can
-always be linked in from the object pool, but DIM may still be used to fail
-early if memory is limited. To optimize sequential access, a pointer to the
-last element accessed is stored in the array object, thus re-accessing the
-last element or the next element is O(1) and iterating over the sequence is
-O(n).
-
-
-┌─────────────┐
-│ Identifiers │
-└─────────────┘
-
-Identifiers are eight characters long (same as a single chunk of a string).
-An identifier must be created (via LET or another assignment keyword) before
-their values can be accessed - they have no default. With no suffix an
-identifier has integer type. With a $ suffix, it is a string, and with a
-() suffix, it is a list.
-
-
-┌───────────┐
-│ Minimizer │
-└───────────┘
-
-Because small embedded arguments are the target, a minimizer is provided that
-condenses a script to a smaller form. In the smallest setting, keywords are
-replaced by single-byte abbreviations - these are listed in the keyword
-documentation.
-
-
-┌───────────┐
-│ Functions │
-└───────────┘
-
-Littlescript supports true functions as an extention to the GOSUB syntax.
-Arguments are given in parentheses in key=value format, and a new scope is
-created in the called function with these values. The AS keyword can create
-a return value:
-
- REM examples/add.bas
- a = 2
- b = 3
- GOSUB Add(a = a, b = b) AS c
- PRINT "a + b = ", c
- END
-
- Add: 'Add(a, b) => a + b
- RETURN a + b
-
-Because a new scope is created, values are passed by value: if the function
-modifies them, the modification only occurs in the new scope. To pass by
-value, name the variables accordingly and omit from the GOSUB statement; they
-will be located and modified in the next scope up.
-
-┌─────────────────────┐
-│ Variables and scope │
-└─────────────────────┘
-
-LittleScript has both global and local variables, differentiated by the case
-of the first character (uppercase = global, lowercase or not a letter = local).
-A new local frame is pushed on GOSUB, and deleted on RETURN.
-
-┌──────────────────┐
-│ Native functions │
-└──────────────────┘
-
-(TODO)
-
-
-┌───────┐
-│ Files │
-└───────┘
-
-Many LittleScript systems will not have a traditional file storage, but may
-want a way to read and write streams. The OPEN, CLOSE, READ, and WRITE keywords
-defer to callbacks provided to LittleScript by the implementing system. The
-following syntax is proposed for stream descriptors:
-
-COMn [speed] [parity] [data]
-FILE device /path/to/file
-
-┌────────────┐
-│ Statements │
-└────────────┘
-
-KEYWORD DOES
-ABS(n)
- Return the absolute value of n
-ASC(s$[, n])
- Return the byte value of the first or nth character
- of s$
-CALL fun(...)
- Call native function `fun` with arguments ...
- See NATIVE FUNCTIONS for the calling convention. `fun`
- must be a literal.
-CAT s$ t$
- Concatenate string t$ onto the end of s$.
-CHR s$ n
- Append the byte value n to the end of string s$.
-CLOSE [#n[,#n]...]
- Close file numbers (or all files, if none specified).
- File (or stream) access is provided by the system
- implementing LittleScript and may not be available.
-DATA expr[ expr...]
- Data for READ
-DEF FN name[(args...)] expr
- Create a function with the given name.
-END
- Stop execution and return to caller
-ERASE ident [ident ...]
- Deallocate any space held by ident and remove that identifier.
- ident may point at a list, a string, a function defined by
- DEF FN, or any other variable. Note that because LittleScript
- is not garbage collected unlike BASIC, you must ERASE anything
- you create if you do not want to leak memory (with the
- exception that everything created in a stack frame is erased
- when that stack frame is popped).
-FN name[(args...)]
- Execute the function specified by `name`, returning its value.
-FOR ident = start TO end [STEP n]
-NEXT [ident]
- Iterate ident from start to end by n (default=1).
- When END is encountered, return back if the
- terminating conditions have not been met. If ident
- is not specified for END, walk outward from the inner
- loop, repeating on the first one that has not
- terminated.
-
- Start and end values are restricted to signed
- 16 bit.
-FREE ident[ ident ...]
- Deallocate any space held by ident and remove that
- identifier. Note that LittleScript is not garbage
- collected - if you're done with a string, free it.
-GOSUB label[(arg[, arg, ...])] [AS ident]
- Push a stack frame and jump to the label. On return,
- if a value was returned, store it in ident.
-
- If args are specified, they are added to the function's local
- scope.
-GOTO label
- Go directly to the label
-HEX s$ n
-OCT s$ n
-DEC s$ n
- Append the hexadecimal, octal, or decimal representation of n
- to s$.
-IF cond THEN ...
-IF cond GOTO ...
- If cond is true, perform the action.
- cond is an expression, and is true if nonzero.
-INPUT #n [AT ?] [UNTIL b] [COUNT n], ident [, ident, ...]
- Read from a stream into variables. Generally only
- string variables are supported, but this is
- implementation-defined. Stop at either the byte
- value b (if UNTIL specified) or the byte count n
- (if COUNT specified), whichever comes first.
- AT is interpreted as for WRITE.
-LEN(s$)
- Return the length of the string
-LET ident [=] val
- Assign the value of a variable. This is the default
- keyword; statements lacking a keyword are parsed
- as LET.
-OPEN s$ AS #n
- Open a file/stream. The "path" is more likely to be
- some form of stream descriptor (see FILES), and may
- be a literal or a string variable.
-PACK n[, n...] AS s$ [: fmt]
-UNPACK n[, n...] FROM s$ [: fmt]
- Pack or unpack the provided values in binary format
- into/from a string, with specific format optionally
- specified. This can be used to assemble arbitrary
- binary data. See PACK AND UNPACK.
-PRINT [TO #n] val [... val...] [,]
- Print values, to a stream or to standard output.
- If the comma is provided, omit newline.
-RANDOMIZE [seed]
- Initialize the PRNG. If seed is not specified, it will be
- initialized with the system's preferred random seed source.
- Not all systems guarantee good quality seeds.
-READ ident[ ident...]
- Read values from DATA statements into the identifiers
- given. Because LittleScript doesn't always have
- access to the entire program at once like BASIC does,
- it works a little differently. By default, the read
- pointer is uninitialized; on the first READ, the
- interpreter will search *forward* for the first DATA
- statement it finds. Once it *executes* that DATA
- statement, the read pointer is reset back to the
- uninitialized state. Every stack context has its own
- read pointer.
-REM
- Comment. Comments may also start with ', and ' may
- appear anywhere in a line.
-RESTORE [label]
- Reset the read pointer. If label is specified, set it
- to the next DATA statement after that label.
- Otherwise, set it to the uninitialized state.
-RETURN [expr]
- Pop a stack frame. If the previous GOSUB specified a return
- location using AS, return expr into it.
-RND
- Return a random number from 0 to 65535.
-SWAP m, n
- Swap the contents of two variables or list indices
-VAL(s$)
- Returns the numerical value of a string
-WHILE cond
-WEND
- Loop as long as cond is true (nonzero).
-WRITE #n [AT ?] val [val ...]
- Write values to a stream. The interpretation of types
- depends on the stream implementation. If AT is
- specified, this implementation-defined value is also
- passed to the implementation (it is usually an
- address, for interfaces like I2C).
-
-┌──────────┐
-│ Keywords │
-└──────────┘
-
-Following is the full list of keywords, and the single-byte abbreviations that
-may be used in their place.
-
-(TODO: update the abbrev values)
-
-ABBREV KEYWORD
-80 ABS
-81 AS
-82 ASC
-83 AT
-84 ATN (not implemented, reserved)
-85 CALL
-86 CAT
-87 CHR
-88 CLOSE
-89 COS (not implemented, reserved)
-8A COUNT
-8B DATA
- DEF
-8C END
- ERASE
-8D ERROR
- FN
-8E FOR
-91 GOSUB
-92 GOTO
-93 IF
-94 INPUT
-95 LEFT
-96 LET
-97 LOG (not implemented, reserved)
-98 MID
-99 NEXT
-9A ON
-9B OPEN
-9C PACK
-9E PRINT
-A0 RANDOMIZE
-A1 READ
-A2 REM
-A3 RESTORE
-A4 RETURN
-A5 RIGHT
-A6 RND
-A7 SIN (not implemented, reserved)
-A8 SQR (not implemented, reserved)
-A9 STEP
-AA SWAP
-AB TAN (not implemented, reserved)
-AC THEN
-AD TO
-AE UNPACK
-AF UNTIL
-B0 VAL
-B1 WEND
-B2 WHILE
-B3 WRITE
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0846124
--- /dev/null
+++ b/README.md
@@ -0,0 +1,355 @@
+```
+╒═════════════════════════════════════════════════════════════════════════════╕
+│ THIS PROJECT IS NEW and the code is UNFINISHED. DON'T USE IT │
+╘═════════════════════════════════════════════════════════════════════════════╛
+```
+
+# Littlescript
+
+LittleScript is a BASIC-like lightweight scripting language for
+microcontrollers. It's meant for adding scriptability to your firmware, not
+for writing your entire code in. LS has some restrictions to make it more
+embedded-friendly:
+
+- Identifiers capped at 6 characters
+- Integer arithmetic by default
+- No garbage collection (strings work like C strings)
+- Allocation from a pool of fixed blocks
+- Code is not stored all at once in RAM - can execute in place from slow
+ external devices with some caching, etc.
+
+## Copying and use
+
+This software disclaims copyright. Do what you want with it. Be gay, do crime.
+Originally written by Alexis Lockwood in 2021. Ⓐ
+
+## Building
+
+### Dependencies (for all builds):
+
+- Python 3
+
+### Dependencies (command-line tools only):
+
+- libexplain (`apt install libexplain-dev`)
+
+### Building locally, running tests, and cleaning:
+
+```
+make
+make check
+make clean
+```
+
+### Cross-compiling only the library:
+
+```
+make CC=avr-gcc EXTRA_FLAGS=-fshort-enums
+```
+
+## So, how small is it?
+
+The short answer: about 15 kB of flash and a bit over 1 kB of RAM.
+
+_That's not so small!_
+
+Well, it's Littlescript, not Microscript. Not so small that it's impractical to
+use. My target platforms are RAM-limited but have plenty of flash (e.g. larger
+8-bit microcontrollers like AVR-Dx and ATxmega). The flash usage is pretty
+high, around 15 kB (TODO update this when closer to a release). The RAM usage,
+however, is much smaller than a typical BASIC. For example, here is an
+estimation of RAM usage on an 8-bit platform with 16-bit addressing like AVR:
+
+| | Count | Each | Total |
+|--------------------|------:|-----:|------:|
+| Pool entries | 64 | 15 | 960 |
+| Main `ls_t` object | 1 | 57 | 57 |
+| Input buffers | 2 | 64 | 128 |
+| **Grand total** | | | 1145 |
+
+All data is allocated from the pool, which can be arbitrarily sized (up to
+64k entries). The 64-byte buffers listed are for storing source code, and are
+not needed if the entire source is already in RAM or ROM.
+
+The following things use pool entries:
+
+- The global scope (1)
+- Named labels, which are stored as they are found (1 each)
+- Variables (1 each for integers, 2 each for other types, plus as many as
+ required for the data inside containers: each holds one list element or
+ 8 string characters)
+- Expression evaluation (number determined by the expression complexity,
+ typically 2-4, these are freed when the expression is finished being
+ evaluated)
+- GOSUB calls, to save the execution context (1, plus 1 per argument for the
+ new scope)
+
+## What's implemented so far
+
+- Basic high-level API
+- Main lex-parse loop
+- Expression evaluator
+- Variable storage
+- LET, PRINT, GOTO, GOSUB, RETURN keywords
+
+## Todo list
+
+- Other keywords
+- Non-integer variable types
+ - String will always be supported
+ - Floating point as optional compile-in
+- Better testing
+- Compile-time options for:
+ - Internal asserts for better `LS_INTERNAL_ERROR` info
+ - Hardwiring the ls object to a static, this should reduce code size a lot
+ - Disabling features not needed, including
+ - Hooks
+ - Label cache
+ - Fetcher (always execute in RAM)
+- `OPER` should be `LS_OPER`
+
+# THE SCRIPTING LANGUAGE
+
+## Positions
+
+Unlike BASIC which uses line numbers, LittleScript uses a system of named
+labels and numbered small directional jumps. These are inspired by GNU
+Assembler jump targets:
+
+```
+REM examples/positions.bas
+GOSUB hello
+PRINT "Returned"
+END
+
+hello: PRINT "Hello, world"
+ GOTO +1
+ PRINT "This line is skipped"
+1: RETURN
+```
+
+There is no requirement of declaration order. If the target of a GOSUB, GOTO,
+or any other line number argument is an identifier that has not been seen yet,
+the interpreter will scan forward until it finds it.
+
+When a number is used, it must be prefixed with `+` or `-`, and refers to the
+next line forward or backward matching that number.
+
+Note that named labels consume memory, and are meant to be used for globally
+important sections like functions. For local control flow, use numbers.
+
+
+## Types
+
+**Note** that strings are not implemented yet.
+
+LittleScript currently has no floating or fixed point support; all numbers are
+integers and this is the default type. As with most BASIC variants, the `$`
+suffix can be used for string variables:
+
+```
+S$ = "Hello, world"
+PRINT S$
+```
+
+Strings are interpreted as ASCII and can contain all byte values including 0.
+This allows them to be used to contain arbitrary binary data. Internally,
+they are represented as a list of eight-character chunks.
+
+Lists are single-dimensional only, and are implemented using linked lists
+rather than arrays. No DIM is necessary to set them up since new elements can
+always be linked in from the object pool, but DIM may still be used to fail
+early if memory is limited. To optimize sequential access, a pointer to the
+last element accessed is stored in the array object, thus re-accessing the
+last element or the next element is O(1) and iterating over the sequence is
+O(n).
+
+
+## Identifiers
+
+Identifiers are six characters long. An identifier must be created (via LET or
+another assignment keyword) before their values can be accessed - they have no
+default. With no suffix, an identifier has integer type. With a $ suffix, it is
+a string, and with a () suffix, it is a list.
+
+
+## Minifier
+
+Because small embedded arguments are the target, a minifier is provided that
+condenses a script to a smaller form. In particular, every keyword becomes a
+single byte. The minifier can also un-minify a script to make it readable
+again (though most comments are lost).
+
+
+## Functions
+
+Littlescript supports true functions as an extention to the GOSUB syntax.
+Arguments are given in parentheses in key=value format, and a new scope is
+created in the called function with these values. The AS keyword can create
+a return value:
+
+```
+REM examples/add.bas
+a = 2
+b = 3
+GOSUB Add(a = a, b = b) AS c
+PRINT "a + b = ", c
+END
+
+REM It is recommended to document the function arguments and return value with
+REM a comment.
+Add: 'Add(a, b) => a + b
+ RETURN a + b
+```
+
+Because a new scope is created, values are passed by value: if the function
+modifies them, the modification only occurs in the new scope. To pass by
+value, name the variables accordingly and omit from the GOSUB statement; they
+will be located and modified in the next scope up.
+
+
+## Native functions
+
+(TODO)
+
+
+## Files
+
+Many LittleScript systems will not have a traditional file storage, but may
+want a way to read and write streams. The OPEN, CLOSE, READ, and WRITE keywords
+defer to callbacks provided to LittleScript by the implementing system. The
+following syntax is proposed for stream descriptors:
+
+```
+COMn [speed] [parity] [data]
+FILE device /path/to/file
+```
+
+## Statements
+
+(More verbose documentation is provided below, when useful)
+
+| **KEYWORD** | **DOES** |
+|-------------------------------|-|
+| `ABS n` | Return the absolute value of `n` |
+| `ASC(s$[, n])` | Return the byte value of the first or `n`th character of `s$` |
+| `BIN s$ n` | Append the binary representation of `n` to `s$` |
+| `CALL fun(...)` | Call native function `fun` with arguments `...` |
+| `CAT s$ t$` | Concatenate string `t$` onto the end of `s$` |
+| `CHR s$ n` | Append the byte value `n` onto the end of string `s$` |
+| `CLOSE [#n[,#n]...]` | Close file numbers (or all files). Defined by implementation |
+| `DATA expr[ expr...]`` | Data for `READ` |
+| `DEC s$ n` | Append the decimal representation of `n` to `s$` |
+| `DEF FN name(args...) expr` | Create a function with the given name |
+| `END` | Stop execution and return to caller |
+| `ERASE ident [, ident...]` | Deallocate any space held by `ident` and delete it. |
+| `FN name(args...)` | Execute the function `name`, returning its value |
+| `FOR ident = a TO b [STEP c]` | Repeat until `NEXT` with `ident` running from `a` to `b` in steps of `c` |
+| `GOSUB f[(args...)] [AS s]` | Call label `f` as a function, optionally passing and returning data |
+| `GOTO label` | Go directly to `label` |
+| `GOTO +n` or `GOTO -n` | Go directly forward or backward to numbered label `n` |
+| `HEX s$ n` | Append the hexadecimal representation of `n` to `s$` |
+| `IF cond THEN ...` | If `cond` is nonzero, execute statement `...` |
+| `IF cond GOTO ...` | Equivalent to `IF cond THEN GOTO ...` |
+| `LEN(s$)` | Return the length of the string or list |
+| `[LET] ident = val` | Assign the variable. |
+| `NEXT ident` | Go back to `FOR` (see that keyword). `ident` is mandatory. |
+| `OCT s$ n` | Append the octal representation of `n` to `s$` |
+| `ON expr GOTO n1, n2, ...` | Go to the `expr`th line given. |
+| `ON expr GOSUB [AS s] s1, s2, ...` | Call the `expr`th subroutine given. |
+| `PACK n... AS s$ [USING fmt] | Pack values into binary format |
+| `PRINT [TO #n] val... [,] | Print values, optionally to file `#n`, optionally without newline (`,`) |
+| `RANDOMIZE [seed]` | Initialize the PRNG. |
+| `READ ident...` | Read from `DATA` statements |
+| `REM ...` | Comment |
+| `RESTORE [label]` | Restore the read pointer, either to null or to the location of `label` |
+| `RETURN [expr]` | Return to the last GOSUB, optionally returning a value |
+| `RND` | Return a random number from 0 to 65535. |
+| `SWAP m, n` | Swap the contents of two variables or list indices |
+| `UNPACK n... FROM s$ [USING fmt] | Unpack values from binary format |
+| `VAL(s$)1 | Returns the numerical value of a string |
+| `WEND` | Go back to the last `WHILE` |
+| `WHILE cond` | Loop as long as `cond` is nonzero. Creates a scope. |
+
+(TODO: define file I/O variables. This will probably look nothing like
+traditional BASICs)
+
+### `ERASE ident [, ident...]`
+
+Note that because there is no garbage collection, you are responsible for
+erasing any unused variables.
+
+### `FOR ident = a TO b [STEP c]`
+
+If the iterator does not land directly on `b` because of the step size,
+iteration stops when it passes `b`.
+`
+Start and end values are restricted to signed 16 bit.
+
+### `GOSUB f[(args...)] [AS s]`
+
+Pushes a new stack frame and jumps to label `f`. Label must be named, not
+numeric. `args...` should be formatted as `key1 = value1, key2 = value2, ...`;
+each argument will be added to the new scope as a variable. On return, if a
+value has been returned it will be stored in `s`, which will be created if
+necessary.
+
+### `[LET] ident = val`
+
+This is the default keyword: the `LET` itself may be omitted.
+
+### `ON expr GOTO n1, n2, ...`
+
+`n1` and so on can be anything valid to appear after `GOTO`. If `expr` is
+not positive or if it is too high, proceed directly to the next statement.
+There is no bound for the number of choices.
+
+### `ON expr GOSUB [AS s] s1, s2, ...`
+
+See also `ON expr GOTO`. If `AS` is given, the return value of the subroutine
+will be placed in this variable (if the subroutine returns nothing, the
+variable is not created or modified). Each choice may have arguments:
+
+```
+ON x GOSUB AS rtn FOO(x), FOO(x + 2), BAR(x)
+```
+
+### `PACK` and `UNPACK`
+
+These work a bit like Python's struct.pack/struct.unpack and can be used to
+assemble arbirary binary data. TODO: format TBD.
+
+### `RANDOMIZE` and `RND`
+
+If the seed is not specified for `RANDOMIZE`, it will be initialized with the
+system's preferred random seed source. Not all systems guarantee good quality
+seeds.
+
+### `READ` and `DATA`
+
+Because Littlescript doesn't have access to the entire program at once like
+a traditional BASIC, READ/DATA work a bit differently. By default, the read
+pointer is uninitialized; on the first `READ`, the interpreter seeks forward
+to the first `DATA` statement. Once it _executes_ the `DATA` statement, the
+read pointer is reset. Every stack context has its own read pointer. The
+intended use is for every group of `READ` statements to have a set of `DATA`
+statements just a short while after.
+
+Note that if the input medium is slow, for example a serial EEPROM, the
+distance between the beginning of the `READ` statement and the end of its
+associated `DATA` statements should be small enough to fit in whatever cache
+the implementation uses. Otherwise, READ and DATA operations could be quite
+slow.
+
+### `REM`
+
+Comments may also start with an apostrophe (`'`), and apostrophe comments
+may appear anywhere in a line.
+
+Note that the minifier strips out all apostrophe comments, but may be
+configured to leave alone `REM` comments. It is therefore recommended that
+any important comments that must remain in the minified text use `REM`.
+
+## Keywords
+
+TODO: list all valid keywords and their abbreviations