1
0
mirror of https://github.com/ksherlock/x65.git synced 2024-12-30 17:30:58 +00:00
x65/x65.cpp
Carl-Henrik Skårstedt 6cbf7f8754 x65macro.i added
- Adding standard macros with for loops, memory copy, add, subtract,
move and set
- macros can be named with dots
- double negatives won't cause errors in expressions
- vice output will convert labels named "debugbreak" to vice breakpoints
rather than vice labels
- fixed issues with mixing conditional operators with math operators in
expressions
2015-12-02 22:16:31 -08:00

7182 lines
261 KiB
C++

//
// x65.cpp
//
//
// Created by Carl-Henrik Skårstedt on 9/23/15.
//
//
// A simple 6502 assembler
//
//
// The MIT License (MIT)
//
// Copyright (c) 2015 Carl-Henrik Skårstedt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Details, source and documentation at https://github.com/Sakrac/x65.
//
// "struse.h" can be found at https://github.com/Sakrac/struse, only the header file is required.
//
#define _CRT_SECURE_NO_WARNINGS // Windows shenanigans
#define STRUSE_IMPLEMENTATION // include implementation of struse in this file
#include "struse.h" // https://github.com/Sakrac/struse/blob/master/struse.h
#include <vector>
#include <stdio.h>
#include <stdlib.h>
// if the number of resolved labels exceed this in one late eval then skip
// checking for relevance and just eval all unresolved expressions.
#define MAX_LABELS_EVAL_ALL 16
// Max number of nested scopes (within { and })
#define MAX_SCOPE_DEPTH 32
// Max number of nested conditional expressions
#define MAX_CONDITIONAL_DEPTH 64
// The maximum complexity of expressions to be evaluated
#define MAX_EVAL_VALUES 32
#define MAX_EVAL_OPER 64
// Max capacity of each label pool
#define MAX_POOL_RANGES 4
#define MAX_POOL_BYTES 128
// Max number of exported binary files from a single source
#define MAX_EXPORT_FILES 64
// Maximum number of opcodes, aliases and directives
#define MAX_OPCODES_DIRECTIVES 320
// minor variation of 6502
#define NUM_ILLEGAL_6502_OPS 21
// minor variation of 65C02
#define NUM_WDC_65C02_SPECIFIC_OPS 18
// To simplify some syntax disambiguation the preferred
// ruleset can be specified on the command line.
enum AsmSyntax {
SYNTAX_SANE,
SYNTAX_MERLIN
};
// Internal status and error type
enum StatusCode {
STATUS_OK, // everything is fine
STATUS_RELATIVE_SECTION, // value is relative to a single section
STATUS_NOT_READY, // label could not be evaluated at this time
STATUS_XREF_DEPENDENT, // evaluated but relied on an XREF label to do so
STATUS_NOT_STRUCT, // return is not a struct.
STATUS_EXPORT_NO_CODE_OR_DATA_SECTION,
FIRST_ERROR,
ERROR_UNDEFINED_CODE = FIRST_ERROR,
ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION,
ERROR_TOO_MANY_VALUES_IN_EXPRESSION,
ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION,
ERROR_UNBALANCED_RIGHT_PARENTHESIS,
ERROR_EXPRESSION_OPERATION,
ERROR_EXPRESSION_MISSING_VALUES,
ERROR_INSTRUCTION_NOT_ZP,
ERROR_INVALID_ADDRESSING_MODE,
ERROR_BRANCH_OUT_OF_RANGE,
ERROR_LABEL_MISPLACED_INTERNAL,
ERROR_BAD_ADDRESSING_MODE,
ERROR_UNEXPECTED_CHARACTER_IN_ADDRESSING_MODE,
ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT,
ERROR_MODIFYING_CONST_LABEL,
ERROR_OUT_OF_LABELS_IN_POOL,
ERROR_INTERNAL_LABEL_POOL_ERROR,
ERROR_POOL_RANGE_EXPRESSION_EVAL,
ERROR_LABEL_POOL_REDECLARATION,
ERROR_POOL_LABEL_ALREADY_DEFINED,
ERROR_STRUCT_ALREADY_DEFINED,
ERROR_REFERENCED_STRUCT_NOT_FOUND,
ERROR_BAD_TYPE_FOR_DECLARE_CONSTANT,
ERROR_REPT_COUNT_EXPRESSION,
ERROR_HEX_WITH_ODD_NIBBLE_COUNT,
ERROR_DS_MUST_EVALUATE_IMMEDIATELY,
ERROR_NOT_AN_X65_OBJECT_FILE,
ERROR_COULD_NOT_INCLUDE_FILE,
ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution
ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY,
ERROR_TOO_DEEP_SCOPE,
ERROR_UNBALANCED_SCOPE_CLOSURE,
ERROR_BAD_MACRO_FORMAT,
ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY,
ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION,
ERROR_CONDITION_COULD_NOT_BE_RESOLVED,
ERROR_ENDIF_WITHOUT_CONDITION,
ERROR_ELSE_WITHOUT_IF,
ERROR_STRUCT_CANT_BE_ASSEMBLED,
ERROR_ENUM_CANT_BE_ASSEMBLED,
ERROR_UNTERMINATED_CONDITION,
ERROR_REPT_MISSING_SCOPE,
ERROR_LINKER_MUST_BE_IN_FIXED_ADDRESS_SECTION,
ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION,
ERROR_UNABLE_TO_PROCESS,
ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE,
ERROR_CPU_NOT_SUPPORTED,
ERROR_CANT_APPEND_SECTION_TO_TARGET,
ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE,
ERROR_NOT_A_SECTION,
ERROR_CANT_REASSIGN_FIXED_SECTION,
ERROR_CANT_LINK_ZP_AND_NON_ZP,
ERROR_OUT_OF_MEMORY,
ERROR_CANT_WRITE_TO_FILE,
ERROR_ABORTED,
ERROR_CONDITION_TOO_NESTED,
STATUSCODE_COUNT
};
// The following strings are in the same order as StatusCode
const char *aStatusStrings[STATUSCODE_COUNT] = {
"ok",
"relative section",
"not ready",
"XREF dependent result",
"name is not a struct",
"Exporting binary without code or data section",
"Undefined code",
"Unexpected character in expression",
"Too many values in expression",
"Too many operators in expression",
"Unbalanced right parenthesis in expression",
"Expression operation",
"Expression missing values",
"Instruction can not be zero page",
"Invalid addressing mode for instruction",
"Branch out of range",
"Internal label organization mishap",
"Bad addressing mode",
"Unexpected character in addressing mode",
"Unexpected label assignment format",
"Changing value of label that is constant",
"Out of labels in pool",
"Internal label pool release confusion",
"Label pool range evaluation failed",
"Label pool was redeclared within its scope",
"Pool label already defined",
"Struct already defined",
"Referenced struct not found",
"Declare constant type not recognized (dc.?)",
"rept count expression could not be evaluated",
"hex must be followed by an even number of hex numbers",
"DS directive failed to evaluate immediately",
"File is not a valid x65 object file",
"Failed to read include file",
"Errors after this point will stop execution",
"Target address must evaluate immediately for this operation",
"Scoping is too deep",
"Unbalanced scope closure",
"Unexpected macro formatting",
"Align must evaluate immediately",
"Out of memory for macro expansion",
"Conditional could not be resolved",
"#endif encountered outside conditional block",
"#else or #elif outside conditional block",
"Struct can not be assembled as is",
"Enum can not be assembled as is",
"Conditional assembly (#if/#ifdef) was not terminated in file or macro",
"rept is missing a scope ('{ ... }')",
"Link can only be used in a fixed address section",
"Link can not be used in dummy sections",
"Can not process this line",
"Unexpected target offset for reloc or late evaluation",
"CPU is not supported",
"Can't append sections",
"Zero page / Direct page section out of range",
"Attempting to assign an address to a non-existent section",
"Attempting to assign an address to a fixed address section",
"Can not link a zero page section with a non-zp section",
"Out of memory while building",
"Can not write to file",
"Assembly aborted",
"Condition too deeply nested",
};
// Assembler directives
enum AssemblerDirective {
AD_CPU, // CPU: Assemble for this target,
AD_ORG, // ORG: Assemble as if loaded at this address
AD_EXPORT, // EXPORT: export this section or disable export
AD_LOAD, // LOAD: If applicable, instruct to load at this address
AD_SECTION, // SECTION: Enable code that will be assigned a start address during a link step
AD_LINK, // LINK: Put sections with this name at this address (must be ORG / fixed address section)
AD_XDEF, // XDEF: Externally declare a symbol
AD_XREF, // XREF: Reference an external symbol
AD_INCOBJ, // INCOBJ: Read in an object file saved from a previous build
AD_ALIGN, // ALIGN: Add to address to make it evenly divisible by this
AD_MACRO, // MACRO: Create a macro
AD_EVAL, // EVAL: Print expression to stdout during assemble
AD_BYTES, // BYTES: Add 8 bit values to output
AD_WORDS, // WORDS: Add 16 bit values to output
AD_DC, // DC.B/DC.W: Declare constant (same as BYTES/WORDS)
AD_TEXT, // TEXT: Add text to output
AD_INCLUDE, // INCLUDE: Load and assemble another file at this address
AD_INCBIN, // INCBIN: Load and directly insert another file at this address
AD_IMPORT, // IMPORT: Include or Incbin or Incobj or Incsym
AD_CONST, // CONST: Prevent a label from mutating during assemble
AD_LABEL, // LABEL: Create a mutable label (optional)
AD_STRING, // STRING: Declare a string symbol
AD_UNDEF, // UNDEF: remove a string or a label
AD_INCSYM, // INCSYM: Reference labels from another assemble
AD_LABPOOL, // POOL: Create a pool of addresses to assign as labels dynamically
AD_IF, // #IF: Conditional assembly follows based on expression
AD_IFDEF, // #IFDEF: Conditional assembly follows based on label defined or not
AD_ELSE, // #ELSE: Otherwise assembly
AD_ELIF, // #ELIF: Otherwise conditional assembly follows
AD_ENDIF, // #ENDIF: End a block of #IF/#IFDEF
AD_STRUCT, // STRUCT: Declare a set of labels offset from a base address
AD_ENUM, // ENUM: Declare a set of incremental labels
AD_REPT, // REPT: Repeat the assembly of the bracketed code a number of times
AD_INCDIR, // INCDIR: Add a folder to search for include files
AD_A16, // A16: Set 16 bit accumulator mode
AD_A8, // A8: Set 8 bit accumulator mode
AD_XY16, // A16: Set 16 bit index register mode
AD_XY8, // A8: Set 8 bit index register mode
AD_HEX, // HEX: LISA assembler data block
AD_ABORT, // ABORT: stop assembler and error
AD_EJECT, // EJECT: Page break for printing assembler code, ignore
AD_LST, // LST: Controls symbol listing
AD_DUMMY, // DUM: Start a dummy section (increment address but don't write anything???)
AD_DUMMY_END, // DEND: End a dummy section
AD_DS, // DS: Define section, zero out # bytes or rewind the address if negative
AD_USR, // USR: MERLIN user defined pseudo op, runs some code at a hard coded address on apple II, on PC does nothing.
AD_SAV, // SAV: MERLIN version of export but contains full filename, not an appendable name
AD_XC, // XC: MERLIN version of setting CPU
AD_MX, // MX: MERLIN control accumulator 16 bit mode
AD_LNK, // LNK: MERLIN load object and link
AD_ADR, // ADR: MERLIN store 3 byte word
AD_ADRL, // ADRL: MERLIN store 4 byte word
AD_ENT, // ENT: MERLIN extern this address label
AD_EXT, // EXT: MERLIN reference this address label from a different file
AD_CYC, // CYC: MERLIN start / stop cycle timer
};
// Operators are either instructions or directives
enum OperationType {
OT_NONE,
OT_MNEMONIC,
OT_DIRECTIVE
};
// These are expression tokens in order of precedence (last is highest precedence)
enum EvalOperator {
EVOP_NONE,
EVOP_VAL = 'a', // a, value => read from value queue
EVOP_EQU, // b, 1 if left equal to right otherwise 0
EVOP_LT, // c, 1 if left less than right otherwise 0
EVOP_GT, // d, 1 if left greater than right otherwise 0
EVOP_LTE, // e, 1 if left less than or equal to right otherwise 0
EVOP_GTE, // f, 1 if left greater than or equal to right otherwise 0
EVOP_LOB, // g, low byte of 16 bit value
EVOP_HIB, // h, high byte of 16 bit value
EVOP_BAB, // i, bank byte of 24 bit value
EVOP_LPR, // j, left parenthesis
EVOP_RPR, // k, right parenthesis
EVOP_ADD, // l, +
EVOP_SUB, // m, -
EVOP_MUL, // n, * (note: if not preceded by value or right paren this is current PC)
EVOP_DIV, // o, /
EVOP_AND, // p, &
EVOP_OR, // q, |
EVOP_EOR, // r, ^
EVOP_SHL, // s, <<
EVOP_SHR, // t, >>
EVOP_NEG, // u, negate value
EVOP_STP, // v, Unexpected input, should stop and evaluate what we have
EVOP_NRY, // w, Not ready yet
EVOP_XRF, // x, value from XREF label
EVOP_EXP, // y, sub expression
EVOP_ERR, // z, Error
};
// Opcode encoding
typedef struct {
unsigned int op_hash;
unsigned char index; // ground index
unsigned char type; // mnemonic or
} OPLookup;
enum AddrMode {
// address mode bit index
// 6502
AMB_ZP_REL_X, // 0 ($12,x)
AMB_ZP, // 1 $12
AMB_IMM, // 2 #$12
AMB_ABS, // 3 $1234
AMB_ZP_Y_REL, // 4 ($12),y
AMB_ZP_X, // 5 $12,x
AMB_ABS_Y, // 6 $1234,y
AMB_ABS_X, // 7 $1234,x
AMB_REL, // 8 ($1234)
AMB_ACC, // 9 A
AMB_NON, // a
// 65C02
AMB_ZP_REL, // b ($12)
AMB_REL_X, // c ($1234,x)
AMB_ZP_ABS, // d $12, *+$12
// 65816
AMB_ZP_REL_L, // e [$02]
AMB_ZP_REL_Y_L, // f [$00],y
AMB_ABS_L, // 10 $bahilo
AMB_ABS_L_X, // 11 $123456,x
AMB_STK, // 12 $12,s
AMB_STK_REL_Y, // 13 ($12,s),y
AMB_REL_L, // 14 [$1234]
AMB_BLK_MOV, // 15 $12,$34
AMB_COUNT,
AMB_FLIPXY = AMB_COUNT, // 16 (indexing index using y treat as x address mode)
AMB_BRANCH, // 17 (relative address 8 bit)
AMB_BRANCH_L, // 18 (relative address 16 bit)
AMB_IMM_DBL_A, // 19 (immediate mode can be doubled in 16 bit mode)
AMB_IMM_DBL_XY, // 1a (immediate mode can be doubled in 16 bit mode)
AMB_ILL, // 1b illegal address mode
// address mode masks
AMM_NON = 1<<AMB_NON,
AMM_IMM = 1<<AMB_IMM,
AMM_ABS = 1<<AMB_ABS,
AMM_REL = 1<<AMB_REL,
AMM_ACC = 1<<AMB_ACC,
AMM_ZP = 1<<AMB_ZP,
AMM_ABS_X = 1<<AMB_ABS_X,
AMM_ABS_Y = 1<<AMB_ABS_Y,
AMM_ZP_X = 1<<AMB_ZP_X,
AMM_ZP_REL_X = 1<<AMB_ZP_REL_X,
AMM_ZP_Y_REL = 1<<AMB_ZP_Y_REL,
AMM_ZP_REL = 1<<AMB_ZP_REL, // b ($12)
AMM_REL_X = 1<<AMB_REL_X, // c ($1234,x)
AMM_ZP_ABS = 1<<AMB_ZP_ABS, // d $12, *+$12
AMM_ZP_REL_L = 1<<AMB_ZP_REL_L, // e [$02]
AMM_ZP_REL_Y_L = 1<<AMB_ZP_REL_Y_L, // f [$00],y
AMM_ABS_L = 1<<AMB_ABS_L, // 10 $bahilo
AMM_ABS_L_X = 1<<AMB_ABS_L_X, // 11 $123456,x
AMM_STK = 1<<AMB_STK, // 12 $12,s
AMM_STK_REL_Y = 1<<AMB_STK_REL_Y, // 13 ($12,s),y
AMM_REL_L = 1<<AMB_REL_L, // 14 [$1234]
AMM_BLK_MOV = 1<<AMB_BLK_MOV, // 15 $12,$34
AMM_FLIPXY = 1<<AMB_FLIPXY,
AMM_BRANCH = 1<<AMB_BRANCH,
AMM_BRANCH_L = 1<<AMB_BRANCH_L,
AMM_IMM_DBL_A = 1<<AMB_IMM_DBL_A,
AMM_IMM_DBL_XY = 1<<AMB_IMM_DBL_XY,
// instruction group specific masks
AMM_BRA = AMM_BRANCH | AMM_ABS,
AMM_ORA = AMM_IMM | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_Y | AMM_ABS_X | AMM_ZP_REL_X | AMM_ZP_Y_REL,
AMM_STA = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_Y | AMM_ABS_X | AMM_ZP_REL_X | AMM_ZP_Y_REL,
AMM_ASL = AMM_ACC | AMM_NON | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X,
AMM_STX = AMM_FLIPXY | AMM_ZP | AMM_ZP_X | AMM_ABS, // note: for x ,x/,y flipped for this instr.
AMM_LDX = AMM_FLIPXY | AMM_IMM | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X, // note: for x ,x/,y flipped for this instr.
AMM_STY = AMM_ZP | AMM_ZP_X | AMM_ABS,
AMM_LDY = AMM_IMM | AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X,
AMM_DEC = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X,
AMM_BIT = AMM_ZP | AMM_ABS,
AMM_JMP = AMM_ABS | AMM_REL,
AMM_CPY = AMM_IMM | AMM_ZP | AMM_ABS,
// 6502 illegal modes
AMM_SLO = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_Y | AMM_ABS_X | AMM_ZP_REL_X | AMM_ZP_Y_REL,
AMM_SAX = AMM_FLIPXY | AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ABS,
AMM_LAX = AMM_FLIPXY | AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ABS | AMM_ABS_X,
AMM_AHX = AMM_FLIPXY | AMM_ZP_REL_X | AMM_ABS_X,
AMM_SHY = AMM_ABS_X,
AMM_SHX = AMM_ABS_Y,
// 65C02 groups
AMC_ORA = AMM_ORA | AMM_ZP_REL,
AMC_STA = AMM_STA | AMM_ZP_REL,
AMC_BIT = AMM_BIT | AMM_IMM | AMM_ZP_X | AMM_ABS_X,
AMC_DEC = AMM_DEC | AMM_NON | AMM_ACC,
AMC_JMP = AMM_JMP | AMM_REL_X,
AMC_STZ = AMM_ZP | AMM_ZP_X | AMM_ABS | AMM_ABS_X,
AMC_TRB = AMM_ZP | AMM_ABS,
AMC_BBR = AMM_ZP_ABS,
// 65816 groups
AM8_JSR = AMM_ABS | AMM_ABS_L | AMM_REL_X,
AM8_JSL = AMM_ABS_L,
AM8_BIT = AMM_IMM_DBL_A | AMC_BIT,
AM8_ORA = AMM_IMM_DBL_A | AMC_ORA | AMM_STK | AMM_ZP_REL_L | AMM_ABS_L | AMM_STK_REL_Y | AMM_ZP_REL_Y_L | AMM_ABS_L_X,
AM8_STA = AMC_STA | AMM_STK | AMM_ZP_REL_L | AMM_ABS_L | AMM_STK_REL_Y | AMM_ZP_REL_Y_L | AMM_ABS_L_X,
AM8_ORL = AMM_ABS_L | AMM_ABS_L_X,
AM8_STL = AMM_ABS_L | AMM_ABS_L_X,
AM8_LDX = AMM_IMM_DBL_XY | AMM_LDX,
AM8_LDY = AMM_IMM_DBL_XY | AMM_LDY,
AM8_CPY = AMM_IMM_DBL_XY | AMM_CPY,
AM8_JMP = AMC_JMP | AMM_REL_L | AMM_ABS_L | AMM_REL_X,
AM8_JML = AMM_REL_L | AMM_ABS_L,
AM8_BRL = AMM_BRANCH_L | AMM_ABS,
AM8_MVN = AMM_BLK_MOV,
AM8_PEI = AMM_ZP_REL,
AM8_PER = AMM_BRANCH_L | AMM_ABS,
AM8_REP = AMM_IMM | AMM_ZP, // Merlin allows this to look like a zp access
};
struct mnem {
const char *instr;
unsigned int modes;
unsigned char aCodes[AMB_COUNT];
};
struct mnem opcodes_6502[] = {
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty
{ "brk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "jsr", AMM_ABS, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rti", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 } },
{ "rts", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60 } },
{ "ora", AMM_ORA, { 0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d, 0x00, 0x00, 0x00 } },
{ "and", AMM_ORA, { 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x00, 0x00, 0x00 } },
{ "eor", AMM_ORA, { 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, 0x5d, 0x00, 0x00, 0x00 } },
{ "adc", AMM_ORA, { 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x00, 0x00, 0x00 } },
{ "sta", AMM_STA, { 0x81, 0x85, 0x00, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0x00, 0x00, 0x00 } },
{ "lda", AMM_ORA, { 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0x00, 0x00, 0x00 } },
{ "cmp", AMM_ORA, { 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd5, 0xd9, 0xdd, 0x00, 0x00, 0x00 } },
{ "sbc", AMM_ORA, { 0xe1, 0xe5, 0xe9, 0xed, 0xf1, 0xf5, 0xf9, 0xfd, 0x00, 0x00, 0x00 } },
{ "asl", AMM_ASL, { 0x00, 0x06, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x1e, 0x00, 0x0a, 0x0a } },
{ "rol", AMM_ASL, { 0x00, 0x26, 0x00, 0x2e, 0x00, 0x36, 0x00, 0x3e, 0x00, 0x2a, 0x2a } },
{ "lsr", AMM_ASL, { 0x00, 0x46, 0x00, 0x4e, 0x00, 0x56, 0x00, 0x5e, 0x00, 0x4a, 0x4a } },
{ "ror", AMM_ASL, { 0x00, 0x66, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x7e, 0x00, 0x6a, 0x6a } },
{ "stx", AMM_STX, { 0x00, 0x86, 0x00, 0x8e, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ldx", AMM_LDX, { 0x00, 0xa6, 0xa2, 0xae, 0x00, 0xb6, 0x00, 0xbe, 0x00, 0x00, 0x00 } },
{ "dec", AMM_DEC, { 0x00, 0xc6, 0x00, 0xce, 0x00, 0xd6, 0x00, 0xde, 0x00, 0x00, 0x00 } },
{ "inc", AMM_DEC, { 0x00, 0xe6, 0x00, 0xee, 0x00, 0xf6, 0x00, 0xfe, 0x00, 0x00, 0x00 } },
{ "php", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } },
{ "plp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28 } },
{ "pha", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48 } },
{ "pla", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68 } },
{ "dey", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88 } },
{ "tay", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8 } },
{ "iny", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8 } },
{ "inx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8 } },
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty
{ "bpl", AMM_BRA, { 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bmi", AMM_BRA, { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bvc", AMM_BRA, { 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bvs", AMM_BRA, { 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bcc", AMM_BRA, { 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bcs", AMM_BRA, { 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bne", AMM_BRA, { 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "beq", AMM_BRA, { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "clc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18 } },
{ "sec", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38 } },
{ "cli", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58 } },
{ "sei", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78 } },
{ "tya", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98 } },
{ "clv", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8 } },
{ "cld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8 } },
{ "sed", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8 } },
{ "bit", AMM_BIT, { 0x00, 0x24, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "jmp", AMM_JMP, { 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00 } },
{ "sty", AMM_STY, { 0x00, 0x84, 0x00, 0x8c, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ldy", AMM_LDY, { 0x00, 0xa4, 0xa0, 0xac, 0x00, 0xb4, 0x00, 0xbc, 0x00, 0x00, 0x00 } },
{ "cpy", AMM_CPY, { 0x00, 0xc4, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cpx", AMM_CPY, { 0x00, 0xe4, 0xe0, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "txa", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a } },
{ "txs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a } },
{ "tax", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa } },
{ "tsx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba } },
{ "dex", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca } },
{ "nop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea } },
// 21 ILLEGAL 6502 OPCODES (http://www.oxyron.de/html/opcodes02.html)
// NOTE: If adding or removing, update NUM_ILLEGAL_6502_OPS
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty
{ "slo", AMM_SLO, { 0x03, 0x07, 0x00, 0x0f, 0x13, 0x17, 0x1b, 0x1f, 0x00, 0x00, 0x00 } },
{ "rla", AMM_SLO, { 0x23, 0x27, 0x00, 0x2f, 0x33, 0x37, 0x3b, 0x3f, 0x00, 0x00, 0x00 } },
{ "sre", AMM_SLO, { 0x43, 0x47, 0x00, 0x4f, 0x53, 0x57, 0x5b, 0x5f, 0x00, 0x00, 0x00 } },
{ "rra", AMM_SLO, { 0x63, 0x67, 0x00, 0x6f, 0x73, 0x77, 0x7b, 0x7f, 0x00, 0x00, 0x00 } },
{ "sax", AMM_SAX, { 0x83, 0x87, 0x00, 0x8f, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "lax", AMM_LAX, { 0xa3, 0xa7, 0x00, 0xaf, 0xb3, 0xb7, 0x00, 0xbf, 0x00, 0x00, 0x00 } },
{ "dcp", AMM_SLO, { 0xc3, 0xc7, 0x00, 0xcf, 0xd3, 0xd7, 0xdb, 0xdf, 0x00, 0x00, 0x00 } },
{ "isc", AMM_SLO, { 0xe3, 0xe7, 0x00, 0xef, 0xf3, 0xf7, 0xfb, 0xff, 0x00, 0x00, 0x00 } },
{ "anc", AMM_IMM, { 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "aac", AMM_IMM, { 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "alr", AMM_IMM, { 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "arr", AMM_IMM, { 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "xaa", AMM_IMM, { 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{"lax2", AMM_IMM, { 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "axs", AMM_IMM, { 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "sbi", AMM_IMM, { 0x00, 0x00, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ahx", AMM_AHX, { 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x00, 0x00, 0x00 } },
{ "shy", AMM_SHY, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00 } },
{ "shx", AMM_SHX, { 0x00, 0x00, 0x00, 0x00, 0x93, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00 } },
{ "tas", AMM_SHX, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00 } },
{ "las", AMM_SHX, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00 } },
};
const char* aliases_6502[] = {
"bcc", "blt",
"bcs", "bge",
nullptr, nullptr
};
unsigned char timing_6502[] = {
0x0e, 0x0c, 0xff, 0xff, 0xff, 0x06, 0x0a, 0xff, 0x06, 0x04, 0x04, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff,
0x0c, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x0a, 0xff, 0x08, 0x04, 0x04, 0xff, 0x08, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff,
0x0c, 0x0c, 0xff, 0xff, 0xff, 0x06, 0x0a, 0xff, 0x06, 0x04, 0x04, 0xff, 0x06, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff,
0x0c, 0x0c, 0xff, 0xff, 0xff, 0x06, 0x0a, 0xff, 0x08, 0x04, 0x04, 0xff, 0x0a, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff,
0xff, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x06, 0xff, 0x04, 0xff, 0x04, 0xff, 0x08, 0x08, 0x08, 0xff, 0x05, 0x0c, 0xff, 0xff, 0x08, 0x08, 0x08, 0xff, 0x04, 0x0a, 0x04, 0xff, 0xff, 0x0a, 0xff, 0xff,
0x04, 0x0c, 0x04, 0xff, 0x06, 0x06, 0x06, 0xff, 0x04, 0x04, 0x04, 0xff, 0x08, 0x08, 0x08, 0xff, 0x05, 0x0b, 0xff, 0xff, 0x08, 0x08, 0x08, 0xff, 0x04, 0x09, 0x04, 0xff, 0x09, 0x09, 0x09, 0xff,
0x04, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x0a, 0xff, 0x04, 0x04, 0x04, 0xff, 0x08, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff,
0x04, 0x0c, 0xff, 0xff, 0x06, 0x06, 0x0a, 0xff, 0x04, 0x04, 0x04, 0xff, 0x08, 0x08, 0x0c, 0xff, 0x05, 0x0b, 0xff, 0xff, 0xff, 0x08, 0x0c, 0xff, 0x04, 0x09, 0xff, 0xff, 0xff, 0x09, 0x0e, 0xff
};
static const int num_opcodes_6502 = sizeof(opcodes_6502) / sizeof(opcodes_6502[0]);
struct mnem opcodes_65C02[] = {
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs
{ "brk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "jsr", AMM_ABS, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rti", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00 } },
{ "rts", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00 } },
{ "ora", AMC_ORA, { 0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00 } },
{ "and", AMC_ORA, { 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00 } },
{ "eor", AMC_ORA, { 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, 0x5d, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00 } },
{ "adc", AMC_ORA, { 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00 } },
{ "sta", AMC_STA, { 0x81, 0x85, 0x00, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00 } },
{ "lda", AMC_ORA, { 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00 } },
{ "cmp", AMC_ORA, { 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd5, 0xd9, 0xdd, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00 } },
{ "sbc", AMC_ORA, { 0xe1, 0xe5, 0xe9, 0xed, 0xf1, 0xf5, 0xf9, 0xfd, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00 } },
{ "asl", AMM_ASL, { 0x00, 0x06, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x1e, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x00 } },
{ "rol", AMM_ASL, { 0x00, 0x26, 0x00, 0x2e, 0x00, 0x36, 0x00, 0x3e, 0x00, 0x2a, 0x2a, 0x00, 0x00, 0x00 } },
{ "lsr", AMM_ASL, { 0x00, 0x46, 0x00, 0x4e, 0x00, 0x56, 0x00, 0x5e, 0x00, 0x4a, 0x4a, 0x00, 0x00, 0x00 } },
{ "ror", AMM_ASL, { 0x00, 0x66, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x7e, 0x00, 0x6a, 0x6a, 0x00, 0x00, 0x00 } },
{ "stx", AMM_STX, { 0x00, 0x86, 0x00, 0x8e, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ldx", AMM_LDX, { 0x00, 0xa6, 0xa2, 0xae, 0x00, 0xb6, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "dec", AMC_DEC, { 0x00, 0xc6, 0x00, 0xce, 0x00, 0xd6, 0x00, 0xde, 0x00, 0x3a, 0x3a, 0x00, 0x00, 0x00 } },
{ "inc", AMC_DEC, { 0x00, 0xe6, 0x00, 0xee, 0x00, 0xf6, 0x00, 0xfe, 0x00, 0x1a, 0x1a, 0x00, 0x00, 0x00 } },
{ "dea", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00 } },
{ "ina", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00 } },
{ "php", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 } },
{ "plp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 } },
{ "pha", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00 } },
{ "pla", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00 } },
{ "phy", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00 } },
{ "ply", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00 } },
{ "phx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x00, 0x00, 0x00 } },
{ "plx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00 } },
{ "dey", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 } },
{ "tay", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00 } },
{ "iny", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00 } },
{ "inx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00 } },
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs
{ "bpl", AMM_BRA, { 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bmi", AMM_BRA, { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bvc", AMM_BRA, { 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bvs", AMM_BRA, { 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bra", AMM_BRA, { 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bcc", AMM_BRA, { 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bcs", AMM_BRA, { 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bne", AMM_BRA, { 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "beq", AMM_BRA, { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "clc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00 } },
{ "sec", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00 } },
{ "cli", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00 } },
{ "sei", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00 } },
{ "tya", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00 } },
{ "clv", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00 } },
{ "cld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00 } },
{ "sed", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00 } },
{ "bit", AMC_BIT, { 0x00, 0x24, 0x89, 0x2c, 0x00, 0x34, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "stz", AMC_STZ, { 0x00, 0x64, 0x00, 0x9c, 0x00, 0x74, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "trb", AMC_TRB, { 0x00, 0x14, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tsb", AMC_TRB, { 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "jmp", AMC_JMP, { 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x7c, 0x00 } },
{ "sty", AMM_STY, { 0x00, 0x84, 0x00, 0x8c, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ldy", AMM_LDY, { 0x00, 0xa4, 0xa0, 0xac, 0x00, 0xb4, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cpy", AMM_CPY, { 0x00, 0xc4, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cpx", AMM_CPY, { 0x00, 0xe4, 0xe0, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "txa", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00 } },
{ "txs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00 } },
{ "tax", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00 } },
{ "tsx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00 } },
{ "dex", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00 } },
{ "nop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00 } },
// WDC specific (18 instructions)
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs
{ "stp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00 } },
{ "wai", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00 } },
{ "bbr0", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f } },
{ "bbr1", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f } },
{ "bbr2", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f } },
{ "bbr3", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f } },
{ "bbr4", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f } },
{ "bbr5", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f } },
{ "bbr6", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f } },
{ "bbr7", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f } },
{ "bbs0", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f } },
{ "bbs1", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f } },
{ "bbs2", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf } },
{ "bbs3", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf } },
{ "bbs4", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf } },
{ "bbs5", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf } },
{ "bbs6", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef } },
{ "bbs7", AMC_BBR, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0xff } },
};
const char* aliases_65C02[] = {
"bcc", "blt",
"bcs", "bge",
nullptr, nullptr
};
static const int num_opcodes_65C02 = sizeof(opcodes_65C02) / sizeof(opcodes_65C02[0]);
struct mnem opcodes_65816[] = {
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs [zp] [zp],y absl absl,x b,s (b,s),y[$000] b,b
{ "brk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "jsr", AM8_JSR, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "jsl", AM8_JSL, { 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rti", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rts", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rtl", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ora", AM8_ORA, { 0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x07, 0x17, 0x0f, 0x1f, 0x03, 0x13, 0x00, 0x00 } },
{ "and", AM8_ORA, { 0x21, 0x25, 0x29, 0x2d, 0x31, 0x35, 0x39, 0x3d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x27, 0x37, 0x2f, 0x3f, 0x23, 0x33, 0x00, 0x00 } },
{ "eor", AM8_ORA, { 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, 0x5d, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x47, 0x57, 0x4f, 0x5f, 0x43, 0x53, 0x00, 0x00 } },
{ "adc", AM8_ORA, { 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x67, 0x77, 0x6f, 0x7f, 0x63, 0x73, 0x00, 0x00 } },
{ "sta", AM8_STA, { 0x81, 0x85, 0x00, 0x8d, 0x91, 0x95, 0x99, 0x9d, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x87, 0x97, 0x8f, 0x9f, 0x83, 0x93, 0x00, 0x00 } },
{ "lda", AM8_ORA, { 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0xa7, 0xb7, 0xaf, 0xbf, 0xa3, 0xb3, 0x00, 0x00 } },
{ "cmp", AM8_ORA, { 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd5, 0xd9, 0xdd, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0xc7, 0xd7, 0xcf, 0xdf, 0xc3, 0xd3, 0x00, 0x00 } },
{ "sbc", AM8_ORA, { 0xe1, 0xe5, 0xe9, 0xed, 0xf1, 0xf5, 0xf9, 0xfd, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00, 0xe7, 0xf7, 0xef, 0xff, 0xe3, 0xf3, 0x00, 0x00 } },
{"oral", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x1f, 0x00, 0x00, 0x00, 0x00 } },
{"andl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x3f, 0x00, 0x00, 0x00, 0x00 } },
{"eorl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5f, 0x00, 0x00, 0x00, 0x00 } },
{"adcl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x7f, 0x00, 0x00, 0x00, 0x00 } },
{"stal", AM8_STL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x9f, 0x00, 0x00, 0x00, 0x00 } },
{"ldal", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xbf, 0x00, 0x00, 0x00, 0x00 } },
{"cmpl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0xdf, 0x00, 0x00, 0x00, 0x00 } },
{"sbcl", AM8_ORL, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xff, 0x00, 0x00, 0x00, 0x00 } },
{ "asl", AMM_ASL, { 0x00, 0x06, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x1e, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rol", AMM_ASL, { 0x00, 0x26, 0x00, 0x2e, 0x00, 0x36, 0x00, 0x3e, 0x00, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "lsr", AMM_ASL, { 0x00, 0x46, 0x00, 0x4e, 0x00, 0x56, 0x00, 0x5e, 0x00, 0x4a, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ror", AMM_ASL, { 0x00, 0x66, 0x00, 0x6e, 0x00, 0x76, 0x00, 0x7e, 0x00, 0x6a, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "stx", AMM_STX, { 0x00, 0x86, 0x00, 0x8e, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ldx", AM8_LDX, { 0x00, 0xa6, 0xa2, 0xae, 0x00, 0xb6, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "dec", AMC_DEC, { 0x00, 0xc6, 0x00, 0xce, 0x00, 0xd6, 0x00, 0xde, 0x00, 0x3a, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "inc", AMC_DEC, { 0x00, 0xe6, 0x00, 0xee, 0x00, 0xf6, 0x00, 0xfe, 0x00, 0x1a, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "dea", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ina", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "php", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "plp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "pha", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs [zp] [zp],y absl absl,x b,s (b,s),y[$0000]b,b
{ "pla", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "phy", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ply", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "phx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "plx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "dey", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tay", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "iny", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "inx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bpl", AMM_BRA, { 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bmi", AMM_BRA, { 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bvc", AMM_BRA, { 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bvs", AMM_BRA, { 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bra", AMM_BRA, { 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "brl", AM8_BRL, { 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bcc", AMM_BRA, { 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bcs", AMM_BRA, { 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bne", AMM_BRA, { 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "beq", AMM_BRA, { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "clc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "sec", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cli", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "sei", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tya", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "clv", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "sed", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "bit", AM8_BIT, { 0x00, 0x24, 0x89, 0x2c, 0x00, 0x34, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "stz", AMC_STZ, { 0x00, 0x64, 0x00, 0x9c, 0x00, 0x74, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "trb", AMC_TRB, { 0x00, 0x14, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tsb", AMC_TRB, { 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
// nam modes (zp,x) zp # $0000 (zp),y zp,x abs,y abs,x (xx) A empty (zp)(abs,x)zp,abs [zp] [zp],y absl absl,x b,s (b,s),y[$0000]b,b
{ "jmp", AM8_JMP, { 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xdc, 0x00 } },
{ "jml", AM8_JML, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xdc, 0x00 } },
{ "sty", AMM_STY, { 0x00, 0x84, 0x00, 0x8c, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "ldy", AM8_LDY, { 0x00, 0xa4, 0xa0, 0xac, 0x00, 0xb4, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cpy", AM8_CPY, { 0x00, 0xc4, 0xc0, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cpx", AM8_CPY, { 0x00, 0xe4, 0xe0, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "txa", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "txs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tax", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tsx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "dex", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "nop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "cop", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "wdm", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "mvp", AM8_MVN, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44 } },
{ "mvn", AM8_MVN, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54 } },
{ "pea", AMM_ABS, { 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "pei", AM8_PEI, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "per", AM8_PER, { 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "rep", AM8_REP, { 0x00, 0xc2, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "sep", AM8_REP, { 0x00, 0xe2, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "phd", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tcs", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "pld", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tsc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "phk", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tcd", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tdc", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "phb", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "txy", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "plb", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "tyx", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "wai", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "stp", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "xba", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ "xce", AMM_NON, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
};
const char* aliases_65816[] = {
"bcc", "blt",
"bcs", "bge",
"tcs", "tas",
"tsc", "tsa",
"xba", "swa",
"tcd", "tad",
"tdc", "tda",
nullptr, nullptr
};
static const int num_opcodes_65816 = sizeof(opcodes_65816) / sizeof(opcodes_65816[0]);
unsigned char timing_65816[] = {
0x4e, 0x1c, 0x4e, 0x28, 0x3a, 0x26, 0x3a, 0x1c, 0x46, 0x24, 0x44, 0x48, 0x4c, 0x28, 0x5c, 0x2a,
0x44, 0x1a, 0x1a, 0x2e, 0x3a, 0x18, 0x6c, 0x1c, 0x44, 0x28, 0x44, 0x44, 0x4c, 0x28, 0x5e, 0x2a,
0x4c, 0x1c, 0x50, 0x28, 0x16, 0x26, 0x3a, 0x1c, 0x48, 0x24, 0x44, 0x4a, 0x28, 0x28, 0x4c, 0x2a,
0x44, 0x1a, 0x1a, 0x2e, 0x18, 0x18, 0x3c, 0x1c, 0x44, 0x28, 0x44, 0x44, 0x28, 0x28, 0x4e, 0x2a,
0x4c, 0x1c, 0x42, 0x28, 0x42, 0x16, 0x6a, 0x1c, 0x26, 0x24, 0x44, 0x46, 0x46, 0x28, 0x5c, 0x2a,
0x44, 0x1a, 0x1a, 0x2e, 0x42, 0x18, 0x6c, 0x1c, 0x44, 0x28, 0x76, 0x44, 0x48, 0x28, 0x5e, 0x2a,
0x4c, 0x1c, 0x4c, 0x28, 0x16, 0x26, 0x3a, 0x1c, 0x28, 0x24, 0x44, 0x4c, 0x4a, 0x28, 0x4c, 0x2a,
0x44, 0x1a, 0x1a, 0x2e, 0x28, 0x18, 0x3c, 0x1c, 0x44, 0x28, 0x78, 0x44, 0x4c, 0x28, 0x4e, 0x2a,
0x46, 0x1c, 0x48, 0x28, 0x86, 0x16, 0x86, 0x1c, 0x44, 0x24, 0x44, 0x46, 0x78, 0x28, 0x78, 0x2a,
0x44, 0x1c, 0x1a, 0x2e, 0x88, 0x18, 0x88, 0x1c, 0x44, 0x2a, 0x44, 0x44, 0x28, 0x2a, 0x2a, 0x2a,
0x74, 0x1c, 0x74, 0x28, 0x86, 0x16, 0x86, 0x1c, 0x44, 0x24, 0x44, 0x48, 0x78, 0x28, 0x78, 0x2a,
0x44, 0x1a, 0x1a, 0x2e, 0x88, 0x18, 0x88, 0x1c, 0x44, 0x28, 0x44, 0x44, 0x78, 0x28, 0x78, 0x2a,
0x74, 0x1c, 0x46, 0x28, 0x86, 0x16, 0x6a, 0x1c, 0x44, 0x24, 0x44, 0x26, 0x78, 0x28, 0x5c, 0x2a,
0x44, 0x1a, 0x1a, 0x2e, 0x4c, 0x18, 0x6c, 0x1c, 0x44, 0x28, 0x76, 0x46, 0x4c, 0x28, 0x5e, 0x2a,
0x74, 0x3c, 0x46, 0x48, 0x86, 0x36, 0x6a, 0x3c, 0x44, 0x44, 0x44, 0x46, 0x78, 0x48, 0x5c, 0x4a,
0x44, 0x3a, 0x3a, 0x4e, 0x4a, 0x38, 0x6c, 0x3c, 0x44, 0x48, 0x78, 0x44, 0x50, 0x48, 0x5e, 0x4a
};
// m=0, i=0, dp!=0
unsigned char timing_65816_plus[9][3] = {
{ 0, 0, 0 }, // 6502 plus timing check bit 0
{ 1, 0, 1 }, // acc 16 bit + dp!=0
{ 1, 0, 0 }, // acc 16 bit
{ 0, 0, 1 }, // dp != 0
{ 0, 0, 0 }, // no plus
{ 2, 0, 0 }, // acc 16 bit yields 2+
{ 2, 0, 1 }, // acc 16 bit yields 2+ + dp!=0
{ 0, 1, 0 }, // idx 16 bit
{ 0, 1, 1 } // idx 16 bit + dp!=0
};
// 65C02
// http://6502.org/tutorials/65c02opcodes.html
// http://www.oxyron.de/html/opcodesc02.html
// 65816
// http://wiki.superfamicom.org/snes/show/65816+Reference#fn:14
// http://softpixel.com/~cwright/sianse/docs/65816NFO.HTM
// http://www.oxyron.de/html/opcodes816.html
// How instruction argument is encoded
enum CODE_ARG {
CA_NONE, // single byte instruction
CA_ONE_BYTE, // instruction carries one byte
CA_TWO_BYTES, // instruction carries two bytes
CA_THREE_BYTES, // instruction carries three bytes
CA_BRANCH, // instruction carries an 8 bit relative address
CA_BRANCH_16, // instruction carries a 16 bit relative address
CA_BYTE_BRANCH, // instruction carries one byte and one branch
CA_TWO_ARG_BYTES, // two separate values
};
enum CPUIndex {
CPU_6502,
CPU_6502_ILLEGAL,
CPU_65C02,
CPU_65C02_WDC,
CPU_65816
};
// CPU by index
struct CPUDetails {
mnem *opcodes;
int num_opcodes;
const char* name;
const char** aliases;
const unsigned char *timing;
} aCPUs[] = {
{ opcodes_6502, num_opcodes_6502 - NUM_ILLEGAL_6502_OPS, "6502", aliases_6502, timing_6502 },
{ opcodes_6502, num_opcodes_6502, "6502ill", aliases_6502, timing_6502 },
{ opcodes_65C02, num_opcodes_65C02 - NUM_WDC_65C02_SPECIFIC_OPS, "65C02", aliases_65C02, nullptr },
{ opcodes_65C02, num_opcodes_65C02, "65C02WDC", aliases_65C02, nullptr },
{ opcodes_65816, num_opcodes_65816, "65816", aliases_65816, timing_65816 },
};
static const int nCPUs = sizeof(aCPUs) / sizeof(aCPUs[0]);
// hardtexted strings
static const strref c_comment("//");
static const strref word_char_range("!0-9a-zA-Z_@$!#");
static const strref label_end_char_range("!0-9a-zA-Z_@$!.");
static const strref label_end_char_range_merlin("!0-9a-zA-Z_@$]:?");
static const strref filename_end_char_range("!0-9a-zA-Z_!@#$%&()/\\-.");
static const strref keyword_equ("equ");
static const strref str_label("label");
static const strref str_const("const");
static const strref struct_byte("byte");
static const strref struct_word("word");
static const strref import_source("source");
static const strref import_binary("binary");
static const strref import_c64("c64");
static const strref import_text("text");
static const strref import_object("object");
static const strref import_symbols("symbols");
static const char* aAddrModeFmt[] = {
"%s ($%02x,x)", // 00
"%s $%02x", // 01
"%s #$%02x", // 02
"%s $%04x", // 03
"%s ($%02x),y", // 04
"%s $%02x,x", // 05
"%s $%04x,y", // 06
"%s $%04x,x", // 07
"%s ($%04x)", // 08
"%s A", // 09
"%s ", // 0a
"%s ($%02x)", // 0b
"%s ($%04x,x)", // 0c
"%s $%02x, $%04x", // 0d
"%s [$%02x]", // 0e
"%s [$%02x],y", // 0f
"%s $%06x", // 10
"%s $%06x,x", // 11
"%s $%02x,s", // 12
"%s ($%02x,s),y", // 13
"%s [$%04x]", // 14
"%s $%02x,$%02x", // 15
};
static const char *str_section_type[] = {
"UNDEFINED", // not set
"CODE", // default type
"DATA", // data section (matters for GS/OS OMF)
"BSS", // uninitialized data section
"ZEROPAGE" // ununitialized data section in zero page / direct page
};
static const int num_section_type_str = sizeof(str_section_type) / sizeof(str_section_type[0]);
typedef struct {
const char *name;
AssemblerDirective directive;
} DirectiveName;
DirectiveName aDirectiveNames[] {
{ "CPU", AD_CPU },
{ "PROCESSOR", AD_CPU },
{ "PC", AD_ORG },
{ "ORG", AD_ORG },
{ "LOAD", AD_LOAD },
{ "EXPORT", AD_EXPORT },
{ "SECTION", AD_SECTION },
{ "SEG", AD_SECTION }, // DASM version of SECTION
{ "SEGMENT", AD_SECTION }, // CA65 version of SECTION
{ "LINK", AD_LINK },
{ "XDEF", AD_XDEF },
{ "XREF", AD_XREF },
{ "INCOBJ", AD_INCOBJ },
{ "ALIGN", AD_ALIGN },
{ "MACRO", AD_MACRO },
{ "MAC", AD_MACRO }, // MERLIN
{ "EVAL", AD_EVAL },
{ "PRINT", AD_EVAL },
{ "ECHO", AD_EVAL }, // DASM version of EVAL/PRINT
{ "BYTE", AD_BYTES },
{ "BYTES", AD_BYTES },
{ "WORD", AD_WORDS },
{ "WORDS", AD_WORDS },
{ "LONG", AD_ADRL },
{ "DC", AD_DC },
{ "DV", AD_DC }, // DASM variation of DC which allows expressions
{ "TEXT", AD_TEXT },
{ "INCLUDE", AD_INCLUDE },
{ "INCBIN", AD_INCBIN },
{ "IMPORT", AD_IMPORT },
{ "CONST", AD_CONST },
{ "LABEL", AD_LABEL },
{ "STRING", AD_STRING },
{ "UNDEF", AD_UNDEF },
{ "INCSYM", AD_INCSYM },
{ "LABPOOL", AD_LABPOOL },
{ "POOL", AD_LABPOOL },
{ "IF", AD_IF },
{ "IFDEF", AD_IFDEF },
{ "ELSE", AD_ELSE },
{ "ELIF", AD_ELIF },
{ "ENDIF", AD_ENDIF },
{ "STRUCT", AD_STRUCT },
{ "ENUM", AD_ENUM },
{ "REPT", AD_REPT },
{ "REPEAT", AD_REPT }, // ca65 version of rept
{ "INCDIR", AD_INCDIR },
{ "A16", AD_A16 }, // A16: Set 16 bit accumulator mode
{ "A8", AD_A8 }, // A8: Set 8 bit accumulator mode
{ "XY16", AD_XY16 }, // XY16: Set 16 bit index register mode
{ "XY8", AD_XY8 }, // XY8: Set 8 bit index register mode
{ "I16", AD_XY16 }, // I16: Set 16 bit index register mode
{ "I8", AD_XY8 }, // I8: Set 8 bit index register mode
{ "DUMMY", AD_DUMMY },
{ "DUMMY_END", AD_DUMMY_END },
{ "DS", AD_DS }, // Define space
{ "ABORT", AD_ABORT },
{ "ERR", AD_ABORT }, // DASM version of ABORT
};
// Merlin specific directives separated from regular directives to avoid confusion
DirectiveName aDirectiveNamesMerlin[] {
{ "MX", AD_MX }, // MERLIN
{ "STR", AD_LNK }, // MERLIN
{ "DA", AD_WORDS }, // MERLIN
{ "DW", AD_WORDS }, // MERLIN
{ "ASC", AD_TEXT }, // MERLIN
{ "PUT", AD_INCLUDE }, // MERLIN
{ "DDB", AD_WORDS }, // MERLIN
{ "DB", AD_BYTES }, // MERLIN
{ "DFB", AD_BYTES }, // MERLIN
{ "HEX", AD_HEX }, // MERLIN
{ "DO", AD_IF }, // MERLIN
{ "FIN", AD_ENDIF }, // MERLIN
{ "EJECT", AD_EJECT }, // MERLIN
{ "OBJ", AD_EJECT }, // MERLIN
{ "TR", AD_EJECT }, // MERLIN
{ "END", AD_EJECT }, // MERLIN
{ "REL", AD_EJECT }, // MERLIN
{ "USR", AD_USR }, // MERLIN
{ "DUM", AD_DUMMY }, // MERLIN
{ "DEND", AD_DUMMY_END }, // MERLIN
{ "LST", AD_LST }, // MERLIN
{ "LSTDO", AD_LST }, // MERLIN
{ "LUP", AD_REPT }, // MERLIN
{ "SAV", AD_SAV }, // MERLIN
{ "DSK", AD_SAV }, // MERLIN
{ "LNK", AD_LNK }, // MERLIN
{ "XC", AD_XC }, // MERLIN
{ "ENT", AD_ENT }, // MERLIN (xdef, but label on same line)
{ "EXT", AD_EXT }, // MERLIN (xref, which are implied in x65 object files)
{ "ADR", AD_ADR }, // ADR: MERLIN store 3 byte word
{ "ADRL", AD_ADRL }, // ADRL: MERLIN store 4 byte word
{ "CYC", AD_CYC }, // MERLIN: Start and stop cycle counter
};
static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]);
static const int nDirectiveNamesMerlin = sizeof(aDirectiveNamesMerlin) / sizeof(aDirectiveNamesMerlin[0]);
// Binary search over an array of unsigned integers, may contain multiple instances of same key
unsigned int FindLabelIndex(unsigned int hash, unsigned int *table, unsigned int count)
{
unsigned int max = count;
unsigned int first = 0;
while (count!=first) {
int index = (first+count)/2;
unsigned int read = table[index];
if (hash==read) {
while (index && table[index-1]==hash)
index--; // guarantee first identical index returned on match
return index;
} else if (hash>read)
first = index+1;
else
count = index;
}
if (count<max && table[count]<hash)
count++;
else if (count && table[count-1]>hash)
count--;
return count;
}
//
//
// ASSEMBLER STATE
//
//
// pairArray is basically two vectors sharing a size without constructors on growth or insert
template <class H, class V> class pairArray {
protected:
H *keys;
V *values;
unsigned int _count;
unsigned int _capacity;
public:
pairArray() : keys(nullptr), values(nullptr), _count(0), _capacity(0) {}
void reserve(unsigned int size) {
if (size>_capacity) {
H *new_keys = (H*)malloc(sizeof(H) * size); if (!new_keys) { return; }
V *new_values = (V*)malloc(sizeof(V) * size); if (!new_values) { free(new_keys); return; }
if (keys && values) {
memcpy(new_keys, keys, sizeof(H) * _count);
memcpy(new_values, values, sizeof(V) * _count);
free(keys); free(values);
}
keys = new_keys;
values = new_values;
_capacity = size;
}
}
bool insert(unsigned int pos) {
if (pos>_count)
return false;
if (_count==_capacity)
reserve(_capacity+64);
if (pos<_count) {
memmove(keys+pos+1, keys+pos, sizeof(H) * (_count-pos));
memmove(values+pos+1, values+pos, sizeof(V) * (_count-pos));
}
memset(keys+pos, 0, sizeof(H));
memset(values+pos, 0, sizeof(V));
_count++;
return true;
}
bool insert(unsigned int pos, H key) {
if (insert(pos) && keys) {
keys[pos] = key;
return true;
}
return false;
}
void remove(unsigned int pos) {
if (pos<_count) {
_count--;
if (pos<_count) {
memmove(keys+pos, keys+pos+1, sizeof(H) * (_count-pos));
memmove(values+pos, values+pos+1, sizeof(V) * (_count-pos));
}
}
}
H* getKeys() { return keys; }
H& getKey(unsigned int pos) { return keys[pos]; }
V* getValues() { return values; }
V& getValue(unsigned int pos) { return values[pos]; }
unsigned int count() const { return _count; }
unsigned int capacity() const { return _capacity; }
void clear() {
if (keys!=nullptr)
free(keys);
keys = nullptr;
if (values!=nullptr)
free(values);
values = nullptr;
_capacity = 0;
_count = 0;
}
};
// relocs are cheaper than full expressions and work with
// local labels for relative sections which would otherwise
// be out of scope at link time.
struct Reloc {
int base_value;
int section_offset; // offset into this section
int target_section; // which section does this reloc target?
char bytes; // number of bytes to write
char shift; // number of bits to shift to get value
Reloc() : base_value(0), section_offset(-1), target_section(-1), bytes(0), shift(0) {}
Reloc(int base, int offs, int sect, char num_bytes, char bit_shift) :
base_value(base), section_offset(offs), target_section(sect), bytes(num_bytes), shift(bit_shift) {}
};
typedef std::vector<struct Reloc> relocList;
// For assembly listing this remembers the location of each line
struct ListLine {
enum Flags {
MNEMONIC = 0x01,
KEYWORD = 0x02,
CYCLES_START = 0x04,
CYCLES_STOP = 0x08,
};
strref source_name; // source file index name
strref code; // line of code this represents
int address; // start address of this line
int size; // number of bytes generated for this line
int line_offs; // offset into code
int flags; // only output code if generated by code
bool wasMnemonic() const { return !!(flags & MNEMONIC); }
bool startClock() const { return !!(flags & CYCLES_START); }
bool stopClock() const { return !!(flags & CYCLES_STOP); }
};
typedef std::vector<struct ListLine> Listing;
enum SectionType : char { // enum order indicates fixed address linking priority
ST_UNDEFINED, // not set
ST_CODE, // default type
ST_DATA, // data section (matters for GS/OS OMF)
ST_BSS, // uninitialized data section
ST_ZEROPAGE, // uninitialized data section in zero page / direct page
ST_REMOVED // removed, don't export to object file
};
// start of data section support
// Default is a relative section
// Whenever org or dum with address is encountered => new section
// If org is fixed and < $200 then it is a dummy section Otherwise clear dummy section
typedef struct Section {
// section name, same named section => append
strref name; // name of section for comparison
strref export_append; // append this name to export of file
strref include_from; // which file did this section originate from?
// generated address status
int load_address; // if assigned a load address
int start_address;
int address; // relative or absolute PC
int align_address; // for relative sections that needs alignment
// merged sections
int merged_offset; // -1 if not merged
int merged_section; // which section merged with
// data output
unsigned char *output; // memory for this section
unsigned char *curr; // current pointer for this section
size_t output_capacity; // current output capacity
// reloc data
relocList *pRelocs; // link time resolve (not all sections need this)
Listing *pListing; // if list output
// grouped sections
int next_group; // next section of a group of relative sections or -1
int first_group; // >=0 if another section is grouped with this section
bool address_assigned; // address is absolute if assigned
bool dummySection; // true if section does not generate data, only labels
SectionType type; // distinguishing section type for relocatable output
void reset() { // explicitly cleaning up sections, not called from Section destructor
name.clear(); export_append.clear(); include_from.clear();
start_address = address = load_address = 0x0; type = ST_CODE;
address_assigned = false; output = nullptr; curr = nullptr;
dummySection = false; output_capacity = 0; merged_offset = -1; merged_section = -1;
align_address = 1; if (pRelocs) delete pRelocs;
next_group = first_group = -1;
pRelocs = nullptr;
if (pListing) delete pListing;
pListing = nullptr;
}
void Cleanup() { if (output) free(output); reset(); }
bool empty() const { return merged_offset<0 && curr==output; }
bool unused() const { return !address_assigned && address == start_address; }
int DataOffset() const { return int(curr - output); }
int size() const { return (int)(curr - output); }
int addr_size() const { return address - start_address; }
const unsigned char *get() { return output; }
int GetPC() const { return address; }
void AddAddress(int value) { address += value; }
void SetLoadAddress(int addr) { load_address = addr; }
int GetLoadAddress() const { return load_address; }
void SetDummySection(bool enable) { dummySection = enable; type = ST_BSS; }
bool IsDummySection() const { return dummySection; }
bool IsRelativeSection() const { return address_assigned == false; }
bool IsMergedSection() const { return merged_offset >= 0; }
void AddReloc(int base, int offset, int section, char bytes, char shift);
Section() : pRelocs(nullptr), pListing(nullptr) { reset(); }
Section(strref _name, int _address) : pRelocs(nullptr), pListing(nullptr) {
reset(); name = _name; start_address = load_address = address = _address;
address_assigned = true;
}
Section(strref _name) : pRelocs(nullptr), pListing(nullptr) {
reset(); name = _name;
start_address = load_address = address = 0; address_assigned = false;
}
~Section() { }
// Append data to a section
StatusCode CheckOutputCapacity(unsigned int addSize);
void AddByte(int b);
void AddWord(int w);
void AddTriple(int l);
void AddBin(unsigned const char *p, int size);
void AddText(strref line, strref text_prefix);
void SetByte(size_t offs, int b) { output[offs] = b; }
void SetWord(size_t offs, int w) { output[offs] = w; output[offs+1] = w>>8; }
void SetTriple(size_t offs, int w) { output[offs] = w; output[offs+1] = w>>8; output[offs+2] = w>>16; }
void SetQuad(size_t offs, int w) { output[offs] = w; output[offs+1] = w>>8; output[offs+2] = w>>16; output[offs+3] = w>>24; }
} Section;
// Symbol list entry (in order of parsing)
struct MapSymbol {
strref name; // string name
int value;
short section;
bool local; // local variables
};
typedef std::vector<struct MapSymbol> MapSymbolArray;
// Data related to a label
typedef struct {
public:
strref label_name; // the name of this label
strref pool_name; // name of the pool that this label is related to
int value;
int section; // rel section address labels belong to a section, -1 if fixed address or assigned
int mapIndex; // index into map symbols in case of late resolve
bool evaluated; // a value may not yet be evaluated
bool pc_relative; // this is an inline label describing a point in the code
bool constant; // the value of this label can not change
bool external; // this label is globally accessible
bool reference; // this label is accessed from external and can't be used for evaluation locally
} Label;
// String data
typedef struct {
public:
strref string_name; // name of the string
strref string_const; // string contents if source reference
strovl string_value; // string contents if modified, initialized to null string
StatusCode Append(strref append);
StatusCode ParseLine(strref line);
strref get() { return string_value.valid() ? string_value.get_strref() : string_const; }
void clear() { if (string_value.cap()) { free(string_value.charstr());
string_value.invalidate(); string_value.clear(); }
string_const.clear();
}
} StringSymbol;
// If an expression can't be evaluated immediately, this is required
// to reconstruct the result when it can be.
typedef struct {
enum Type { // When an expression is evaluated late, determine how to encode the result
LET_LABEL, // this evaluation applies to a label and not memory
LET_ABS_REF, // calculate an absolute address and store at 0, +1
LET_ABS_L_REF, // calculate a bank + absolute address and store at 0, +1, +2
LET_ABS_4_REF, // calculate a 32 bit number
LET_BRANCH, // calculate a branch offset and store at this address
LET_BRANCH_16, // calculate a branch offset of 16 bits and store at this address
LET_BYTE, // calculate a byte and store at this address
};
int target; // offset into output buffer
int address; // current pc
int scope; // scope pc
int scope_depth; // relevant for scope end
short section; // which section to apply to.
short rept; // value of rept
int file_ref; // -1 if current or xdef'd otherwise index of file for label
strref label; // valid if this is not a target but another label
strref expression;
strref source_file;
Type type;
} LateEval;
// A macro is a text reference to where it was defined
typedef struct {
strref name;
strref macro;
strref source_name; // source file name (error output)
strref source_file; // entire source file (req. for line #)
bool params_first_line; // the first line of this macro are parameters
} Macro;
// All local labels are removed when a global label is defined but some when a scope ends
typedef struct {
strref label;
int scope_depth;
bool scope_reserve; // not released for global label, only scope
} LocalLabelRecord;
// Label pools allows C like stack frame label allocation
typedef struct {
strref pool_name;
short numRanges; // normally 1 range, support multiple for ease of use
short scopeDepth; // Required for scope closure cleanup
unsigned short ranges[MAX_POOL_RANGES*2]; // 2 shorts per range
unsigned int usedMap[(MAX_POOL_BYTES+15)>>4]; // 2 bits per byte to store byte count of label
StatusCode Reserve(int numBytes, unsigned int &addr);
StatusCode Release(unsigned int addr);
} LabelPool;
// One member of a label struct
struct MemberOffset {
unsigned short offset;
unsigned int name_hash;
strref name;
strref sub_struct;
};
// Label struct
typedef struct {
strref name;
unsigned short first_member;
unsigned short numMembers;
unsigned short size;
} LabelStruct;
// object file labels that are not xdef'd end up here
struct ExtLabels {
pairArray<unsigned int, Label> labels;
};
// EvalExpression needs a location reference to work out some addresses
struct EvalContext {
int pc; // current address at point of eval
int scope_pc; // current scope open at point of eval
int scope_end_pc; // late scope closure after eval
int scope_depth; // scope depth for eval (must match current for scope_end_pc to eval)
int relative_section; // return can be relative to this section
int file_ref; // can access private label from this file or -1
int rept_cnt; // current repeat counter
EvalContext() {}
EvalContext(int _pc, int _scope, int _close, int _sect, int _rept_cnt) :
pc(_pc), scope_pc(_scope), scope_end_pc(_close), scope_depth(-1),
relative_section(_sect), file_ref(-1), rept_cnt(_rept_cnt) {}
};
// Source context is current file (include file, etc.) or current macro.
typedef struct {
strref source_name; // source file name (error output)
strref source_file; // entire source file (req. for line #)
strref code_segment; // the segment of the file for this context
strref read_source; // current position/length in source file
strref next_source; // next position/length in source file
short repeat; // how many times to repeat this code segment
short repeat_total; // initial number of repeats for this code segment
short conditional_ctx; // conditional depth at root of this context
bool scoped_context;
void restart() { read_source = code_segment; }
bool complete() { repeat--; return repeat <= 0; }
} SourceContext;
// Context stack is a stack of currently processing text
class ContextStack {
private:
std::vector<SourceContext> stack;
SourceContext *currContext;
public:
ContextStack() : currContext(nullptr) { stack.reserve(32); }
SourceContext& curr() { return *currContext; }
const SourceContext& curr() const { return *currContext; }
void push(strref src_name, strref src_file, strref code_seg, int rept = 1) {
if (currContext)
currContext->read_source = currContext->next_source;
SourceContext context;
context.source_name = src_name;
context.source_file = src_file;
context.code_segment = code_seg;
context.read_source = code_seg;
context.next_source = code_seg;
context.repeat = rept;
context.repeat_total = rept;
context.scoped_context = false;
stack.push_back(context);
currContext = &stack[stack.size()-1];
}
void pop() { stack.pop_back(); currContext = stack.size() ? &stack[stack.size()-1] : nullptr; }
bool has_work() { return currContext!=nullptr; }
};
// The state of the assembler
class Asm {
public:
pairArray<unsigned int, Label> labels;
pairArray<unsigned int, StringSymbol> strings;
pairArray<unsigned int, Macro> macros;
pairArray<unsigned int, LabelPool> labelPools;
pairArray<unsigned int, LabelStruct> labelStructs;
pairArray<unsigned int, strref> xdefs; // labels matching xdef names will be marked as external
std::vector<LateEval> lateEval;
std::vector<LocalLabelRecord> localLabels;
std::vector<char*> loadedData; // free when assembler is completed
std::vector<MemberOffset> structMembers; // labelStructs refer to sets of structMembers
std::vector<strref> includePaths;
std::vector<Section> allSections;
std::vector<ExtLabels> externals; // external labels organized by object file
MapSymbolArray map;
// CPU target
struct mnem *opcode_table;
int opcode_count;
CPUIndex cpu, list_cpu;
OPLookup aInstructions[MAX_OPCODES_DIRECTIVES];
int num_instructions;
int default_org;
// context for macros / include files
ContextStack contextStack;
// Current section
Section *current_section;
// Special syntax rules
AsmSyntax syntax;
// Conditional assembly vars
int conditional_depth; // conditional depth / base depth for context
strref conditional_source[MAX_CONDITIONAL_DEPTH]; // start of conditional for error report
char conditional_nesting[MAX_CONDITIONAL_DEPTH];
bool conditional_consumed[MAX_CONDITIONAL_DEPTH];
// Scope info
int scope_address[MAX_SCOPE_DEPTH];
int scope_depth;
// Eval relative result (only valid if EvalExpression returns STATUS_RELATIVE_SECTION)
int lastEvalSection;
int lastEvalValue;
char lastEvalShift;
strref export_base_name; // binary output name if available
strref last_label; // most recently defined label for Merlin macro
char list_flags; // listing flags accumulating for each line
bool accumulator_16bit; // 65816 specific software dependent immediate mode
bool index_reg_16bit; // -"-
char cycle_counter_level; // merlin toggles the cycle counter rather than hierarchically evals
bool error_encountered; // if any error encountered, don't export binary
bool list_assembly; // generate assembler listing
bool end_macro_directive; // whether to use { } or macro / endmacro for macro scope
// Convert source to binary
void Assemble(strref source, strref filename, bool obj_target);
// Push a new context and handle enter / exit of context
StatusCode PushContext(strref src_name, strref src_file, strref code_seg, int rept = 1);
StatusCode PopContext();
// Generate assembler listing if requested
bool List(strref filename);
// Generate source for all valid instructions and addressing modes for current CPU
bool AllOpcodes(strref filename);
// Clean up memory allocations, reset assembler state
void Cleanup();
// Make sure there is room to write more code
StatusCode CheckOutputCapacity(unsigned int addSize);
// Operations on current section
void SetSection(strref name, int address); // fixed address section
void SetSection(strref name); // relative address section
void LinkLabelsToAddress(int section_id, int section_new, int section_address);
StatusCode LinkRelocs(int section_id, int section_new, int section_address);
StatusCode AssignAddressToSection(int section_id, int address);
StatusCode LinkSections(strref name); // link relative address sections with this name here
StatusCode MergeSections(int section_id, int section_merge); // Combine the result of a section onto another
StatusCode MergeSectionsByName(int first_section);
StatusCode MergeAllSections(int first_section);
void DummySection(int address); // non-data section (fixed)
void DummySection(); // non-data section (relative)
void EndSection(); // pop current section
Section& CurrSection() { return *current_section; }
void AssignAddressToGroup(); // Merlin LNK support
unsigned char* BuildExport(strref append, int &file_size, int &addr);
int GetExportNames(strref *aNames, int maxNames);
StatusCode LinkZP();
int SectionId() { return int(current_section - &allSections[0]); }
int SectionId(Section &s) { return (int)(&s - &allSections[0]); }
void AddByte(int b) { CurrSection().AddByte(b); }
void AddWord(int w) { CurrSection().AddWord(w); }
void AddTriple(int l) { CurrSection().AddTriple(l); }
void AddBin(unsigned const char *p, int size) { CurrSection().AddBin(p, size); }
// Object file handling
StatusCode WriteObjectFile(strref filename); // write x65 object file
StatusCode ReadObjectFile(strref filename, int link_to_section = -1); // read x65 object file
// Apple II GS OMF
StatusCode WriteA2GS_OMF(strref filename, bool full_collapse);
// Scope management
StatusCode EnterScope();
StatusCode ExitScope();
// Macro management
StatusCode AddMacro(strref macro, strref source_name, strref source_file, strref &left);
StatusCode BuildMacro(Macro &m, strref arg_list);
// Structs
StatusCode BuildStruct(strref name, strref declaration);
StatusCode EvalStruct(strref name, int &value);
StatusCode BuildEnum(strref name, strref declaration);
// Calculate a value based on an expression.
EvalOperator RPNToken_Merlin(strref &expression, const struct EvalContext &etx,
EvalOperator prev_op, short &section, int &value);
EvalOperator RPNToken(strref &expression, const struct EvalContext &etx,
EvalOperator prev_op, short &section, int &value, strref &subexp);
StatusCode EvalExpression(strref expression, const struct EvalContext &etx, int &result);
void SetEvalCtxDefaults(struct EvalContext &etx);
int ReptCnt() const;
// Access labels
Label* GetLabel(strref label);
Label* GetLabel(strref label, int file_ref);
Label* AddLabel(unsigned int hash);
bool MatchXDEF(strref label);
StatusCode AssignLabel(strref label, strref line, bool make_constant = false);
StatusCode AddressLabel(strref label);
void LabelAdded(Label *pLabel, bool local = false);
StatusCode IncludeSymbols(strref line);
// Strings
StringSymbol *GetString(strref string_name);
StringSymbol *AddString(strref string_name, strref string_value);
StatusCode StringAction(StringSymbol *pStr, strref line);
StatusCode ParseStringOp(StringSymbol *pStr, strref line);
// Manage locals
void MarkLabelLocal(strref label, bool scope_label = false);
StatusCode FlushLocalLabels(int scope_exit = -1);
// Label pools
LabelPool* GetLabelPool(strref pool_name);
StatusCode AddLabelPool(strref name, strref args);
StatusCode AssignPoolLabel(LabelPool &pool, strref args);
void FlushLabelPools(int scope_exit);
// Late expression evaluation
void AddLateEval(int target, int pc, int scope_pc, strref expression,
strref source_file, LateEval::Type type);
void AddLateEval(strref label, int pc, int scope_pc,
strref expression, LateEval::Type type);
StatusCode CheckLateEval(strref added_label = strref(), int scope_end = -1, bool missing_is_error = false);
// Assembler Directives
StatusCode ApplyDirective(AssemblerDirective dir, strref line, strref source_file);
StatusCode Directive_Rept(strref line, strref source_file);
StatusCode Directive_Macro(strref line, strref source_file);
StatusCode Directive_String(strref line);
StatusCode Directive_Undef(strref line);
StatusCode Directive_Include(strref line);
StatusCode Directive_Incbin(strref line, int skip=0, int len=0);
StatusCode Directive_Import(strref line);
StatusCode Directive_ORG(strref line);
StatusCode Directive_LOAD(strref line);
StatusCode Directive_LNK(strref line);
StatusCode Directive_XDEF(strref line);
StatusCode Directive_XREF(strref label);
StatusCode Directive_DC(strref line, int width, strref source_file);
StatusCode Directive_DS(strref line);
StatusCode Directive_ALIGN(strref line);
StatusCode Directive_EVAL(strref line);
StatusCode Directive_HEX(strref line);
StatusCode Directive_ENUM_STRUCT(strref line, AssemblerDirective dir);
// Assembler steps
StatusCode GetAddressMode(strref line, bool flipXY, unsigned int validModes,
AddrMode &addrMode, int &len, strref &expression);
StatusCode AddOpcode(strref line, int index, strref source_file);
StatusCode BuildLine(strref line);
StatusCode BuildSegment();
// Display error in stderr
void PrintError(strref line, StatusCode error);
// Conditional Status
bool ConditionalAsm(); // Assembly is currently enabled
bool NewConditional(); // Start a new conditional block
void CloseConditional(); // Close a conditional block
void CheckConditionalDepth(); // Check if this conditional will nest the assembly (a conditional is already consumed)
void ConsumeConditional(); // This conditional block is going to be assembled, mark it as consumed
bool ConditionalConsumed(); // Has a block of this conditional already been assembled?
void SetConditional(); // This conditional block is not going to be assembled so mark that it is nesting
bool ConditionalAvail(); // Returns true if this conditional can be consumed
void ConditionalElse(); // Conditional else that does not enable block
void EnableConditional(bool enable); // This conditional block is enabled and the prior wasn't
// Conditional statement evaluation (A==B? A?)
StatusCode EvalStatement(strref line, bool &result);
// Add include folder
void AddIncludeFolder(strref path);
char* LoadText(strref filename, size_t &size);
char* LoadBinary(strref filename, size_t &size);
// Change CPU
void SetCPU(CPUIndex CPU);
// constructor
Asm() : opcode_table(opcodes_6502), opcode_count(num_opcodes_6502), num_instructions(0),
cpu(CPU_6502), list_cpu(CPU_6502) {
Cleanup(); localLabels.reserve(256); loadedData.reserve(16); lateEval.reserve(64); }
};
// Clean up work allocations
void Asm::Cleanup() {
for (std::vector<char*>::iterator i = loadedData.begin(); i != loadedData.end(); ++i) {
if (char *data = *i)
free(data);
}
map.clear();
labelPools.clear();
loadedData.clear();
labels.clear();
macros.clear();
allSections.clear();
for (unsigned int i = 0; i < strings.count(); ++i) {
StringSymbol &str = strings.getValue(i);
if (str.string_value.cap())
free(str.string_value.charstr());
}
strings.clear();
for (std::vector<ExtLabels>::iterator exti = externals.begin(); exti !=externals.end(); ++exti)
exti->labels.clear();
externals.clear();
// this section is relocatable but is assigned address $1000 if exporting without directives
SetSection(strref("default,code"));
current_section = &allSections[0];
syntax = SYNTAX_SANE;
default_org = 0x1000;
scope_depth = 0;
conditional_depth = 0;
conditional_nesting[0] = 0;
conditional_consumed[0] = false;
error_encountered = false;
list_assembly = false;
end_macro_directive = false;
accumulator_16bit = false; // default 65816 8 bit immediate mode
index_reg_16bit = false; // other CPUs won't be affected.
cycle_counter_level = 0;
}
int sortHashLookup(const void *A, const void *B) {
const OPLookup *_A = (const OPLookup*)A;
const OPLookup *_B = (const OPLookup*)B;
return _A->op_hash > _B->op_hash ? 1 : -1;
}
int BuildInstructionTable(OPLookup *pInstr, int maxInstructions, struct mnem *opcodes,
int count, const char **aliases, bool merlin)
{
// create an instruction table (mnemonic hash lookup)
int numInstructions = 0;
for (int i = 0; i < count; i++) {
OPLookup &op = pInstr[numInstructions++];
op.op_hash = strref(opcodes[i].instr).fnv1a_lower();
op.index = i;
op.type = OT_MNEMONIC;
}
// add instruction aliases
if (aliases) {
while (*aliases) {
strref orig(*aliases++);
strref alias(*aliases++);
for (int o=0; o<count; o++) {
if (orig.same_str_case(opcodes[o].instr)) {
OPLookup &op = pInstr[numInstructions++];
op.op_hash = alias.fnv1a_lower();
op.index = o;
op.type = OT_MNEMONIC;
break;
}
}
}
}
// add assembler directives
for (int d = 0; d<nDirectiveNames; d++) {
OPLookup &op_hash = pInstr[numInstructions++];
op_hash.op_hash = strref(aDirectiveNames[d].name).fnv1a_lower();
op_hash.index = (unsigned char)aDirectiveNames[d].directive;
op_hash.type = OT_DIRECTIVE;
}
if (merlin) {
for (int d = 0; d<nDirectiveNamesMerlin; d++) {
OPLookup &op_hash = pInstr[numInstructions++];
op_hash.op_hash = strref(aDirectiveNamesMerlin[d].name).fnv1a_lower();
op_hash.index = (unsigned char)aDirectiveNamesMerlin[d].directive;
op_hash.type = OT_DIRECTIVE;
}
}
// sort table by hash for binary search lookup
qsort(pInstr, numInstructions, sizeof(OPLookup), sortHashLookup);
return numInstructions;
}
// Change the instruction set
void Asm::SetCPU(CPUIndex CPU) {
cpu = CPU;
if (cpu > list_cpu)
list_cpu = cpu;
opcode_table = aCPUs[CPU].opcodes;
opcode_count = aCPUs[CPU].num_opcodes;
num_instructions = BuildInstructionTable(aInstructions, MAX_OPCODES_DIRECTIVES, opcode_table,
opcode_count, aCPUs[CPU].aliases, syntax == SYNTAX_MERLIN);
}
// Read in text data (main source, include, etc.)
char* Asm::LoadText(strref filename, size_t &size) {
strown<512> file(filename);
std::vector<strref>::iterator i = includePaths.begin();
for (;;) {
if (FILE *f = fopen(file.c_str(), "rb")) { // rb is intended here since OS
fseek(f, 0, SEEK_END); // eol conversion can do ugly things
size_t _size = ftell(f);
fseek(f, 0, SEEK_SET);
if (char *buf = (char*)calloc(_size, 1)) {
fread(buf, _size, 1, f);
fclose(f);
size = _size;
return buf;
}
fclose(f);
}
if (i==includePaths.end())
break;
file.copy(*i);
if (file.get_last()!='/' && file.get_last()!='\\')
file.append('/');
file.append(filename);
++i;
}
size = 0;
return nullptr;
}
// Read in binary data (incbin)
char* Asm::LoadBinary(strref filename, size_t &size) {
strown<512> file(filename);
std::vector<strref>::iterator i = includePaths.begin();
for (;;) {
if (FILE *f = fopen(file.c_str(), "rb")) {
fseek(f, 0, SEEK_END);
size_t _size = ftell(f);
fseek(f, 0, SEEK_SET);
if (char *buf = (char*)malloc(_size)) {
fread(buf, _size, 1, f);
fclose(f);
size = _size;
return buf;
}
fclose(f);
}
if (i==includePaths.end())
break;
file.copy(*i);
if (file.get_last()!='/' && file.get_last()!='\\')
file.append('/');
file.append(filename);
#ifdef WIN32
file.replace('/', '\\');
#endif
++i;
}
size = 0;
return nullptr;
}
// Create a new section with a fixed address
void Asm::SetSection(strref name, int address)
{
if (name) {
for (std::vector<Section>::iterator i = allSections.begin(); i!=allSections.end(); ++i) {
if (i->name && name.same_str(i->name)) {
current_section = &*i;
return;
}
}
}
if (allSections.size()==allSections.capacity())
allSections.reserve(allSections.size() + 16);
Section newSection(name, address);
if (address < 0x200) // don't compile over zero page and stack frame (may be bad assumption)
newSection.SetDummySection(true);
allSections.push_back(newSection);
current_section = &allSections[allSections.size()-1];
}
void Asm::SetSection(strref line)
{
if (allSections.size() && CurrSection().unused())
allSections.erase(allSections.begin() + SectionId());
if (allSections.size() == allSections.capacity())
allSections.reserve(allSections.size() + 16);
SectionType type = ST_UNDEFINED;
// SEG.U etc.
if (line.get_first() == '.') {
++line;
switch (strref::tolower(line.get_first())) {
case 'u': type = ST_BSS; break;
case 'z': type = ST_ZEROPAGE; break;
case 'd': type = ST_DATA; break;
case 'c': type = ST_CODE; break;
}
}
line.trim_whitespace();
int align = 1;
strref name;
while (strref arg = line.split_token_any_trim(",:")) {
if (arg.get_first() == '$') { ++arg; align = arg.ahextoui(); }
else if (arg.is_number()) align = arg.atoi();
else if (arg.get_first() == '"') name = (arg + 1).before_or_full('"');
else if (!name) name = arg;
else if (arg.same_str("code")) type = ST_CODE;
else if (arg.same_str("data")) type = ST_DATA;
else if (arg.same_str("bss")) type = ST_BSS;
else if (arg.same_str("zp") || arg.same_str("dp") ||
arg.same_str("zeropage") || arg.same_str("direct")) type = ST_ZEROPAGE;
}
if (type == ST_UNDEFINED) {
if (name.find("code") >= 0) type = ST_CODE;
else if (name.find("data") >= 0) type = ST_DATA;
else if (name.find("bss" >= 0 || name.same_str("directpage_stack"))) type = ST_BSS;
else if (name.find("zp") >= 0 || name.find("zeropage") >= 0 || name.find("direct") >= 0)
type = ST_ZEROPAGE;
else type = ST_CODE;
}
Section newSection(name);
newSection.align_address = align;
newSection.type = type;
allSections.push_back(newSection);
current_section = &allSections[allSections.size()-1];
}
// Fixed address dummy section
void Asm::DummySection(int address) {
if (allSections.size()==allSections.capacity())
allSections.reserve(allSections.size() + 16);
Section newSection(strref(), address);
newSection.SetDummySection(true);
allSections.push_back(newSection);
current_section = &allSections[allSections.size()-1];
}
// Current address dummy section
void Asm::DummySection() {
DummySection(CurrSection().GetPC());
}
void Asm::EndSection() {
int section = SectionId();
if (section)
current_section = &allSections[section-1];
}
// Iterate through the current group of sections and assign addresses if this section is fixed
// This is to handle the special linking of Merlin where sections are brought together pre-export
void Asm::AssignAddressToGroup()
{
Section &curr = CurrSection();
if (!curr.address_assigned)
return;
// Put in all the sections cared about into either the fixed sections or the relative sections
std::vector<Section*> FixedExport;
std::vector<Section*> RelativeExport;
int seg = SectionId();
while (seg>=0) {
Section &s = allSections[seg];
if (s.address_assigned && s.type != ST_ZEROPAGE && s.start_address >= curr.start_address) {
bool inserted = false;
for (std::vector<Section*>::iterator i = FixedExport.begin(); i!=FixedExport.end(); ++i) {
if (s.start_address < (*i)->start_address) {
FixedExport.insert(i, &s);
inserted = true;
break;
}
}
if (!inserted)
FixedExport.push_back(&s);
} else if (!s.address_assigned && s.type != ST_ZEROPAGE) {
RelativeExport.push_back(&s);
s.export_append = curr.export_append;
}
seg = allSections[seg].next_group;
}
// in this case each block should be added individually in order of code / data / bss
for (int type = ST_CODE; type <= ST_BSS; type++) {
std::vector<Section*>::iterator i = RelativeExport.begin();
while (i!=RelativeExport.end()) {
Section *pSec = *i;
if (pSec->type == type) {
int bytes = pSec->address - pSec->start_address;
size_t insert_after = FixedExport.size()-1;
for (size_t p = 0; p<insert_after; p++) {
int end_prev = FixedExport[p]->address;
int start_next = FixedExport[p+1]->start_address;
int avail = start_next - end_prev;
if (avail >= bytes) {
int addr = end_prev;
addr += pSec->align_address <= 1 ? 0 :
(pSec->align_address - (addr % pSec->align_address)) % pSec->align_address;
if ((addr + bytes) <= start_next) {
insert_after = p;
break;
}
}
}
int address = FixedExport[insert_after]->address;
address += pSec->align_address <= 1 ? 0 :
(pSec->align_address - (address % pSec->align_address)) % pSec->align_address;
AssignAddressToSection(SectionId(*pSec), address);
FixedExport.insert((FixedExport.begin() + insert_after + 1), pSec);
i = RelativeExport.erase(i);
} else
++i;
}
}
}
// list all export append names
// for each valid export append name build a binary fixed address code
// - find lowest and highest address
// - alloc & 0 memory
// - any matching relative sections gets linked in after
// - go through all section that matches export_append in order and copy over memory
unsigned char* Asm::BuildExport(strref append, int &file_size, int &addr)
{
int start_address = 0x7fffffff;
int end_address = 0;
bool has_relative_section = false;
bool has_fixed_section = false;
int first_link_section = -1;
std::vector<Section*> FixedExport;
// find address range
while (!has_relative_section && !has_fixed_section) {
int section_id = 0;
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
if (((!append && !i->export_append) || append.same_str_case(i->export_append)) && i->type != ST_ZEROPAGE) {
if (!i->IsMergedSection()) {
if (i->IsRelativeSection()) {
// prioritize code over data, local code over included code for initial binary segment
if ((i->type == ST_CODE || i->type == ST_DATA) && i->first_group < 0 &&
(first_link_section < 0 || (i->type == ST_CODE &&
(allSections[first_link_section].type == ST_DATA ||
(!i->include_from && allSections[first_link_section].include_from)))))
first_link_section = SectionId(*i);
has_relative_section = true;
} else if (i->start_address >= 0x100 && i->size() > 0) {
has_fixed_section = true;
bool inserted = false;
for (std::vector<Section*>::iterator f = FixedExport.begin(); f != FixedExport.end(); ++f) {
if ((*f)->start_address > i->start_address) {
FixedExport.insert(f, &*i);
inserted = true;
break;
}
}
if (!inserted)
FixedExport.push_back(&*i);
if (i->start_address < start_address)
start_address = i->start_address;
if ((i->start_address + (int)i->size()) > end_address) {
end_address = i->start_address + (int)i->size();
}
}
}
}
section_id++;
}
if (!has_relative_section && !has_fixed_section)
return nullptr;
if (has_relative_section) {
if (!has_fixed_section) {
// there is not a fixed section so go through and assign addresses to all sections
// starting with the first reasonable section
start_address = default_org;
if (first_link_section < 0)
return nullptr;
while (first_link_section >= 0) {
FixedExport.push_back(&allSections[first_link_section]);
AssignAddressToSection(first_link_section, start_address);
start_address = allSections[first_link_section].address;
first_link_section = allSections[first_link_section].next_group;
}
}
// First link code sections, then data sections, then BSS sections
for (int sectype = ST_CODE; sectype <= ST_BSS; sectype++) {
// there are fixed sections so fit all relative sections after or inbetween fixed sections in export group
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
if (sectype == i->type && ((!append && !i->export_append) || append.same_str_case(i->export_append))) {
int id = (int)(&*i - &allSections[0]);
if (i->IsRelativeSection() && i->first_group < 0) {
// try to fit this section in between existing sections if possible
int insert_after = (int)FixedExport.size()-1;
for (int f = 0; f < insert_after; f++) {
int start_block = FixedExport[f]->address;
int end_block = FixedExport[f + 1]->start_address;
if ((end_block - start_block) >= (i->address - i->start_address)) {
int addr = start_block;
int sec = id;
while (sec >= 0) {
Section &s = allSections[sec];
addr += s.align_address <= 1 ? 0 :
(s.align_address - (addr % s.align_address)) % s.align_address;
addr += s.address - s.start_address;
sec = s.next_group;
}
if (addr <= end_block) {
insert_after = f;
break;
}
}
}
int sec = id;
start_address = FixedExport[insert_after]->address;
while (sec >= 0) {
insert_after++;
if (insert_after<(int)FixedExport.size())
FixedExport.insert(FixedExport.begin() + insert_after, &allSections[sec]);
else
FixedExport.push_back(&allSections[sec]);
AssignAddressToSection(sec, start_address);
start_address = allSections[sec].address;
sec = allSections[sec].next_group;
}
}
}
}
}
}
}
// get memory for output buffer
start_address = FixedExport[0]->start_address;
int last_data_export = (int)(FixedExport.size() - 1);
while (last_data_export>0 && FixedExport[last_data_export]->type == ST_BSS)
last_data_export--;
end_address = FixedExport[last_data_export]->address;
unsigned char *output = (unsigned char*)calloc(1, end_address - start_address);
// copy over in order
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
if (((!append && !i->export_append) || append.same_str_case(i->export_append)) && i->type != ST_ZEROPAGE) {
if (i->merged_offset == -1 && i->start_address >= 0x200 && i->size() > 0)
memcpy(output + i->start_address - start_address, i->output, i->size());
}
}
printf("Linker export + \"" STRREF_FMT "\" summary:\n", STRREF_ARG(append));
for (std::vector<Section*>::iterator f = FixedExport.begin(); f != FixedExport.end(); ++f) {
if ((*f)->include_from) {
printf("* $%04x-$%04x: " STRREF_FMT " (%d) included from " STRREF_FMT "\n", (*f)->start_address,
(*f)->address, STRREF_ARG((*f)->name), (int)(*f - &allSections[0]), STRREF_ARG((*f)->include_from));
} else {
printf("* $%04x-$%04x: " STRREF_FMT " (%d)\n", (*f)->start_address,
(*f)->address, STRREF_ARG((*f)->name), (int)(*f - &allSections[0]));
}
}
// return the result
file_size = end_address - start_address;
addr = start_address;
return output;
}
// Collect all the export names
int Asm::GetExportNames(strref *aNames, int maxNames)
{
int count = 0;
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
if (!i->IsMergedSection()) {
bool found = false;
unsigned int hash = i->export_append.fnv1a_lower();
for (int n = 0; n < count; n++) {
if (aNames[n].fnv1a_lower() == hash) {
found = true;
break;
}
}
if (!found && count < maxNames)
aNames[count++] = i->export_append;
}
}
return count;
}
// Collect all unassigned ZP sections and link them
StatusCode Asm::LinkZP()
{
unsigned char min_addr = 0xff, max_addr = 0x00;
int num_addr = 0;
bool has_assigned = false, has_unassigned = false;
int first_unassigned = -1;
// determine if any zeropage section has been asseigned
for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) {
if (s->type == ST_ZEROPAGE && !s->IsMergedSection()) {
if (s->address_assigned) {
has_assigned = true;
if (s->start_address < min_addr)
min_addr = s->start_address;
else if (s->address > max_addr)
max_addr = s->address;
} else {
has_unassigned = true;
first_unassigned = first_unassigned >=0 ? first_unassigned : (int)(&*s - &allSections[0]);
}
num_addr += s->address - s->start_address;
}
}
if (num_addr > 0x100)
return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE;
// no unassigned zp section, nothing to fix
if (!has_unassigned)
return STATUS_OK;
StatusCode status = STATUS_OK;
// no section assigned => fit together at end
if (!has_assigned) {
int address = 0x100 - num_addr;
for (std::vector<Section>::iterator s = allSections.begin(); status==STATUS_OK && s != allSections.end(); ++s) {
if (s->type == ST_ZEROPAGE && !s->IsMergedSection()) {
status = AssignAddressToSection((int)(&*s - &allSections[0]), address);
address = s->address;
}
}
} else { // find first fit neighbouring an address assigned zero page section
for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) {
if (s->type == ST_ZEROPAGE && !s->IsMergedSection() && !s->address_assigned) {
int size = s->address - s->start_address;
bool found = false;
// find any assigned address section and try to place before or after
for (std::vector<Section>::iterator sa = allSections.begin(); sa != allSections.end(); ++sa) {
if (sa->type == ST_ZEROPAGE && !sa->IsMergedSection() && sa->address_assigned) {
for (int e = 0; e < 2; ++e) {
int start = e ? sa->start_address - size : sa->address;
int align_size = s->align_address <= 1 ? 0 :
(s->align_address - (start % s->align_address)) % s->align_address;
start += align_size;
int end = start + size;
if (start >= 0 && end <= 0x100) {
for (std::vector<Section>::iterator sc = allSections.begin(); !found && sc != allSections.end(); ++sc) {
found = true;
if (&*sa != &*sc && sc->type == ST_ZEROPAGE && !sc->IsMergedSection() && sc->address_assigned) {
if (start <= sc->address && sc->start_address <= end)
found = false;
}
}
}
if (found)
AssignAddressToSection((int)(&*s - &allSections[0]), start);
}
}
}
if (!found)
return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE;
}
}
}
return status;
}
// Apply labels assigned to addresses in a relative section a fixed address or as part of another section
void Asm::LinkLabelsToAddress(int section_id, int section_new, int section_address)
{
Label *pLabels = labels.getValues();
int numLabels = labels.count();
for (int l = 0; l < numLabels; l++) {
if (pLabels->section == section_id) {
pLabels->value += section_address;
pLabels->section = section_new;
if (pLabels->mapIndex>=0 && pLabels->mapIndex<(int)map.size()) {
struct MapSymbol &msym = map[pLabels->mapIndex];
msym.value = pLabels->value;
msym.section = section_new;
}
CheckLateEval(pLabels->label_name);
}
++pLabels;
}
}
// go through relocs in all sections to see if any targets this section
// relocate section to address!
StatusCode Asm::LinkRelocs(int section_id, int section_new, int section_address)
{
for (std::vector<Section>::iterator j = allSections.begin(); j != allSections.end(); ++j) {
Section &s2 = *j;
if (s2.pRelocs) {
relocList *pList = s2.pRelocs;
relocList::iterator i = pList->end();
while (i != pList->begin()) {
--i;
if (i->target_section == section_id) {
Section *trg_sect = &s2;
size_t output_offs = 0;
while (trg_sect->merged_offset>=0) {
output_offs += trg_sect->merged_offset;
trg_sect = &allSections[trg_sect->merged_section];
}
// only finalize the target value if fixed address
if (section_new == -1 || allSections[section_new].address_assigned) {
unsigned char *trg = trg_sect->output + output_offs + i->section_offset;
int value = i->base_value + section_address;
if (i->shift < 0)
value >>= -i->shift;
else if (i->shift)
value <<= i->shift;
for (int b = 0; b < i->bytes; b++)
*trg++ = (unsigned char)(value >> (b * 8));
i = pList->erase(i);
if (i != pList->end())
++i;
}
}
}
if (pList->empty()) {
free(pList);
s2.pRelocs = nullptr;
}
}
}
return STATUS_OK;
}
// Append one section to the end of another
StatusCode Asm::AssignAddressToSection(int section_id, int address)
{
if (section_id < 0 || section_id >= (int)allSections.size())
return ERROR_NOT_A_SECTION;
Section &s = allSections[section_id];
if (s.address_assigned)
return ERROR_CANT_REASSIGN_FIXED_SECTION;
// fix up the alignment of the address
int align_size = s.align_address <= 1 ? 0 :
(s.align_address - (address % s.align_address)) % s.align_address;
address += align_size;
s.start_address = address;
s.address += address;
s.address_assigned = true;
LinkLabelsToAddress(section_id, -1, s.start_address);
return LinkRelocs(section_id, -1, s.start_address);
}
// Link sections with a specific name at this point
// Relative sections will just be appeneded to a grouping list
// Fixed address sections will be merged together
StatusCode Asm::LinkSections(strref name) {
if (CurrSection().IsDummySection())
return ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION;
int last_section_group = CurrSection().next_group;
while (last_section_group > -1 && allSections[last_section_group].next_group > -1)
last_section_group = allSections[last_section_group].next_group;
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
if ((!name || i->name.same_str_case(name)) && i->IsRelativeSection() && !i->IsMergedSection()) {
// it is ok to link other sections with the same name to this section
if (&*i == &CurrSection())
continue;
// Zero page sections can only be linked with zero page sections
if (i->type != ST_ZEROPAGE || CurrSection().type == ST_ZEROPAGE) {
i->export_append = CurrSection().export_append;
if (!i->address_assigned) {
if (i->first_group < 0) {
int prev = last_section_group >= 0 ? last_section_group : SectionId();
int curr = (int)(&*i - &allSections[0]);
allSections[prev].next_group = curr;
i->first_group = CurrSection().first_group ? CurrSection().first_group : SectionId();
last_section_group = curr;
}
}
} else
return ERROR_CANT_LINK_ZP_AND_NON_ZP;
}
}
return STATUS_OK;
}
StatusCode Asm::MergeSections(int section_id, int section_merge) {
if (section_id == section_merge || section_id<0 || section_merge<0)
return STATUS_OK;
Section &s = allSections[section_id];
Section &m = allSections[section_merge];
// merging section needs to be relative to be appended
if (!m.IsRelativeSection())
return ERROR_CANT_APPEND_SECTION_TO_TARGET;
// if merging section is aligned and target section is not aligned to that or multiple of then can't merge
if (m.align_address>1 && (!s.IsRelativeSection() || (s.align_address%m.align_address)!=0))
return ERROR_CANT_APPEND_SECTION_TO_TARGET;
// append the binary to the target..
int addr_start = s.address;
int align = m.align_address <= 1 ? 0 : (m.align_address - (addr_start % m.align_address)) % m.align_address;
if (m.size()) {
if (s.CheckOutputCapacity(m.size() + align) == STATUS_OK) {
for (int a = 0; a < align; a++)
s.AddByte(0);
s.AddBin(m.output, m.size());
}
} else if (m.addr_size() && s.type != ST_BSS && s.type != ST_ZEROPAGE && !s.dummySection) {
if (s.CheckOutputCapacity(m.address - m.start_address) == STATUS_OK) {
for (int a = (m.start_address-align); a < m.address; a++)
s.AddByte(0);
}
} else if (m.addr_size())
s.AddAddress(align + m.addr_size());
addr_start += align;
// move the relocs from the merge section to the keep section
if (m.pRelocs) {
if (!s.pRelocs)
s.pRelocs = new relocList;
if (s.pRelocs->capacity() < (s.pRelocs->size() + m.pRelocs->size()))
s.pRelocs->reserve(s.pRelocs->size() + m.pRelocs->size());
for (relocList::iterator r = m.pRelocs->begin(); r != m.pRelocs->end(); ++r) {
struct Reloc rel = *r;
rel.section_offset += addr_start;
s.pRelocs->push_back(rel);
}
delete m.pRelocs;
m.pRelocs = nullptr;
}
// go through all the relocs referring to merging section and replace
for (std::vector<Section>::iterator i = allSections.begin(); i!=allSections.end(); ++i) {
if (relocList *pReloc = i->pRelocs) {
for (relocList::iterator r = pReloc->begin(); r!=pReloc->end(); ++r) {
if (r->target_section == section_merge) {
r->base_value += addr_start;
r->target_section = section_id;
}
}
}
}
// go through all labels referencing merging section
for (unsigned int i = 0; i<labels.count(); i++) {
Label &lab = labels.getValue(i);
if (lab.section == section_merge && lab.evaluated) {
lab.value += addr_start;
lab.section = section_id;
}
}
// go through map symbols
for (MapSymbolArray::iterator i = map.begin(); i!=map.end(); ++i) {
if (i->section == section_merge) {
i->value += addr_start;
i->section = section_id;
}
}
// go through all late evals referencing this section
for (std::vector<LateEval>::iterator i = lateEval.begin(); i!=lateEval.end(); ++i) {
if (i->section == section_merge) {
i->section = section_id;
if (i->target >= 0)
i->target += addr_start;
i->address += addr_start;
if (i->scope >= 0)
i->scope += addr_start;
}
}
// go through listing
if (m.pListing) {
if (!s.pListing)
s.pListing = new Listing;
if (s.pListing->capacity() < (m.pListing->size() + s.pListing->size()))
s.pListing->reserve((m.pListing->size() + s.pListing->size()));
for (Listing::iterator i = m.pListing->begin(); i!=m.pListing->end(); ++i) {
ListLine l = *i;
l.address += addr_start;
s.pListing->push_back(l);
}
delete m.pListing;
m.pListing = nullptr;
}
printf("merged section %d into section %d at $%x offset\n", section_merge, section_id, addr_start);
m.type = ST_REMOVED;
return STATUS_OK;
}
// Go through sections and merge same name sections together
StatusCode Asm::MergeSectionsByName(int first_section)
{
int first_code_seg = -1;
StatusCode status = STATUS_OK;
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
if (i->type != ST_REMOVED) {
if (first_code_seg<0 && i->type==ST_CODE)
first_code_seg = (int)(&*i-&allSections[0]);
std::vector<Section>::iterator n = i;
++n;
while (n != allSections.end()) {
if (n->name.same_str_case(i->name) && n->type == i->type) {
int sk = (int)(&*i - &allSections[0]);
int sm = (int)(&*n - &allSections[0]);
if (sm == first_section || (n->align_address > i->align_address)) {
if (n->align_address<i->align_address)
n->align_address = i->align_address;
status = MergeSections(sm, sk);
} else
status = MergeSections(sk, sm);
if (status != STATUS_OK)
return status;
}
++n;
}
}
}
return STATUS_OK;
}
// Merge all sections in order of code, data, bss and make sure a specific section remains first
#define MERGE_ORDER_CNT (ST_BSS - ST_CODE+1)
StatusCode Asm::MergeAllSections(int first_section)
{
StatusCode status = STATUS_OK;
// combine all sections by type first
for (int t = ST_CODE; t<ST_ZEROPAGE && status == STATUS_OK; t++) {
for (int i = 0; i<(int)allSections.size() && status == STATUS_OK; ++i) {
if (allSections[i].type == t) {
for (int j = i + 1; j<(int)allSections.size() && status == STATUS_OK; ++j) {
if (allSections[j].type == t) {
if (j == first_section || (t != ST_CODE && allSections[i].align_address<allSections[j].align_address)) {
if (allSections[i].align_address > allSections[j].align_address)
allSections[i].align_address = allSections[j].align_address;
status = MergeSections(j, i);
} else
status = MergeSections(i, j);
}
}
}
}
}
// then combine by category except zero page
int merge_order[MERGE_ORDER_CNT] = { -1 };
for (int t = ST_CODE; t <= ST_BSS; t++) {
for (int i = 0; i<(int)allSections.size(); ++i) {
if (allSections[i].type == t) {
merge_order[t - ST_CODE] = i;
break;
}
}
}
for (int n = 1; n < MERGE_ORDER_CNT; n++) {
if (merge_order[n] == -1) {
for (int m = n + 1; m < MERGE_ORDER_CNT; m++)
merge_order[m - 1] = merge_order[m];
}
}
if (merge_order[0] == -1)
return ERROR_NOT_A_SECTION;
for (int o = 1; o < MERGE_ORDER_CNT; o++) {
if (merge_order[o] != -1 && status == STATUS_OK) {
if (allSections[merge_order[0]].align_address < allSections[merge_order[o]].align_address)
allSections[merge_order[0]].align_address = allSections[merge_order[o]].align_address;
status = MergeSections(merge_order[0], merge_order[o]);
}
}
return status;
}
// Section based output capacity
// Make sure there is room to assemble in
StatusCode Section::CheckOutputCapacity(unsigned int addSize) {
if (dummySection || type == ST_ZEROPAGE || type == ST_BSS)
return STATUS_OK;
size_t currSize = curr - output;
if ((addSize + currSize) >= output_capacity) {
size_t newSize = currSize * 2;
if (newSize < 64*1024)
newSize = 64*1024;
if ((addSize+currSize) > newSize)
newSize += newSize;
if (unsigned char *new_output = (unsigned char*)malloc(newSize)) {
memcpy(new_output, output, size());
curr = new_output + (curr - output);
free(output);
output = new_output;
output_capacity = newSize;
} else
return ERROR_OUT_OF_MEMORY;
}
return STATUS_OK;
}
// Add one byte to a section
void Section::AddByte(int b) {
if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) {
if (CheckOutputCapacity(1) == STATUS_OK)
*curr++ = (unsigned char)b;
}
address++;
}
// Add a 16 bit word to a section
void Section::AddWord(int w) {
if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) {
if (CheckOutputCapacity(2) == STATUS_OK) {
*curr++ = (unsigned char)(w & 0xff);
*curr++ = (unsigned char)(w >> 8);
}
}
address += 2;
}
// Add a 24 bit word to a section
void Section::AddTriple(int l) {
if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) {
if (CheckOutputCapacity(3) == STATUS_OK) {
*curr++ = (unsigned char)(l & 0xff);
*curr++ = (unsigned char)(l >> 8);
*curr++ = (unsigned char)(l >> 16);
}
}
address += 3;
}
// Add arbitrary length data to a section
void Section::AddBin(unsigned const char *p, int size) {
if (!dummySection && type != ST_ZEROPAGE && type != ST_BSS) {
if (CheckOutputCapacity(size) == STATUS_OK) {
memcpy(curr, p, size);
curr += size;
}
}
address += size;
}
// Add text data to a section
void Section::AddText(strref line, strref text_prefix) {
// https://en.wikipedia.org/wiki/PETSCII
// ascii: no change
// shifted: a-z => $41.. A-Z => $61..
// unshifted: a-z, A-Z => $41
if (CheckOutputCapacity(line.get_len()) == STATUS_OK) {
if (!text_prefix || text_prefix.same_str("ascii")) {
AddBin((unsigned const char*)line.get(), line.get_len());
} else if (text_prefix.same_str("petscii")) {
while (line) {
char c = line[0];
AddByte((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : (c > 0x60 ? ' ' : line[0]));
++line;
}
} else if (text_prefix.same_str("petscii_shifted")) {
while (line) {
char c = line[0];
AddByte((c >= 'a' && c <= 'z') ? (c - 'a' + 0x61) :
((c >= 'A' && c <= 'Z') ? (c - 'A' + 0x61) : (c > 0x60 ? ' ' : line[0])));
++line;
}
}
}
}
// Add a relocation marker to a section
void Section::AddReloc(int base, int offset, int section, char bytes, char shift)
{
if (!pRelocs)
pRelocs = new relocList;
if (pRelocs->size() == pRelocs->capacity())
pRelocs->reserve(pRelocs->size() + 32);
pRelocs->push_back(Reloc(base, offset, section, bytes, shift));
}
// Make sure there is room to assemble in
StatusCode Asm::CheckOutputCapacity(unsigned int addSize) {
return CurrSection().CheckOutputCapacity(addSize);
}
//
//
// SCOPE MANAGEMENT
//
//
StatusCode Asm::EnterScope()
{
if (scope_depth >= (MAX_SCOPE_DEPTH - 1))
return ERROR_TOO_DEEP_SCOPE;
scope_address[++scope_depth] = CurrSection().GetPC();
return STATUS_OK;
}
StatusCode Asm::ExitScope()
{
CheckLateEval(strref(), CurrSection().GetPC());
FlushLocalLabels(scope_depth);
FlushLabelPools(scope_depth);
--scope_depth;
if (scope_depth<0)
return ERROR_UNBALANCED_SCOPE_CLOSURE;
return STATUS_OK;
}
//
//
// CONTEXT ISOLATION
//
//
StatusCode Asm::PushContext(strref src_name, strref src_file, strref code_seg, int rept)
{
if (conditional_depth>=MAX_CONDITIONAL_DEPTH)
return ERROR_CONDITION_TOO_NESTED;
conditional_depth++;
conditional_nesting[conditional_depth] = 0;
conditional_consumed[conditional_depth] = false;
contextStack.push(src_name, src_file, code_seg, rept);
contextStack.curr().conditional_ctx = conditional_depth;
return STATUS_OK;
}
StatusCode Asm::PopContext()
{
if (contextStack.curr().scoped_context && scope_depth) {
StatusCode ret = ExitScope();
if (ret != STATUS_OK)
return ret;
}
if (!ConditionalAsm() || ConditionalConsumed() ||
conditional_depth!=contextStack.curr().conditional_ctx)
return ERROR_UNTERMINATED_CONDITION;
conditional_depth = contextStack.curr().conditional_ctx-1;
contextStack.pop();
return STATUS_OK;
}
//
//
// MACROS
//
//
// add a custom macro
StatusCode Asm::AddMacro(strref macro, strref source_name, strref source_file, strref &left)
{ //
// Recommended macro syntax:
// macro name(optional params) { actual macro }
//
// -endm option macro syntax:
// macro name arg
// actual macro
// endmacro
//
// Merlin macro syntax: (TODO: ignore arguments and use ]1, ]2, etc.)
// name mac arg1 arg2
// actual macro
// [<<<]/[EOM]
//
strref name;
bool params_first_line = false;
if (syntax == SYNTAX_MERLIN) {
if (Label *pLastLabel = GetLabel(last_label)) {
labels.remove((unsigned int)(pLastLabel - labels.getValues()));
name = last_label;
last_label.clear();
macro.skip_whitespace();
if (macro.get_first()==';' || macro.has_prefix(c_comment))
macro.line();
else
params_first_line = true;
} else
return ERROR_BAD_MACRO_FORMAT;
} else {
name = macro.split_range(label_end_char_range);
macro.skip_whitespace();
strref left_line = macro.get_line();
left_line.skip_whitespace();
left_line = left_line.before_or_full(';').before_or_full(c_comment);
if (left_line && left_line[0] != '(' && left_line[0] != '{')
params_first_line = true;
}
unsigned int hash = name.fnv1a();
unsigned int ins = FindLabelIndex(hash, macros.getKeys(), macros.count());
Macro *pMacro = nullptr;
while (ins < macros.count() && macros.getKey(ins)==hash) {
if (name.same_str_case(macros.getValue(ins).name)) {
pMacro = macros.getValues() + ins;
break;
}
++ins;
}
if (!pMacro) {
macros.insert(ins, hash);
pMacro = macros.getValues() + ins;
}
pMacro->name = name;
if (syntax == SYNTAX_MERLIN) {
strref source = macro;
while (strref next_line = macro.line()) {
next_line = next_line.before_or_full(';');
next_line = next_line.before_or_full(c_comment);
int term = next_line.find("<<<");
if (term < 0)
term = next_line.find("EOM");
if (term >= 0) {
strl_t macro_len = strl_t(next_line.get() + term - source.get());
source = source.get_substr(0, macro_len);
break;
}
}
left = macro;
pMacro->macro = source;
source.skip_whitespace();
} else if (end_macro_directive) {
int f = -1;
const strref endm("endm");
for (;;) {
f = macro.find(endm, f+1);
if (f<0)
return ERROR_BAD_MACRO_FORMAT;
if (f == 0 || strref::is_ws(macro[f - 1]))
break;
}
pMacro->macro = macro.get_substr(0, f);
macro += f;
macro.line();
left = macro;
} else {
int pos_bracket = macro.find('{');
if (pos_bracket < 0) {
pMacro->macro = strref();
return ERROR_BAD_MACRO_FORMAT;
}
strref source = macro + pos_bracket;
strref macro_body = source.scoped_block_skip();
pMacro->macro = strref(macro.get(), pos_bracket + macro_body.get_len() + 2);
source.skip_whitespace();
left = source;
}
pMacro->source_name = source_name;
pMacro->source_file = source_file;
pMacro->params_first_line = params_first_line;
return STATUS_OK;
}
// Compile in a macro
StatusCode Asm::BuildMacro(Macro &m, strref arg_list)
{
strref macro_src = m.macro, params;
if (m.params_first_line) {
if (end_macro_directive)
params = macro_src.line();
else {
params = macro_src.before('{');
macro_src += params.get_len();
}
}
else
params = (macro_src[0] == '(' ? macro_src.scoped_block_skip() : strref());
params.trim_whitespace();
arg_list.trim_whitespace();
if (syntax == SYNTAX_MERLIN) {
// need to include comment field because separator is ;
if (contextStack.curr().read_source.is_substr(arg_list.get()))
arg_list = (contextStack.curr().read_source +
strl_t(arg_list.get()-contextStack.curr().read_source.get())
).line();
arg_list = arg_list.before_or_full(c_comment).get_trimmed_ws();
strref arg = arg_list;
strown<16> tag;
int t_max = 16;
int dSize = 0;
for (int t=1; t<t_max; t++) {
tag.sprintf("]%d", t);
strref a = arg.split_token_trim(';');
if (!a) {
t_max = t;
break;
}
int count = macro_src.substr_case_count(tag.get_strref());
dSize += count * ((int)a.get_len() - (int)tag.get_len());
}
int mac_size = macro_src.get_len() + dSize + 32;
if (char *buffer = (char*)malloc(mac_size)) {
loadedData.push_back(buffer);
strovl macexp(buffer, mac_size);
macexp.copy(macro_src);
arg = arg_list;
if (tag) {
for (int t=1; t<t_max; t++) {
tag.sprintf("]%d", t);
strref a = arg.split_token_trim(';');
macexp.replace_bookend(tag.get_strref(), a, label_end_char_range_merlin);
}
}
PushContext(m.source_name, macexp.get_strref(), macexp.get_strref());
if (scope_depth>=(MAX_SCOPE_DEPTH-1))
return ERROR_TOO_DEEP_SCOPE;
else
scope_address[++scope_depth] = CurrSection().GetPC();
contextStack.curr().scoped_context = true;
return STATUS_OK;
} else
return ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION;
} else if (params) {
if (arg_list[0]=='(')
arg_list = arg_list.scoped_block_skip();
strref pchk = params;
strref arg = arg_list;
int dSize = 0;
char token = arg_list.find(',')>=0 ? ',' : ' ';
char token_macro = m.params_first_line && params.find(',') < 0 ? ' ' : ',';
while (strref param = pchk.split_token_trim(token_macro)) {
strref a = arg.split_token_trim(token);
if (param.get_len() < a.get_len()) {
int count = macro_src.substr_case_count(param);
dSize += count * ((int)a.get_len() - (int)param.get_len());
}
}
int mac_size = macro_src.get_len() + dSize + 32;
if (char *buffer = (char*)malloc(mac_size)) {
loadedData.push_back(buffer);
strovl macexp(buffer, mac_size);
macexp.copy(macro_src);
while (strref param = params.split_token_trim(token_macro)) {
strref a = arg_list.split_token_trim(token);
macexp.replace_bookend(param, a, label_end_char_range);
}
PushContext(m.source_name, macexp.get_strref(), macexp.get_strref());
if (end_macro_directive) {
PushContext(m.source_name, macexp.get_strref(), macexp.get_strref());
if (scope_depth>=(MAX_SCOPE_DEPTH-1))
return ERROR_TOO_DEEP_SCOPE;
else
scope_address[++scope_depth] = CurrSection().GetPC();
contextStack.curr().scoped_context = true;
}
return STATUS_OK;
} else
return ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION;
}
PushContext(m.source_name, m.source_file, macro_src);
return STATUS_OK;
}
//
//
// STRUCTS AND ENUMS
//
//
// Enums are Structs in disguise
StatusCode Asm::BuildEnum(strref name, strref declaration)
{
unsigned int hash = name.fnv1a();
unsigned int ins = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count());
LabelStruct *pEnum = nullptr;
while (ins < labelStructs.count() && labelStructs.getKey(ins)==hash) {
if (name.same_str_case(labelStructs.getValue(ins).name)) {
pEnum = labelStructs.getValues() + ins;
break;
}
++ins;
}
if (pEnum)
return ERROR_STRUCT_ALREADY_DEFINED;
labelStructs.insert(ins, hash);
pEnum = labelStructs.getValues() + ins;
pEnum->name = name;
pEnum->first_member = (unsigned short)structMembers.size();
pEnum->numMembers = 0;
pEnum->size = 0; // enums are 0 sized
int value = 0;
struct EvalContext etx;
SetEvalCtxDefaults(etx);
while (strref line = declaration.line()) {
line = line.before_or_full(',');
line.trim_whitespace();
strref name = line.split_token_trim('=');
line = line.before_or_full(';').before_or_full(c_comment).get_trimmed_ws();
if (line) {
StatusCode error = EvalExpression(line, etx, value);
if (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT)
return ERROR_ENUM_CANT_BE_ASSEMBLED;
else if (error != STATUS_OK)
return error;
}
struct MemberOffset member;
member.offset = value;
member.name = name;
member.name_hash = member.name.fnv1a();
member.sub_struct = strref();
structMembers.push_back(member);
++value;
pEnum->numMembers++;
}
return STATUS_OK;
}
StatusCode Asm::BuildStruct(strref name, strref declaration)
{
unsigned int hash = name.fnv1a();
unsigned int ins = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count());
LabelStruct *pStruct = nullptr;
while (ins < labelStructs.count() && labelStructs.getKey(ins)==hash) {
if (name.same_str_case(labelStructs.getValue(ins).name)) {
pStruct = labelStructs.getValues() + ins;
break;
}
++ins;
}
if (pStruct)
return ERROR_STRUCT_ALREADY_DEFINED;
labelStructs.insert(ins, hash);
pStruct = labelStructs.getValues() + ins;
pStruct->name = name;
pStruct->first_member = (unsigned short)structMembers.size();
unsigned int byte_hash = struct_byte.fnv1a();
unsigned int word_hash = struct_word.fnv1a();
unsigned short size = 0;
unsigned short member_count = 0;
while (strref line = declaration.line()) {
line.trim_whitespace();
strref type = line.split_label();
line.skip_whitespace();
unsigned int type_hash = type.fnv1a();
unsigned short type_size = 0;
LabelStruct *pSubStruct = nullptr;
if (type_hash==byte_hash && struct_byte.same_str_case(type))
type_size = 1;
else if (type_hash==word_hash && struct_word.same_str_case(type))
type_size = 2;
else {
unsigned int index = FindLabelIndex(type_hash, labelStructs.getKeys(), labelStructs.count());
while (index < labelStructs.count() && labelStructs.getKey(index)==type_hash) {
if (type.same_str_case(labelStructs.getValue(index).name)) {
pSubStruct = labelStructs.getValues() + index;
break;
}
++index;
}
if (!pSubStruct) {
labelStructs.remove(ins);
return ERROR_REFERENCED_STRUCT_NOT_FOUND;
}
type_size = pSubStruct->size;
}
// add the new member, don't grow vectors one at a time.
if (structMembers.size() == structMembers.capacity())
structMembers.reserve(structMembers.size() + 64);
struct MemberOffset member;
member.offset = size;
member.name = line.get_label();
member.name_hash = member.name.fnv1a();
member.sub_struct = pSubStruct ? pSubStruct->name : strref();
structMembers.push_back(member);
size += type_size;
member_count++;
}
pStruct->numMembers = member_count;
pStruct->size = size;
return STATUS_OK;
}
// Evaluate a struct offset as if it was a label
StatusCode Asm::EvalStruct(strref name, int &value)
{
LabelStruct *pStruct = nullptr;
unsigned short offset = 0;
while (strref struct_seg = name.split_token('.')) {
strref sub_struct = struct_seg;
unsigned int seg_hash = struct_seg.fnv1a();
if (pStruct) {
struct MemberOffset *member = &structMembers[pStruct->first_member];
bool found = false;
for (int i = 0; i<pStruct->numMembers; i++) {
if (member->name_hash == seg_hash && member->name.same_str_case(struct_seg)) {
offset += member->offset;
sub_struct = member->sub_struct;
found = true;
break;
}
++member;
}
if (!found)
return ERROR_REFERENCED_STRUCT_NOT_FOUND;
}
if (sub_struct) {
unsigned int hash = sub_struct.fnv1a();
unsigned int index = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count());
while (index < labelStructs.count() && labelStructs.getKey(index)==hash) {
if (sub_struct.same_str_case(labelStructs.getValue(index).name)) {
pStruct = labelStructs.getValues() + index;
break;
}
++index;
}
} else if (name)
return STATUS_NOT_STRUCT;
}
if (pStruct == nullptr)
return STATUS_NOT_STRUCT;
value = offset;
return STATUS_OK;
}
//
//
// EXPRESSIONS AND LATE EVALUATION
//
//
int Asm::ReptCnt() const
{
return contextStack.curr().repeat_total - contextStack.curr().repeat;
}
void Asm::SetEvalCtxDefaults(struct EvalContext &etx)
{
etx.pc = CurrSection().GetPC(); // current address at point of eval
etx.scope_pc = scope_address[scope_depth]; // current scope open at point of eval
etx.scope_end_pc = -1; // late scope closure after eval
etx.scope_depth = scope_depth; // scope depth for eval (must match current for scope_end_pc to eval)
etx.relative_section = -1; // return can be relative to this section
etx.file_ref = -1; // can access private label from this file or -1
etx.rept_cnt = ReptCnt(); // current repeat counter
}
// Get a single token from a merlin expression
EvalOperator Asm::RPNToken_Merlin(strref &expression, const struct EvalContext &etx, EvalOperator prev_op, short &section, int &value)
{
char c = expression.get_first();
switch (c) {
case '$': ++expression; value = expression.ahextoui_skip(); return EVOP_VAL;
case '-': ++expression; return EVOP_SUB;
case '+': ++expression; return EVOP_ADD;
case '*': // asterisk means both multiply and current PC, disambiguate!
++expression;
if (expression[0] == '*') return EVOP_STP; // double asterisks indicates comment
else if (prev_op==EVOP_VAL || prev_op==EVOP_RPR) return EVOP_MUL;
value = etx.pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL;
case '/': ++expression; return EVOP_DIV;
case '>': if (expression.get_len() >= 2 && expression[1] == '>') { expression += 2; return EVOP_SHR; }
++expression; return EVOP_HIB;
case '<': if (expression.get_len() >= 2 && expression[1] == '<') { expression += 2; return EVOP_SHL; }
++expression; return EVOP_LOB;
case '%': // % means both binary and scope closure, disambiguate!
if (expression[1]=='0' || expression[1]=='1') {
++expression; value = expression.abinarytoui_skip(); return EVOP_VAL; }
if (etx.scope_end_pc<0 || scope_depth != etx.scope_depth) return EVOP_NRY;
++expression; value = etx.scope_end_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL;
case '|':
case '.': ++expression; return EVOP_OR; // MERLIN: . is or, | is not used
case '^': if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++expression; return EVOP_EOR; }
++expression; return EVOP_BAB;
case '&': ++expression; return EVOP_AND;
case '(': if (prev_op!=EVOP_VAL) { ++expression; return EVOP_LPR; } return EVOP_STP;
case ')': ++expression; return EVOP_RPR;
case '"': if (expression[2] == '"') { value = expression[1]; expression += 3; return EVOP_VAL; } return EVOP_STP;
case '\'': if (expression[2] == '\'') { value = expression[1]; expression += 3; return EVOP_VAL; } return EVOP_STP;
case ',':
case '?':
default: { // MERLIN: ! is eor
if (c == '!' && (prev_op == EVOP_VAL || prev_op == EVOP_RPR)) { ++expression; return EVOP_EOR; }
else if (c == '!' && !(expression + 1).len_label()) {
if (etx.scope_pc < 0) return EVOP_NRY; // ! by itself is current scope, !+label char is a local label
++expression; value = etx.scope_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL;
} else if (strref::is_number(c)) {
if (prev_op == EVOP_VAL) return EVOP_STP; // value followed by value doesn't make sense, stop
value = expression.atoi_skip(); return EVOP_VAL;
} else if (c == '!' || c == ']' || c==':' || strref::is_valid_label(c)) {
if (prev_op == EVOP_VAL) return EVOP_STP; // a value followed by a value does not make sense, probably start of a comment (ORCA/LISA?)
char e0 = expression[0];
int start_pos = (e0==']' || e0==':' || e0=='!' || e0=='.') ? 1 : 0;
strref label = expression.split_range_trim(label_end_char_range_merlin, start_pos);
Label *pLabel = pLabel = GetLabel(label, etx.file_ref);
if (!pLabel) {
StatusCode ret = EvalStruct(label, value);
if (ret == STATUS_OK) return EVOP_VAL;
if (ret != STATUS_NOT_STRUCT) return EVOP_ERR; // partial struct
}
if (!pLabel && label.same_str("rept")) { value = etx.rept_cnt; return EVOP_VAL; }
if (!pLabel || !pLabel->evaluated) return EVOP_NRY; // this label could not be found (yet)
value = pLabel->value; section = pLabel->section; return EVOP_VAL;
} else
return EVOP_ERR;
break;
}
}
return EVOP_NONE; // shouldn't get here normally
}
// Get a single token from most non-apple II assemblers
EvalOperator Asm::RPNToken(strref &exp, const struct EvalContext &etx, EvalOperator prev_op, short &section, int &value, strref &subexp)
{
char c = exp.get_first();
switch (c) {
case '$': ++exp; value = exp.ahextoui_skip(); return EVOP_VAL;
case '-': ++exp; return EVOP_SUB;
case '+': ++exp; return EVOP_ADD;
case '*': // asterisk means both multiply and current PC, disambiguate!
++exp;
if (exp[0] == '*') return EVOP_STP; // double asterisks indicates comment
else if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) return EVOP_MUL;
value = etx.pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL;
case '/': ++exp; return EVOP_DIV;
case '=': if (exp[1] == '=') { exp += 2; return EVOP_EQU; } return EVOP_STP;
case '>': if (exp.get_len() >= 2 && exp[1] == '>') { exp += 2; return EVOP_SHR; }
if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++exp;
if (exp[0] == '=') { ++exp; return EVOP_GTE; } return EVOP_GT; }
++exp; return EVOP_HIB;
case '<': if (exp.get_len() >= 2 && exp[1] == '<') { exp += 2; return EVOP_SHL; }
if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++exp;
if (exp[0] == '=') { ++exp; return EVOP_LTE; } return EVOP_LT; }
++exp; return EVOP_LOB;
case '%': // % means both binary and scope closure, disambiguate!
if (exp[1] == '0' || exp[1] == '1') { ++exp; value = exp.abinarytoui_skip(); return EVOP_VAL; }
if (etx.scope_end_pc<0 || scope_depth != etx.scope_depth) return EVOP_NRY;
++exp; value = etx.scope_end_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL;
case '|': ++exp; return EVOP_OR;
case '^': if (prev_op == EVOP_VAL || prev_op == EVOP_RPR) { ++exp; return EVOP_EOR; }
++exp; return EVOP_BAB;
case '&': ++exp; return EVOP_AND;
case '(': if (prev_op != EVOP_VAL) { ++exp; return EVOP_LPR; } return EVOP_STP;
case ')': ++exp; return EVOP_RPR;
case ',':
case '?':
case '\'': return EVOP_STP;
default: { // ! by itself is current scope, !+label char is a local label
if (c == '!' && !(exp + 1).len_label()) {
if (etx.scope_pc < 0) return EVOP_NRY;
++exp; value = etx.scope_pc; section = CurrSection().IsRelativeSection() ? SectionId() : -1; return EVOP_VAL;
} else if (strref::is_number(c)) {
if (prev_op == EVOP_VAL) return EVOP_STP; // value followed by value doesn't make sense, stop
value = exp.atoi_skip(); return EVOP_VAL;
} else if (c == '!' || c == ':' || c=='.' || c=='@' || strref::is_valid_label(c)) {
if (prev_op == EVOP_VAL) return EVOP_STP; // a value followed by a value does not make sense, probably start of a comment (ORCA/LISA?)
char e0 = exp[0];
int start_pos = (e0 == ':' || e0 == '!' || e0 == '.') ? 1 : 0;
strref label = exp.split_range_trim(label_end_char_range, start_pos);
Label *pLabel = pLabel = GetLabel(label, etx.file_ref);
if (!pLabel) {
StatusCode ret = EvalStruct(label, value);
if (ret == STATUS_OK) return EVOP_VAL;
if (ret != STATUS_NOT_STRUCT) return EVOP_ERR; // partial struct
}
if (!pLabel && label.same_str("rept")) { value = etx.rept_cnt; return EVOP_VAL; }
if (!pLabel) { if (StringSymbol *pStr = GetString(label)) { subexp = pStr->get(); return EVOP_EXP; } }
if (!pLabel || !pLabel->evaluated) return EVOP_NRY; // this label could not be found (yet)
value = pLabel->value; section = pLabel->section; return pLabel->reference ? EVOP_XRF : EVOP_VAL;
}
return EVOP_ERR;
}
}
return EVOP_NONE; // shouldn't get here normally
}
//
// EvalExpression
// Uses the Shunting Yard algorithm to convert to RPN first
// which makes the actual calculation trivial and avoids recursion.
// https://en.wikipedia.org/wiki/Shunting-yard_algorithm
//
// Return:
// STATUS_OK means value is completely evaluated
// STATUS_NOT_READY means value could not be evaluated right now
// ERROR_* means there is an error in the expression
//
// Max number of unresolved sections to evaluate in a single expression
#define MAX_EVAL_SECTIONS 4
// determine if a scalar can be a shift
static int mul_as_shift(int scalar)
{
int shift = 0;
while (scalar > 1 && (scalar & 1) == 0) {
shift++;
scalar >>= 1;
}
return scalar == 1 ? shift : 0;
}
#define MAX_EXPR_STACK 2
StatusCode Asm::EvalExpression(strref expression, const struct EvalContext &etx, int &result)
{
int numValues = 0;
int numOps = 0;
strref expression_stack[MAX_EXPR_STACK];
int exp_sp = 0;
char ops[MAX_EVAL_OPER]; // RPN expression
int values[MAX_EVAL_VALUES]; // RPN values (in order of RPN EVOP_VAL operations)
short section_ids[MAX_EVAL_SECTIONS]; // local index of each referenced section
short section_val[MAX_EVAL_VALUES] = { 0 }; // each value can be assigned to one section, or -1 if fixed
short num_sections = 0; // number of sections in section_ids (normally 0 or 1, can be up to MAX_EVAL_SECTIONS)
bool xrefd = false;
values[0] = 0; // Initialize RPN if no expression
{
int sp = 0;
char op_stack[MAX_EVAL_OPER];
EvalOperator prev_op = EVOP_NONE;
expression.trim_whitespace();
while (expression || exp_sp) {
int value = 0;
short section = -1, index_section = -1;
EvalOperator op = EVOP_NONE;
strref subexp;
if (!expression && exp_sp) {
expression = expression_stack[--exp_sp];
op = EVOP_RPR;
} else if (syntax == SYNTAX_MERLIN)
op = RPNToken_Merlin(expression, etx, prev_op, section, value);
else
op = RPNToken(expression, etx, prev_op, section, value, subexp);
if (op == EVOP_ERR)
return ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION;
else if (op == EVOP_NRY)
return STATUS_NOT_READY;
else if (op == EVOP_EXP) {
if (exp_sp >= MAX_EXPR_STACK)
return ERROR_TOO_MANY_VALUES_IN_EXPRESSION;
expression_stack[exp_sp++] = expression;
expression = subexp;
op = EVOP_LPR;
} else if (op == EVOP_XRF) {
xrefd = true;
op = EVOP_VAL;
}
if (section >= 0) {
for (int s = 0; s<num_sections && index_section<0; s++) {
if (section_ids[s] == section) index_section = s;
}
if (index_section<0) {
if (num_sections <= MAX_EVAL_SECTIONS)
section_ids[index_section = num_sections++] = section;
else
return STATUS_NOT_READY;
}
}
// this is the body of the shunting yard algorithm
if (op == EVOP_VAL) {
section_val[numValues] = index_section; // only value operators can be section specific
values[numValues++] = value;
ops[numOps++] = op;
} else if (op == EVOP_LPR) {
op_stack[sp++] = op;
} else if (op == EVOP_RPR) {
while (sp && op_stack[sp-1]!=EVOP_LPR) {
sp--;
ops[numOps++] = op_stack[sp];
}
// check that there actually was a left parenthesis
if (!sp || op_stack[sp-1]!=EVOP_LPR)
return ERROR_UNBALANCED_RIGHT_PARENTHESIS;
sp--; // skip open paren
} else if (op == EVOP_STP) {
break;
} else {
bool skip = false;
if ((prev_op >= EVOP_EQU && prev_op <= EVOP_GTE) || (prev_op==EVOP_HIB || prev_op==EVOP_LOB)) {
if (op==EVOP_SUB)
op = EVOP_NEG;
else if (op == EVOP_ADD)
skip = true;
}
if (op == EVOP_SUB && sp && op_stack[sp-1]== EVOP_SUB)
sp--;
else {
while (sp && !skip) {
EvalOperator p = (EvalOperator)op_stack[sp-1];
if (p==EVOP_LPR || op>p)
break;
ops[numOps++] = p;
sp--;
}
op_stack[sp++] = op;
}
}
// check for out of bounds or unexpected input
if (numValues==MAX_EVAL_VALUES)
return ERROR_TOO_MANY_VALUES_IN_EXPRESSION;
else if (numOps==MAX_EVAL_OPER || sp==MAX_EVAL_OPER)
return ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION;
prev_op = op;
expression.skip_whitespace();
}
while (sp) {
sp--;
ops[numOps++] = op_stack[sp];
}
}
// Check if dependent on XREF'd symbol
if (xrefd)
return STATUS_XREF_DEPENDENT;
// processing the result RPN will put the completed expression into values[0].
// values is used as both the queue and the stack of values since reads/writes won't
// exceed itself.
{
int valIdx = 0;
int ri = 0; // RPN index (value)
int prev_val = values[0];
int shift_bits = 0; // special case for relative reference to low byte / high byte
short section_counts[MAX_EVAL_SECTIONS][MAX_EVAL_VALUES] = { 0 };
for (int o = 0; o<numOps; o++) {
EvalOperator op = (EvalOperator)ops[o];
shift_bits = 0;
prev_val = ri ? values[ri-1] : prev_val;
if (op!=EVOP_VAL && op!=EVOP_LOB && op!=EVOP_HIB && op!=EVOP_BAB && op!=EVOP_SUB && ri<2)
break; // ignore suffix operations that are lacking values
switch (op) {
case EVOP_VAL: // value
for (int i = 0; i<num_sections; i++)
section_counts[i][ri] = i==section_val[ri] ? 1 : 0;
values[ri++] = values[valIdx++]; break;
case EVOP_EQU: // ==
ri--;
values[ri - 1] = values[ri - 1] == values[ri];
break;
case EVOP_GT: // >
ri--;
values[ri - 1] = values[ri - 1] > values[ri];
break;
case EVOP_LT: // <
ri--;
values[ri - 1] = values[ri - 1] < values[ri];
break;
case EVOP_GTE: // >=
ri--;
values[ri - 1] = values[ri - 1] >= values[ri];
break;
case EVOP_LTE: // >=
ri--;
values[ri - 1] = values[ri - 1] <= values[ri];
break;
case EVOP_ADD: // +
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] += section_counts[i][ri];
values[ri-1] += values[ri]; break;
case EVOP_SUB: // -
if (ri==1)
values[ri-1] = -values[ri-1];
else if (ri>1) {
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] -= section_counts[i][ri];
values[ri-1] -= values[ri];
} break;
case EVOP_NEG:
if (ri>=1)
values[ri-1] = -values[ri-1];
break;
case EVOP_MUL: // *
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
shift_bits = mul_as_shift(values[ri]);
prev_val = values[ri - 1];
values[ri-1] *= values[ri]; break;
case EVOP_DIV: // /
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
shift_bits = -mul_as_shift(values[ri]);
prev_val = values[ri - 1];
values[ri - 1] /= values[ri]; break;
case EVOP_AND: // &
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
values[ri-1] &= values[ri]; break;
case EVOP_OR: // |
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
values[ri-1] |= values[ri]; break;
case EVOP_EOR: // ^
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
values[ri-1] ^= values[ri]; break;
case EVOP_SHL: // <<
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
shift_bits = values[ri];
prev_val = values[ri - 1];
values[ri - 1] <<= values[ri]; break;
case EVOP_SHR: // >>
ri--;
for (int i = 0; i<num_sections; i++)
section_counts[i][ri-1] |= section_counts[i][ri];
shift_bits = -values[ri];
prev_val = values[ri - 1];
values[ri - 1] >>= values[ri]; break;
case EVOP_LOB: // low byte
if (ri)
values[ri-1] &= 0xff;
break;
case EVOP_HIB:
if (ri) {
shift_bits = -8;
values[ri - 1] = values[ri - 1] >> 8;
} break;
case EVOP_BAB:
if (ri) {
shift_bits = -16;
values[ri - 1] = (values[ri - 1] >> 16);
}
break;
default:
return ERROR_EXPRESSION_OPERATION;
break;
}
if (shift_bits==0 && ri)
prev_val = values[ri-1];
}
int section_index = -1;
bool curr_relative = false;
// If relative to any section unless specifically interested in a relative value then return not ready
for (int i = 0; i<num_sections; i++) {
if (section_counts[i][0]) {
if (section_counts[i][0]!=1 || section_index>=0)
return STATUS_NOT_READY;
else if (etx.relative_section==section_ids[i])
curr_relative = true;
else if (etx.relative_section>=0)
return STATUS_NOT_READY;
section_index = i;
}
}
result = values[0];
if (section_index>=0 && !curr_relative) {
lastEvalSection = section_ids[section_index];
lastEvalValue = prev_val;
lastEvalShift = shift_bits;
return STATUS_RELATIVE_SECTION;
}
}
return STATUS_OK;
}
// if an expression could not be evaluated, add it along with
// the action to perform if it can be evaluated later.
void Asm::AddLateEval(int target, int pc, int scope_pc, strref expression, strref source_file, LateEval::Type type)
{
LateEval le;
le.address = pc;
le.scope = scope_pc;
le.scope_depth = scope_depth;
le.target = target;
le.section = (int)(&CurrSection() - &allSections[0]);
le.rept = contextStack.curr().repeat_total - contextStack.curr().repeat;
le.file_ref = -1; // current or xdef'd
le.label.clear();
le.expression = expression;
le.source_file = source_file;
le.type = type;
lateEval.push_back(le);
}
void Asm::AddLateEval(strref label, int pc, int scope_pc, strref expression, LateEval::Type type)
{
LateEval le;
le.address = pc;
le.scope = scope_pc;
le.scope_depth = scope_depth;
le.target = -1;
le.label = label;
le.section = (int)(&CurrSection() - &allSections[0]);
le.rept = contextStack.curr().repeat_total - contextStack.curr().repeat;
le.file_ref = -1; // current or xdef'd
le.expression = expression;
le.source_file.clear();
le.type = type;
lateEval.push_back(le);
}
// When a label is defined or a scope ends check if there are
// any related late label evaluators that can now be evaluated.
StatusCode Asm::CheckLateEval(strref added_label, int scope_end, bool print_missing_reference_errors)
{
bool evaluated_label = true;
strref new_labels[MAX_LABELS_EVAL_ALL];
int num_new_labels = 0;
if (added_label)
new_labels[num_new_labels++] = added_label;
bool all = !added_label;
while (evaluated_label) {
evaluated_label = false;
std::vector<LateEval>::iterator i = lateEval.begin();
while (i != lateEval.end()) {
int value = 0;
// check if this expression is related to the late change (new label or end of scope)
bool check = all || num_new_labels==MAX_LABELS_EVAL_ALL;
for (int l = 0; l<num_new_labels && !check; l++)
check = i->expression.find(new_labels[l]) >= 0;
if (!check && scope_end>0) {
int gt_pos = 0;
while (gt_pos>=0 && !check) {
gt_pos = i->expression.find_at('%', gt_pos);
if (gt_pos>=0) {
if (i->expression[gt_pos+1]=='%')
gt_pos++;
else
check = true;
gt_pos++;
}
}
}
if (check) {
struct EvalContext etx(i->address, i->scope, scope_end,
i->type == LateEval::LET_BRANCH ? SectionId() : -1, i->rept);
etx.scope_depth = i->scope_depth;
etx.file_ref = i->file_ref;
StatusCode ret = EvalExpression(i->expression, etx, value);
if (ret == STATUS_OK || ret==STATUS_RELATIVE_SECTION) {
// Check if target section merged with another section
int trg = i->target;
int sec = i->section;
if (i->type != LateEval::LET_LABEL) {
if (allSections[sec].IsMergedSection()) {
trg += allSections[sec].merged_offset;
sec = allSections[sec].merged_section;
}
}
bool resolved = true;
switch (i->type) {
case LateEval::LET_BYTE:
if (ret==STATUS_RELATIVE_SECTION) {
if (i->section<0)
resolved = false;
else {
allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 1, lastEvalShift);
value = 0;
}
}
if (trg >= allSections[sec].size())
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
allSections[sec].SetByte(trg, value);
break;
case LateEval::LET_ABS_REF:
if (ret==STATUS_RELATIVE_SECTION) {
if (i->section<0)
resolved = false;
else {
allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 2, lastEvalShift);
value = 0;
}
}
if ((trg+1) >= allSections[sec].size())
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
allSections[sec].SetWord(trg, value);
break;
case LateEval::LET_ABS_L_REF:
if (ret==STATUS_RELATIVE_SECTION) {
if (i->section<0)
resolved = false;
else {
allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 3, lastEvalShift);
value = 0;
}
}
if ((trg+2) >= allSections[sec].size())
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
allSections[sec].SetTriple(trg, value);
break;
case LateEval::LET_ABS_4_REF:
if (ret==STATUS_RELATIVE_SECTION) {
if (i->section<0)
resolved = false;
else {
allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 4, lastEvalShift);
value = 0;
}
}
if ((trg+3) >= allSections[sec].size())
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
allSections[sec].SetQuad(trg, value);
break;
case LateEval::LET_BRANCH:
value -= i->address+1;
if (value<-128 || value>127) {
i = lateEval.erase(i);
return ERROR_BRANCH_OUT_OF_RANGE;
} if (trg >= allSections[sec].size())
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
allSections[sec].SetByte(trg, value);
break;
case LateEval::LET_BRANCH_16:
value -= i->address+2;
if (trg >= allSections[sec].size())
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
allSections[sec].SetWord(trg, value);
break;
case LateEval::LET_LABEL: {
Label *label = GetLabel(i->label, i->file_ref);
if (!label)
return ERROR_LABEL_MISPLACED_INTERNAL;
label->value = value;
label->evaluated = true;
label->section = ret==STATUS_RELATIVE_SECTION ? i->section : -1;
if (num_new_labels<MAX_LABELS_EVAL_ALL)
new_labels[num_new_labels++] = label->label_name;
evaluated_label = true;
char f = i->label[0], l = i->label.get_last();
LabelAdded(label, f=='.' || f=='!' || f=='@' || f==':' || l=='$');
break;
}
default:
break;
}
if (resolved)
i = lateEval.erase(i);
} else {
if (print_missing_reference_errors && ret!=STATUS_XREF_DEPENDENT) {
PrintError(i->expression, ret);
error_encountered = true;
}
++i;
}
} else
++i;
}
all = false;
added_label.clear();
}
return STATUS_OK;
}
//
//
// LABELS
//
//
// Get a label record if it exists
Label *Asm::GetLabel(strref label)
{
unsigned int label_hash = label.fnv1a();
unsigned int index = FindLabelIndex(label_hash, labels.getKeys(), labels.count());
while (index < labels.count() && label_hash == labels.getKey(index)) {
if (label.same_str(labels.getValue(index).label_name))
return labels.getValues() + index;
index++;
}
return nullptr;
}
// Get a protected label record from a file if it exists
Label *Asm::GetLabel(strref label, int file_ref)
{
if (file_ref>=0 && file_ref<(int)externals.size()) {
ExtLabels &labs = externals[file_ref];
unsigned int label_hash = label.fnv1a();
unsigned int index = FindLabelIndex(label_hash, labs.labels.getKeys(), labs.labels.count());
while (index < labs.labels.count() && label_hash == labs.labels.getKey(index)) {
if (label.same_str(labs.labels.getValue(index).label_name))
return labs.labels.getValues() + index;
index++;
}
}
return GetLabel(label);
}
// If exporting labels, append this label to the list
void Asm::LabelAdded(Label *pLabel, bool local)
{
if (pLabel && pLabel->evaluated) {
if (map.size() == map.capacity())
map.reserve(map.size() + 256);
MapSymbol sym;
sym.name = pLabel->label_name;
sym.section = pLabel->section;
sym.value = pLabel->value;
sym.local = local;
pLabel->mapIndex = pLabel->evaluated ? -1 : (int)map.size();
map.push_back(sym);
}
}
// Add a label entry
Label* Asm::AddLabel(unsigned int hash) {
unsigned int index = FindLabelIndex(hash, labels.getKeys(), labels.count());
labels.insert(index, hash);
return labels.getValues() + index;
}
// mark a label as a local label
void Asm::MarkLabelLocal(strref label, bool scope_reserve)
{
LocalLabelRecord rec;
rec.label = label;
rec.scope_depth = scope_depth;
rec.scope_reserve = scope_reserve;
localLabels.push_back(rec);
}
// find all local labels or up to given scope level and remove them
StatusCode Asm::FlushLocalLabels(int scope_exit)
{
StatusCode status = STATUS_OK;
// iterate from end of local label records and early out if the label scope is lower than the current.
std::vector<LocalLabelRecord>::iterator i = localLabels.end();
while (i!=localLabels.begin()) {
--i;
if (i->scope_depth < scope_depth)
break;
strref label = i->label;
StatusCode this_status = CheckLateEval(label);
if (this_status>FIRST_ERROR)
status = this_status;
if (!i->scope_reserve || i->scope_depth<=scope_exit) {
unsigned int index = FindLabelIndex(label.fnv1a(), labels.getKeys(), labels.count());
while (index<labels.count()) {
if (label.same_str_case(labels.getValue(index).label_name)) {
if (i->scope_reserve) {
if (LabelPool *pool = GetLabelPool(labels.getValue(index).pool_name)) {
pool->Release(labels.getValue(index).value);
break;
}
}
labels.remove(index);
break;
}
++index;
}
i = localLabels.erase(i);
}
}
return status;
}
// Get a label pool by name
LabelPool* Asm::GetLabelPool(strref pool_name)
{
unsigned int pool_hash = pool_name.fnv1a();
unsigned int ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count());
while (ins < labelPools.count() && pool_hash == labelPools.getKey(ins)) {
if (pool_name.same_str(labelPools.getValue(ins).pool_name)) {
return &labelPools.getValue(ins);
}
ins++;
}
return nullptr;
}
// When going out of scope, label pools are deleted.
void Asm::FlushLabelPools(int scope_exit)
{
unsigned int i = 0;
while (i<labelPools.count()) {
if (labelPools.getValue(i).scopeDepth >= scope_exit)
labelPools.remove(i);
else
++i;
}
}
// Add a label pool
StatusCode Asm::AddLabelPool(strref name, strref args)
{
unsigned int pool_hash = name.fnv1a();
unsigned int ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count());
unsigned int index = ins;
while (index < labelPools.count() && pool_hash == labelPools.getKey(index)) {
if (name.same_str(labelPools.getValue(index).pool_name))
return ERROR_LABEL_POOL_REDECLARATION;
index++;
}
// check that there is at least one valid address
int ranges = 0;
int num32 = 0;
unsigned short aRng[256];
struct EvalContext etx;
SetEvalCtxDefaults(etx);
while (strref arg = args.split_token_trim(',')) {
strref start = arg[0]=='(' ? arg.scoped_block_skip() : arg.split_token_trim('-');
int addr0 = 0, addr1 = 0;
if (STATUS_OK != EvalExpression(start, etx, addr0))
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
if (STATUS_OK != EvalExpression(arg, etx, addr1))
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
if (addr1<=addr0 || addr0<0)
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
aRng[ranges++] = addr0;
aRng[ranges++] = addr1;
num32 += (addr1-addr0+15)>>4;
if (ranges >(MAX_POOL_RANGES*2) ||
num32 > ((MAX_POOL_BYTES+15)>>4))
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
}
if (!ranges)
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
LabelPool pool;
pool.pool_name = name;
pool.numRanges = ranges>>1;
pool.scopeDepth = scope_depth;
memset(pool.usedMap, 0, sizeof(unsigned int) * num32);
for (int r = 0; r<ranges; r++)
pool.ranges[r] = aRng[r];
labelPools.insert(ins, pool_hash);
LabelPool &poolValue = labelPools.getValue(ins);
poolValue = pool;
return STATUS_OK;
}
StatusCode Asm::AssignPoolLabel(LabelPool &pool, strref label)
{
strref type = label;
label = type.split_token('.');
int bytes = 1;
switch (strref::tolower(type.get_first())) {
case 'l': bytes = 4; break;
case 't': bytes = 3; break;
case 'd':
case 'w': bytes = 2; break;
}
if (GetLabel(label))
return ERROR_POOL_LABEL_ALREADY_DEFINED;
unsigned int addr;
StatusCode error = pool.Reserve(bytes, addr);
if (error != STATUS_OK)
return error;
Label *pLabel = AddLabel(label.fnv1a());
pLabel->label_name = label;
pLabel->pool_name = pool.pool_name;
pLabel->evaluated = true;
pLabel->section = -1; // pool labels are section-less
pLabel->value = addr;
pLabel->pc_relative = true;
pLabel->constant = true;
pLabel->external = false;
pLabel->reference = false;
MarkLabelLocal(label, true);
return error;
}
// Request a label from a pool
StatusCode LabelPool::Reserve(int numBytes, unsigned int &ret_addr)
{
unsigned int *map = usedMap;
unsigned short *pRanges = ranges;
for (int r = 0; r<numRanges; r++) {
int sequence = 0;
unsigned int a0 = *pRanges++, a1 = *pRanges++;
unsigned int addr = a1-1, *range_map = map;
while (addr>=a0 && sequence<numBytes) {
unsigned int chk = *map++, m = 3;
while (m && addr >= a0) {
if ((m & chk)==0) {
sequence++;
if (sequence == numBytes)
break;
} else
sequence = 0;
--addr;
m <<= 2;
}
}
if (sequence == numBytes) {
unsigned int index = (a1-addr-numBytes);
unsigned int *addr_map = range_map + (index>>4);
unsigned int m = numBytes << (index << 1);
for (int b = 0; b<numBytes; b++) {
*addr_map |= m;
unsigned int _m = m << 2;
if (!_m) { m <<= 30; addr_map++; } else { m = _m; }
}
ret_addr = addr;
return STATUS_OK;
}
}
return ERROR_OUT_OF_LABELS_IN_POOL;
}
// Release a label from a pool (at scope closure)
StatusCode LabelPool::Release(unsigned int addr) {
unsigned int *map = usedMap;
unsigned short *pRanges = ranges;
for (int r = 0; r<numRanges; r++) {
unsigned short a0 = *pRanges++, a1 = *pRanges++;
if (addr>=a0 && addr<a1) {
unsigned int index = (a1-addr-1);
map += index>>4;
index &= 0xf;
unsigned int u = *map, m = 3 << (index << 1);
unsigned int b = u & m, bytes = b >> (index << 1);
if (bytes) {
for (unsigned int f = 0; f<bytes; f++) {
u &= ~m;
unsigned int _m = m>>2;
if (!_m) { m <<= 30; *map-- = u; } else { m = _m; }
}
*map = u;
return STATUS_OK;
} else
return ERROR_INTERNAL_LABEL_POOL_ERROR;
} else
map += (a1-a0+15)>>4;
}
return STATUS_OK;
}
// Check if a label is marked as an xdef
bool Asm::MatchXDEF(strref label)
{
unsigned int hash = label.fnv1a();
unsigned int pos = FindLabelIndex(hash, xdefs.getKeys(), xdefs.count());
while (pos < xdefs.count() && xdefs.getKey(pos) == hash) {
if (label.same_str_case(xdefs.getValue(pos)))
return true;
++pos;
}
return false;
}
// assignment of label (<label> = <expression>)
StatusCode Asm::AssignLabel(strref label, strref line, bool make_constant)
{
line.trim_whitespace();
int val = 0;
struct EvalContext etx;
SetEvalCtxDefaults(etx);
StatusCode status = EvalExpression(line, etx, val);
if (status != STATUS_NOT_READY && status != STATUS_OK && status != STATUS_RELATIVE_SECTION)
return status;
Label *pLabel = GetLabel(label);
if (pLabel) {
if (pLabel->constant && pLabel->evaluated && val != pLabel->value)
return (status == STATUS_NOT_READY) ? STATUS_OK : ERROR_MODIFYING_CONST_LABEL;
} else
pLabel = AddLabel(label.fnv1a());
pLabel->label_name = label;
pLabel->pool_name.clear();
pLabel->evaluated = status==STATUS_OK || status == STATUS_RELATIVE_SECTION;
pLabel->section = status == STATUS_RELATIVE_SECTION ? lastEvalSection : -1; // assigned labels are section-less
pLabel->value = val;
pLabel->mapIndex = -1;
pLabel->pc_relative = false;
pLabel->constant = make_constant;
pLabel->external = MatchXDEF(label);
pLabel->reference = false;
bool local = label[0]=='.' || label[0]=='@' || label[0]=='!' || label[0]==':' || label.get_last()=='$';
if (!pLabel->evaluated)
AddLateEval(label, CurrSection().GetPC(), scope_address[scope_depth], line, LateEval::LET_LABEL);
else {
if (local)
MarkLabelLocal(label);
LabelAdded(pLabel, local);
return CheckLateEval(label);
}
return STATUS_OK;
}
// Adding a fixed address label
StatusCode Asm::AddressLabel(strref label)
{
StatusCode status = STATUS_OK;
Label *pLabel = GetLabel(label);
bool constLabel = false;
if (!pLabel)
pLabel = AddLabel(label.fnv1a());
else if (pLabel->constant && pLabel->value != CurrSection().GetPC())
return ERROR_MODIFYING_CONST_LABEL;
else
constLabel = pLabel->constant;
pLabel->label_name = label;
pLabel->pool_name.clear();
pLabel->section = CurrSection().IsRelativeSection() ? SectionId() : -1; // address labels are based on section
pLabel->value = CurrSection().GetPC();
pLabel->evaluated = true;
pLabel->pc_relative = true;
pLabel->external = MatchXDEF(label);
pLabel->reference = false;
pLabel->constant = constLabel;
last_label = label;
bool local = label[0]=='.' || label[0]=='@' || label[0]=='!' || label[0]==':' || label.get_last()=='$';
LabelAdded(pLabel, local);
if (local)
MarkLabelLocal(label);
status = CheckLateEval(label);
if (!local && label[0]!=']') { // MERLIN: Variable label does not invalidate local labels
StatusCode this_status = FlushLocalLabels();
if (status<FIRST_ERROR && this_status>=FIRST_ERROR)
status = this_status;
}
return status;
}
// include symbols listed from a .sym file or all if no listing
StatusCode Asm::IncludeSymbols(strref line)
{
strref symlist = line.before('"').get_trimmed_ws();
line = line.between('"', '"');
size_t size;
if (char *buffer = LoadText(line, size)) {
strref symfile(buffer, strl_t(size));
while (symfile) {
symfile.skip_whitespace();
if (symfile[0]=='{') // don't include local labels
symfile.scoped_block_skip();
if (strref symdef = symfile.line()) {
strref symtype = symdef.split_token(' ');
strref label = symdef.split_token_trim('=');
bool constant = symtype.same_str(".const"); // first word is either .label or .const
if (symlist) {
strref symchk = symlist;
while (strref symwant = symchk.split_token_trim(',')) {
if (symwant.same_str_case(label)) {
AssignLabel(label, symdef, constant);
break;
}
}
} else
AssignLabel(label, symdef, constant);
}
}
loadedData.push_back(buffer);
} else
return ERROR_COULD_NOT_INCLUDE_FILE;
return STATUS_OK;
}
// Get a string record if it exists
StringSymbol *Asm::GetString(strref string_name)
{
unsigned int string_hash = string_name.fnv1a();
unsigned int index = FindLabelIndex(string_hash, strings.getKeys(), strings.count());
while (index < strings.count() && string_hash == strings.getKey(index)) {
if (string_name.same_str(strings.getValue(index).string_name))
return strings.getValues() + index;
index++;
}
return nullptr;
}
// Add or modify a string record
StringSymbol *Asm::AddString(strref string_name, strref string_value)
{
StringSymbol *pStr = GetString(string_name);
if (pStr==nullptr) {
unsigned int string_hash = string_name.fnv1a();
unsigned int index = FindLabelIndex(string_hash, strings.getKeys(), strings.count());
strings.insert(index, string_hash);
pStr = strings.getValues() + index;
pStr->string_name = string_name;
pStr->string_value.invalidate();
pStr->string_value.clear();
}
if (pStr->string_value.cap()) {
free(pStr->string_value.charstr());
pStr->string_value.invalidate();
pStr->string_value.clear();
}
pStr->string_const = string_value;
return pStr;
}
// append a string to another string
StatusCode StringSymbol::Append(strref append)
{
if (!append)
return STATUS_OK;
strl_t add_len = append.get_len();
if (!string_value.cap()) {
strl_t new_len = (add_len + 0xff)&(~(strl_t)0xff);
char *buf = (char*)malloc(new_len);
if (!buf)
return ERROR_OUT_OF_MEMORY;
string_value.set_overlay(buf, new_len);
string_value.copy(string_const);
} else if (string_value.cap() < (string_value.get_len() + add_len)) {
strl_t new_len = (string_value.get_len() + add_len + 0xff)&(~(strl_t)0xff);
char *buf = (char*)malloc(new_len);
if (!buf)
return ERROR_OUT_OF_MEMORY;
strovl ovl(buf, new_len);
ovl.copy(string_value.get_strref());
free(string_value.charstr());
string_value.set_overlay(buf, new_len);
}
string_const.clear();
string_value.append(append);
return STATUS_OK;
}
StatusCode Asm::ParseStringOp(StringSymbol *pStr, strref line)
{
line.skip_whitespace();
if (line[0] == '+')
++line;
for (;;) {
line.skip_whitespace();
if (line[0] == '"') {
strref substr = line.between('"', '"');
line += substr.get_len() + 2;
pStr->Append(substr);
} else {
strref label = line.split_range(syntax == SYNTAX_MERLIN ?
label_end_char_range_merlin : label_end_char_range);
if (StringSymbol *pStr2 = GetString(label))
pStr->Append(pStr2->get());
else if (Label *pLabel = GetLabel(label)) {
if (!pLabel->evaluated)
return ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY;
strown<32> lblstr;
lblstr.sprintf("$%x", pLabel->value);
pStr->Append(lblstr.get_strref());
} else
break;
}
line.skip_whitespace();
if (!line || line[0] != '+')
break;
++line;
line.skip_whitespace();
}
return STATUS_OK;
}
StatusCode Asm::StringAction(StringSymbol *pStr, strref line)
{
line.skip_whitespace();
if (line[0] == '+' && line[1] == '=') { // append strings
line += 2;
line.skip_whitespace();
return ParseStringOp(pStr, line);
} else if (line[0] == '=') {
++line;
line.skip_whitespace();
pStr->clear();
return ParseStringOp(pStr, line);
} else {
strref str = pStr->string_value.valid() ?
pStr->string_value.get_strref() : pStr->string_const;
if (!str)
return STATUS_OK;
char *macro = (char*)malloc(str.get_len());
strovl mac(macro, str.get_len());
mac.copy(str);
mac.replace("\\n", "\n");
loadedData.push_back(macro);
PushContext(contextStack.curr().source_name, mac.get_strref(), mac.get_strref());
if (scope_depth >= (MAX_SCOPE_DEPTH - 1))
return ERROR_TOO_DEEP_SCOPE;
else
scope_address[++scope_depth] = CurrSection().GetPC();
contextStack.curr().scoped_context = true;
return STATUS_OK;
}
return STATUS_OK;
}
//
//
// CONDITIONAL ASSEMBLY
//
//
// Encountered #if or #ifdef, return true if assembly is enabled
bool Asm::NewConditional() {
if (conditional_nesting[conditional_depth]) {
conditional_nesting[conditional_depth]++;
return false;
}
return true;
}
// Encountered #endif, close out the current conditional
void Asm::CloseConditional() {
if (conditional_depth>contextStack.curr().conditional_ctx)
conditional_depth--;
else
conditional_consumed[conditional_depth] = false;
}
// Check if this conditional will nest the assembly (a conditional is already consumed)
void Asm::CheckConditionalDepth() {
if (conditional_consumed[conditional_depth]) {
conditional_depth++;
conditional_source[conditional_depth] = contextStack.curr().read_source.get_line();
conditional_consumed[conditional_depth] = false;
conditional_nesting[conditional_depth] = 0;
}
}
// This conditional block is going to be assembled, mark it as consumed
void Asm::ConsumeConditional()
{
conditional_source[conditional_depth] = contextStack.curr().read_source.get_line();
conditional_consumed[conditional_depth] = true;
}
// This conditional block is not going to be assembled so mark that it is nesting
void Asm::SetConditional()
{
conditional_source[conditional_depth] = contextStack.curr().read_source.get_line();
conditional_nesting[conditional_depth] = 1;
}
// Returns true if assembly is currently enabled
bool Asm::ConditionalAsm() {
return conditional_nesting[conditional_depth]==0;
}
// Returns true if this conditional has a block that has already been assembled
bool Asm::ConditionalConsumed() {
return conditional_consumed[conditional_depth];
}
// Returns true if this conditional can be consumed
bool Asm::ConditionalAvail() {
return conditional_nesting[conditional_depth]==1 &&
!conditional_consumed[conditional_depth];
}
// This conditional block is enabled and the prior wasn't
void Asm::EnableConditional(bool enable) {
if (enable) {
conditional_nesting[conditional_depth] = 0;
conditional_consumed[conditional_depth] = true;
}
}
// Conditional else that does not enable block
void Asm::ConditionalElse() {
if (conditional_consumed[conditional_depth])
conditional_nesting[conditional_depth]++;
}
// Conditional statement evaluation (true/false)
StatusCode Asm::EvalStatement(strref line, bool &result)
{
int equ = line.find('=');
struct EvalContext etx;
SetEvalCtxDefaults(etx);
bool invert = line.get_first()=='!';
if (invert)
++line;
int value;
if (STATUS_OK != EvalExpression(line, etx, value))
return ERROR_CONDITION_COULD_NOT_BE_RESOLVED;
result = (value!=0 && !invert) || (value==0 && invert);
return STATUS_OK;
}
// Add a folder for including files
void Asm::AddIncludeFolder(strref path)
{
if (!path)
return;
for (std::vector<strref>::const_iterator i = includePaths.begin(); i!=includePaths.end(); ++i) {
if (path.same_str(*i))
return;
}
if (includePaths.size()==includePaths.capacity())
includePaths.reserve(includePaths.size() + 16);
includePaths.push_back(path);
}
// unique key binary search
int LookupOpCodeIndex(unsigned int hash, OPLookup *lookup, int count)
{
int first = 0;
while (count!=first) {
int index = (first+count)/2;
unsigned int read = lookup[index].op_hash;
if (hash==read) {
return index;
} else if (hash>read)
first = index+1;
else
count = index;
}
return -1; // index not found
}
// Encountered a REPT or LUP
StatusCode Asm::Directive_Rept(strref line, strref source_file)
{
SourceContext &ctx = contextStack.curr();
strref read_source = ctx.read_source;
if (read_source.is_substr(line.get())) {
read_source.skip(strl_t(line.get() - read_source.get()));
strref expression;
if (syntax == SYNTAX_MERLIN || end_macro_directive) {
expression = line; // Merlin repeat body begins next line
read_source.line();
} else {
int block = read_source.find('{');
if (block<0)
return ERROR_REPT_MISSING_SCOPE;
expression = read_source.get_substr(0, block);
read_source += block;
read_source.skip_whitespace();
}
expression.trim_whitespace();
int count;
struct EvalContext etx;
SetEvalCtxDefaults(etx);
if (STATUS_OK != EvalExpression(expression, etx, count))
return ERROR_REPT_COUNT_EXPRESSION;
strref recur;
if (syntax == SYNTAX_MERLIN || end_macro_directive) {
recur = read_source; // Merlin repeat body ends at "--^"
while (strref next_line = read_source.line()) {
next_line = next_line.before_or_full(';');
next_line = next_line.before_or_full(c_comment);
int term = next_line.find(end_macro_directive ? "endr" : "--^");
if (term >= 0) {
recur = recur.get_substr(0, strl_t(next_line.get() + term - recur.get()));
break;
}
}
} else
recur = read_source.scoped_block_skip();
ctx.next_source = read_source;
PushContext(ctx.source_name, ctx.source_file, recur, count);
}
return STATUS_OK;
}
// macro: create an assembler macro
StatusCode Asm::Directive_Macro(strref line, strref source_file)
{
strref read_source = contextStack.curr().read_source;
if (read_source.is_substr(line.get())) {
read_source.skip(strl_t(line.get()-read_source.get()));
StatusCode error = AddMacro(read_source, contextStack.curr().source_name,
contextStack.curr().source_file, read_source);
contextStack.curr().next_source = read_source;
return error;
}
return STATUS_OK;
}
// string: create a symbolic string
StatusCode Asm::Directive_String(strref line)
{
line.skip_whitespace();
strref string_name = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0);
if (line[0]=='=' || keyword_equ.is_prefix_word(line)) {
line.next_word_ws();
strref substr = line;
if (line[0] == '"') {
substr = line.between('"', '"');
line += substr.get_len() + 2;
StringSymbol *pStr = AddString(string_name, substr);
if (pStr == nullptr)
return ERROR_OUT_OF_MEMORY;
line.skip_whitespace();
if (line[0] == '+')
return ParseStringOp(pStr, line);
} else {
StringSymbol *pStr = AddString(string_name, strref());
return ParseStringOp(pStr, line);
}
} else {
if (!AddString(string_name, strref()))
return ERROR_OUT_OF_MEMORY;
}
return STATUS_OK;
}
StatusCode Asm::Directive_Undef(strref line)
{
strref name = line.split_range_trim(syntax == SYNTAX_MERLIN ? label_end_char_range_merlin : label_end_char_range);
unsigned int name_hash = name.fnv1a();
unsigned int index = FindLabelIndex(name_hash, labels.getKeys(), labels.count());
while (index < labels.count() && name_hash == labels.getKey(index)) {
if (name.same_str(labels.getValue(index).label_name)) {
labels.remove(index);
return STATUS_OK;
}
index++;
}
index = FindLabelIndex(name_hash, strings.getKeys(), strings.count());
while (index < strings.count() && name_hash == strings.getKey(index)) {
if (name.same_str(strings.getValue(index).string_name)) {
StringSymbol str = strings.getValue(index);
if (str.string_value.cap()) {
free(str.string_value.charstr());
str.string_value.invalidate();
}
strings.remove(index);
return STATUS_OK;
}
index++;
}
return STATUS_OK;
}
// include: read in a source file and assemble at this point
StatusCode Asm::Directive_Include(strref line)
{
strref file = line.between('"', '"');
if (!file) // MERLIN: No quotes around PUT filenames
file = line.split_range(filename_end_char_range);
size_t size = 0;
char *buffer = nullptr;
if ((buffer = LoadText(file, size))) {
loadedData.push_back(buffer);
strref src(buffer, strl_t(size));
PushContext(file, src, src);
} else if (syntax == SYNTAX_MERLIN) {
// MERLIN include file name rules
if (file[0]>='!' && file[0]<='&' && (buffer = LoadText(file+1, size))) {
loadedData.push_back(buffer); // MERLIN: prepend with !-& to not auto-prepend with T.
strref src(buffer, strl_t(size));
PushContext(file+1, src, src);
} else {
strown<512> fileadd(file[0]>='!' && file[0]<='&' ? (file+1) : file);
fileadd.append(".s");
if ((buffer = LoadText(fileadd.get_strref(), size))) {
loadedData.push_back(buffer); // MERLIN: !+filename appends .S to filenames
strref src(buffer, strl_t(size));
PushContext(file, src, src);
} else {
fileadd.copy("T."); // MERLIN: just filename prepends T. to filenames
fileadd.append(file[0]>='!' && file[0]<='&' ? (file+1) : file);
if ((buffer = LoadText(fileadd.get_strref(), size))) {
loadedData.push_back(buffer);
strref src(buffer, strl_t(size));
PushContext(file, src, src);
}
}
}
}
if (!size)
return ERROR_COULD_NOT_INCLUDE_FILE;
return STATUS_OK;
}
// incbin: import binary data in place
StatusCode Asm::Directive_Incbin(strref line, int skip, int len)
{
line = line.between('"', '"');
strown<512> filename(line);
size_t size = 0;
if (char *buffer = LoadBinary(line, size)) {
int bin_size = (int)size - skip;
if (bin_size>len)
bin_size = len;
if (bin_size>0)
AddBin((const unsigned char*)buffer+skip, bin_size);
free(buffer);
return STATUS_OK;
}
return ERROR_COULD_NOT_INCLUDE_FILE;
}
// import is a catch-all file reference
StatusCode Asm::Directive_Import(strref line)
{
line.skip_whitespace();
int skip = 0; // binary import skip this amount
int len = 0; // binary import load up to this amount
strref param; // read out skip & max len parameters
int q = line.find('"');
if (q>=0) {
param = line + q;
param.scoped_block_skip();
param.trim_whitespace();
if (param[0]==',') {
++param;
param.skip_whitespace();
if (param) {
struct EvalContext etx;
SetEvalCtxDefaults(etx);
EvalExpression(param.split_token_trim(','), etx, skip);
if (param)
EvalExpression(param, etx, len);
}
}
}
if (line[0]=='"')
return Directive_Incbin(line);
else if (import_source.is_prefix_word(line)) {
line += import_source.get_len();
line.skip_whitespace();
return Directive_Include(line);
} else if (import_binary.is_prefix_word(line)) {
line += import_binary.get_len();
line.skip_whitespace();
return Directive_Incbin(line, skip, len);
} else if (import_c64.is_prefix_word(line)) {
line += import_c64.get_len();
line.skip_whitespace();
return Directive_Incbin(line, 2+skip, len); // 2 = load address skip size
} else if (import_text.is_prefix_word(line)) {
line += import_text.get_len();
line.skip_whitespace();
strref text_type = "petscii";
while (line[0]!='"') {
strref word = line.get_word_ws();
if (word.same_str("petscii") || word.same_str("petscii_shifted")) {
text_type = line.get_word_ws();
line += text_type.get_len();
line.skip_whitespace();
} else if (StringSymbol *pStr = GetString(line.get_word_ws())) {
line = pStr->get();
break;
}
}
CurrSection().AddText(line, text_type);
return STATUS_OK;
} else if (import_object.is_prefix_word(line)) {
line += import_object.get_len();
line.trim_whitespace();
return ReadObjectFile(line[0]=='"' ? line.between('"', '"') : line);
} else if (import_symbols.is_prefix_word(line)) {
line += import_symbols.get_len();
line.skip_whitespace();
return IncludeSymbols(line);
}
return STATUS_OK;
}
// org / pc: current address of code
StatusCode Asm::Directive_ORG(strref line)
{
int addr;
if (line[0]=='=')
++line;
else if (keyword_equ.is_prefix_word(line)) // optional '=' or equ
line.next_word_ws();
line.skip_whitespace();
struct EvalContext etx;
SetEvalCtxDefaults(etx);
StatusCode error = EvalExpression(line, etx, addr);
if (error != STATUS_OK)
return (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) ?
ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY : error;
// Section immediately followed by ORG reassigns that section to be fixed
if (CurrSection().size()==0 && !CurrSection().IsDummySection() && !CurrSection().address_assigned) {
if (CurrSection().type == ST_ZEROPAGE && addr >= 0x100)
return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE;
AssignAddressToSection(SectionId(), addr);
} else
SetSection(strref(), addr);
return STATUS_OK;
}
// load: address for target to load code at
StatusCode Asm::Directive_LOAD(strref line)
{
int addr;
if (line[0]=='=' || keyword_equ.is_prefix_word(line))
line.next_word_ws();
struct EvalContext etx;
SetEvalCtxDefaults(etx);
StatusCode error = EvalExpression(line, etx, addr);
if (error != STATUS_OK)
return (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) ?
ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY : error;
CurrSection().SetLoadAddress(addr);
return STATUS_OK;
}
// MERLIN version of AD_LINK, which is more like AD_INCOBJ + link to current section
StatusCode Asm::Directive_LNK(strref line)
{
strref file = line.between('"', '"');
if (!file) // MERLIN: No quotes around include filenames
file = line.split_range(filename_end_char_range);
int section_id = SectionId();
StatusCode error = ReadObjectFile(file, SectionId());
// restore current section
current_section = &allSections[section_id];
return error;
}
// this stores a string that when matched with a label will make that label external
StatusCode Asm::Directive_XDEF(strref line)
{
line.trim_whitespace();
if (strref xdef = line.split_range(syntax == SYNTAX_MERLIN ?
label_end_char_range_merlin : label_end_char_range)) {
char f = xdef.get_first();
char e = xdef.get_last();
if (f != '.' && f != '!' && f != '@' && e != '$') {
unsigned int hash = xdef.fnv1a();
unsigned int pos = FindLabelIndex(hash, xdefs.getKeys(), xdefs.count());
while (pos < xdefs.count() && xdefs.getKey(pos) == hash) {
if (xdefs.getValue(pos).same_str_case(xdef))
return STATUS_OK;
++pos;
}
xdefs.insert(pos, hash);
xdefs.getValues()[pos] = xdef;
}
}
return STATUS_OK;
}
StatusCode Asm::Directive_XREF(strref label)
{
// XREF already defined label => no action
if (!GetLabel(label)) {
Label *pLabelXREF = AddLabel(label.fnv1a());
pLabelXREF->label_name = label;
pLabelXREF->pool_name.clear();
pLabelXREF->section = -1; // address labels are based on section
pLabelXREF->value = 0;
pLabelXREF->evaluated = true;
pLabelXREF->pc_relative = true;
pLabelXREF->external = false;
pLabelXREF->constant = false;
pLabelXREF->reference = true;
}
return STATUS_OK;
}
// dc.b, dc.w, dc.t, dc.l, ADR, ADRL, bytes, words, long
StatusCode Asm::Directive_DC(strref line, int width, strref source_file)
{
struct EvalContext etx;
SetEvalCtxDefaults(etx);
line.trim_whitespace();
while (strref exp_dc = line.split_token_trim(',')) {
int value = 0;
if (!CurrSection().IsDummySection()) {
if (syntax == SYNTAX_MERLIN && exp_dc.get_first() == '#') // MERLIN allows for an immediate declaration on data
++exp_dc;
StatusCode error = EvalExpression(exp_dc, etx, value);
if (error > STATUS_XREF_DEPENDENT)
break;
else if (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], exp_dc, source_file,
width == 1 ? LateEval::LET_BYTE : (width == 2 ? LateEval::LET_ABS_REF : (width == 3 ? LateEval::LET_ABS_L_REF : LateEval::LET_ABS_4_REF)));
else if (error == STATUS_RELATIVE_SECTION) {
value = 0;
CurrSection().AddReloc(lastEvalValue, CurrSection().DataOffset(), lastEvalSection, width, lastEvalShift);
}
}
unsigned char bytes[4] = {
(unsigned char)value, (unsigned char)(value >> 8),
(unsigned char)(value >> 16), (unsigned char)(value >> 24) };
AddBin(bytes, width);
}
return STATUS_OK;
}
// ds/ds.b/ds.w/ds.t/ds.l
StatusCode Asm::Directive_DS(strref line)
{
int width = 1;
int value;
if (line.get_first() == '.' && strref::is_alphabetic(line[1])) {
switch (strref::tolower(line[1])) {
case 'b': break;
case 'w': width = 2; break;
case 't': width = 3; break;
case 'l': width = 4; break;
}
line += 2;
line.skip_whitespace();
}
struct EvalContext etx;
SetEvalCtxDefaults(etx);
strref size = line.split_token_trim(',');
if (STATUS_OK != EvalExpression(size, etx, value))
return ERROR_DS_MUST_EVALUATE_IMMEDIATELY;
int fill = 0;
if (line && STATUS_OK != EvalExpression(line, etx, fill))
return ERROR_DS_MUST_EVALUATE_IMMEDIATELY;
value *= width;
if (value > 0) {
for (int n = 0; n < value; n++)
AddByte(fill);
} else if (value) {
CurrSection().AddAddress(value);
if (CurrSection().type == ST_ZEROPAGE && CurrSection().address > 0x100)
return ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE;
}
return STATUS_OK;
}
StatusCode Asm::Directive_ALIGN(strref line)
{
if (line) {
if (line[0] == '=' || keyword_equ.is_prefix_word(line))
line.next_word_ws();
struct EvalContext etx;
SetEvalCtxDefaults(etx);
int value;
int status = EvalExpression(line, etx, value);
if (status == STATUS_NOT_READY || status == STATUS_XREF_DEPENDENT)
return ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY;
if (status == STATUS_OK && value>0) {
if (CurrSection().address_assigned) {
int add = (CurrSection().GetPC() + value - 1) % value;
for (int a = 0; a < add; a++)
AddByte(0);
} else
CurrSection().align_address = value;
}
}
return STATUS_OK;
}
StatusCode Asm::Directive_EVAL(strref line)
{
int value = 0;
strref description = line.find(':') >= 0 ? line.split_token_trim(':') : strref();
line.trim_whitespace();
struct EvalContext etx;
SetEvalCtxDefaults(etx);
strref lab1 = line;
lab1 = lab1.split_token_any_trim(syntax == SYNTAX_MERLIN ? label_end_char_range_merlin : label_end_char_range);
StringSymbol *pStr = line.same_str_case(lab1) ? GetString(lab1) : nullptr;
if (line && EvalExpression(line, etx, value) == STATUS_OK) {
if (description) {
if (pStr != nullptr) {
printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line), STRREF_ARG(pStr->get()), value);
} else {
printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line), value);
}
} else {
if (pStr != nullptr) {
printf("EVAL(%d): \"" STRREF_FMT "\" = \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line), STRREF_ARG(pStr->get()), value);
} else {
printf("EVAL(%d): \"" STRREF_FMT "\" = $%x\n",
contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line), value);
}
}
} else if (description) {
if (pStr != nullptr) {
printf("EVAL(%d): " STRREF_FMT ": \"" STRREF_FMT "\" = \"" STRREF_FMT "\"\n",
contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line), STRREF_ARG(pStr->get()));
} else {
printf("EVAL(%d): \"" STRREF_FMT ": " STRREF_FMT"\"\n",
contextStack.curr().source_file.count_lines(description) + 1, STRREF_ARG(description), STRREF_ARG(line));
}
} else {
if (pStr != nullptr) {
printf("EVAL(%d): \"" STRREF_FMT "\" = \"" STRREF_FMT "\"\n",
contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line), STRREF_ARG(pStr->get()));
} else {
printf("EVAL(%d): \"" STRREF_FMT "\"\n",
contextStack.curr().source_file.count_lines(line) + 1, STRREF_ARG(line));
}
}
return STATUS_OK;
}
StatusCode Asm::Directive_HEX(strref line)
{
unsigned char b = 0, v = 0;
while (line) { // indeterminable length, can't read hex to int
char c = *line.get();
++line;
if (c == ',') {
if (b) // probably an error but seems safe
AddByte(v);
b = 0;
line.skip_whitespace();
} else {
if (c >= '0' && c <= '9') v = (v << 4) + (c - '0');
else if (c >= 'A' && c <= 'Z') v = (v << 4) + (c - 'A' + 10);
else if (c >= 'a' && c <= 'z') v = (v << 4) + (c - 'a' + 10);
else break;
b ^= 1;
if (!b)
AddByte(v);
}
}
if (b)
return ERROR_HEX_WITH_ODD_NIBBLE_COUNT;
return STATUS_OK;
}
StatusCode Asm::Directive_ENUM_STRUCT(strref line, AssemblerDirective dir)
{
strref read_source = contextStack.curr().read_source;
if (read_source.is_substr(line.get())) {
strref struct_name = line.get_word();
line.skip(struct_name.get_len());
line.skip_whitespace();
read_source.skip(strl_t(line.get() - read_source.get()));
if (read_source[0] == '{') {
if (dir == AD_STRUCT)
BuildStruct(struct_name, read_source.scoped_block_skip());
else
BuildEnum(struct_name, read_source.scoped_block_skip());
} else
return dir == AD_STRUCT ? ERROR_STRUCT_CANT_BE_ASSEMBLED :
ERROR_ENUM_CANT_BE_ASSEMBLED;
contextStack.curr().next_source = read_source;
} else
return ERROR_STRUCT_CANT_BE_ASSEMBLED;
return STATUS_OK;
}
// Action based on assembler directive
StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref source_file)
{
StatusCode error = STATUS_OK;
if (!ConditionalAsm()) { // If conditionally blocked from assembling only check conditional directives
if (dir!=AD_IF && dir!=AD_IFDEF && dir!=AD_ELSE && dir!=AD_ELIF && dir!=AD_ELSE && dir!=AD_ENDIF)
return STATUS_OK;
}
struct EvalContext etx;
SetEvalCtxDefaults(etx);
switch (dir) {
case AD_CPU:
for (int c = 0; c < nCPUs; c++) {
if (line.same_str(aCPUs[c].name)) {
if (c != cpu)
SetCPU((CPUIndex)c);
return STATUS_OK;
}
}
return ERROR_CPU_NOT_SUPPORTED;
case AD_EXPORT:
line.trim_whitespace();
CurrSection().export_append = line.split_label();
break;
case AD_ORG:
return Directive_ORG(line);
case AD_LOAD:
return Directive_LOAD(line);
case AD_SECTION:
SetSection(line);
break;
case AD_LINK:
return LinkSections(line.get_trimmed_ws());
case AD_LNK:
return Directive_LNK(line);
case AD_INCOBJ: {
strref file = line.between('"', '"');
if (!file) // MERLIN: No quotes around include filenames
file = line.split_range(filename_end_char_range);
error = ReadObjectFile(file);
break;
}
case AD_XDEF:
return Directive_XDEF(line.get_trimmed_ws());
case AD_XREF:
Directive_XREF(line.split_range_trim(
syntax == SYNTAX_MERLIN ? label_end_char_range_merlin : label_end_char_range));
break;
case AD_ENT: // MERLIN version of xdef, makes most recently defined label external
if (Label *pLastLabel = GetLabel(last_label))
pLastLabel->external = true;
break;
case AD_EXT:
Directive_XREF(last_label);
break;
case AD_ALIGN: // align: align address to multiple of value, fill space with 0
return Directive_ALIGN(line);
case AD_EVAL: // eval: display the result of an expression in stdout
return Directive_EVAL(line);
case AD_BYTES: // bytes: add bytes by comma separated values/expressions
return Directive_DC(line, 1, source_file);
case AD_WORDS: // words: add words (16 bit values) by comma separated values
return Directive_DC(line, 2, source_file);
case AD_ADR: // ADR: MERLIN store 3 byte word
return Directive_DC(line, 3, source_file);
case AD_ADRL: // ADRL: MERLIN store 4 byte word
return Directive_DC(line, 4, source_file);
case AD_DC: {
int width = 1;
if (line[0]=='.') {
++line;
switch (strref::tolower(line.get_first())) {
case 'b': width = 1; break;
case 'w': width = 2; break;
case 't': width = 3; break;
case 'l': width = 4; break;
default:
return ERROR_BAD_TYPE_FOR_DECLARE_CONSTANT;
}
++line;
}
return Directive_DC(line, width, source_file);
}
case AD_HEX:
return Directive_HEX(line);
case AD_EJECT:
line.clear();
break;
case AD_USR:
line.clear();
break;
case AD_CYC:
list_flags |= cycle_counter_level ? ListLine::CYCLES_STOP : ListLine::CYCLES_START;
cycle_counter_level = !!cycle_counter_level;
break;
case AD_SAV:
line.trim_whitespace();
if (line.has_prefix(export_base_name))
line.skip(export_base_name.get_len());
if (line)
CurrSection().export_append = line.split_label();
AssignAddressToGroup();
break;
case AD_XC: // XC: MERLIN version of setting CPU
if (strref("off").is_prefix_word(line))
SetCPU(CPU_6502);
else if (strref("xc").is_prefix_word(line))
SetCPU(CPU_65816);
else if (cpu==CPU_65C02)
SetCPU(CPU_65816);
else
SetCPU(CPU_65C02);
break;
case AD_TEXT: { // text: add text within quotes
strref text_prefix;
while (line[0] != '"') {
strref word = line.get_word_ws();
if (word.same_str("petscii") || word.same_str("petscii_shifted")) {
text_prefix = line.get_word_ws();
line += text_prefix.get_len();
line.skip_whitespace();
} else if (StringSymbol *pStr = GetString(line.get_word_ws())) {
line = pStr->get();
break;
}
}
if (line[0] == '"')
line = line.between('"', '"');
CurrSection().AddText(line, text_prefix);
break;
}
case AD_MACRO:
error = Directive_Macro(line, source_file);
break;
case AD_INCLUDE: // assemble another file in place
return Directive_Include(line);
case AD_INCBIN:
return Directive_Incbin(line);
case AD_IMPORT:
return Directive_Import(line);
case AD_LABEL:
case AD_CONST: {
line.trim_whitespace();
strref label = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0);
if (line[0]=='=' || keyword_equ.is_prefix_word(line)) {
line.next_word_ws();
AssignLabel(label, line, dir==AD_CONST);
} else
error = ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT;
break;
}
case AD_STRING:
return Directive_String(line);
case AD_UNDEF:
return Directive_Undef(line);
case AD_INCSYM:
return IncludeSymbols(line);
case AD_LABPOOL: {
strref name = line.split_range_trim(word_char_range, line[0]=='.' ? 1 : 0);
AddLabelPool(name, line);
break;
}
case AD_IF:
if (NewConditional()) { // Start new conditional block
CheckConditionalDepth(); // Check if nesting
bool conditional_result;
error = EvalStatement(line, conditional_result);
if (conditional_result)
ConsumeConditional();
else
SetConditional();
}
break;
case AD_IFDEF:
if (NewConditional()) { // Start new conditional block
CheckConditionalDepth(); // Check if nesting
bool conditional_result;
error = EvalStatement(line, conditional_result);
strref name = line.get_trimmed_ws();
if (GetLabel(name) != nullptr || GetString(name) != nullptr)
ConsumeConditional();
else
SetConditional();
}
break;
case AD_ELSE:
if (ConditionalAsm()) {
if (ConditionalConsumed())
ConditionalElse();
else
error = ERROR_ELSE_WITHOUT_IF;
} else if (ConditionalAvail())
EnableConditional(true);
break;
case AD_ELIF:
if (ConditionalAsm()) {
if (ConditionalConsumed())
ConditionalElse();
else
error = ERROR_ELSE_WITHOUT_IF;
} else if (ConditionalAvail()) {
bool conditional_result;
error = EvalStatement(line, conditional_result);
EnableConditional(conditional_result);
}
break;
case AD_ENDIF:
if (ConditionalAsm()) {
if (ConditionalConsumed())
CloseConditional();
else
error = ERROR_ENDIF_WITHOUT_CONDITION;
} else {
conditional_nesting[conditional_depth]--;
if (ConditionalAsm())
CloseConditional();
}
break;
case AD_ENUM:
case AD_STRUCT:
return Directive_ENUM_STRUCT(line, dir);
case AD_REPT:
return Directive_Rept(line, source_file);
case AD_INCDIR:
AddIncludeFolder(line.between('"', '"'));
break;
case AD_A16: // A16: Set 16 bit accumulator mode
accumulator_16bit = true;
break;
case AD_A8: // A8: Set 8 bit accumulator mode
accumulator_16bit = false;
break;
case AD_XY16: // A16: Set 16 bit accumulator mode
index_reg_16bit = true;
break;
case AD_XY8: // A8: Set 8 bit accumulator mode
index_reg_16bit = false;
break;
case AD_MX:
if (line) {
line.trim_whitespace();
int value = 0;
error = EvalExpression(line, etx, value);
index_reg_16bit = !(value&1);
accumulator_16bit = !(value&2);
}
break;
case AD_ABORT:
line.trim_whitespace();
if (line)
printf("Assembler aborted: " STRREF_FMT "\n", STRREF_ARG(line));
return ERROR_ABORTED;
case AD_LST:
line.clear();
break;
case AD_DUMMY:
line.trim_whitespace();
if (line) {
int reorg;
if (STATUS_OK == EvalExpression(line, etx, reorg)) {
DummySection(reorg);
break;
}
}
DummySection();
break;
case AD_DUMMY_END:
while (CurrSection().IsDummySection()) {
EndSection();
if (SectionId()==0)
break;
}
break;
case AD_DS:
return Directive_DS(line);
}
return error;
}
// Make an educated guess at the intended address mode from an opcode argument
StatusCode Asm::GetAddressMode(strref line, bool flipXY, unsigned int validModes, AddrMode &addrMode, int &len, strref &expression)
{
bool force_zp = false;
bool force_24 = false;
bool force_abs = false;
bool need_more = true;
strref arg, deco;
len = 0;
while (need_more) {
need_more = false;
unsigned char c = line.get_first();
if (!c)
addrMode = AMB_NON;
else if (!force_abs && (c == '[' || (c == '(' &&
(validModes&(AMM_REL | AMM_REL_X | AMM_ZP_REL | AMM_ZP_REL_X | AMM_ZP_Y_REL))))) {
deco = line.scoped_block_skip();
line.skip_whitespace();
expression = deco.split_token_trim(',');
addrMode = c == '[' ? (force_zp ? AMB_ZP_REL_L : AMB_REL_L) : (force_zp ? AMB_ZP_REL : AMB_REL);
if (strref::tolower(deco[0]) == 'x')
addrMode = c == '[' ? AMB_ILL : AMB_ZP_REL_X;
else if (line[0] == ',') {
++line;
line.skip_whitespace();
if (strref::tolower(line[0]) == 'y') {
if (strref::tolower(deco[0]) == 's')
addrMode = AMB_STK_REL_Y;
else
addrMode = c == '[' ? AMB_ZP_REL_Y_L : AMB_ZP_Y_REL;
++line;
}
}
} else if (c == '#') {
++line;
addrMode = AMB_IMM;
expression = line;
} else if (line) {
if (line[0]=='.' && strref::is_ws(line[2])) {
switch (strref::tolower(line[1])) {
case 'z': force_zp = true; line += 3; need_more = true; len = 1; break;
case 'b': line += 3; need_more = true; len = 1; break;
case 'w': line += 3; need_more = true; len = 2; break;
case 'l': force_24 = true; line += 3; need_more = true; len = 3; break;
case 'a': force_abs = true; line += 3; need_more = true; break;
}
}
if (!need_more) {
if (strref("A").is_prefix_word(line)) {
addrMode = AMB_ACC;
} else { // absolute (zp, offs x, offs y)
addrMode = force_24 ? AMB_ABS_L : (force_zp ? AMB_ZP : AMB_ABS);
expression = line.split_token_trim(',');
if (line && (line[0]=='s' || line[0]=='S'))
addrMode = AMB_STK;
else {
bool relX = line && (line[0]=='x' || line[0]=='X');
bool relY = line && (line[0]=='y' || line[0]=='Y');
if ((flipXY && relY) || (!flipXY && relX))
addrMode = force_24 ? AMB_ABS_L_X : (force_zp ? AMB_ZP_X : AMB_ABS_X);
else if ((flipXY && relX) || (!flipXY && relY)) {
if (force_zp)
return ERROR_INSTRUCTION_NOT_ZP;
addrMode = AMB_ABS_Y;
}
}
}
}
}
}
return STATUS_OK;
}
// Push an opcode to the output buffer in the current section
StatusCode Asm::AddOpcode(strref line, int index, strref source_file)
{
StatusCode error = STATUS_OK;
strref expression;
// allowed modes
unsigned int validModes = opcode_table[index].modes;
// instruction parameter length override
int op_param = 0;
// Get the addressing mode and the expression it refers to
AddrMode addrMode;
switch (validModes) {
case AMC_BBR:
addrMode = AMB_ZP_ABS;
expression = line.split_token_trim(',');
if (!expression || !line)
return ERROR_INVALID_ADDRESSING_MODE;
break;
case AMM_BRA:
addrMode = AMB_ABS;
expression = line;
break;
case AMM_ACC:
case (AMM_ACC|AMM_NON):
case AMM_NON:
addrMode = AMB_NON;
break;
case AMM_BLK_MOV:
addrMode = AMB_BLK_MOV;
expression = line.before_or_full(',');
break;
default:
error = GetAddressMode(line, !!(validModes & AMM_FLIPXY), validModes, addrMode, op_param, expression);
break;
}
int value = 0;
int target_section = -1;
int target_section_offs = -1;
char target_section_shift = 0;
bool evalLater = false;
if (expression) {
struct EvalContext etx;
SetEvalCtxDefaults(etx);
if (validModes & (AMM_BRANCH | AMM_BRANCH_L))
etx.relative_section = SectionId();
error = EvalExpression(expression, etx, value);
if (error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) {
evalLater = true;
error = STATUS_OK;
} else if (error == STATUS_RELATIVE_SECTION) {
target_section = lastEvalSection;
target_section_offs = lastEvalValue;
target_section_shift = lastEvalShift;
} else if (error != STATUS_OK)
return error;
}
// check if address is in zero page range and should use a ZP mode instead of absolute
if (!evalLater && value>=0 && value<0x100 && (error != STATUS_RELATIVE_SECTION ||
(target_section>=0 && allSections[target_section].type==ST_ZEROPAGE))) {
switch (addrMode) {
case AMB_ABS:
if (validModes & AMM_ZP)
addrMode = AMB_ZP;
else if (validModes & AMM_ABS_L)
addrMode = AMB_ABS_L;
break;
case AMB_ABS_X:
if (validModes & AMM_ZP_X)
addrMode = AMB_ZP_X;
else if (validModes & AMM_ABS_L_X)
addrMode = AMB_ABS_L_X;
break;
default:
break;
}
}
// Check if an explicit 24 bit address
if (expression[0] == '$' && (expression + 1).len_hex()>4) {
if (addrMode == AMB_ABS && (validModes & AMM_ABS_L))
addrMode = AMB_ABS_L;
else if (addrMode == AMB_ABS_X && (validModes & AMM_ABS_L_X))
addrMode = AMB_ABS_L_X;
}
if (!(validModes & (1 << addrMode))) {
if (addrMode==AMB_ZP_REL_X && (validModes & AMM_REL_X))
addrMode = AMB_REL_X;
else if (addrMode==AMB_REL && (validModes & AMM_ZP_REL))
addrMode = AMB_ZP_REL;
else if (addrMode==AMB_ABS && (validModes & AMM_ABS_L))
addrMode = AMB_ABS_L;
else if (addrMode==AMB_ABS_X && (validModes & AMM_ABS_L_X))
addrMode = AMB_ABS_L_X;
else if (addrMode==AMB_REL_L && (validModes & AMM_ZP_REL_L))
addrMode = AMB_ZP_REL_L;
else if (syntax == SYNTAX_MERLIN && addrMode==AMB_IMM && validModes==AMM_ABS)
addrMode = AMB_ABS; // Merlin seems to allow this
else if (syntax == SYNTAX_MERLIN && addrMode==AMB_ABS && validModes==AMM_ZP_REL)
addrMode = AMB_ZP_REL; // Merlin seems to allow this
else
return ERROR_INVALID_ADDRESSING_MODE;
}
// Add the instruction and argument to the code
if (error == STATUS_OK || error == STATUS_RELATIVE_SECTION) {
unsigned char opcode = opcode_table[index].aCodes[addrMode];
StatusCode cap_status = CheckOutputCapacity(4);
if (cap_status != STATUS_OK)
return error;
AddByte(opcode);
CODE_ARG codeArg = CA_NONE;
if (validModes & AMM_BRANCH_L)
codeArg = CA_BRANCH_16;
else if (validModes & AMM_BRANCH)
codeArg = CA_BRANCH;
else {
switch (addrMode) {
case AMB_ZP_REL_X: // 0 ($12:x)
case AMB_ZP: // 1 $12
case AMB_ZP_Y_REL: // 4 ($12),y
case AMB_ZP_X: // 5 $12,x
case AMB_ZP_REL: // b ($12)
case AMB_ZP_REL_L: // e [$02]
case AMB_ZP_REL_Y_L: // f [$00],y
case AMB_STK: // 12 $12,s
case AMB_STK_REL_Y: // 13 ($12,s),y
codeArg = CA_ONE_BYTE;
break;
case AMB_ABS_Y: // 6 $1234,y
case AMB_ABS_X: // 7 $1234,x
case AMB_ABS: // 3 $1234
case AMB_REL: // 8 ($1234)
case AMB_REL_X: // c ($1234,x)
case AMB_REL_L: // 14 [$1234]
codeArg = CA_TWO_BYTES;
break;
case AMB_ABS_L: // 10 $e00001
case AMB_ABS_L_X: // 11 $123456,x
codeArg = CA_THREE_BYTES;
break;
case AMB_ZP_ABS: // d $12, label
codeArg = CA_BYTE_BRANCH;
break;
case AMB_BLK_MOV: // 15 $12,$34
codeArg = CA_TWO_ARG_BYTES;
break;
case AMB_IMM: // 2 #$12
if (op_param && (validModes&(AMM_IMM_DBL_A | AMM_IMM_DBL_XY)))
codeArg = op_param == 2 ? CA_TWO_BYTES : CA_ONE_BYTE;
else if ((validModes&(AMM_IMM_DBL_A | AMM_IMM_DBL_XY)) &&
expression[0]=='$' && (expression+1).len_hex()==4)
codeArg = CA_TWO_BYTES;
else if (((validModes&AMM_IMM_DBL_A) && accumulator_16bit) ||
((validModes&AMM_IMM_DBL_XY) && index_reg_16bit))
codeArg = CA_TWO_BYTES;
else
codeArg = CA_ONE_BYTE;
break;
case AMB_ACC: // 9 A
case AMB_NON: // a
default:
break;
}
}
switch (codeArg) {
case CA_ONE_BYTE:
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE);
else if (error == STATUS_RELATIVE_SECTION)
CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 1, target_section_shift);
AddByte(value);
break;
case CA_TWO_BYTES:
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_ABS_REF);
else if (error == STATUS_RELATIVE_SECTION) {
CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 2, target_section_shift);
value = 0;
}
AddWord(value);
break;
case CA_THREE_BYTES:
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_ABS_L_REF);
else if (error == STATUS_RELATIVE_SECTION) {
CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 3, target_section_shift);
value = 0;
}
AddTriple(value);
break;
case CA_TWO_ARG_BYTES: {
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE);
else if (error == STATUS_RELATIVE_SECTION) {
CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 1, target_section_shift);
}
AddByte(value);
struct EvalContext etx;
SetEvalCtxDefaults(etx);
etx.pc = CurrSection().GetPC()-2;
line.split_token_trim(',');
error = EvalExpression(line, etx, value);
if (error==STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], line, source_file, LateEval::LET_BYTE);
AddByte(value);
break;
}
case CA_BRANCH:
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BRANCH);
else if (((int)value - (int)CurrSection().GetPC()-1) < -128 || ((int)value - (int)CurrSection().GetPC()-1) > 127)
error = ERROR_BRANCH_OUT_OF_RANGE;
AddByte(evalLater ? 0 : (unsigned char)((int)value - (int)CurrSection().GetPC()) - 1);
break;
case CA_BRANCH_16:
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BRANCH_16);
AddWord(evalLater ? 0 : (value-(CurrSection().GetPC()+2)));
break;
case CA_BYTE_BRANCH: {
if (evalLater)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE);
else if (error == STATUS_RELATIVE_SECTION)
CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, 1, target_section_shift);
AddByte(value);
struct EvalContext etx;
SetEvalCtxDefaults(etx);
etx.pc = CurrSection().GetPC()-2;
etx.relative_section = SectionId();
error = EvalExpression(line, etx, value);
if (error==STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT)
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], line, source_file, LateEval::LET_BRANCH);
else if (((int)value - (int)CurrSection().GetPC() - 1) < -128 || ((int)value - (int)CurrSection().GetPC() - 1) > 127)
error = ERROR_BRANCH_OUT_OF_RANGE;
AddByte((error == STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT) ?
0 : (unsigned char)((int)value - (int)CurrSection().GetPC()) - 1);
break;
}
case CA_NONE:
break;
}
}
return error;
}
// Build a line of code
void Asm::PrintError(strref line, StatusCode error)
{
strown<512> errorText;
if (contextStack.has_work()) {
errorText.sprintf("Error " STRREF_FMT "(%d): ", STRREF_ARG(contextStack.curr().source_name),
contextStack.curr().source_file.count_lines(line)+1);
} else
errorText.append("Error: ");
errorText.append(aStatusStrings[error]);
errorText.append(" \"");
errorText.append(line.get_trimmed_ws());
errorText.append("\"\n");
errorText.c_str();
fwrite(errorText.get(), errorText.get_len(), 1, stderr);
error_encountered = true;
}
// Build a line of code
StatusCode Asm::BuildLine(strref line)
{
StatusCode error = STATUS_OK;
// MERLIN: First char of line is * means comment
if (syntax==SYNTAX_MERLIN && line[0]=='*')
return STATUS_OK;
// remember for listing
int start_section = SectionId();
int start_address = CurrSection().address;
strref code_line = line;
list_flags = 0;
while (line && error == STATUS_OK) {
strref line_start = line;
char char0 = line[0]; // first char including white space
line.skip_whitespace(); // skip to first character
line = line.before_or_full(';'); // clip any line comments
line = line.before_or_full(c_comment);
line.clip_trailing_whitespace();
if (line[0]==':' && syntax!=SYNTAX_MERLIN) // Kick Assembler macro prefix (incompatible with merlin)
++line;
strref line_nocom = line;
strref operation = line.split_range(syntax==SYNTAX_MERLIN ? label_end_char_range_merlin : label_end_char_range);
char char1 = operation[0]; // first char of first word
char charE = operation.get_last(); // end char of first word
line.trim_whitespace();
bool force_label = charE==':' || charE=='$';
if (!force_label && syntax==SYNTAX_MERLIN && (line || operation)) // MERLIN fixes and PoP does some naughty stuff like 'and = 0'
force_label = !strref::is_ws(char0) || char1==']' || charE=='?';
else if (!force_label && syntax!=SYNTAX_MERLIN && line[0]==':')
force_label = true;
if (!operation && !force_label) {
if (ConditionalAsm()) {
// scope open / close
switch (line[0]) {
case '{':
error = EnterScope();
list_flags |= ListLine::CYCLES_START;
if (error == STATUS_OK) {
++line;
line.skip_whitespace();
}
break;
case '}':
// check for late eval of anything with an end scope
error = ExitScope();
list_flags |= ListLine::CYCLES_STOP;
if (error == STATUS_OK) {
++line;
line.skip_whitespace();
}
break;
case '*':
// if first char is '*' this seems like a line comment on some assemblers
line.clear();
break;
case 127:
++line; // bad character?
break;
}
} else
line.clear();
} else {
// ignore leading period for instructions and directives - not for labels
strref label = operation;
if ((syntax != SYNTAX_MERLIN && operation[0]==':') || operation[0]=='.')
++operation;
operation = operation.before_or_full('.');
int op_idx = LookupOpCodeIndex(operation.fnv1a_lower(), aInstructions, num_instructions);
if (op_idx >= 0 && !force_label && (aInstructions[op_idx].type==OT_DIRECTIVE || line[0]!='=')) {
if (line_nocom.is_substr(operation.get())) {
line = line_nocom + strl_t(operation.get()+operation.get_len()-line_nocom.get());
line.skip_whitespace();
}
if (aInstructions[op_idx].type==OT_DIRECTIVE) {
error = ApplyDirective((AssemblerDirective)aInstructions[op_idx].index, line, contextStack.curr().source_file);
list_flags |= ListLine::KEYWORD;
} else if (ConditionalAsm() && aInstructions[op_idx].type == OT_MNEMONIC) {
error = AddOpcode(line, aInstructions[op_idx].index, contextStack.curr().source_file);
list_flags |= ListLine::MNEMONIC;
}
line.clear();
} else if (!ConditionalAsm()) {
line.clear(); // do nothing if conditional nesting so clear the current line
} else if (line.get_first()=='=') {
++line;
error = AssignLabel(label, line);
line.clear();
list_flags |= ListLine::KEYWORD;
}
else if (keyword_equ.is_prefix_word(line)) {
line += keyword_equ.get_len();
line.skip_whitespace();
error = AssignLabel(label, line);
line.clear();
list_flags |= ListLine::KEYWORD;
}
else {
unsigned int nameHash = label.fnv1a();
unsigned int macro = FindLabelIndex(nameHash, macros.getKeys(), macros.count());
bool gotConstruct = false;
while (macro < macros.count() && nameHash==macros.getKey(macro)) {
if (macros.getValue(macro).name.same_str_case(label)) {
error = BuildMacro(macros.getValue(macro), line);
gotConstruct = true;
line.clear(); // don't process codes from here
break;
}
macro++;
}
if (!gotConstruct) {
unsigned int labPool = FindLabelIndex(nameHash, labelPools.getKeys(), labelPools.count());
gotConstruct = false;
while (labPool < labelPools.count() && nameHash==labelPools.getKey(labPool)) {
if (labelPools.getValue(labPool).pool_name.same_str_case(label)) {
error = AssignPoolLabel(labelPools.getValue(labPool), line);
gotConstruct = true;
line.clear(); // don't process codes from here
break;
}
labPool++;
}
if (!gotConstruct) {
if (StringSymbol *pStr = GetString(label)) {
StringAction(pStr, line);
line.clear();
} else if (syntax==SYNTAX_MERLIN && strref::is_ws(line_start[0])) {
error = ERROR_UNDEFINED_CODE;
} else if (label[0]=='$' || strref::is_number(label[0]))
line.clear();
else {
if (label.get_last()==':')
label.clip(1);
error = AddressLabel(label);
line = line_start + int(label.get() + label.get_len() -line_start.get());
if (line[0]==':' || line[0]=='?')
++line; // there may be codes after the label
list_flags |= ListLine::KEYWORD;
}
}
}
}
}
// Check for unterminated condition in source
if (!contextStack.curr().next_source &&
(!ConditionalAsm() || ConditionalConsumed() || !conditional_depth)) {
if (syntax == SYNTAX_MERLIN) { // this isn't a listed feature,
conditional_nesting[0] = 0; // some files just seem to get away without closing
conditional_consumed[0] = 0;
conditional_depth = 0;
} else {
PrintError(conditional_source[conditional_depth], ERROR_UNTERMINATED_CONDITION);
return ERROR_UNTERMINATED_CONDITION;
}
}
if (line.same_str_case(line_start))
error = ERROR_UNABLE_TO_PROCESS;
else if (CurrSection().type == ST_ZEROPAGE && CurrSection().address > 0x100)
error = ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE;
if (error > STATUS_XREF_DEPENDENT)
PrintError(line_start, error);
// dealt with error, continue with next instruction unless too broken
if (error < ERROR_STOP_PROCESSING_ON_HIGHER)
error = STATUS_OK;
}
// update listing
if (error == STATUS_OK && list_assembly) {
if (SectionId() == start_section) {
Section &curr = CurrSection();
if (!curr.pListing)
curr.pListing = new Listing;
if (curr.pListing && curr.pListing->size() == curr.pListing->capacity())
curr.pListing->reserve(curr.pListing->size() + 256);
if (((list_flags&(ListLine::KEYWORD|ListLine::CYCLES_START|ListLine::CYCLES_STOP)) ||
(curr.address != start_address && curr.size())) && !curr.IsDummySection()) {
struct ListLine lst;
lst.address = start_address - curr.start_address;
lst.size = curr.address - start_address;
lst.code = contextStack.curr().source_file;
lst.source_name = contextStack.curr().source_name;
lst.line_offs = int(code_line.get() - lst.code.get());
lst.flags = list_flags;
curr.pListing->push_back(lst);
}
}
}
return error;
}
// Build a segment of code (file or macro)
StatusCode Asm::BuildSegment()
{
StatusCode error = STATUS_OK;
while (contextStack.curr().read_source) {
contextStack.curr().next_source = contextStack.curr().read_source;
error = BuildLine(contextStack.curr().next_source.line());
if (error > ERROR_STOP_PROCESSING_ON_HIGHER)
break;
contextStack.curr().read_source = contextStack.curr().next_source;
}
if (error == STATUS_OK) {
error = CheckLateEval(strref(), CurrSection().GetPC());
}
return error;
}
// Produce the assembler listing
#define MAX_DEPTH_CYCLE_COUNTER 64
struct cycleCnt {
int base;
short plus, a16, x16, dp;
void clr() { base = 0; plus = a16 = x16 = dp = 0; }
void add(unsigned char c) {
if (c != 0xff) {
base += (c >> 1) & 7;
plus += c & 1;
if (c & 0xf0) {
int i = c >> 4;
if (i <= 8) {
a16 += timing_65816_plus[i][0];
x16 += timing_65816_plus[i][1];
dp += timing_65816_plus[i][2];
}
}
}
}
int plus_acc() {
return plus + a16 + x16 + dp;
}
void combine(const struct cycleCnt &o) {
base += o.base; plus += o.plus; a16 += o.a16; x16 += o.x16; dp += o.dp;
}
bool complex() const { return a16 != 0 || x16 != 0 || dp != 0; }
static int get_base(unsigned char c) {
return (c & 0xf) >> 1;
}
static int sum_plus(unsigned char c) {
if (c == 0xff)
return 0;
int i = c >> 4;
if (i)
return i <= 8 ? (timing_65816_plus[i][0] + timing_65816_plus[i][1] + timing_65816_plus[i][2]) : 0;
return c & 1;
}
};
bool Asm::List(strref filename)
{
FILE *f = stdout;
bool opened = false;
if (filename) {
f = fopen(strown<512>(filename).c_str(), "w");
if (!f)
return false;
opened = true;
}
// ensure that the greatest instruction set referenced is used for listing
if (list_cpu != cpu)
SetCPU(list_cpu);
// Build a disassembly lookup table
unsigned char mnemonic[256];
unsigned char addrmode[256];
memset(mnemonic, 255, sizeof(mnemonic));
memset(addrmode, 255, sizeof(addrmode));
for (int i = 0; i < opcode_count; i++) {
for (int j = AMB_COUNT-1; j >= 0; j--) {
if (opcode_table[i].modes & (1 << j)) {
unsigned char op = opcode_table[i].aCodes[j];
if (addrmode[op]==255) {
mnemonic[op] = i;
addrmode[op] = j;
}
}
}
}
struct cycleCnt cycles[MAX_DEPTH_CYCLE_COUNTER];
short cycles_depth = 0;
memset(cycles, 0, sizeof(cycles));
strref prev_src;
int prev_offs = 0;
for (std::vector<Section>::iterator si = allSections.begin(); si != allSections.end(); ++si) {
if (si->type == ST_REMOVED)
continue;
if (si->address_assigned)
fprintf(f, "Section " STRREF_FMT " (%d, %s): $%04x-$%04x\n", STRREF_ARG(si->name),
(int)(&*si - &allSections[0]), si->type>=0 && si->type<num_section_type_str ?
str_section_type[si->type] : "???", si->start_address, si->address);
else
fprintf(f, "Section " STRREF_FMT " (%d, %s) (relocatable)\n", STRREF_ARG(si->name),
(int)(&*si - &allSections[0]), str_section_type[si->type]);
if (!si->pListing)
continue;
for (Listing::iterator li = si->pListing->begin(); li != si->pListing->end(); ++li) {
strown<256> out;
const struct ListLine &lst = *li;
if (prev_src.fnv1a() != lst.source_name.fnv1a() || lst.line_offs < prev_offs) {
fprintf(f, STRREF_FMT "(%d):\n", STRREF_ARG(lst.source_name), lst.code.count_lines(lst.line_offs));
prev_src = lst.source_name;
} else {
strref prvline = lst.code.get_substr(prev_offs, lst.line_offs - prev_offs);
prvline.next_line();
if (prvline.count_lines() < 5) {
while (strref space_line = prvline.line()) {
space_line.clip_trailing_whitespace();
strown<128> line_fix(space_line);
for (strl_t pos = 0; pos < line_fix.len(); ++pos) {
if (line_fix[pos] == '\t')
line_fix.exchange(pos, 1, pos & 1 ? strref(" ") : strref(" "));
}
out.append_to(' ', aCPUs[cpu].timing ? 40 : 33);
out.append(line_fix.get_strref());
fprintf(f, STRREF_FMT "\n", STRREF_ARG(out));
out.clear();
}
} else {
fprintf(f, STRREF_FMT "(%d):\n", STRREF_ARG(lst.source_name), lst.code.count_lines(lst.line_offs));
}
}
if (lst.size)
out.sprintf_append("$%04x ", lst.address + si->start_address);
int s = lst.wasMnemonic() ? (lst.size < 4 ? lst.size : 4) : (lst.size < 8 ? lst.size : 8);
if (si->output && si->output_capacity >= size_t(lst.address + s)) {
for (int b = 0; b < s; ++b)
out.sprintf_append("%02x ", si->output[lst.address + b]);
}
if (lst.startClock() && cycles_depth<MAX_DEPTH_CYCLE_COUNTER) {
cycles_depth++; cycles[cycles_depth].clr();
out.append_to(' ', 6); out.sprintf_append("c>%d", cycles_depth);
}
if (lst.stopClock()) {
out.append_to(' ', 6);
if (cycles[cycles_depth].complex())
out.sprintf_append("c<%d = %d + m%d + i%d + d%d", cycles_depth,
cycles[cycles_depth].base, cycles[cycles_depth].a16,
cycles[cycles_depth].x16, cycles[cycles_depth].dp);
else
out.sprintf_append("c<%d = %d + %d", cycles_depth,
cycles[cycles_depth].base, cycles[cycles_depth].plus_acc());
if (cycles_depth) {
cycles_depth--;
cycles[cycles_depth].combine(cycles[cycles_depth + 1]);
}
}
if (lst.size && lst.wasMnemonic()) {
out.append_to(' ', 18);
unsigned char *buf = si->output + lst.address;
unsigned char op = mnemonic[*buf];
unsigned char am = addrmode[*buf];
if (op != 255 && am != 255 && am<(sizeof(aAddrModeFmt)/sizeof(aAddrModeFmt[0]))) {
const char *fmt = aAddrModeFmt[am];
if (opcode_table[op].modes & AMM_FLIPXY) {
if (am == AMB_ZP_X) fmt = "%s $%02x,y";
else if (am == AMB_ABS_X) fmt = "%s $%04x,y";
}
if (opcode_table[op].modes & AMM_ZP_ABS)
out.sprintf_append(fmt, opcode_table[op].instr, buf[1], (char)buf[2] + lst.address + si->start_address + 3);
else if (opcode_table[op].modes & AMM_BRANCH)
out.sprintf_append(fmt, opcode_table[op].instr, (char)buf[1] + lst.address + si->start_address + 2);
else if (opcode_table[op].modes & AMM_BRANCH_L)
out.sprintf_append(fmt, opcode_table[op].instr, (short)(buf[1] | (buf[2] << 8)) + lst.address + si->start_address + 3);
else if (am == AMB_NON || am == AMB_ACC)
out.sprintf_append(fmt, opcode_table[op].instr);
else if (am == AMB_ABS || am == AMB_ABS_X || am == AMB_ABS_Y || am == AMB_REL || am == AMB_REL_X || am == AMB_REL_L)
out.sprintf_append(fmt, opcode_table[op].instr, buf[1] | (buf[2] << 8));
else if (am == AMB_ABS_L || am == AMB_ABS_L_X)
out.sprintf_append(fmt, opcode_table[op].instr, buf[1] | (buf[2] << 8) | (buf[3] << 16));
else if (am == AMB_BLK_MOV)
out.sprintf_append(fmt, opcode_table[op].instr, buf[1], buf[2]);
else if (am == AMB_IMM && lst.size==3)
out.sprintf_append("%s #$%04x", opcode_table[op].instr, buf[1] | (buf[2]<<8));
else
out.sprintf_append(fmt, opcode_table[op].instr, buf[1]);
if (aCPUs[cpu].timing) {
cycles[cycles_depth].add(aCPUs[cpu].timing[*buf]);
out.append_to(' ', 33);
if (cycleCnt::sum_plus(aCPUs[cpu].timing[*buf])==1)
out.sprintf_append("%d+", cycleCnt::get_base(aCPUs[cpu].timing[*buf]));
else if (cycleCnt::sum_plus(aCPUs[cpu].timing[*buf]))
out.sprintf_append("%d+%d", cycleCnt::get_base(aCPUs[cpu].timing[*buf]), cycleCnt::sum_plus(aCPUs[cpu].timing[*buf]));
else
out.sprintf_append("%d", cycleCnt::get_base(aCPUs[cpu].timing[*buf]));
}
}
}
out.append_to(' ', aCPUs[cpu].timing ? 40 : 33);
strref line = lst.code.get_skipped(lst.line_offs).get_line();
line.clip_trailing_whitespace();
strown<128> line_fix(line);
for (strl_t pos = 0; pos < line_fix.len(); ++pos) {
if (line_fix[pos] == '\t')
line_fix.exchange(pos, 1, pos & 1 ? strref(" ") : strref(" "));
}
out.append(line_fix.get_strref());
fprintf(f, STRREF_FMT "\n", STRREF_ARG(out));
prev_offs = lst.line_offs;
}
}
if (opened)
fclose(f);
return true;
}
// Create a listing of all valid instructions and addressing modes
bool Asm::AllOpcodes(strref filename)
{
FILE *f = stdout;
bool opened = false;
if (filename) {
f = fopen(strown<512>(filename).c_str(), "w");
if (!f)
return false;
opened = true;
}
for (int i = 0; i < opcode_count; i++) {
unsigned int modes = opcode_table[i].modes;
for (int a = 0; a < AMB_COUNT; a++) {
if (modes & (1 << a)) {
const char *fmt = aAddrModeFmt[a];
fputs("\t", f);
if (opcode_table[i].modes & AMM_BRANCH)
fprintf(f, "%s *+%d", opcode_table[i].instr, 5);
else if (a==AMB_ZP_ABS)
fprintf(f, "%s $%02x,*+%d", opcode_table[i].instr, 0x23, 13);
else {
if (opcode_table[i].modes & AMM_FLIPXY) {
if (a == AMB_ZP_X) fmt = "%s $%02x,y";
else if (a == AMB_ABS_X) fmt = "%s $%04x,y";
}
if (a == AMB_ABS_L || a == AMB_ABS_L_X) {
if ((modes & ~(AMM_ABS_L|AMM_ABS_L_X)))
fprintf(f, a==AMB_ABS_L ? "%s.l $%06x" : "%s.l $%06x,x", opcode_table[i].instr, 0x222120);
else
fprintf(f, fmt, opcode_table[i].instr, 0x222120);
} else if (a == AMB_ABS || a == AMB_ABS_X || a == AMB_ABS_Y || a == AMB_REL || a == AMB_REL_X || a == AMB_REL_L)
fprintf(f, fmt, opcode_table[i].instr, 0x2120);
else if (a == AMB_IMM && (modes&(AMM_IMM_DBL_A|AMM_IMM_DBL_XY))) {
fprintf(f, "%s.b #$%02x\n", opcode_table[i].instr, 0x21);
fprintf(f, "\t%s.w #$%04x", opcode_table[i].instr, 0x2322);
} else
fprintf(f, fmt, opcode_table[i].instr, 0x21, 0x20, 0x1f);
}
fputs("\n", f);
}
}
}
if (opened)
fclose(f);
return true;
}
// create an instruction table (mnemonic hash lookup + directives)
void Asm::Assemble(strref source, strref filename, bool obj_target)
{
SetCPU(cpu);
StatusCode error = STATUS_OK;
PushContext(filename, source, source);
scope_address[scope_depth] = CurrSection().GetPC();
while (contextStack.has_work() && error == STATUS_OK) {
error = BuildSegment();
if (contextStack.curr().complete())
error = PopContext();
else
contextStack.curr().restart();
}
if (error == STATUS_OK) {
if (!obj_target)
LinkZP();
error = CheckLateEval();
if (error > STATUS_XREF_DEPENDENT) {
strown<512> errorText;
errorText.copy("Error: ");
errorText.append(aStatusStrings[error]);
fwrite(errorText.get(), errorText.get_len(), 1, stderr);
} else
CheckLateEval(strref(), -1, true); // output any missing xref's
if (!obj_target) {
for (std::vector<LateEval>::iterator i = lateEval.begin(); i!=lateEval.end(); ++i) {
strown<512> errorText;
int line = i->source_file.count_lines(i->expression);
errorText.sprintf("Error (%d): ", line+1);
errorText.append("Failed to evaluate label \"");
errorText.append(i->expression);
if (line>=0) {
errorText.append("\" : \"");
errorText.append(i->source_file.get_line(line).get_trimmed_ws());
}
errorText.append("\"\n");
fwrite(errorText.get(), errorText.get_len(), 1, stderr);
}
}
} else
PrintError(&contextStack.curr() ?
contextStack.curr().read_source.get_line() : strref(), error);
}
//
//
// OBJECT FILE HANDLING
//
//
struct ObjFileHeader {
short id; // 'x6'
short sections;
short relocs;
short labels;
short late_evals;
short map_symbols;
unsigned int stringdata;
int bindata;
};
struct ObjFileStr {
int offs; // offset into string table
};
struct ObjFileSection {
enum SectionFlags {
OFS_DUMMY,
OFS_FIXED,
OFS_MERGED,
};
struct ObjFileStr name;
struct ObjFileStr exp_app;
int start_address;
int end_address; // address size
int output_size; // assembled binary size
int align_address;
short next_group; // next section of group
short first_group; // first section of group
short relocs;
SectionType type;
char flags;
};
struct ObjFileReloc {
int base_value;
int section_offset;
short target_section;
char bytes;
char shift;
};
struct ObjFileLabel {
enum LabelFlags {
OFL_EVAL = (1<<15), // Evaluated (may still be relative)
OFL_ADDR = (1<<14), // Address or Assign
OFL_CNST = (1<<13), // Constant
OFL_XDEF = OFL_CNST-1 // External (index into file array)
};
struct ObjFileStr name;
int value;
int flags; // 1<<(LabelFlags)
short section; // -1 if resolved, file section # if section rel
short mapIndex; // -1 if resolved, index into map if relative
};
struct ObjFileLateEval {
struct ObjFileStr label;
struct ObjFileStr expression;
int address; // PC relative to section or fixed
int target; // offset into section memory
short section; // section to target
short rept; // value of rept for this late eval
short scope; // PC start of scope
short type; // label, byte, branch, word (LateEval::Type)
};
struct ObjFileMapSymbol {
struct ObjFileStr name; // symbol name
int value;
short section;
bool local; // local labels are probably needed
};
// Simple string pool, converts strref strings to zero terminated strings and returns the offset to the string in the pool.
static int _AddStrPool(const strref str, pairArray<unsigned int, int> *pLookup, char **strPool, unsigned int &strPoolSize, unsigned int &strPoolCap)
{
if (!str.get() || !str.get_len())
return -1; // empty string
unsigned int hash = str.fnv1a();
unsigned int index = FindLabelIndex(hash, pLookup->getKeys(), pLookup->count());
if (index<pLookup->count() && str.same_str_case(*strPool + pLookup->getValue(index)))
return pLookup->getValue(index);
int strOffs = strPoolSize;
if ((strOffs + str.get_len() + 1) > strPoolCap) {
strPoolCap = strOffs + str.get_len() + 4096;
char *strPoolGrow = (char*)malloc(strPoolCap);
if (strPoolGrow) {
if (*strPool) {
memcpy(strPoolGrow, *strPool, strPoolSize);
free(*strPool);
}
*strPool = strPoolGrow;
} else
return -1;
}
if (*strPool) {
char *dest = *strPool + strPoolSize;
memcpy(dest, str.get(), str.get_len());
dest[str.get_len()] = 0;
strPoolSize += str.get_len()+1;
pLookup->insert(index, hash);
pLookup->getValues()[index] = strOffs;
}
return strOffs;
}
StatusCode Asm::WriteObjectFile(strref filename)
{
if (allSections.size()==0)
return ERROR_NOT_A_SECTION;
if (FILE *f = fopen(strown<512>(filename).c_str(), "wb")) {
struct ObjFileHeader hdr = { 0 };
hdr.id = 0x7836;
hdr.sections = (short)allSections.size();
hdr.relocs = 0;
hdr.bindata = 0;
for (std::vector<Section>::iterator s = allSections.begin(); s!=allSections.end(); ++s) {
if (s->type != ST_REMOVED) {
if (s->pRelocs)
hdr.relocs += short(s->pRelocs->size());
hdr.bindata += s->size();
}
}
hdr.late_evals = (short)lateEval.size();
hdr.map_symbols = (short)map.size();
hdr.stringdata = 0;
// labels don't include XREF labels
hdr.labels = 0;
for (unsigned int l = 0; l<labels.count(); l++) {
if (!labels.getValue(l).reference)
hdr.labels++;
}
short *aRemapSects = hdr.sections ? (short*)malloc(sizeof(short) * hdr.sections) : nullptr;
if (!aRemapSects) {
fclose(f);
return ERROR_OUT_OF_MEMORY;
}
// include space for external protected labels
for (std::vector<ExtLabels>::iterator el = externals.begin(); el != externals.end(); ++el)
hdr.labels += el->labels.count();
char *stringPool = nullptr;
unsigned int stringPoolCap = 0;
pairArray<unsigned int, int> stringArray;
stringArray.reserve(hdr.labels * 2 + hdr.sections + hdr.late_evals*2);
struct ObjFileSection *aSects = hdr.sections ? (struct ObjFileSection*)calloc(hdr.sections, sizeof(struct ObjFileSection)) : nullptr;
struct ObjFileReloc *aRelocs = hdr.relocs ? (struct ObjFileReloc*)calloc(hdr.relocs, sizeof(struct ObjFileReloc)) : nullptr;
struct ObjFileLabel *aLabels = hdr.labels ? (struct ObjFileLabel*)calloc(hdr.labels, sizeof(struct ObjFileLabel)) : nullptr;
struct ObjFileLateEval *aLateEvals = hdr.late_evals ? (struct ObjFileLateEval*)calloc(hdr.late_evals, sizeof(struct ObjFileLateEval)) : nullptr;
struct ObjFileMapSymbol *aMapSyms = hdr.map_symbols ? (struct ObjFileMapSymbol*)calloc(hdr.map_symbols, sizeof(struct ObjFileMapSymbol)) : nullptr;
int sect = 0, reloc = 0, labs = 0, late = 0, map_sym = 0;
memset(aRemapSects, 0xff, sizeof(short) * hdr.sections);
// discard the removed sections by making a table of skipped indices
for (std::vector<Section>::iterator si = allSections.begin(); si!=allSections.end(); ++si) {
if (si->type != ST_REMOVED)
aRemapSects[&*si-&allSections[0]] = sect++;
}
sect = 0;
// write out sections and relocs
if (hdr.sections) {
for (std::vector<Section>::iterator si = allSections.begin(); si!=allSections.end(); ++si) {
if (si->type == ST_REMOVED)
continue;
struct ObjFileSection &s = aSects[sect++];
s.name.offs = _AddStrPool(si->name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
s.exp_app.offs = _AddStrPool(si->export_append, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
s.output_size = si->size();
s.align_address = si->align_address;
s.next_group = si->next_group >= 0 ? aRemapSects[si->next_group] : -1;
s.first_group = si->first_group >= 0 ? aRemapSects[si->first_group] : -1;
s.relocs = si->pRelocs ? (short)(si->pRelocs->size()) : 0;
s.start_address = si->start_address;
s.end_address = si->address;
s.type = si->type;
s.flags =
(si->IsDummySection() ? (1 << ObjFileSection::OFS_DUMMY) : 0) |
(si->IsMergedSection() ? (1 << ObjFileSection::OFS_MERGED) : 0) |
(si->address_assigned ? (1 << ObjFileSection::OFS_FIXED) : 0);
if (si->pRelocs && si->pRelocs->size() && aRelocs) {
for (relocList::iterator ri = si->pRelocs->begin(); ri!=si->pRelocs->end(); ++ri) {
struct ObjFileReloc &r = aRelocs[reloc++];
r.base_value = ri->base_value;
r.section_offset = ri->section_offset;
r.target_section = ri->target_section >= 0 ? aRemapSects[ri->target_section] : -1;
r.bytes = ri->bytes;
r.shift = ri->shift;
}
}
}
}
hdr.sections = sect;
// write out labels
if (hdr.labels) {
for (unsigned int li = 0; li<labels.count(); li++) {
Label &lo = labels.getValue(li);
if (!lo.reference) {
struct ObjFileLabel &l = aLabels[labs++];
l.name.offs = _AddStrPool(lo.label_name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
l.value = lo.value;
l.section = lo.section >=0 ? aRemapSects[lo.section] : -1;
l.mapIndex = lo.mapIndex;
l.flags =
(lo.constant ? ObjFileLabel::OFL_CNST : 0) |
(lo.pc_relative ? ObjFileLabel::OFL_ADDR : 0) |
(lo.evaluated ? ObjFileLabel::OFL_EVAL : 0) |
(lo.external ? ObjFileLabel::OFL_XDEF : 0);
}
}
}
// protected labels included from other object files
if (hdr.labels) {
int file_index = 1;
for (std::vector<ExtLabels>::iterator el = externals.begin(); el != externals.end(); ++el) {
for (unsigned int li = 0; li < el->labels.count(); ++li) {
Label &lo = el->labels.getValue(li);
struct ObjFileLabel &l = aLabels[labs++];
l.name.offs = _AddStrPool(lo.label_name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
l.value = lo.value;
l.section = lo.section >= 0 ? aRemapSects[lo.section] : -1;
l.mapIndex = lo.mapIndex;
l.flags =
(lo.constant ? ObjFileLabel::OFL_CNST : 0) |
(lo.pc_relative ? ObjFileLabel::OFL_ADDR : 0) |
(lo.evaluated ? ObjFileLabel::OFL_EVAL : 0) |
file_index;
}
file_index++;
}
}
// write out late evals
if (aLateEvals) {
for (std::vector<LateEval>::iterator lei = lateEval.begin(); lei != lateEval.end(); ++lei) {
struct ObjFileLateEval &le = aLateEvals[late++];
le.label.offs = _AddStrPool(lei->label, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
le.expression.offs = _AddStrPool(lei->expression, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
le.section = lei->section >= 0 ? aRemapSects[lei->section] : -1;
le.rept = lei->rept;
le.target = (short)lei->target;
le.address = lei->address;
le.scope = lei->scope;
le.type = lei->type;
}
}
// write out map symbols
if (aMapSyms) {
for (MapSymbolArray::iterator mi = map.begin(); mi != map.end(); ++mi) {
struct ObjFileMapSymbol &ms = aMapSyms[map_sym++];
ms.name.offs = _AddStrPool(mi->name, &stringArray, &stringPool, hdr.stringdata, stringPoolCap);
ms.value = mi->value;
ms.local = mi->local;
ms.section = mi->section >= 0 ? aRemapSects[mi->section] : -1;
}
}
// write out the file
fwrite(&hdr, sizeof(hdr), 1, f);
fwrite(aSects, sizeof(aSects[0]), sect, f);
fwrite(aRelocs, sizeof(aRelocs[0]), reloc, f);
fwrite(aLabels, sizeof(aLabels[0]), labs, f);
fwrite(aLateEvals, sizeof(aLateEvals[0]), late, f);
fwrite(aMapSyms, sizeof(aMapSyms[0]), map_sym, f);
fwrite(stringPool, hdr.stringdata, 1, f);
for (std::vector<Section>::iterator si = allSections.begin(); si!=allSections.end(); ++si) {
if (!si->IsDummySection() && !si->IsMergedSection() && si->size()!=0 && si->type != ST_REMOVED)
fwrite(si->output, si->size(), 1, f);
}
// done with I/O
fclose(f);
if (aRemapSects)
free(aRemapSects);
if (stringPool)
free(stringPool);
if (aMapSyms)
free(aMapSyms);
if (aLateEvals)
free(aLateEvals);
if (aLabels)
free(aLabels);
if (aRelocs)
free(aRelocs);
if (aSects)
free(aSects);
stringArray.clear();
}
return STATUS_OK;
}
StatusCode Asm::ReadObjectFile(strref filename, int link_to_section)
{
size_t size;
strown<512> file;
file.copy(filename); // Merlin mostly uses extension-less files, append .x65 as a default
if ((syntax==SYNTAX_MERLIN && !file.has_suffix(".x65")) || filename.find('.')<0)
file.append(".x65");
int file_index = (int)externals.size();
if (char *data = LoadBinary(file.get_strref(), size)) {
struct ObjFileHeader &hdr = *(struct ObjFileHeader*)data;
size_t sum = sizeof(hdr) + hdr.sections*sizeof(struct ObjFileSection) +
hdr.relocs * sizeof(struct ObjFileReloc) + hdr.labels * sizeof(struct ObjFileLabel) +
hdr.late_evals * sizeof(struct ObjFileLateEval) +
hdr.map_symbols * sizeof(struct ObjFileMapSymbol) + hdr.stringdata + hdr.bindata;
if (hdr.id == 0x7836 && sum == size) {
struct ObjFileSection *aSect = (struct ObjFileSection*)(&hdr + 1);
struct ObjFileReloc *aReloc = (struct ObjFileReloc*)(aSect + hdr.sections);
struct ObjFileLabel *aLabels = (struct ObjFileLabel*)(aReloc + hdr.relocs);
struct ObjFileLateEval *aLateEval = (struct ObjFileLateEval*)(aLabels + hdr.labels);
struct ObjFileMapSymbol *aMapSyms = (struct ObjFileMapSymbol*)(aLateEval + hdr.late_evals);
const char *str_orig = (const char*)(aMapSyms + hdr.map_symbols);
const char *bin_data = str_orig + hdr.stringdata;
char *str_pool = (char*)malloc(hdr.stringdata);
memcpy(str_pool, str_orig, hdr.stringdata);
loadedData.push_back(str_pool);
int prevSection = SectionId();
short *aSctRmp = (short*)malloc(hdr.sections * sizeof(short));
int last_linked_section = link_to_section;
while (last_linked_section>=0 && allSections[last_linked_section].next_group >= 0)
last_linked_section = allSections[last_linked_section].next_group;
// for now just append to existing assembler data
// sections
for (int si = 0; si < hdr.sections; si++) {
short f = aSect[si].flags;
if (f & (1 << ObjFileSection::OFS_MERGED))
continue;
if (f & (1 << ObjFileSection::OFS_DUMMY)) {
if (f&(1 << ObjFileSection::OFS_FIXED)) {
DummySection(aSect[si].start_address);
CurrSection().AddBin(nullptr, aSect[si].end_address - aSect[si].start_address);
} else {
DummySection();
CurrSection().AddBin(nullptr, aSect[si].end_address - aSect[si].start_address);
}
} else {
if (f&(1 << ObjFileSection::OFS_FIXED))
SetSection(aSect[si].name.offs>=0 ? strref(str_pool + aSect[si].name.offs) : strref(), aSect[si].start_address);
else
SetSection(aSect[si].name.offs >= 0 ? strref(str_pool + aSect[si].name.offs) : strref());
Section &s = CurrSection();
s.include_from = filename;
s.export_append = aSect[si].exp_app.offs>=0 ? strref(str_pool + aSect[si].name.offs) : strref();
s.align_address = aSect[si].align_address;
s.address = aSect[si].end_address;
s.type = aSect[si].type;
if (aSect[si].output_size) {
s.output = (unsigned char*)malloc(aSect[si].output_size);
memcpy(s.output, bin_data, aSect[si].output_size);
s.curr = s.output + aSect[si].output_size;
s.output_capacity = aSect[si].output_size;
bin_data += aSect[si].output_size;
}
if (last_linked_section>=0) {
allSections[last_linked_section].next_group = SectionId();
s.first_group = allSections[last_linked_section].first_group >=0 ? allSections[last_linked_section].first_group : last_linked_section;
last_linked_section = SectionId();
}
}
aSctRmp[si] = (short)allSections.size()-1;
}
// fix up groups and relocs
int curr_reloc = 0;
for (int si = 0; si < hdr.sections; si++) {
Section &s = allSections[aSctRmp[si]];
if (aSect[si].first_group >= 0)
s.first_group = aSctRmp[aSect[si].first_group];
if (aSect[si].next_group >= 0)
s.first_group = aSctRmp[aSect[si].next_group];
for (int ri = 0; ri < aSect[si].relocs; ri++) {
int r = ri + curr_reloc;
struct ObjFileReloc &rs = aReloc[r];
allSections[aSctRmp[si]].AddReloc(rs.base_value, rs.section_offset, aSctRmp[rs.target_section], rs.bytes, rs.shift);
}
curr_reloc += aSect[si].relocs;
}
for (int mi = 0; mi < hdr.map_symbols; mi++) {
struct ObjFileMapSymbol &m = aMapSyms[mi];
if (map.size() == map.capacity())
map.reserve(map.size() + 256);
MapSymbol sym;
sym.name = m.name.offs>=0 ? strref(str_pool + m.name.offs) : strref();
sym.section = m.section >=0 ? aSctRmp[m.section] : m.section;
sym.value = m.value;
sym.local = m.local;
map.push_back(sym);
}
for (int li = 0; li < hdr.labels; li++) {
struct ObjFileLabel &l = aLabels[li];
strref name = l.name.offs >= 0 ? strref(str_pool + l.name.offs) : strref();
Label *lbl = GetLabel(name);
short f = l.flags;
int external = f & ObjFileLabel::OFL_XDEF;
if (external == ObjFileLabel::OFL_XDEF) {
if (!lbl)
lbl = AddLabel(name.fnv1a()); // insert shared label
else if (!lbl->reference)
continue;
} else { // insert protected label
while ((file_index + external) >= (int)externals.size()) {
if (externals.size() == externals.capacity())
externals.reserve(externals.size() + 32);
externals.push_back(ExtLabels());
}
unsigned int hash = name.fnv1a();
unsigned int index = FindLabelIndex(hash, externals[file_index].labels.getKeys(), externals[file_index].labels.count());
externals[file_index].labels.insert(index, hash);
lbl = externals[file_index].labels.getValues() + index;
}
lbl->label_name = name;
lbl->pool_name.clear();
lbl->value = l.value;
lbl->section = l.section >= 0 ? aSctRmp[l.section] : l.section;
lbl->mapIndex = l.mapIndex >= 0 ? (l.mapIndex + (int)map.size()) : -1;
lbl->evaluated = !!(f & ObjFileLabel::OFL_EVAL);
lbl->pc_relative = !!(f & ObjFileLabel::OFL_ADDR);
lbl->constant = !!(f & ObjFileLabel::OFL_CNST);
lbl->external = external == ObjFileLabel::OFL_XDEF;
lbl->reference = false;
}
if (file_index==(int)externals.size())
file_index = -1; // no protected labels => don't track as separate file
for (int li = 0; li < hdr.late_evals; ++li) {
struct ObjFileLateEval &le = aLateEval[li];
strref name = le.label.offs >= 0 ? strref(str_pool + le.label.offs) : strref();
Label *pLabel = GetLabel(name);
if (pLabel) {
if (pLabel->evaluated) {
AddLateEval(name, le.address, le.scope, strref(str_pool + le.expression.offs), (LateEval::Type)le.type);
LateEval &last = lateEval[lateEval.size()-1];
last.section = le.section >= 0 ? aSctRmp[le.section] : le.section;
last.rept = le.rept;
last.source_file = strref();
last.file_ref = file_index;
}
} else {
AddLateEval(le.target, le.address, le.scope, strref(str_pool + le.expression.offs), strref(), (LateEval::Type)le.type);
LateEval &last = lateEval[lateEval.size()-1];
last.section = le.section >= 0 ? aSctRmp[le.section] : le.section;
last.rept = le.rept;
last.file_ref = file_index;
}
}
free(aSctRmp);
// restore previous section
current_section = &allSections[prevSection];
} else
return ERROR_NOT_AN_X65_OBJECT_FILE;
}
return STATUS_OK;
}
// number of section types that can be merged
enum OMFRecCode {
OMFR_END = 0,
OMFR_RELOC = 0xe2, // bytes.b, bitshift.b, offset.l, value.l
OMFR_INTERSEG = 0xe3, // bytes.b, bitshift.b, offset.l, filenum.w, segnum.w, offsref.l
OMFR_LCONST = 0xf2, // bytes.b, b,b,b, data.b[bytes]
OMFR_cRELOC = 0xf5, // bytes.b, bitshift.b, offset.w, value.w
OMFR_cINTERSEG = 0xf6, // bytes.b, bitshift.b, offset.w, segnum.b, offsref.w
OMFR_SUPER = 0xf7,
};
typedef unsigned char u8;
struct OMFSegHdr {
u8 SegTotal[4]; // + Segment Segment Header size Body size
u8 ResSpc[4]; // Number of 0x00 to add to the end of Body
u8 Length[4]; // Memory Size Segment
u8 pad1[1];
u8 LabLen[1]; // Length Names: 10
u8 NumLen[1]; // Size = 4 numbers uint8s
u8 Version[1]; // OMF Version: 2
u8 BankSize[4]; // Size of a Bank: 64 KB code if, between 0 and 64 KB for Data
u8 Kind[2]; // Type Segment
u8 pad2[2];
u8 Org[4];
u8 Align[4]; // Alignment: 0, 64 or 256 KB
u8 NumSEx[1]; // Little Endian: 0 for IIgs
u8 pad3[1];
u8 SegNum[2]; // Segment Number: 1-> N
u8 EntryPointOffset[4]; // Entry point in the Segment
u8 DispNameOffset[2]; // Where the offset is located LoadName
u8 DispDataOffset[2]; // Offset begins the Body Segment
u8 tempOrg[4]; //
};
// write a number of bytes of a value
static void _writeNBytes(unsigned char *dest, int bytes, int value) {
while (bytes--) {
*dest++ = (unsigned char)value;
value >>= 8;
}
}
// sort relocs before writing GS OS reloc instructions
static int sortRelocByOffs(const void *A, const void *B) {
return ((const Reloc*)A)->section_offset - ((const Reloc*)B)->section_offset;
}
// Export an Apple II GS relocatable executable
StatusCode Asm::WriteA2GS_OMF(strref filename, bool full_collapse)
{
// determine the section with startup code - either first loaded object file or current file
int first_section = 0;
for (int s = 1; s<(int)allSections.size(); s++) {
if (allSections[s].type==ST_CODE && (allSections[first_section].type != ST_CODE ||
(!allSections[s].include_from && allSections[first_section].include_from)))
first_section = s;
}
// collapse all section together that share the same name
StatusCode status = MergeSectionsByName(first_section);
if (status != STATUS_OK)
return status;
// Zero page section for x65 implies addresses, OMF direct-page/stack seg implies size of direct page + stack
// so resolve the zero page sections first
status = LinkZP();
if (status != STATUS_OK)
return status;
// full collapse means that all sections gets merged into one
// code+data section and one bss section which will be appended
if (full_collapse) {
status = MergeAllSections(first_section);
if (status != STATUS_OK)
return status;
}
// determine if there is a direct page stack
int DP_Stack_Size = 0; // 0 => default size (don't include)
for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) {
if (s->type == ST_ZEROPAGE)
s->type = ST_REMOVED;
if (s->type == ST_BSS && s->name.same_str("directpage_stack")) {
DP_Stack_Size += s->addr_size();
s->type = ST_REMOVED;
}
}
std::vector<int> SegNum; // order of valid segments
std::vector<int> SegLookup; // inverse of SegNum
SegNum.reserve(allSections.size());
SegLookup.reserve(allSections.size());
int reloc_max = 1;
// OMF super instructions work by incremental addresses, sort relocs to simplify output
for (std::vector<Section>::iterator s = allSections.begin(); s != allSections.end(); ++s) {
if (first_section == SectionId(*s))
SegNum.insert(SegNum.begin(), SectionId(*s));
else if (s->type != ST_REMOVED)
SegNum.push_back(SectionId(*s));
SegLookup.push_back(-1);
if ((s->type == ST_CODE || s->type == ST_DATA) && s->pRelocs && s->pRelocs->size() > 1) {
qsort(&(*s->pRelocs)[0], s->pRelocs->size(), sizeof(Reloc), sortRelocByOffs);
if ((int)s->pRelocs->size() > reloc_max)
reloc_max = (int)s->pRelocs->size();
}
}
for (std::vector<int>::iterator i = SegNum.begin(); i != SegNum.end(); ++i)
SegLookup[*i] = (int)(&*i - &SegNum[0]);
unsigned char *instructions = (unsigned char*)malloc(reloc_max * 16);
if (!instructions)
return ERROR_OUT_OF_MEMORY;
// open a file for writing
FILE *f = fopen(strown<512>(filename).c_str(), "wb");
if (!f) {
free(instructions);
return ERROR_CANT_WRITE_TO_FILE;
}
// consume all the relocs
struct OMFSegHdr hdr = { 0 }; // initialize segment header
hdr.NumLen[0] = 4; // numbers are 4 bytes under GS OS
hdr.Version[0] = 2; // version is 2 for GS OS
hdr.BankSize[2] = 1; // 64k banks
_writeNBytes(hdr.DispNameOffset, 2, sizeof(hdr)); // start of file name (10 chars)
strref fileBase = export_base_name;
char segfile[10];
memset(segfile, ' ', 10);
memcpy(segfile, fileBase.get(), fileBase.get_len() > 10 ? 10 : fileBase.get_len());
for (std::vector<int>::iterator i = SegNum.begin(); i != SegNum.end(); ++i) {
Section &s = allSections[*i];
strref segName = s.name ? s.name : (s.type == ST_CODE ? strref("CODE") : strref("DATA"));
// support zero bytes at end of block
int num_zeroes_at_end = s.addr_size() - s.size();
int num_bytes_file = s.size();
while (num_bytes_file && s.output[num_bytes_file - 1] == 0) {
num_zeroes_at_end++;
num_bytes_file--;
}
_writeNBytes(hdr.SegNum, 2, SegLookup[*i] + 1);
_writeNBytes(hdr.Kind, 2, s.type == ST_CODE ? 0x1000 : (s.type == ST_ZEROPAGE ? 0x12 : 0x1001));
_writeNBytes(hdr.DispDataOffset, 2, sizeof(hdr) + 10 + 1 + segName.get_len());
_writeNBytes(hdr.Length, 4, num_bytes_file + num_zeroes_at_end);
_writeNBytes(hdr.ResSpc, 4, num_zeroes_at_end);
_writeNBytes(hdr.Align, 4, s.align_address > 1 ? 256 : 0);
// instruction list starts with a LCONST + Length(4) + binary, the relocs begin after that
int instruction_offs = 0;
instructions[instruction_offs++] = OMFR_LCONST;
_writeNBytes(instructions+instruction_offs, 4, num_bytes_file);
instruction_offs += 4;
if (s.pRelocs && s.pRelocs->size()) {
// insert all SUPER_RELOC2 / SUPER_RELOC3
for (int b = 0; b <= 1; b++) {
int count_offs = -1;
int prev_page = 0;
int inst_curr = instruction_offs;
instructions[inst_curr++] = OMFR_SUPER;
int len_offs = inst_curr;
inst_curr += 4;
instructions[inst_curr++] = b; // SUPER_RELOC2 / SUPER_RELOC3
// try all SUPER_RELOC2 (2 bytes self reference, no shift)
relocList::iterator r = s.pRelocs->begin();
while (r != s.pRelocs->end()) {
if (r->shift == 0 && r->bytes == (b+2) && r->target_section == SectionId(s)) {
if ((r->section_offset >> 8) != prev_page) {
instructions[inst_curr++] = 0x80 | ((r->section_offset >> 8) - prev_page - 1);
count_offs = -1;
} // update patch counter for current page or add a new counter
if (count_offs < 0) { count_offs = inst_curr; instructions[inst_curr++] = 0; }
else instructions[count_offs]++;
prev_page = r->section_offset>>8;
instructions[inst_curr++] = (unsigned char)r->section_offset; // write patch offset into binary
_writeNBytes(s.output + r->section_offset, b + 2, r->base_value); // patch binary with base value
r = s.pRelocs->erase(r);
} else
++r;
}
if (inst_curr > (instruction_offs + 6)) {
_writeNBytes(instructions + len_offs, 4, inst_curr - instruction_offs - 5);
instruction_offs = inst_curr;
}
}
// insert all other records as they are encountered
relocList::iterator r = s.pRelocs->begin();
while (r != s.pRelocs->end()) {
if (r->target_section == SectionId(s)) {
// this is a reloc, check if cRELOC is ok or if need RELOC
bool cRELOC = r->section_offset < 0x10000 && r->base_value < 0x10000;
instructions[instruction_offs++] = cRELOC ? OMFR_cRELOC : OMFR_RELOC;
instructions[instruction_offs++] = r->bytes;
instructions[instruction_offs++] = r->shift;
_writeNBytes(instructions + instruction_offs, cRELOC ? 2 : 4, r->section_offset);
instruction_offs += cRELOC ? 2 : 4;
_writeNBytes(instructions + instruction_offs, cRELOC ? 2 : 4, r->base_value);
instruction_offs += cRELOC ? 2 : 4;
} else {
// this is an interseg
bool cINTERSEG = r->section_offset < 0x10000 && r->base_value < 0x10000;
instructions[instruction_offs++] = cINTERSEG ? OMFR_cINTERSEG : OMFR_INTERSEG;
instructions[instruction_offs++] = r->bytes;
instructions[instruction_offs++] = r->shift;
_writeNBytes(instructions + instruction_offs, cINTERSEG ? 2 : 4, r->section_offset);
instruction_offs += cINTERSEG ? 2 : 4;
_writeNBytes(instructions + instruction_offs, cINTERSEG ? 0: 2 , 1); // file number = 1
instruction_offs += cINTERSEG ? 0 : 2;
_writeNBytes(instructions + instruction_offs, cINTERSEG ? 1 : 2, SegLookup[r->target_section] + 1); // segment number starting from 1
instruction_offs += cINTERSEG ? 1 : 2;
_writeNBytes(instructions + instruction_offs, cINTERSEG ? 2 : 4, r->base_value);
instruction_offs += cINTERSEG ? 2 : 4;
}
r = s.pRelocs->erase(r);
}
}
instructions[instruction_offs++] = OMFR_END;
// size of seg = file header + 10 bytes file name + 1 byte seg name length + seg nameh + seg.addr_size() + instruction_size
int segSize = sizeof(hdr) + 10 + 1 + segName.get_len() + num_bytes_file + instruction_offs;
if (num_bytes_file == 0)
segSize -= 5;
_writeNBytes(hdr.SegTotal, 4, segSize);
u8 lenSegName = (u8)segName.get_len();
fwrite(&hdr, sizeof(hdr), 1, f);
fwrite(segfile, 10, 1, f);
fwrite(&lenSegName, 1, 1, f);
fwrite(segName.get(), segName.get_len(), 1, f);
if (num_bytes_file) {
fwrite(instructions, 5, 1, f); // $f2 + 4 bytes data size
fwrite(s.output, num_bytes_file, 1, f); // segment data
}
if (instruction_offs > 5)
fwrite(instructions + 5, instruction_offs - 5, 1, f); // reloc instructions
}
// if there is a size of the direct page & stack, write it
if (DP_Stack_Size) {
strref segName("DPStack");
char lenSegName = segName.get_len();
_writeNBytes(hdr.SegNum, 2, (int)SegNum.size()+1);
_writeNBytes(hdr.Kind, 2, 0x12);
_writeNBytes(hdr.DispDataOffset, 2, sizeof(hdr) + 10 + 1 + segName.get_len());
_writeNBytes(hdr.Length, 4, DP_Stack_Size);
_writeNBytes(hdr.ResSpc, 4, DP_Stack_Size);
_writeNBytes(hdr.Align, 4, 256);
int segSize = sizeof(hdr) + 10 + 1 + segName.get_len() + 1;
_writeNBytes(hdr.SegTotal, 4, segSize);
instructions[0] = 0;
fwrite(&hdr, sizeof(hdr), 1, f);
fwrite(segfile, 10, 1, f);
fwrite(&lenSegName, 1, 1, f);
fwrite(segName.get(), segName.get_len(), 1, f);
fwrite(instructions, 1, 1, f); // end instruction
}
free(instructions);
fclose(f);
return STATUS_OK;
}
int main(int argc, char **argv)
{
const strref listing("lst");
const strref allinstr("opcodes");
const strref endmacro("endm");
const strref cpu("cpu");
const strref acc("acc");
const strref xy("xy");
const strref org("org");
int return_value = 0;
bool load_header = true;
bool size_header = false;
bool info = false;
bool gen_allinstr = false;
bool gs_os_reloc = false;
bool force_merge_sections = false;
Asm assembler;
const char *source_filename = nullptr, *obj_out_file = nullptr;
const char *binary_out_name = nullptr;
const char *sym_file = nullptr, *vs_file = nullptr;
strref list_file, allinstr_file;
for (int a = 1; a<argc; a++) {
strref arg(argv[a]);
if (arg.get_first()=='-') {
++arg;
if (arg.get_first()=='i')
assembler.AddIncludeFolder(arg+1);
else if (arg.same_str("merlin"))
assembler.syntax = SYNTAX_MERLIN;
else if (arg.get_first()=='D' || arg.get_first()=='d') {
++arg;
if (arg.find('=')>0)
assembler.AssignLabel(arg.before('='), arg.after('='));
else
assembler.AssignLabel(arg, "1");
} else if (arg.same_str("c64")) {
load_header = true;
size_header = false;
} else if (arg.same_str("a2b")) {
assembler.default_org = 0x0803;
load_header = true;
size_header = true;
} else if (arg.same_str("bin")) {
load_header = false;
size_header = false;
} else if (arg.same_str("a2p")) {
assembler.default_org = 0x2000;
load_header = false;
size_header = false;
} else if (arg.same_str("a2o")) {
gs_os_reloc = true;
} else if (arg.same_str("mrg")) {
force_merge_sections = true;
} else if (arg.same_str("sect"))
info = true;
else if (arg.same_str(endmacro))
assembler.end_macro_directive = true;
else if (arg.has_prefix(listing) && (arg.get_len() == listing.get_len() || arg[listing.get_len()] == '=')) {
assembler.list_assembly = true;
list_file = arg.after('=');
} else if (arg.has_prefix(allinstr) && (arg.get_len() == allinstr.get_len() || arg[allinstr.get_len()] == '=')) {
gen_allinstr = true;
allinstr_file = arg.after('=');
} else if (arg.has_prefix(org)) {
arg = arg.after('=');
if (arg && arg.get_first() == '$' && arg.get_len()>1)
assembler.default_org = (arg + 1).ahextoui();
else if (arg.is_number())
assembler.default_org = arg.atoi();
} else if (arg.has_prefix(acc) && arg[acc.get_len()] == '=') {
assembler.accumulator_16bit = arg.after('=').atoi() == 16;
} else if (arg.has_prefix(xy) && arg[xy.get_len()] == '=') {
assembler.index_reg_16bit = arg.after('=').atoi() == 16;
} else if (arg.has_prefix(cpu) && (arg.get_len() == cpu.get_len() || arg[cpu.get_len()] == '=')) {
arg.split_token_trim('=');
bool found = false;
for (int c = 0; c<nCPUs; c++) {
if (arg) {
if (arg.same_str(aCPUs[c].name)) {
assembler.SetCPU((CPUIndex)c);
found = true;
break;
}
} else
printf("%s\n", aCPUs[c].name);
}
if (!found && arg) {
printf("ERROR: UNKNOWN CPU " STRREF_FMT "\n", STRREF_ARG(arg));
return 1;
}
if (!arg)
return 0;
} else if (arg.same_str("sym") && (a + 1) < argc)
sym_file = argv[++a];
else if (arg.same_str("obj") && (a + 1) < argc)
obj_out_file = argv[++a];
else if (arg.same_str("vice") && (a + 1) < argc)
vs_file = argv[++a];
} else if (!source_filename)
source_filename = arg.get();
else if (!binary_out_name)
binary_out_name = arg.get();
}
if (gen_allinstr) {
assembler.AllOpcodes(allinstr_file);
} else if (!source_filename) {
puts("Usage:\n"
" x65 filename.s code.prg [options]\n"
" * -i(path) : Add include path\n"
" * -D(label)[=value] : Define a label with an optional value (otherwise defined as 1)\n"
" * -cpu=6502/65c02/65c02wdc/65816: assemble with opcodes for a different cpu\n"
" * -acc=8/16: set the accumulator mode for 65816 at start, default is 8 bits\n"
" * -xy=8/16: set the index register mode for 65816 at start, default is 8 bits\n"
" * -org = $2000 or - org = 4096: set the default start address of fixed address code\n"
" * -obj (file.x65) : generate object file for later linking\n"
" * -bin : Raw binary\n"
" * -c64 : Include load address(default)\n"
" * -a2b : Apple II Dos 3.3 Binary\n"
" * -a2p : Apple II ProDos Binary\n"
" * -a2o : Apple II GS OS executable (relocatable)\n"
" * -mrg : Force merge all sections (use with -a2o)\n"
" * -sym (file.sym) : symbol file\n"
" * -lst / -lst = (file.lst) : generate disassembly text from result(file or stdout)\n"
" * -opcodes / -opcodes = (file.s) : dump all available opcodes(file or stdout)\n"
" * -sect: display sections loaded and built\n"
" * -vice (file.vs) : export a vice symbol file\n"
" * -merlin: use Merlin syntax\n"
" * -endm : macros end with endm or endmacro instead of scoped('{' - '}')\n");
return 0;
}
// Load source
if (source_filename) {
size_t size = 0;
strref srcname(source_filename);
assembler.export_base_name =
strref(binary_out_name).after_last_or_full('/', '\\').before_or_full('.');
if (char *buffer = assembler.LoadText(srcname, size)) {
// if source_filename contains a path add that as a search path for include files
assembler.AddIncludeFolder(srcname.before_last('/', '\\'));
assembler.Assemble(strref(buffer, strl_t(size)), srcname, obj_out_file != nullptr);
if (assembler.error_encountered)
return_value = 1;
else {
// export object file (this can be done at the same time as building a binary)
if (obj_out_file)
assembler.WriteObjectFile(obj_out_file);
// if exporting binary or relocatable executable, complete the build
if (binary_out_name && !srcname.same_str(binary_out_name)) {
if (gs_os_reloc)
assembler.WriteA2GS_OMF(binary_out_name, force_merge_sections);
else {
strref binout(binary_out_name);
strref ext = binout.after_last('.');
if (ext)
binout.clip(ext.get_len() + 1);
strref aAppendNames[MAX_EXPORT_FILES];
StatusCode err = assembler.LinkZP(); // link zero page sections
if (err > FIRST_ERROR) {
assembler.PrintError(strref(), err);
return_value = 1;
}
int numExportFiles = assembler.GetExportNames(aAppendNames, MAX_EXPORT_FILES);
for (int e = 0; e < numExportFiles; e++) {
strown<512> file(binout);
file.append(aAppendNames[e]);
file.append('.');
file.append(ext);
int size;
int addr;
if (unsigned char *buf = assembler.BuildExport(aAppendNames[e], size, addr)) {
if (FILE *f = fopen(file.c_str(), "wb")) {
if (load_header) {
char load_addr[2] = { (char)addr, (char)(addr >> 8) };
fwrite(load_addr, 2, 1, f);
}
if (size_header) {
char byte_size[2] = { (char)size, (char)(size >> 8) };
fwrite(byte_size, 2, 1, f);
}
fwrite(buf, size, 1, f);
fclose(f);
}
free(buf);
}
}
}
}
// print encountered sections info
if (info) {
printf("SECTIONS SUMMARY\n================\n");
for (size_t i = 0; i < assembler.allSections.size(); ++i) {
Section &s = assembler.allSections[i];
if (s.address > s.start_address) {
printf("Section %d: \"" STRREF_FMT "\" Dummy: %s Relative: %s Merged: %s Start: 0x%04x End: 0x%04x\n",
(int)i, STRREF_ARG(s.name), s.dummySection ? "yes" : "no",
s.IsRelativeSection() ? "yes" : "no", s.IsMergedSection() ? "yes" : "no", s.start_address, s.address);
if (s.pRelocs) {
for (relocList::iterator i = s.pRelocs->begin(); i != s.pRelocs->end(); ++i)
printf("\tReloc value $%x at offs $%x section %d\n", i->base_value, i->section_offset, i->target_section);
}
}
}
}
// listing after export since addresses are now resolved
if (assembler.list_assembly)
assembler.List(list_file);
// export .sym file
if (sym_file && !srcname.same_str(sym_file) && !assembler.map.empty()) {
if (FILE *f = fopen(sym_file, "w")) {
bool wasLocal = false;
for (MapSymbolArray::iterator i = assembler.map.begin(); i!=assembler.map.end(); ++i) {
unsigned int value = (unsigned int)i->value;
int section = i->section;
while (section >= 0 && section < (int)assembler.allSections.size()) {
if (assembler.allSections[section].IsMergedSection()) {
value += assembler.allSections[section].merged_offset;
section = assembler.allSections[section].merged_section;
} else {
value += assembler.allSections[section].start_address;
break;
}
}
fprintf(f, "%s.label " STRREF_FMT " = $%04x", wasLocal==i->local ? "\n" :
(i->local ? " {\n" : "\n}\n"), STRREF_ARG(i->name), value);
wasLocal = i->local;
}
fputs(wasLocal ? "\n}\n" : "\n", f);
fclose(f);
}
}
// export vice label file
if (vs_file && !srcname.same_str(vs_file) && !assembler.map.empty()) {
if (FILE *f = fopen(vs_file, "w")) {
for (MapSymbolArray::iterator i = assembler.map.begin(); i!=assembler.map.end(); ++i) {
unsigned int value = (unsigned int)i->value;
int section = i->section;
while (section >= 0 && section < (int)assembler.allSections.size()) {
if (assembler.allSections[section].IsMergedSection()) {
value += assembler.allSections[section].merged_offset;
section = assembler.allSections[section].merged_section;
} else {
value += assembler.allSections[section].start_address;
break;
}
}
if (i->name.same_str("debugbreak"))
fprintf(f, "break $%04x\n", value);
else
fprintf(f, "al $%04x %s" STRREF_FMT "\n", value, i->name[0]=='.' ? "" : ".",
STRREF_ARG(i->name));
}
fclose(f);
}
}
}
// free some memory
assembler.Cleanup();
}
}
return return_value;
}