Add FOR/NEXT loops.

This commit is contained in:
Lawrence Kesteloot 2018-08-04 23:38:44 -07:00
parent fb467e5289
commit e143757158
3 changed files with 320 additions and 33 deletions

159
main.c
View File

@ -14,6 +14,7 @@ uint8_t title_length = 9;
#define I_SEC 0x38
#define I_JMP_ABS 0x4C
#define I_RTS 0x60
#define I_JMP_IND 0x6C
#define I_STA_ZPG 0x85
#define I_STX_ZPG 0x86
#define I_STA_IND_Y 0x91
@ -22,6 +23,7 @@ uint8_t title_length = 9;
#define I_LDA_ZPG 0xA5
#define I_LDX_ZPG 0xA6
#define I_LDA_IMM 0xA9
#define I_TAX 0xAA
#define I_BNE_REL 0xD0
#define I_BEQ_REL 0xF0
@ -49,6 +51,10 @@ uint8_t title_length = 9;
#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
// Operators. These encode both the operator (high nybble) and the precedence
// (low nybble). Lower precedence has a lower low nybble value. For example,
@ -77,9 +83,6 @@ uint8_t title_length = 9;
#define OP_OPEN_PARENS 0xFE // Ignore precedence.
#define OP_INVALID 0xFF
// Line number used for "no line number".
#define INVALID_LINE_NUMBER 0xFFFF
// Variable for "No more space for variables".
#define OUT_OF_VARIABLE_SPACE 0xFF
@ -146,6 +149,10 @@ static uint8_t *TOKEN[] = {
"TEXT",
"COLOR",
"PLOT",
"FOR",
"TO",
"STEP",
"NEXT",
};
static int16_t TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]);
@ -778,7 +785,7 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) {
error = 1;
} else {
s += 1;
// Parse address.
// Parse value.
s = compile_expression(s);
// Copy to var.
g_compiled[g_compiled_length++] = I_STA_ZPG;
@ -818,9 +825,9 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) {
s++;
// Parse value. LSB is in A.
s = compile_expression(s);
g_compiled[g_compiled_length++] = I_LDY_IMM;
g_compiled[g_compiled_length++] = I_LDY_IMM; // Zero out Y.
g_compiled[g_compiled_length++] = 0;
g_compiled[g_compiled_length++] = I_STA_IND_Y;
g_compiled[g_compiled_length++] = I_STA_IND_Y; // Store at *ptr1.
g_compiled[g_compiled_length++] = (uint8_t) &ptr1;
}
} else if (*s == T_GOTO) {
@ -877,6 +884,125 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) {
g_compiled_length = saved_compiled_length;
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)) {
uint8_t var;
// For the error message.
compile_load_ax(line_number);
add_call(pushax);
var = find_variable(&s);
if (var == OUT_OF_VARIABLE_SPACE) {
// TODO: Nicer error specifically for out of variable space.
} else {
compile_load_ax(var);
add_call(pushax);
if (*s == T_EQUAL) {
s += 1;
// Parse initial value.
s = compile_expression(s);
// Copy to var.
g_compiled[g_compiled_length++] = I_STA_ZPG;
g_compiled[g_compiled_length++] = var;
g_compiled[g_compiled_length++] = I_STX_ZPG;
g_compiled[g_compiled_length++] = var + 1;
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_compiled[g_compiled_length];
g_compiled[g_compiled_length++] = I_LDX_IMM;
g_compiled_length++;
g_compiled[g_compiled_length++] = I_LDA_IMM;
g_compiled_length++;
add_call(for_statement);
error = 0;
}
}
}
}
if (loop_top_addr_addr != 0) {
uint16_t loop_top_addr = (uint16_t) &g_compiled[g_compiled_length];
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)) {
uint8_t var = find_variable(&s);
if (var == OUT_OF_VARIABLE_SPACE) {
// TODO: Nicer error specifically for out of variable space.
error = 1;
} else {
compile_load_ax(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.
g_compiled[g_compiled_length++] = I_STA_ZPG;
g_compiled[g_compiled_length++] = (uint8_t) &ptr1;
g_compiled[g_compiled_length++] = I_STX_ZPG;
g_compiled[g_compiled_length++] = (uint8_t) &ptr1 + 1;
// Check if AX is zero. Destroys AX.
g_compiled[g_compiled_length++] = I_ORA_ZPG;
g_compiled[g_compiled_length++] = (uint8_t) &ptr1 + 1; // OR X into A.
// If zero, skip over jump.
g_compiled[g_compiled_length++] = I_BEQ_REL;
g_compiled[g_compiled_length++] = 3; // Skip over indirect jump.
// Jump to top of loop, indirectly through ptr1, which has the address.
g_compiled[g_compiled_length++] = I_JMP_IND;
g_compiled[g_compiled_length++] = (uint8_t) &ptr1 & 0x0F;
g_compiled[g_compiled_length++] = (uint8_t) &ptr1 >> 8;
} else if (*s == T_GR) {
s += 1;
add_call(gr_statement);
@ -926,15 +1052,10 @@ static void compile_buffer(uint8_t *buffer, uint16_t line_number) {
if (error) {
end_of_line_count = 0;
if (line_number != INVALID_LINE_NUMBER) {
compile_load_ax(line_number);
add_call(syntax_error_in_line);
} else {
add_call(syntax_error);
}
compile_load_ax(line_number);
add_call(syntax_error);
// Terminate program.
// TODO This won't work after a GOSUB. Maybe we should have our
// own stack for that.
add_return();
}
} while (!done);
@ -954,6 +1075,10 @@ static void complete_compile_and_execute(void) {
// 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++) {
@ -967,6 +1092,7 @@ static void complete_compile_and_execute(void) {
// 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();
}
@ -1016,9 +1142,8 @@ static void compile_stored_program(void) {
set_up_compile();
// Generate code to zero out all variable values. Do this in the program
// itself because each RUN should clear them out.
add_call(clear_variable_values);
// Clear runtime state.
add_call(initialize_runtime);
while ((next_line = get_next_line(line)) != 0) {
uint16_t line_number = get_line_number(line);

182
runtime.c
View File

@ -2,6 +2,9 @@
#include <string.h>
#include "runtime.h"
// Max number of nested FOR loops. This value matches AppleSoft BASIC.
#define MAX_FOR 10
#define CURSOR_GLYPH 127
#define SCREEN_HEIGHT 24
#define SCREEN_WIDTH 40
@ -17,6 +20,23 @@
#define HIRES_OFF_SWITCH ((uint8_t *) 49238U)
#define HIRES_ON_SWITCH ((uint8_t *) 49239U)
/**
* Run-time stack of FOR loops.
*/
typedef struct {
// Address (in the zero page) of the loop variable.
uint8_t var_address;
// End value.
int16_t end_value;
// Step.
int16_t step;
// Address of the top of the loop to jump back to.
uint16_t loop_top_addr;
} ForInfo;
// Location of cursor in logical screen space.
uint16_t g_cursor_x = 0;
uint16_t g_cursor_y = 0;
@ -37,11 +57,24 @@ uint8_t g_gr_color_low = 0; // Low nybble.
// slot. One-letter variable names have a nul for the second character.
uint8_t g_variable_names[MAX_VARIABLES*2];
// Stack of FOR loops.
ForInfo g_for_info[MAX_FOR];
uint8_t g_for_count;
/**
* Clear out the values of all variables.
* Clear the FOR stack.
*/
void clear_variable_values(void) {
void clear_for_stack(void) {
g_for_count = 0;
}
/**
* Clear out the values of all variables and generally initialize runtime
* state.
*/
void initialize_runtime(void) {
memset((void *) FIRST_VARIABLE, 0, MAX_VARIABLES*2);
clear_for_stack();
}
/**
@ -222,29 +255,46 @@ void print_int(uint16_t i) {
}
/**
* Display a syntax error message.
* Print an error message, optionally with a line number if it's
* not INVALID_LINE_NUMBER.
*/
void syntax_error(void) {
print("\n?SYNTAX ERROR");
// No linefeed, assume prompt will do it.
static void generic_error_message(uint8_t *message, uint16_t line_number) {
print("\n?");
print(message);
if (line_number != INVALID_LINE_NUMBER) {
print(" IN ");
print_int(line_number);
}
}
/**
* Display a syntax error message for stored program.
* Display a syntax error message.
*/
void syntax_error_in_line(uint16_t line_number) {
print("\n?SYNTAX ERROR IN ");
print_int(line_number);
// No linefeed, assume prompt will do it.
void syntax_error(uint16_t line_number) {
generic_error_message("SYNTAX ERROR", 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) {
print("\n?UNDEF'D STATEMENT ERROR IN ");
print_int(line_number);
generic_error_message("UNDEF'D STATEMENT ERROR", line_number);
}
/**
* Display an out-of-memory error, which could also mean that various
* stacks have been overflowed.
*/
void out_of_memory_error(uint16_t line_number) {
generic_error_message("OUT OF MEMORY ERROR", 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);
}
/**
@ -310,3 +360,107 @@ void plot_statement(uint16_t x, uint16_t y) {
*pos = (*pos & 0x0F) | g_gr_color_high;
}
}
/**
* Find a FOR loop info structure by variable address, or null if not found.
*/
static ForInfo *find_for_info(uint16_t var_address) {
int i;
ForInfo *f;
for (i = 0; i < g_for_count; i++) {
f = &g_for_info[i];
if (f->var_address == var_address) {
// Found it.
return f;
}
}
return 0;
}
/**
* Remove any FOR loop for this variable anywhere in the stack, if any.
*/
static void remove_for_info(uint16_t var_address) {
ForInfo *f = find_for_info(var_address);
if (f != 0) {
// Compute the index of this entry.
int index = f - g_for_info;
// Shift the rest over.
memmove(f, f + 1, sizeof(ForInfo)*(g_for_count - index - 1));
g_for_count -= 1;
}
}
/**
* Push a FOR statement on the stack.
*/
void for_statement(uint16_t line_number, uint16_t var_address, int16_t end_value, int16_t step,
uint16_t loop_top_addr) {
// First, kill any existing loop for this variable.
remove_for_info(var_address);
// Add the loop to our stack.
if (g_for_count == MAX_FOR) {
// TODO should quit program. Return a failure value, and have called return.
out_of_memory_error(line_number);
} else {
ForInfo *f = &g_for_info[g_for_count++];
f->var_address = var_address;
f->end_value = end_value;
f->step = step;
f->loop_top_addr = loop_top_addr;
}
}
/**
* Handle a NEXT statement. Returns the address to jump to at the top of the loop,
* or zero to not jump.
*/
uint16_t next_statement(uint16_t line_number, uint16_t var_address) {
ForInfo *f;
uint16_t jump_addr = 0;
if (var_address == 0) {
// Pick top of stack.
if (g_for_count == 0) {
// Stack is empty.
f = 0;
} else {
f = &g_for_info[g_for_count - 1];
}
} else {
f = find_for_info(var_address);
}
if (f == 0) {
next_without_for_error(line_number);
} else {
uint16_t *var;
// Pop off every loop below us in the stack.
g_for_count = f - g_for_info + 1;
// Step the loop variable.
var = (uint16_t *) f->var_address;
*var += f->step;
// TODO if the step is negative, switch inequality here:
if (*var > f->end_value) {
// We're done, remove our FOR loop.
g_for_count -= 1;
// Continue after the NEXT statement.
} else {
// Loop back to the top of the NEXT statement.
jump_addr = f->loop_top_addr;
}
}
return jump_addr;
}

View File

@ -3,6 +3,9 @@
#include "platform.h"
// Line number used for "no line number".
#define INVALID_LINE_NUMBER 0xFFFF
// Maximum number of variables. These fit in the zero page.
#define MAX_VARIABLES 16
@ -17,7 +20,8 @@ extern uint16_t g_showing_cursor;
extern uint8_t g_cursor_ch;
extern uint8_t g_variable_names[MAX_VARIABLES*2];
void clear_variable_values(void);
void initialize_runtime(void);
void clear_for_stack(void);
uint8_t *cursor_pos(void);
void show_cursor(void);
@ -33,7 +37,11 @@ void print_char(uint8_t c);
void print_int(uint16_t i);
void print_newline(void);
void syntax_error(void);
void for_statement(uint16_t line_number, uint16_t var_address, int16_t end_value, int16_t step,
uint16_t loop_top_addr);
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);