mirror of
https://github.com/uffejakobsen/acme.git
synced 2024-11-15 17:05:33 +00:00
365306428b
git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@159 4df02467-bbd4-4a76-a152-e7ce94205b78
1381 lines
39 KiB
C
1381 lines
39 KiB
C
// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code.
|
|
// Copyright (C) 1998-2020 Marco Baye
|
|
// Have a look at "acme.c" for further info
|
|
//
|
|
// pseudo opcode stuff
|
|
#include "pseudoopcodes.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include "acme.h"
|
|
#include "config.h"
|
|
#include "cpu.h"
|
|
#include "alu.h"
|
|
#include "dynabuf.h"
|
|
#include "encoding.h"
|
|
#include "flow.h"
|
|
#include "input.h"
|
|
#include "macro.h"
|
|
#include "global.h"
|
|
#include "output.h"
|
|
#include "section.h"
|
|
#include "symbol.h"
|
|
#include "tree.h"
|
|
#include "typesystem.h"
|
|
|
|
|
|
// different ways to handle end-of-statement:
|
|
enum eos {
|
|
SKIP_REMAINDER, // skip remainder of line - (after errors)
|
|
ENSURE_EOS, // make sure there's nothing left in statement
|
|
PARSE_REMAINDER, // parse what's left
|
|
AT_EOS_ANYWAY // actually, same as PARSE_REMAINDER
|
|
};
|
|
|
|
// constants
|
|
static const char s_08[] = "08";
|
|
#define s_8 (s_08 + 1) // Yes, I know I'm sick
|
|
#define s_sl (s_asl + 1) // Yes, I know I'm sick
|
|
#define s_rl (s_brl + 1) // Yes, I know I'm sick
|
|
static const char exception_unknown_pseudo_opcode[] = "Unknown pseudo opcode.";
|
|
|
|
|
|
// variables
|
|
static struct ronode *pseudo_opcode_tree = NULL; // tree to hold pseudo opcodes
|
|
|
|
|
|
// this is not really a pseudo opcode, but similar enough to be put here:
|
|
// called when "* = EXPRESSION" is parsed, to set the program counter
|
|
void notreallypo_setpc(void) // GotByte is '*'
|
|
{
|
|
int segment_flags = 0;
|
|
struct number intresult;
|
|
|
|
// next non-space must be '='
|
|
NEXTANDSKIPSPACE();
|
|
if (GotByte != '=') {
|
|
Throw_error(exception_syntax);
|
|
goto fail;
|
|
}
|
|
|
|
GetByte();
|
|
ALU_defined_int(&intresult); // read new address
|
|
// check for modifiers
|
|
while (Input_accept_comma()) {
|
|
// parse modifier. if no keyword given, give up
|
|
if (Input_read_and_lower_keyword() == 0)
|
|
goto fail;
|
|
|
|
if (strcmp(GlobalDynaBuf->buffer, "overlay") == 0) {
|
|
segment_flags |= SEGMENT_FLAG_OVERLAY;
|
|
} else if (strcmp(GlobalDynaBuf->buffer, "invisible") == 0) {
|
|
segment_flags |= SEGMENT_FLAG_INVISIBLE;
|
|
/*TODO } else if (strcmp(GlobalDynaBuf->buffer, "limit") == 0) {
|
|
skip '='
|
|
read memory limit
|
|
} else if (strcmp(GlobalDynaBuf->buffer, "name") == 0) {
|
|
skip '='
|
|
read segment name (quoted string!) */
|
|
} else {
|
|
Throw_error("Unknown \"* =\" segment modifier.");
|
|
goto fail;
|
|
}
|
|
}
|
|
vcpu_set_pc(intresult.val.intval, segment_flags);
|
|
// TODO - allow block syntax, so it is possible to put data "somewhere else" and then return to old position
|
|
Input_ensure_EOS();
|
|
return;
|
|
|
|
fail:
|
|
Input_skip_remainder();
|
|
}
|
|
|
|
|
|
// define default value for empty memory ("!initmem" pseudo opcode)
|
|
static enum eos po_initmem(void)
|
|
{
|
|
struct number intresult;
|
|
|
|
// ignore in all passes but in first
|
|
if (!FIRST_PASS)
|
|
return SKIP_REMAINDER;
|
|
|
|
// get value
|
|
ALU_defined_int(&intresult);
|
|
if ((intresult.val.intval > 0xff) || (intresult.val.intval < -0x80))
|
|
Throw_error(exception_number_out_of_range);
|
|
if (output_initmem(intresult.val.intval & 0xff))
|
|
return SKIP_REMAINDER;
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// change output "encryption" ("!xor" pseudo opcode)
|
|
static enum eos po_xor(void)
|
|
{
|
|
char old_value;
|
|
intval_t change;
|
|
|
|
old_value = output_get_xor();
|
|
change = ALU_any_int();
|
|
if ((change > 0xff) || (change < -0x80)) {
|
|
Throw_error(exception_number_out_of_range);
|
|
change = 0;
|
|
}
|
|
output_set_xor(old_value ^ change);
|
|
// if there's a block, parse that and then restore old value!
|
|
if (Parse_optional_block())
|
|
output_set_xor(old_value);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// select output file and format ("!to" pseudo opcode)
|
|
static enum eos po_to(void)
|
|
{
|
|
// bugfix: first read filename, *then* check for first pass.
|
|
// if skipping right away, quoted colons might be misinterpreted as EOS
|
|
// FIXME - fix the skipping code to handle quotes! :)
|
|
// "!sl" has been fixed as well
|
|
|
|
// read filename to global dynamic buffer
|
|
// if no file name given, exit (complaining will have been done)
|
|
if (Input_read_filename(FALSE, NULL))
|
|
return SKIP_REMAINDER;
|
|
|
|
// only act upon this pseudo opcode in first pass
|
|
if (!FIRST_PASS)
|
|
return SKIP_REMAINDER;
|
|
|
|
if (outputfile_set_filename())
|
|
return SKIP_REMAINDER;
|
|
|
|
// select output format
|
|
// if no comma found, use default file format
|
|
if (Input_accept_comma() == FALSE) {
|
|
if (outputfile_prefer_cbm_format()) {
|
|
// output deprecation warning
|
|
Throw_warning("Used \"!to\" without file format indicator. Defaulting to \"cbm\".");
|
|
}
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
// parse output format name
|
|
// if no keyword given, give up
|
|
if (Input_read_and_lower_keyword() == 0)
|
|
return SKIP_REMAINDER;
|
|
|
|
if (outputfile_set_format()) {
|
|
// error occurred
|
|
Throw_error("Unknown output format.");
|
|
return SKIP_REMAINDER;
|
|
}
|
|
return ENSURE_EOS; // success
|
|
}
|
|
|
|
|
|
// helper function for !8, !16, !24 and !32 pseudo opcodes
|
|
static enum eos iterate(void (*fn)(intval_t))
|
|
{
|
|
do
|
|
fn(ALU_any_int());
|
|
while (Input_accept_comma());
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// Insert 8-bit values ("!08" / "!8" / "!by" / "!byte" pseudo opcode)
|
|
static enum eos po_byte(void)
|
|
{
|
|
return iterate(output_8);
|
|
}
|
|
|
|
|
|
// Insert 16-bit values ("!16" / "!wo" / "!word" pseudo opcode)
|
|
static enum eos po_16(void)
|
|
{
|
|
return iterate((CPU_state.type->flags & CPUFLAG_ISBIGENDIAN) ? output_be16 : output_le16);
|
|
}
|
|
// Insert 16-bit values big-endian ("!be16" pseudo opcode)
|
|
static enum eos po_be16(void)
|
|
{
|
|
return iterate(output_be16);
|
|
}
|
|
// Insert 16-bit values little-endian ("!le16" pseudo opcode)
|
|
static enum eos po_le16(void)
|
|
{
|
|
return iterate(output_le16);
|
|
}
|
|
|
|
|
|
// Insert 24-bit values ("!24" pseudo opcode)
|
|
static enum eos po_24(void)
|
|
{
|
|
return iterate((CPU_state.type->flags & CPUFLAG_ISBIGENDIAN) ? output_be24 : output_le24);
|
|
}
|
|
// Insert 24-bit values big-endian ("!be24" pseudo opcode)
|
|
static enum eos po_be24(void)
|
|
{
|
|
return iterate(output_be24);
|
|
}
|
|
// Insert 24-bit values little-endian ("!le24" pseudo opcode)
|
|
static enum eos po_le24(void)
|
|
{
|
|
return iterate(output_le24);
|
|
}
|
|
|
|
|
|
// Insert 32-bit values ("!32" pseudo opcode)
|
|
static enum eos po_32(void)
|
|
{
|
|
return iterate((CPU_state.type->flags & CPUFLAG_ISBIGENDIAN) ? output_be32 : output_le32);
|
|
}
|
|
// Insert 32-bit values big-endian ("!be32" pseudo opcode)
|
|
static enum eos po_be32(void)
|
|
{
|
|
return iterate(output_be32);
|
|
}
|
|
// Insert 32-bit values little-endian ("!le32" pseudo opcode)
|
|
static enum eos po_le32(void)
|
|
{
|
|
return iterate(output_le32);
|
|
}
|
|
|
|
|
|
// Insert bytes given as pairs of hex digits (helper for source code generators)
|
|
static enum eos po_hex(void) // now GotByte = illegal char
|
|
{
|
|
int digits = 0;
|
|
unsigned char byte = 0;
|
|
|
|
for (;;) {
|
|
if (digits == 2) {
|
|
Output_byte(byte);
|
|
digits = 0;
|
|
byte = 0;
|
|
}
|
|
if (GotByte >= '0' && GotByte <= '9') {
|
|
byte = (byte << 4) | (GotByte - '0');
|
|
++digits;
|
|
GetByte();
|
|
continue;
|
|
}
|
|
if (GotByte >= 'a' && GotByte <= 'f') {
|
|
byte = (byte << 4) | (GotByte - 'a' + 10);
|
|
++digits;
|
|
GetByte();
|
|
continue;
|
|
}
|
|
if (GotByte >= 'A' && GotByte <= 'F') {
|
|
byte = (byte << 4) | (GotByte - 'A' + 10);
|
|
++digits;
|
|
GetByte();
|
|
continue;
|
|
}
|
|
// if we're here, the current character is not a hex digit,
|
|
// which is only allowed outside of pairs:
|
|
if (digits == 1) {
|
|
Throw_error("Hex digits are not given in pairs.");
|
|
return SKIP_REMAINDER; // error exit
|
|
}
|
|
switch (GotByte) {
|
|
case ' ':
|
|
case '\t':
|
|
GetByte(); // spaces and tabs are ignored (maybe add commas, too?)
|
|
continue;
|
|
case CHAR_EOS:
|
|
return AT_EOS_ANYWAY; // normal exit
|
|
default:
|
|
Throw_error(exception_syntax); // all other characters are errors
|
|
return SKIP_REMAINDER; // error exit
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// "!cbm" pseudo opcode (now obsolete)
|
|
static enum eos obsolete_po_cbm(void)
|
|
{
|
|
Throw_error("\"!cbm\" is obsolete; use \"!ct pet\" instead.");
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
// read encoding table from file
|
|
static enum eos user_defined_encoding(FILE *stream)
|
|
{
|
|
char local_table[256],
|
|
*buffered_table = encoding_loaded_table;
|
|
const struct encoder *buffered_encoder = encoder_current;
|
|
|
|
if (stream) {
|
|
encoding_load_from_file(local_table, stream);
|
|
fclose(stream);
|
|
}
|
|
encoder_current = &encoder_file; // activate new encoding
|
|
encoding_loaded_table = local_table; // activate local table
|
|
// If there's a block, parse that and then restore old values
|
|
if (Parse_optional_block()) {
|
|
encoder_current = buffered_encoder;
|
|
} else {
|
|
// if there's *no* block, the table must be used from now on.
|
|
// copy the local table to the "outer" table
|
|
memcpy(buffered_table, local_table, 256);
|
|
}
|
|
// re-activate "outer" table (it might have been changed by memcpy())
|
|
encoding_loaded_table = buffered_table;
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
// use one of the pre-defined encodings (raw, pet, scr)
|
|
static enum eos predefined_encoding(void)
|
|
{
|
|
char local_table[256],
|
|
*buffered_table = encoding_loaded_table;
|
|
const struct encoder *buffered_encoder = encoder_current;
|
|
|
|
if (Input_read_and_lower_keyword()) {
|
|
const struct encoder *new_encoder = encoding_find();
|
|
|
|
if (new_encoder)
|
|
encoder_current = new_encoder; // activate new encoder
|
|
}
|
|
encoding_loaded_table = local_table; // activate local table
|
|
// if there's a block, parse that and then restore old values
|
|
if (Parse_optional_block())
|
|
encoder_current = buffered_encoder;
|
|
// re-activate "outer" table
|
|
encoding_loaded_table = buffered_table;
|
|
return ENSURE_EOS;
|
|
}
|
|
// set current encoding ("!convtab" pseudo opcode)
|
|
static enum eos po_convtab(void)
|
|
{
|
|
boolean uses_lib;
|
|
FILE *stream;
|
|
|
|
if ((GotByte == '<') || (GotByte == '"')) {
|
|
// if file name is missing, don't bother continuing
|
|
if (Input_read_filename(TRUE, &uses_lib))
|
|
return SKIP_REMAINDER;
|
|
|
|
stream = includepaths_open_ro(uses_lib);
|
|
return user_defined_encoding(stream);
|
|
} else {
|
|
return predefined_encoding();
|
|
}
|
|
}
|
|
// insert string(s)
|
|
static enum eos encode_string(const struct encoder *inner_encoder, char xor)
|
|
{
|
|
const struct encoder *outer_encoder = encoder_current; // buffer encoder
|
|
|
|
// make given encoder the current one (for ALU-parsed values)
|
|
encoder_current = inner_encoder;
|
|
do {
|
|
if (GotByte == '"') {
|
|
// read initial character
|
|
GetQuotedByte();
|
|
// send characters until closing quote is reached
|
|
while (GotByte && (GotByte != '"')) {
|
|
output_8(xor ^ encoding_encode_char(GotByte));
|
|
GetQuotedByte();
|
|
}
|
|
if (GotByte == CHAR_EOS)
|
|
return AT_EOS_ANYWAY;
|
|
|
|
// after closing quote, proceed with next char
|
|
GetByte();
|
|
} else {
|
|
// Parse value. No problems with single characters
|
|
// because the current encoding is
|
|
// temporarily set to the given one.
|
|
output_8(ALU_any_int());
|
|
}
|
|
} while (Input_accept_comma());
|
|
encoder_current = outer_encoder; // reactivate buffered encoder
|
|
return ENSURE_EOS;
|
|
}
|
|
// insert text string (default format)
|
|
static enum eos po_text(void)
|
|
{
|
|
return encode_string(encoder_current, 0);
|
|
}
|
|
// insert raw string
|
|
static enum eos po_raw(void)
|
|
{
|
|
return encode_string(&encoder_raw, 0);
|
|
}
|
|
// insert PetSCII string
|
|
static enum eos po_pet(void)
|
|
{
|
|
return encode_string(&encoder_pet, 0);
|
|
}
|
|
// insert screencode string
|
|
static enum eos po_scr(void)
|
|
{
|
|
return encode_string(&encoder_scr, 0);
|
|
}
|
|
// insert screencode string, XOR'd
|
|
static enum eos po_scrxor(void)
|
|
{
|
|
intval_t num = ALU_any_int();
|
|
|
|
if (Input_accept_comma() == FALSE) {
|
|
Throw_error(exception_syntax);
|
|
return SKIP_REMAINDER;
|
|
}
|
|
return encode_string(&encoder_scr, num);
|
|
}
|
|
|
|
// Include binary file ("!binary" pseudo opcode)
|
|
// FIXME - split this into "parser" and "worker" fn and move worker fn somewhere else.
|
|
static enum eos po_binary(void)
|
|
{
|
|
boolean uses_lib;
|
|
FILE *stream;
|
|
int byte;
|
|
struct number size,
|
|
skip;
|
|
|
|
size.val.intval = -1; // means "not given" => "until EOF"
|
|
skip.val.intval = 0;
|
|
|
|
// if file name is missing, don't bother continuing
|
|
if (Input_read_filename(TRUE, &uses_lib))
|
|
return SKIP_REMAINDER;
|
|
|
|
// try to open file
|
|
stream = includepaths_open_ro(uses_lib);
|
|
if (stream == NULL)
|
|
return SKIP_REMAINDER;
|
|
|
|
// read optional arguments
|
|
if (Input_accept_comma()) {
|
|
// any size given?
|
|
if ((GotByte != ',') && (GotByte != CHAR_EOS)) {
|
|
// then parse it
|
|
ALU_defined_int(&size);
|
|
if (size.val.intval < 0)
|
|
Throw_serious_error(exception_negative_size);
|
|
}
|
|
// more?
|
|
if (Input_accept_comma()) {
|
|
// any skip given?
|
|
if (GotByte != CHAR_EOS) {
|
|
// then parse it
|
|
ALU_defined_int(&skip);
|
|
}
|
|
}
|
|
}
|
|
// check whether including is a waste of time
|
|
// FIXME - future changes ("several-projects-at-once")
|
|
// may be incompatible with this!
|
|
if ((size.val.intval >= 0) && (pass.undefined_count || pass.error_count)) {
|
|
//if ((size.val.intval >= 0) && (pass.needvalue_count || pass.error_count)) { FIXME - use!
|
|
output_skip(size.val.intval); // really including is useless anyway
|
|
} else {
|
|
// really insert file
|
|
fseek(stream, skip.val.intval, SEEK_SET); // set read pointer
|
|
// if "size" non-negative, read "size" bytes.
|
|
// otherwise, read until EOF.
|
|
while (size.val.intval != 0) {
|
|
byte = getc(stream);
|
|
if (byte == EOF)
|
|
break;
|
|
Output_byte(byte);
|
|
--size.val.intval;
|
|
}
|
|
// if more should have been read, warn and add padding
|
|
if (size.val.intval > 0) {
|
|
Throw_warning("Padding with zeroes.");
|
|
do
|
|
Output_byte(0);
|
|
while (--size.val.intval);
|
|
}
|
|
}
|
|
fclose(stream);
|
|
// if verbose, produce some output
|
|
if (FIRST_PASS && (config.process_verbosity > 1)) {
|
|
int amount = vcpu_get_statement_size();
|
|
|
|
printf("Loaded %d (0x%04x) bytes from file offset %ld (0x%04lx).\n",
|
|
amount, amount, skip.val.intval, skip.val.intval);
|
|
}
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// reserve space by sending bytes of given value ("!fi" / "!fill" pseudo opcode)
|
|
static enum eos po_fill(void)
|
|
{
|
|
struct number sizeresult;
|
|
intval_t fill = FILLVALUE_FILL;
|
|
|
|
ALU_defined_int(&sizeresult); // FIXME - forbid addresses!
|
|
if (Input_accept_comma())
|
|
fill = ALU_any_int(); // FIXME - forbid addresses!
|
|
while (sizeresult.val.intval--)
|
|
output_8(fill);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// skip over some bytes in output without starting a new segment.
|
|
// in contrast to "*=*+AMOUNT", "!skip AMOUNT" does not start a new segment.
|
|
// (...and it will be needed in future for assemble-to-end-address)
|
|
static enum eos po_skip(void) // now GotByte = illegal char
|
|
{
|
|
struct number amount;
|
|
|
|
ALU_defined_int(&amount); // FIXME - forbid addresses!
|
|
if (amount.val.intval < 0)
|
|
Throw_serious_error(exception_negative_size); // TODO - allow this?
|
|
else
|
|
output_skip(amount.val.intval);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// insert byte until PC fits condition
|
|
static enum eos po_align(void)
|
|
{
|
|
// FIXME - read cpu state via function call!
|
|
struct number andresult,
|
|
equalresult;
|
|
intval_t fill,
|
|
test = CPU_state.pc.val.intval;
|
|
|
|
// make sure PC is defined.
|
|
if ((CPU_state.pc.flags & NUMBER_IS_DEFINED) == 0) {
|
|
Throw_error(exception_pc_undefined);
|
|
CPU_state.pc.flags |= NUMBER_IS_DEFINED; // do not complain again
|
|
return SKIP_REMAINDER;
|
|
}
|
|
|
|
// TODO:
|
|
// now: !align ANDVALUE, EQUALVALUE [,FILLVALUE]
|
|
// new: !align BLOCKSIZE
|
|
// ...where block size must be a power of two
|
|
ALU_defined_int(&andresult); // FIXME - forbid addresses!
|
|
if (!Input_accept_comma())
|
|
Throw_error(exception_syntax);
|
|
ALU_defined_int(&equalresult); // ...allow addresses (unlikely, but possible)
|
|
if (Input_accept_comma())
|
|
fill = ALU_any_int();
|
|
else
|
|
fill = CPU_state.type->default_align_value;
|
|
while ((test++ & andresult.val.intval) != equalresult.val.intval)
|
|
output_8(fill);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
static const char Error_old_offset_assembly[] =
|
|
"\"!pseudopc/!realpc\" is obsolete; use \"!pseudopc {}\" instead.";
|
|
// start offset assembly
|
|
// FIXME - split in two parts and move backend to output.c?
|
|
// TODO - maybe add a label argument to assign the block size afterwards (for assemble-to-end-address) (or add another pseudo opcode)
|
|
static enum eos po_pseudopc(void)
|
|
{
|
|
// FIXME - read pc using a function call!
|
|
struct number new_pc_result;
|
|
intval_t new_offset;
|
|
int outer_flags = CPU_state.pc.flags;
|
|
|
|
// set new
|
|
ALU_defined_int(&new_pc_result); // FIXME - allow for undefined! (complaining about non-addresses would be logical, but annoying)
|
|
new_offset = (new_pc_result.val.intval - CPU_state.pc.val.intval) & 0xffff;
|
|
CPU_state.pc.val.intval = new_pc_result.val.intval;
|
|
CPU_state.pc.flags |= NUMBER_IS_DEFINED; // FIXME - remove when allowing undefined!
|
|
// TODO - accept ', name = "section name"'
|
|
// if there's a block, parse that and then restore old value!
|
|
if (Parse_optional_block()) {
|
|
// restore old
|
|
CPU_state.pc.val.intval = (CPU_state.pc.val.intval - new_offset) & 0xffff;
|
|
CPU_state.pc.flags = outer_flags;
|
|
} else {
|
|
// not using a block is no longer allowed
|
|
Throw_error(Error_old_offset_assembly);
|
|
}
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// "!realpc" pseudo opcode (now obsolete)
|
|
static enum eos obsolete_po_realpc(void)
|
|
{
|
|
Throw_error(Error_old_offset_assembly);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// select CPU ("!cpu" pseudo opcode)
|
|
static enum eos po_cpu(void)
|
|
{
|
|
const struct cpu_type *cpu_buffer = CPU_state.type; // remember current cpu
|
|
const struct cpu_type *new_cpu_type;
|
|
|
|
if (Input_read_and_lower_keyword()) {
|
|
new_cpu_type = cputype_find();
|
|
if (new_cpu_type)
|
|
CPU_state.type = new_cpu_type; // activate new cpu type
|
|
else
|
|
Throw_error("Unknown processor.");
|
|
}
|
|
// if there's a block, parse that and then restore old value
|
|
if (Parse_optional_block())
|
|
CPU_state.type = cpu_buffer;
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// set register length, block-wise if needed.
|
|
static enum eos set_register_length(boolean *var, boolean make_long)
|
|
{
|
|
int old_size = *var;
|
|
|
|
// set new register length (or complain - whichever is more fitting)
|
|
vcpu_check_and_set_reg_length(var, make_long);
|
|
// if there's a block, parse that and then restore old value!
|
|
if (Parse_optional_block())
|
|
vcpu_check_and_set_reg_length(var, old_size); // restore old length
|
|
return ENSURE_EOS;
|
|
}
|
|
// switch to long accumulator ("!al" pseudo opcode)
|
|
static enum eos po_al(void)
|
|
{
|
|
return set_register_length(&CPU_state.a_is_long, TRUE);
|
|
}
|
|
// switch to short accumulator ("!as" pseudo opcode)
|
|
static enum eos po_as(void)
|
|
{
|
|
return set_register_length(&CPU_state.a_is_long, FALSE);
|
|
}
|
|
// switch to long index registers ("!rl" pseudo opcode)
|
|
static enum eos po_rl(void)
|
|
{
|
|
return set_register_length(&CPU_state.xy_are_long, TRUE);
|
|
}
|
|
// switch to short index registers ("!rs" pseudo opcode)
|
|
static enum eos po_rs(void)
|
|
{
|
|
return set_register_length(&CPU_state.xy_are_long, FALSE);
|
|
}
|
|
|
|
|
|
// force explicit label definitions to set "address" flag ("!addr"). Has to be re-entrant.
|
|
static enum eos po_address(void) // now GotByte = illegal char
|
|
{
|
|
SKIPSPACE();
|
|
if (GotByte == CHAR_SOB) {
|
|
typesystem_force_address_block();
|
|
return ENSURE_EOS;
|
|
}
|
|
typesystem_force_address_statement(TRUE);
|
|
return PARSE_REMAINDER;
|
|
}
|
|
|
|
|
|
#if 0
|
|
// enumerate constants ("!enum")
|
|
static enum eos po_enum(void) // now GotByte = illegal char
|
|
{
|
|
struct number step;
|
|
|
|
step.val.intval = 1;
|
|
ALU_defined_int(&step);
|
|
Throw_serious_error("Not yet"); // FIXME
|
|
return ENSURE_EOS;
|
|
}
|
|
#endif
|
|
|
|
|
|
// (re)set symbol
|
|
static enum eos po_set(void) // now GotByte = illegal char
|
|
{
|
|
struct object result;
|
|
int force_bit;
|
|
struct symbol *symbol;
|
|
scope_t scope;
|
|
|
|
if (Input_read_scope_and_keyword(&scope) == 0) // skips spaces before
|
|
// now GotByte = illegal char
|
|
return SKIP_REMAINDER;
|
|
|
|
force_bit = Input_get_force_bit(); // skips spaces after
|
|
symbol = symbol_find(scope, force_bit);
|
|
if (GotByte != '=') {
|
|
Throw_error(exception_syntax);
|
|
return SKIP_REMAINDER;
|
|
}
|
|
|
|
// symbol = parsed value
|
|
GetByte(); // proceed with next char
|
|
ALU_any_result(&result);
|
|
// clear symbol's force bits and set new ones
|
|
// (but only do this for numbers!)
|
|
if (((symbol->result.type == &type_int) || (symbol->result.type == &type_float))
|
|
&& ((result.type == &type_int) || (result.type == &type_float))) {
|
|
symbol->result.u.number.flags &= ~(NUMBER_FORCEBITS | NUMBER_FITS_BYTE);
|
|
if (force_bit) {
|
|
symbol->result.u.number.flags |= force_bit;
|
|
result.u.number.flags &= ~(NUMBER_FORCEBITS | NUMBER_FITS_BYTE);
|
|
}
|
|
}
|
|
// FIXME - take a good look at the flags handling above and in the fn called below and clean this up!
|
|
symbol_set_object(symbol, &result, TRUE);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// set file name for symbol list
|
|
static enum eos po_symbollist(void)
|
|
{
|
|
// bugfix: first read filename, *then* check for first pass.
|
|
// if skipping right away, quoted colons might be misinterpreted as EOS
|
|
// FIXME - why not just fix the skipping code to handle quotes? :)
|
|
// "!to" has been fixed as well
|
|
|
|
// read filename to global dynamic buffer
|
|
// if no file name given, exit (complaining will have been done)
|
|
if (Input_read_filename(FALSE, NULL))
|
|
return SKIP_REMAINDER;
|
|
|
|
// only process this pseudo opcode in first pass
|
|
if (!FIRST_PASS)
|
|
return SKIP_REMAINDER;
|
|
|
|
// if symbol list file name already set, complain and exit
|
|
if (symbollist_filename) {
|
|
Throw_warning("Symbol list file name already chosen.");
|
|
return SKIP_REMAINDER;
|
|
}
|
|
|
|
// get malloc'd copy of filename
|
|
symbollist_filename = DynaBuf_get_copy(GlobalDynaBuf);
|
|
// ensure there's no garbage at end of line
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// switch to new zone ("!zone" or "!zn"). has to be re-entrant.
|
|
static enum eos po_zone(void)
|
|
{
|
|
struct section entry_values; // buffer for outer zone
|
|
char *new_title;
|
|
int allocated;
|
|
|
|
// remember everything about current structure
|
|
entry_values = *section_now;
|
|
// set default values in case there is no valid title
|
|
new_title = s_untitled;
|
|
allocated = FALSE;
|
|
// Check whether a zone title is given. If yes and it can be read,
|
|
// get copy, remember pointer and remember to free it later on.
|
|
if (BYTE_CONTINUES_KEYWORD(GotByte)) {
|
|
// Because we know of one character for sure,
|
|
// there's no need to check the return value.
|
|
Input_read_keyword();
|
|
new_title = DynaBuf_get_copy(GlobalDynaBuf);
|
|
allocated = TRUE;
|
|
}
|
|
// setup new section
|
|
// section type is "subzone", just in case a block follows
|
|
section_new(section_now, "Subzone", new_title, allocated);
|
|
if (Parse_optional_block()) {
|
|
// Block has been parsed, so it was a SUBzone.
|
|
section_finalize(section_now); // end inner zone
|
|
*section_now = entry_values; // restore entry values
|
|
} else {
|
|
// no block found, so it's a normal zone change
|
|
section_finalize(&entry_values); // end outer zone
|
|
section_now->type = s_Zone; // change type to "Zone"
|
|
}
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
// "!subzone" or "!sz" pseudo opcode (now obsolete)
|
|
static enum eos obsolete_po_subzone(void)
|
|
{
|
|
Throw_error("\"!subzone {}\" is obsolete; use \"!zone {}\" instead.");
|
|
// call "!zone" instead
|
|
return po_zone();
|
|
}
|
|
|
|
// include source file ("!source" or "!src"). has to be re-entrant.
|
|
static enum eos po_source(void) // now GotByte = illegal char
|
|
{
|
|
boolean uses_lib;
|
|
FILE *stream;
|
|
char local_gotbyte;
|
|
struct input new_input,
|
|
*outer_input;
|
|
|
|
// enter new nesting level
|
|
// quit program if recursion too deep
|
|
if (--source_recursions_left < 0)
|
|
Throw_serious_error("Too deeply nested. Recursive \"!source\"?");
|
|
// read file name. quit function on error
|
|
if (Input_read_filename(TRUE, &uses_lib))
|
|
return SKIP_REMAINDER;
|
|
|
|
// if file could be opened, parse it. otherwise, complain
|
|
stream = includepaths_open_ro(uses_lib);
|
|
if (stream) {
|
|
#ifdef __GNUC__
|
|
char filename[GlobalDynaBuf->size]; // GCC can do this
|
|
#else
|
|
char *filename = safe_malloc(GlobalDynaBuf->size); // VS can not
|
|
#endif
|
|
|
|
strcpy(filename, GLOBALDYNABUF_CURRENT);
|
|
outer_input = Input_now; // remember old input
|
|
local_gotbyte = GotByte; // CAUTION - ugly kluge
|
|
Input_now = &new_input; // activate new input
|
|
flow_parse_and_close_file(stream, filename);
|
|
Input_now = outer_input; // restore previous input
|
|
GotByte = local_gotbyte; // CAUTION - ugly kluge
|
|
#ifndef __GNUC__
|
|
free(filename); // GCC auto-frees
|
|
#endif
|
|
}
|
|
// leave nesting level
|
|
++source_recursions_left;
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
static boolean check_ifdef_condition(void)
|
|
{
|
|
scope_t scope;
|
|
struct rwnode *node;
|
|
struct symbol *symbol;
|
|
|
|
// read symbol name
|
|
if (Input_read_scope_and_keyword(&scope) == 0) // skips spaces before
|
|
return FALSE; // there was an error, it has been reported, so return value is more or less meaningless anway
|
|
|
|
// look for it
|
|
Tree_hard_scan(&node, symbols_forest, scope, FALSE);
|
|
if (!node)
|
|
return FALSE; // not found -> no, not defined
|
|
|
|
symbol = (struct symbol *) node->body;
|
|
// in first pass, count usage
|
|
if (FIRST_PASS)
|
|
symbol->usage++;
|
|
return symbol->result.type->is_defined(&symbol->result);
|
|
}
|
|
// new if/ifdef/ifndef/else function, to be able to do ELSE IF
|
|
enum ifmode {
|
|
IFMODE_IF, // parse expression, then block
|
|
IFMODE_IFDEF, // check symbol, then parse block or line
|
|
IFMODE_IFNDEF, // check symbol, then parse block or line
|
|
IFMODE_ELSE // unconditional last block
|
|
};
|
|
// new function for if/ifdef/ifndef/else. has to be re-entrant.
|
|
static enum eos ifelse(enum ifmode mode)
|
|
{
|
|
boolean nothing_done = TRUE; // once a block gets executed, this becomes FALSE, so all others will be skipped even if condition met
|
|
boolean condition_met; // condition result for next block
|
|
struct number ifresult;
|
|
|
|
for (;;) {
|
|
// check condition according to mode
|
|
switch (mode) {
|
|
case IFMODE_IF:
|
|
ALU_defined_int(&ifresult);
|
|
condition_met = !!ifresult.val.intval;
|
|
if (GotByte != CHAR_SOB)
|
|
Throw_serious_error(exception_no_left_brace);
|
|
break;
|
|
case IFMODE_IFDEF:
|
|
condition_met = check_ifdef_condition();
|
|
break;
|
|
case IFMODE_IFNDEF:
|
|
condition_met = !check_ifdef_condition();
|
|
break;
|
|
case IFMODE_ELSE:
|
|
condition_met = TRUE;
|
|
break;
|
|
default:
|
|
Bug_found("IllegalIfMode", mode); // FIXME - put in docs!
|
|
condition_met = TRUE; // inhibit compiler warning ;)
|
|
}
|
|
SKIPSPACE();
|
|
// execute this block?
|
|
if (condition_met && nothing_done) {
|
|
nothing_done = FALSE; // all further ones will be skipped, even if conditions meet
|
|
if (GotByte == CHAR_SOB) {
|
|
Parse_until_eob_or_eof(); // parse block
|
|
// if block isn't correctly terminated, complain and exit
|
|
if (GotByte != CHAR_EOB)
|
|
Throw_serious_error(exception_no_right_brace);
|
|
} else {
|
|
return PARSE_REMAINDER; // parse line (only for ifdef/ifndef)
|
|
}
|
|
} else {
|
|
if (GotByte == CHAR_SOB) {
|
|
Input_skip_or_store_block(FALSE); // skip block
|
|
} else {
|
|
return SKIP_REMAINDER; // skip line (only for ifdef/ifndef)
|
|
}
|
|
}
|
|
// now GotByte = '}'
|
|
NEXTANDSKIPSPACE();
|
|
// after ELSE {} it's all over. it must be.
|
|
if (mode == IFMODE_ELSE) {
|
|
// we could just return ENSURE_EOS, but checking here allows for better error message
|
|
if (GotByte != CHAR_EOS)
|
|
Throw_error("Expected end-of-statement after ELSE block"); // FIXME - put in docs!
|
|
return SKIP_REMAINDER; // normal exit after ELSE {...}
|
|
}
|
|
|
|
// anything more?
|
|
if (GotByte == CHAR_EOS)
|
|
return AT_EOS_ANYWAY; // normal exit if there is no ELSE {...} block
|
|
|
|
// read keyword (expected to be "else")
|
|
if (Input_read_and_lower_keyword() == 0)
|
|
return SKIP_REMAINDER; // "missing string error" -> ignore rest of line
|
|
|
|
// make sure it's "else"
|
|
if (strcmp(GlobalDynaBuf->buffer, "else")) {
|
|
Throw_error("Expected ELSE or end-of-statement"); // FIXME - put in docs!
|
|
return SKIP_REMAINDER; // an error has been reported, so ignore rest of line
|
|
}
|
|
// anything more?
|
|
SKIPSPACE();
|
|
if (GotByte == CHAR_SOB) {
|
|
// ELSE {...} -> one last round
|
|
mode = IFMODE_ELSE;
|
|
continue;
|
|
}
|
|
|
|
// read keyword (expected to be if/ifdef/ifndef)
|
|
if (Input_read_and_lower_keyword() == 0)
|
|
return SKIP_REMAINDER; // "missing string error" -> ignore rest of line
|
|
|
|
// which one is it?
|
|
if (strcmp(GlobalDynaBuf->buffer, "if") == 0) {
|
|
mode = IFMODE_IF;
|
|
} else if (strcmp(GlobalDynaBuf->buffer, "ifdef") == 0) {
|
|
mode = IFMODE_IFDEF;
|
|
} else if (strcmp(GlobalDynaBuf->buffer, "ifndef") == 0) {
|
|
mode = IFMODE_IFNDEF;
|
|
} else {
|
|
Throw_error("After ELSE, expected block or IF/IFDEF/IFNDEF"); // FIXME - put in docs!
|
|
return SKIP_REMAINDER; // an error has been reported, so ignore rest of line
|
|
}
|
|
}
|
|
}
|
|
|
|
// conditional assembly ("!if"). has to be re-entrant.
|
|
static enum eos po_if(void) // now GotByte = illegal char
|
|
{
|
|
struct number cond_result;
|
|
|
|
if (config.test_new_features)
|
|
return ifelse(IFMODE_IF);
|
|
|
|
ALU_defined_int(&cond_result);
|
|
if (GotByte != CHAR_SOB)
|
|
Throw_serious_error(exception_no_left_brace);
|
|
flow_parse_block_else_block(!!cond_result.val.intval);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// conditional assembly ("!ifdef" and "!ifndef"). has to be re-entrant.
|
|
// TODO - remove when new ifelse function is finished
|
|
static enum eos ifdef_ifndef(boolean invert) // now GotByte = illegal char
|
|
{
|
|
struct rwnode *node;
|
|
struct symbol *symbol;
|
|
scope_t scope;
|
|
int defined = FALSE;
|
|
|
|
if (Input_read_scope_and_keyword(&scope) == 0) // skips spaces before
|
|
return SKIP_REMAINDER;
|
|
|
|
Tree_hard_scan(&node, symbols_forest, scope, FALSE);
|
|
if (node) {
|
|
symbol = (struct symbol *) node->body;
|
|
// in first pass, count usage
|
|
if (FIRST_PASS)
|
|
symbol->usage++;
|
|
if (symbol->result.u.number.flags & NUMBER_IS_DEFINED)
|
|
defined = TRUE;
|
|
}
|
|
SKIPSPACE();
|
|
// if "ifndef", invert condition
|
|
if (invert)
|
|
defined = !defined;
|
|
if (GotByte != CHAR_SOB)
|
|
return defined ? PARSE_REMAINDER : SKIP_REMAINDER;
|
|
|
|
flow_parse_block_else_block(defined);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// conditional assembly ("!ifdef"). has to be re-entrant.
|
|
static enum eos po_ifdef(void) // now GotByte = illegal char
|
|
{
|
|
if (config.test_new_features)
|
|
return ifelse(IFMODE_IFDEF);
|
|
|
|
return ifdef_ifndef(FALSE);
|
|
}
|
|
|
|
|
|
// conditional assembly ("!ifndef"). has to be re-entrant.
|
|
static enum eos po_ifndef(void) // now GotByte = illegal char
|
|
{
|
|
if (config.test_new_features)
|
|
return ifelse(IFMODE_IFNDEF);
|
|
|
|
return ifdef_ifndef(TRUE);
|
|
}
|
|
|
|
|
|
// looping assembly ("!for"). has to be re-entrant.
|
|
// old syntax: !for VAR, END { BLOCK } VAR counts from 1 to END
|
|
// new syntax: !for VAR, START, END { BLOCK } VAR counts from START to END
|
|
static enum eos po_for(void) // now GotByte = illegal char
|
|
{
|
|
scope_t scope;
|
|
int force_bit;
|
|
struct number intresult;
|
|
struct for_loop loop;
|
|
|
|
if (Input_read_scope_and_keyword(&scope) == 0) // skips spaces before
|
|
return SKIP_REMAINDER;
|
|
|
|
// now GotByte = illegal char
|
|
force_bit = Input_get_force_bit(); // skips spaces after
|
|
loop.symbol = symbol_find(scope, force_bit);
|
|
if (!Input_accept_comma()) {
|
|
Throw_error(exception_syntax);
|
|
return SKIP_REMAINDER;
|
|
}
|
|
|
|
ALU_defined_int(&intresult); // read first argument
|
|
loop.counter.addr_refs = intresult.addr_refs;
|
|
if (Input_accept_comma()) {
|
|
loop.use_old_algo = FALSE; // new format - yay!
|
|
if (!config.warn_on_old_for)
|
|
Throw_first_pass_warning("Found new \"!for\" syntax.");
|
|
loop.counter.first = intresult.val.intval; // use first argument
|
|
ALU_defined_int(&intresult); // read second argument
|
|
loop.counter.last = intresult.val.intval; // use second argument
|
|
// compare addr_ref counts and complain if not equal!
|
|
if (config.warn_on_type_mismatch
|
|
&& (intresult.addr_refs != loop.counter.addr_refs)) {
|
|
Throw_first_pass_warning("Wrong type for loop's END value - must match type of START value.");
|
|
}
|
|
loop.counter.increment = (loop.counter.last < loop.counter.first) ? -1 : 1;
|
|
} else {
|
|
loop.use_old_algo = TRUE; // old format - booo!
|
|
if (config.warn_on_old_for)
|
|
Throw_first_pass_warning("Found old \"!for\" syntax.");
|
|
if (intresult.val.intval < 0)
|
|
Throw_serious_error("Loop count is negative.");
|
|
loop.counter.first = 0; // CAUTION - old algo pre-increments and therefore starts with 1!
|
|
loop.counter.last = intresult.val.intval; // use given argument
|
|
loop.counter.increment = 1;
|
|
}
|
|
if (GotByte != CHAR_SOB)
|
|
Throw_serious_error(exception_no_left_brace);
|
|
|
|
// remember line number of loop pseudo opcode
|
|
loop.block.start = Input_now->line_number;
|
|
// read loop body into DynaBuf and get copy
|
|
loop.block.body = Input_skip_or_store_block(TRUE); // changes line number!
|
|
|
|
flow_forloop(&loop);
|
|
// free memory
|
|
free(loop.block.body);
|
|
|
|
// GotByte of OuterInput would be '}' (if it would still exist)
|
|
GetByte(); // fetch next byte
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// looping assembly ("!do"). has to be re-entrant.
|
|
static enum eos po_do(void) // now GotByte = illegal char
|
|
{
|
|
struct do_while loop;
|
|
|
|
// read head condition to buffer
|
|
SKIPSPACE();
|
|
flow_store_doloop_condition(&loop.head_cond, CHAR_SOB); // must be freed!
|
|
if (GotByte != CHAR_SOB)
|
|
Throw_serious_error(exception_no_left_brace);
|
|
// remember line number of loop body,
|
|
// then read block and get copy
|
|
loop.block.start = Input_now->line_number;
|
|
// reading block changes line number!
|
|
loop.block.body = Input_skip_or_store_block(TRUE); // must be freed!
|
|
// now GotByte = '}'
|
|
NEXTANDSKIPSPACE(); // now GotByte = first non-blank char after block
|
|
// read tail condition to buffer
|
|
flow_store_doloop_condition(&loop.tail_cond, CHAR_EOS); // must be freed!
|
|
// now GotByte = CHAR_EOS
|
|
flow_do_while(&loop);
|
|
// free memory
|
|
free(loop.head_cond.body);
|
|
free(loop.block.body);
|
|
free(loop.tail_cond.body);
|
|
return AT_EOS_ANYWAY;
|
|
}
|
|
|
|
|
|
// looping assembly ("!while", alternative for people used to c-style loops). has to be re-entrant.
|
|
static enum eos po_while(void) // now GotByte = illegal char
|
|
{
|
|
struct do_while loop;
|
|
|
|
if (!config.test_new_features) {
|
|
Throw_error(exception_unknown_pseudo_opcode);
|
|
return SKIP_REMAINDER;
|
|
}
|
|
// read condition to buffer
|
|
SKIPSPACE();
|
|
flow_store_while_condition(&loop.head_cond); // must be freed!
|
|
if (GotByte != CHAR_SOB)
|
|
Throw_serious_error(exception_no_left_brace);
|
|
// remember line number of loop body,
|
|
// then read block and get copy
|
|
loop.block.start = Input_now->line_number;
|
|
// reading block changes line number!
|
|
loop.block.body = Input_skip_or_store_block(TRUE); // must be freed!
|
|
// clear tail condition
|
|
loop.tail_cond.body = NULL;
|
|
flow_do_while(&loop);
|
|
// free memory
|
|
free(loop.head_cond.body);
|
|
free(loop.block.body);
|
|
// GotByte of OuterInput would be '}' (if it would still exist)
|
|
GetByte(); // fetch next byte
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// macro definition ("!macro").
|
|
static enum eos po_macro(void) // now GotByte = illegal char
|
|
{
|
|
// in first pass, parse. In all other passes, skip.
|
|
if (FIRST_PASS) {
|
|
Macro_parse_definition(); // now GotByte = '}'
|
|
} else {
|
|
// skip until CHAR_SOB ('{') is found.
|
|
// no need to check for end-of-statement, because such an
|
|
// error would already have been detected in first pass.
|
|
// for the same reason, there is no need to check for quotes.
|
|
while (GotByte != CHAR_SOB)
|
|
GetByte();
|
|
Input_skip_or_store_block(FALSE); // now GotByte = '}'
|
|
}
|
|
GetByte(); // Proceed with next character
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
// constants
|
|
#define USERMSG_DYNABUF_INITIALSIZE 80
|
|
|
|
|
|
// variables
|
|
static struct dynabuf *user_message; // dynamic buffer (!warn/error/serious)
|
|
|
|
|
|
// helper function to show user-defined messages
|
|
static enum eos throw_string(const char prefix[], void (*fn)(const char *))
|
|
{
|
|
struct object object;
|
|
|
|
DYNABUF_CLEAR(user_message);
|
|
DynaBuf_add_string(user_message, prefix);
|
|
do {
|
|
if (GotByte == '"') {
|
|
// parse string
|
|
GetQuotedByte(); // read initial character
|
|
// send characters until closing quote is reached
|
|
while (GotByte && (GotByte != '"')) {
|
|
DYNABUF_APPEND(user_message, GotByte);
|
|
GetQuotedByte();
|
|
}
|
|
if (GotByte == CHAR_EOS)
|
|
return AT_EOS_ANYWAY;
|
|
// after closing quote, proceed with next char
|
|
GetByte();
|
|
} else {
|
|
// parse value
|
|
ALU_any_result(&object);
|
|
object.type->print(&object, user_message);
|
|
}
|
|
} while (Input_accept_comma());
|
|
DynaBuf_append(user_message, '\0');
|
|
fn(user_message->buffer);
|
|
return ENSURE_EOS;
|
|
}
|
|
|
|
|
|
#if 0
|
|
// show debug data given in source code
|
|
static enum eos po_debug(void)
|
|
{
|
|
// FIXME - make debug output depend on some cli switch
|
|
return throw_string("!debug: ", throw_message);
|
|
}
|
|
// show info given in source code
|
|
static enum eos po_info(void)
|
|
{
|
|
return throw_string("!info: ", throw_message);
|
|
}
|
|
#endif
|
|
|
|
|
|
// throw warning as given in source code
|
|
static enum eos po_warn(void)
|
|
{
|
|
return throw_string("!warn: ", Throw_warning);
|
|
|
|
}
|
|
|
|
|
|
// throw error as given in source code
|
|
static enum eos po_error(void)
|
|
{
|
|
return throw_string("!error: ", Throw_error);
|
|
}
|
|
|
|
|
|
// throw serious error as given in source code
|
|
static enum eos po_serious(void)
|
|
{
|
|
return throw_string("!serious: ", Throw_serious_error);
|
|
}
|
|
|
|
|
|
// end of source file ("!endoffile" or "!eof")
|
|
static enum eos po_endoffile(void)
|
|
{
|
|
// well, it doesn't end right here and now, but at end-of-line! :-)
|
|
Input_ensure_EOS();
|
|
Input_now->state = INPUTSTATE_EOF;
|
|
return AT_EOS_ANYWAY;
|
|
}
|
|
|
|
// pseudo opcode table
|
|
static struct ronode pseudo_opcode_list[] = {
|
|
PREDEFNODE("initmem", po_initmem),
|
|
PREDEFNODE("xor", po_xor),
|
|
PREDEFNODE("to", po_to),
|
|
PREDEFNODE(s_8, po_byte),
|
|
PREDEFNODE(s_08, po_byte),
|
|
PREDEFNODE("by", po_byte),
|
|
PREDEFNODE("byte", po_byte),
|
|
PREDEFNODE("wo", po_16),
|
|
PREDEFNODE("word", po_16),
|
|
PREDEFNODE("16", po_16),
|
|
PREDEFNODE("be16", po_be16),
|
|
PREDEFNODE("le16", po_le16),
|
|
PREDEFNODE("24", po_24),
|
|
PREDEFNODE("be24", po_be24),
|
|
PREDEFNODE("le24", po_le24),
|
|
PREDEFNODE("32", po_32),
|
|
PREDEFNODE("be32", po_be32),
|
|
PREDEFNODE("le32", po_le32),
|
|
PREDEFNODE("h", po_hex),
|
|
PREDEFNODE("hex", po_hex),
|
|
PREDEFNODE(s_cbm, obsolete_po_cbm),
|
|
PREDEFNODE("ct", po_convtab),
|
|
PREDEFNODE("convtab", po_convtab),
|
|
PREDEFNODE("tx", po_text),
|
|
PREDEFNODE("text", po_text),
|
|
PREDEFNODE(s_raw, po_raw),
|
|
PREDEFNODE(s_pet, po_pet),
|
|
PREDEFNODE(s_scr, po_scr),
|
|
PREDEFNODE(s_scrxor, po_scrxor),
|
|
PREDEFNODE("bin", po_binary),
|
|
PREDEFNODE("binary", po_binary),
|
|
PREDEFNODE("fi", po_fill),
|
|
PREDEFNODE("fill", po_fill),
|
|
PREDEFNODE("skip", po_skip),
|
|
PREDEFNODE("align", po_align),
|
|
PREDEFNODE("pseudopc", po_pseudopc),
|
|
PREDEFNODE("realpc", obsolete_po_realpc),
|
|
PREDEFNODE("cpu", po_cpu),
|
|
PREDEFNODE("al", po_al),
|
|
PREDEFNODE("as", po_as),
|
|
PREDEFNODE(s_rl, po_rl),
|
|
PREDEFNODE("rs", po_rs),
|
|
PREDEFNODE("addr", po_address),
|
|
PREDEFNODE("address", po_address),
|
|
// PREDEFNODE("enum", po_enum),
|
|
PREDEFNODE("set", po_set),
|
|
PREDEFNODE(s_sl, po_symbollist),
|
|
PREDEFNODE("symbollist", po_symbollist),
|
|
PREDEFNODE("zn", po_zone),
|
|
PREDEFNODE(s_zone, po_zone),
|
|
PREDEFNODE("sz", obsolete_po_subzone),
|
|
PREDEFNODE(s_subzone, obsolete_po_subzone),
|
|
PREDEFNODE("src", po_source),
|
|
PREDEFNODE("source", po_source),
|
|
PREDEFNODE("if", po_if),
|
|
PREDEFNODE("ifdef", po_ifdef),
|
|
PREDEFNODE("ifndef", po_ifndef),
|
|
PREDEFNODE("for", po_for),
|
|
PREDEFNODE("do", po_do),
|
|
PREDEFNODE("while", po_while),
|
|
PREDEFNODE("macro", po_macro),
|
|
// PREDEFNODE("debug", po_debug),
|
|
// PREDEFNODE("info", po_info),
|
|
PREDEFNODE("warn", po_warn),
|
|
PREDEFNODE(s_error, po_error),
|
|
PREDEFNODE("serious", po_serious),
|
|
PREDEFNODE("eof", po_endoffile),
|
|
PREDEFLAST("endoffile", po_endoffile),
|
|
// ^^^^ this marks the last element
|
|
};
|
|
|
|
|
|
// register pseudo opcodes and create dynamic buffer
|
|
void pseudoopcodes_init(void)
|
|
{
|
|
user_message = DynaBuf_create(USERMSG_DYNABUF_INITIALSIZE);
|
|
Tree_add_table(&pseudo_opcode_tree, pseudo_opcode_list);
|
|
}
|
|
|
|
|
|
// parse a pseudo opcode. has to be re-entrant.
|
|
void pseudoopcode_parse(void) // now GotByte = "!"
|
|
{
|
|
void *node_body;
|
|
enum eos (*fn)(void),
|
|
then = SKIP_REMAINDER; // prepare for errors
|
|
|
|
GetByte(); // read next byte
|
|
// on missing keyword, return (complaining will have been done)
|
|
if (Input_read_and_lower_keyword()) {
|
|
// search for tree item
|
|
if ((Tree_easy_scan(pseudo_opcode_tree, &node_body, GlobalDynaBuf))
|
|
&& node_body) {
|
|
fn = (enum eos (*)(void)) node_body;
|
|
SKIPSPACE();
|
|
// call function
|
|
then = fn();
|
|
} else {
|
|
Throw_error(exception_unknown_pseudo_opcode);
|
|
}
|
|
}
|
|
if (then == SKIP_REMAINDER)
|
|
Input_skip_remainder();
|
|
else if (then == ENSURE_EOS)
|
|
Input_ensure_EOS();
|
|
// the other two possibilities (PARSE_REMAINDER and AT_EOS_ANYWAY)
|
|
// will lead to the remainder of the line being parsed by the mainloop.
|
|
}
|