mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
938 lines
22 KiB
C++
938 lines
22 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#define MAX_SOURCE_LINE_LENGTH 256
|
|
#define MAX_TOKEN_STRING_LENGTH MAX_SOURCE_LINE_LENGTH
|
|
#define MAX_DIGIT_COUNT 20
|
|
#define MAX_SYMBOLS 1024
|
|
#define MAX_SUBROUTINES 1024
|
|
#define MAX_SUBROUTINE_SIZE 4096
|
|
#define MAX_SUBROUTINE_JUMPS 256
|
|
|
|
#define OPSIZE8 0x40 ///< when this bit is set - the operand size is 8 bits
|
|
#define OPSIZE16 0x80 ///< when this bit is set - the operand size is 16 bits
|
|
#define OPSIZE32 0x00 ///< when no bits are set - the operand size is 32 bits
|
|
|
|
#define VERSION 1
|
|
|
|
enum CharCode {
|
|
LETTER, DIGIT, SPECIAL, EOF_CODE, EOL_CODE
|
|
};
|
|
|
|
enum TokenCode {
|
|
NO_TOKEN, WORD, NUMBER, IDENTIFIER, END_OF_FILE, END_OF_LINE,
|
|
RW_DEFINE, RW_COLON, RW_SUB, RW_END, RW_OPCODE,
|
|
ERROR
|
|
};
|
|
|
|
enum LiteralType {
|
|
INTEGER_LIT
|
|
};
|
|
|
|
struct Literal {
|
|
LiteralType type;
|
|
union {
|
|
int integer;
|
|
} value;
|
|
};
|
|
|
|
struct SymbolEntry {
|
|
char symbol[MAX_TOKEN_STRING_LENGTH];
|
|
char value[MAX_TOKEN_STRING_LENGTH];
|
|
};
|
|
|
|
struct SubEntry {
|
|
char name[MAX_TOKEN_STRING_LENGTH];
|
|
int fileOffset;
|
|
};
|
|
|
|
struct JumpSource {
|
|
char name[MAX_TOKEN_STRING_LENGTH];
|
|
int line_number;
|
|
int offset;
|
|
};
|
|
|
|
struct JumpDest {
|
|
char name[MAX_TOKEN_STRING_LENGTH];
|
|
int offset;
|
|
};
|
|
|
|
enum Opcodes {
|
|
OP_HALT = 0, OP_IMM = 1, OP_ZERO = 2, OP_ONE = 3, OP_MINUSONE = 4, OP_STR = 5, OP_DLOAD = 6,
|
|
OP_DSTORE = 7, OP_PAL = 8, OP_LOAD = 9, OP_GLOAD = 10, OP_STORE = 11, OP_GSTORE = 12,
|
|
OP_CALL = 13, OP_LIBCALL = 14, OP_RET = 15, OP_ALLOC = 16, OP_JUMP = 17, OP_JMPFALSE = 18,
|
|
OP_JMPTRUE = 19, OP_EQUAL = 20, OP_LESS = 21, OP_LEQUAL = 22, OP_NEQUAL = 23, OP_GEQUAL = 24,
|
|
OP_GREAT = 25, OP_PLUS = 26, OP_MINUS = 27, OP_LOR = 28, OP_MULT = 29, OP_DIV = 30,
|
|
OP_MOD = 31, OP_AND = 32, OP_OR = 33, OP_EOR = 34, OP_LAND = 35, OP_NOT = 36, OP_COMP = 37,
|
|
OP_NEG = 38, OP_DUP = 39,
|
|
TOTAL_OPCODES = 40
|
|
};
|
|
|
|
typedef unsigned char byte;
|
|
|
|
const unsigned char EOF_CHAR = (unsigned char)255;
|
|
const unsigned char EOL_CHAR = (unsigned char)254;
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Reserved words tables */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
enum OpcodeParamType {OP_NO_PARAM, OP_IMM_PARAM, OP_TRANSFER_PARAM};
|
|
|
|
struct OpcodeEntry {
|
|
const char *str;
|
|
OpcodeParamType paramType;
|
|
};
|
|
|
|
OpcodeEntry OpcodeList[OP_DUP + 1] = {
|
|
{"HALT", OP_NO_PARAM}, {"IMM", OP_IMM_PARAM}, {"ZERO", OP_NO_PARAM}, {"ONE", OP_NO_PARAM},
|
|
{"MINUSONE", OP_NO_PARAM}, {"STR", OP_IMM_PARAM}, {"DLOAD", OP_IMM_PARAM}, {"DSTORE", OP_IMM_PARAM},
|
|
{"PAL", OP_IMM_PARAM}, {"LOAD", OP_IMM_PARAM}, {"GLOAD", OP_IMM_PARAM}, {"STORE", OP_IMM_PARAM},
|
|
{"GSTORE", OP_IMM_PARAM}, {"CALL", OP_IMM_PARAM}, {"LIBCALL", OP_IMM_PARAM}, {"RET", OP_NO_PARAM},
|
|
{"ALLOC", OP_IMM_PARAM}, {"JUMP", OP_TRANSFER_PARAM}, {"JMPFALSE", OP_TRANSFER_PARAM},
|
|
{"JMPTRUE", OP_TRANSFER_PARAM}, {"EQUAL", OP_NO_PARAM}, {"LESS", OP_NO_PARAM},
|
|
{"LEQUAL", OP_NO_PARAM}, {"NEQUAL", OP_NO_PARAM}, {"GEQUAL", OP_NO_PARAM},
|
|
{"GREAT", OP_NO_PARAM}, {"PLUS", OP_NO_PARAM}, {"MINUS", OP_NO_PARAM},
|
|
{"LOR", OP_NO_PARAM}, {"MULT", OP_NO_PARAM}, {"DIV", OP_IMM_PARAM}, {"MOD", OP_NO_PARAM},
|
|
{"AND", OP_NO_PARAM}, {"OR", OP_NO_PARAM}, {"EOR", OP_NO_PARAM}, {"LAND", OP_NO_PARAM},
|
|
{"NOT", OP_NO_PARAM}, {"COMP", OP_NO_PARAM}, {"NEG", OP_NO_PARAM}, {"DUP", OP_NO_PARAM}
|
|
};
|
|
|
|
|
|
const char *symbol_strings[] = {"#DEFINE", ":", "SUB", "END"};
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Globals */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
unsigned char ch; // Current input character
|
|
TokenCode token; // code of current token
|
|
Opcodes opcode; // Current instruction opcode
|
|
OpcodeParamType paramType; // Parameter type opcode expects
|
|
Literal literal; // Value of literal
|
|
int buffer_offset; // Char offset into source buffer
|
|
int level = 0; // current nesting level
|
|
int line_number = 0; // current line number
|
|
|
|
char source_buffer[MAX_SOURCE_LINE_LENGTH]; // Source file buffer
|
|
char token_string[MAX_TOKEN_STRING_LENGTH]; // Token string
|
|
const char *bufferp = source_buffer; // Source buffer ptr
|
|
char *tokenp = token_string; // Token string ptr
|
|
|
|
int digit_count; // Total no. of digits in number
|
|
bool count_error; // Too many digits in number?
|
|
|
|
FILE *source_file;
|
|
FILE *dest_file;
|
|
CharCode char_table[256];
|
|
|
|
SymbolEntry symbolTable[MAX_SYMBOLS];
|
|
int symbolCount = 0;
|
|
|
|
int game_number = 0;
|
|
int language = 0;
|
|
|
|
int indexSize = 0;
|
|
int fileOffset = 0;
|
|
SubEntry subroutinesTable[MAX_SUBROUTINES];
|
|
int subroutinesCount = 0;
|
|
|
|
byte subroutineData[MAX_SUBROUTINE_SIZE];
|
|
int subroutineSize = 0;
|
|
|
|
JumpSource jumpSources[MAX_SUBROUTINE_JUMPS];
|
|
int jumpSourceCount = 0;
|
|
JumpDest jumpDests[MAX_SUBROUTINE_JUMPS];
|
|
int jumpDestCount = 0;
|
|
|
|
#define char_code(ch) char_table[ch]
|
|
|
|
void get_char();
|
|
void get_token();
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Miscellaneous support functions */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
void strToUpper(char *string) {
|
|
while (*string) {
|
|
*string = toupper(*string);
|
|
++string;
|
|
}
|
|
}
|
|
|
|
void strToLower(char *string) {
|
|
while (*string) {
|
|
*string = tolower(*string);
|
|
++string;
|
|
}
|
|
}
|
|
|
|
int strToInt(const char *s) {
|
|
unsigned int tmp;
|
|
|
|
if (!*s)
|
|
// No string at all
|
|
return 0;
|
|
else if (toupper(s[strlen(s) - 1]) == 'H')
|
|
// Hexadecimal string with trailing 'h'
|
|
sscanf(s, "%xh", &tmp);
|
|
else if (*s == '$')
|
|
// Hexadecimal string starting with '$'
|
|
sscanf(s + 1, "%x", &tmp);
|
|
else
|
|
// Standard decimal string
|
|
return atoi(s);
|
|
|
|
return (int)tmp;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Initialisation / De-initialisation code */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Open the input file for parsing
|
|
*/
|
|
void open_source_file(const char *name) {
|
|
if ((source_file = fopen(name, "r")) == NULL) {
|
|
printf("*** Error: Failed to open source file.\n");
|
|
exit(0);
|
|
}
|
|
|
|
// Fetch the first character
|
|
bufferp = "";
|
|
get_char();
|
|
}
|
|
|
|
/**
|
|
* Close the source file
|
|
*/
|
|
void close_source_file() {
|
|
fclose(source_file);
|
|
}
|
|
|
|
/**
|
|
* Initialises the scanner
|
|
*/
|
|
void init_scanner(const char *name) {
|
|
// Initialise character table
|
|
for (int i = 0; i < 256; ++i) char_table[i] = SPECIAL;
|
|
for (int i = '0'; i <= '9'; ++i) char_table[i] = DIGIT;
|
|
for (int i = 'A'; i <= 'Z'; ++i) char_table[i] = LETTER;
|
|
for (int i = 'a'; i <= 'z'; ++i) char_table[i] = LETTER;
|
|
char_table[EOF_CHAR] = EOF_CODE;
|
|
char_table[EOL_CHAR] = EOL_CODE;
|
|
char_table[(int)'$'] = DIGIT; // Needed for hexadecimal number handling
|
|
|
|
open_source_file(name);
|
|
}
|
|
|
|
/**
|
|
* Shuts down the scanner
|
|
*/
|
|
void quit_scanner() {
|
|
close_source_file();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Output routines */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
/**
|
|
* Initialises the output
|
|
*/
|
|
void init_output(const char *destFilename) {
|
|
dest_file = fopen(destFilename, "wb");
|
|
if (dest_file == NULL) {
|
|
printf("Could not open file for writing\n");
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the output file
|
|
*/
|
|
void close_output() {
|
|
fclose(dest_file);
|
|
}
|
|
|
|
/**
|
|
* Writes a single byte to the output
|
|
*/
|
|
void write_byte(byte v) {
|
|
fwrite(&v, 1, 1, dest_file);
|
|
++fileOffset;
|
|
}
|
|
|
|
/**
|
|
* Writes a word to the output
|
|
*/
|
|
void write_word(int v) {
|
|
write_byte(v & 0xff);
|
|
write_byte((v >> 8) & 0xff);
|
|
}
|
|
|
|
/**
|
|
* Writes a 32-bit value to the output
|
|
*/
|
|
void write_long(int v) {
|
|
write_byte(v & 0xff);
|
|
write_byte((v >> 8) & 0xff);
|
|
write_byte((v >> 16) & 0xff);
|
|
write_byte((v >> 24) & 0xff);
|
|
}
|
|
|
|
/**
|
|
* Writes a sequence of bytes to the output
|
|
*/
|
|
void write_bytes(byte *v, int len) {
|
|
fwrite(v, 1, len, dest_file);
|
|
fileOffset += len;
|
|
}
|
|
|
|
/**
|
|
* Writes a repeat sequence of a value to the output
|
|
*/
|
|
void write_byte_seq(byte v, int len) {
|
|
byte *tempData = (byte *)malloc(len);
|
|
memset(tempData, v, len);
|
|
write_bytes(tempData, len);
|
|
free(tempData);
|
|
}
|
|
|
|
/**
|
|
* Writes out the header and allocates space for the symbol table
|
|
*/
|
|
void write_header() {
|
|
// Write out three bytes - game Id, language Id, and version number
|
|
if (game_number == 0) {
|
|
game_number = 1;
|
|
printf("No game specified, defaulting to Rex Nebular\n");
|
|
}
|
|
write_byte(game_number);
|
|
|
|
if (language == 0) {
|
|
language = 1;
|
|
printf("No language specified, defaulting to English\n");
|
|
}
|
|
write_byte(language);
|
|
|
|
write_byte(VERSION);
|
|
|
|
// Write out space to later come back and store the list of subroutine names and offsets
|
|
if (indexSize == 0) {
|
|
indexSize = 4096;
|
|
printf("No index size specified, defaulting to %d bytes\n", indexSize);
|
|
}
|
|
write_byte_seq(0, indexSize - 3);
|
|
|
|
fileOffset = indexSize;
|
|
}
|
|
|
|
/**
|
|
* Goes back and writes out the subroutine list
|
|
*/
|
|
void write_index() {
|
|
fseek(dest_file, 3, SEEK_SET);
|
|
|
|
int bytesRemaining = indexSize - 3;
|
|
for (int i = 0; i < subroutinesCount; ++i) {
|
|
int entrySize = strlen(subroutinesTable[i].name) + 5;
|
|
|
|
// Ensure there is enough remaining space
|
|
if ((bytesRemaining - entrySize) < 0) {
|
|
printf("Index has exceeded allowable size.\n");
|
|
token = ERROR;
|
|
}
|
|
|
|
// Write out the name and the file offset
|
|
write_bytes((byte *)&subroutinesTable[i].name, strlen(subroutinesTable[i].name) + 1);
|
|
write_long(subroutinesTable[i].fileOffset);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Processing routines */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
int symbolFind() {
|
|
for (int i = 0; i < symbolCount; ++i) {
|
|
if (!strcmp(symbolTable[i].symbol, token_string))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int subIndexOf() {
|
|
for (int i = 0; i < subroutinesCount; ++i) {
|
|
if (!strcmp(subroutinesTable[i].name, token_string))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int jumpIndexOf(const char *name) {
|
|
for (int i = 0; i < jumpDestCount; ++i) {
|
|
if (!strcmp(jumpDests[i].name, name))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void handle_define() {
|
|
// Read the variable name
|
|
get_token();
|
|
if (token != IDENTIFIER) {
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Make sure it doesn't already exist
|
|
if (symbolFind() != -1) {
|
|
printf("Duplicate symbol encountered.\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Store the new symbol name
|
|
strcpy(symbolTable[symbolCount].symbol, token_string);
|
|
|
|
// Get the value
|
|
get_token();
|
|
if (token == END_OF_LINE) {
|
|
printf("Unexpected end of line.\n");
|
|
token = ERROR;
|
|
}
|
|
if ((token != NUMBER) && (token != IDENTIFIER)) {
|
|
printf("Invalid define value.\n");
|
|
token = ERROR;
|
|
}
|
|
if (token == ERROR)
|
|
return;
|
|
|
|
// Handle special symbols
|
|
if (!strcmp(symbolTable[symbolCount].symbol, "GAME_ID")) {
|
|
// Specify game number
|
|
if (!strcmp(token_string, "REX"))
|
|
game_number = 1;
|
|
else
|
|
token = ERROR;
|
|
} else if (!strcmp(symbolTable[symbolCount].symbol, "LANGUAGE")) {
|
|
// Specify the language
|
|
if (!strcmp(token_string, "ENGLISH"))
|
|
language = 1;
|
|
else
|
|
token = ERROR;
|
|
} else if (!strcmp(symbolTable[symbolCount].symbol, "INDEX_BLOCK_SIZE")) {
|
|
// Specifying the size of the index
|
|
indexSize = strToInt(token_string);
|
|
} else {
|
|
// Standard symbol - save it's value
|
|
strcpy(symbolTable[symbolCount].value, token_string);
|
|
++symbolCount;
|
|
}
|
|
|
|
if (token == ERROR)
|
|
return;
|
|
|
|
// Ensure the next symbol is the end of line
|
|
get_token();
|
|
if (token != END_OF_LINE) {
|
|
printf("Extraneous information on line.\n");
|
|
token = ERROR;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles getting a parameter for an opcode
|
|
*/
|
|
void get_parameter() {
|
|
int nvalue;
|
|
|
|
if (token == NUMBER) {
|
|
literal.value.integer = strToInt(token_string);
|
|
return;
|
|
}
|
|
|
|
if (token != IDENTIFIER)
|
|
return;
|
|
|
|
nvalue = symbolFind();
|
|
if (nvalue != -1) {
|
|
// Found symbol, so get it's numeric value and return
|
|
token = NUMBER;
|
|
literal.value.integer = strToInt(symbolTable[nvalue].value);
|
|
return;
|
|
}
|
|
|
|
// Check if the parameter is the name of an already processed subroutine
|
|
strToLower(token_string);
|
|
nvalue = subIndexOf();
|
|
if (nvalue == -1) {
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Store the index (not the offset) of the subroutine to call
|
|
token = NUMBER;
|
|
literal.value.integer = nvalue;
|
|
}
|
|
|
|
#define INC_SUB_PTR if (++subroutineSize == MAX_SUBROUTINE_SIZE) { \
|
|
printf("Maximum allowable subroutine size exceeded\n"); \
|
|
token = ERROR; \
|
|
return; \
|
|
}
|
|
|
|
#define WRITE_SUB_BYTE(v) subroutineData[subroutineSize] = (byte)(v)
|
|
|
|
/**
|
|
* Handles a single instruction within the sub-routine
|
|
*/
|
|
void handle_instruction() {
|
|
// Write out the opcode
|
|
WRITE_SUB_BYTE(opcode);
|
|
INC_SUB_PTR;
|
|
|
|
get_token();
|
|
|
|
if (OpcodeList[opcode].paramType == OP_IMM_PARAM) {
|
|
get_parameter();
|
|
|
|
if (token != NUMBER) {
|
|
printf("Incorrect opcode parameter encountered\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Apply the correct opcode size to the previously stored opcode and save the byte(s)
|
|
if (literal.value.integer <= 0xff) {
|
|
subroutineData[subroutineSize - 1] |= OPSIZE8;
|
|
WRITE_SUB_BYTE(literal.value.integer);
|
|
INC_SUB_PTR;
|
|
} else if (literal.value.integer <= 0xffff) {
|
|
subroutineData[subroutineSize - 1] |= OPSIZE16;
|
|
WRITE_SUB_BYTE(literal.value.integer);
|
|
INC_SUB_PTR;
|
|
WRITE_SUB_BYTE(literal.value.integer >> 8);
|
|
INC_SUB_PTR;
|
|
|
|
} else {
|
|
subroutineData[subroutineSize - 1] |= OPSIZE32;
|
|
int v = literal.value.integer;
|
|
for (int i = 0; i < 4; ++i, v >>= 8) {
|
|
WRITE_SUB_BYTE(v);
|
|
INC_SUB_PTR;
|
|
}
|
|
}
|
|
|
|
get_token();
|
|
} else if (OpcodeList[opcode].paramType == OP_TRANSFER_PARAM) {
|
|
|
|
if (token != IDENTIFIER) {
|
|
printf("Incorrect opcode parameter encountered\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Check to see if it's a backward jump to an existing label
|
|
int idx = jumpIndexOf(token_string);
|
|
if (idx != -1) {
|
|
// It's a backwards jump whose destination is already known
|
|
if (jumpDests[idx].offset < 256) {
|
|
// 8-bit destination
|
|
subroutineData[subroutineSize - 1] |= OPSIZE8;
|
|
subroutineData[subroutineSize] = jumpDests[idx].offset;
|
|
INC_SUB_PTR;
|
|
} else {
|
|
// 16-bit destination
|
|
subroutineData[subroutineSize - 1] |= OPSIZE16;
|
|
INC_SUB_PTR;
|
|
subroutineData[subroutineSize] = jumpDests[idx].offset & 0xff;
|
|
INC_SUB_PTR;
|
|
subroutineData[subroutineSize] = (jumpDests[idx].offset >> 8) & 0xff;
|
|
}
|
|
} else {
|
|
// Unknown destination, so save it for later resolving
|
|
strcpy(jumpSources[jumpSourceCount].name, token_string);
|
|
jumpSources[jumpSourceCount].line_number = line_number;
|
|
jumpSources[jumpSourceCount].offset = subroutineSize;
|
|
if (++jumpSourceCount == MAX_SUBROUTINE_JUMPS) {
|
|
printf("Maximum allowable jumps size exceeded\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Store a 16-bit placeholder
|
|
subroutineData[subroutineSize - 1] |= OPSIZE16;
|
|
WRITE_SUB_BYTE(0);
|
|
INC_SUB_PTR;
|
|
WRITE_SUB_BYTE(0);
|
|
INC_SUB_PTR;
|
|
}
|
|
|
|
get_token();
|
|
}
|
|
|
|
if (token != END_OF_LINE)
|
|
token = ERROR;
|
|
}
|
|
|
|
/**
|
|
* Called at the end of the sub-routine, fixes the destination of any forward jump references
|
|
*/
|
|
void fix_subroutine_jumps() {
|
|
for (int i = 0; i < jumpSourceCount; ++i) {
|
|
// Scan through the list of transfer destinations within the script
|
|
int idx = jumpIndexOf(jumpSources[i].name);
|
|
if (idx == -1) {
|
|
token = ERROR;
|
|
line_number = jumpSources[i].line_number;
|
|
return;
|
|
}
|
|
|
|
// Replace the placeholder bytes with the new destination
|
|
subroutineData[jumpSources[i].offset] = jumpDests[idx].offset & 0xff;
|
|
subroutineData[jumpSources[i].offset + 1] = (jumpDests[idx].offset >> 8) & 0xff;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles parsing a sub-routine
|
|
*/
|
|
void handle_sub() {
|
|
// Get the subroutine name
|
|
get_token();
|
|
if (token != IDENTIFIER) {
|
|
printf("Missing subroutine name.\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
strToLower(token_string);
|
|
if (subIndexOf() != -1) {
|
|
printf("Duplicate sub-routine encountered\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// If this is the first subroutine, start writing out the data
|
|
if (subroutinesCount == 0)
|
|
write_header();
|
|
|
|
// Save the sub-routine details
|
|
strcpy(subroutinesTable[subroutinesCount].name, token_string);
|
|
subroutinesTable[subroutinesCount].fileOffset = fileOffset;
|
|
if (++subroutinesCount == MAX_SUBROUTINES) {
|
|
printf("Exceeded maximum allowed subroutine count\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Ensure the line end
|
|
get_token();
|
|
if (token != END_OF_LINE) {
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Initial processing arrays
|
|
memset(subroutineData, 0, MAX_SUBROUTINE_SIZE);
|
|
subroutineSize = 0;
|
|
jumpSourceCount = 0;
|
|
jumpDestCount = 0;
|
|
|
|
// Loop through the lines of the sub-routine
|
|
while (token != ERROR) {
|
|
get_token();
|
|
|
|
if (token == END_OF_LINE) continue;
|
|
if (token == RW_OPCODE) {
|
|
// Handle instructions
|
|
handle_instruction();
|
|
|
|
} else if (token == IDENTIFIER) {
|
|
// Save identifier, it's hopefully a jump symbol
|
|
strcpy(jumpDests[jumpDestCount].name, token_string);
|
|
get_token();
|
|
if (token != RW_COLON)
|
|
token = ERROR;
|
|
else {
|
|
// Save the jump point
|
|
jumpDests[jumpDestCount].offset = subroutineSize;
|
|
|
|
if (++jumpDestCount == MAX_SUBROUTINE_JUMPS) {
|
|
printf("Subroutine exceeded maximum allowable jump points\n");
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
// Ensure it's the last value on the line
|
|
get_token();
|
|
if (token != END_OF_LINE)
|
|
token = ERROR;
|
|
}
|
|
} else if (token == RW_END) {
|
|
// End of subroutine reached
|
|
get_token();
|
|
if (token != ERROR)
|
|
fix_subroutine_jumps();
|
|
write_bytes(&subroutineData[0], subroutineSize);
|
|
break;
|
|
|
|
} else {
|
|
token = ERROR;
|
|
printf("Unexpected error\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Character routines */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Read the next line from the source file.
|
|
*/
|
|
bool get_source_line() {
|
|
if ((fgets(source_buffer, MAX_SOURCE_LINE_LENGTH, source_file)) != NULL) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set ch to the next character from the source buffer
|
|
*/
|
|
void get_char() {
|
|
// If at the end of current source line, read another line.
|
|
// If at end of file, set ch to the EOF character and return
|
|
if (*bufferp == '\0') {
|
|
if (!get_source_line()) {
|
|
ch = EOF_CHAR;
|
|
return;
|
|
}
|
|
bufferp = source_buffer;
|
|
buffer_offset = 0;
|
|
++line_number;
|
|
ch = EOL_CHAR;
|
|
return;
|
|
}
|
|
|
|
ch = *bufferp++; // Next character in the buffer
|
|
|
|
if ((ch == '\n') || (ch == '\t')) ch = ' ';
|
|
}
|
|
|
|
/**
|
|
* Skip past any blanks in the current location in the source buffer.
|
|
* Set ch to the next nonblank character
|
|
*/
|
|
void skip_blanks() {
|
|
while (ch == ' ') get_char();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Token routines */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
bool is_reserved_word() {
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (!strcmp(symbol_strings[i], token_string)) {
|
|
token = (TokenCode)(RW_DEFINE + i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool is_opcode() {
|
|
for (int i = 0; i < TOTAL_OPCODES; ++i) {
|
|
if (!strcmp(OpcodeList[i].str, token_string)) {
|
|
token = RW_OPCODE;
|
|
opcode = (Opcodes)i;
|
|
paramType = OpcodeList[i].paramType;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extract a word token and set token to IDENTIFIER
|
|
*/
|
|
void get_word() {
|
|
// Extract the word
|
|
while ((char_code(ch) == LETTER) || (char_code(ch) == DIGIT) || (ch == '_')) {
|
|
*tokenp++ = ch;
|
|
get_char();
|
|
}
|
|
|
|
*tokenp = '\0';
|
|
|
|
strToUpper(token_string);
|
|
token = WORD;
|
|
if (!is_reserved_word() && !is_opcode()) token = IDENTIFIER;
|
|
}
|
|
|
|
/**
|
|
* Extract a number token and set literal to it's value. Set token to NUMBER
|
|
*/
|
|
void get_number() {
|
|
digit_count = 0; // Total no. of digits in number */
|
|
count_error = false; // Too many digits in number?
|
|
|
|
do {
|
|
*tokenp++ = ch;
|
|
|
|
if (++digit_count > MAX_DIGIT_COUNT) {
|
|
count_error = true;
|
|
break;
|
|
}
|
|
|
|
get_char();
|
|
} while ((char_code(ch) == DIGIT) || (toupper(ch) == 'X') || ((toupper(ch) >= 'A') && (toupper(ch) <= 'F')));
|
|
|
|
if (count_error) {
|
|
token = ERROR;
|
|
return;
|
|
}
|
|
|
|
literal.type = INTEGER_LIT;
|
|
literal.value.integer = strToInt(token_string);
|
|
*tokenp = '\0';
|
|
token = NUMBER;
|
|
}
|
|
|
|
/**
|
|
* Extract a special token
|
|
*/
|
|
void get_special() {
|
|
*tokenp++ = ch;
|
|
if (ch == ':') {
|
|
token = RW_COLON;
|
|
get_char();
|
|
return;
|
|
} else if (ch == '/') {
|
|
*tokenp++ = ch;
|
|
get_char();
|
|
if (ch == '/') {
|
|
// Comment, so read until end of line
|
|
while ((ch != EOL_CHAR) && (ch != EOF_CHAR))
|
|
get_char();
|
|
token = END_OF_LINE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Extract the rest of the word
|
|
get_char();
|
|
while ((char_code(ch) == LETTER) || (char_code(ch) == DIGIT)) {
|
|
*tokenp++ = ch;
|
|
get_char();
|
|
}
|
|
*tokenp = '\0';
|
|
|
|
strToUpper(token_string);
|
|
if (token_string[0] == '@')
|
|
token = IDENTIFIER;
|
|
else if (!is_reserved_word())
|
|
token = ERROR;
|
|
}
|
|
|
|
/**
|
|
* Extract the next token from the source buffer
|
|
*/
|
|
void get_token() {
|
|
skip_blanks();
|
|
tokenp = token_string;
|
|
|
|
switch (char_code(ch)) {
|
|
case LETTER: get_word(); break;
|
|
case DIGIT: get_number(); break;
|
|
case EOL_CODE: { token = END_OF_LINE; get_char(); break; }
|
|
case EOF_CODE: token = END_OF_FILE; break;
|
|
default: get_special(); break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles processing a line outside of subroutines
|
|
*/
|
|
void process_line() {
|
|
if ((token == ERROR) || (token == END_OF_FILE)) return;
|
|
|
|
switch (token) {
|
|
case RW_DEFINE:
|
|
handle_define();
|
|
break;
|
|
case RW_SUB:
|
|
handle_sub();
|
|
break;
|
|
case END_OF_LINE:
|
|
break;
|
|
default:
|
|
token = ERROR;
|
|
break;
|
|
}
|
|
|
|
if (token == END_OF_LINE) {
|
|
get_token();
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Interface methods */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Main compiler method
|
|
*/
|
|
bool Compile(const char *srcFilename, const char *destFilename) {
|
|
init_scanner(srcFilename);
|
|
init_output(destFilename);
|
|
|
|
get_token();
|
|
while ((token != END_OF_FILE) && (token != ERROR))
|
|
process_line();
|
|
|
|
if (token != ERROR) {
|
|
write_index();
|
|
}
|
|
|
|
quit_scanner();
|
|
|
|
if (token == ERROR)
|
|
printf("Error encountered on line %d\n", line_number);
|
|
else
|
|
printf("Compilation complete\n");
|
|
return token != ERROR;
|
|
}
|