mirror of
https://github.com/bradgrantham/apple2a.git
synced 2024-11-27 03:49:31 +00:00
Add arrays.
This commit is contained in:
parent
1eccf0b089
commit
af7c5b440f
2
Makefile
2
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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
11
exporter.s
11
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
|
||||
|
502
main.c
502
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();
|
||||
|
||||
|
69
runtime.c
69
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;
|
||||
}
|
||||
}
|
||||
|
20
runtime.h
20
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);
|
||||
|
Loading…
Reference in New Issue
Block a user