// 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 #include #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 exception_unknown_pseudo_opcode[] = "Unknown pseudo opcode."; // 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 '*' { bits 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, "stay" or "same" or something like that) == 0) { mutually exclusive with all other arguments! this would mean to keep all previous segment data, so it could be used with "*=*-5" or "*=*+3" } 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 > 255) || (intresult.val.intval < -128)) Throw_error(exception_number_out_of_8b_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(); ALU_any_int(&change); if ((change > 255) || (change < -128)) { Throw_error(exception_number_out_of_8b_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 (unless user requests really old behaviour) if (config.wanted_version >= VER_DEPRECATE_REALPC) 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)) { struct iter_context iter; struct object object; iter.fn = fn; iter.accept_long_strings = FALSE; iter.stringxor = 0; do { ALU_any_result(&object); output_object(&object, &iter); } while (Input_accept_comma()); return ENSURE_EOS; } // insert 8-bit values ("!8" / "!08" / "!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 po_cbm(void) { if (config.wanted_version >= VER_DISABLED_OBSOLETE_STUFF) { Throw_error("\"!cbm\" is obsolete; use \"!ct pet\" instead."); } else { encoder_current = &encoder_pet; Throw_first_pass_warning("\"!cbm\" is deprecated; use \"!ct pet\" instead."); } return ENSURE_EOS; } // read encoding table from file static enum eos user_defined_encoding(FILE *stream) { unsigned 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) { unsigned 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 == '"')) { // encoding table from file if (Input_read_filename(TRUE, &uses_lib)) return SKIP_REMAINDER; // missing or unterminated file name stream = includepaths_open_ro(uses_lib); return user_defined_encoding(stream); } else { // one of the pre-defined encodings return predefined_encoding(); } } // insert string(s) static enum eos encode_string(const struct encoder *inner_encoder, unsigned char xor) { const struct encoder *outer_encoder = encoder_current; // buffer encoder struct iter_context iter; struct object object; iter.fn = output_8; iter.accept_long_strings = TRUE; iter.stringxor = xor; // make given encoder the current one (for ALU-parsed values) encoder_current = inner_encoder; do { // we need to keep the old string handler code, because if user selects // older dialect, the new code will complain about string lengths > 1! if ((GotByte == '"') && (config.wanted_version < VER_BACKSLASHESCAPING)) { // the old way of handling string literals: int offset; DYNABUF_CLEAR(GlobalDynaBuf); if (Input_quoted_to_dynabuf('"')) return SKIP_REMAINDER; // unterminated or escaping error // eat closing quote GetByte(); // now convert to unescaped version if (Input_unescape_dynabuf(0)) return SKIP_REMAINDER; // escaping error // send characters for (offset = 0; offset < GlobalDynaBuf->size; ++offset) output_8(xor ^ encoding_encode_char(GLOBALDYNABUF_CURRENT[offset])); } else { // handle everything else (also strings in newer dialects): // parse value. no problems with single characters because the // current encoding is temporarily set to the given one. ALU_any_result(&object); output_object(&object, &iter); } } 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 xor; ALU_any_int(&xor); if (Input_accept_comma() == FALSE) { Throw_error(exception_syntax); return SKIP_REMAINDER; } return encode_string(&encoder_scr, xor); } // 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()) ALU_any_int(&fill); // 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) { struct number andresult, equalresult; intval_t fill; struct number pc; // 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()) ALU_any_int(&fill); else fill = CPU_state.type->default_align_value; // make sure PC is defined vcpu_read_pc(&pc); if (pc.ntype == NUMTYPE_UNDEFINED) { Throw_error(exception_pc_undefined); return SKIP_REMAINDER; } while ((pc.val.intval++ & andresult.val.intval) != equalresult.val.intval) output_8(fill); return ENSURE_EOS; } // not using a block is no longer allowed static void old_offset_assembly(void) { // really old versions allowed it if (config.wanted_version < VER_DEPRECATE_REALPC) return; // then it was deprecated if (config.wanted_version < VER_DISABLED_OBSOLETE_STUFF) { Throw_first_pass_warning("\"!pseudopc/!realpc\" is deprecated; use \"!pseudopc {}\" instead."); return; } // now it's obsolete Throw_error("\"!pseudopc/!realpc\" is obsolete; use \"!pseudopc {}\" instead."); // FIXME - amend msg, tell user how to use old behaviour! } // start offset assembly // 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) { struct number new_pc; // get new value ALU_defined_int(&new_pc); // FIXME - allow for undefined! (complaining about non-addresses would be logical, but annoying) /* TODO - add this. check if code can be shared with "*="! // check for modifiers while (Input_accept_comma()) { // parse modifier. if no keyword given, give up if (Input_read_and_lower_keyword() == 0) return SKIP_REMAINDER; 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 !pseudopc segment modifier."); return SKIP_REMAINDER; } } */ pseudopc_start(&new_pc); // if there's a block, parse that and then restore old value! if (Parse_optional_block()) { pseudopc_end(); // restore old state } else { old_offset_assembly(); } return ENSURE_EOS; } // "!realpc" pseudo opcode (now obsolete) static enum eos po_realpc(void) { old_offset_assembly(); pseudopc_end_all(); // restore outermost state 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 { scope_t scope; int force_bit; if (Input_read_scope_and_keyword(&scope) == 0) // skips spaces before return SKIP_REMAINDER; // zero length force_bit = Input_get_force_bit(); // skips spaces after if (GotByte != '=') { Throw_error(exception_syntax); return SKIP_REMAINDER; } // TODO: in versions before 0.97, force bit handling was broken in both // "!set" and "!for": // trying to change a force bit raised an error (which is correct), 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? I'd rather not... parse_assignment(scope, force_bit, POWER_CHANGE_VALUE | POWER_CHANGE_OBJTYPE); 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 = "Zone"; // fix type } return ENSURE_EOS; } // "!subzone" or "!sz" pseudo opcode (now obsolete) static enum eos po_subzone(void) { if (config.wanted_version >= VER_DISABLED_OBSOLETE_STUFF) Throw_error("\"!subzone {}\" is obsolete; use \"!zone {}\" instead."); else Throw_first_pass_warning("\"!subzone {}\" is deprecated; 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) { // FIXME - just use safe_malloc and never free! this also saves us making a copy if defining macros down the road... #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; } // if/ifdef/ifndef/else 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 }; // 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); 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."); 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."); 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."); 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 { return ifelse(IFMODE_IF); } // conditional assembly ("!ifdef"). has to be re-entrant. static enum eos po_ifdef(void) // now GotByte = illegal char { return ifelse(IFMODE_IFDEF); } // conditional assembly ("!ifndef"). has to be re-entrant. static enum eos po_ifndef(void) // now GotByte = illegal char { return ifelse(IFMODE_IFNDEF); } // 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; struct for_loop loop; struct number intresult; if (Input_read_scope_and_keyword(&scope) == 0) // skips spaces before return SKIP_REMAINDER; // zero length // now GotByte = illegal char loop.force_bit = Input_get_force_bit(); // skips spaces after loop.symbol = symbol_find(scope); // if not number, error will be reported on first assignment 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()) { // new format - yay! loop.use_old_algo = FALSE; if (config.wanted_version < VER_NEWFORSYNTAX) 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 { // old format - booo! loop.use_old_algo = TRUE; if (config.wanted_version >= VER_NEWFORSYNTAX) 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; // 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; } /* // trace/watch #define TRACEWATCH_LOAD (1u << 0) #define TRACEWATCH_STORE (1u << 1) #define TRACEWATCH_EXEC (1u << 2) #define TRACEWATCH_DEFAULT (TRACEWATCH_LOAD | TRACEWATCH_STORE | TRACEWATCH_EXEC) #define TRACEWATCH_BREAK (1u << 3) static enum eos tracewatch(boolean enter_monitor) { struct number pc; bits flags = 0; vcpu_read_pc(&pc); SKIPSPACE(); // check for flags if (GotByte != CHAR_EOS) { do { // parse flag. if no keyword given, give up if (Input_read_and_lower_keyword() == 0) return SKIP_REMAINDER; // fail (error has been reported) if (strcmp(GlobalDynaBuf->buffer, "load") == 0) { flags |= TRACEWATCH_LOAD; } else if (strcmp(GlobalDynaBuf->buffer, "store") == 0) { flags |= TRACEWATCH_STORE; } else if (strcmp(GlobalDynaBuf->buffer, "exec") == 0) { flags |= TRACEWATCH_EXEC; } else { Throw_error("Unknown flag (known are: load, store, exec)."); // FIXME - add to docs! return SKIP_REMAINDER; } } while (Input_accept_comma()); } // shortcut: no flags at all -> set all flags! if (!flags) flags = TRACEWATCH_DEFAULT; if (enter_monitor) flags |= TRACEWATCH_BREAK; if (pc.ntype != NUMTYPE_UNDEFINED) { //FIXME - store pc and flags! } return ENSURE_EOS; } // make next byte a trace point (for VICE debugging) static enum eos po_trace(void) { return tracewatch(FALSE); // do not enter monitor, just output } // make next byte a watch point (for VICE debugging) static enum eos po_watch(void) { return tracewatch(TRUE); // break into monitor } */ // constants #define USERMSG_INITIALSIZE 80 // variables static STRUCT_DYNABUF_REF(user_message, USERMSG_INITIALSIZE); // for !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 == '"') && (config.wanted_version < VER_BACKSLASHESCAPING)) { DYNABUF_CLEAR(GlobalDynaBuf); if (Input_quoted_to_dynabuf('"')) return SKIP_REMAINDER; // unterminated or escaping error // eat closing quote GetByte(); // now convert to unescaped version if (Input_unescape_dynabuf(0)) return SKIP_REMAINDER; // escaping error DynaBuf_append(GlobalDynaBuf, '\0'); // terminate string DynaBuf_add_string(user_message, GLOBALDYNABUF_CURRENT); // add to message } 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_tree[] = { PREDEF_START, PREDEFNODE("initmem", po_initmem), PREDEFNODE("xor", po_xor), PREDEFNODE("to", po_to), PREDEFNODE("by", po_byte), PREDEFNODE("byte", po_byte), PREDEFNODE("8", po_byte), PREDEFNODE("08", po_byte), // legacy alias, don't ask... 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("cbm", po_cbm), // obsolete PREDEFNODE("ct", po_convtab), PREDEFNODE("convtab", po_convtab), PREDEFNODE("tx", po_text), PREDEFNODE("text", po_text), PREDEFNODE("raw", po_raw), PREDEFNODE("pet", po_pet), PREDEFNODE("scr", po_scr), PREDEFNODE("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", po_realpc), // obsolete PREDEFNODE("cpu", po_cpu), PREDEFNODE("al", po_al), PREDEFNODE("as", po_as), PREDEFNODE("rl", po_rl), PREDEFNODE("rs", po_rs), PREDEFNODE("addr", po_address), PREDEFNODE("address", po_address), // PREDEFNODE("enum", po_enum), PREDEFNODE("set", po_set), PREDEFNODE("sl", po_symbollist), PREDEFNODE("symbollist", po_symbollist), PREDEFNODE("zn", po_zone), PREDEFNODE("zone", po_zone), PREDEFNODE("sz", po_subzone), // obsolete PREDEFNODE("subzone", po_subzone), // obsolete 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("trace", po_trace), PREDEFNODE("watch", po_watch), */ // PREDEFNODE("debug", po_debug), // PREDEFNODE("info", po_info), PREDEFNODE("warn", po_warn), PREDEFNODE("error", po_error), PREDEFNODE("serious", po_serious), PREDEFNODE("eof", po_endoffile), PREDEF_END("endoffile", po_endoffile), // ^^^^ this marks the last element }; // 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. }