mirror of
https://github.com/uffejakobsen/acme.git
synced 2025-08-06 07:25:04 +00:00
message now includes symbol name, fixed bug in type system (!for loop counters), added address() as alternative to addr(), fixed bug in report listing generator (CR in input caused additional blank lines in output). git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@63 4df02467-bbd4-4a76-a152-e7ce94205b78
562 lines
16 KiB
C
562 lines
16 KiB
C
// ACME - a crossassembler for producing 6502/65c02/65816 code.
|
|
// Copyright (C) 1998-2014 Marco Baye
|
|
// Have a look at "acme.c" for further info
|
|
//
|
|
// Input stuff
|
|
// 19 Nov 2014 Merged Johann Klasek's report listing generator patch
|
|
#include "input.h"
|
|
#include "config.h"
|
|
#include "alu.h"
|
|
#include "dynabuf.h"
|
|
#include "global.h" // FIXME - remove when no longer needed
|
|
#include "platform.h"
|
|
#include "section.h"
|
|
#include "tree.h"
|
|
|
|
|
|
// Constants
|
|
const char FILE_READBINARY[] = "rb";
|
|
#define CHAR_TAB (9) // Tab character
|
|
#define CHAR_LF (10) // line feed (in file)
|
|
// (10) // start of line (in high-level format)
|
|
#define CHAR_CR (13) // carriage return (in file)
|
|
// (13) // end of file (in high-level format)
|
|
#define CHAR_STATEMENT_DELIMITER ':'
|
|
#define CHAR_COMMENT_SEPARATOR ';'
|
|
// if the characters above are changed, don't forget to adjust ByteFlags[]!
|
|
|
|
// fake input structure (for error msgs before any real input is established)
|
|
static struct input outermost = {
|
|
"<none>", // file name
|
|
0, // line number
|
|
FALSE, // Faked file access, so no RAM read
|
|
INPUTSTATE_EOF, // state of input
|
|
{
|
|
NULL // RAM read pointer or file handle
|
|
}
|
|
};
|
|
|
|
|
|
// variables
|
|
struct input *Input_now = &outermost; // current input structure
|
|
|
|
|
|
// functions
|
|
|
|
// let current input point to start of file
|
|
void Input_new_file(const char *filename, FILE *fd)
|
|
{
|
|
Input_now->original_filename = filename;
|
|
Input_now->line_number = 1;
|
|
Input_now->source_is_ram = FALSE;
|
|
Input_now->state = INPUTSTATE_NORMAL;
|
|
Input_now->src.fd = fd;
|
|
}
|
|
|
|
|
|
// remember source code character for report generator
|
|
#define IF_WANTED_REPORT_SRCCHAR(c) do { if (report->fd) report_srcchar(c); } while(0)
|
|
static void report_srcchar(char new_char)
|
|
{
|
|
static char prev_char = '\0';
|
|
int ii;
|
|
char hex_address[5];
|
|
char hexdump[2 * REPORT_BINBUFSIZE + 2]; // +2 for '.' and terminator
|
|
|
|
// if input has changed, insert explanation
|
|
if (Input_now != report->last_input) {
|
|
fprintf(report->fd, "\n; ******** Source: %s\n", Input_now->original_filename);
|
|
report->last_input = Input_now;
|
|
report->asc_used = 0; // clear buffer
|
|
prev_char = '\0';
|
|
}
|
|
if (prev_char == '\n') {
|
|
// line start after line break detected and EOS processed,
|
|
// build report line:
|
|
// show line number...
|
|
fprintf(report->fd, "%6d ", Input_now->line_number - 1);
|
|
// prepare outbytes' start address
|
|
if (report->bin_used)
|
|
snprintf(hex_address, 5, "%04x", report->bin_address);
|
|
else
|
|
hex_address[0] = '\0';
|
|
// prepare outbytes
|
|
hexdump[0] = '\0';
|
|
for (ii = 0; ii < report->bin_used; ++ii)
|
|
sprintf(hexdump + 2 * ii, "%02x", (unsigned int) (unsigned char) (report->bin_buf[ii]));
|
|
// if binary buffer is full, overwrite last byte with "..."
|
|
if (report->bin_used == REPORT_BINBUFSIZE)
|
|
sprintf(hexdump + 2 * (REPORT_BINBUFSIZE - 1), "...");
|
|
// show address and bytes
|
|
fprintf(report->fd, "%-4s %-19s", hex_address, hexdump);
|
|
// at this point the output should be a multiple of 8 characters
|
|
// so far to preserve tabs of the source...
|
|
if (report->asc_used == REPORT_ASCBUFSIZE)
|
|
--report->asc_used;
|
|
report->asc_buf[report->asc_used] = '\0';
|
|
fprintf(report->fd, "%s\n", report->asc_buf); // show source line
|
|
report->asc_used = 0; // reset buffers
|
|
report->bin_used = 0;
|
|
}
|
|
if (new_char != '\n' && new_char != '\r') { // detect line break
|
|
if (report->asc_used < REPORT_ASCBUFSIZE)
|
|
report->asc_buf[report->asc_used++] = new_char;
|
|
}
|
|
prev_char = new_char;
|
|
}
|
|
|
|
|
|
// Deliver source code from current file (!) in shortened high-level format
|
|
static char get_processed_from_file(void)
|
|
{
|
|
int from_file = 0;
|
|
|
|
for (;;) {
|
|
switch (Input_now->state) {
|
|
case INPUTSTATE_NORMAL:
|
|
// fetch a fresh byte from the current source file
|
|
from_file = getc(Input_now->src.fd);
|
|
IF_WANTED_REPORT_SRCCHAR(from_file);
|
|
// now process it
|
|
/*FALLTHROUGH*/
|
|
case INPUTSTATE_AGAIN:
|
|
// Process the latest byte again. Of course, this only
|
|
// makes sense if the loop has executed at least once,
|
|
// otherwise the contents of from_file are undefined.
|
|
// If the source is changed so there is a possibility
|
|
// to enter INPUTSTATE_AGAIN mode without first having
|
|
// defined "from_file", trouble may arise...
|
|
Input_now->state = INPUTSTATE_NORMAL;
|
|
// EOF must be checked first because it cannot be used
|
|
// as an index into Byte_flags[]
|
|
if (from_file == EOF) {
|
|
// remember to send an end-of-file
|
|
Input_now->state = INPUTSTATE_EOF;
|
|
return CHAR_EOS; // end of statement
|
|
}
|
|
|
|
// check whether character is special one
|
|
// if not, everything's cool and froody, so return it
|
|
if ((BYTEFLAGS(from_file) & BYTEIS_SYNTAX) == 0)
|
|
return (char) from_file;
|
|
|
|
// check special characters ("0x00 TAB LF CR SPC :;}")
|
|
switch (from_file) {
|
|
case CHAR_TAB: // TAB character
|
|
case ' ':
|
|
// remember to skip all following blanks
|
|
Input_now->state = INPUTSTATE_SKIPBLANKS;
|
|
return ' ';
|
|
|
|
case CHAR_LF: // LF character
|
|
// remember to send a start-of-line
|
|
Input_now->state = INPUTSTATE_LF;
|
|
return CHAR_EOS; // end of statement
|
|
|
|
case CHAR_CR: // CR character
|
|
// remember to check CRLF + send start-of-line
|
|
Input_now->state = INPUTSTATE_CR;
|
|
return CHAR_EOS; // end of statement
|
|
|
|
case CHAR_EOB:
|
|
// remember to send an end-of-block
|
|
Input_now->state = INPUTSTATE_EOB;
|
|
return CHAR_EOS; // end of statement
|
|
|
|
case CHAR_STATEMENT_DELIMITER:
|
|
// just deliver an EOS instead
|
|
return CHAR_EOS; // end of statement
|
|
|
|
case CHAR_COMMENT_SEPARATOR:
|
|
// remember to skip remainder of line
|
|
Input_now->state = INPUTSTATE_COMMENT;
|
|
return CHAR_EOS; // end of statement
|
|
|
|
default:
|
|
// complain if byte is 0
|
|
Throw_error("Source file contains illegal character.");
|
|
return (char) from_file;
|
|
}
|
|
case INPUTSTATE_SKIPBLANKS:
|
|
// read until non-blank, then deliver that
|
|
do {
|
|
from_file = getc(Input_now->src.fd);
|
|
IF_WANTED_REPORT_SRCCHAR(from_file);
|
|
} while ((from_file == CHAR_TAB) || (from_file == ' '));
|
|
// re-process last byte
|
|
Input_now->state = INPUTSTATE_AGAIN;
|
|
break;
|
|
case INPUTSTATE_LF:
|
|
// return start-of-line, then continue in normal mode
|
|
Input_now->state = INPUTSTATE_NORMAL;
|
|
return CHAR_SOL; // new line
|
|
|
|
case INPUTSTATE_CR:
|
|
// return start-of-line, remember to check for LF
|
|
Input_now->state = INPUTSTATE_SKIPLF;
|
|
return CHAR_SOL; // new line
|
|
|
|
case INPUTSTATE_SKIPLF:
|
|
from_file = getc(Input_now->src.fd);
|
|
IF_WANTED_REPORT_SRCCHAR(from_file);
|
|
// if LF, ignore it and fetch another byte
|
|
// otherwise, process current byte
|
|
if (from_file == CHAR_LF)
|
|
Input_now->state = INPUTSTATE_NORMAL;
|
|
else
|
|
Input_now->state = INPUTSTATE_AGAIN;
|
|
break;
|
|
case INPUTSTATE_COMMENT:
|
|
// read until end-of-line or end-of-file
|
|
do {
|
|
from_file = getc(Input_now->src.fd);
|
|
IF_WANTED_REPORT_SRCCHAR(from_file);
|
|
} while ((from_file != EOF) && (from_file != CHAR_CR) && (from_file != CHAR_LF));
|
|
// re-process last byte
|
|
Input_now->state = INPUTSTATE_AGAIN;
|
|
break;
|
|
case INPUTSTATE_EOB:
|
|
// deliver EOB
|
|
Input_now->state = INPUTSTATE_NORMAL;
|
|
return CHAR_EOB; // end of block
|
|
|
|
case INPUTSTATE_EOF:
|
|
// deliver EOF
|
|
Input_now->state = INPUTSTATE_NORMAL;
|
|
return CHAR_EOF; // end of file
|
|
|
|
default:
|
|
Bug_found("StrangeInputMode", Input_now->state);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This function delivers the next byte from the currently active byte source
|
|
// in shortened high-level format. FIXME - use fn ptr?
|
|
// When inside quotes, use GetQuotedByte() instead!
|
|
char GetByte(void)
|
|
{
|
|
// for (;;) {
|
|
// If byte source is RAM, then no conversions are
|
|
// necessary, because in RAM the source already has
|
|
// high-level format
|
|
// Otherwise, the source is a file. This means we will call
|
|
// GetFormatted() which will do a shit load of conversions.
|
|
if (Input_now->source_is_ram)
|
|
GotByte = *(Input_now->src.ram_ptr++);
|
|
else
|
|
GotByte = get_processed_from_file();
|
|
// // if start-of-line was read, increment line counter and repeat
|
|
// if (GotByte != CHAR_SOL)
|
|
// return GotByte;
|
|
// Input_now->line_number++;
|
|
// }
|
|
if (GotByte == CHAR_SOL)
|
|
Input_now->line_number++;
|
|
return GotByte;
|
|
}
|
|
|
|
// This function delivers the next byte from the currently active byte source
|
|
// in un-shortened high-level format.
|
|
// This function complains if CHAR_EOS (end of statement) is read.
|
|
char GetQuotedByte(void)
|
|
{
|
|
int from_file; // must be an int to catch EOF
|
|
|
|
// if byte source is RAM, then no conversion is necessary,
|
|
// because in RAM the source already has high-level format
|
|
if (Input_now->source_is_ram) {
|
|
GotByte = *(Input_now->src.ram_ptr++);
|
|
// Otherwise, the source is a file.
|
|
} else {
|
|
// fetch a fresh byte from the current source file
|
|
from_file = getc(Input_now->src.fd);
|
|
IF_WANTED_REPORT_SRCCHAR(from_file);
|
|
switch (from_file) {
|
|
case EOF:
|
|
// remember to send an end-of-file
|
|
Input_now->state = INPUTSTATE_EOF;
|
|
GotByte = CHAR_EOS; // end of statement
|
|
break;
|
|
case CHAR_LF: // LF character
|
|
// remember to send a start-of-line
|
|
Input_now->state = INPUTSTATE_LF;
|
|
GotByte = CHAR_EOS; // end of statement
|
|
break;
|
|
case CHAR_CR: // CR character
|
|
// remember to check for CRLF + send a start-of-line
|
|
Input_now->state = INPUTSTATE_CR;
|
|
GotByte = CHAR_EOS; // end of statement
|
|
break;
|
|
default:
|
|
GotByte = from_file;
|
|
}
|
|
|
|
}
|
|
// now check for end of statement
|
|
if (GotByte == CHAR_EOS)
|
|
Throw_error("Quotes still open at end of line.");
|
|
return GotByte;
|
|
}
|
|
|
|
// Skip remainder of statement, for example on error
|
|
void Input_skip_remainder(void)
|
|
{
|
|
while (GotByte)
|
|
GetByte(); // Read characters until end-of-statement
|
|
}
|
|
|
|
// Ensure that the remainder of the current statement is empty, for example
|
|
// after mnemonics using implied addressing.
|
|
void Input_ensure_EOS(void) // Now GotByte = first char to test
|
|
{
|
|
SKIPSPACE();
|
|
if (GotByte) {
|
|
Throw_error("Garbage data at end of statement.");
|
|
Input_skip_remainder();
|
|
}
|
|
}
|
|
|
|
// Skip or store block (starting with next byte, so call directly after
|
|
// reading opening brace).
|
|
// If "Store" is TRUE, the block is read into GlobalDynaBuf, then a copy
|
|
// is made and a pointer to that is returned.
|
|
// If "Store" is FALSE, NULL is returned.
|
|
// After calling this function, GotByte holds '}'. Unless EOF was found first,
|
|
// but then a serious error would have been thrown.
|
|
// FIXME - use a struct block *ptr argument!
|
|
char *Input_skip_or_store_block(int store)
|
|
{
|
|
char byte;
|
|
int depth = 1; // to find matching block end
|
|
|
|
// prepare global dynamic buffer
|
|
DYNABUF_CLEAR(GlobalDynaBuf);
|
|
do {
|
|
byte = GetByte();
|
|
// if wanted, store
|
|
if (store)
|
|
DYNABUF_APPEND(GlobalDynaBuf, byte);
|
|
// now check for some special characters
|
|
switch (byte) {
|
|
case CHAR_EOF: // End-of-file in block? Sorry, no way.
|
|
Throw_serious_error(exception_no_right_brace);
|
|
|
|
case '"': // Quotes? Okay, read quoted stuff.
|
|
case '\'':
|
|
do {
|
|
GetQuotedByte();
|
|
// if wanted, store
|
|
if (store)
|
|
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
|
|
} while ((GotByte != CHAR_EOS) && (GotByte != byte));
|
|
break;
|
|
case CHAR_SOB:
|
|
++depth;
|
|
break;
|
|
case CHAR_EOB:
|
|
--depth;
|
|
break;
|
|
}
|
|
} while (depth);
|
|
// in case of skip, return now
|
|
if (!store)
|
|
return NULL;
|
|
// otherwise, prepare to return copy of block
|
|
// add EOF, just to make sure block is never read too far
|
|
DynaBuf_append(GlobalDynaBuf, CHAR_EOS);
|
|
DynaBuf_append(GlobalDynaBuf, CHAR_EOF);
|
|
// return pointer to copy
|
|
return DynaBuf_get_copy(GlobalDynaBuf);
|
|
}
|
|
|
|
// Read bytes and add to GlobalDynaBuf until the given terminator (or CHAR_EOS)
|
|
// is found. Act upon single and double quotes by entering (and leaving) quote
|
|
// mode as needed (So the terminator does not terminate when inside quotes).
|
|
void Input_until_terminator(char terminator)
|
|
{
|
|
char byte = GotByte;
|
|
|
|
for (;;) {
|
|
// Terminator? Exit. EndOfStatement? Exit.
|
|
if ((byte == terminator) || (byte == CHAR_EOS))
|
|
return;
|
|
// otherwise, append to GlobalDynaBuf and check for quotes
|
|
DYNABUF_APPEND(GlobalDynaBuf, byte);
|
|
if ((byte == '"') || (byte == '\'')) {
|
|
do {
|
|
// Okay, read quoted stuff.
|
|
GetQuotedByte(); // throws error on EOS
|
|
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
|
|
} while ((GotByte != CHAR_EOS) && (GotByte != byte));
|
|
// on error, exit now, before calling GetByte()
|
|
if (GotByte != byte)
|
|
return;
|
|
}
|
|
byte = GetByte();
|
|
}
|
|
}
|
|
|
|
// Append to GlobalDynaBuf while characters are legal for keywords.
|
|
// Throws "missing string" error if none.
|
|
// Returns number of characters added.
|
|
int Input_append_keyword_to_global_dynabuf(void)
|
|
{
|
|
int length = 0;
|
|
|
|
// add characters to buffer until an illegal one comes along
|
|
while (BYTEFLAGS(GotByte) & CONTS_KEYWORD) {
|
|
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
|
|
++length;
|
|
GetByte();
|
|
}
|
|
if (length == 0)
|
|
Throw_error(exception_missing_string);
|
|
return length;
|
|
}
|
|
|
|
// Check whether GotByte is LOCAL_PREFIX (default '.').
|
|
// If not, store global zone value.
|
|
// If yes, store current zone value and read next byte.
|
|
// Then jump to Input_read_keyword(), which returns length of keyword.
|
|
int Input_read_zone_and_keyword(zone_t *zone)
|
|
{
|
|
SKIPSPACE();
|
|
if (GotByte == LOCAL_PREFIX) {
|
|
GetByte();
|
|
*zone = Section_now->zone;
|
|
} else {
|
|
*zone = ZONE_GLOBAL;
|
|
}
|
|
return Input_read_keyword();
|
|
}
|
|
|
|
// Clear dynamic buffer, then append to it until an illegal (for a keyword)
|
|
// character is read. Zero-terminate the string. Return its length (without
|
|
// terminator).
|
|
// Zero lengths will produce a "missing string" error.
|
|
int Input_read_keyword(void)
|
|
{
|
|
int length;
|
|
|
|
DYNABUF_CLEAR(GlobalDynaBuf);
|
|
length = Input_append_keyword_to_global_dynabuf();
|
|
// add terminator to buffer (increments buffer's length counter)
|
|
DynaBuf_append(GlobalDynaBuf, '\0');
|
|
return length;
|
|
}
|
|
|
|
// Clear dynamic buffer, then append to it until an illegal (for a keyword)
|
|
// character is read. Zero-terminate the string, then convert to lower case.
|
|
// Return its length (without terminator).
|
|
// Zero lengths will produce a "missing string" error.
|
|
int Input_read_and_lower_keyword(void)
|
|
{
|
|
int length;
|
|
|
|
DYNABUF_CLEAR(GlobalDynaBuf);
|
|
length = Input_append_keyword_to_global_dynabuf();
|
|
// add terminator to buffer (increments buffer's length counter)
|
|
DynaBuf_append(GlobalDynaBuf, '\0');
|
|
DynaBuf_to_lower(GlobalDynaBuf, GlobalDynaBuf); // convert to lower case
|
|
return length;
|
|
}
|
|
|
|
// Try to read a file name. If "allow_library" is TRUE, library access by using
|
|
// <...> quoting is possible as well. The file name given in the assembler
|
|
// source code is converted from UNIX style to platform style.
|
|
// Returns whether error occurred (TRUE on error). Filename in GlobalDynaBuf.
|
|
// Errors are handled and reported, but caller should call
|
|
// Input_skip_remainder() then.
|
|
int Input_read_filename(int allow_library)
|
|
{
|
|
char *lib_prefix,
|
|
end_quote;
|
|
|
|
DYNABUF_CLEAR(GlobalDynaBuf);
|
|
SKIPSPACE();
|
|
// check for library access
|
|
if (GotByte == '<') {
|
|
// if library access forbidden, complain
|
|
if (allow_library == FALSE) {
|
|
Throw_error("Writing to library not supported.");
|
|
return TRUE;
|
|
}
|
|
|
|
// read platform's lib prefix
|
|
lib_prefix = PLATFORM_LIBPREFIX;
|
|
#ifndef NO_NEED_FOR_ENV_VAR
|
|
// if lib prefix not set, complain
|
|
if (lib_prefix == NULL) {
|
|
Throw_error("\"ACME\" environment variable not found.");
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
// copy lib path and set quoting char
|
|
DynaBuf_add_string(GlobalDynaBuf, lib_prefix);
|
|
end_quote = '>';
|
|
} else {
|
|
if (GotByte == '"') {
|
|
end_quote = '"';
|
|
} else {
|
|
Throw_error("File name quotes not found (\"\" or <>).");
|
|
return TRUE;
|
|
}
|
|
}
|
|
// read first character, complain if closing quote
|
|
if (GetQuotedByte() == end_quote) {
|
|
Throw_error("No file name given.");
|
|
return TRUE;
|
|
}
|
|
|
|
// read characters until closing quote (or EOS) is reached
|
|
// append platform-converted characters to current string
|
|
while ((GotByte != CHAR_EOS) && (GotByte != end_quote)) {
|
|
DYNABUF_APPEND(GlobalDynaBuf, PLATFORM_CONVERTPATHCHAR(GotByte));
|
|
GetQuotedByte();
|
|
}
|
|
// on error, return
|
|
if (GotByte == CHAR_EOS)
|
|
return TRUE;
|
|
|
|
GetByte(); // fetch next to forget closing quote
|
|
// terminate string
|
|
DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator
|
|
return FALSE; // no error
|
|
}
|
|
|
|
// Try to read a comma, skipping spaces before and after. Return TRUE if comma
|
|
// found, otherwise FALSE.
|
|
int Input_accept_comma(void)
|
|
{
|
|
SKIPSPACE();
|
|
if (GotByte != ',')
|
|
return FALSE;
|
|
|
|
NEXTANDSKIPSPACE();
|
|
return TRUE;
|
|
}
|
|
|
|
// read optional info about parameter length
|
|
int Input_get_force_bit(void)
|
|
{
|
|
char byte;
|
|
int force_bit = 0;
|
|
|
|
if (GotByte == '+') {
|
|
byte = GetByte();
|
|
if (byte == '1')
|
|
force_bit = MVALUE_FORCE08;
|
|
else if (byte == '2')
|
|
force_bit = MVALUE_FORCE16;
|
|
else if (byte == '3')
|
|
force_bit = MVALUE_FORCE24;
|
|
if (force_bit)
|
|
GetByte();
|
|
else
|
|
Throw_error("Illegal postfix.");
|
|
}
|
|
SKIPSPACE();
|
|
return force_bit;
|
|
}
|