mirror of
https://github.com/bradgrantham/apple2a.git
synced 2025-02-17 04:30:32 +00:00
Add FOR/NEXT loops.
This commit is contained in:
parent
fb467e5289
commit
e143757158
159
main.c
159
main.c
@ -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
182
runtime.c
@ -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;
|
||||
}
|
||||
|
12
runtime.h
12
runtime.h
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user