diff --git a/Makefile b/Makefile index bb7438f..bb3c7a8 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ CPU = 6502 ROM = apple2a.rom LIB = apple2rom.lib -CC65_FLAGS = -t none --cpu $(CPU) +CC65_FLAGS = -t none --cpu $(CPU) --register-vars $(ROM): a.out (dd count=5 bs=4096 if=/dev/zero 2> /dev/null; cat a.out) > $(ROM) diff --git a/README.md b/README.md index 855ecd0..7889472 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Runs between 5 and 30 times faster. Supported features: The classic way to enter programs with line numbers, 16-bit integer variables, `HOME`, `PRINT`, `IF/THEN`, `FOR/NEXT`, `GOTO`, low-res graphics (`GR`, `PLOT`, `COLOR=`, `TEXT`), -`POKE`, and integer and boolean arithmetic. +`DIM` (arrays), `POKE`, and integer and boolean arithmetic. Not supported: Floating point, strings, high-res graphics, `DATA/READ/RESUME`, `GOSUB/RETURN/POP`, -`DIM` (arrays), `REM`, keyboard input, exponentiation (`A^B`), and cassette I/O. +`REM`, keyboard input, exponentiation (`A^B`), and cassette I/O. # Dependencies diff --git a/exporter.h b/exporter.h index 3a848bb..c2a4aae 100644 --- a/exporter.h +++ b/exporter.h @@ -5,6 +5,7 @@ extern void pushax(); extern void popax(); +extern void incsp2(); extern void tosaddax(); extern void tossubax(); extern void tosmulax(); @@ -17,8 +18,13 @@ extern void tosleax(); extern void tosgeax(); extern void bnegax(); extern void negax(); +extern void aslax1(); +extern void ldaxi(); +extern void staxspidx(); // Two bytes each. +extern unsigned int sp; +#pragma zpsym ("sp"); extern unsigned int ptr1; #pragma zpsym ("ptr1"); diff --git a/exporter.s b/exporter.s index ee47161..bf84ebd 100644 --- a/exporter.s +++ b/exporter.s @@ -9,6 +9,8 @@ .export _pushax := pushax .import popax .export _popax := popax +.import incsp2 +.export _incsp2 := incsp2 .import tosaddax .export _tosaddax := tosaddax .import tossubax @@ -33,6 +35,15 @@ .export _bnegax := bnegax .import negax .export _negax := negax +.import aslax1 +.export _aslax1 := aslax1 +.import ldaxi +.export _ldaxi := ldaxi +.import staxspidx +.export _staxspidx := staxspidx + +.importzp sp +.exportzp _sp = sp .importzp ptr1 .exportzp _ptr1 = ptr1 diff --git a/main.c b/main.c index 07cfea7..0a535be 100644 --- a/main.c +++ b/main.c @@ -12,11 +12,16 @@ uint8_t title_length = 9; #define I_CLC 0x18 #define I_JSR 0x20 #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 @@ -24,6 +29,7 @@ uint8_t title_length = 9; #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 @@ -56,6 +62,7 @@ uint8_t title_length = 9; #define T_STEP 0x99 #define T_NEXT 0x9A #define T_NOT 0x9B +#define T_DIM 0x9C // Operators. These encode both the operator (high nybble) and the precedence // (low nybble). Lower precedence has a lower low nybble value. For example, @@ -80,14 +87,12 @@ uint8_t title_length = 9; #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 -// Variable for "No more space for variables". -#define OUT_OF_VARIABLE_SPACE 0xFF - // Maximum number of lines in stored program. #define MAX_LINES 128 @@ -156,11 +161,12 @@ static uint8_t *TOKEN[] = { "STEP", "NEXT", "NOT", + "DIM", }; static int16_t TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]); uint8_t g_input_buffer[80]; -int16_t g_input_buffer_length = 0; +int16_t g_input_buffer_length; // Compiled binary. uint8_t g_compiled[1024]; @@ -181,12 +187,12 @@ 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 = 0; +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 = 0; +uint8_t g_forward_goto_count; /** * Print the tokenized string, with tokens displayed as their full text. @@ -339,54 +345,84 @@ static void compile_load_ax(uint16_t value) { } /** - * Find a variable by name. Only the first two letters are considered. - * Advances the pointer past the variable name (including letters after - * the first two). Returns the memory address of the variable. If we - * ran out of space for variables, returns OUT_OF_VARIABLE_SPACE - * and does not modify the buffer pointer. + * Generate code to store AX to a zero-page word. */ -static uint8_t find_variable(uint8_t **buffer) { +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; - uint8_t *existing_name = g_variable_names; - uint8_t name[2]; - int16_t var; + VarInfo *var = g_variables; + uint16_t name; + int16_t i; + uint8_t data_type; // Pull out the variable name. - name[0] = *s++; + name = *s++; if (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) { - name[1] = *s++; - } else { - name[1] = 0; + name |= *s++ << 8; } // Skip rest of name. while (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) { s++; } - for (var = 0; var < MAX_VARIABLES; var++) { - if (existing_name[0] == 0 && existing_name[1] == 0) { + // 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. - existing_name[0] = name[0]; - existing_name[1] = name[1]; + var->name = name; + var->data_type = data_type; break; - } else if (existing_name[0] == name[0] && existing_name[1] == name[1]) { + } else if (var->name == name && var->data_type == data_type) { // Found it. break; } - existing_name += 2; } - if (var == MAX_VARIABLES) { - var = OUT_OF_VARIABLE_SPACE; + if (i == MAX_VARIABLES) { + // Not found and can't create it. + var = 0; } else { - // Convert index to address. - var = FIRST_VARIABLE + 2*var; - // Advance pointer. *buffer = s; } - return (uint8_t) var; + 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); } /** @@ -411,6 +447,7 @@ static uint8_t *find_line_address(uint16_t line_number) { */ static void pop_operator_stack() { uint8_t op = g_op_stack[--g_op_stack_size]; + register uint8_t *c; switch (op) { case OP_ADD: @@ -457,60 +494,62 @@ static void pop_operator_stack() { // AppleSoft BASIC does not have short-circuit logical operators. // See if second operand is 0. - g_c[0] = I_STX_ZPG; - g_c[1] = (uint8_t) &tmp1; - g_c[2] = I_ORA_ZPG; - g_c[3] = (uint8_t) &tmp1; + 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 other test. A contains 0. - g_c[4] = I_BEQ_REL; - g_c[5] = 11; // 3 + 6 + 2 - g_c += 6; + c[4] = I_BEQ_REL; + c[5] = 11; // 3 + 6 + 2 + g_c = c + 6; add_call(popax); // 3 instructions + c = g_c; // See if first operand is 0. - g_c[0] = I_STX_ZPG; - g_c[1] = (uint8_t) &tmp1; - g_c[2] = I_ORA_ZPG; - g_c[3] = (uint8_t) &tmp1; + c[0] = I_STX_ZPG; + c[1] = (uint8_t) &tmp1; + c[2] = I_ORA_ZPG; + c[3] = (uint8_t) &tmp1; // If so, skip setting A to 1. A contains 0. - g_c[4] = I_BEQ_REL; - g_c[5] = 2; // The LDA below. - g_c += 6; + c[4] = I_BEQ_REL; + c[5] = 2; // The LDA below. // Set A to 1. - g_c[0] = I_LDA_IMM; - g_c[1] = 1; - g_c[2] = I_LDX_IMM; // The BEQs above arrive here. - g_c[3] = 0; - g_c += 4; + c[6] = I_LDA_IMM; + c[7] = 1; + c[8] = I_LDX_IMM; // The BEQs above arrive here. + c[9] = 0; + g_c = c + 10; break; case OP_OR: // AppleSoft BASIC does not have short-circuit logical operators. // See if second operand is 0. - g_c[0] = I_STX_ZPG; - g_c[1] = (uint8_t) &tmp1; - g_c[2] = I_ORA_ZPG; - g_c[3] = (uint8_t) &tmp1; + c = g_c; + c[0] = I_STX_ZPG; + c[1] = (uint8_t) &tmp1; + c[2] = I_ORA_ZPG; + c[3] = (uint8_t) &tmp1; // If not, skip other test. - g_c[4] = I_BNE_REL; - g_c[5] = 9; // 3 + 6 - g_c += 6; + c[4] = I_BNE_REL; + c[5] = 9; // 3 + 6 + g_c = c + 6; add_call(popax); // 3 instructions + c = g_c; // See if first operand is 0. - g_c[0] = I_STX_ZPG; - g_c[1] = (uint8_t) &tmp1; - g_c[2] = I_ORA_ZPG; - g_c[3] = (uint8_t) &tmp1; + c[0] = I_STX_ZPG; + c[1] = (uint8_t) &tmp1; + c[2] = I_ORA_ZPG; + c[3] = (uint8_t) &tmp1; // If so, skip setting A to 1. A contains 0. - g_c[4] = I_BEQ_REL; - g_c[5] = 2; // The LDA below. - g_c += 6; + c[4] = I_BEQ_REL; + c[5] = 2; // The LDA below. // Set A to 1. - g_c[0] = I_LDA_IMM; // The BNE arrives here. - g_c[1] = 1; - g_c[2] = I_LDX_IMM; // The BEQ arrives here. - g_c[3] = 0; - g_c += 4; + c[6] = I_LDA_IMM; // The BNE arrives here. + c[7] = 1; + c[8] = I_LDX_IMM; // The BEQ arrives here. + c[9] = 0; + g_c = c + 10; break; case OP_NOT: @@ -521,6 +560,37 @@ static void pop_operator_stack() { 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; @@ -537,15 +607,19 @@ static void pop_operator_stack() { * 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 && - g_op_stack[g_op_stack_size - 1] != OP_OPEN_PARENS && - OP_PRECEDENCE(g_op_stack[g_op_stack_size - 1]) >= OP_PRECEDENCE(op)) { + (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(); } @@ -578,25 +652,36 @@ static uint8_t *compile_expression(uint8_t *s) { expect_unary = 0; } else if (IS_FIRST_VARIABLE_LETTER(*s)) { // Variable reference. - uint8_t var = find_variable(&s); + VarInfo *var = find_variable(&s); if (have_value_in_ax) { // Push on the number stack. add_call(pushax); } - if (var == OUT_OF_VARIABLE_SPACE) { + 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. - g_c[0] = I_LDA_ZPG; - g_c[1] = var; - g_c[2] = I_LDX_ZPG; - g_c[3] = var + 1; - g_c += 4; + compile_load_zero_page(var_addr); + + if (var->data_type == DT_ARRAY) { + // 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; @@ -658,26 +743,33 @@ static uint8_t *compile_expression(uint8_t *s) { } 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 parethesis. - while (g_op_stack_size > 0 && g_op_stack[g_op_stack_size - 1] != OP_OPEN_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) { - // TODO we should generate a syntax error here. - print("Extra close parenthesis\n"); + // 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. + // 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_OPEN_PARENS && op != OP_ARRAY_DEREF) { // TODO we should generate a syntax error here. + print("Unexpected unary\n"); break; } @@ -872,6 +964,7 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { // 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; @@ -884,23 +977,63 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { // Empty statement. We skip the colon below. } else if (IS_FIRST_VARIABLE_LETTER(*s)) { // Must be variable assignment. - uint8_t var = find_variable(&s); - if (var == OUT_OF_VARIABLE_SPACE) { + VarInfo *var = find_variable(&s); + if (var == 0) { // TODO: Nicer error specifically for out of variable space. error = 1; } else { - if (*s != T_EQUAL) { + 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 { - s += 1; // Parse value. - s = compile_expression(s); - // Copy to var. - g_c[0] = I_STA_ZPG; - g_c[1] = var; - g_c[2] = I_STX_ZPG; - g_c[3] = var + 1; - g_c += 4; + 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) { @@ -924,22 +1057,19 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { // Parse address. s = compile_expression(s); // Copy from AX to ptr1. - g_c[0] = I_STA_ZPG; - g_c[1] = (uint8_t) &ptr1; - g_c[2] = I_STX_ZPG; - g_c[3] = (uint8_t) &ptr1 + 1; - g_c += 4; + compile_store_zero_page((uint8_t) &ptr1); if (*s != ',') { error = 1; } else { s++; // Parse value. LSB is in A. s = compile_expression(s); - g_c[0] = I_LDY_IMM; // Zero out Y. - g_c[1] = 0; - g_c[2] = I_STA_IND_Y; // Store at *ptr1. - g_c[3] = (uint8_t) &ptr1; - g_c += 4; + 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; @@ -960,10 +1090,11 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { } } - g_c[0] = I_JMP_ABS; - g_c[1] = addr & 0xFF; - g_c[2] = addr >> 8; - g_c += 3; + 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. @@ -973,20 +1104,21 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { // Parse conditional expression. s = compile_expression(s); // Check if AX is zero. Or the two bytes together, through the zero page. - g_c[0] = I_STX_ZPG; - g_c[1] = (uint8_t) &tmp1; - g_c[2] = I_ORA_ZPG; - g_c[3] = (uint8_t) &tmp1; + 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. - g_c[4] = I_BNE_REL; - g_c[5] = 3; // Skip over absolute jump. - g_c[6] = I_JMP_ABS; - g_c += 7; + 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 **) g_c; - g_c[0] = 0; // Address of next line. - g_c[1] = 0; // Address of next line. - g_c += 2; + 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 @@ -1009,17 +1141,21 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { error = 1; if (IS_FIRST_VARIABLE_LETTER(*s)) { - uint8_t var; + VarInfo *var; // For the error message. compile_load_ax(line_number); add_call(pushax); var = find_variable(&s); - if (var == OUT_OF_VARIABLE_SPACE) { + 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 { - compile_load_ax(var); + uint16_t var_addr = get_var_address(var); + + compile_load_ax(var_addr); add_call(pushax); if (*s == T_EQUAL) { @@ -1029,11 +1165,7 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { s = compile_expression(s); // Copy to var. - g_c[0] = I_STA_ZPG; - g_c[1] = var; - g_c[2] = I_STX_ZPG; - g_c[3] = var + 1; - g_c += 4; + compile_store_zero_page(var_addr); if (*s == T_TO) { s += 1; @@ -1086,12 +1218,12 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { // See if there's the optional variable. We don't support multiple // variables ("NEXT I,J"). if (IS_FIRST_VARIABLE_LETTER(*s)) { - uint8_t var = find_variable(&s); - if (var == OUT_OF_VARIABLE_SPACE) { + VarInfo *var = find_variable(&s); + if (var == 0) { // TODO: Nicer error specifically for out of variable space. error = 1; } else { - compile_load_ax(var); + compile_load_ax(get_var_address(var)); } } else { // Zero means find the most recent FOR loop. @@ -1105,21 +1237,91 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { // 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. - g_c[0] = I_STA_ZPG; - g_c[1] = (uint8_t) &ptr1; - g_c[2] = I_STX_ZPG; - g_c[3] = (uint8_t) &ptr1 + 1; + 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. - g_c[4] = I_ORA_ZPG; - g_c[5] = (uint8_t) &ptr1 + 1; // OR X into A. + c[4] = I_ORA_ZPG; + c[5] = (uint8_t) &ptr1 + 1; // OR X into A. // If zero, skip over jump. - g_c[6] = I_BEQ_REL; - g_c[7] = 3; // Skip over indirect 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. - g_c[8] = I_JMP_IND; - g_c[9] = (uint8_t) &ptr1 & 0x0F; - g_c[10] = (uint8_t) &ptr1 >> 8; - g_c += 11; + c[8] = I_JMP_IND; + c[9] = (uint8_t) &ptr1 & 0x0F; + 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_GR) { s += 1; add_call(gr_statement); @@ -1131,8 +1333,7 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) { if (*s != T_EQUAL) { error = 1; } else { - s += 1; - s = compile_expression(s); + s = compile_expression(s + 1); add_call(color_statement); } } else if (*s == T_PLOT) { @@ -1246,7 +1447,7 @@ static void complete_compile_and_execute(void) { * knowledge of them. */ void clear_variables(void) { - memset(g_variable_names, 0, sizeof(g_variable_names)); + memset(g_variables, 0, sizeof(g_variables)); } /** @@ -1369,6 +1570,13 @@ int16_t main(void) { int16_t blink; + { + uint16_t *foo; + int x = 5; + blink = foo[x]; + } + + // Clear stored program. new_statement(); diff --git a/runtime.c b/runtime.c index 7d6aa02..0286edc 100644 --- a/runtime.c +++ b/runtime.c @@ -5,6 +5,9 @@ // Max number of nested FOR loops. This value matches AppleSoft BASIC. #define MAX_FOR 10 +// Max words for arrays. +#define MAX_ARRAY_WORDS 2048 + #define CURSOR_GLYPH 127 #define SCREEN_HEIGHT 24 #define SCREEN_WIDTH 40 @@ -38,29 +41,32 @@ typedef struct { } ForInfo; // Location of cursor in logical screen space. -uint16_t g_cursor_x = 0; -uint16_t g_cursor_y = 0; +uint16_t g_cursor_x; +uint16_t g_cursor_y; // Whether the cursor is being displayed. -uint16_t g_showing_cursor = 0; +uint16_t g_showing_cursor; // Character at the cursor location. -uint8_t g_cursor_ch = 0; +uint8_t g_cursor_ch; // Whether in low-res graphics mode. -uint8_t g_gr_mode = 0; +uint8_t g_gr_mode; // 4-bit low-res color. -uint8_t g_gr_color_high = 0; // High nybble. -uint8_t g_gr_color_low = 0; // Low nybble. +uint8_t g_gr_color_high; // High nybble. +uint8_t g_gr_color_low; // Low nybble. -// List of variable names, two bytes each, in the same order they are -// in the zero page (starting at FIRST_VARIABLE). Two nuls means an unused -// slot. One-letter variable names have a nul for the second character. -uint8_t g_variable_names[MAX_VARIABLES*2]; +// List of variable, in the same order they are in the zero page (starting at +// FIRST_VARIABLE). +VarInfo g_variables[MAX_VARIABLES]; // Stack of FOR loops. ForInfo g_for_info[MAX_FOR]; uint8_t g_for_count; +// Space for arrays. +uint16_t g_arrays[MAX_ARRAY_WORDS]; +uint16_t g_arrays_size; + /** * Clear the FOR stack. */ @@ -75,6 +81,7 @@ void clear_for_stack(void) { void initialize_runtime(void) { memset((void *) FIRST_VARIABLE, 0, MAX_VARIABLES*2); clear_for_stack(); + g_arrays_size = 0; } /** @@ -274,6 +281,7 @@ void print_int(int16_t i) { static void generic_error_message(uint8_t *message, uint16_t line_number) { print("\n?"); print(message); + print(" ERROR"); if (line_number != INVALID_LINE_NUMBER) { print(" IN "); @@ -285,14 +293,14 @@ static void generic_error_message(uint8_t *message, uint16_t line_number) { * Display a syntax error message. */ void syntax_error(uint16_t line_number) { - generic_error_message("SYNTAX ERROR", line_number); + generic_error_message("SYNTAX", line_number); } /** * Display an error for a GOTO that went to a line that doesn't exist. */ void undefined_statement_error(uint16_t line_number) { - generic_error_message("UNDEF'D STATEMENT ERROR", line_number); + generic_error_message("UNDEF'D STATEMENT", line_number); } /** @@ -300,14 +308,21 @@ void undefined_statement_error(uint16_t line_number) { * stacks have been overflowed. */ void out_of_memory_error(uint16_t line_number) { - generic_error_message("OUT OF MEMORY ERROR", line_number); + generic_error_message("OUT OF MEMORY", line_number); } /** * Display an error for when the user does a NEXT without a matching FOR. */ void next_without_for_error(uint16_t line_number) { - generic_error_message("NEXT WITHOUT FOR ERROR", line_number); + generic_error_message("NEXT WITHOUT FOR", line_number); +} + +/** + * Display an error for when the user does a DIM on a variable a second time. + */ +void redimd_array_error(uint16_t line_number) { + generic_error_message("REDIM'D ARRAY", line_number); } /** @@ -480,3 +495,27 @@ uint16_t next_statement(uint16_t line_number, uint16_t var_address) { return jump_addr; } + +/** + * Allocate an array. Size is in words, and var_addr points to + * the variable that will store the array location. + */ +void allocate_array(uint16_t size, uint16_t var_addr) { + // Check for overflow. + if (g_arrays_size + size > MAX_ARRAY_WORDS) { + print("Too many arrays.\n"); + } else { + // Allocate next chunk. + *(uint16_t *) var_addr = (uint16_t) (g_arrays + g_arrays_size); + + // TODO temporary, fill with stuff. + { + int i; + for (i = 0; i < size; i++) { + g_arrays[g_arrays_size + i] = i; + } + } + + g_arrays_size += size; + } +} diff --git a/runtime.h b/runtime.h index c9f9e19..56c94d3 100644 --- a/runtime.h +++ b/runtime.h @@ -14,11 +14,26 @@ // Each variable takes two bytes (int16_t). #define FIRST_VARIABLE 26 +// Data types for variables. +#define DT_INT 0 +#define DT_ARRAY 1 + +typedef struct { + // The name of the variable, with the first letter in the lower byte + // and the second letter (or nul) in the higher byte. Zero indicates + // that this entry is unused. + uint16_t name; + + // Data type of the variable. See DT_ constants above. This is also the + // namespace that the variable name is in. + uint8_t data_type; +} VarInfo; + extern uint16_t g_cursor_x; extern uint16_t g_cursor_y; extern uint16_t g_showing_cursor; extern uint8_t g_cursor_ch; -extern uint8_t g_variable_names[MAX_VARIABLES*2]; +extern VarInfo g_variables[MAX_VARIABLES]; void initialize_runtime(void); void clear_for_stack(void); @@ -32,6 +47,8 @@ void clear_to_eol(void); void home(void); +void allocate_array(uint16_t size, uint16_t var_addr); + void print(uint8_t *s); void print_char(uint8_t c); void print_uint(uint16_t i); @@ -45,6 +62,7 @@ uint16_t next_statement(uint16_t line_number, uint16_t var_address); void syntax_error(uint16_t line_number); void syntax_error_in_line(uint16_t line_number); void undefined_statement_error(uint16_t line_number); +void redimd_array_error(uint16_t line_number); void gr_statement(void); void text_statement(void);