From a428b905da08d0232235b186411473358fe259b1 Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Mon, 6 May 2024 15:12:12 -0400 Subject: [PATCH] Added the ability to parse commands as well as expressions. Added basic PRINT and REM commands. Added solver tests which had been around for a while and not committed. All execution is still immediate mode. --- src/basic.c | 190 +++++++++++++++++++++++++++++----------- src/basic.h | 37 +++++--- tests/basic_parser.c | 44 +++++++++- tests/basic_solver.c | 36 ++++++++ tests/basic_solver.deps | 5 ++ tests/basic_tokenizer.c | 3 +- 6 files changed, 247 insertions(+), 68 deletions(-) create mode 100644 tests/basic_solver.c create mode 100644 tests/basic_solver.deps diff --git a/src/basic.c b/src/basic.c index 8589663..82ad63a 100644 --- a/src/basic.c +++ b/src/basic.c @@ -11,6 +11,62 @@ char _tokenizer_value[BASIC_TOKENIZER_MAX_LENGTH]; char *_tokenizer_prev; char *_tokenizer_prev_next; +void basic_report_error() +{ + char decimal[2]; + decimal[0] = 0; + decimal[1] = 0; + _cputs("Error: "); + decimal[0] = dtoa(basic_errno); + _cputs((char *)&decimal); + _cputs("\n"); +} + +/*************************************************************** + * + * Basic Commands + * + ***************************************************************/ + +void basic_cmd_rem(void *expr) +{ + return; +} + +void basic_cmd_print(void *data) +{ + struct basic_variable result; + basic_expr *expr; + if ( data == NULL ) { + basic_errno = BASIC_ERR_INTERNAL_NULLPOINTER; + return; + } + + _cputs("PRINT("); + _cputs((char *)data); + _cputs(")\n"); + + expr = basic_parse_expr((char *)data); + if ( expr == NULL ) { + basic_errno = BASIC_ERR_INTERNAL_NULLPOINTER; + return; + } + + memset((void *)&result, 0x00, sizeof(struct basic_variable)); + basic_solve_expr(expr, &result); + if ( basic_errno != 0 ) { + return; + } else { + basic_print_var(&result); + } +} + +/*************************************************************** + * + * Basic Tokenizer + * + ***************************************************************/ + void _tokenizer_init(void) { _tokenizer_prev = NULL; @@ -64,11 +120,17 @@ _tokenize_copy: return ptr; } -char *_token_get(void) +char *tokenizer_token(void) { return (char *)&_tokenizer_value; } +/*************************************************************** + * + * Basic Interpreter + * + ***************************************************************/ + int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) { if ( expr == NULL || result == NULL ) { @@ -77,9 +139,18 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) } switch (expr->type) { + case BASIC_OPTP_NONE: + if ( expr->lval_type == BASIC_LVAL_CONST_INT ) { + result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT); + result->value.i = expr->lval.i; + } else { + basic_errno = BASIC_ERR_INTERNAL_UNIMPLEMENTED; + return 0; + } + break; case BASIC_OPTP_ADD: - if ( expr->lval_type != BASIC_LVAL_CONST || - ( expr->rval_type != BASIC_RVAL_CONST ) ) { + if ( expr->lval_type != BASIC_LVAL_CONST_INT || + ( expr->rval_type != BASIC_RVAL_CONST_INT ) ) { basic_errno = BASIC_ERR_INVALID_ARGUMENTS; return 0; } @@ -87,8 +158,8 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) result->value.i = expr->lval.i + expr->rval.i; break; case BASIC_OPTP_SUB: - if ( expr->lval_type != BASIC_LVAL_CONST || - ( expr->rval_type != BASIC_RVAL_CONST ) ) { + if ( expr->lval_type != BASIC_LVAL_CONST_INT || + ( expr->rval_type != BASIC_RVAL_CONST_INT ) ) { basic_errno = BASIC_ERR_INVALID_ARGUMENTS; return 0; } @@ -96,8 +167,8 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) result->value.i = expr->lval.i - expr->rval.i; break; case BASIC_OPTP_MUL: - if ( expr->lval_type != BASIC_LVAL_CONST || - ( expr->rval_type != BASIC_RVAL_CONST ) ) { + if ( expr->lval_type != BASIC_LVAL_CONST_INT || + ( expr->rval_type != BASIC_RVAL_CONST_INT ) ) { basic_errno = BASIC_ERR_INVALID_ARGUMENTS; return 0; } @@ -105,8 +176,8 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) result->value.i = expr->lval.i * expr->rval.i; break; case BASIC_OPTP_DIV: - if ( expr->lval_type != BASIC_LVAL_CONST || - ( expr->rval_type != BASIC_RVAL_CONST ) ) { + if ( expr->lval_type != BASIC_LVAL_CONST_INT || + ( expr->rval_type != BASIC_RVAL_CONST_INT ) ) { basic_errno = BASIC_ERR_INVALID_ARGUMENTS; return 0; } @@ -118,8 +189,8 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) result->value.i = expr->lval.i / expr->rval.i; break; case BASIC_OPTP_MOD: - if ( expr->lval_type != BASIC_LVAL_CONST || - ( expr->rval_type != BASIC_RVAL_CONST ) ) { + if ( expr->lval_type != BASIC_LVAL_CONST_INT || + ( expr->rval_type != BASIC_RVAL_CONST_INT ) ) { basic_errno = BASIC_ERR_INVALID_ARGUMENTS; return 0; } @@ -131,8 +202,8 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result) result->value.i = (expr->lval.i) - ((expr->lval.i / expr->rval.i)*expr->rval.i); break; case BASIC_OPTP_EQL: - if ( expr->lval_type != BASIC_LVAL_CONST || - ( expr->rval_type != BASIC_RVAL_CONST ) ) { + if ( expr->lval_type != BASIC_LVAL_CONST_INT || + ( expr->rval_type != BASIC_RVAL_CONST_INT ) ) { basic_errno = BASIC_ERR_INVALID_ARGUMENTS; return 0; } @@ -169,37 +240,60 @@ struct basic_expr *basic_parse_expr(char *expbuf) continue; } - expbuf = _tokenize(expbuf, BASIC_TOKENIZER_TOKENS); - token = _token_get(); + expbuf = _tokenize(expbuf, " "); + token = tokenizer_token(); if ( isdigit(*token) == 1 ) { if ( (ret->type == 0) && (flags & BASIC_PARSE_FOUND_LVAL) == BASIC_PARSE_FOUND_LVAL ) { basic_errno = BASIC_ERR_SYNTAX_MULTIPLE_LVALUES; return NULL; } else if ( ret->type == 0x0 ) { ret->lval.i = atoi(token); - ret->lval_type = BASIC_LVAL_CONST; + ret->lval_type = BASIC_LVAL_CONST_INT; flags = (flags | BASIC_PARSE_FOUND_LVAL); } else if ( ret->type != 0x0 && ((flags & BASIC_PARSE_FOUND_RVAL) == BASIC_PARSE_FOUND_RVAL)) { basic_errno = BASIC_ERR_SYNTAX_MULTIPLE_RVALUES; return NULL; } else if ( ret->type != 0x0 ) { ret->rval.i = atoi(token); - ret->rval_type = BASIC_RVAL_CONST; + ret->rval_type = BASIC_RVAL_CONST_INT; } } else if ( token != NULL && ret->type == 0x0 ) { - if ( *token == '+' ) { - ret->type = BASIC_OPTP_ADD; - } else if ( *token == '*' ) { - ret->type = BASIC_OPTP_MUL; - } else if ( *token == '-' ) { - ret->type = BASIC_OPTP_SUB; - } else if ( *token == '/' ) { - ret->type = BASIC_OPTP_DIV; - } else if ( *token == '%' ) { - ret->type = BASIC_OPTP_MOD; - } else { - basic_errno = BASIC_ERR_SYNTAX_GENERAL; - return NULL; + switch ( *token ) { + case '+': + ret->type = BASIC_OPTP_ADD; + break; + case '*': + ret->type = BASIC_OPTP_MUL; + break; + case '-': + ret->type = BASIC_OPTP_SUB; + break; + case '/': + ret->type = BASIC_OPTP_DIV; + break; + case '%': + ret->type = BASIC_OPTP_MOD; + break; + default: + /* see if a command will claim this token */ + if ( strcmp(token, "REM") == 0 ) { + ret->type = BASIC_OPTP_CMD; + ret->lval_type = BASIC_LVAL_CMDPTR; + ret->lval.cmd_ptr = basic_cmd_rem; + ret->rval_type = BASIC_RVAL_NONE; + return ret; + } else if ( strcmp(token, "PRINT") == 0 ) { + ret->type = BASIC_OPTP_CMD; + ret->lval_type = BASIC_LVAL_CMDPTR; + ret->lval.cmd_ptr = basic_cmd_print; + ret->rval_type = BASIC_RVAL_PTR; + ret->rval.ptr = expbuf; + return ret; + } else { + basic_errno = BASIC_ERR_SYNTAX_GENERAL; + return NULL; + break; + } } } else { basic_errno = BASIC_ERR_SYNTAX_GENERAL; @@ -209,6 +303,12 @@ struct basic_expr *basic_parse_expr(char *expbuf) return ret; } +/*************************************************************** + * + * Basic REPL + * + ***************************************************************/ + void basic_print_var(struct basic_variable *var) { char decimal[32]; @@ -229,20 +329,14 @@ void basic_repl(basic_program *program) { char keybuff[512]; char outbuff[128]; - char decimal[2]; struct basic_expr *expr; - struct basic_variable result; - - decimal[0] = 0; - decimal[1] = 0; blankScreen(); setCursorPosition(0, 0); _cputs("Piquant Basic v0.1\n\n"); while ( 1 ) { - memset((void *)&result, 0x00, sizeof(struct basic_variable)); expr = NULL; _cputs("> "); @@ -256,23 +350,17 @@ void basic_repl(basic_program *program) /* Evaluate */ expr = basic_parse_expr((char *)&keybuff); if ( expr == NULL ) { - _cputs("Error: "); - decimal[0] = dtoa(basic_errno); - _cputs((char *)&decimal); - _cputs("\n"); + basic_report_error(); basic_errno = 0; - continue; } - - basic_solve_expr(expr, &result); - if ( basic_errno != 0 ) { - _cputs("Error: "); - decimal[0] = dtoa(basic_errno); - _cputs((char *)&decimal); - _cputs("\n"); - } else { - /* Print */ - basic_print_var(&result); + if ( expr->lval_type == BASIC_LVAL_CMDPTR ) { + if ( expr->lval.cmd_ptr == NULL ) { + basic_errno = BASIC_ERR_INTERNAL_NULLPOINTER; + basic_report_error(); + basic_errno = 0; + } else { + (*expr->lval.cmd_ptr)(expr->rval.ptr); + } } } } diff --git a/src/basic.h b/src/basic.h index 099f5a7..55c6c79 100644 --- a/src/basic.h +++ b/src/basic.h @@ -1,25 +1,30 @@ #ifndef _BASIC_H_ #define _BASIC_H_ -#define BASIC_MAX_LINES 1000 -#define BASIC_MAX_LINE_LENGTH 256 +/* Per MS BASIC-80 ref page 1-2 */ +#define BASIC_MAX_LINES sizeof(int) +#define BASIC_MAX_LINE_LENGTH 255 -#define BASIC_OPTP_ADD 1 /* Add */ -#define BASIC_OPTP_SUB 2 /* Subtract */ -#define BASIC_OPTP_MUL 3 /* Multiply */ -#define BASIC_OPTP_DIV 4 /* Divide */ -#define BASIC_OPTP_MOD 5 /* Modulus */ -#define BASIC_OPTP_EQL 6 /* Equality test */ -#define BASIC_OPTP_ASN 7 /* Assignment */ -#define BASIC_OPTP_STOR 8 /* Store line for later */ +#define BASIC_OPTP_NONE 0 /* No operation (only valid when LVAL is CONST and no RVAL) */ +#define BASIC_OPTP_ADD 1 /* Add */ +#define BASIC_OPTP_SUB 2 /* Subtract */ +#define BASIC_OPTP_MUL 3 /* Multiply */ +#define BASIC_OPTP_DIV 4 /* Divide */ +#define BASIC_OPTP_MOD 5 /* Modulus */ +#define BASIC_OPTP_EQL 6 /* Equality test */ +#define BASIC_OPTP_ASN 7 /* Assignment */ +#define BASIC_OPTP_STOR 8 /* Store line for later */ +#define BASIC_OPTP_CMD 9 /* BASIC command (PRINT, FOR, REM, etc)*/ #define BASIC_LVAL_EXPR 0 #define BASIC_LVAL_VAR 1 -#define BASIC_LVAL_CONST 2 +#define BASIC_LVAL_CONST_INT 2 +#define BASIC_LVAL_CMDPTR 3 #define BASIC_RVAL_EXPR 3 #define BASIC_RVAL_VAR 4 -#define BASIC_RVAL_CONST 5 +#define BASIC_RVAL_CONST_INT 5 #define BASIC_RVAL_PTR 6 /* Only used internally */ +#define BASIC_RVAL_NONE 7 #define BASIC_PARSE_FOUND_LVAL 1 #define BASIC_PARSE_FOUND_RVAL 2 @@ -62,6 +67,7 @@ union basic_value { */ char *str; void *ptr; + void (*cmd_ptr)(void *data); }; typedef union basic_value basic_value; @@ -94,5 +100,12 @@ extern int basic_errno; void basic_repl(basic_program *program); basic_expr *basic_parse_expr(char *); int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result); +char *tokenizer_token(void); + +void basic_cmd_rem(void *data); +void basic_cmd_print(void *data); + +void basic_print_var(basic_variable *var); +void basic_report_error(); #endif /* _BASIC_H_ */ diff --git a/tests/basic_parser.c b/tests/basic_parser.c index 75d733d..60a27e6 100644 --- a/tests/basic_parser.c +++ b/tests/basic_parser.c @@ -8,13 +8,14 @@ int main(void) { struct basic_expr *expr; + char *line; /*struct basic_line *retline; struct basic_line *arrayline;*/ /* Store a line */ /* expr = basic_parse_expr("10 =1 + 1"); if ( expr == NULL ) return 1; - if ( expr->lval_type != BASIC_LVAL_CONST ) return 2; + if ( expr->lval_type != BASIC_LVAL_CONST_INT ) return 2; if ( expr->rval_type != BASIC_RVAL_PTR ) return 3; if ( expr->lval.i != 10 ) return 4; if ( expr->rval.ptr == NULL ) return 5; @@ -25,12 +26,49 @@ int main(void) expr = basic_parse_expr(retline->content);*/ expr = basic_parse_expr("1 + 1"); if ( expr == NULL ) return 7; - if ( expr->lval_type != BASIC_LVAL_CONST ) return 8; - if ( expr->rval_type != BASIC_RVAL_CONST ) return 9; + if ( expr->lval_type != BASIC_LVAL_CONST_INT ) return 8; + if ( expr->rval_type != BASIC_RVAL_CONST_INT ) return 9; if ( expr->lval.i != 1 ) return 10; if ( expr->rval.i != 1 ) return 11; if ( expr->type != BASIC_OPTP_ADD ) return 12; + line = "REM This is a comment that gets ignored"; + expr = basic_parse_expr(line); + printf("%s\n", line); + printf("token = %s\n", tokenizer_token()); + printf("errno = %d\n", basic_errno); + if ( expr == NULL ) return 13; + if ( expr->type != BASIC_OPTP_CMD ) return 14; + if ( expr->lval_type != BASIC_LVAL_CMDPTR ) return 15; + if ( expr->lval.cmd_ptr != &basic_cmd_rem ) return 16; + + line = "PRINT 10"; + expr = basic_parse_expr(line); + printf("%s\n", line); + printf("token = %s\n", tokenizer_token()); + printf("errno = %d\n", basic_errno); + printf("rval.ptr = '%s'\n", (char *)expr->rval.ptr); + if ( expr == NULL ) return 17; + if ( expr->type != BASIC_OPTP_CMD ) return 18; + if ( expr->lval_type != BASIC_LVAL_CMDPTR ) return 19; + if ( expr->lval.cmd_ptr != &basic_cmd_print ) return 20; + if ( expr->rval_type != BASIC_RVAL_PTR ) return 21; + if ( expr->rval.ptr == NULL ) return 22; + if ( strcmp((char *)expr->rval.ptr, " 10") != 0 ) return 23; + + line = " 10"; /* ... continuing logic from the previous */ + expr = basic_parse_expr(line); + printf("%s\n", line); + printf("token = %s\n", tokenizer_token()); + printf("errno = %d\n", basic_errno); + printf("expr.type = %d\n", expr->type); + printf("expr->lval_type = %d\n", expr->lval_type); + if ( expr == NULL ) return 24; + if ( expr->type != BASIC_OPTP_NONE ) return 25; + if ( expr->lval_type != BASIC_LVAL_CONST_INT ) return 26; + if ( expr->lval.i != 10 ) return 27; + + /* arrayline = &basic_memory_lines[0]; if ( retline != arrayline) return 13; diff --git a/tests/basic_solver.c b/tests/basic_solver.c new file mode 100644 index 0000000..d21c4b2 --- /dev/null +++ b/tests/basic_solver.c @@ -0,0 +1,36 @@ +#include "types.h" +#include "basic.h" +#include "string.h" +#include + +int main(void) +{ + basic_expr expr; + basic_variable result; + result.flags = 0; + + expr.type = BASIC_OPTP_ADD; + expr.lval_type = BASIC_LVAL_CONST_INT; + expr.lval.i = 1; + expr.rval_type = BASIC_RVAL_CONST_INT; + expr.rval.i = 1; + + basic_solve_expr(&expr, &result); + if ( basic_errno != 0 ) return 2; + if ( result.flags != (BASIC_VARFLAG_INIT | BASIC_VARFLAG_TINT) ) return 3; + if ( result.value.i != 2 ) return 4; + + expr.type = BASIC_OPTP_NONE; + expr.lval_type = BASIC_LVAL_CONST_INT; + expr.lval.i = 1; + expr.rval_type = BASIC_RVAL_NONE; + memset(&result, 0x00, sizeof(basic_variable)); + basic_solve_expr(&expr, &result); + if ( basic_errno != 0 ) return 5; + if ( result.flags != (BASIC_VARFLAG_INIT | BASIC_VARFLAG_TINT) ) return 6; + if ( result.value.i != 1 ) return 7; + + return 0; + + +} \ No newline at end of file diff --git a/tests/basic_solver.deps b/tests/basic_solver.deps new file mode 100644 index 0000000..1f29ad1 --- /dev/null +++ b/tests/basic_solver.deps @@ -0,0 +1,5 @@ +basic +stdlib +string +conio +screen diff --git a/tests/basic_tokenizer.c b/tests/basic_tokenizer.c index 86427fb..ffdeeac 100644 --- a/tests/basic_tokenizer.c +++ b/tests/basic_tokenizer.c @@ -4,11 +4,10 @@ #include char *_tokenize(char *ptr, char *token); -char *_token_get(void); #define assert_token_value(str, val, ret_null, ret_neq) \ ptr = _tokenize(str, BASIC_TOKENIZER_TOKENS); \ - value = _token_get(); \ + value = tokenizer_token(); \ if ( ptr == NULL ) return ret_null; \ rc = strcmp(value, val); \ printf("(%s) => (value) == (val) ? : (%s) == (%s) %d\n", str, value, val, rc); \