From d9726a333f1e04e310b3732bd73c7d35f07ea92b Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Sat, 4 Nov 2017 21:01:16 +0000 Subject: [PATCH] baisc: nothing to see here - putting the framework together for it --- Applications/basic/README | 1 + Applications/basic/basic.h | 9 + Applications/basic/calc.c | 365 +++++++++++++++++++ Applications/basic/maketokens.c | 42 +++ Applications/basic/tokenizer.c | 622 ++++++++++++++++++++++++++++++++ Applications/basic/tokens.h | 25 ++ 6 files changed, 1064 insertions(+) create mode 100644 Applications/basic/README create mode 100644 Applications/basic/basic.h create mode 100644 Applications/basic/calc.c create mode 100644 Applications/basic/maketokens.c create mode 100644 Applications/basic/tokenizer.c create mode 100644 Applications/basic/tokens.h diff --git a/Applications/basic/README b/Applications/basic/README new file mode 100644 index 00000000..cf5429d6 --- /dev/null +++ b/Applications/basic/README @@ -0,0 +1 @@ +Internal working tree for the replacmeent of ubasic diff --git a/Applications/basic/basic.h b/Applications/basic/basic.h new file mode 100644 index 00000000..ba9ec685 --- /dev/null +++ b/Applications/basic/basic.h @@ -0,0 +1,9 @@ +extern void error(int n); +void execute_line_content(uint8_t *l); + + +#define MISSING_QUOTE 1 +#define OUT_OF_MEMORY 2 +#define BAD_LINE_NUMBER 3 +#define SYNTAX_ERROR 4 +#define RETURN_UDNERFLOW 5 \ No newline at end of file diff --git a/Applications/basic/calc.c b/Applications/basic/calc.c new file mode 100644 index 00000000..f0ccafa2 --- /dev/null +++ b/Applications/basic/calc.c @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include +#include + + +static const char *ctypes[9] = { + "eof", + "constant", + "leftassoc", + "leftparen", + "rightparen", + "function", + "unary", + "symbol", + "postfix", +}; + +struct token { + uint8_t tok; +#define EOF_TOKEN 128 +#define INTEGER 129 +#define SIZEOF 130 + uint8_t class; +#define CONSTANT 1 +#define LEFT_ASSOC 2 +#define LEFTPAREN 3 +#define RIGHTPAREN 4 +#define FUNCTION 5 +#define UNARY 6 +#define SYMBOL 7 +#define POSTFIX 8 + uint16_t data; +}; + +/* For now before we pack it all as an integer value */ +#define CLASS(x) ((x)->class) +#define PRECEDENCE(x) ((x)->data) + +#define OPMAX 32 + +char *input; + +static const char ops[] = {"+-*/%"}; + +static struct token eoftok = { + 0, + EOF_TOKEN, + 0 +}; + +static struct token eoftok2 = { + ';', + EOF_TOKEN, + 0 +}; + +static struct token peek; +static int peeked; + +struct token *token(void) +{ + static struct token n; + char *x; + + if (peeked) { + peeked = 0; + return &peek; + } + + while(isspace(*input)) + input++; + if (*input == 0) + return &eoftok; + + if (*input == ';') + return &eoftok2; + + if ((x = strchr(ops, *input)) != NULL) { + n.tok = *input; /* Op code */ + n.class = LEFT_ASSOC; + n.data = 1 + x - ops; /* Priority */ + input++; + return &n; + } + if (*input == '(') { + n.tok = *input; + n.class = LEFTPAREN; + input++; + return &n; + } + if (*input == ')') { + n.tok = *input; + n.class = RIGHTPAREN; + input++; + return &n; + } + if (*input == '!') { + n.tok = '!'; + n.class = UNARY; + n.data = 0; + input++; + return &n; + } + /* symbol or numeric value */ + if (isdigit(*input)) { + errno = 0; + n.data = strtol(input, &input, 0); + if (errno) { + fprintf(stderr, "invalid integer.\n"); + exit(1); + } + n.class = CONSTANT; + return &n; + } + if (isalpha(*input) || *input == '_') { + char *p = input++; + while(*input && (isalnum(*input) || *input == '_')) + input++; + printf("Will look for symbol\n"); + n.class = SYMBOL; + n.data = 1; /* Will be symbol index */ + return &n; + } + /* To add for testing function and comma */ + fprintf(stderr, "?? %s\n", input); + exit(1); +} + +const struct token *token_peek(void) +{ + if (peeked) + return &peek; + memcpy(&peek, token(), sizeof(peek)); + peeked = 1; + return &peek; +} + +struct token opstack[OPMAX]; +struct token *optop = opstack; +uint16_t datastack[OPMAX]; +uint16_t *datatop = datastack; + +void push(uint16_t data) +{ + if (datatop == &datastack[OPMAX]) { + fprintf(stderr, "push: too complex\n"); + exit(1); + } + *datatop++ = data; +} + +uint16_t pop(void) +{ + if (datatop == datastack) { + fprintf(stderr, "pop: underflow\n"); + exit(1); + } + return *--datatop; +} + +void popop(void) +{ + struct token t; + uint16_t tmp; + if (optop == opstack) { + fprintf(stderr, "popop: underflow\n"); + exit(1); + } + t = *--optop; + switch(t.tok) { + case '(': + break; + case '+': + push(pop() + pop()); + break; + case '*': + push(pop() * pop()); + break; + case '/': + tmp = pop(); + push(pop() / tmp); + break; + case '%': + tmp = pop(); + push(pop() % tmp); + break; + case '-': + tmp = pop(); + if(t.class == LEFT_ASSOC) + push(pop() - tmp); + else + push(-tmp); + break; + case '!': + push(!pop()); + break; + default: + if (t.class == FUNCTION) { + /* ??? */ + } + fprintf(stderr,"popop: bad op '%c'\n", t.tok); + exit(1); + } +} + +void pushop(const struct token *t) +{ + if (optop == &opstack[OPMAX]) { + fprintf(stderr, "pushop: too complex\n"); + exit(1); + } +#ifdef DEBUG + printf("pushop %d %c\n",t->class,t->tok); +#endif + *optop++ = *t; +} + +void do_popout(uint8_t stopclass) +{ + while(optop != opstack) { + if (optop[-1].class == stopclass) + return; + popop(); + } +} + +void popout(void) { + do_popout(LEFTPAREN); + if (optop == opstack) + fprintf(stderr, "Unbalanced brackets in expression.\n"); +} + +/* Do we need to be smarter here and spot ?'s left onthe opstack too ? */ +void popout_final(void) +{ + do_popout(LEFTPAREN); + if (optop != opstack) + fprintf(stderr, "Unbalanced brackets expression end.\n"); + printf("Answer = %u\n", pop()); + if (datatop != datastack) + fprintf(stderr, "Unbalanced data end.\n"); +} + + +static uint8_t next; +#define LPAREN 1 /* A left bracket - eg func( */ +#define OPERAND 2 /* Any operand - symbol/constnat */ +#define OPERATOR 4 /* An operator */ +#define RPAREN 8 /* A right bracket - closing typedef */ + + +/* We use bitmasks. The need mask must overlap the types passed. Usually + it's a single bit but functions require a left-paren only, while a + a left-paren is also valid for an operand */ + +void neednext(uint8_t h, uint8_t n) +{ + if (next & h) { + next = n; + return; + } + fprintf(stderr, "Syntax error: Want %x got %x.\n", + (int)h, (int)next); +} + +static const struct token fncall = { + FUNCTION, + FUNCTION, + 0 +}; + +/* Write out an expression tree as we linearly parse the code. We arrange + things so we can write out all constants and in particular strings as + we go so we don't buffer anything we don't need to in this pass + + TODO: ?: finish postfix operators (x++, x--, []) and typecasts (type)x + Postfix is generalising the function call stuff. We may well want + to defer knowing about function calls to tree parsing and just do + generic markers and logic, however we do need to handle [expr] at + this layer. sizeof may force our hands a bit + + Note that at this level we don't care about types. We will do type + checking and type conversion insertions when we parse the resulting + trees + + Would it be better to recurse , and do ?: at a higher level ? + + */ + +const struct token *eval(int in_decl) +{ + struct token *t; + next = OPERAND; + + while((t = token())->class != EOF_TOKEN) { +#if DEBUG + printf("|Token %d Class %s Data %d\n", + t->tok, ctypes[t->class], t->data); +#endif + switch(CLASS(t)) { + case CONSTANT: + neednext(OPERAND, OPERATOR); + push(t->data); + break; + case SYMBOL: + /* symbols might be functions - tidy this up */ + neednext(OPERAND, OPERATOR|LPAREN); + push(t->data); + break; + case UNARY: + neednext(OPERAND, OPERAND); + pushop(t); + break; + case LEFT_ASSOC: + /* Umary minus is special */ + if (t->tok == '-' && next == OPERAND) { + neednext(OPERAND, OPERAND); + t->class = UNARY; + pushop(t); + break; + } + case FUNCTION: + neednext(OPERATOR, OPERAND); + while (optop > opstack && optop->class == LEFT_ASSOC && + PRECEDENCE(t) <= PRECEDENCE(optop)) + popop(); + pushop(t); + if (optop > opstack && optop->class == FUNCTION) + popop(); + break; + case LEFTPAREN: + if (next & OPERATOR) { + /* Function call */ + neednext(LPAREN,OPERAND); + pushop(&fncall); + } else + /* Else precedence bracketing */ + neednext(OPERAND|LPAREN,OPERAND); + pushop(t); + break; + case RIGHTPAREN: + neednext(OPERATOR|RPAREN, OPERATOR); + popout(); + popop(); /* drop the left paren */ + break; + default: /* Assume we've hit the expression end */ + goto done; + } + } +done: + neednext(OPERATOR,0); + popout_final(); + return t; +} + +int main(int argc, char *argv[]) +{ + char buf[512]; + fgets(buf, 512, stdin); + input = buf; + eval(0); +} diff --git a/Applications/basic/maketokens.c b/Applications/basic/maketokens.c new file mode 100644 index 00000000..bdd985ff --- /dev/null +++ b/Applications/basic/maketokens.c @@ -0,0 +1,42 @@ +#include +#include + +char *tokens[] = { + "PRINT", + "IF", + "GO", + "TO", + "SUB", + "LET", + "INPUT", + "RETURN", + "CLEAR", + "LIST", + "RUN", + "END", + NULL +}; + +/* Print the token table. All tokens must be at least two bytes long */ +int main(int argc, char *argv[]) +{ + int tokbase = 192; + char **p = tokens; + char *x; + printf("#define TOKEN_BASE %d\n", tokbase); + while(x = *p) { + putchar('\t'); + while(x[1]) + printf("'%c',", *x++); + printf("0x%02X,\n", *x|0x80); + printf("#define TOK_"); + x = *p; + while(*x) { + if (isalnum(*x)) + putchar(*x); + x++; + } + printf(" %d\n", tokbase++); + p++; + } +} diff --git a/Applications/basic/tokenizer.c b/Applications/basic/tokenizer.c new file mode 100644 index 00000000..400683b5 --- /dev/null +++ b/Applications/basic/tokenizer.c @@ -0,0 +1,622 @@ +/* + * This module deals with keeping the BASIC program present and tokenized + * + * The code ends with a fake like 65535 which we must ensure the user + * never gets to replace as it avoids us ever having to special case + * last line. + * + * The interpreter core uses memory above lines_end for data. We don't + * do anything to make life easy for it if lines are added. + */ + +#include +#include +#include +#include +#include + +#include "basic.h" + +uint8_t blob[2048]; +uint8_t *lines = blob; +uint8_t *lines_end; +uint8_t *lines_limit = &blob[2047]; +uint8_t *execp; +uint16_t run_line; +uint8_t *next_line; +uint8_t running; +uint8_t pending_go; +uint16_t go_target; +uint8_t no_colon; +uint16_t editbase; +uint16_t newest; + + +uint8_t cursor_x; +uint8_t cursor_y; +uint8_t width = 80; +uint8_t height = 24; + +static uint8_t last_ch; + +static void print_ch(uint8_t c) +{ + if (c == 13) + cursor_x = 0; + else if (c == 10) { + cursor_x = 0; + if (cursor_y < height - 1) + cursor_y++; + } + else if (c == 8) { + if (cursor_x) + cursor_x--; + else + return; + } + else if (c == '\t') { + do { + print_ch(' '); + } while(cursor_x % 8); + return; + } else { + cursor_x++; + if (cursor_x == width - 1) { + if (cursor_y < height - 1) + cursor_y++; + cursor_x = 0; + } + } + putchar(c); + last_ch = c; +} + +static void print_str(char *c) +{ + while(*c) + print_ch((uint8_t)*c++); +} + + +static void printat(uint8_t y, uint8_t x) +{ + printf("\033[%d;%dH", y, x); + cursor_x = x; + cursor_y = y; +} + +static void wipe(void) +{ + printat(0,0); + printf("\033[J"); +} + +static char toktab[] = { +#include "tokens.h" + 0xFF +}; + +static uint8_t *outptr; + +/* Input is a 7bit basic token with 0x80 on the last byte */ +/* Could binary search this as we can always find a marker with 0x80 to + split the search in halfish */ + +uint8_t tokget(uint8_t * c) +{ + uint8_t *ptr = toktab; + uint8_t *cp = c; + uint8_t v; + uint8_t n = TOKEN_BASE; + + while (*ptr != 0xFF) { + /* keep matching bytes */ + while (*ptr == *cp) { + /* If we match and 0x80 is set then we matched end bytes + and we got a hit */ + if (*ptr++ & 0x80) + return n; + /* If we matched top bit clear then keep matching */ + cp++; + } + /* We matched until a '.' in the input that indicates a short form + except for the case of '.' */ + if (*cp == ('.' | 0x80) && cp != c) + return n; + /* When the match fails walk on until the start of the next match + in the table */ + while (!(*ptr++ & 0x80)); + cp = c; + n++; + } + return 0; +} + +void tokenize_line(uint8_t * input) +{ + uint8_t *p = input; + uint8_t t; + + while (*p) { + /* Strip spaces */ + while (*p && isspace(*p)) + p++; + if (*p == 0) + break; + /* Strings require special handling so we don't tokenize them */ + if (*p == '"') { + *outptr++ = *p++; + while (*p != '"') { + if (*p == 0) + error(MISSING_QUOTE); + *outptr++ = *p++; + } + *outptr++ = *p++; + } else { + uint8_t *f = p++; + /* Otherwise we keep adding letters and trying to tokenize. This + is inefficient but we don't do it often */ + while (isalnum(*p) || *p == '.') { + *p |= 0x80; + t = tokget(f); + if (t == 0) + *p++ &= 0x7f; + else { + *outptr++ = t; + p++; + break; + } + } + if (t) + continue; + *outptr++ = *f; + p = f + 1; + } + } + *outptr++ = 0; +} + +/* Find the line (or next one). As we have a dummy line 65535 this never + fails to find something */ +uint8_t *find_line(uint16_t line) +{ + uint8_t *ptr = lines; + /* We pad lines on alignment icky processors */ + while (*(uint16_t *) ptr < line) + ptr += *(uint16_t *) (ptr + 2); /* Length in bytes */ + return ptr; +} + +/* Currently we linear search. It's possible to do clever things but we don't + as it's not a performance critical spot. Pass 0 size for a delete */ +void insdel_line(uint16_t num, uint8_t * toks, uint16_t size) +{ + uint8_t *ptr = lines; + int16_t shift = size; + uint8_t *cptr; + + if (size == 1) { + size = 0; /* End mark only - delete */ + shift = 0; + } + if (size) { + size += 4; /* Length, number */ + shift += 4; + } + + /* This is conservative - we should check for deletion/insertion to see + if a replaced line will fit.. but it's close to the line anyway so + already deep in trouble */ + + if (size + lines_end > lines_limit) + error(OUT_OF_MEMORY); + + /* We pad lines on alignment icky processors */ + while (*(uint16_t *) ptr < num) + ptr += *(uint16_t *) (ptr + 2); /* Length in bytes */ + + cptr = ptr; + + /* Our line exists, we will need to shift by the difference in line + size, and also we need to move relative to the next line */ + if (*(uint16_t *) ptr == num) { + /* Find the next line */ + shift -= *(uint16_t *) (ptr + 2); + cptr = ptr + *(uint16_t *) (ptr + 2); /* Length in bytes */ + } + if (shift) { + memmove(cptr + shift, cptr, lines_end - cptr); + lines_end += shift; + } + if (size) { + *(uint16_t *)ptr = num; + *(uint16_t *)(ptr + 2) = size; + memcpy(ptr + 4, toks, size - 4); + lines_end += size; + } +} + +uint8_t *detok(uint8_t c) +{ + static uint8_t rv; + uint8_t *p; + + rv = c | 0x80; + if (c < TOKEN_BASE) + return &rv; + p = toktab; + + c -= TOKEN_BASE; + while (c--) { + while (!(*p++ & 0x80)); + if (*p == 0xFF) { + fprintf(stderr, "badtok\n"); + exit(1); + } + } + return p; +} + +void print_line(uint8_t * p) +{ + uint8_t *o; + while (*p) { + /* Quoted text */ + if (*p == '"') { + do { + print_ch(*p++); + } while (*p != '"'); + print_ch(*p++); + /* Symbols */ + } else if (*p < TOKEN_BASE) { + print_ch(*p); + p++; + } + /* Tokens */ + else { + if (last_ch != ' ') + print_ch(' '); + o = detok(*p++); + do { + print_ch(*o & 0x7F); + } while (!(*o++ & 0x80)); + if (o[-1] != '(') + print_ch(' '); + } + } +} + +void find_exec_line(uint16_t l) +{ + uint8_t *ptr = find_line(l); + run_line = *(uint16_t *)ptr; + next_line = ptr + *(uint16_t *)(ptr + 2); +} + + +/* Might also be worth having renumber but that involves parsing each line + and fixing up anything that's a constant for goto/gosub etc - messy */ + +void error(int n) +{ + char tmpbuf[64]; + sprintf(tmpbuf, "\nError %d/%d\n", n, run_line); + print_str(tmpbuf); + running = 0; + longjmp(aborted, 1); +} + +/* Will use the more general parser once present */ +uint8_t *linenumber(uint8_t * n, uint16_t * v) +{ + uint16_t vp = 0, vn; + while (isdigit(*n)) { + vn = vp * 10 + *n - '0'; + if (vn < vp) + goto bad; + vp = vn; + n++; + } + *v = vp; + return n; + + bad: + error(BAD_LINE_NUMBER); + return NULL; +} + +/* + * Wipe and initialize the program area with the single dummy line 0xFFFF + */ + +void new_command(void) +{ +// statememt_end(); + lines[0] = 0xFF; + lines[1] = 0xFF; + /* FIXME: endianness */ + lines[2] = 0x01; + lines[3] = 0x00; + lines[4] = TOK_END; /* An END marker so we always end */ + lines[5] = 0x00; + lines_end = lines + 6; +// variables = lines_end; +// *variables++ = 0; +} + +void cls_command(void) +{ + wipe(); +} + +void clear_command(void) +{ +} + + +/* + * Wipe and initialize the program area with the single dummy line 0xFFFF + */ + +void new_command(void) +{ +// statememt_end(); + lines[0] = 0xFF; + lines[1] = 0xFF; + /* FIXME: endianness */ + lines[2] = 0x01; + lines[3] = 0x00; + lines[4] = TOK_END; /* An END marker so we always end */ + lines[5] = 0x00; + lines_end = lines + 6; + clear_command(); +} + +void do_list_command(uint16_t low, uint16_t high, int must) +{ + char tmp[6]; + uint8_t *p; + uint16_t n; + int c = 0; + uint8_t seen = 0; + +retry: + wipe(); + + p = find_line(low); + + for (n = *(uint16_t *) p; n <= high; p += *(uint16_t *) (p + 2)) { + n = *(uint16_t *)p; + if (n == must) + seen = 1; + /* End marker - never print */ + if (n == 0xFFFF) + break; + sprintf(tmp, "%5d ", n); + print_str(tmp); + print_line(p + 4); + print_ch('\n'); + if (must && cursor_y == height - 5) { + if (seen) + return; + editbase += (must - editbase) / 2; + low = editbase; + goto retry; + } + } +} + +void editor_update(void) +{ + wipe(); + do_list_command(editbase, 65534, newest); +} + +void list_command(void) +{ + do_list_command(0,65534,0); +} + +void run_command(void) +{ + find_exec_line(0); + execute_line_content(execp); +} + +void go_statement(void) { + uint8_t *c = *execp++; + if (c == TOK_SUB) + stack_frame(); + else if (c != TOK_TO) { + error(SYNTAX_ERROR); + return; + } + if (int_expr(&l) == 0) + return; + find_exec_line(l); +} + +void let_statement(void) { + struct variable v; + variable_name(&v); + if (*c != '=') + error(SYNTAX_ERROR); + else + variable_assign(v, expression()); +} + +void if_statement(void) { + uint8_t b = boolean_expression(); + if (*execp++ != TOK_THEN) + error(SYNTAX_ERROR); + if (b == 0 && run_line) + find_exec_line(run_line + 1); + else + no_colon = 1; +} + +void return_command(void) +{ + if (unstack_frame() == 0) + error(RETURN_UNDERFLOW); +} + +void clear_command(void) +{ + /* TODO */ +} + +void run_command(void) +{ + clear_command(); + find_exec_line(0); + execute_line_content(execp); +} + +void do_print_input(uint8_t in) +{ + uint8_t need_punc = 0; + while(ch = *execp++) { + if (c == '"' && !need_punc) { + while((c = *execp++) != '"') { + if (c == 0) { + error(MISSING_QUOTE); + return; + } + print_ch(c); + } + need_punc = 1; + } + /* TODO: AT TAB and SPC(x) */ + if (c == ',') { + print_ch('\t'); + need_punc = 0; + continue; + } + if (c == ';') { + need_punc 0; + continue; + } + if (need_punc) + error(SYNTAX_ERROR); + if (in) { + struct variable v; + variable_name(&v); + do_input(&v); + } else + print_expression(); + } +} + +void execute_statement(void) +{ + switch(*execp++) { + case '?': + case TOK_PRINT: + print_command(); + break; + case TOK_IF: + if_command(); + break; + case TOK_GO: + go_command(); + break; + case TOK_LET: + let_command(); + break; + case TOK_INPUT: + input_command(); + break; + case TOK_RETURN: + return_command(); + break; + case TOK_CLEAR: + clear_command(); + break; + case TOK_LIST: + list_command(); + break; + case TOK_RUN: + run_command(); + break; + case TOK_END: + running = 0; + return; + default: + execp--; + let_command(); + break; + } +} + +void execute_line_content(uint8_t *l) +{ + uint8_t c; + execp = l; + running = 1; + do { + execute_statement(); + if (!running) + return; + if (no_colon) + continue; + if(pending_go) { + find_exec_line(go_target); + continue; + } + c = *execp++; + if (c == 0) + return; + } while(c == ':'); + error(SYNTAX_ERROR); +} + +void parse_line(uint8_t * l, int s) +{ + uint16_t n; + if (isdigit(*l)) { + uint8_t *dp = linenumber(l, &n); + if (l == NULL) + return; + newest = n; + insdel_line(n, dp, s - (dp - l)); + editor_update(); + printat(height-4, 0); + return; + } + run_line = 0; + execute_line_content(l); +} + +int main(int argc, char *argv[]) +{ + char buf[256]; + uint8_t o[256]; + uint8_t *p; + + cls_command(); + printf("Fuzix Basic 0.1\n"); + printf("%d bytes free.\n", lines_limit - lines); + + new_command(); + + while (1) { + fgets(buf, 256, stdin); + outptr = o; + tokenize_line(buf); +#if 0 + p = o; + while (p < outptr) { + if (*p > 31 && *p < 127) + putchar(*p); + else + printf("[%02X]", *p); + p++; + } + printf("\n"); + print_line(o); + printf("\n"); +#endif + parse_line(o, outptr - o); + } +} diff --git a/Applications/basic/tokens.h b/Applications/basic/tokens.h new file mode 100644 index 00000000..0c10ee72 --- /dev/null +++ b/Applications/basic/tokens.h @@ -0,0 +1,25 @@ +#define TOKEN_BASE 192 + 'P','R','I','N',0xD4, +#define TOK_PRINT 192 + 'I',0xC6, +#define TOK_IF 193 + 'G',0xCF, +#define TOK_GO 194 + 'T',0xCF, +#define TOK_TO 195 + 'S','U',0xC2, +#define TOK_SUB 196 + 'L','E',0xD4, +#define TOK_LET 197 + 'I','N','P','U',0xD4, +#define TOK_INPUT 198 + 'R','E','T','U','R',0xCE, +#define TOK_RETURN 199 + 'C','L','E','A',0xD2, +#define TOK_CLEAR 200 + 'L','I','S',0xD4, +#define TOK_LIST 201 + 'R','U',0xCE, +#define TOK_RUN 202 + 'E','N',0xC4, +#define TOK_END 203 -- 2.34.1