mirror of
https://github.com/bradgrantham/apple2a.git
synced 2025-02-18 11:30:28 +00:00
1668 lines
48 KiB
C
1668 lines
48 KiB
C
#include <string.h>
|
|
|
|
#include "exporter.h"
|
|
#include "platform.h"
|
|
#include "runtime.h"
|
|
|
|
uint8_t *title = "Apple IIa";
|
|
uint8_t title_length = 9;
|
|
|
|
// 6502 instructions.
|
|
#define I_ORA_ZPG 0x05
|
|
#define I_PHP 0x08
|
|
#define I_CLC 0x18
|
|
#define I_JSR 0x20
|
|
#define I_PLP 0x28
|
|
#define I_SEC 0x38
|
|
#define I_PHA 0x48
|
|
#define I_JMP_ABS 0x4C
|
|
#define I_RTS 0x60
|
|
#define I_ADC_ZPG 0x65
|
|
#define I_PLA 0x68
|
|
#define I_JMP_IND 0x6C
|
|
#define I_ADC_ZPG_Y 0x71
|
|
#define I_STA_ZPG 0x85
|
|
#define I_STX_ZPG 0x86
|
|
#define I_TXA 0x8A
|
|
#define I_STA_IND_Y 0x91
|
|
#define I_LDY_IMM 0xA0
|
|
#define I_LDX_IMM 0xA2
|
|
#define I_LDA_ZPG 0xA5
|
|
#define I_LDX_ZPG 0xA6
|
|
#define I_LDA_IMM 0xA9
|
|
#define I_TAX 0xAA
|
|
#define I_INY 0xC8
|
|
#define I_BNE_REL 0xD0
|
|
#define I_BEQ_REL 0xF0
|
|
|
|
// Tokens.
|
|
#define T_HOME 0x80
|
|
#define T_PRINT 0x81
|
|
#define T_LIST 0x82
|
|
#define T_POKE 0x83
|
|
#define T_RUN 0x84
|
|
#define T_NEW 0x85
|
|
#define T_PLUS 0x86
|
|
#define T_MINUS 0x87
|
|
#define T_ASTERISK 0x88
|
|
#define T_SLASH 0x89
|
|
#define T_CARET 0x8A
|
|
#define T_AND 0x8B
|
|
#define T_OR 0x8C
|
|
#define T_GREATER_THAN 0x8D
|
|
#define T_EQUAL 0x8E
|
|
#define T_LESS_THAN 0x8F
|
|
#define T_GOTO 0x90
|
|
#define T_IF 0x91
|
|
#define T_THEN 0x92
|
|
#define T_GR 0x93
|
|
#define T_TEXT 0x94
|
|
#define T_COLOR 0x95
|
|
#define T_PLOT 0x96
|
|
#define T_FOR 0x97
|
|
#define T_TO 0x98
|
|
#define T_STEP 0x99
|
|
#define T_NEXT 0x9A
|
|
#define T_NOT 0x9B
|
|
#define T_DIM 0x9C
|
|
#define T_REM 0x9D
|
|
|
|
// Operators. These encode both the operator (high nybble) and the precedence
|
|
// (low nybble). Lower precedence has a lower low nybble value. For example,
|
|
// OP_ADD (0x99) and OP_SUB (0xA9) have the same precedence (9). By convention
|
|
// the precedence is the value of the lowest-valued operator in its class
|
|
// (OP_ADD = 0x99), but only the relative values of precedence matter. All
|
|
// of these are left-associative, as in AppleSoft BASIC. (Even though
|
|
// exponentiation really should be right-associative.)
|
|
#define OP_PRECEDENCE(op) ((op) & 0x0F)
|
|
#define OP_OR 0x00
|
|
#define OP_AND 0x11
|
|
#define OP_NOT 0x22
|
|
#define OP_LTE 0x33
|
|
#define OP_GTE 0x43
|
|
#define OP_EQ 0x55
|
|
#define OP_NEQ 0x65
|
|
#define OP_LT 0x75
|
|
#define OP_GT 0x85
|
|
#define OP_ADD 0x99
|
|
#define OP_SUB 0xA9
|
|
#define OP_MULT 0xBB
|
|
#define OP_DIV 0xCB
|
|
#define OP_NEG 0xDD
|
|
#define OP_EXP 0xEE
|
|
#define OP_ARRAY_DEREF 0xFB // Ignore precedence.
|
|
#define OP_NO_OP 0xFC // Never on the stack.
|
|
#define OP_CLOSE_PARENS 0xFD // Never on the stack.
|
|
#define OP_OPEN_PARENS 0xFE // Ignore precedence.
|
|
#define OP_INVALID 0xFF
|
|
|
|
// Maximum number of lines in stored program.
|
|
#define MAX_LINES 56
|
|
|
|
// Maximum number of operators in the operator stack.
|
|
#define MAX_OP_STACK 16
|
|
|
|
// Maximum number of forward GOTOs.
|
|
#define MAX_FORWARD_GOTO 16
|
|
|
|
// Test for whether a character is a digit.
|
|
#define IS_DIGIT(ch) ((ch) >= '0' && (ch) <= '9')
|
|
|
|
// Test for first and subsequent variable name letters.
|
|
#define IS_FIRST_VARIABLE_LETTER(ch) ((ch) >= 'A' && (ch) <= 'Z')
|
|
#define IS_SUBSEQUENT_VARIABLE_LETTER(ch) (IS_FIRST_VARIABLE_LETTER(ch) || IS_DIGIT(ch))
|
|
|
|
// Info for each "forward GOTO", which is a GOTO to a line that we've
|
|
// not compiled yet.
|
|
typedef struct {
|
|
// The line number the GOTO is on. This is for error messages.
|
|
uint16_t source_line_number;
|
|
|
|
// The line number it's trying to jump to.
|
|
uint16_t target_line_number;
|
|
|
|
// The address of the JMP instructions. This is 0 if this entry is unused.
|
|
uint8_t *jmp_address;
|
|
} ForwardGoto;
|
|
|
|
// Info for each compiled line.
|
|
typedef struct {
|
|
// The line's number.
|
|
uint16_t line_number;
|
|
|
|
// The address in memory where its code was compiled.
|
|
uint8_t *code;
|
|
} LineInfo;
|
|
|
|
// List of tokens. The token value is the index plus 0x80.
|
|
static uint8_t *TOKEN[] = {
|
|
"HOME",
|
|
"PRINT",
|
|
"LIST",
|
|
"POKE",
|
|
"RUN",
|
|
"NEW",
|
|
"+",
|
|
"-",
|
|
"*",
|
|
"/",
|
|
"^",
|
|
"AND",
|
|
"OR",
|
|
">",
|
|
"=",
|
|
"<",
|
|
"GOTO",
|
|
"IF",
|
|
"THEN",
|
|
"GR",
|
|
"TEXT",
|
|
"COLOR",
|
|
"PLOT",
|
|
"FOR",
|
|
"TO",
|
|
"STEP",
|
|
"NEXT",
|
|
"NOT",
|
|
"DIM",
|
|
"REM",
|
|
};
|
|
static int16_t TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]);
|
|
|
|
uint8_t g_input_buffer[80];
|
|
int16_t g_input_buffer_length;
|
|
|
|
// Compiled binary.
|
|
uint8_t g_compiled[1024*10];
|
|
uint8_t *g_c = g_compiled;
|
|
void (*g_compiled_function)() = (void (*)()) g_compiled;
|
|
|
|
// Stored program. Each line is:
|
|
// - Two bytes for pointer to next line (or zero if none).
|
|
// - Two bytes for line number.
|
|
// - Program line.
|
|
// - Nul.
|
|
uint8_t g_program[1024];
|
|
|
|
// Info about each compiled line.
|
|
LineInfo g_line_info[MAX_LINES];
|
|
uint8_t g_line_info_count;
|
|
|
|
// Operator stack, of the expression-evaluation routines. These are from the
|
|
// OP_ constants.
|
|
uint8_t g_op_stack[MAX_OP_STACK];
|
|
uint8_t g_op_stack_size;
|
|
|
|
// List of all forward GOTOs. These are packed at the beginning, so the
|
|
// first invalid (jmp_address == 0) entry marks the end.
|
|
ForwardGoto g_forward_goto[MAX_FORWARD_GOTO];
|
|
uint8_t g_forward_goto_count;
|
|
|
|
/**
|
|
* Print the tokenized string, with tokens displayed as their full text.
|
|
* Prints a newline at the end.
|
|
*/
|
|
static void print_detokenized(uint8_t *s) {
|
|
while (*s != '\0') {
|
|
if (*s >= 0x80) {
|
|
print_char(' ');
|
|
print(TOKEN[*s - 0x80]);
|
|
print_char(' ');
|
|
} else {
|
|
print_char(*s);
|
|
}
|
|
|
|
s += 1;
|
|
}
|
|
|
|
print_char('\n');
|
|
}
|
|
|
|
/**
|
|
* Get the pointer to the next line in the stored program. Returns 0
|
|
* if we're at the end.
|
|
*/
|
|
static uint8_t *get_next_line(uint8_t *line) {
|
|
return *((uint8_t **) line);
|
|
}
|
|
|
|
/**
|
|
* Get the line number of a stored program line.
|
|
*/
|
|
static uint16_t get_line_number(uint8_t *line) {
|
|
return *((uint16_t *) (line + 2));
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to the end of the program. This is one byte PAST the
|
|
* last bytes in the program, which are two nuls. The "line" parameter is
|
|
* an optional starting point, to use as an optimization instead of starting
|
|
* from the beginning.
|
|
*/
|
|
static uint8_t *get_end_of_program(uint8_t *line) {
|
|
uint8_t *next_line;
|
|
|
|
if (line == 0) {
|
|
// Start at the beginning if not specified.
|
|
line = g_program;
|
|
}
|
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
|
line = next_line;
|
|
}
|
|
|
|
// Skip the null "next" pointer.
|
|
return line + 2;
|
|
}
|
|
|
|
/**
|
|
* Clear the stored program.
|
|
*/
|
|
static void new_statement() {
|
|
g_program[0] = '\0';
|
|
g_program[1] = '\0';
|
|
}
|
|
|
|
/**
|
|
* List the stored program.
|
|
*/
|
|
static void list_statement() {
|
|
uint8_t *line = g_program;
|
|
uint8_t *next_line;
|
|
|
|
print_newline();
|
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
|
print_uint(get_line_number(line));
|
|
print_char(' ');
|
|
print_detokenized(line + 4);
|
|
|
|
line = next_line;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a starts with string b, returns the position in a after b. Else returns null.
|
|
*/
|
|
static uint8_t *skip_over(uint8_t *a, uint8_t *b) {
|
|
while (*a != '\0' && *b != '\0') {
|
|
if (*a != *b) {
|
|
// Doesn't start with b.
|
|
return 0;
|
|
}
|
|
|
|
a += 1;
|
|
b += 1;
|
|
}
|
|
|
|
// See if we're at the end of b.
|
|
return *b == '\0' ? a : 0;
|
|
}
|
|
|
|
/**
|
|
* Add a function call to the compiled buffer.
|
|
*/
|
|
static void add_call(void *function) {
|
|
uint16_t addr = (uint16_t) function;
|
|
|
|
g_c[0] = I_JSR;
|
|
g_c[1] = addr & 0xFF;
|
|
g_c[2] = addr >> 8;
|
|
g_c += 3;
|
|
}
|
|
|
|
/**
|
|
* Add a function return to the compiled buffer.
|
|
*/
|
|
static void add_return() {
|
|
*g_c++ = I_RTS;
|
|
}
|
|
|
|
/**
|
|
* Parse an unsigned integer, returning the value and moving the pointer
|
|
* past the end of the number. The pointer must already be at the beginning
|
|
* of the number.
|
|
*/
|
|
static uint16_t parse_uint16(uint8_t **s_ptr) {
|
|
uint16_t value = 0;
|
|
uint8_t *s = *s_ptr;
|
|
|
|
while (IS_DIGIT(*s)) {
|
|
value = value*10 + (*s - '0');
|
|
s += 1;
|
|
}
|
|
|
|
*s_ptr = s;
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Generate code to put the value into AX.
|
|
*/
|
|
static void compile_load_ax(uint16_t value) {
|
|
g_c[0] = I_LDX_IMM;
|
|
g_c[1] = value >> 8;
|
|
g_c[2] = I_LDA_IMM;
|
|
g_c[3] = value & 0xFF;
|
|
g_c += 4;
|
|
}
|
|
|
|
/**
|
|
* Generate code to store AX to a zero-page word.
|
|
*/
|
|
static void compile_store_zero_page(uint8_t addr) {
|
|
g_c[0] = I_STA_ZPG;
|
|
g_c[1] = addr;
|
|
g_c[2] = I_STX_ZPG;
|
|
g_c[3] = addr + 1;
|
|
g_c += 4;
|
|
}
|
|
|
|
/**
|
|
* Generate code to load AX from a zero-page word.
|
|
*/
|
|
static void compile_load_zero_page(uint8_t addr) {
|
|
g_c[0] = I_LDA_ZPG;
|
|
g_c[1] = addr;
|
|
g_c[2] = I_LDX_ZPG;
|
|
g_c[3] = addr + 1;
|
|
g_c += 4;
|
|
}
|
|
|
|
/**
|
|
* Find a variable by name. The buffer pointer must already be on the
|
|
* first letter of a variable. Only the first two letters are considered.
|
|
* Advances the pointer past the variable name (including letters after
|
|
* the first two). Returns the VarInfo structure, or 0 if we can't find
|
|
* the variable or create it.
|
|
*/
|
|
static VarInfo *find_variable(uint8_t **buffer) {
|
|
uint8_t *s = *buffer;
|
|
VarInfo *var = g_variables;
|
|
uint16_t name;
|
|
int16_t i;
|
|
uint8_t data_type;
|
|
|
|
// Pull out the variable name.
|
|
name = *s++;
|
|
if (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) {
|
|
name |= *s++ << 8;
|
|
}
|
|
// Skip rest of name.
|
|
while (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) {
|
|
s++;
|
|
}
|
|
|
|
// Determine data type based on next letter. Don't skip over the open
|
|
// parenthesis.
|
|
data_type = *s == '(' ? DT_ARRAY : DT_INT;
|
|
|
|
// Look for our variable or the first unused slot.
|
|
for (i = 0; i < MAX_VARIABLES; i++, var++) {
|
|
if (var->name == 0) {
|
|
// First free entry. Allocate it.
|
|
var->name = name;
|
|
var->data_type = data_type;
|
|
break;
|
|
} else if (var->name == name && var->data_type == data_type) {
|
|
// Found it.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == MAX_VARIABLES) {
|
|
// Not found and can't create it.
|
|
var = 0;
|
|
} else {
|
|
// Advance pointer.
|
|
*buffer = s;
|
|
}
|
|
|
|
return var;
|
|
}
|
|
|
|
/**
|
|
* Get the zero page address of a VarInfo pointer's variable.
|
|
*/
|
|
static uint8_t get_var_address(VarInfo *var) {
|
|
return FIRST_VARIABLE + 2*(var - g_variables);
|
|
}
|
|
|
|
/**
|
|
* Find the address of a line in the compiled buffer, or 0 if not found.
|
|
*/
|
|
static uint8_t *find_line_address(uint16_t line_number) {
|
|
int i;
|
|
|
|
for (i = 0; i < g_line_info_count; i++) {
|
|
LineInfo *l = &g_line_info[i];
|
|
|
|
if (l->line_number == line_number) {
|
|
return l->code;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Pop an operator off the operator stack and compile it.
|
|
*/
|
|
static void pop_operator_stack() {
|
|
uint8_t op = g_op_stack[--g_op_stack_size];
|
|
register uint8_t *c;
|
|
|
|
switch (op) {
|
|
case OP_ADD:
|
|
add_call(tosaddax);
|
|
break;
|
|
|
|
case OP_SUB:
|
|
add_call(tossubax);
|
|
break;
|
|
|
|
case OP_MULT:
|
|
add_call(tosmulax);
|
|
break;
|
|
|
|
case OP_DIV:
|
|
add_call(tosdivax);
|
|
break;
|
|
|
|
case OP_EQ:
|
|
add_call(toseqax);
|
|
break;
|
|
|
|
case OP_NEQ:
|
|
add_call(tosneax);
|
|
break;
|
|
|
|
case OP_LT:
|
|
add_call(tosltax);
|
|
break;
|
|
|
|
case OP_GT:
|
|
add_call(tosgtax);
|
|
break;
|
|
|
|
case OP_LTE:
|
|
add_call(tosleax);
|
|
break;
|
|
|
|
case OP_GTE:
|
|
add_call(tosgeax);
|
|
break;
|
|
|
|
case OP_AND:
|
|
// AppleSoft BASIC does not have short-circuit logical operators.
|
|
|
|
// See if second operand is 0.
|
|
c = g_c;
|
|
c[0] = I_STX_ZPG;
|
|
c[1] = (uint8_t) &tmp1;
|
|
c[2] = I_ORA_ZPG;
|
|
c[3] = (uint8_t) &tmp1;
|
|
c[4] = I_PHP; // Save the zero bit.
|
|
g_c = c + 5;
|
|
add_call(popax); // Pop first parameters. 3 instructions.
|
|
c = g_c;
|
|
c[0] = I_PLP; // Restore zero bit from second operand.
|
|
c[1] = I_BNE_REL; // Check whether second operand was zero.
|
|
c[2] = 4; // Not zero, continue to first parameter.
|
|
c[3] = I_LDA_IMM;
|
|
c[4] = 0;
|
|
c[5] = I_BEQ_REL; // Zero bit always true here, will always jump.
|
|
c[6] = 8; // Set X to zero and exit.
|
|
// See if first operand (we just popped) is 0.
|
|
c[7] = I_STX_ZPG;
|
|
c[8] = (uint8_t) &tmp1;
|
|
c[9] = I_ORA_ZPG;
|
|
c[10] = (uint8_t) &tmp1;
|
|
// If so, skip setting A to 1. A contains 0.
|
|
c[11] = I_BEQ_REL;
|
|
c[12] = 2; // The LDA below.
|
|
// Set A to 1.
|
|
c[13] = I_LDA_IMM;
|
|
c[14] = 1;
|
|
c[15] = I_LDX_IMM; // The BEQs above arrive here.
|
|
c[16] = 0;
|
|
g_c = c + 17;
|
|
break;
|
|
|
|
case OP_OR:
|
|
// AppleSoft BASIC does not have short-circuit logical operators.
|
|
|
|
// See if second operand is 0.
|
|
c = g_c;
|
|
c[0] = I_STX_ZPG;
|
|
c[1] = (uint8_t) &tmp1;
|
|
c[2] = I_ORA_ZPG;
|
|
c[3] = (uint8_t) &tmp1;
|
|
c[4] = I_STA_ZPG;
|
|
c[5] = (uint8_t) &tmp2; // Store OR of bytes in tmp2 for later.
|
|
g_c = c + 6;
|
|
add_call(popax); // Pop first parameters. 3 instructions.
|
|
c = g_c;
|
|
// See if first operand is 0.
|
|
c[0] = I_STX_ZPG;
|
|
c[1] = (uint8_t) &tmp1;
|
|
c[2] = I_ORA_ZPG;
|
|
c[3] = (uint8_t) &tmp1;
|
|
c[4] = I_ORA_ZPG;
|
|
c[5] = (uint8_t) &tmp2; // OR with other parameter.
|
|
// If so, skip setting A to 1. A contains 0.
|
|
c[6] = I_BEQ_REL;
|
|
c[7] = 2; // The LDA below.
|
|
// Set A to 1.
|
|
c[8] = I_LDA_IMM;
|
|
c[9] = 1;
|
|
c[10] = I_LDX_IMM; // The BEQs above arrive here.
|
|
c[11] = 0;
|
|
g_c = c + 12;
|
|
break;
|
|
|
|
case OP_NOT:
|
|
add_call(bnegax);
|
|
break;
|
|
|
|
case OP_NEG:
|
|
add_call(negax);
|
|
break;
|
|
|
|
case OP_ARRAY_DEREF:
|
|
// Index is in AX and array address is at the top of the stack.
|
|
|
|
// Double the index, since each entry takes two bytes.
|
|
add_call(aslax1);
|
|
|
|
// Add A to low byte of array address.
|
|
c = g_c;
|
|
c[0] = I_CLC;
|
|
c[1] = I_LDY_IMM; // First entry in stack.
|
|
c[2] = 0;
|
|
c[3] = I_ADC_ZPG_Y;
|
|
c[4] = (uint8_t) &sp;
|
|
c[5] = I_PHA;
|
|
|
|
// Add X to high byte by array address.
|
|
c[6] = I_TXA;
|
|
c[7] = I_INY;
|
|
c[8] = I_ADC_ZPG_Y;
|
|
c[9] = (uint8_t) &sp;
|
|
c[10] = I_TAX;
|
|
c[11] = I_PLA;
|
|
g_c = c + 12;
|
|
|
|
// Load word at AX.
|
|
add_call(ldaxi);
|
|
|
|
// Discard address off stack.
|
|
add_call(incsp2);
|
|
break;
|
|
|
|
case OP_OPEN_PARENS:
|
|
// No-op.
|
|
break;
|
|
|
|
default:
|
|
print("Unhandled operator\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push an operator onto the operator stack. Follow the Shunting-yard
|
|
* algorithm so that higher-precedence operators are performed
|
|
* first.
|
|
*
|
|
* https://en.wikipedia.org/wiki/Shunting-yard_algorithm
|
|
* http://wcipeg.com/wiki/Shunting_yard_algorithm
|
|
*/
|
|
static void push_operator_stack(uint8_t op) {
|
|
uint8_t top_op;
|
|
|
|
// Don't pop anything off if op is unary.
|
|
if (op != OP_NOT && op != OP_NEG) {
|
|
// All our operators are left-associative, so no special check for the case
|
|
// of equal precedence.
|
|
while (g_op_stack_size > 0 &&
|
|
op != OP_OPEN_PARENS &&
|
|
op != OP_ARRAY_DEREF &&
|
|
(top_op = g_op_stack[g_op_stack_size - 1]) != OP_OPEN_PARENS &&
|
|
top_op != OP_ARRAY_DEREF &&
|
|
OP_PRECEDENCE(top_op) >= OP_PRECEDENCE(op)) {
|
|
|
|
pop_operator_stack();
|
|
}
|
|
}
|
|
|
|
// TODO Check for g_op_stack overflow.
|
|
g_op_stack[g_op_stack_size++] = op;
|
|
}
|
|
|
|
/**
|
|
* Parse an expression, generating code to compute it, leaving the
|
|
* result in AX.
|
|
*/
|
|
static uint8_t *compile_expression(uint8_t *s) {
|
|
char have_value_in_ax = 0;
|
|
uint8_t expect_unary = 1; // Expect unary operator at start of expression.
|
|
|
|
while (1) {
|
|
if (IS_DIGIT(*s)) {
|
|
// Parse number.
|
|
if (have_value_in_ax) {
|
|
// Push on the number stack.
|
|
add_call(pushax);
|
|
}
|
|
|
|
compile_load_ax(parse_uint16(&s));
|
|
have_value_in_ax = 1;
|
|
|
|
// Expect binary operator after operand.
|
|
expect_unary = 0;
|
|
} else if (IS_FIRST_VARIABLE_LETTER(*s)) {
|
|
// Variable reference.
|
|
VarInfo *var = find_variable(&s);
|
|
|
|
if (have_value_in_ax) {
|
|
// Push on the number stack.
|
|
add_call(pushax);
|
|
}
|
|
|
|
if (var == 0) {
|
|
// TODO: Not sure how to deal with this. For now just
|
|
// fill in with zero, since assigning to this elsewhere
|
|
// will cause an error.
|
|
compile_load_ax(0);
|
|
} else {
|
|
uint8_t var_addr = get_var_address(var);
|
|
|
|
// Load from var.
|
|
compile_load_zero_page(var_addr);
|
|
|
|
if (var->data_type == DT_ARRAY) {
|
|
// TODO: Check that it's been DIM'ed. The data at var_addr should
|
|
// not be zero.
|
|
|
|
// Treat the open parenthesis as an array-dereferencing operator.
|
|
if (*s == '(') {
|
|
s += 1;
|
|
push_operator_stack(OP_ARRAY_DEREF);
|
|
expect_unary = 1;
|
|
} else {
|
|
// This is really a programming error, since the
|
|
// variable should only be of type DT_ARRAY if it's
|
|
// followed by an open parenthesis.
|
|
}
|
|
}
|
|
}
|
|
have_value_in_ax = 1;
|
|
|
|
// Expect binary operator after operand.
|
|
expect_unary = 0;
|
|
} else {
|
|
// Check if it's an operator.
|
|
uint8_t op = OP_INVALID;
|
|
|
|
if (*s == T_PLUS) {
|
|
op = expect_unary ? OP_NO_OP : OP_ADD;
|
|
} else if (*s == T_MINUS) {
|
|
op = expect_unary ? OP_NEG : OP_SUB;
|
|
} else if (*s == T_ASTERISK) {
|
|
op = OP_MULT;
|
|
} else if (*s == T_SLASH) {
|
|
op = OP_DIV;
|
|
} else if (*s == T_AND) {
|
|
op = OP_AND;
|
|
} else if (*s == T_OR) {
|
|
op = OP_OR;
|
|
} else if (*s == T_NOT) {
|
|
if (expect_unary) {
|
|
op = OP_NOT;
|
|
} else {
|
|
// TODO generate syntax error.
|
|
break;
|
|
}
|
|
} else if (*s == T_EQUAL) {
|
|
if (s[1] == T_LESS_THAN) {
|
|
s += 1;
|
|
op = OP_LTE;
|
|
} else if (s[1] == T_GREATER_THAN) {
|
|
s += 1;
|
|
op = OP_GTE;
|
|
} else {
|
|
op = OP_EQ;
|
|
}
|
|
} else if (*s == T_LESS_THAN) {
|
|
if (s[1] == T_EQUAL) {
|
|
s += 1;
|
|
op = OP_LTE;
|
|
} else if (s[1] == T_GREATER_THAN) {
|
|
s += 1;
|
|
op = OP_NEQ;
|
|
} else {
|
|
op = OP_LT;
|
|
}
|
|
} else if (*s == T_GREATER_THAN) {
|
|
if (s[1] == T_EQUAL) {
|
|
s += 1;
|
|
op = OP_GTE;
|
|
} else if (s[1] == T_LESS_THAN) {
|
|
s += 1;
|
|
op = OP_NEQ;
|
|
} else {
|
|
op = OP_GT;
|
|
}
|
|
} else if (*s == '(') { // Parentheses are not tokenized.
|
|
op = OP_OPEN_PARENS;
|
|
} else if (*s == ')') { // Parentheses are not tokenized.
|
|
uint8_t top_op;
|
|
|
|
op = OP_CLOSE_PARENS;
|
|
|
|
// Pop until open parenthesis or array dereference.
|
|
while (g_op_stack_size > 0 &&
|
|
(top_op = g_op_stack[g_op_stack_size - 1]) != OP_OPEN_PARENS &&
|
|
top_op != OP_ARRAY_DEREF) {
|
|
|
|
pop_operator_stack();
|
|
}
|
|
if (g_op_stack_size == 0) {
|
|
// Maybe this close parenthesis wasn't ours. For example,
|
|
// "DIM X(5)". Treat it like end of expression.
|
|
op = OP_INVALID;
|
|
} else {
|
|
// Pop open parenthesis or array dereference.
|
|
pop_operator_stack();
|
|
}
|
|
}
|
|
|
|
// Check that we didn't have an inappropriate unary operator.
|
|
if (expect_unary && op != OP_NO_OP && op != OP_NEG && op != OP_NOT &&
|
|
op != OP_OPEN_PARENS && op != OP_ARRAY_DEREF) {
|
|
|
|
// TODO we should generate a syntax error here.
|
|
print("Unexpected unary\n");
|
|
break;
|
|
}
|
|
|
|
// Expect unary operator after operators or open parenthesis. Expect
|
|
// binary operator after close parenthesis.
|
|
expect_unary = op != OP_CLOSE_PARENS;
|
|
|
|
if (op != OP_INVALID) {
|
|
s += 1;
|
|
if (op != OP_CLOSE_PARENS && op != OP_NO_OP) {
|
|
push_operator_stack(op);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (have_value_in_ax) {
|
|
// Empty the operator stack.
|
|
while (g_op_stack_size > 0) {
|
|
if (g_op_stack[g_op_stack_size - 1] == OP_OPEN_PARENS) {
|
|
// TODO we should generate a syntax error here.
|
|
print("Extra open parenthesis\n");
|
|
}
|
|
pop_operator_stack();
|
|
}
|
|
} else {
|
|
// Something went wrong, we never got anything.
|
|
print("Expression has no content\n");
|
|
compile_load_ax(0);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Tokenize a string in place. Returns (and removes) any line number, or
|
|
* INVALID_LINE_NUMBER if there's none.
|
|
*/
|
|
static uint16_t tokenize(uint8_t *s) {
|
|
uint8_t *t = s; // Tokenized version.
|
|
int16_t line_number;
|
|
|
|
// Parse optional line number.
|
|
if (IS_DIGIT(*s)) {
|
|
line_number = parse_uint16(&s);
|
|
} else {
|
|
line_number = INVALID_LINE_NUMBER;
|
|
}
|
|
|
|
// Convert tokens.
|
|
while (*s != '\0') {
|
|
if (*s == ' ') {
|
|
// Skip spaces.
|
|
s++;
|
|
} else {
|
|
int16_t i;
|
|
uint8_t *skipped = 0;
|
|
|
|
// Try every token.
|
|
for (i = 0; i < TOKEN_COUNT; i++) {
|
|
// Quick optimization, peek at the first letter.
|
|
skipped = s[0] == TOKEN[i][0] ? skip_over(s, TOKEN[i]) : 0;
|
|
if (skipped != 0) {
|
|
// Record token.
|
|
*t++ = 0x80 + i;
|
|
s = skipped;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (skipped == 0) {
|
|
// Didn't find a token, just copy text.
|
|
*t++ = *s++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Terminate string.
|
|
*t++ = '\0';
|
|
|
|
return line_number;
|
|
}
|
|
|
|
/**
|
|
* Find the stored program line with the given line number. If the line does
|
|
* not exist, returns a pointer to the location where it should be inserted.
|
|
*/
|
|
static uint8_t *find_line(uint16_t line_number) {
|
|
uint8_t *line = g_program;
|
|
uint8_t *next_line;
|
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
|
// See if we hit it or just blew past it.
|
|
if (get_line_number(line) >= line_number) {
|
|
break;
|
|
}
|
|
|
|
line = next_line;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
/**
|
|
* Adds a new entry to the list for forward GOTOs. Returns whether successful.
|
|
*/
|
|
static uint8_t add_forward_goto(uint16_t source_line_number, uint16_t target_line_number,
|
|
uint8_t *jmp_address) {
|
|
|
|
ForwardGoto *f;
|
|
|
|
if (g_forward_goto_count == MAX_FORWARD_GOTO) {
|
|
return 0;
|
|
}
|
|
|
|
f = &g_forward_goto[g_forward_goto_count++];
|
|
f->source_line_number = source_line_number;
|
|
f->target_line_number = target_line_number;
|
|
f->jmp_address = jmp_address;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Go through the list of forward GOTOs and set their jumps to this code.
|
|
*/
|
|
static void fix_up_forward_gotos(uint16_t line_number, uint8_t *code) {
|
|
int i;
|
|
ForwardGoto *f;
|
|
uint16_t addr = (uint16_t) code;
|
|
|
|
for (i = 0; i < g_forward_goto_count; i++) {
|
|
f = &g_forward_goto[i];
|
|
|
|
if (f->target_line_number == line_number) {
|
|
// Fill in jump address.
|
|
f->jmp_address[1] = addr & 0xFF;
|
|
f->jmp_address[2] = addr >> 8;
|
|
|
|
// Swap last entry with this one. It's okay if these
|
|
// are the same entry.
|
|
*f = g_forward_goto[g_forward_goto_count - 1];
|
|
|
|
// Reduce size of array.
|
|
g_forward_goto_count -= 1;
|
|
|
|
// Re-process this entry, since we've swapped it.
|
|
i -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds an entry to the list of line infos. Returns whether successful.
|
|
*/
|
|
static uint8_t add_line_info(uint16_t line_number, uint8_t *code) {
|
|
LineInfo *l;
|
|
|
|
if (g_line_info_count == MAX_LINES) {
|
|
// TODO not sure what to do here.
|
|
print("Program too large");
|
|
return 0;
|
|
}
|
|
|
|
l = &g_line_info[g_line_info_count++];
|
|
l->line_number = line_number;
|
|
l->code = code;
|
|
|
|
// Fix up any forward GOTOs to this line.
|
|
fix_up_forward_gotos(line_number, code);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Call to configure the compilation step.
|
|
*/
|
|
static void set_up_compile(void) {
|
|
g_c = g_compiled;
|
|
g_line_info_count = 0;
|
|
g_forward_goto_count = 0;
|
|
}
|
|
|
|
/**
|
|
* Compile the tokenized line of BASIC, adding it to the g_compiled binary.
|
|
*/
|
|
static void compile_buffer(uint8_t *buffer, uint16_t line_number) {
|
|
uint8_t *s = buffer;
|
|
uint8_t done;
|
|
// Keep track of addresses that point to the end of the line.
|
|
uint8_t **end_of_line_address[4];
|
|
uint8_t end_of_line_count = 0;
|
|
register uint8_t *c;
|
|
|
|
do {
|
|
int8_t error = 0;
|
|
int8_t continue_statement = 0;
|
|
|
|
// Default to being done after one statement.
|
|
done = 1;
|
|
|
|
if (*s == '\0' || *s == ':') {
|
|
// Empty statement. We skip the colon below.
|
|
} else if (IS_FIRST_VARIABLE_LETTER(*s)) {
|
|
// Must be variable assignment.
|
|
VarInfo *var = find_variable(&s);
|
|
if (var == 0) {
|
|
// TODO: Nicer error specifically for out of variable space.
|
|
error = 1;
|
|
} else {
|
|
uint8_t var_addr = get_var_address(var);
|
|
|
|
if (var->data_type == DT_ARRAY) {
|
|
// Array element assignment.
|
|
|
|
// Compile index expression. Skip open parenthesis.
|
|
s = compile_expression(s + 1);
|
|
if (*s != ')') {
|
|
error = 1;
|
|
} else {
|
|
s += 1;
|
|
|
|
// Index is on the stack. Double the index, since each
|
|
// entry takes two bytes.
|
|
add_call(aslax1);
|
|
|
|
// Add A to low byte of array address.
|
|
c = g_c;
|
|
c[0] = I_CLC;
|
|
c[1] = I_ADC_ZPG;
|
|
c[2] = var_addr;
|
|
c[3] = I_PHA;
|
|
|
|
// Add X to high byte by array address.
|
|
c[4] = I_TXA;
|
|
c[5] = I_ADC_ZPG;
|
|
c[6] = var_addr + 1;
|
|
c[7] = I_TAX;
|
|
c[8] = I_PLA;
|
|
g_c = c + 9;
|
|
|
|
// Push element address onto the stack.
|
|
add_call(pushax);
|
|
}
|
|
}
|
|
|
|
if (*s != T_EQUAL || error) {
|
|
error = 1;
|
|
} else {
|
|
// Parse value.
|
|
s = compile_expression(s + 1);
|
|
|
|
if (var->data_type == DT_ARRAY) {
|
|
// Value is in AX, address is on top of stack. The staxspidx
|
|
// function uses Y as an index, so must zero it out.
|
|
*g_c++ = I_LDY_IMM;
|
|
*g_c++ = 0;
|
|
add_call(staxspidx);
|
|
} else {
|
|
// Copy to var.
|
|
compile_store_zero_page(var_addr);
|
|
}
|
|
}
|
|
}
|
|
} else if (*s == T_HOME) {
|
|
s += 1;
|
|
add_call(home);
|
|
} else if (*s == T_PRINT) {
|
|
s += 1;
|
|
|
|
if (*s != '\0' && *s != ':') {
|
|
// Parse expression.
|
|
s = compile_expression(s);
|
|
add_call(print_int);
|
|
}
|
|
|
|
if (*s == ';') {
|
|
// Ends with a semicolon, don't print newline.
|
|
s += 1;
|
|
} else {
|
|
add_call(print_newline);
|
|
}
|
|
} else if (*s == T_LIST) {
|
|
s += 1;
|
|
add_call(list_statement);
|
|
} else if (*s == T_POKE) {
|
|
s += 1;
|
|
// Parse address.
|
|
s = compile_expression(s);
|
|
// Copy from AX to ptr1.
|
|
compile_store_zero_page((uint8_t) &ptr1);
|
|
if (*s != ',') {
|
|
error = 1;
|
|
} else {
|
|
s++;
|
|
// Parse value. LSB is in A.
|
|
s = compile_expression(s);
|
|
c = g_c;
|
|
c[0] = I_LDY_IMM; // Zero out Y.
|
|
c[1] = 0;
|
|
c[2] = I_STA_IND_Y; // Store at *ptr1.
|
|
c[3] = (uint8_t) &ptr1;
|
|
g_c = c + 4;
|
|
}
|
|
} else if (*s == T_GOTO) {
|
|
s += 1;
|
|
|
|
if (!IS_DIGIT(*s)) {
|
|
error = 1;
|
|
} else {
|
|
uint16_t target_line_number = parse_uint16(&s);
|
|
uint16_t addr = (uint16_t) find_line_address(target_line_number);
|
|
|
|
if (addr == 0) {
|
|
// Line not found. Must be a forward GOTO. Record it
|
|
// and keep going.
|
|
uint8_t success = add_forward_goto(line_number, target_line_number,
|
|
g_c);
|
|
if (!success) {
|
|
// TODO handle error.
|
|
}
|
|
}
|
|
|
|
c = g_c;
|
|
c[0] = I_JMP_ABS;
|
|
c[1] = addr & 0xFF;
|
|
c[2] = addr >> 8;
|
|
g_c = c + 3;
|
|
}
|
|
} else if (*s == T_IF) {
|
|
// Save where we are in case we need to roll back.
|
|
uint8_t *saved_c = g_c;
|
|
|
|
s += 1;
|
|
// Parse conditional expression.
|
|
s = compile_expression(s);
|
|
// Check if AX is zero. Or the two bytes together, through the zero page.
|
|
c = g_c;
|
|
c[0] = I_STX_ZPG;
|
|
c[1] = (uint8_t) &tmp1;
|
|
c[2] = I_ORA_ZPG;
|
|
c[3] = (uint8_t) &tmp1;
|
|
// If so, skip to end of this line.
|
|
c[4] = I_BNE_REL;
|
|
c[5] = 3; // Skip over absolute jump.
|
|
c[6] = I_JMP_ABS;
|
|
c += 7;
|
|
// TODO Check for overflow of end_of_line_address:
|
|
end_of_line_address[end_of_line_count++] = (uint8_t **) c;
|
|
c[0] = 0; // Address of next line.
|
|
c[1] = 0; // Address of next line.
|
|
g_c = c + 2;
|
|
|
|
if (*s == T_THEN) {
|
|
// Skip THEN and continue
|
|
s += 1;
|
|
continue_statement = 1;
|
|
} else if (*s == T_GOTO) {
|
|
// Just continue, we'll pick it up after the loop.
|
|
continue_statement = 1;
|
|
} else {
|
|
// Must be THEN or GOTO. Erase what we've done.
|
|
g_c = saved_c;
|
|
error = 1;
|
|
}
|
|
} else if (*s == T_FOR) {
|
|
uint8_t *loop_top_addr_addr = 0;
|
|
|
|
s += 1;
|
|
|
|
// We'll set this to 0 if we succeed.
|
|
error = 1;
|
|
|
|
if (IS_FIRST_VARIABLE_LETTER(*s)) {
|
|
VarInfo *var;
|
|
|
|
// For the error message.
|
|
compile_load_ax(line_number);
|
|
add_call(pushax);
|
|
|
|
var = find_variable(&s);
|
|
if (var == 0) {
|
|
// TODO: Nicer error specifically for out of variable space.
|
|
} else if (var->data_type == DT_ARRAY) {
|
|
// Syntax error, can't use array index for FOR loop variable.
|
|
} else {
|
|
uint16_t var_addr = get_var_address(var);
|
|
|
|
compile_load_ax(var_addr);
|
|
add_call(pushax);
|
|
|
|
if (*s == T_EQUAL) {
|
|
s += 1;
|
|
|
|
// Parse initial value.
|
|
s = compile_expression(s);
|
|
|
|
// Copy to var.
|
|
compile_store_zero_page(var_addr);
|
|
|
|
if (*s == T_TO) {
|
|
s += 1;
|
|
|
|
// Parse end value.
|
|
s = compile_expression(s);
|
|
add_call(pushax);
|
|
|
|
if (*s == T_STEP) {
|
|
s += 1;
|
|
|
|
// Parse step.
|
|
s = compile_expression(s);
|
|
} else {
|
|
// Default to step of 1.
|
|
compile_load_ax(1);
|
|
}
|
|
add_call(pushax);
|
|
|
|
// Finally, the address at the top of the FOR loop.
|
|
// We don't have it yet, so we leave space and record
|
|
// the location where the NEXT should jump to.
|
|
// Don't use compile_load_ax() here because a change
|
|
// there would mess up how we fill it in below. Inline
|
|
// it here so we have control over that.
|
|
loop_top_addr_addr = g_c;
|
|
g_c[0] = I_LDX_IMM;
|
|
g_c[2] = I_LDA_IMM;
|
|
g_c += 4;
|
|
|
|
add_call(for_statement);
|
|
error = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (loop_top_addr_addr != 0) {
|
|
uint16_t loop_top_addr = (uint16_t) g_c;
|
|
loop_top_addr_addr[1] = loop_top_addr >> 8; // X
|
|
loop_top_addr_addr[3] = loop_top_addr & 0xFF; // A
|
|
}
|
|
} else if (*s == T_NEXT) {
|
|
s += 1;
|
|
|
|
// For the error message.
|
|
compile_load_ax(line_number);
|
|
add_call(pushax);
|
|
|
|
// See if there's the optional variable. We don't support multiple
|
|
// variables ("NEXT I,J").
|
|
if (IS_FIRST_VARIABLE_LETTER(*s)) {
|
|
VarInfo *var = find_variable(&s);
|
|
if (var == 0) {
|
|
// TODO: Nicer error specifically for out of variable space.
|
|
error = 1;
|
|
} else {
|
|
compile_load_ax(get_var_address(var));
|
|
}
|
|
} else {
|
|
// Zero means find the most recent FOR loop.
|
|
compile_load_ax(0);
|
|
}
|
|
|
|
// Process the NEXT instruction.
|
|
add_call(next_statement);
|
|
|
|
// The next_statement() function returns the address to jump
|
|
// to if we're looping, or 0 if we're not.
|
|
|
|
// Copy from AX to ptr1. We must save it because checking it destroys it.
|
|
c = g_c;
|
|
c[0] = I_STA_ZPG;
|
|
c[1] = (uint8_t) &ptr1;
|
|
c[2] = I_STX_ZPG;
|
|
c[3] = (uint8_t) &ptr1 + 1;
|
|
// Check if AX is zero. Destroys AX.
|
|
c[4] = I_ORA_ZPG;
|
|
c[5] = (uint8_t) &ptr1 + 1; // OR X into A.
|
|
// If zero, skip over jump.
|
|
c[6] = I_BEQ_REL;
|
|
c[7] = 3; // Skip over indirect jump.
|
|
// Jump to top of loop, indirectly through ptr1, which has the address.
|
|
c[8] = I_JMP_IND;
|
|
c[9] = (uint8_t) &ptr1 & 0xFF;
|
|
c[10] = (uint8_t) &ptr1 >> 8;
|
|
g_c = c + 11;
|
|
} else if (*s == T_DIM) {
|
|
s += 1;
|
|
|
|
while (1) {
|
|
// Expect variable name.
|
|
if (!IS_FIRST_VARIABLE_LETTER(*s)) {
|
|
error = 1;
|
|
} else {
|
|
VarInfo *var = find_variable(&s);
|
|
|
|
if (var == 0) {
|
|
// TODO handle error.
|
|
error = 1;
|
|
} else {
|
|
// Must be an array variable.
|
|
if (var->data_type != DT_ARRAY) {
|
|
// TODO handle error.
|
|
error = 1;
|
|
} else {
|
|
uint8_t var_addr = get_var_address(var);
|
|
|
|
// Put array address in AX.
|
|
compile_load_zero_page(var_addr);
|
|
|
|
// See if we have an address. If yes, then we've been
|
|
// dimensioned before.
|
|
c = g_c;
|
|
c[0] = I_STX_ZPG;
|
|
c[1] = (uint8_t) &tmp1;
|
|
c[2] = I_ORA_ZPG;
|
|
c[3] = (uint8_t) &tmp1;
|
|
c[4] = I_BEQ_REL; // If zero, branch to actual work.
|
|
c[5] = 8; // Load, call, and return.
|
|
g_c = c + 6;
|
|
compile_load_ax(line_number);
|
|
add_call(redimd_array_error);
|
|
add_return();
|
|
|
|
// Assume we're followed by an open parenthesis. Parse
|
|
// expression for the size of the array.
|
|
s = compile_expression(s + 1);
|
|
|
|
if (*s != ')') {
|
|
error = 1;
|
|
} else {
|
|
s += 1;
|
|
|
|
// AX now holds the size of the array.
|
|
add_call(pushax);
|
|
|
|
// Push address in zero page where the array address
|
|
// should be stored.
|
|
compile_load_ax(var_addr);
|
|
|
|
// Call a runtime routine to allocate it.
|
|
add_call(allocate_array);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*s == ',') {
|
|
// More variables to dim.
|
|
s += 1;
|
|
} else {
|
|
// We're done.
|
|
break;
|
|
}
|
|
}
|
|
} else if (*s == T_REM) {
|
|
// Done with line.
|
|
break;
|
|
} else if (*s == T_GR) {
|
|
s += 1;
|
|
add_call(gr_statement);
|
|
} else if (*s == T_TEXT) {
|
|
s += 1;
|
|
add_call(text_statement);
|
|
} else if (*s == T_COLOR) {
|
|
s += 1;
|
|
if (*s != T_EQUAL) {
|
|
error = 1;
|
|
} else {
|
|
s = compile_expression(s + 1);
|
|
add_call(color_statement);
|
|
}
|
|
} else if (*s == T_PLOT) {
|
|
s += 1;
|
|
s = compile_expression(s);
|
|
add_call(pushax);
|
|
if (*s != ',') {
|
|
error = 1;
|
|
} else {
|
|
s += 1;
|
|
s = compile_expression(s);
|
|
add_call(plot_statement);
|
|
}
|
|
} else {
|
|
error = 1;
|
|
}
|
|
|
|
// Now we're at the end of our statement.
|
|
if (!error) {
|
|
if (continue_statement) {
|
|
// No problem, just continue from here.
|
|
done = 0;
|
|
} else if (*s == ':') {
|
|
// Skip colon.
|
|
s += 1;
|
|
|
|
// Next statement.
|
|
done = 0;
|
|
} else if (*s != '\0') {
|
|
// Junk at the end of the statement.
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
end_of_line_count = 0;
|
|
compile_load_ax(line_number);
|
|
add_call(syntax_error);
|
|
|
|
// Terminate program.
|
|
add_return();
|
|
}
|
|
} while (!done);
|
|
|
|
// Fill in the places where we needed the address of the end of the line.
|
|
while (end_of_line_count > 0) {
|
|
*end_of_line_address[--end_of_line_count] = g_c;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete the compilation buffer and run it.
|
|
*/
|
|
static void complete_compile_and_execute(void) {
|
|
int i;
|
|
uint16_t compiled_length;
|
|
|
|
// Return from function.
|
|
add_return();
|
|
|
|
// Always clear the FOR stack before running. We don't want it
|
|
// either in stored program mode, or in immediate mode.
|
|
clear_for_stack();
|
|
|
|
// Forward GOTOs that couldn't be resolved are changed to
|
|
// jumps to error messages.
|
|
for (i = 0; i < g_forward_goto_count; i++) {
|
|
ForwardGoto *f = &g_forward_goto[i];
|
|
uint16_t addr = (uint16_t) g_c;
|
|
|
|
// Jump to end of buffer.
|
|
f->jmp_address[1] = addr & 0xFF;
|
|
f->jmp_address[2] = addr >> 8;
|
|
|
|
// Add code at end of buffer to show error.
|
|
compile_load_ax(f->source_line_number);
|
|
add_call(undefined_statement_error);
|
|
|
|
// Terminate program.
|
|
add_return();
|
|
}
|
|
|
|
// Dump compiled buffer to the terminal.
|
|
compiled_length = g_c - g_compiled;
|
|
if (1) {
|
|
int i;
|
|
uint8_t *debug_port = (uint8_t *) 0xBFFE;
|
|
|
|
// Start process. Data is ignored.
|
|
debug_port[0] = 0;
|
|
|
|
// Size of program, little endian.
|
|
debug_port[1] = compiled_length & 0xFF;
|
|
debug_port[1] = compiled_length >> 8;
|
|
// Address of program start, little endian.
|
|
debug_port[1] = ((uint16_t) &g_compiled[0]) & 0xFF;
|
|
debug_port[1] = ((uint16_t) &g_compiled[0]) >> 8;
|
|
// Program bytes.
|
|
for (i = 0; i < compiled_length; i++) {
|
|
debug_port[1] = g_compiled[i];
|
|
}
|
|
}
|
|
|
|
if (compiled_length > sizeof(g_compiled)) {
|
|
// TODO: Check while adding bytes, not at the end.
|
|
print("\n?Binary length exceeded");
|
|
} else {
|
|
// Call it.
|
|
g_compiled_function();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear out all variables. This does not clear their value, only our
|
|
* knowledge of them.
|
|
*/
|
|
void clear_variables(void) {
|
|
memset(g_variables, 0, sizeof(g_variables));
|
|
}
|
|
|
|
/**
|
|
* Compile the stored program and execute it.
|
|
*/
|
|
static void compile_stored_program(void) {
|
|
uint8_t *line = g_program;
|
|
uint8_t *next_line;
|
|
|
|
// Clear out all variables.
|
|
clear_variables();
|
|
|
|
set_up_compile();
|
|
|
|
// Clear runtime state.
|
|
add_call(initialize_runtime);
|
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
|
uint16_t line_number = get_line_number(line);
|
|
uint8_t success = add_line_info(line_number, g_c);
|
|
|
|
// Compile just this line.
|
|
compile_buffer(line + 4, line_number);
|
|
|
|
line = next_line;
|
|
}
|
|
|
|
complete_compile_and_execute();
|
|
}
|
|
|
|
/**
|
|
* Process the user's line of input, possibly compiling the code.
|
|
* and executing it.
|
|
*/
|
|
static void process_input_buffer() {
|
|
uint16_t line_number;
|
|
|
|
g_input_buffer[g_input_buffer_length] = '\0';
|
|
|
|
// Tokenize in-place.
|
|
line_number = tokenize(g_input_buffer);
|
|
if (line_number == INVALID_LINE_NUMBER) {
|
|
// Immediate mode.
|
|
|
|
if (g_input_buffer[0] == T_RUN) {
|
|
// We don't compile "RUN".
|
|
compile_stored_program();
|
|
} else if (g_input_buffer[0] == T_NEW) {
|
|
// We don't compile "NEW".
|
|
new_statement();
|
|
} else {
|
|
// Compile the immediate mode line.
|
|
set_up_compile();
|
|
compile_buffer(g_input_buffer, INVALID_LINE_NUMBER);
|
|
complete_compile_and_execute();
|
|
}
|
|
} else {
|
|
// Stored mode. Add line to program.
|
|
|
|
// Return line to replace or delete, or location to insert new line.
|
|
uint8_t *line = find_line(line_number);
|
|
uint8_t *next_line = get_next_line(line);
|
|
uint8_t *end_of_program = get_end_of_program(line);
|
|
int16_t adjustment = 0;
|
|
|
|
if (next_line == 0 || get_line_number(line) != line_number) {
|
|
// Didn't find line. Insert it here.
|
|
|
|
// Next pointer, line number, line, and nul.
|
|
uint8_t buffer_length = strlen(g_input_buffer);
|
|
adjustment = 4 + buffer_length + 1;
|
|
|
|
// Shift rest of program over.
|
|
memmove(line + adjustment, line, end_of_program - line);
|
|
|
|
// Next line. Point to yourself initially, we'll adjust below.
|
|
*((uint8_t **) line) = line;
|
|
|
|
// Line number.
|
|
*((uint16_t *) (line + 2)) = line_number;
|
|
|
|
// Buffer and nul.
|
|
memmove(line + 4, g_input_buffer, buffer_length + 1);
|
|
} else {
|
|
// Found line.
|
|
|
|
if (g_input_buffer[0] == '\0') {
|
|
// Empty line, delete old one.
|
|
|
|
// Adjustment is negative.
|
|
adjustment = line - next_line;
|
|
memmove(line, next_line, end_of_program - next_line);
|
|
} else {
|
|
// Replace line.
|
|
|
|
// Compute adjustment.
|
|
uint8_t buffer_length = strlen(g_input_buffer);
|
|
adjustment = line - next_line + 4 + buffer_length + 1;
|
|
memmove(next_line + adjustment, next_line, end_of_program - next_line);
|
|
|
|
// Buffer and nul.
|
|
memmove(line + 4, g_input_buffer, buffer_length + 1);
|
|
}
|
|
}
|
|
|
|
if (adjustment != 0) {
|
|
// Adjust all the next pointers.
|
|
while ((next_line = get_next_line(line)) != 0) {
|
|
// Adjust by the amount we inserted or deleted.
|
|
next_line += adjustment;
|
|
|
|
*((uint8_t **) line) = next_line;
|
|
line = next_line;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int16_t main(void)
|
|
{
|
|
int16_t blink;
|
|
|
|
// Clear stored program.
|
|
new_statement();
|
|
|
|
// Clear out all variables.
|
|
clear_variables();
|
|
|
|
// Initialize UI.
|
|
home();
|
|
|
|
// Print title.
|
|
move_cursor((40 - title_length) / 2, 0);
|
|
print(title);
|
|
|
|
// Prompt.
|
|
print("\n\n]");
|
|
|
|
// Keyboard input.
|
|
blink = 0;
|
|
g_input_buffer_length = 0;
|
|
show_cursor();
|
|
while(1) {
|
|
// Blink cursor.
|
|
blink += 1;
|
|
if (blink == 3000) {
|
|
if (g_showing_cursor) {
|
|
hide_cursor();
|
|
} else {
|
|
show_cursor();
|
|
}
|
|
blink = 0;
|
|
}
|
|
|
|
if(keyboard_test()) {
|
|
hide_cursor();
|
|
|
|
while(keyboard_test()) {
|
|
uint8_t key;
|
|
|
|
key = keyboard_get();
|
|
if (key == 8) {
|
|
// Backspace.
|
|
if (g_input_buffer_length > 0) {
|
|
move_cursor(g_cursor_x - 1, g_cursor_y);
|
|
g_input_buffer_length -= 1;
|
|
}
|
|
} else if (key == 13) {
|
|
// Return.
|
|
clear_to_eol();
|
|
print_char('\n');
|
|
|
|
process_input_buffer();
|
|
|
|
print("\n]");
|
|
g_input_buffer_length = 0;
|
|
} else {
|
|
if (g_input_buffer_length < sizeof(g_input_buffer) - 1) {
|
|
print_char(key);
|
|
g_input_buffer[g_input_buffer_length++] = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
show_cursor();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|