mirror of
https://github.com/uffejakobsen/acme.git
synced 2024-11-25 23:49:25 +00:00
640373c54f
also prepared filespecs to be relative to current file in next version. git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@357 4df02467-bbd4-4a76-a152-e7ce94205b78
267 lines
8.8 KiB
C
267 lines
8.8 KiB
C
// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code.
|
|
// Copyright (C) 1998-2024 Marco Baye
|
|
// Have a look at "acme.c" for further info
|
|
//
|
|
// Flow control stuff (loops, conditional assembly etc.)
|
|
//
|
|
// Macros, conditional assembly, loops and sourcefile-includes are all based on
|
|
// parsing blocks of code. When defining macros or using loops or conditional
|
|
// assembly, the block starts with "{" and ends with "}". In the case of
|
|
// "!source", the given file is treated like a block - the outermost assembler
|
|
// function uses the same technique to parse the top level file.
|
|
//
|
|
// 24 Nov 2007 Added "!ifndef"
|
|
#include "flow.h"
|
|
#include <string.h>
|
|
#include "alu.h"
|
|
#include "config.h"
|
|
#include "dynabuf.h"
|
|
#include "global.h"
|
|
#include "input.h"
|
|
#include "mnemo.h"
|
|
#include "symbol.h"
|
|
#include "tree.h"
|
|
|
|
|
|
// helper functions for if/ifdef/ifndef/else/for/do/while
|
|
|
|
|
|
// parse symbol name and return if symbol has defined value (called by ifdef/ifndef)
|
|
boolean check_ifdef_condition(void)
|
|
{
|
|
scope_t scope;
|
|
struct rwnode *node;
|
|
struct symbol *symbol;
|
|
|
|
// read symbol name
|
|
if (input_read_scope_and_symbol_name(&scope)) // 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;
|
|
symbol->has_been_read = TRUE; // we did not really read the symbol's value, but checking for its existence still counts as "used it"
|
|
if (symbol->object.type == NULL)
|
|
BUG("ObjectHasNullType", 0);
|
|
return symbol->object.type->is_defined(&symbol->object);
|
|
}
|
|
|
|
|
|
// parse a loop body (TODO - also use for macro body?)
|
|
static void parse_ram_block(struct block *block)
|
|
{
|
|
input_now->location.line_number = block->start; // set line number to loop start
|
|
input_now->src.ram_ptr = block->body; // set RAM read pointer to loop
|
|
// parse block
|
|
parse_until_eob_or_eof();
|
|
if (GotByte != CHAR_EOB)
|
|
BUG("IllegalBlockTerminator", GotByte);
|
|
}
|
|
|
|
|
|
// function for "!for" with counter variable
|
|
static void counting_for(struct for_loop *loop)
|
|
{
|
|
struct object loop_var;
|
|
|
|
// init counter
|
|
loop_var.type = &type_number;
|
|
loop_var.u.number.ntype = NUMTYPE_INT;
|
|
loop_var.u.number.flags = 0;
|
|
loop_var.u.number.val.intval = 0; // SEE BELOW - default value if old algo skips loop entirely
|
|
loop_var.u.number.addr_refs = loop->u.counter.addr_refs;
|
|
// CAUTION: next line does not have power to change symbol type, but if
|
|
// "symbol already defined" error is thrown, the type will still have
|
|
// been changed. this was done so the code below has a counter var.
|
|
symbol_set_object(loop->symbol, &loop_var, POWER_CHANGE_VALUE);
|
|
// TODO: in versions before 0.97, force bit handling was broken
|
|
// in both "!set" and "!for":
|
|
// trying to change a force bit correctly raised an error, but
|
|
// in any case, ALL FORCE BITS WERE CLEARED in symbol. only
|
|
// cases like !set N=N+1 worked, because the force bit was
|
|
// taken from result.
|
|
// maybe support this behaviour via --dialect?
|
|
if (loop->u.counter.force_bit)
|
|
symbol_set_force_bit(loop->symbol, loop->u.counter.force_bit);
|
|
loop_var = loop->symbol->object; // update local copy with force bit
|
|
loop->symbol->has_been_read = TRUE; // lock force bit
|
|
loop_var.u.number.val.intval = loop->u.counter.first; // SEE ABOVE - this may be nonzero, but has not yet been copied to user symbol!
|
|
while (loop->iterations_left) {
|
|
loop->symbol->object = loop_var; // overwrite whole struct, in case some joker has re-assigned loop counter var
|
|
parse_ram_block(&loop->block);
|
|
loop_var.u.number.val.intval += loop->u.counter.increment;
|
|
loop->iterations_left--;
|
|
}
|
|
// new algo wants illegal value in loop counter after block:
|
|
if (loop->algorithm == FORALGO_NEWCOUNT)
|
|
loop->symbol->object = loop_var; // overwrite whole struct, in case some joker has re-assigned loop counter var
|
|
}
|
|
|
|
// function for "!for" with iterating variable
|
|
static void iterating_for(struct for_loop *loop)
|
|
{
|
|
intval_t index = 0;
|
|
struct object obj;
|
|
|
|
while (loop->iterations_left) {
|
|
loop->u.iter.obj.type->at(&loop->u.iter.obj, &obj, index++);
|
|
symbol_set_object(loop->symbol, &obj, POWER_CHANGE_VALUE | POWER_CHANGE_OBJTYPE);
|
|
parse_ram_block(&loop->block);
|
|
loop->iterations_left--;
|
|
}
|
|
}
|
|
|
|
|
|
// back end function for "!for" pseudo opcode
|
|
void flow_forloop(struct for_loop *loop)
|
|
{
|
|
struct input loop_input,
|
|
*outer_input;
|
|
|
|
// switching input makes us lose GotByte. But we know it's '}' anyway!
|
|
// set up new input
|
|
loop_input = *input_now; // copy current input structure into new
|
|
loop_input.source = INPUTSRC_RAM; // set new byte source
|
|
// remember old input
|
|
outer_input = input_now;
|
|
// activate new input
|
|
// (not yet useable; pointer and line number are still missing)
|
|
input_now = &loop_input;
|
|
// fix line number (not for block, but in case symbol handling throws errors)
|
|
input_now->location.line_number = loop->block.start;
|
|
switch (loop->algorithm) {
|
|
case FORALGO_OLDCOUNT:
|
|
case FORALGO_NEWCOUNT:
|
|
counting_for(loop);
|
|
break;
|
|
case FORALGO_ITERATE:
|
|
iterating_for(loop);
|
|
break;
|
|
default:
|
|
BUG("IllegalLoopAlgo", loop->algorithm);
|
|
}
|
|
// restore previous input:
|
|
input_now = outer_input;
|
|
}
|
|
|
|
|
|
// read condition, make copy, link to struct
|
|
static void copy_condition(struct condition *condition, char terminator)
|
|
{
|
|
int err;
|
|
|
|
SKIPSPACE();
|
|
dynabuf_clear(GlobalDynaBuf);
|
|
while ((GotByte != terminator) && (GotByte != CHAR_EOS)) {
|
|
// append to GlobalDynaBuf and check for quotes
|
|
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
|
|
if ((GotByte == '"') || (GotByte == '\'')) {
|
|
err = input_quoted_to_dynabuf(GotByte);
|
|
// here GotByte changes, it might become CHAR_EOS
|
|
DYNABUF_APPEND(GlobalDynaBuf, GotByte); // add closing quotes (or CHAR_EOS) as well
|
|
if (err)
|
|
break; // on error, exit before eating CHAR_EOS via GetByte()
|
|
}
|
|
GetByte();
|
|
}
|
|
dynabuf_append(GlobalDynaBuf, CHAR_EOS); // ensure terminator
|
|
condition->body = dynabuf_get_copy(GlobalDynaBuf);
|
|
}
|
|
|
|
// try to read a condition into DynaBuf and store pointer to copy in
|
|
// given loop_condition structure.
|
|
// if no condition given, NULL is written to structure.
|
|
// call with GotByte = first interesting character
|
|
void flow_store_doloop_condition(struct condition *condition, char terminator)
|
|
{
|
|
// write line number
|
|
condition->line = input_now->location.line_number;
|
|
// set defaults
|
|
condition->invert = FALSE;
|
|
condition->body = NULL;
|
|
// check for empty condition
|
|
if (GotByte == terminator)
|
|
return;
|
|
|
|
// seems as if there really *is* a condition, so check for until/while
|
|
if (input_read_and_lower_keyword()) {
|
|
if (strcmp(GlobalDynaBuf->buffer, "while") == 0) {
|
|
//condition.invert = FALSE;
|
|
} else if (strcmp(GlobalDynaBuf->buffer, "until") == 0) {
|
|
condition->invert = TRUE;
|
|
} else {
|
|
Throw_error(exception_syntax);
|
|
return;
|
|
}
|
|
// write given condition into buffer
|
|
copy_condition(condition, terminator);
|
|
}
|
|
}
|
|
|
|
|
|
// read a condition into DynaBuf and store pointer to copy in
|
|
// given loop_condition structure.
|
|
// call with GotByte = first interesting character
|
|
void flow_store_while_condition(struct condition *condition)
|
|
{
|
|
condition->line = input_now->location.line_number;
|
|
condition->invert = FALSE;
|
|
copy_condition(condition, CHAR_SOB);
|
|
}
|
|
|
|
|
|
// check a condition expression
|
|
static boolean check_condition(struct condition *condition)
|
|
{
|
|
struct number intresult;
|
|
|
|
// first, check whether there actually *is* a condition
|
|
if (condition->body == NULL)
|
|
return TRUE; // non-existing conditions are always true
|
|
|
|
// set up input for expression evaluation
|
|
input_now->location.line_number = condition->line;
|
|
input_now->src.ram_ptr = condition->body;
|
|
GetByte(); // proceed with next char
|
|
ALU_defined_int(&intresult);
|
|
if (GotByte)
|
|
Throw_serious_error(exception_syntax);
|
|
return condition->invert ? !intresult.val.intval : !!intresult.val.intval;
|
|
}
|
|
|
|
|
|
// back end function for "!do" and "!while" pseudo opcodes
|
|
void flow_do_while(struct do_while *loop)
|
|
{
|
|
struct input loop_input;
|
|
struct input *outer_input;
|
|
|
|
// set up new input
|
|
loop_input = *input_now; // copy current input structure into new
|
|
loop_input.source = INPUTSRC_RAM; // set new byte source
|
|
// remember old input
|
|
outer_input = input_now;
|
|
// activate new input (not useable yet, as pointer and
|
|
// line number are not yet set up)
|
|
input_now = &loop_input;
|
|
for (;;) {
|
|
// check head condition
|
|
if (!check_condition(&loop->head_cond))
|
|
break;
|
|
parse_ram_block(&loop->block);
|
|
// check tail condition
|
|
if (!check_condition(&loop->tail_cond))
|
|
break;
|
|
}
|
|
// restore previous input:
|
|
input_now = outer_input;
|
|
GotByte = CHAR_EOS; // CAUTION! Very ugly kluge.
|
|
// But by switching input, we lost the outer input's GotByte. We know
|
|
// it was CHAR_EOS. We could just call GetByte() to get real input, but
|
|
// then the main loop could choke on unexpected bytes. So we pretend
|
|
// that we got the outer input's GotByte value magically back.
|
|
}
|