Table of Contents

- [Modules](#modules) - [Grammer Routines](#grammer-routines) ## Modules Modules help to extend the Grammer language. Unfortunately, there is a lot of overhead involved, so locating and loading routines is rather slow. As such, I recommend modules where speed isn't critical. Where it is, you can still use assembly programs and hex codes. Here is the format of a module: ``` ;All routines must be 768 bytes or less ;They get copied to moduleExec #include "grammer2.5.inc" .db "Gram" ;Magic number .dw MODULE_VERSION ;Minimum module version. Uses the current version. .dw TableSize ;Number of elements on the table .dw func0 .dw func1 ... .org 0 ;Set the code counter to 0 here func0: .db "MYFUNC0",$10 \ .dw size_of_func0_code ;func0 ;code func1: .db "MYFUNC1",$10 \ .dw size_of_func1_code ;func1 ;code ``` Here is the template that I use: ``` ;All routines must be 768 bytes or less ;They get copied to moduleExec #include "grammer2.5.inc" .db "Gram" ;magic number .dw MODULE_VERSION ;minimum version .dw (mod_start-2-$)/2 ;num elements .dw func0-mod_start ;offset to func0 .dw func1-mod_start ;offset to func1 .dw func2-mod_start ; ... .dw funcn-mod_start ;offset to funcn mod_start: func0: .db "func0",$10 \ .dw func1-$-2 ;<> func1: .db "func1",$10 \ .dw func2-$-2 ;<> func2: ;... funcn: .db "funcn",$10 \ .dw mod_end-$-2 ;<> mod_end: ``` There is already a lot of overhead with module look-up and loading, so to minimize lookup time a little, Grammer uses binary search to locate functions. As a consequence, the table needs to be sorted by their strings. For example, if func0 is called "XXX" and func1 is called "AAA", then the table should have `.dw func1` before `.dw func0`. Another issue to take into account is that the code is copied to `moduleExec`. At this time, this limits your code to 768 bytes, and also jumps and calls need to be relocated if they occur in your code. *This does* **not** *apply to `jr` instructions as these jump a relative distance.* So for example, let's look at this super complicated piece of code and make it even more complicated: ``` func0: .db "XXX",$10 .dw func0_end-func0_start func0_start: call label ret label: ret func0_end: ``` That call will almost certainly crash. Instead, you need: ``` func0: .db "XXX",$10 .dw funco_end-func0_start func0_start: call moduleExec+label-func0_start ret label: ret func0_end: ``` Again, this only applies to `jp` and `call` instructions, and only those for routines within the code. You can still make calls to Grammer routines without all the `moduleExec+` stuff. One final note before we get to Grammer routines: the function names must be terminated by a `0` byte, a space token (`$29`), or an open parentheses (`$10`). ## Grammer Routines To use the parser and some of the more useful routines, you can use Grammer's jump table. **NOTE:** *This jump table might change in the future, which is why the `minimum module version` field is necessary.* You can use [`grammer2.5.inc`](grammer2.5.inc) for all the equates, available to modules. First, it is important to note that any routines that invoke the parser expect BC to hold the "ans" value. | call | description | example | |:------------------- |:----------- |:-------- | | cmdJmp | `A` is holds the first byte of the token to evaluate, `(parsePtr)` points to the next byte to parse | Suppose your function mimics `sin(`, given `$C2` is the `sin(` token: `ld a,$C2 \ jp cmdJmp` | | ~~ProgramAccessStart~~ | ~~This takes a string in the OS Ans variable as the name of a var. The var is then executed as a Grammer program.~~ | ~~`jp ProgramAccessStart`~~ | | CompatCall | I can't remember. | Sorry. | | SelectedProg | Why tf is this in the jump table? | Just why? | | ExecOP1 | OP1 contains the name of the program to execute as Grammer code. | `ld hl,prog_name \ rst rMov9ToOP1 \ jp ExecOP1` | | ParseFullArg | This parses an argument. `BC` is the input, `HL` points to the next byte to parse. **Note:** *Use this to parse the first argument in most cases!* | `call ParseFullArg` | | ParseNextFullArg | This parses an argument. `BC` is the input, `HL` points to the next byte to parse. **Note:** *Use this to parse subsequent arguments!* | `call ParseNextFullArg` | | ParseNextFullArg_Inc | `HL` points to the byte before the code to parse. | `ld hl,my_code \ call ParseNextFullArg_Inc \ ...` | | ParseCondition | Use this to parse a condition, as in for `While`, `Repeat`, etc. | `jp ParseCondition` | | DrawRectToGraph | See [drawrect.z80](../src/gfx/drawrect.z80) | Draw an inverted rectangle in the upper-left quarter of the screen: `ld a,2 \ ld bc,$0000 \ ld de,$3020 \ call DrawRectToGraph` | | GraphToLCD | This copies the gbuf to the LCD. (BufPtr) and (GrayBufPtr) point to the primary and secondary buffers respectively. | `call GraphToLCD` | | VPutSC | `B` is the char to draw, (textrow) and (textcol) are where to draw | Draw an exclamation point at (x,y): `ld hl,$xxyy \ ld b,'!' \ call VPutSC` | | GetKey | This reads the current keypress into the `A` register. | `call GetKey` | | GetGrammerText | This is used to convert a token string to an ASCII string. `HL` points to the start, returns `BC` is the size, `DE` points to the converted string. | `ld hl,tok_str \ call GetGrammerText` | | GetGrammerText_DE | This is used to convert a token string to an ASCII string. **`DE` points to where to output the converted text** `HL` points to the start, returns `BC` is the size, `DE` points to the converted string. | Convert from the string at HL to OP1: `ld hl,tok_str \ ld de,OP1 \ call GetGrammerText_DE` | | GetGrammerStr | Gets the size of a token string. HL` points to the string. Returns `BC` as the size, `HL` points to the end of the input string. | `ld hl,tok_str \ call GetGrammerStr` | | GetKeyDebounce | This reads a debounced keypress into the `A` register. This is best for things like menus where you don't want key presses to be *too* fast. | `call GetKeyDebounce` | | SearchString | Use this to find a substring within a string. See [searchstring.z80](../src/cmd/searchstring.z80) | _ | If you wanted to make your own addition routine that looked like: ``` $ADD(x,y ``` Your module code would look something like: ``` func_add: .db "ADD",$10 \ .dw nextfunc-$-2 call ParseFullArg ;parse the first argument push bc ;Save the result call ParseNextFullArg ;parse subsequent arguments pop hl ;pop first arg back into HL add hl,bc ;Second arg in BC. Add the two. ld b,h ;\ Store the result in BC ld c,l ;/ ret nextfunc: ```