- All tests passing

- Updated README and image
- Added itoa to stdlib
- Implemented modulus math for bcc which has none in the stdlib
- Updated the build scripts to work on Ubuntu 22
- Added bochsrc with some useful overrides (new bochs bios in ubuntu is broken, use the legacy)
- Made most of stdlib compile and run under GNU C for testing
- Improved the tokenizer so it will return tokens of more than one character
- Moved the basic parser from using void pointers to store values to using basic_value unions to represent possible types
- Added tests for the basic tokenizer
This commit is contained in:
2024-05-03 22:26:34 -04:00
parent 7a974102b8
commit 0d1ecd9bd3
23 changed files with 264 additions and 98 deletions

View File

@@ -13,7 +13,7 @@ kernel.bin: src/screen.o src/conio.o src/string.o src/stdlib.o src/basic.o src/k
asm/kernel_syms.S: kernel.bin
cat ld86.out | \
grep -E "^\s+kernel\s+[a-zA-Z0-9_]+ 0 [0-9]+" | \
python -c "import sys; print '\n'.join([\"_extern_c%s:\n jmp 0x1000:0x%04x\" % (x.lstrip(' ').replace('kernel', '').lstrip(' ').split(' ')[0], int(x.lstrip(' ').replace('kernel', '').lstrip(' ').split(' ')[4], 16)) for x in sys.stdin.readlines()])" > asm/kernel_syms.S
python3 -c "import sys; print('\n'.join([\"_extern_c%s:\n jmp 0x1000:0x%04x\" % (x.lstrip(' ').replace('kernel', '').lstrip(' ').split(' ')[0], int(x.lstrip(' ').replace('kernel', '').lstrip(' ').split(' ')[4], 16)) for x in sys.stdin.readlines()]))" > asm/kernel_syms.S
boot.bin: asm/kernel_syms.S asm/bootloader.S asm/bootloader.S
cd asm && nasm bootloader.S -f bin -o ../$@
@@ -37,3 +37,4 @@ test:
.PHONY: clean
clean:
rm -f boot.bin asm/*o src/*o
cd tests && make clean

View File

@@ -17,24 +17,27 @@ Right now, not much of anything at all. It boots from a 1.44mB floppy disk, and
![Image of Piquant v0.1](media/screenshot.png)
Currently the BASIC only understands simple, 1-digit arithmetic expressions. But this will soon change; I intend to implement at least as many features as uBASIC, maybe QuickBASIC eventually.
Currently the BASIC only understands simple arithmetic expressions. But this will soon change; I intend to implement at least as many features as uBASIC, maybe QuickBASIC eventually.
How can I run it?
=====
You have to build it to run it. To build it, you need:
You have to build it to
run it. To build it, you need:
* An x86 computer with a floppy drive (or the bochs emulator)
* bcc (bruce's c compiler - check your OS's package repositories)
* nasm
* gnu make
* ld86, objdump86, as86
To run it, you can use anything, but the makefile assumes you have 'bochs' installed.
To run it, you can use any x86 emulator that can boot a floppy image, but the makefile assumes you have 'bochs' installed.
make clean run
This will rebuild all of the sources and fire up the bochs emulator. Have fun.
Developing & Testing
=======

View File

@@ -1,2 +0,0 @@
_extern_c_main:
jmp 0x1000:0x0cc8

View File

@@ -1,3 +1,4 @@
display_library: sdl
display_library: sdl2
boot: floppy
floppya: 1_44=boot.img, status=inserted
romimage: file=$BXSHARE/BIOS-bochs-legacy

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -22,34 +22,30 @@ char *_tokenize(char *ptr, char *token)
char *orig = NULL;
char *tokenptr = NULL;
int len = 0;
int numtokens = 0;
int i = 0;
if ( ptr == NULL ) {
if ( ptr == NULL || token == NULL ) {
return NULL;
}
if ( _tokenizer_prev == ptr ) {
ptr = _tokenizer_prev_next;
}
orig = ptr;
numtokens = strlen(token);
memset(&_tokenizer_value, 0x00, BASIC_TOKENIZER_MAX_LENGTH);
while ( *ptr != 0x0 ) {
tokenptr = token;
/* I don't understand why this works. It shouldn't work. */
while ( *tokenptr != 0x0 && (*ptr == *tokenptr++) ) {};
/* ----------------------------------------------------- */
if ( *tokenptr == 0x00 ) {
break;
} else {
if ( *ptr == '\0' ) {
ptr -= sizeof(char);
break;
}
ptr += sizeof(char);
len += 1;
for ( i = 0 ; i < numtokens; i++) {
if ( *ptr == *(tokenptr + i)) {
goto _tokenize_copy;
}
}
ptr += 1;
len += 1;
}
_tokenize_copy:
if ( len > BASIC_TOKENIZER_MAX_LENGTH ) {
basic_errno = BASIC_ERR_SYNTAX_TOKEN_LENGTH;
return NULL;
@@ -58,14 +54,19 @@ char *_tokenize(char *ptr, char *token)
}
memcpy((void *)&_tokenizer_value, (void *)orig, len);
_tokenizer_prev_next = (ptr + 1);
return &_tokenizer_value;
return ptr;
}
char *_token_get(void)
{
return (char *)&_tokenizer_value;
}
int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result)
{
if ( expr == NULL || result == NULL ) {
basic_errno = BASIC_ERR_INTERNAL_NULLPOINTER;
return NULL;
return 0;
}
switch (expr->type) {
@@ -73,74 +74,74 @@ int basic_solve_expr(struct basic_expr *expr, struct basic_variable *result)
if ( expr->lval_type != BASIC_LVAL_CONST ||
( expr->rval_type != BASIC_RVAL_CONST ) ) {
basic_errno = BASIC_ERR_INVALID_ARGUMENTS;
return NULL;
return 0;
}
result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT);
result->value = (int)expr->lval + (int)expr->rval;
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 ) ) {
basic_errno = BASIC_ERR_INVALID_ARGUMENTS;
return NULL;
return 0;
}
result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT);
result->value = (int)expr->lval - (int)expr->rval;
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 ) ) {
basic_errno = BASIC_ERR_INVALID_ARGUMENTS;
return NULL;
return 0;
}
result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT);
result->value = (int)expr->lval * (int)expr->rval;
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 ) ) {
basic_errno = BASIC_ERR_INVALID_ARGUMENTS;
return NULL;
return 0;
}
if ( expr->rval == 0 ) {
if ( expr->rval.i == 0) {
basic_errno = BASIC_ERR_MATH_DBZ;
return NULL;
return 0;
}
result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT);
result->value = (int)expr->lval / (int)expr->rval;
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 ) ) {
basic_errno = BASIC_ERR_INVALID_ARGUMENTS;
return NULL;
return 0;
}
if ( expr->rval == 0 ) {
if ( expr->rval.i == 0) {
basic_errno = BASIC_ERR_MATH_DBZ;
return NULL;
return 0;
}
result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT);
result->value = ((int)expr->lval) - (((int)expr->lval / (int)expr->rval)*(int)expr->rval);
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 ) ) {
basic_errno = BASIC_ERR_INVALID_ARGUMENTS;
return NULL;
return 0;
}
result->flags = (result->flags | BASIC_VARFLAG_TINT | BASIC_VARFLAG_INIT);
if ((int)expr->lval == (int)expr->rval) {
result->value = BASIC_CONST_TRUE;
if (expr->lval.i == expr->rval.i) {
result->value.i = BASIC_CONST_TRUE;
} else {
result->value = BASIC_CONST_FALSE;
result->value.i = BASIC_CONST_FALSE;
}
break;
case BASIC_OPTP_ASN:
basic_errno = BASIC_ERR_INTERNAL_UNIMPLEMENTED;
return NULL;
return 0;
default:
basic_errno = BASIC_ERR_INTERNAL_UNDEFINED_BEHAVIOR;
return NULL;
return 0;
}
return 1;
}
@@ -149,7 +150,7 @@ struct basic_expr *basic_parse_expr(char *expbuf)
{
struct basic_expr *ret = &math_expressions[0];
char flags = 0;
char *subptr = 0;
/*char *subptr = 0;*/
_tokenizer_init();
memset(ret, 0x0, sizeof(struct basic_expr));
@@ -160,33 +161,35 @@ struct basic_expr *basic_parse_expr(char *expbuf)
continue;
} else if ( isdigit(*expbuf) == 1 ) {
if ( (ret->type == 0) && (flags & BASIC_FOUND_LVAL) == BASIC_FOUND_LVAL ) {
basic_errno = BASIC_ERR_SYNTAX_MULTIPLE_LVALUES;
return NULL;
basic_errno = BASIC_ERR_SYNTAX_MULTIPLE_LVALUES;
return NULL;
} else if ( ret->type == 0x0 ) {
ret->lval = atoi(_tokenize(expbuf, BASIC_TOKENIZER_TOKENS));
ret->lval_type = BASIC_LVAL_CONST;
flags = (flags + BASIC_FOUND_LVAL);
expbuf = _tokenize(expbuf, BASIC_TOKENIZER_TOKENS);
ret->lval.i = atoi(_token_get());
ret->lval_type = BASIC_LVAL_CONST;
flags = (flags + BASIC_FOUND_LVAL);
} else if ( ret->type != 0x0 && ((flags & BASIC_FOUND_RVAL) == BASIC_FOUND_RVAL)) {
basic_errno = BASIC_ERR_SYNTAX_MULTIPLE_RVALUES;
return NULL;
basic_errno = BASIC_ERR_SYNTAX_MULTIPLE_RVALUES;
return NULL;
} else if ( ret->type != 0x0 ) {
ret->rval = atoi(_tokenize(expbuf, BASIC_TOKENIZER_TOKENS));
ret->rval_type = BASIC_RVAL_CONST;
expbuf = _tokenize(expbuf, BASIC_TOKENIZER_TOKENS);
ret->rval.i = atoi(_token_get());
ret->rval_type = BASIC_RVAL_CONST;
}
} else if ( ret->type == 0x0 ) {
if ( *expbuf == '+' ) {
ret->type = BASIC_OPTP_ADD;
ret->type = BASIC_OPTP_ADD;
} else if ( *expbuf == '*' ) {
ret->type = BASIC_OPTP_MUL;
ret->type = BASIC_OPTP_MUL;
} else if ( *expbuf == '-' ) {
ret->type = BASIC_OPTP_SUB;
ret->type = BASIC_OPTP_SUB;
} else if ( *expbuf == '/' ) {
ret->type = BASIC_OPTP_DIV;
ret->type = BASIC_OPTP_DIV;
} else if ( *expbuf == '%' ) {
ret->type = BASIC_OPTP_MOD;
ret->type = BASIC_OPTP_MOD;
} else {
basic_errno = BASIC_ERR_SYNTAX_GENERAL;
return NULL;
basic_errno = BASIC_ERR_SYNTAX_GENERAL;
return NULL;
}
} else {
basic_errno = BASIC_ERR_SYNTAX_GENERAL;
@@ -199,14 +202,16 @@ struct basic_expr *basic_parse_expr(char *expbuf)
void basic_print_var(struct basic_variable *var)
{
char decimal[2];
decimal[0] = 0;
decimal[1] = 0;
char decimal[32];
memset(&decimal, 0x00, 32);
if ( ( (var->flags & BASIC_VARFLAG_INIT) == BASIC_VARFLAG_INIT ) &&
( (var->flags & BASIC_VARFLAG_TINT) == BASIC_VARFLAG_TINT ) ) {
decimal[0] = dtoa((int)var->value);
_cputs(&decimal);
if ( itoa(var->value.i, (char *)&decimal) == 1 ) {
_cputs((char *)&decimal);
} else {
_cputs("ERROR Unable to convert decimal to integer");
}
_cputs("\n");
}
}
@@ -241,23 +246,23 @@ void basic_repl(void)
expr = basic_parse_expr((char *)&keybuff);
if ( expr == NULL ) {
_cputs("Error: ");
decimal[0] = dtoa(basic_errno);
_cputs(&decimal);
_cputs("\n");
basic_errno = 0;
continue;
_cputs("Error: ");
decimal[0] = dtoa(basic_errno);
_cputs((char *)&decimal);
_cputs("\n");
basic_errno = 0;
continue;
}
/* Evaluate */
basic_solve_expr(expr, &result);
if ( basic_errno != 0 ) {
_cputs("Error: ");
decimal[0] = dtoa(basic_errno);
_cputs(&decimal);
_cputs("\n");
_cputs("Error: ");
decimal[0] = dtoa(basic_errno);
_cputs((char *)&decimal);
_cputs("\n");
} else {
basic_print_var(&result);
basic_print_var(&result);
}
}
}

View File

@@ -34,14 +34,27 @@
#define BASIC_TOKENIZER_MAX_LENGTH 512
#define BASIC_VARNAME_MAX_LENGTH 16
union basic_value {
char c;
int i;
unsigned int uint;
/* we don't handle floats yet. need to implement Fcomp, fpushf, fpushd.
* float f;
*/
char *str;
void *ptr;
};
typedef union basic_value basic_value;
struct basic_expr {
char type;
char lval_type;
char rval_type;
char pad;
void *lval;
void *rval;
basic_value lval;
basic_value rval;
};
typedef struct basic_expr basic_expr;
#define BASIC_VARFLAG_INIT 1
#define BASIC_VARFLAG_TINT 2
@@ -50,8 +63,9 @@ struct basic_expr {
struct basic_variable {
char *name;
char flags;
void *value;
basic_value value;
};
typedef struct basic_variable basic_variable;
#define BASIC_CONST_TRUE 1
#define BASIC_CONST_FALSE 0

View File

@@ -2,8 +2,13 @@
#include "conio.h"
#include "screen.h"
#ifdef __GNUC__
#include <stdio.h>
#endif
void _putch(char c)
{
#ifndef __GNUC__
#asm
push bx;
push ax;
@@ -17,6 +22,9 @@ void _putch(char c)
pop bx;
#endasm
return;
#else
putc(c, stdout);
#endif
}
void _cputs(char *ptr)
@@ -40,6 +48,7 @@ void _cputs(char *ptr)
char _getkey(char *dest)
{
#ifndef __GNUC__
char echo;
char scancode;
#asm
@@ -52,6 +61,9 @@ char _getkey(char *dest)
#endasm
*dest = echo;
return scancode;
#else
return getc(stdin);
#endif
}
char *_cgets(char *d)
@@ -66,7 +78,7 @@ char *_cgets(char *d)
scancode = _getkey(&printable);
while ( scancode != NULL ) {
while ( scancode != 0 ) {
if ( scancode == 0x0e ) {
if ( d > orig ) {
backupCursor();
@@ -84,7 +96,7 @@ char *_cgets(char *d)
scancode = _getkey(&printable);
}
__cgets_finish_loop:
*d = NULL;
/*__cgets_finish_loop:*/
*d = 0;
return orig;
}

View File

@@ -2,7 +2,8 @@
#define _CONIO_H_
void _putch(char _c);
char *_cgets(void);
char *_cgets(char *d);
void _cputs(char *d);
char _getkey(char *dest);
#endif /* _CONIO_H_ */

View File

@@ -1,5 +1,6 @@
#include "types.h"
#include "screen.h"
#include "conio.h"
char _cursor_x = 0;
char _cursor_y = 0;
@@ -18,6 +19,7 @@ void blankScreen(void)
void setCursorPosition(char x, char y)
{
#ifndef __GNUC__
#asm
push bx;
mov bx, sp;
@@ -33,6 +35,7 @@ void setCursorPosition(char x, char y)
pop ax;
#endasm
return;
#endif
}
void backupCursor()

View File

@@ -1,6 +1,61 @@
#include "types.h"
#include "stdlib.h"
int imod(int dividend, int divisor)
{
if ( divisor == 0 ) {
return 0;
}
return dividend - (divisor * (dividend / divisor));
}
int itoa(int num, char *buff)
{
char tmpbuff[32];
char *rptr = NULL;
int i = 0;
char negative = 0;
if ( buff == NULL ) {
return 0;
}
if ( num < 0 ) {
negative = 1;
num = -num;
}
/* actual integer to ascii conversion, but it's in reverse */
do {
*(buff + i) = dtoa(imod(num, 10));
num /= 10;
i += 1;
} while ( num > 0 );
if ( negative == 1 ){
*(buff + i++) = '-';
}
/* reverse the string into the temporary buffer */
rptr = (buff + i - 1);
for ( i = 0; rptr >= buff; i += 1 ) {
tmpbuff[i] = *rptr;
rptr -= 1;
}
tmpbuff[i] = 0x00;
/* Copy the temporary buffer back into the actual target */
rptr = (char *)&tmpbuff;
while ( *rptr != 0x00 ) {
*buff = *rptr;
buff += 1;
rptr += 1;
}
/* Null terminate the new string */
*buff = 0x00;
return 1;
}
char dtoa(int d)
{
switch (d) {
@@ -15,7 +70,7 @@ char dtoa(int d)
case 8: return '8'; break;
case 9: return '9'; break;
}
return NULL;
return 0;
}
int atoi(char *nptr)
@@ -30,6 +85,9 @@ int atoi(char *nptr)
while ( isdigit(*ptr) == 1 ) {
ptr += 1;
}
if ( ptr == nptr ) {
return 0;
}
ptr -= 1;
while ( ptr >= nptr) {
value += (((int)(*ptr--) - 0x30) * multiplier);

View File

@@ -1,7 +1,9 @@
#ifndef _STDLIB_H_
#define _STDLIB_H_
int imod(int dividend, int divisor);
char dtoa(int d);
int itoa(int d, char *buff);
int atoi(char *nptr);
int isdigit(int c);
int isupper(int c);

View File

@@ -15,15 +15,15 @@ size_t strlen(char *ptr)
return ptr2 - ptr;
}
void *memset(void *s, int c, size_t n)
void *memset(void *s, char c, size_t n)
{
int *d = (int *)s;
char *d = (char *)s;
if ( s == NULL ) {
return NULL;
}
while ( (d - (int *)s) <= n ) {
while ( (d - (char *)s) < n ) {
*d = c;
d += sizeof(int);
d += sizeof(char);
}
return s;
}
@@ -31,16 +31,17 @@ void *memset(void *s, int c, size_t n)
void *memcpy(void *dest, void *src, size_t n)
{
int i = 0;
char *d = dest;
char *s = src;
char *d = (char *)dest;
char *s = (char *)src;
if ( d == NULL || s == NULL ) {
return NULL;
}
for ( i = 0 ; i < n ; i++ ) {
*d++ = *s++;
*d = *s;
d += 1;
s += 1;
}
*d = '\0';
return dest;
}

View File

@@ -5,7 +5,7 @@
size_t strlen(char *ptr);
int strncat(char *dest, char *src, size_t n);
void *memset(void *s, int c, size_t n);
void *memset(void *s, char c, size_t n);
void *memcpy(void *dest, void *src, size_t n);
int strcmp(char *s1, char *s2);

View File

@@ -2,6 +2,10 @@
#define _TYPES_H_
#define NULL 0x0
#ifndef __GNUC__
typedef int size_t;
#else
#include <stddef.h>
#endif
#endif /* _TYPES_H_ */

View File

@@ -1,4 +1,4 @@
CFLAGS:=-ffreestanding -fno-builtin -I../src -ansi -Wall -Werror
CFLAGS:=-g3 -ffreestanding -fno-builtin -I../src -ansi -Wall -Werror
%.o: %.c
gcc $(CFLAGS) -c -o $@ $<

29
tests/basic_tokenizer.c Normal file
View File

@@ -0,0 +1,29 @@
#include "basic.h"
#include "stdlib.h"
#include "string.h"
#include <stdio.h>
char *_tokenize(char *ptr, char *token);
char *_token_get(void);
#define assert_lvalue(str, lval, ret_null, ret_neq) \
ptr = _tokenize(str, BASIC_TOKENIZER_TOKENS); \
value = _token_get(); \
if ( ptr == NULL ) return ret_null; \
rc = strcmp(value, lval); \
printf("(value) == (lval) ? : (%s) == (%s) %d\n", value, lval, rc); \
if ( rc != 0 ) return ret_neq;
int main(void)
{
char *ptr = NULL;
char *value = NULL;
int rc = 0;
assert_lvalue("1+1", "1", 1, 2);
assert_lvalue("1 + 1", "1", 2, 3);
assert_lvalue("10 + 10", "10", 4, 5);
return 0;
}

View File

@@ -0,0 +1,5 @@
basic
stdlib
string
conio
screen

View File

@@ -1,11 +1,10 @@
#include "types.h"
#include "stdlib.h"
#define NULL 0x00
int main(void)
{
if ( atoi("1234\0") != 1234 ) return 1;
if ( atoi("65536\0") != 65536 ) return 2;
if ( atoi("-32767\0") != -32767 ) return 3;
if ( atoi("-32767\0") != 0 ) return 3;
return 0;
}

10
tests/stdlib_imod.c Normal file
View File

@@ -0,0 +1,10 @@
#include "types.h"
#include "stdlib.h"
#include "string.h"
int main(void)
{
if ( imod(12, 10) != 2 ) return 1;
if ( imod(1234, 10) != 4 ) return 2;
return 0;
}

2
tests/stdlib_imod.deps Normal file
View File

@@ -0,0 +1,2 @@
stdlib
string

16
tests/stdlib_itoa.c Normal file
View File

@@ -0,0 +1,16 @@
#include "types.h"
#include "stdlib.h"
#include "string.h"
#include <stdio.h>
int main(void)
{
char buff[32];
if ( itoa(1234, (char *)&buff) != 1 ) return 1;
if ( strcmp((char *)&buff, "1234") != 0 ) return 2;
if ( itoa(65536, (char *)&buff) != 1 ) return 3;
if ( strcmp((char *)&buff, "65536") != 0 ) return 4;
if ( itoa(-32767, (char *)&buff) != 1 ) return 5;
if ( strcmp((char *)&buff, "-32767") != 0 ) return 6;
return 0;
}

2
tests/stdlib_itoa.deps Normal file
View File

@@ -0,0 +1,2 @@
stdlib
string