apple2a/main.c

404 lines
9.0 KiB
C
Raw Normal View History

2018-07-31 19:02:26 +00:00
#include "platform.h"
2018-07-28 05:30:44 +00:00
char *title = "Apple IIa";
unsigned char title_length = 9;
2018-07-31 19:48:29 +00:00
#define CURSOR_GLYPH 127
#define SCREEN_WIDTH 40
#define SCREEN_STRIDE (3*SCREEN_WIDTH + 8)
2018-08-01 17:44:24 +00:00
#define T_HOME 0x80
#define T_PRINT 0x81
// List of tokens. The token value is the index plus 0x80.
static unsigned char *TOKEN[] = {
"HOME",
"PRINT",
};
static int TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]);
2018-07-31 19:48:29 +00:00
// Location of cursor in logical screen space.
unsigned int cursor_x = 0;
unsigned int cursor_y = 0;
// Whether the cursor is being displayed.
unsigned int showing_cursor = 0;
// Character at the cursor location.
unsigned char cursor_ch = 0;
2018-07-31 22:03:06 +00:00
unsigned char input_buffer[40];
int input_buffer_length = 0;
// Compiled binary.
2018-08-01 17:56:48 +00:00
char compiled[128];
int compiled_length = 0;
void (*compiled_function)() = (void (*)()) compiled;
2018-07-31 19:48:29 +00:00
/**
* Return the memory location of the cursor.
*/
static volatile unsigned char *cursor_pos() {
int block = cursor_y >> 3;
int line = cursor_y & 0x07;
return TEXT_PAGE1_BASE + line*SCREEN_STRIDE + block*SCREEN_WIDTH + cursor_x;
}
/**
* Shows the cursor. Safe to call if it's already showing.
*/
static void show_cursor() {
if (!showing_cursor) {
volatile unsigned char *pos = cursor_pos();
cursor_ch = *pos;
*pos = CURSOR_GLYPH | 0x80;
showing_cursor = 1;
}
}
/**
* Hides the cursor. Safe to call if it's not already shown.
*/
static void hide_cursor() {
if (showing_cursor) {
volatile unsigned char *pos = cursor_pos();
*pos = cursor_ch;
showing_cursor = 0;
}
}
/**
* Moves the cursor to the specified location, where X
* is 0 to 39 inclusive, Y is 0 to 23 inclusive.
*/
static void move_cursor(int x, int y) {
hide_cursor();
cursor_x = x;
cursor_y = y;
}
2018-07-31 22:03:06 +00:00
/**
* Clear the screen with non-reversed spaces.
*/
static void home() {
volatile unsigned char *p = TEXT_PAGE1_BASE;
unsigned char ch = ' ' | 0x80;
int i;
// TODO: Could write these as words, not chars.
for (i = SCREEN_STRIDE*8; i >= 0; i--) {
*p++ = ch;
}
move_cursor(0, 0);
}
/**
2018-08-01 17:44:24 +00:00
* Prints the character and advances the cursor. Handles newlines.
2018-07-31 22:03:06 +00:00
*/
2018-08-01 17:44:24 +00:00
static void print_char(unsigned char c) {
2018-07-31 22:03:06 +00:00
volatile unsigned char *loc = cursor_pos();
2018-08-01 17:44:24 +00:00
if (c == '\n') {
// TODO: Scroll.
move_cursor(0, cursor_y + 1);
} else {
// Print character.
*loc = c | 0x80;
move_cursor(cursor_x + 1, cursor_y);
}
}
/**
* Print a string at the cursor.
*/
static void print(unsigned char *s) {
2018-07-31 22:03:06 +00:00
while (*s != '\0') {
2018-08-01 17:44:24 +00:00
print_char(*s++);
2018-07-31 22:03:06 +00:00
}
}
2018-08-01 06:19:15 +00:00
static void print_statement() {
print("Hello world!\n");
}
2018-07-31 22:03:06 +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
*/
static unsigned char *skip_over(unsigned char *a, unsigned char *b) {
while (*a != '\0' && *b != '\0') {
2018-07-31 22:03:06 +00:00
if (*a != *b) {
// 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
// See if we're at the end of b.
return *b == '\0' ? a : 0;
2018-07-31 22:03:06 +00:00
}
/**
* Display a syntax error message.
*/
static void syntax_error() {
print("\n?SYNTAX ERROR");
// No linefeed, assume prompt will do it.
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
*/
static void add_call(void (*function)(void)) {
unsigned int addr = (int) function;
2018-08-01 17:56:48 +00:00
compiled[compiled_length++] = 0x20; // JSR
compiled[compiled_length++] = addr & 0xFF;
compiled[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-01 17:56:48 +00:00
compiled[compiled_length++] = 0x60; // RTS
2018-07-31 22:03:06 +00:00
}
/**
* Advance s over whitespace, which is just a space, returning
* the new pointer.
*/
static unsigned char *skip_whitespace(unsigned char *s) {
while (*s == ' ') {
s += 1;
}
return s;
}
2018-08-01 17:44:24 +00:00
/**
* Tokenize a string in place. Returns (and removes) any line number, or 0xFFFF
* if there's none. The new line will be terminated by three nuls.
*/
static unsigned int tokenize(unsigned char *s) {
unsigned char *t = s; // Tokenized version.
int line_number;
// Parse optional line number.
if (*s >= '0' && *s <= '9') {
line_number = 0;
while (*s >= '0' && *s <= '9') {
line_number = line_number*10 + (*s - '0');
s += 1;
}
} else {
line_number = 0xFFFF;
}
// Convert tokens.
while (*s != '\0') {
if (*s == ' ') {
// Skip spaces.
s++;
} else {
int i;
unsigned char *skipped = 0;
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++;
}
}
}
// End with three nuls. The first is the end of line.
// The next two is the address of the next line, which is "none".
*t++ = '\0';
*t++ = '\0';
*t++ = '\0';
return line_number;
}
/**
* Print the tokenized string, with tokens displayed as their full text.
* Prints a line number first if it's not 0xFFFF. Prints a newline at the end.
*/
static void print_detokenized(unsigned int line_number, unsigned char *s) {
while (*s != '\0') {
if (*s >= 0x80) {
print(TOKEN[*s - 0x80]);
} else {
print_char(*s);
}
s += 1;
}
print_char('\n');
}
2018-07-31 22:03:06 +00:00
/**
* Process the user's line of input, possibly compiling the code.
* and executing it.
*/
static void process_input_buffer() {
unsigned char *s; // Where we are in the buffer.
char done;
2018-08-01 17:44:24 +00:00
unsigned int line_number;
2018-07-31 22:03:06 +00:00
input_buffer[input_buffer_length] = '\0';
2018-08-01 17:44:24 +00:00
// Tokenize in-place.
line_number = tokenize(input_buffer);
2018-08-01 06:19:15 +00:00
s = input_buffer;
2018-07-31 22:03:06 +00:00
// Compile the line of BASIC.
2018-08-01 17:56:48 +00:00
compiled_length = 0;
do {
char error = 0;
2018-08-01 17:56:48 +00:00
// Default to being done after one statement.
done = 1;
2018-08-01 06:19:15 +00:00
if (*s == '\0' || *s == ':') {
// Empty statement.
2018-08-01 17:44:24 +00:00
} else if (*s == T_HOME) {
s += 1;
add_call(home);
2018-08-01 17:44:24 +00:00
} else if (*s == T_PRINT) {
s += 1;
2018-08-01 06:19:15 +00:00
// TODO: Parse expression.
add_call(print_statement);
} else {
error = 1;
}
2018-08-01 06:19:15 +00:00
// Now we're at the end of our statement.
if (!error) {
if (*s == ':') {
// Skip colon.
s += 1;
2018-08-01 06:19:15 +00:00
// Next statement.
done = 0;
} else if (*s != '\0') {
2018-08-01 06:19:15 +00:00
// Junk at the end of the statement.
error = 1;
}
}
if (error) {
add_call(syntax_error);
}
} while (!done);
2018-07-31 22:03:06 +00:00
// Return from function.
add_return();
2018-08-01 17:56:48 +00:00
if (compiled_length > sizeof(compiled)) {
// TODO: Check while adding bytes, not at the end.
print("\n?Binary length exceeded");
} else {
// Call it.
2018-08-01 17:56:48 +00:00
compiled_function();
}
2018-07-31 22:03:06 +00:00
}
int main(void)
{
2018-07-28 05:30:44 +00:00
int i;
2018-07-31 07:05:22 +00:00
home();
2018-07-28 05:30:44 +00:00
2018-07-31 22:03:06 +00:00
/*
2018-07-31 20:42:21 +00:00
// Display the character set.
for (i = 0; i < 256; i++) {
2018-08-01 17:44:24 +00:00
volatile unsigned char *loc;
2018-07-31 20:42:21 +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 22:03:06 +00:00
*/
2018-07-31 20:42:21 +00:00
2018-07-31 07:05:22 +00:00
// 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.
i = 0;
2018-07-31 22:03:06 +00:00
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.
i += 1;
if (i == 3000) {
2018-07-31 19:48:29 +00:00
if (showing_cursor) {
hide_cursor();
} else {
show_cursor();
}
i = 0;
}
if(keyboard_test()) {
hide_cursor();
while(keyboard_test()) {
unsigned char 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-07-31 22:03:06 +00:00
if (input_buffer_length > 0) {
2018-07-31 19:48:29 +00:00
move_cursor(cursor_x - 1, cursor_y);
2018-07-31 22:03:06 +00:00
input_buffer_length -= 1;
2018-07-31 19:48:29 +00:00
}
} else if (key == 13) {
// Return.
move_cursor(0, cursor_y + 1);
2018-07-31 22:03:06 +00:00
process_input_buffer();
print("\n]");
2018-07-31 22:03:06 +00:00
input_buffer_length = 0;
2018-07-31 19:48:29 +00:00
} else {
2018-07-31 22:03:06 +00:00
if (input_buffer_length < sizeof(input_buffer) - 1) {
volatile unsigned char *loc = cursor_pos();
*loc = key | 0x80;
move_cursor(cursor_x + 1, cursor_y);
input_buffer[input_buffer_length++] = key;
}
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
}