2018-08-03 01:59:31 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
2018-08-02 07:45:58 +00:00
|
|
|
#include "exporter.h"
|
2018-07-31 19:02:26 +00:00
|
|
|
#include "platform.h"
|
2018-08-02 23:26:42 +00:00
|
|
|
#include "runtime.h"
|
2018-07-31 19:02:26 +00:00
|
|
|
|
2018-08-01 20:37:31 +00:00
|
|
|
uint8_t *title = "Apple IIa";
|
|
|
|
uint8_t title_length = 9;
|
2018-07-28 05:30:44 +00:00
|
|
|
|
2018-08-02 06:29:37 +00:00
|
|
|
// 6502 instructions.
|
2018-08-02 21:43:55 +00:00
|
|
|
#define I_CLC 0x18
|
2018-08-02 06:29:37 +00:00
|
|
|
#define I_JSR 0x20
|
2018-08-02 21:43:55 +00:00
|
|
|
#define I_SEC 0x38
|
2018-08-03 21:20:04 +00:00
|
|
|
#define I_JMP_ABS 0x4C
|
2018-08-02 06:29:37 +00:00
|
|
|
#define I_RTS 0x60
|
2018-08-02 21:43:55 +00:00
|
|
|
#define I_STA_ZPG 0x85
|
|
|
|
#define I_STX_ZPG 0x86
|
|
|
|
#define I_STA_IND_Y 0x91
|
|
|
|
#define I_LDY_IMM 0xA0
|
2018-08-03 20:19:29 +00:00
|
|
|
#define I_LDX_IMM 0xA2
|
|
|
|
#define I_LDA_ZPG 0xA5
|
|
|
|
#define I_LDX_ZPG 0xA6
|
|
|
|
#define I_LDA_IMM 0xA9
|
2018-08-02 06:29:37 +00:00
|
|
|
|
|
|
|
// Tokens.
|
2018-08-01 17:44:24 +00:00
|
|
|
#define T_HOME 0x80
|
|
|
|
#define T_PRINT 0x81
|
2018-08-01 20:34:26 +00:00
|
|
|
#define T_LIST 0x82
|
2018-08-02 21:43:55 +00:00
|
|
|
#define T_POKE 0x83
|
2018-08-03 18:35:15 +00:00
|
|
|
#define T_RUN 0x84
|
2018-08-03 19:02:14 +00:00
|
|
|
#define T_NEW 0x85
|
2018-08-03 20:09:02 +00:00
|
|
|
#define T_PLUS 0x86
|
|
|
|
#define T_MINUS 0x87
|
|
|
|
#define T_TIMES 0x88
|
|
|
|
#define T_DIVIDE 0x89
|
|
|
|
#define T_CARET 0x8A
|
|
|
|
#define T_AND 0x8B
|
|
|
|
#define T_OR 0x8C
|
|
|
|
#define T_GREATER_THAN 0x8D
|
|
|
|
#define T_EQUALS 0x8E
|
|
|
|
#define T_LESS_THAN 0x8F
|
2018-08-03 21:20:04 +00:00
|
|
|
#define T_GOTO 0x90
|
2018-08-03 18:35:15 +00:00
|
|
|
|
|
|
|
// Line number used for "no line number".
|
|
|
|
#define INVALID_LINE_NUMBER 0xFFFF
|
2018-08-01 17:44:24 +00:00
|
|
|
|
2018-08-03 20:09:02 +00:00
|
|
|
// Variable for "No more space for variables".
|
|
|
|
#define OUT_OF_VARIABLE_SPACE 0xFF
|
|
|
|
|
2018-08-03 21:20:04 +00:00
|
|
|
// Maximum number of lines in stored program.
|
|
|
|
#define MAX_LINES 128
|
|
|
|
|
2018-08-03 20:31:31 +00:00
|
|
|
// 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))
|
|
|
|
|
2018-08-01 17:44:24 +00:00
|
|
|
// List of tokens. The token value is the index plus 0x80.
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint8_t *TOKEN[] = {
|
2018-08-01 17:44:24 +00:00
|
|
|
"HOME",
|
|
|
|
"PRINT",
|
2018-08-01 20:34:26 +00:00
|
|
|
"LIST",
|
2018-08-02 21:43:55 +00:00
|
|
|
"POKE",
|
2018-08-03 18:35:15 +00:00
|
|
|
"RUN",
|
2018-08-03 19:02:14 +00:00
|
|
|
"NEW",
|
2018-08-03 20:09:02 +00:00
|
|
|
"+",
|
|
|
|
"-",
|
|
|
|
"*",
|
|
|
|
"/",
|
|
|
|
"^",
|
|
|
|
"AND",
|
|
|
|
"OR",
|
|
|
|
">",
|
|
|
|
"=",
|
|
|
|
"<",
|
2018-08-03 21:20:04 +00:00
|
|
|
"GOTO",
|
2018-08-01 17:44:24 +00:00
|
|
|
};
|
2018-08-01 20:37:31 +00:00
|
|
|
static int16_t TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]);
|
2018-08-01 17:44:24 +00:00
|
|
|
|
2018-08-01 20:37:31 +00:00
|
|
|
uint8_t g_input_buffer[40];
|
|
|
|
int16_t g_input_buffer_length = 0;
|
2018-07-31 22:03:06 +00:00
|
|
|
|
|
|
|
// Compiled binary.
|
2018-08-03 18:35:15 +00:00
|
|
|
uint8_t g_compiled[1024];
|
2018-08-01 20:37:31 +00:00
|
|
|
int16_t g_compiled_length = 0;
|
2018-08-01 17:58:03 +00:00
|
|
|
void (*g_compiled_function)() = (void (*)()) g_compiled;
|
2018-07-31 19:48:29 +00:00
|
|
|
|
2018-08-01 20:34:26 +00:00
|
|
|
// Stored program. Each line is:
|
|
|
|
// - Two bytes for pointer to next line (or zero if none).
|
|
|
|
// - Two bytes for line number.
|
|
|
|
// - Program line.
|
|
|
|
// - Nul.
|
2018-08-01 20:37:31 +00:00
|
|
|
uint8_t g_program[1024];
|
2018-08-01 20:34:26 +00:00
|
|
|
|
2018-08-03 21:20:04 +00:00
|
|
|
// Address of each line of code when compiled (for GOTO statements).
|
|
|
|
// Each line takes two words: one for the line number and
|
|
|
|
// one for the address in memory of the compiled code.
|
|
|
|
uint16_t g_line_address[MAX_LINES*2];
|
|
|
|
uint16_t g_line_address_count;
|
|
|
|
|
2018-08-01 20:34:26 +00:00
|
|
|
/**
|
|
|
|
* Print the tokenized string, with tokens displayed as their full text.
|
|
|
|
* Prints a newline at the end.
|
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static void print_detokenized(uint8_t *s) {
|
2018-08-01 20:34:26 +00:00
|
|
|
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.
|
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint8_t *get_next_line(uint8_t *line) {
|
|
|
|
return *((uint8_t **) line);
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the line number of a stored program line.
|
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint16_t get_line_number(uint8_t *line) {
|
|
|
|
return *((uint16_t *) (line + 2));
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a pointer to the end of the program. This is one byte PAST the
|
2018-08-01 21:58:29 +00:00
|
|
|
* last bytes in the program, which are two nuls. The "line" parameter is
|
2018-08-01 20:34:26 +00:00
|
|
|
* an optional starting point, to use as an optimization instead of starting
|
|
|
|
* from the beginning.
|
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint8_t *get_end_of_program(uint8_t *line) {
|
2018-08-01 21:58:29 +00:00
|
|
|
uint8_t *next_line;
|
2018-08-01 20:34:26 +00:00
|
|
|
|
|
|
|
if (line == 0) {
|
2018-08-01 21:58:29 +00:00
|
|
|
// Start at the beginning if not specified.
|
|
|
|
line = g_program;
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 21:58:29 +00:00
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
2018-08-01 20:34:26 +00:00
|
|
|
line = next_line;
|
|
|
|
}
|
2018-08-01 21:58:29 +00:00
|
|
|
|
|
|
|
// Skip the null "next" pointer.
|
|
|
|
return line + 2;
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
|
2018-08-03 19:02:14 +00:00
|
|
|
/**
|
|
|
|
* Clear the stored program.
|
|
|
|
*/
|
|
|
|
static void new_statement() {
|
|
|
|
g_program[0] = '\0';
|
|
|
|
g_program[1] = '\0';
|
|
|
|
}
|
|
|
|
|
2018-08-01 20:34:26 +00:00
|
|
|
/**
|
|
|
|
* List the stored program.
|
|
|
|
*/
|
|
|
|
static void list_statement() {
|
2018-08-01 21:58:29 +00:00
|
|
|
uint8_t *line = g_program;
|
|
|
|
uint8_t *next_line;
|
2018-08-01 20:34:26 +00:00
|
|
|
|
2018-08-03 07:02:40 +00:00
|
|
|
print_newline();
|
|
|
|
|
2018-08-01 21:58:29 +00:00
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
2018-08-01 20:34:26 +00:00
|
|
|
print_int(get_line_number(line));
|
|
|
|
print_char(' ');
|
|
|
|
print_detokenized(line + 4);
|
|
|
|
|
2018-08-01 21:58:29 +00:00
|
|
|
line = next_line;
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-31 22:03:06 +00:00
|
|
|
/**
|
2018-08-01 03:47:05 +00:00
|
|
|
* If a starts with string b, returns the position in a after b. Else returns null.
|
2018-07-31 22:03:06 +00:00
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint8_t *skip_over(uint8_t *a, uint8_t *b) {
|
2018-08-01 03:47:05 +00:00
|
|
|
while (*a != '\0' && *b != '\0') {
|
2018-07-31 22:03:06 +00:00
|
|
|
if (*a != *b) {
|
2018-08-01 03:47:05 +00:00
|
|
|
// Doesn't start with b.
|
2018-07-31 22:03:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2018-07-28 05:30:44 +00:00
|
|
|
|
2018-07-31 22:03:06 +00:00
|
|
|
a += 1;
|
|
|
|
b += 1;
|
|
|
|
}
|
2018-07-28 05:30:44 +00:00
|
|
|
|
2018-08-01 03:47:05 +00:00
|
|
|
// See if we're at the end of b.
|
|
|
|
return *b == '\0' ? a : 0;
|
2018-07-31 22:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-08-01 17:56:48 +00:00
|
|
|
* Add a function call to the compiled buffer.
|
2018-07-31 22:03:06 +00:00
|
|
|
*/
|
2018-08-02 06:29:37 +00:00
|
|
|
static void add_call(void *function) {
|
2018-08-03 21:20:04 +00:00
|
|
|
uint16_t addr = (uint16_t) function;
|
2018-07-31 22:03:06 +00:00
|
|
|
|
2018-08-02 06:29:37 +00:00
|
|
|
g_compiled[g_compiled_length++] = I_JSR;
|
2018-08-01 17:58:03 +00:00
|
|
|
g_compiled[g_compiled_length++] = addr & 0xFF;
|
|
|
|
g_compiled[g_compiled_length++] = addr >> 8;
|
2018-07-31 22:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-08-01 17:56:48 +00:00
|
|
|
* Add a function return to the compiled buffer.
|
2018-07-31 22:03:06 +00:00
|
|
|
*/
|
|
|
|
static void add_return() {
|
2018-08-02 06:29:37 +00:00
|
|
|
g_compiled[g_compiled_length++] = I_RTS;
|
2018-07-31 22:03:06 +00:00
|
|
|
}
|
|
|
|
|
2018-08-02 06:29:37 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2018-08-03 20:31:31 +00:00
|
|
|
while (IS_DIGIT(*s)) {
|
2018-08-02 06:29:37 +00:00
|
|
|
value = value*10 + (*s - '0');
|
|
|
|
s += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*s_ptr = s;
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
2018-08-01 03:47:05 +00:00
|
|
|
|
2018-08-03 19:02:14 +00:00
|
|
|
/**
|
|
|
|
* Generate code to put the value into AX.
|
|
|
|
*/
|
|
|
|
static void compile_load_ax(uint16_t value) {
|
2018-08-03 20:19:29 +00:00
|
|
|
g_compiled[g_compiled_length++] = I_LDX_IMM;
|
2018-08-03 19:02:14 +00:00
|
|
|
g_compiled[g_compiled_length++] = value >> 8;
|
2018-08-03 20:19:29 +00:00
|
|
|
g_compiled[g_compiled_length++] = I_LDA_IMM;
|
2018-08-03 19:02:14 +00:00
|
|
|
g_compiled[g_compiled_length++] = value & 0xFF;
|
|
|
|
}
|
|
|
|
|
2018-08-03 20:19:29 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static uint8_t find_variable(uint8_t **buffer) {
|
|
|
|
uint8_t *s = *buffer;
|
|
|
|
uint8_t *existing_name = g_variable_names;
|
|
|
|
uint8_t name[2];
|
|
|
|
int16_t var;
|
|
|
|
|
|
|
|
// Pull out the variable name.
|
|
|
|
name[0] = *s++;
|
2018-08-03 20:31:31 +00:00
|
|
|
if (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) {
|
2018-08-03 20:19:29 +00:00
|
|
|
name[1] = *s++;
|
|
|
|
} else {
|
|
|
|
name[1] = 0;
|
|
|
|
}
|
|
|
|
// Skip rest of name.
|
2018-08-03 20:31:31 +00:00
|
|
|
while (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) {
|
2018-08-03 20:19:29 +00:00
|
|
|
s++;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var = 0; var < MAX_VARIABLES; var++) {
|
|
|
|
if (existing_name[0] == 0 && existing_name[1] == 0) {
|
|
|
|
// First free entry. Allocate it.
|
|
|
|
existing_name[0] = name[0];
|
|
|
|
existing_name[1] = name[1];
|
|
|
|
break;
|
|
|
|
} else if (existing_name[0] == name[0] && existing_name[1] == name[1]) {
|
|
|
|
// Found it.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
existing_name += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (var == MAX_VARIABLES) {
|
|
|
|
var = OUT_OF_VARIABLE_SPACE;
|
|
|
|
} else {
|
|
|
|
// Convert index to address.
|
|
|
|
var = FIRST_VARIABLE + 2*var;
|
|
|
|
|
|
|
|
// Advance pointer.
|
|
|
|
*buffer = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (uint8_t) var;
|
|
|
|
}
|
|
|
|
|
2018-08-03 21:20:04 +00:00
|
|
|
/**
|
|
|
|
* Find the address of a line in the compiled buffer, or 0xFFFF if not found.
|
|
|
|
*/
|
|
|
|
static uint16_t find_line_address(uint16_t line_number) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < g_line_address_count; i++) {
|
|
|
|
if (g_line_address[i*2] == line_number) {
|
|
|
|
return g_line_address[i*2 + 1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0xFFFF;
|
|
|
|
}
|
|
|
|
|
2018-08-02 07:45:58 +00:00
|
|
|
/**
|
|
|
|
* Parse an expression, generating code to compute it, leaving the
|
|
|
|
* result in AX.
|
|
|
|
*/
|
|
|
|
static uint8_t *compile_expression(uint8_t *s) {
|
|
|
|
int plus_count = 0;
|
2018-08-02 21:52:23 +00:00
|
|
|
char have_value_in_ax = 0;
|
2018-08-02 07:45:58 +00:00
|
|
|
|
|
|
|
while (1) {
|
2018-08-03 20:31:31 +00:00
|
|
|
if (IS_DIGIT(*s)) {
|
2018-08-02 07:45:58 +00:00
|
|
|
// Parse number.
|
2018-08-02 21:52:23 +00:00
|
|
|
uint16_t value;
|
|
|
|
|
|
|
|
if (have_value_in_ax) {
|
|
|
|
// Push on the number stack.
|
|
|
|
add_call(pushax);
|
|
|
|
}
|
|
|
|
|
|
|
|
value = parse_uint16(&s);
|
2018-08-03 19:02:14 +00:00
|
|
|
compile_load_ax(value);
|
2018-08-02 21:52:23 +00:00
|
|
|
have_value_in_ax = 1;
|
2018-08-03 20:31:31 +00:00
|
|
|
} else if (IS_FIRST_VARIABLE_LETTER(*s)) {
|
2018-08-03 20:19:29 +00:00
|
|
|
// Variable reference.
|
|
|
|
uint8_t var = find_variable(&s);
|
|
|
|
|
|
|
|
if (have_value_in_ax) {
|
|
|
|
// Push on the number stack.
|
|
|
|
add_call(pushax);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (var == OUT_OF_VARIABLE_SPACE) {
|
|
|
|
// 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 {
|
|
|
|
// Load from var.
|
|
|
|
g_compiled[g_compiled_length++] = I_LDA_ZPG;
|
|
|
|
g_compiled[g_compiled_length++] = var;
|
|
|
|
g_compiled[g_compiled_length++] = I_LDX_ZPG;
|
|
|
|
g_compiled[g_compiled_length++] = var + 1;
|
|
|
|
}
|
|
|
|
have_value_in_ax = 1;
|
2018-08-03 20:09:02 +00:00
|
|
|
} else if (*s == T_PLUS) {
|
2018-08-02 07:45:58 +00:00
|
|
|
plus_count += 1;
|
|
|
|
s += 1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (plus_count > 0) {
|
|
|
|
add_call(tosaddax);
|
|
|
|
plus_count -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2018-08-01 17:44:24 +00:00
|
|
|
/**
|
2018-08-03 18:35:15 +00:00
|
|
|
* Tokenize a string in place. Returns (and removes) any line number, or
|
|
|
|
* INVALID_LINE_NUMBER if there's none.
|
2018-08-01 17:44:24 +00:00
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint16_t tokenize(uint8_t *s) {
|
|
|
|
uint8_t *t = s; // Tokenized version.
|
|
|
|
int16_t line_number;
|
2018-08-01 17:44:24 +00:00
|
|
|
|
|
|
|
// Parse optional line number.
|
2018-08-03 20:31:31 +00:00
|
|
|
if (IS_DIGIT(*s)) {
|
2018-08-02 06:29:37 +00:00
|
|
|
line_number = parse_uint16(&s);
|
2018-08-01 17:44:24 +00:00
|
|
|
} else {
|
2018-08-03 18:35:15 +00:00
|
|
|
line_number = INVALID_LINE_NUMBER;
|
2018-08-01 17:44:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert tokens.
|
|
|
|
while (*s != '\0') {
|
|
|
|
if (*s == ' ') {
|
|
|
|
// Skip spaces.
|
|
|
|
s++;
|
|
|
|
} else {
|
2018-08-01 20:37:31 +00:00
|
|
|
int16_t i;
|
|
|
|
uint8_t *skipped = 0;
|
2018-08-01 17:44:24 +00:00
|
|
|
|
|
|
|
for (i = 0; i < TOKEN_COUNT; i++) {
|
|
|
|
skipped = skip_over(s, TOKEN[i]);
|
|
|
|
if (skipped != 0) {
|
|
|
|
// Record token.
|
|
|
|
*t++ = 0x80 + i;
|
|
|
|
s = skipped;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skipped == 0) {
|
|
|
|
// Didn't find a token, just copy text.
|
|
|
|
*t++ = *s++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-02 01:49:21 +00:00
|
|
|
// Terminate string.
|
2018-08-01 17:44:24 +00:00
|
|
|
*t++ = '\0';
|
|
|
|
|
|
|
|
return line_number;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-08-01 21:58:29 +00:00
|
|
|
* 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.
|
2018-08-01 17:44:24 +00:00
|
|
|
*/
|
2018-08-01 20:37:31 +00:00
|
|
|
static uint8_t *find_line(uint16_t line_number) {
|
2018-08-01 21:58:29 +00:00
|
|
|
uint8_t *line = g_program;
|
|
|
|
uint8_t *next_line;
|
2018-08-01 20:34:26 +00:00
|
|
|
|
2018-08-01 21:58:29 +00:00
|
|
|
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;
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
line = next_line;
|
|
|
|
}
|
2018-08-01 21:58:29 +00:00
|
|
|
|
|
|
|
return line;
|
2018-08-01 17:44:24 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 22:03:06 +00:00
|
|
|
/**
|
2018-08-03 18:35:15 +00:00
|
|
|
* Call to configure the compilation step.
|
2018-07-31 22:03:06 +00:00
|
|
|
*/
|
2018-08-03 18:35:15 +00:00
|
|
|
static void set_up_compile(void) {
|
|
|
|
g_compiled_length = 0;
|
2018-08-03 21:20:04 +00:00
|
|
|
g_line_address_count = 0;
|
2018-08-03 18:35:15 +00:00
|
|
|
}
|
2018-08-01 17:44:24 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
/**
|
|
|
|
* Compile the tokenized line of BASIC, adding it to the g_compiled binary.
|
|
|
|
*/
|
2018-08-03 19:02:14 +00:00
|
|
|
static void compile_buffer(uint8_t *buffer, uint16_t line_number) {
|
2018-08-03 18:35:15 +00:00
|
|
|
uint8_t *s = buffer;
|
|
|
|
uint8_t done;
|
2018-08-01 17:44:24 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
do {
|
|
|
|
int8_t error = 0;
|
2018-07-31 22:03:06 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
// Default to being done after one statement.
|
|
|
|
done = 1;
|
2018-08-01 03:47:05 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
if (*s == '\0' || *s == ':') {
|
|
|
|
// Empty statement. We skip the colon below.
|
2018-08-03 20:31:31 +00:00
|
|
|
} else if (IS_FIRST_VARIABLE_LETTER(*s)) {
|
2018-08-03 20:19:29 +00:00
|
|
|
// Must be variable assignment.
|
2018-08-03 20:09:02 +00:00
|
|
|
uint8_t var = find_variable(&s);
|
|
|
|
if (var == OUT_OF_VARIABLE_SPACE) {
|
|
|
|
// TODO: Nicer error specifically for out of variable space.
|
|
|
|
error = 1;
|
|
|
|
} else {
|
|
|
|
if (*s != T_EQUALS) {
|
|
|
|
error = 1;
|
|
|
|
} else {
|
|
|
|
s += 1;
|
|
|
|
// Parse address.
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2018-08-03 18:35:15 +00:00
|
|
|
} else if (*s == T_HOME) {
|
|
|
|
s += 1;
|
|
|
|
add_call(home);
|
|
|
|
} else if (*s == T_PRINT) {
|
|
|
|
s += 1;
|
2018-08-01 03:47:05 +00:00
|
|
|
|
2018-08-03 20:19:29 +00:00
|
|
|
if (*s != '\0' && *s != ':') {
|
2018-08-03 18:35:15 +00:00
|
|
|
// Parse expression.
|
|
|
|
s = compile_expression(s);
|
|
|
|
add_call(print_int);
|
|
|
|
}
|
2018-08-01 03:47:05 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
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.
|
|
|
|
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;
|
2018-08-03 20:09:02 +00:00
|
|
|
if (*s != ',') {
|
|
|
|
error = 1;
|
|
|
|
} else {
|
2018-08-03 18:35:15 +00:00
|
|
|
s++;
|
2018-08-03 20:09:02 +00:00
|
|
|
// Parse value. LSB is in A.
|
|
|
|
s = compile_expression(s);
|
|
|
|
g_compiled[g_compiled_length++] = I_LDY_IMM;
|
|
|
|
g_compiled[g_compiled_length++] = 0;
|
|
|
|
g_compiled[g_compiled_length++] = I_STA_IND_Y;
|
|
|
|
g_compiled[g_compiled_length++] = (uint8_t) &ptr1;
|
2018-08-03 18:35:15 +00:00
|
|
|
}
|
2018-08-03 21:20:04 +00:00
|
|
|
} 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 = find_line_address(target_line_number);
|
|
|
|
|
|
|
|
if (addr == 0xFFFF) {
|
|
|
|
// Line not found.
|
|
|
|
// TODO better error message.
|
|
|
|
error = 1;
|
|
|
|
} else {
|
|
|
|
g_compiled[g_compiled_length++] = I_JMP_ABS;
|
|
|
|
g_compiled[g_compiled_length++] = addr & 0xFF;
|
|
|
|
g_compiled[g_compiled_length++] = addr >> 8;
|
|
|
|
}
|
|
|
|
}
|
2018-08-03 18:35:15 +00:00
|
|
|
} else {
|
|
|
|
error = 1;
|
|
|
|
}
|
2018-08-02 06:29:37 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
// Now we're at the end of our statement.
|
|
|
|
if (!error) {
|
|
|
|
if (*s == ':') {
|
|
|
|
// Skip colon.
|
2018-08-02 21:43:55 +00:00
|
|
|
s += 1;
|
2018-08-03 18:35:15 +00:00
|
|
|
|
|
|
|
// Next statement.
|
|
|
|
done = 0;
|
|
|
|
} else if (*s != '\0') {
|
|
|
|
// Junk at the end of the statement.
|
2018-08-01 03:47:05 +00:00
|
|
|
error = 1;
|
|
|
|
}
|
2018-08-03 18:35:15 +00:00
|
|
|
}
|
2018-08-01 03:47:05 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
if (error) {
|
2018-08-03 19:02:14 +00:00
|
|
|
if (line_number != INVALID_LINE_NUMBER) {
|
|
|
|
compile_load_ax(line_number);
|
|
|
|
add_call(syntax_error_in_line);
|
|
|
|
} else {
|
|
|
|
add_call(syntax_error);
|
|
|
|
}
|
2018-08-03 18:35:15 +00:00
|
|
|
}
|
|
|
|
} while (!done);
|
|
|
|
}
|
2018-07-31 22:03:06 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
/**
|
|
|
|
* Complete the compilation buffer and run it.
|
|
|
|
*/
|
|
|
|
static void complete_compile_and_execute(void) {
|
|
|
|
// Return from function.
|
|
|
|
add_return();
|
|
|
|
|
|
|
|
// Dump compiled buffer to the terminal.
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
uint8_t *debug_port = (uint8_t *) 0xBFFE;
|
|
|
|
|
2018-08-03 21:27:16 +00:00
|
|
|
// Size of program (including initial address).
|
|
|
|
debug_port[0] = 2 + g_compiled_length;
|
|
|
|
// 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.
|
2018-08-03 18:35:15 +00:00
|
|
|
for (i = 0; i < g_compiled_length; i++) {
|
|
|
|
debug_port[1] = g_compiled[i];
|
2018-08-02 21:43:55 +00:00
|
|
|
}
|
2018-08-03 18:35:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (g_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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-03 20:09:02 +00:00
|
|
|
/**
|
|
|
|
* Clear out all variables. This does not clear their value, only our
|
|
|
|
* knowledge of them.
|
|
|
|
*/
|
|
|
|
void clear_variables(void) {
|
|
|
|
memset(g_variable_names, 0, sizeof(g_variable_names));
|
|
|
|
}
|
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
/**
|
|
|
|
* Compile the stored program and execute it.
|
|
|
|
*/
|
|
|
|
static void compile_stored_program(void) {
|
|
|
|
uint8_t *line = g_program;
|
|
|
|
uint8_t *next_line;
|
|
|
|
|
2018-08-03 20:09:02 +00:00
|
|
|
// Clear out all variables.
|
|
|
|
clear_variables();
|
|
|
|
|
2018-08-03 19:02:14 +00:00
|
|
|
set_up_compile();
|
2018-08-03 20:09:02 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
while ((next_line = get_next_line(line)) != 0) {
|
|
|
|
uint16_t line_number = get_line_number(line);
|
2018-08-03 21:20:04 +00:00
|
|
|
|
|
|
|
// Store address of line in compiled buffer.
|
|
|
|
if (g_line_address_count == MAX_LINES) {
|
|
|
|
// TODO not sure what to do here.
|
|
|
|
print("Program too large");
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
g_line_address[g_line_address_count++] = line_number;
|
|
|
|
g_line_address[g_line_address_count++] = (uint16_t) (g_compiled + g_compiled_length);
|
|
|
|
}
|
|
|
|
|
2018-08-03 19:02:14 +00:00
|
|
|
compile_buffer(line + 4, line_number);
|
2018-08-03 18:35:15 +00:00
|
|
|
|
|
|
|
line = next_line;
|
|
|
|
}
|
2018-08-03 19:02:14 +00:00
|
|
|
complete_compile_and_execute();
|
2018-08-03 18:35:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2018-08-02 21:43:55 +00:00
|
|
|
|
2018-08-03 18:35:15 +00:00
|
|
|
if (g_input_buffer[0] == T_RUN) {
|
2018-08-03 19:02:14 +00:00
|
|
|
// We don't compile "RUN".
|
2018-08-03 18:35:15 +00:00
|
|
|
compile_stored_program();
|
2018-08-03 19:02:14 +00:00
|
|
|
} else if (g_input_buffer[0] == T_NEW) {
|
|
|
|
// We don't compile "NEW".
|
|
|
|
new_statement();
|
2018-08-01 20:34:26 +00:00
|
|
|
} else {
|
2018-08-03 18:35:15 +00:00
|
|
|
// Compile the immediate mode line.
|
2018-08-03 19:02:14 +00:00
|
|
|
set_up_compile();
|
|
|
|
compile_buffer(g_input_buffer, INVALID_LINE_NUMBER);
|
|
|
|
complete_compile_and_execute();
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
2018-08-01 03:47:05 +00:00
|
|
|
} else {
|
2018-08-01 21:58:29 +00:00
|
|
|
// Stored mode. Add line to program.
|
|
|
|
|
|
|
|
// Return line to replace or delete, or location to insert new line.
|
2018-08-01 20:37:31 +00:00
|
|
|
uint8_t *line = find_line(line_number);
|
2018-08-01 21:58:29 +00:00
|
|
|
uint8_t *next_line = get_next_line(line);
|
2018-08-01 22:08:56 +00:00
|
|
|
uint8_t *end_of_program = get_end_of_program(line);
|
|
|
|
int16_t adjustment = 0;
|
2018-08-01 20:34:26 +00:00
|
|
|
|
2018-08-01 21:58:29 +00:00
|
|
|
if (next_line == 0 || get_line_number(line) != line_number) {
|
|
|
|
// Didn't find line. Insert it here.
|
2018-08-01 20:34:26 +00:00
|
|
|
|
2018-08-01 21:58:29 +00:00
|
|
|
// Next pointer, line number, line, and nul.
|
2018-08-01 22:08:56 +00:00
|
|
|
uint8_t buffer_length = strlen(g_input_buffer);
|
|
|
|
adjustment = 4 + buffer_length + 1;
|
2018-08-01 21:58:29 +00:00
|
|
|
|
|
|
|
// Shift rest of program over.
|
2018-08-01 22:08:56 +00:00
|
|
|
memmove(line + adjustment, line, end_of_program - line);
|
2018-08-01 21:58:29 +00:00
|
|
|
|
|
|
|
// Next line. Point to yourself initially, we'll adjust below.
|
|
|
|
*((uint8_t **) line) = line;
|
2018-08-01 20:34:26 +00:00
|
|
|
|
|
|
|
// Line number.
|
2018-08-01 21:58:29 +00:00
|
|
|
*((uint16_t *) (line + 2)) = line_number;
|
|
|
|
|
|
|
|
// Buffer and nul.
|
|
|
|
memmove(line + 4, g_input_buffer, buffer_length + 1);
|
2018-08-01 20:34:26 +00:00
|
|
|
} else {
|
2018-08-01 21:58:29 +00:00
|
|
|
// Found line.
|
|
|
|
|
|
|
|
if (g_input_buffer[0] == '\0') {
|
|
|
|
// Empty line, delete old one.
|
2018-08-01 22:08:56 +00:00
|
|
|
|
|
|
|
// Adjustment is negative.
|
|
|
|
adjustment = line - next_line;
|
|
|
|
memmove(line, next_line, end_of_program - next_line);
|
2018-08-01 20:34:26 +00:00
|
|
|
} else {
|
2018-08-01 21:58:29 +00:00
|
|
|
// Replace line.
|
2018-08-01 22:08:56 +00:00
|
|
|
|
|
|
|
// 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;
|
2018-08-01 20:34:26 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-01 03:47:05 +00:00
|
|
|
}
|
2018-07-31 22:03:06 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 20:37:31 +00:00
|
|
|
int16_t main(void)
|
2018-07-31 22:03:06 +00:00
|
|
|
{
|
2018-08-01 20:37:31 +00:00
|
|
|
int16_t blink;
|
2018-07-28 05:30:44 +00:00
|
|
|
|
2018-08-03 19:02:14 +00:00
|
|
|
// Clear stored program.
|
|
|
|
new_statement();
|
2018-08-01 20:34:26 +00:00
|
|
|
|
2018-08-03 20:09:02 +00:00
|
|
|
// Clear out all variables.
|
|
|
|
clear_variables();
|
|
|
|
|
2018-08-01 20:34:26 +00:00
|
|
|
// Initialize UI.
|
2018-07-31 07:05:22 +00:00
|
|
|
home();
|
2018-07-28 05:30:44 +00:00
|
|
|
|
2018-07-31 20:42:21 +00:00
|
|
|
// Display the character set.
|
2018-08-02 06:29:37 +00:00
|
|
|
/*
|
|
|
|
if (1) {
|
2018-08-01 20:37:31 +00:00
|
|
|
int16_t i;
|
2018-08-01 20:34:26 +00:00
|
|
|
for (i = 0; i < 256; i++) {
|
2018-08-03 01:59:31 +00:00
|
|
|
uint8_t *loc;
|
2018-08-01 20:34:26 +00:00
|
|
|
// Fails with: unhandled instruction B2
|
|
|
|
move_cursor(i % 16, i >> 4);
|
|
|
|
// Works.
|
|
|
|
// move_cursor(i & 0x0F, i >> 4);
|
|
|
|
loc = cursor_pos();
|
|
|
|
*loc = i;
|
|
|
|
}
|
|
|
|
while(1);
|
2018-07-31 20:42:21 +00:00
|
|
|
}
|
2018-08-02 06:29:37 +00:00
|
|
|
*/
|
2018-07-31 20:42:21 +00:00
|
|
|
|
2018-08-01 20:34:26 +00:00
|
|
|
// Print title.
|
2018-07-31 22:03:06 +00:00
|
|
|
move_cursor((40 - title_length) / 2, 0);
|
|
|
|
print(title);
|
2018-07-28 05:30:44 +00:00
|
|
|
|
2018-07-31 07:05:22 +00:00
|
|
|
// Prompt.
|
2018-07-31 22:03:06 +00:00
|
|
|
print("\n\n]");
|
2018-07-31 07:05:22 +00:00
|
|
|
|
2018-07-31 19:48:29 +00:00
|
|
|
// Keyboard input.
|
2018-08-01 20:34:26 +00:00
|
|
|
blink = 0;
|
2018-08-01 17:58:03 +00:00
|
|
|
g_input_buffer_length = 0;
|
2018-07-31 19:48:29 +00:00
|
|
|
show_cursor();
|
2018-07-31 07:05:22 +00:00
|
|
|
while(1) {
|
2018-07-31 19:48:29 +00:00
|
|
|
// Blink cursor.
|
2018-08-01 20:34:26 +00:00
|
|
|
blink += 1;
|
|
|
|
if (blink == 3000) {
|
2018-08-01 17:58:03 +00:00
|
|
|
if (g_showing_cursor) {
|
2018-07-31 19:48:29 +00:00
|
|
|
hide_cursor();
|
|
|
|
} else {
|
|
|
|
show_cursor();
|
|
|
|
}
|
2018-08-01 20:34:26 +00:00
|
|
|
blink = 0;
|
2018-07-31 19:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(keyboard_test()) {
|
|
|
|
hide_cursor();
|
|
|
|
|
|
|
|
while(keyboard_test()) {
|
2018-08-01 20:37:31 +00:00
|
|
|
uint8_t key;
|
2018-07-31 19:02:26 +00:00
|
|
|
|
2018-07-31 19:48:29 +00:00
|
|
|
key = keyboard_get();
|
|
|
|
if (key == 8) {
|
|
|
|
// Backspace.
|
2018-08-01 17:58:03 +00:00
|
|
|
if (g_input_buffer_length > 0) {
|
|
|
|
move_cursor(g_cursor_x - 1, g_cursor_y);
|
|
|
|
g_input_buffer_length -= 1;
|
2018-07-31 19:48:29 +00:00
|
|
|
}
|
|
|
|
} else if (key == 13) {
|
|
|
|
// Return.
|
2018-08-03 07:02:40 +00:00
|
|
|
clear_to_eol();
|
2018-08-03 01:59:31 +00:00
|
|
|
print_char('\n');
|
2018-07-31 22:03:06 +00:00
|
|
|
|
|
|
|
process_input_buffer();
|
|
|
|
|
2018-08-01 03:47:05 +00:00
|
|
|
print("\n]");
|
2018-08-01 17:58:03 +00:00
|
|
|
g_input_buffer_length = 0;
|
2018-07-31 19:48:29 +00:00
|
|
|
} else {
|
2018-08-01 17:58:03 +00:00
|
|
|
if (g_input_buffer_length < sizeof(g_input_buffer) - 1) {
|
2018-08-03 01:59:31 +00:00
|
|
|
uint8_t *loc = cursor_pos();
|
2018-07-31 22:03:06 +00:00
|
|
|
*loc = key | 0x80;
|
2018-08-01 17:58:03 +00:00
|
|
|
move_cursor(g_cursor_x + 1, g_cursor_y);
|
2018-07-31 22:03:06 +00:00
|
|
|
|
2018-08-01 17:58:03 +00:00
|
|
|
g_input_buffer[g_input_buffer_length++] = key;
|
2018-07-31 22:03:06 +00:00
|
|
|
}
|
2018-07-31 19:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-31 19:02:26 +00:00
|
|
|
|
2018-07-31 19:48:29 +00:00
|
|
|
show_cursor();
|
2018-07-31 19:02:26 +00:00
|
|
|
}
|
2018-07-31 07:05:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2018-07-28 05:30:44 +00:00
|
|
|
}
|