mirror of
https://github.com/ksherlock/x65.git
synced 2024-12-27 13:29:18 +00:00
3a33dc7056
fix merlin ddb
8212 lines
309 KiB
C++
8212 lines
309 KiB
C++
//
|
|
// x65.cpp
|
|
//
|
|
//
|
|
// Created by Carl-Henrik Skårstedt on 9/23/15.
|
|
//
|
|
//
|
|
// A "simple" 6502/65C02/65816 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>
|
|
#include <inttypes.h>
|
|
#include <assert.h>
|
|
|
|
#ifndef _WIN32
|
|
#define _strdup strdup
|
|
#endif
|
|
|
|
// Command line arguments
|
|
static const strref cmdarg_listing("lst"); // -lst / -lst=(file.lst) : generate disassembly text from result(file or stdout)
|
|
static const strref cmdarg_tass_listing("tsl"); // -tsl=(file) : generate listing file in TASS style
|
|
static const strref cmdarg_tass_labels("tl"); // -tl=(file) : generate labels in TASS style
|
|
static const strref cmdarg_allinstr("opcodes"); // -opcodes / -opcodes=(file.s) : dump all available opcodes(file or stdout)
|
|
static const strref cmdarg_endmacro("endm"); // -endm : macros end with endm or endmacro instead of scoped('{' - '}')
|
|
static const strref cmdarg_cpu("cpu"); // declare CPU type, use with argument: -cpu=6502/65c02/65c02wdc/65816
|
|
static const strref cmdarg_acc("acc"); // [65816] -acc=8/16: set the accumulator mode for 65816 at start, default is 8 bits
|
|
static const strref cmdarg_xy("xy"); // [65816] -xy=8/16: set the index register mode for 65816 at start, default is 8 bits
|
|
static const strref cmdarg_org("org"); // -org = $2000 or - org = 4096: force fixed address code at address
|
|
static const strref cmdarg_kickasm("kickasm"); // -kickasm: use Kick Assembler syntax
|
|
static const strref cmdarg_merlin("merlin"); // -merlin: use Merlin syntax
|
|
static const strref cmdarg_c64("c64"); // -c64 : Include load address(default)
|
|
static const strref cmdarg_a2b("a2b"); // -a2b : Apple II Dos 3.3 Binary
|
|
static const strref cmdarg_bin("bin"); // -bin : Produce raw binary\n"
|
|
static const strref cmdarg_a2p("a2p"); // -a2p : Apple II ProDos Binary
|
|
static const strref cmdarg_a2o("a2o"); // -a2o : Apple II GS OS executable (relocatable)
|
|
static const strref cmdarg_mrg("mrg"); // -mrg : Force merge all sections (use with -a2o)
|
|
static const strref cmdarg_sect("sect"); // -sect: display sections loaded and built
|
|
static const strref cmdarg_sym("sym"); // -sym (file.sym) : generate symbol file
|
|
static const strref cmdarg_obj("obj"); // -obj (file.x65) : generate object file for later linking
|
|
static const strref cmdarg_vice("vice"); // -vice (file.vs) : export a vice symbol file
|
|
static const strref cmdarg_xrefimp("xrefimp"); // -xrefimp : import directive means xref, not include/incbin
|
|
static const strref cmdarg_references("refs"); // -refs: show label dependencies before linking
|
|
|
|
// 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_BYTES 255
|
|
|
|
// 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_KICKASM,
|
|
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_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_PULL_WITHOUT_PUSH,
|
|
ERROR_USER,
|
|
|
|
ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution
|
|
|
|
ERROR_BRANCH_OUT_OF_RANGE,
|
|
ERROR_INCOMPLETE_FUNCTION,
|
|
ERROR_FUNCTION_DID_NOT_RESOLVE,
|
|
ERROR_EXPRESSION_RECURSION,
|
|
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_MACRO_ARGUMENT,
|
|
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", // STATUS_OK, // everything is fine
|
|
"relative section", // STATUS_RELATIVE_SECTION, // value is relative to a single section
|
|
"not ready", // STATUS_NOT_READY, // label could not be evaluated at this time
|
|
"XREF dependent result", // STATUS_XREF_DEPENDENT, // evaluated but relied on an XREF label to do so
|
|
"name is not a struct", // STATUS_NOT_STRUCT, // return is not a struct.
|
|
"Exporting binary without code or data section", // STATUS_EXPORT_NO_CODE_OR_DATA_SECTION,
|
|
"Undefined code", // ERROR_UNDEFINED_CODE = FIRST_ERROR,
|
|
"Unexpected character in expression", // ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION,
|
|
"Too many values in expression", // ERROR_TOO_MANY_VALUES_IN_EXPRESSION,
|
|
"Too many operators in expression", // ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION,
|
|
"Unbalanced right parenthesis in expression", // ERROR_UNBALANCED_RIGHT_PARENTHESIS,
|
|
"Expression operation", // ERROR_EXPRESSION_OPERATION,
|
|
"Expression missing values", // ERROR_EXPRESSION_MISSING_VALUES,
|
|
"Instruction can not be zero page", // ERROR_INSTRUCTION_NOT_ZP,
|
|
"Invalid addressing mode for instruction", // ERROR_INVALID_ADDRESSING_MODE,
|
|
"Internal label organization mishap", // ERROR_LABEL_MISPLACED_INTERNAL,
|
|
"Bad addressing mode", // ERROR_BAD_ADDRESSING_MODE,
|
|
"Unexpected character in addressing mode", // ERROR_UNEXPECTED_CHARACTER_IN_ADDRESSING_MODE,
|
|
"Unexpected label assignment format", // ERROR_UNEXPECTED_LABEL_ASSIGMENT_FORMAT,
|
|
"Changing value of label that is constant", // ERROR_MODIFYING_CONST_LABEL,
|
|
"Out of labels in pool", // ERROR_OUT_OF_LABELS_IN_POOL,
|
|
"Internal label pool release confusion", // ERROR_INTERNAL_LABEL_POOL_ERROR,
|
|
"Label pool range evaluation failed", // ERROR_POOL_RANGE_EXPRESSION_EVAL,
|
|
"Label pool was redeclared within its scope", // ERROR_LABEL_POOL_REDECLARATION,
|
|
"Pool label already defined", // ERROR_POOL_LABEL_ALREADY_DEFINED,
|
|
"Struct already defined", // ERROR_STRUCT_ALREADY_DEFINED,
|
|
"Referenced struct not found", // ERROR_REFERENCED_STRUCT_NOT_FOUND,
|
|
"Declare constant type not recognized (dc.?)", // ERROR_BAD_TYPE_FOR_DECLARE_CONSTANT,
|
|
"rept count expression could not be evaluated", // ERROR_REPT_COUNT_EXPRESSION,
|
|
"hex must be followed by an even number of hex numbers", // ERROR_HEX_WITH_ODD_NIBBLE_COUNT,
|
|
"DS directive failed to evaluate immediately", // ERROR_DS_MUST_EVALUATE_IMMEDIATELY,
|
|
"File is not a valid x65 object file", // ERROR_NOT_AN_X65_OBJECT_FILE,
|
|
"Failed to read include file", // ERROR_COULD_NOT_INCLUDE_FILE,
|
|
"Using symbol PULL without first using a PUSH", // ERROR_PULL_WITHOUT_PUSH
|
|
"User invoked error", // ERROR_USER,
|
|
|
|
"Errors after this point will stop execution", // ERROR_STOP_PROCESSING_ON_HIGHER, // errors greater than this will stop execution
|
|
|
|
"Branch is out of range", // ERROR_BRANCH_OUT_OF_RANGE,
|
|
"Function declaration is missing name or expression", // ERROR_INCOMPLETE_FUNCTION,
|
|
"Function could not resolve the expression", // ERROR_FUNCTION_DID_NOT_RESOLVE
|
|
"Expression evaluateion recursion too deep", // ERROR_EXPRESSION_RECURSION
|
|
"Target address must evaluate immediately for this operation", // ERROR_TARGET_ADDRESS_MUST_EVALUATE_IMMEDIATELY,
|
|
"Scoping is too deep", // ERROR_TOO_DEEP_SCOPE,
|
|
"Unbalanced scope closure", // ERROR_UNBALANCED_SCOPE_CLOSURE,
|
|
"Unexpected macro formatting", // ERROR_BAD_MACRO_FORMAT,
|
|
"Align must evaluate immediately", // ERROR_ALIGN_MUST_EVALUATE_IMMEDIATELY,
|
|
"Out of memory for macro expansion", // ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION,
|
|
"Problem with macro argument", // ERROR_MACRO_ARGUMENT,
|
|
"Conditional could not be resolved", // ERROR_CONDITION_COULD_NOT_BE_RESOLVED,
|
|
"#endif encountered outside conditional block", // ERROR_ENDIF_WITHOUT_CONDITION,
|
|
"#else or #elif outside conditional block", // ERROR_ELSE_WITHOUT_IF,
|
|
"Struct can not be assembled as is", // ERROR_STRUCT_CANT_BE_ASSEMBLED,
|
|
"Enum can not be assembled as is", // ERROR_ENUM_CANT_BE_ASSEMBLED,
|
|
"Conditional assembly (#if/#ifdef) was not terminated in file or macro", // ERROR_UNTERMINATED_CONDITION,
|
|
"rept is missing a scope ('{ ... }')", // ERROR_REPT_MISSING_SCOPE,
|
|
"Link can only be used in a fixed address section", // ERROR_LINKER_MUST_BE_IN_FIXED_ADDRESS_SECTION,
|
|
"Link can not be used in dummy sections", // ERROR_LINKER_CANT_LINK_TO_DUMMY_SECTION,
|
|
"Can not process this line", // ERROR_UNABLE_TO_PROCESS,
|
|
"Unexpected target offset for reloc or late evaluation", // ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE,
|
|
"CPU is not supported", // ERROR_CPU_NOT_SUPPORTED,
|
|
"Can't append sections", // ERROR_CANT_APPEND_SECTION_TO_TARGET,
|
|
"Zero page / Direct page section out of range", // ERROR_ZEROPAGE_SECTION_OUT_OF_RANGE,
|
|
"Attempting to assign an address to a non-existent section", // ERROR_NOT_A_SECTION,
|
|
"Attempting to assign an address to a fixed address section", // ERROR_CANT_REASSIGN_FIXED_SECTION,
|
|
"Can not link a zero page section with a non-zp section", // ERROR_CANT_LINK_ZP_AND_NON_ZP,
|
|
"Out of memory while building", // ERROR_OUT_OF_MEMORY,
|
|
"Can not write to file", // ERROR_CANT_WRITE_TO_FILE,
|
|
"Assembly aborted", // ERROR_ABORTED,
|
|
"Condition too deeply nested", // ERROR_CONDITION_TOO_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_MERGE, // MERGE: Merge named sections in order listed
|
|
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_FUNCTION, // FUNCTION: Declare a user defined function
|
|
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_IFNDEF, // #IFNDEF: Conditional assembly inverted from IFDEF
|
|
AD_IFCONST, // #IFCONST: Conditional assembly follows based on label being const
|
|
AD_IFBLANK, // #IFBLANK: Conditional assembly follows based on rest of line empty
|
|
AD_IFNBLANK, // #IFNBLANK: Conditional assembly follows based on rest of line not empty
|
|
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_SCOPE, // SCOPE: Begin ca65 style scope
|
|
AD_ENDSCOPE, // ENDSCOPR: End ca65 style scope
|
|
AD_PUSH, // PUSH: Push the value of a variable symbol on a stack
|
|
AD_PULL, // PULL: Pull the value of a variable symbol from its stack, must be pushed first
|
|
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
|
|
AD_DBL_BYTES, // DDB: MERLIN Store 2 bytes, big endian format.
|
|
AD_ERROR,
|
|
};
|
|
|
|
// evaluation functions
|
|
enum EvalFuncs {
|
|
EF_DEFINED, // DEFINED(label) 1 if label is defined
|
|
EF_REFERENCED, // REFERENCED(label) 1 if label has been referenced in this file
|
|
EF_BLANK, // BLANK() 1 if the contents within the parenthesis is empty
|
|
EF_CONST, // CONST(label) 1 if label is a const label
|
|
EF_SIZEOF, // SIZEOF(struct) returns size of structs
|
|
EF_SIN, // SIN(index, period, amplitude)
|
|
};
|
|
|
|
// 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_NOT, // u, ~
|
|
EVOP_NEG, // v, negate value
|
|
EVOP_STP, // w, Unexpected input, should stop and evaluate what we have
|
|
EVOP_NRY, // x, Not ready yet
|
|
EVOP_XRF, // y, value from XREF label
|
|
EVOP_EXP, // z, sub expression
|
|
EVOP_ERR, // z+1, Error
|
|
};
|
|
|
|
// Opcode encoding
|
|
typedef struct sOPLookup {
|
|
uint32_t op_hash;
|
|
uint8_t index; // ground index
|
|
uint8_t 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_BRK = AMM_NON | AMM_IMM,
|
|
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_ZP_Y_REL,
|
|
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 | AMM_ZP,
|
|
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;
|
|
uint32_t modes;
|
|
uint8_t 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_BRK, { 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
|
|
};
|
|
|
|
uint8_t 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, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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]);
|
|
|
|
uint8_t 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
|
|
uint8_t 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 uint8_t *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 macro_arg_bookend("!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 strref pool_subpool("pool");
|
|
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" // uninitialized 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 sDirectiveName {
|
|
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
|
|
{ "MERGE", AD_MERGE },
|
|
{ "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 },
|
|
{ "FUNCTION", AD_FUNCTION },
|
|
{ "UNDEF", AD_UNDEF },
|
|
{ "INCSYM", AD_INCSYM },
|
|
{ "LABPOOL", AD_LABPOOL },
|
|
{ "POOL", AD_LABPOOL },
|
|
{ "IF", AD_IF },
|
|
{ "IFDEF", AD_IFDEF },
|
|
{ "IFNDEF", AD_IFNDEF },
|
|
{ "IFCONST", AD_IFCONST },
|
|
{ "IFBLANK", AD_IFBLANK }, // #IFBLANK: Conditional assembly follows based on rest of line empty
|
|
{ "IFNBLANK", AD_IFNBLANK }, // #IFDEF: Conditional assembly follows based on rest of line not empty
|
|
{ "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
|
|
{ "RES", AD_DS }, // Reserve space
|
|
{ "SCOPE", AD_SCOPE }, // SCOPE: Begin ca65 style scope
|
|
{ "ENDSCOPE", AD_ENDSCOPE },// ENDSCOPR: End ca65 style scope
|
|
{ "PUSH", AD_PUSH },
|
|
{ "PULL", AD_PULL },
|
|
{ "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_DBL_BYTES }, // 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
|
|
};
|
|
|
|
struct EvalFuncNames {
|
|
const char* name;
|
|
EvalFuncs function;
|
|
};
|
|
|
|
EvalFuncNames aEvalFunctions[] = {
|
|
{ "DEFINED", EF_DEFINED }, // DEFINED(label) 1 if label is defined
|
|
{ "DEF", EF_DEFINED }, // DEFINED(label) 1 if label is defined
|
|
{ "REFERENCED", EF_REFERENCED }, // REFERENCED(label) 1 if label has been referenced in this file
|
|
{ "BLANK", EF_BLANK }, // BLANK() 1 if the contents within the parenthesis is empty
|
|
{ "CONST", EF_CONST }, // CONST(label) 1 if label is a const label
|
|
{ "SIZEOF", EF_SIZEOF}, // SIZEOF(struct) returns size of structs
|
|
{ "TRIGSIN", EF_SIN }, // TRIGSIN(index, period, amplitude)
|
|
};
|
|
|
|
static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]);
|
|
static const int nDirectiveNamesMerlin = sizeof(aDirectiveNamesMerlin) / sizeof(aDirectiveNamesMerlin[0]);
|
|
static const int nEvalFuncs = sizeof(aEvalFunctions) / sizeof(aEvalFunctions[0]);
|
|
|
|
// Binary search over an array of unsigned integers, may contain multiple instances of same key
|
|
uint32_t FindLabelIndex(uint32_t hash, uint32_t *table, uint32_t count)
|
|
{
|
|
uint32_t max = count;
|
|
uint32_t first = 0;
|
|
while (count!=first) {
|
|
int index = (first+count)/2;
|
|
uint32_t 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;
|
|
uint32_t _count;
|
|
uint32_t _capacity;
|
|
public:
|
|
pairArray() : keys(nullptr), values(nullptr), _count(0), _capacity(0) {}
|
|
void reserve(uint32_t 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(uint32_t 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(uint32_t pos, H key) {
|
|
if (insert(pos) && keys) {
|
|
keys[pos] = key;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void remove(uint32_t 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(uint32_t pos) { return keys[pos]; }
|
|
V* getValues() { return values; }
|
|
V& getValue(uint32_t pos) { return values[pos]; }
|
|
uint32_t count() const { return _count; }
|
|
uint32_t capacity() const { return _capacity; }
|
|
void clear() {
|
|
if (keys!=nullptr)
|
|
free(keys);
|
|
keys = nullptr;
|
|
if (values!=nullptr)
|
|
free(values);
|
|
values = nullptr;
|
|
_capacity = 0;
|
|
_count = 0;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
template< class KeyType, class ValueType, class CountType = size_t > struct HashTable {
|
|
CountType size, maxSteps, used;
|
|
KeyType* keys;
|
|
ValueType* values;
|
|
|
|
static CountType HashFunction(KeyType v) { return CountType(((v + (v >> 27) + (v << 29)) + 14695981039346656037UL) * 1099511628211UL); }
|
|
static CountType HashIndex(KeyType hash, CountType tableSize) { return hash & (tableSize - 1); }
|
|
static CountType GetNextIndex(KeyType hash, CountType tableSize) { return (hash + 1) & (tableSize - 1); }
|
|
static CountType KeyToIndex(KeyType key, CountType tableSize) { return HashIndex(HashFunction(key), tableSize); }
|
|
static CountType FindKeyIndex(KeyType hash, CountType hashTableSize, KeyType* hashKeys, CountType maxKeySteps) {
|
|
CountType index = KeyToIndex(hash, hashTableSize);
|
|
while (hashKeys) {
|
|
KeyType key = hashKeys[index];
|
|
if (!key || key == hash) { return index; }
|
|
index = GetNextIndex(index, hashTableSize);
|
|
if (!maxKeySteps--) { break; }
|
|
}
|
|
return index;
|
|
}
|
|
|
|
CountType KeyToIndex(KeyType key) { return KeyToIndex(key, size); }
|
|
|
|
CountType InsertKey(KeyType key, CountType index) {
|
|
const KeyType* hashKeys = keys;
|
|
CountType currSize = size;
|
|
CountType insertSteps = 0;
|
|
while (KeyType k = hashKeys[index]) {
|
|
if (k == key) { return index; } // key already exists
|
|
CountType kfirst = KeyToIndex(k, currSize);
|
|
CountType ksteps = kfirst > index ? (currSize + index - kfirst) : (index - kfirst);
|
|
if (insertSteps > ksteps) { return index; }
|
|
index = GetNextIndex(index, size);
|
|
++insertSteps;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
CountType FindKeyIndex(KeyType hash) const { return FindKeyIndex(hash, size, keys, maxSteps); }
|
|
|
|
CountType Steps(KeyType hash) {
|
|
CountType slot = KeyToIndex(hash, size);
|
|
CountType numSteps = 0;
|
|
while (keys[slot] && keys[slot] != hash) {
|
|
++numSteps;
|
|
slot = GetNextIndex(slot, size);
|
|
}
|
|
return numSteps;
|
|
}
|
|
|
|
void UpdateSteps(CountType first, CountType slot) {
|
|
CountType steps = slot > first ? (slot - first) : (size + slot - first);
|
|
if (steps > maxSteps) { maxSteps = steps; }
|
|
}
|
|
|
|
ValueType* InsertFitted(KeyType key) {
|
|
assert(key); // key may not be 0
|
|
CountType first = KeyToIndex(key);
|
|
CountType slot = InsertKey(key, first);
|
|
UpdateSteps(first, slot);
|
|
if (keys[slot]) {
|
|
if (keys[slot] == key) { return &values[slot]; } else {
|
|
KeyType prvKey = keys[slot];
|
|
ValueType prev_value = values[slot];
|
|
keys[slot] = key;
|
|
for (;; ) {
|
|
CountType prev_first = KeyToIndex(prvKey);
|
|
CountType slotRH = InsertKey(prvKey, prev_first);
|
|
UpdateSteps(prev_first, slotRH);
|
|
if (keys[slotRH] && keys[slotRH] != prvKey) {
|
|
KeyType tmpKey = keys[slotRH];
|
|
keys[slotRH] = prvKey;
|
|
prvKey = tmpKey;
|
|
ValueType temp_value = values[slotRH];
|
|
values[slotRH] = prev_value;
|
|
prev_value = temp_value;
|
|
} else {
|
|
keys[slotRH] = prvKey;
|
|
values[slotRH] = prev_value;
|
|
++used;
|
|
return &values[slot];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
keys[slot] = key;
|
|
++used;
|
|
return &values[slot];
|
|
}
|
|
|
|
HashTable() { Reset(); }
|
|
|
|
void Reset() {
|
|
used = 0;
|
|
size = 0;
|
|
maxSteps = 0;
|
|
keys = nullptr;
|
|
values = nullptr;
|
|
}
|
|
|
|
~HashTable() { Clear(); }
|
|
|
|
void Clear() {
|
|
if (values) {
|
|
for (CountType i = 0, n = size; i < n; ++i) {
|
|
values[i].~ValueType();
|
|
}
|
|
free(values);
|
|
}
|
|
if (keys) { free(keys); }
|
|
Reset();
|
|
}
|
|
|
|
CountType GetUsed() const { return used; }
|
|
bool TableMax() const { return used && (used << 4) >= (size * 13); }
|
|
|
|
void Grow() {
|
|
KeyType *prevKeys = keys;
|
|
ValueType *prevValues = values;
|
|
CountType prevSize = size, newSize = prevSize ? (prevSize << 1) : 64;
|
|
size = newSize;
|
|
keys = (KeyType*)calloc(1, newSize * sizeof(KeyType));
|
|
values = (ValueType*)calloc(1, newSize * sizeof(ValueType));
|
|
maxSteps = 0;
|
|
for (CountType i = 0; i < newSize; ++i) { new (values + i) ValueType; }
|
|
if (used) {
|
|
used = 0;
|
|
for (CountType i = 0; i < prevSize; i++) {
|
|
if (KeyType key = prevKeys[i]) { *InsertFitted(key) = prevValues[i]; }
|
|
}
|
|
}
|
|
if (prevKeys) { free(prevKeys); }
|
|
if (prevValues) {
|
|
for (CountType i = 0; i != prevSize; ++i) { prevValues[i].~ValueType(); }
|
|
free(prevValues);
|
|
}
|
|
}
|
|
|
|
ValueType* InsertKey(KeyType key)
|
|
{
|
|
if (!size || TableMax()) { Grow(); }
|
|
return InsertFitted(key);
|
|
}
|
|
|
|
ValueType* InsertKeyValue(KeyType key, ValueType& value)
|
|
{
|
|
ValueType* value_ptr = InsertKey(key);
|
|
*value_ptr = value;
|
|
return value_ptr;
|
|
}
|
|
|
|
bool KeyExists(KeyType key)
|
|
{
|
|
return size && key && keys[FindKeyIndex(key)] == key;
|
|
}
|
|
|
|
ValueType* GetValue(KeyType key)
|
|
{
|
|
if (size && key) {
|
|
CountType slot = FindKeyIndex(key);
|
|
if (keys[slot] == key) {
|
|
return &values[slot];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
|
|
// 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?
|
|
int8_t bytes; // number of bytes to write
|
|
int8_t 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, int8_t num_bytes, int8_t 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;
|
|
|
|
// Source level debugging info that can be saved into linkable object files, this is close to ListLine so possibly combinable.
|
|
// this belongs in each section so it can be saved with that into the x65 files or generated if linked
|
|
struct SourceDebugEntry {
|
|
int source_file_index; // index into Assembler::source_file vector
|
|
int address; // local address in section
|
|
int size;
|
|
int source_file_offset; // can be converted into line/column while linking
|
|
};
|
|
typedef std::vector<struct SLDEntry> SourceDebug;
|
|
|
|
|
|
enum SectionType : int8_t { // 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
|
|
};
|
|
|
|
// String data
|
|
typedef struct sStringSymbols {
|
|
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);
|
|
|
|
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;
|
|
|
|
// 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_at; // merged into a section at this offset
|
|
int merged_into; // -1 if not merged otherwise section merged into
|
|
int merged_size; // how many bytes were merged in
|
|
|
|
// data output
|
|
uint8_t *output; // memory for this section
|
|
uint8_t *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
|
|
SourceDebug *pSrcDbg; // if source level debugging info generated
|
|
|
|
// 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_at = -1; merged_into = -1; merged_size = 0;
|
|
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 type != ST_REMOVED && 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 uint8_t *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 false; }
|
|
void AddReloc(int base, int offset, int section, int8_t bytes, int8_t 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(uint32_t addSize);
|
|
void AddByte(int b);
|
|
void AddWord(int w);
|
|
void AddTriple(int l);
|
|
void AddBin(const uint8_t *p, int size);
|
|
void AddText(strref line, strref text_prefix);
|
|
void AddIndexText(StringSymbol * strSym, strref text);
|
|
void SetByte(size_t offs, int b) { output[offs] = (uint8_t)b; }
|
|
void SetWord(size_t offs, int w) { output[offs] = (uint8_t)w; output[offs+1] = uint8_t(w>>8); }
|
|
void SetTriple(size_t offs, int w) { output[offs] = (uint8_t)w; output[offs+1] = uint8_t(w>>8); output[offs+2] = uint8_t(w>>16); }
|
|
void SetQuad(size_t offs, int w) { output[offs] = (uint8_t)w; output[offs+1] = uint8_t(w>>8); output[offs+2] = uint8_t(w>>16); output[offs+3] = uint8_t(w>>24); }
|
|
} Section;
|
|
|
|
// Symbol list entry (in order of parsing)
|
|
struct MapSymbol {
|
|
strref name; // string name
|
|
int value;
|
|
int16_t section;
|
|
bool local; // local variables
|
|
};
|
|
typedef std::vector<struct MapSymbol> MapSymbolArray;
|
|
|
|
// Data related to a label
|
|
typedef struct sLabel {
|
|
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
|
|
bool referenced; // this label has been found via GetLabel and can be assumed to be referenced for some purpose
|
|
} Label;
|
|
|
|
|
|
// If an expression can't be evaluated immediately, this is required
|
|
// to reconstruct the result when it can be.
|
|
typedef struct sLateEval {
|
|
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
|
|
LET_DBL_BYTE, // calculate a 16-bit, big endian number.
|
|
};
|
|
int target; // offset into output buffer
|
|
int address; // current pc
|
|
int scope; // scope pc
|
|
int scope_depth; // relevant for scope end
|
|
int16_t section; // which section to apply to.
|
|
int16_t rept; // value of rept
|
|
int file_ref; // -1 if current or xdef'd otherwise index of file for label
|
|
char* expression_mem; // if a temporary string was used for the expression
|
|
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 sMacro {
|
|
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 sLocalLabelRecord {
|
|
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 sLabelPool {
|
|
strref pool_name;
|
|
int16_t numRanges; // normally 1 range, support multiple for ease of use
|
|
int16_t depth; // Required for scope closure cleanup
|
|
uint32_t start;
|
|
uint32_t end;
|
|
uint32_t scopeUsed[MAX_SCOPE_DEPTH][2]; // last address assigned + scope depth
|
|
StatusCode Reserve(uint32_t numBytes, uint32_t &ret_addr, uint16_t scope);
|
|
void ExitScope(uint16_t scope);
|
|
} LabelPool;
|
|
|
|
// One member of a label struct
|
|
struct MemberOffset {
|
|
uint16_t offset;
|
|
uint32_t name_hash;
|
|
strref name;
|
|
strref sub_struct;
|
|
};
|
|
|
|
// Label struct
|
|
typedef struct sLabelStruct {
|
|
strref name;
|
|
uint16_t first_member;
|
|
uint16_t numMembers;
|
|
uint16_t size;
|
|
} LabelStruct;
|
|
|
|
// object file labels that are not xdef'd end up here
|
|
struct ExtLabels {
|
|
pairArray<uint32_t, 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
|
|
int recursion; // track recursion depth
|
|
StatusCode internalErr; // if an error occured during an internal stage of evaluation
|
|
EvalContext() : pc(0), scope_pc(0), scope_end_pc(0), scope_depth(0), relative_section(-1),
|
|
file_ref(-1), rept_cnt(0), recursion(0), internalErr(STATUS_OK) {}
|
|
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),
|
|
recursion(0), internalErr(STATUS_OK) {}
|
|
};
|
|
|
|
// Source context is current file (include file, etc.) or current macro.
|
|
typedef struct sSourceContext {
|
|
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
|
|
int16_t repeat; // how many times to repeat this code segment
|
|
int16_t repeat_total; // initial number of repeats for this code segment
|
|
int16_t conditional_ctx; // conditional depth at root of this 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; // stack of contexts
|
|
SourceContext *currContext; // current context
|
|
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 = (int16_t)rept;
|
|
context.repeat_total = (int16_t)rept;
|
|
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; }
|
|
bool empty() const { return stack.size() == 0; }
|
|
};
|
|
|
|
// Support for the PULL and PUSH directives
|
|
typedef union { int value; char* string; } ValueOrString;
|
|
typedef std::vector < ValueOrString > SymbolStack;
|
|
class SymbolStackTable : public HashTable< uint64_t, SymbolStack* > {
|
|
public:
|
|
void PushSymbol(Label* symbol);
|
|
StatusCode PullSymbol(Label* symbol);
|
|
void PushSymbol(StringSymbol* string);
|
|
StatusCode PullSymbol(StringSymbol* string);
|
|
~SymbolStackTable();
|
|
};
|
|
|
|
// user declared functions
|
|
struct UserFunction {
|
|
const char* name;
|
|
const char* params;
|
|
const char* expression;
|
|
};
|
|
class UserFunctionMap : public HashTable<uint64_t, UserFunction*> {
|
|
public:
|
|
UserFunction *Get(strref name);
|
|
StatusCode Add(strref name, strref params, strref expresion);
|
|
~UserFunctionMap();
|
|
};
|
|
|
|
// The state of the assembler
|
|
class Asm {
|
|
public:
|
|
pairArray<uint32_t, Label> labels;
|
|
pairArray<uint32_t, StringSymbol> strings;
|
|
pairArray<uint32_t, Macro> macros;
|
|
pairArray<uint32_t, LabelPool> labelPools;
|
|
pairArray<uint32_t, LabelStruct> labelStructs;
|
|
pairArray<uint32_t, strref> xdefs; // labels matching xdef names will be marked as external
|
|
|
|
std::vector<char*> source_files; // all source files encountered while assembling. referenced by source level debugging
|
|
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;
|
|
|
|
SymbolStackTable symbolStacks; // enable push/pull of symbols
|
|
UserFunctionMap userFunctions; // user defined expression functions
|
|
|
|
// 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
|
|
int8_t conditional_nesting[MAX_CONDITIONAL_DEPTH];
|
|
bool conditional_consumed[MAX_CONDITIONAL_DEPTH];
|
|
|
|
// Scope info
|
|
int scope_address[MAX_SCOPE_DEPTH];
|
|
int scope_depth;
|
|
int brace_depth; // scope depth defined only by braces, not files
|
|
|
|
strref export_base_name; // binary output name if available
|
|
strref last_label; // most recently defined label for Merlin macro
|
|
|
|
// ca65 style scope (for now treat global symbols as local symbols, no outside name lookup)
|
|
int directive_scope_depth;
|
|
|
|
// Eval relative result (only valid if EvalExpression returns STATUS_RELATIVE_SECTION)
|
|
int lastEvalSection;
|
|
int lastEvalValue;
|
|
int8_t lastEvalShift;
|
|
|
|
int8_t list_flags; // listing flags accumulating for each line
|
|
bool accumulator_16bit; // 65816 specific software dependent immediate mode
|
|
bool index_reg_16bit; // -"-
|
|
int8_t 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
|
|
bool import_means_xref;
|
|
|
|
// 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);
|
|
|
|
// Mimic TASS listing
|
|
bool ListTassStyle( 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(uint32_t 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
|
|
uint8_t* 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(const uint8_t *p, int size) { CurrSection().AddBin(p, size); }
|
|
|
|
void ShowReferences();
|
|
|
|
// 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);
|
|
|
|
// determine a value from a user function with given parameters
|
|
int EvalUserFunction(UserFunction* user, strref params, EvalContext& etx);
|
|
|
|
// Check if function is a valid function and if so evaluate the expression
|
|
bool EvalFunction(strref function, strref &expression, EvalContext& etx, int &value);
|
|
|
|
// Calculate a value based on an expression.
|
|
EvalOperator RPNToken_Merlin(strref &expression, const struct EvalContext &etx,
|
|
EvalOperator prev_op, int16_t §ion, int &value);
|
|
EvalOperator RPNToken(strref &expression, EvalContext &etx,
|
|
EvalOperator prev_op, int16_t §ion, int &value, strref &subexp);
|
|
StatusCode EvalExpression(strref expression, struct EvalContext &etx, int &result);
|
|
char* PartialEval( strref expression );
|
|
void SetEvalCtxDefaults( struct EvalContext &etx );
|
|
int ReptCnt() const;
|
|
|
|
// Access labels
|
|
Label * GetLabel(strref label, bool reference_check = false);
|
|
Label* GetLabel(strref label, int file_ref);
|
|
Label* AddLabel(uint32_t 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);
|
|
|
|
// 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);
|
|
StatusCode Directive_Macro(strref line);
|
|
StatusCode Directive_String(strref line);
|
|
StatusCode Directive_Function(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_MERGE(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, bool little_endian = true);
|
|
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, uint32_t &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, strref file = strref());
|
|
|
|
// 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);
|
|
|
|
// Syntax
|
|
bool Merlin() const { return syntax == SYNTAX_MERLIN; }
|
|
bool KickAsm() const { return syntax == SYNTAX_KICKASM; }
|
|
|
|
// 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); }
|
|
};
|
|
|
|
|
|
void SymbolStackTable::PushSymbol(Label* symbol)
|
|
{
|
|
uint64_t key = symbol->label_name.fnv1a_64(symbol->pool_name.fnv1a_64());
|
|
SymbolStack** ppStack = InsertKey(key); // ppStack will exist but contains a pointer that may not exist
|
|
if (!*ppStack) { *ppStack = new SymbolStack; }
|
|
ValueOrString val;
|
|
val.value = symbol->value;
|
|
(*ppStack)->push_back(val);
|
|
}
|
|
|
|
StatusCode SymbolStackTable::PullSymbol(Label* symbol)
|
|
{
|
|
uint64_t key = symbol->label_name.fnv1a_64(symbol->pool_name.fnv1a_64());
|
|
SymbolStack** ppStack = GetValue(key);
|
|
if (!ppStack || !(*ppStack)->size()) { return ERROR_PULL_WITHOUT_PUSH; }
|
|
symbol->value = (**ppStack)[(*ppStack)->size() - 1].value;
|
|
(*ppStack)->pop_back();
|
|
return STATUS_OK;
|
|
}
|
|
|
|
void SymbolStackTable::PushSymbol(StringSymbol* string)
|
|
{
|
|
uint64_t key = string->string_name.fnv1a_64();
|
|
SymbolStack** ppStack = InsertKey(key); // ppStack will exist but contains a pointer that may not exist
|
|
if (!*ppStack) { *ppStack = new SymbolStack; }
|
|
ValueOrString val;
|
|
val.string = nullptr;
|
|
if (string->string_value) {
|
|
val.string = (char*)malloc(string->string_value.get_len() + 1);
|
|
memcpy(val.string, string->string_value.get(), string->string_value.get_len());
|
|
val.string[string->string_value.get_len()] = 0;
|
|
}
|
|
(*ppStack)->push_back(val);
|
|
}
|
|
|
|
StatusCode SymbolStackTable::PullSymbol(StringSymbol* string)
|
|
{
|
|
uint64_t key = string->string_name.fnv1a_64();
|
|
SymbolStack** ppStack = GetValue(key);
|
|
if (!ppStack || !(*ppStack)->size()) { return ERROR_PULL_WITHOUT_PUSH; }
|
|
char* str = (**ppStack)[(*ppStack)->size() - 1].string;
|
|
if (!str && string->string_value) {
|
|
free(string->string_value.charstr());
|
|
string->string_value.invalidate();
|
|
} else {
|
|
if (string->string_value.empty() || string->string_value.cap() < (strlen(str) + 1)) {
|
|
if (string->string_value.charstr()) { free(string->string_value.charstr()); }
|
|
string->string_value.set_overlay((char*)malloc(strlen(str) + 1), (strl_t)strlen(str) + 1);
|
|
}
|
|
string->string_value.copy(str);
|
|
free(str);
|
|
}
|
|
(*ppStack)->pop_back();
|
|
return STATUS_OK;
|
|
}
|
|
|
|
SymbolStackTable::~SymbolStackTable()
|
|
{
|
|
for (size_t i = 0; i < size; ++i) {
|
|
if (keys[i] && values[i]) {
|
|
delete values[i];
|
|
values[i] = nullptr;
|
|
keys[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
UserFunction* UserFunctionMap::Get(strref name)
|
|
{
|
|
UserFunction** ret = GetValue(name.fnv1a_64());
|
|
if (ret) { return *ret; }
|
|
return nullptr;
|
|
}
|
|
|
|
StatusCode UserFunctionMap::Add(strref name, strref params, strref expresion)
|
|
{
|
|
if (!name || !expresion) { return ERROR_INCOMPLETE_FUNCTION; }
|
|
strl_t stringlen = name.get_len() + 1 + (params ? (params.get_len() + 1) : 0) + expresion.get_len() + 1;
|
|
UserFunction *func = (UserFunction*)calloc(1, sizeof(UserFunction) + stringlen);
|
|
char* strings = (char*)(func + 1);
|
|
func->name = strings;
|
|
memcpy(strings, name.get(), name.get_len());
|
|
strings[name.get_len()] = 0;
|
|
strings += name.get_len() + 1;
|
|
if (params) {
|
|
func->params = strings;
|
|
memcpy(strings, params.get(), params.get_len());
|
|
strings[params.get_len()] = 0;
|
|
strings += params.get_len() + 1;
|
|
}
|
|
func->expression = strings;
|
|
memcpy(strings, expresion.get(), expresion.get_len());
|
|
|
|
if (UserFunction** existing = GetValue(name.fnv1a_64())) {
|
|
free(*existing);
|
|
*existing = func;
|
|
} else {
|
|
InsertKeyValue(name.fnv1a_64(), func);
|
|
}
|
|
return STATUS_OK;
|
|
}
|
|
|
|
UserFunctionMap::~UserFunctionMap()
|
|
{
|
|
for (size_t i = 0; i < size; ++i) {
|
|
if (keys[i] && values[i]) {
|
|
free(values[i]);
|
|
values[i] = nullptr;
|
|
keys[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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);
|
|
}
|
|
for( std::vector<LateEval>::iterator i = lateEval.begin(); i != lateEval.end(); ++i ) {
|
|
if( i->expression_mem ) { free( i->expression_mem ); i->expression_mem = nullptr; i->expression.clear(); }
|
|
}
|
|
map.clear();
|
|
labelPools.clear();
|
|
loadedData.clear();
|
|
labels.clear();
|
|
macros.clear();
|
|
allSections.clear();
|
|
lateEval.clear();
|
|
for (uint32_t 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;
|
|
brace_depth = 0;
|
|
conditional_depth = 0;
|
|
conditional_nesting[0] = 0;
|
|
conditional_consumed[0] = false;
|
|
directive_scope_depth = 0;
|
|
error_encountered = false;
|
|
list_assembly = false;
|
|
end_macro_directive = false;
|
|
import_means_xref = 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, 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 = (uint8_t)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 = (uint8_t)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 = (uint8_t)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 = (uint8_t)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, opcode_table,
|
|
opcode_count, aCPUs[CPU].aliases, 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);
|
|
// don't compile over zero page and stack frame (may be bad assumption)
|
|
if (address<0x200) {
|
|
newSection.SetDummySection(true); }
|
|
allSections.push_back(newSection);
|
|
current_section = &allSections[allSections.size()-1];
|
|
}
|
|
|
|
void Asm::SetSection(strref line) {
|
|
if (allSections.size()==allSections.capacity()) {
|
|
allSections.reserve(allSections.size()+16);
|
|
}
|
|
|
|
SectionType type = ST_UNDEFINED;
|
|
if (line.get_first() == '.') { // SEG.U etc.
|
|
++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 = (int)arg.ahextoui(); }
|
|
else if (arg.is_number()) { align = (int)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
|
|
uint8_t* 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;
|
|
|
|
// automatically merge sections with the same name and type if one is relative and other is fixed
|
|
for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) {
|
|
const Section §ion = allSections[section_id];
|
|
if (!section.IsMergedSection()&&!section.IsRelativeSection()) {
|
|
for (size_t section_merge_id = 0; section_merge_id!=allSections.size(); ++section_merge_id) {
|
|
const Section §ion_merge = allSections[section_merge_id];
|
|
if(!section_merge.IsMergedSection()&§ion_merge.IsRelativeSection() &&
|
|
section_merge.type == section.type && section.name.same_str_case(section_merge.name)) {
|
|
MergeSections((int)section_id, (int)section_merge_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// link any relocs to sections that are fixed
|
|
for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) {
|
|
const Section §ion = allSections[section_id];
|
|
if (section.address_assigned) {
|
|
LinkRelocs((int)section_id, -1, section.start_address);
|
|
}
|
|
}
|
|
|
|
// 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 || i->addr_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_block = start_block;
|
|
int sec = id;
|
|
while (sec >= 0) {
|
|
Section &s = allSections[sec];
|
|
addr_block += s.align_address <= 1 ? 0 :
|
|
(s.align_address - (addr_block % s.align_address)) % s.align_address;
|
|
addr_block += s.address - s.start_address;
|
|
sec = s.next_group;
|
|
}
|
|
if (addr_block <= 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) {
|
|
const Section §ion = allSections[section_id];
|
|
if (section.address_assigned) {
|
|
LinkRelocs((int)section_id, -1, section.start_address);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
uint8_t *output = (uint8_t*)calloc(1, end_address - start_address);
|
|
|
|
// copy over in order
|
|
for (std::vector<Section>::iterator i = allSections.begin(); i != allSections.end(); ++i) {
|
|
if (i->type == ST_REMOVED) { continue; }
|
|
if (((!append && !i->export_append) || append.same_str_case(i->export_append)) && i->type != ST_ZEROPAGE) {
|
|
if (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;
|
|
uint32_t 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() {
|
|
uint8_t 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<(int)min_addr) {
|
|
min_addr = (uint8_t)s->start_address;
|
|
} else if ((int)s->address>max_addr) {
|
|
max_addr = (uint8_t)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;
|
|
if (!has_assigned) { // no section assigned => fit together at end
|
|
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 = (int16_t)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;
|
|
// only finalize the target value if fixed address
|
|
if (section_new == -1 || allSections[section_new].address_assigned) {
|
|
uint8_t *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++ = (uint8_t)(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 >= 0 ? 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;
|
|
}
|
|
|
|
m.merged_size = m.address - m.start_address;
|
|
|
|
// 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 - s.start_address;
|
|
|
|
// append info for result output
|
|
m.merged_at = addr_start;
|
|
m.merged_into = section_id;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!s.IsRelativeSection()) { LinkLabelsToAddress(section_merge, -1, m.start_address); }
|
|
|
|
// go through all labels referencing merging section
|
|
for (uint32_t 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 = (int16_t)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 = (int16_t)section_id;
|
|
if (i->target>=0) { i->target += addr_start; }
|
|
i->address += addr_start;
|
|
if (i->scope>=0) { i->scope += addr_start; }
|
|
}
|
|
}
|
|
|
|
if (m.output) {
|
|
free(m.output);
|
|
m.output = nullptr;
|
|
m.curr = nullptr;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
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(uint32_t 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 (uint8_t *new_output = (uint8_t*)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++ = (uint8_t)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++ = (uint8_t)(w & 0xff);
|
|
*curr++ = (uint8_t)(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++ = (uint8_t)(l & 0xff);
|
|
*curr++ = (uint8_t)(l >> 8);
|
|
*curr++ = (uint8_t)(l >> 16);
|
|
}
|
|
}
|
|
address += 3;
|
|
}
|
|
// Add arbitrary length data to a section
|
|
void Section::AddBin(const uint8_t *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((uint32_t)line.get_len()) == STATUS_OK) {
|
|
if (!text_prefix || text_prefix.same_str("ascii")) {
|
|
AddBin((const uint8_t*)line.get(), (int)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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Section::AddIndexText(StringSymbol *strSym, strref text) {
|
|
if (CheckOutputCapacity((uint32_t)text.get_len())==STATUS_OK) {
|
|
const strref lookup = strSym->get();
|
|
while (text) {
|
|
char c = text.pop_first();
|
|
AddByte(lookup.find(c));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a relocation marker to a section
|
|
void Section::AddReloc(int base, int offset, int section, int8_t bytes, int8_t 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(uint32_t 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()
|
|
{
|
|
StatusCode error = CheckLateEval(strref(), CurrSection().GetPC());
|
|
if( error >= FIRST_ERROR ) { return error; }
|
|
error = FlushLocalLabels(scope_depth);
|
|
if (error>=FIRST_ERROR) { return error; }
|
|
--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-1)) { 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 = (int16_t)conditional_depth;
|
|
if (scope_depth>=(MAX_SCOPE_DEPTH-1)) {
|
|
return ERROR_TOO_DEEP_SCOPE;
|
|
} else {
|
|
scope_address[++scope_depth] = CurrSection().GetPC();
|
|
}
|
|
return STATUS_OK;
|
|
}
|
|
|
|
StatusCode Asm::PopContext() {
|
|
if (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 (Merlin()) {
|
|
if (Label *pLastLabel = GetLabel(last_label)) {
|
|
labels.remove((uint32_t)(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);
|
|
while (macro.get_first() == ' ' || macro.get_first() == '\t') { ++macro; }
|
|
strref left_line = macro.get_line();
|
|
if (left_line.get_first() == ';' || left_line.has_prefix("//")) { left_line.clear(); }
|
|
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;
|
|
}
|
|
}
|
|
uint32_t hash = name.fnv1a();
|
|
uint32_t 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 (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]) || macro[f - 1] == '.') {
|
|
if (f && macro[f - 1] == '.') { --f; }
|
|
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||Merlin()) {
|
|
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 (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 = (int)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) {
|
|
strref match("]*{0-9}");
|
|
strl_t pos = 0;
|
|
while (strref tag_mac = macexp.find_wildcard(match, pos)) {
|
|
bool success = false;
|
|
strl_t offs = strl_t(tag_mac.get() - macexp.get());
|
|
if (!offs || !strref::is_valid_label(macexp[offs])) {
|
|
int t = (int)(tag_mac + 1).atoi();
|
|
strref args = arg;
|
|
if (t > 0) {
|
|
for (int skip = 1; skip < t; skip++)
|
|
args.split_token_trim(';');
|
|
strref a = args.split_token_trim(';');
|
|
macexp.exchange(offs, tag_mac.get_len(), a);
|
|
pos += a.get_len();
|
|
success = true;
|
|
}
|
|
}
|
|
if (!success) { return ERROR_MACRO_ARGUMENT; }
|
|
}
|
|
}
|
|
PushContext(m.source_name, macexp.get_strref(), macexp.get_strref());
|
|
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 = (int)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, macro_arg_bookend);
|
|
}
|
|
PushContext(m.source_name, macexp.get_strref(), macexp.get_strref());
|
|
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) {
|
|
uint32_t hash = name.fnv1a();
|
|
uint32_t 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 = (uint16_t)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 member_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 = (uint16_t)value;
|
|
member.name = member_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) {
|
|
uint32_t hash = name.fnv1a();
|
|
uint32_t 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 = (uint16_t)structMembers.size();
|
|
|
|
uint32_t byte_hash = struct_byte.fnv1a();
|
|
uint32_t word_hash = struct_word.fnv1a();
|
|
uint16_t size = 0;
|
|
uint16_t member_count = 0;
|
|
|
|
while (strref line = declaration.line()) {
|
|
line.trim_whitespace();
|
|
strref type = line.split_label();
|
|
if (!type) { continue; }
|
|
line.skip_whitespace();
|
|
uint32_t type_hash = type.fnv1a();
|
|
uint16_t 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 {
|
|
uint32_t 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++;
|
|
}
|
|
{ // add a trailing member of 0 bytes to access the size of the structure
|
|
struct MemberOffset bytes_member;
|
|
bytes_member.offset = size;
|
|
bytes_member.name = "bytes";
|
|
bytes_member.name_hash = bytes_member.name.fnv1a();
|
|
bytes_member.sub_struct = strref();
|
|
structMembers.push_back(bytes_member);
|
|
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;
|
|
uint16_t offset = 0;
|
|
while (strref struct_seg = name.split_token('.')) {
|
|
strref sub_struct = struct_seg;
|
|
uint32_t 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) {
|
|
uint32_t hash = sub_struct.fnv1a();
|
|
uint32_t 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;
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
// USER FUNCTION EVAL
|
|
//
|
|
//
|
|
|
|
int Asm::EvalUserFunction(UserFunction* user, strref params, EvalContext& etx)
|
|
{
|
|
strref expression(user->expression);
|
|
strref orig_param(user->params);
|
|
strref paraiter = orig_param;
|
|
strref in_params = params;
|
|
int newSize = expression.get_len();
|
|
while (strref param = paraiter.split_token(',')) {
|
|
strref replace = in_params.split_token(',');
|
|
param.trim_whitespace();
|
|
replace.trim_whitespace();
|
|
|
|
if (param.get_len() < replace.get_len()) {
|
|
int count = expression.substr_count(param);
|
|
newSize += count * (replace.get_len() - param.get_len());
|
|
}
|
|
}
|
|
|
|
char* subst = (char*)malloc(newSize);
|
|
strovl subststr(subst, newSize);
|
|
subststr.copy(expression);
|
|
while (strref param = orig_param.split_token(',')) {
|
|
strref replace = params.split_token(',');
|
|
param.trim_whitespace();
|
|
replace.trim_whitespace();
|
|
|
|
subststr.replace_bookend(param, replace, macro_arg_bookend);
|
|
}
|
|
|
|
int value = 0;
|
|
etx.internalErr = EvalExpression(subststr.get_strref(), etx, value);
|
|
if (etx.internalErr != STATUS_OK && etx.internalErr < FIRST_ERROR) {
|
|
etx.internalErr = ERROR_FUNCTION_DID_NOT_RESOLVE;
|
|
}
|
|
|
|
free(subst);
|
|
|
|
return value;
|
|
}
|
|
|
|
//
|
|
//
|
|
// EVAL FUNCTIONS
|
|
//
|
|
//
|
|
|
|
bool Asm::EvalFunction(strref function, strref& expression, EvalContext& etx, int &value)
|
|
{
|
|
// all eval functions take a parenthesis with arguments
|
|
if (expression.get_first() != '(') { return false; }
|
|
|
|
strref expRet = expression;
|
|
strref params = expRet.scoped_block_comment_skip();
|
|
params.trim_whitespace();
|
|
if (function.get_first() == '.') { ++function; }
|
|
|
|
// look up user defined function
|
|
if (UserFunction* user = userFunctions.Get(function)) {
|
|
expression = expRet;
|
|
value = EvalUserFunction(user, params, etx);
|
|
return true;
|
|
}
|
|
|
|
// built-in function
|
|
for (int i = 0; i < nEvalFuncs; ++i) {
|
|
if (function.same_str(aEvalFunctions[i].name)) {
|
|
switch (aEvalFunctions[i].function) {
|
|
case EF_DEFINED:
|
|
expression = expRet;
|
|
value = GetLabel(params, true) != nullptr ? 1 : 0;
|
|
return true;
|
|
case EF_REFERENCED:
|
|
expression = expRet;
|
|
if (Label* label = GetLabel(params, true)) { value = label->referenced; return true; }
|
|
return true;
|
|
case EF_BLANK:
|
|
expression = expRet;
|
|
if (params.get_first() == '{') { params = params.scoped_block_comment_skip(); }
|
|
params.trim_whitespace();
|
|
value = params.is_empty();
|
|
return true;
|
|
case EF_CONST:
|
|
expression = expRet;
|
|
if (Label* label = GetLabel(params, true)) {
|
|
value = label->constant ? 1 : 0;
|
|
}
|
|
return true;
|
|
case EF_SIZEOF:
|
|
{
|
|
expression = expRet;
|
|
uint32_t hash = params.fnv1a();
|
|
uint32_t index = FindLabelIndex(hash, labelStructs.getKeys(), labelStructs.count());
|
|
value = 0;
|
|
while (index < labelStructs.count() && labelStructs.getKey(index) == hash) {
|
|
if (params.same_str_case(labelStructs.getValue(index).name)) {
|
|
value = (labelStructs.getValues() + index)->size;
|
|
break;
|
|
}
|
|
++index;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case EF_SIN:
|
|
expression = expRet;
|
|
value = 0; // TODO: implement trigsin
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
// 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 = contextStack.empty() ? 0 : 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, int16_t §ion, int &value) {
|
|
char c = expression.get_first();
|
|
switch (c) {
|
|
case '$': ++expression; value = (int)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 = int16_t(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 = (int)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 = int16_t(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 '?': return EVOP_STP;
|
|
}
|
|
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 = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL;
|
|
} else if (expression.match_chars_str("0-9", "!a-zA-Z_")) {
|
|
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 = 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 = int16_t(pLabel->section); return EVOP_VAL;
|
|
}
|
|
return EVOP_ERR;
|
|
}
|
|
|
|
// Get a single token from most non-apple II assemblers
|
|
EvalOperator Asm::RPNToken(strref &exp, EvalContext &etx, EvalOperator prev_op, int16_t §ion, int &value, strref &subexp)
|
|
{
|
|
char c = exp.get_first();
|
|
switch (c) {
|
|
case '$': ++exp; value = (int)exp.ahextoui_skip(); return EVOP_VAL;
|
|
case '-': ++exp; return EVOP_SUB;
|
|
case '+': ++exp; return EVOP_ADD;
|
|
case '*': ++exp; // asterisk means both multiply and current PC, disambiguate!
|
|
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 = int16_t(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 = (int)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 = int16_t(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 '~': ++exp; return EVOP_NOT;
|
|
case '(': if (prev_op != EVOP_VAL) { ++exp; return EVOP_LPR; } return EVOP_STP;
|
|
case ')': ++exp; return EVOP_RPR;
|
|
case ',':
|
|
case '?': return EVOP_STP;
|
|
case '\'': if( exp[ 2 ] == '\'' ) { value = exp[ 1 ]; exp += 3; return EVOP_VAL; } return EVOP_STP;
|
|
}
|
|
// ! 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 = int16_t(CurrSection().IsRelativeSection() ? SectionId() : -1); return EVOP_VAL;
|
|
} else if (exp.match_chars_str("0-9", "!a-zA-Z_")) {
|
|
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 = 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) { if (EvalFunction(label, exp, etx, value)) { return EVOP_VAL; } }
|
|
if (!pLabel || !pLabel->evaluated) return EVOP_NRY; // this label could not be found (yet)
|
|
value = pLabel->value; section = int16_t(pLabel->section); return pLabel->reference ? EVOP_XRF : EVOP_VAL;
|
|
}
|
|
return EVOP_ERR;
|
|
}
|
|
|
|
//
|
|
// 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, 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)
|
|
int16_t section_ids[MAX_EVAL_SECTIONS]; // local index of each referenced section
|
|
int16_t section_val[MAX_EVAL_VALUES] = { 0 }; // each value can be assigned to one section, or -1 if fixed
|
|
int16_t num_sections = 0; // number of sections in section_ids (normally 0 or 1, can be up to MAX_EVAL_SECTIONS)
|
|
bool xrefd = false;
|
|
|
|
// don't allow too deep recursion of this function, this should be rare as it takes a user function or variadic macro to recurse.
|
|
if (etx.recursion > 3) { return ERROR_EXPRESSION_RECURSION; }
|
|
etx.recursion++; // increment recursion of EvalExpression body
|
|
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;
|
|
int16_t 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 (Merlin()) {
|
|
op = RPNToken_Merlin(expression, etx, prev_op, section, value);
|
|
} else {
|
|
op = RPNToken(expression, etx, prev_op, section, value, subexp);
|
|
}
|
|
if (op == EVOP_ERR) { etx.recursion--; return ERROR_UNEXPECTED_CHARACTER_IN_EXPRESSION; }
|
|
else if (op==EVOP_NRY) { etx.recursion--; return STATUS_NOT_READY; }
|
|
else if (op == EVOP_EXP) {
|
|
if (exp_sp>=MAX_EXPR_STACK) { etx.recursion--; 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 = (int16_t)s; }
|
|
}
|
|
if (index_section<0) {
|
|
if (num_sections<=MAX_EVAL_SECTIONS) {
|
|
section_ids[index_section = num_sections++] = section;
|
|
} else { etx.recursion--; 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++] = (char)op;
|
|
} else if (op == EVOP_LPR) {
|
|
op_stack[sp++] = (char)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) { etx.recursion--; 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 && prev_op==EVOP_SUB) {
|
|
sp--;
|
|
} else {
|
|
while (sp && !skip) {
|
|
EvalOperator p = (EvalOperator)op_stack[sp-1];
|
|
if (p==EVOP_LPR||op>p) { break; }
|
|
ops[numOps++] = (char)p;
|
|
sp--;
|
|
}
|
|
op_stack[sp++] = (char)op;
|
|
}
|
|
}
|
|
// check for out of bounds or unexpected input
|
|
if (numValues==MAX_EVAL_VALUES) { etx.recursion--; return ERROR_TOO_MANY_VALUES_IN_EXPRESSION; }
|
|
else if (numOps==MAX_EVAL_OPER||sp==MAX_EVAL_OPER) {
|
|
etx.recursion--;
|
|
return ERROR_TOO_MANY_OPERATORS_IN_EXPRESSION;
|
|
}
|
|
prev_op = op;
|
|
expression.skip_whitespace();
|
|
}
|
|
while (sp) {
|
|
sp--;
|
|
ops[numOps++] = op_stack[sp];
|
|
}
|
|
}
|
|
etx.recursion--; // recursion only occurs in loop above
|
|
|
|
// 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
|
|
int16_t 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 && op!=EVOP_NOT && 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_NOT:
|
|
if (ri) { values[ri - 1] = ~values[ri - 1]; }
|
|
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 = (int8_t)shift_bits;
|
|
return STATUS_RELATIVE_SECTION;
|
|
}
|
|
}
|
|
return STATUS_OK;
|
|
}
|
|
|
|
// if labels referenced in the expression have been evaluated absolutely
|
|
// create a new expression with those labels replaced by their value.
|
|
char* Asm::PartialEval( strref expression )
|
|
{
|
|
struct EvalContext etx;
|
|
SetEvalCtxDefaults( etx );
|
|
strown< 1024 > partial;
|
|
|
|
strref input = expression;
|
|
|
|
EvalOperator prev_op = EVOP_NONE;
|
|
bool partial_solved = false;
|
|
|
|
while( expression ) {
|
|
int value = 0;
|
|
int16_t section = -1, index_section = -1;
|
|
EvalOperator op = EVOP_NONE;
|
|
strref subexp;
|
|
while( expression.get_len() && strref::is_ws( expression.get_first() ) ) {
|
|
partial.append( expression.get_first() );
|
|
++expression;
|
|
}
|
|
if( expression ) {
|
|
char f = expression.get_first();
|
|
if( strref::is_number( f ) ) {
|
|
strl_t l = expression.len_alphanumeric();
|
|
partial.append( strref( expression.get(), l ) );
|
|
expression += l;
|
|
} else if( f=='.' || f == '_' || strref::is_alphabetic( f ) ) {
|
|
strref label = expression.split_range( label_end_char_range );
|
|
Label *pLabel = GetLabel( label, etx.file_ref );
|
|
|
|
if (pLabel && pLabel->evaluated&&!pLabel->external && pLabel->section<0) {
|
|
partial.sprintf_append( "$%x ", pLabel->value ); // insert extra whitespace for separation
|
|
partial_solved = true;
|
|
} else {
|
|
partial.append( label );
|
|
}
|
|
} else {
|
|
partial.append( expression.get_first() );
|
|
++expression;
|
|
}
|
|
}
|
|
}
|
|
if( partial_solved ) {
|
|
//printf( "PARTIAL EXPRESSION SOLVED:\n\t" STRREF_FMT "\n\t" STRREF_FMT "\n", STRREF_ARG( input ), STRREF_ARG( partial.get_strref() ) );
|
|
return _strdup( partial.c_str() );
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// 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;
|
|
char* partial = PartialEval( expression );
|
|
le.address = pc;
|
|
le.scope = scope_pc;
|
|
le.scope_depth = scope_depth;
|
|
le.target = target;
|
|
le.section = (int16_t)(&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 = partial ? strref( partial ) : expression;
|
|
le.expression_mem = partial;
|
|
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;
|
|
char* partial = PartialEval( expression );
|
|
le.address = pc;
|
|
le.scope = scope_pc;
|
|
le.scope_depth = scope_depth;
|
|
le.target = -1;
|
|
le.label = label;
|
|
le.section = (int16_t)(&CurrSection() - &allSections[0]);
|
|
le.rept = contextStack.curr().repeat_total - contextStack.curr().repeat;
|
|
le.file_ref = -1; // current or xdef'd
|
|
le.expression = partial ? strref( partial ) : expression;
|
|
le.expression_mem = partial;
|
|
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) {
|
|
}
|
|
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_DBL_BYTE:
|
|
if (ret==STATUS_RELATIVE_SECTION) {
|
|
if (i->section<0) {
|
|
resolved = false;
|
|
} else {
|
|
allSections[sec].AddReloc(lastEvalValue, trg, lastEvalSection, 1, lastEvalShift-8);
|
|
allSections[sec].AddReloc(lastEvalValue, trg+1, lastEvalSection, 1, lastEvalShift);
|
|
value = 0;
|
|
}
|
|
}
|
|
if ((trg+1)>=allSections[sec].size()) {
|
|
return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE;
|
|
}
|
|
allSections[sec].SetByte(trg, value>>8);
|
|
allSections[sec].SetByte(trg+1, 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, i->source_file);
|
|
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, bool reference_check) {
|
|
uint32_t label_hash = label.fnv1a();
|
|
uint32_t 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)) {
|
|
Label *label = labels.getValues() + index;
|
|
if (!reference_check) { label->referenced = true; }
|
|
return label;
|
|
}
|
|
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];
|
|
uint32_t label_hash = label.fnv1a();
|
|
uint32_t 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)) {
|
|
Label *label = labs.labels.getValues()+index;
|
|
label->referenced = true;
|
|
return label;
|
|
}
|
|
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 = (int16_t)(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(uint32_t hash) {
|
|
uint32_t 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) {
|
|
uint32_t index = FindLabelIndex(label.fnv1a(), labels.getKeys(), labels.count());
|
|
while (index<labels.count()) {
|
|
if (label.same_str_case(labels.getValue(index).label_name)) {
|
|
labels.remove(index);
|
|
break;
|
|
}
|
|
++index;
|
|
}
|
|
i = localLabels.erase(i);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Get a label pool by name
|
|
LabelPool* Asm::GetLabelPool(strref pool_name) {
|
|
uint32_t pool_hash = pool_name.fnv1a();
|
|
uint32_t 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;
|
|
}
|
|
|
|
// Add a label pool
|
|
StatusCode Asm::AddLabelPool(strref name, strref args) {
|
|
uint32_t pool_hash = name.fnv1a();
|
|
uint32_t ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count());
|
|
uint32_t 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;
|
|
uint32_t 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++] = (uint32_t)addr0;
|
|
aRng[ranges++] = (uint32_t)addr1;
|
|
num32 += (addr1-addr0+15)>>4;
|
|
if (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 = (int16_t)(ranges>>1);
|
|
pool.depth = 0;
|
|
pool.start = aRng[0];
|
|
pool.end = aRng[1];
|
|
|
|
labelPools.insert(ins, pool_hash);
|
|
LabelPool &poolValue = labelPools.getValue(ins);
|
|
poolValue = pool;
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
StatusCode Asm::AssignPoolLabel(LabelPool &pool, strref label) {
|
|
if (pool_subpool.is_prefix_word(label)) { // declaring a pool within another pool?
|
|
label += pool_subpool.get_len();
|
|
label.skip_whitespace();
|
|
strref size = label;
|
|
label = size.split_label();
|
|
if (strref::is_number(size.get_first())) {
|
|
uint32_t bytes = (uint32_t)size.atoi();
|
|
if (!bytes) { return ERROR_POOL_RANGE_EXPRESSION_EVAL; }
|
|
if (!GetLabelPool(label)) {
|
|
uint32_t addr;
|
|
StatusCode error = pool.Reserve(bytes, addr, (uint16_t)brace_depth);
|
|
if( error == STATUS_OK ) {
|
|
// permanently remove this chunk from the parent pool
|
|
pool.end = addr;
|
|
pool.depth = 0;
|
|
uint32_t pool_hash = label.fnv1a();
|
|
uint32_t ins = FindLabelIndex(pool_hash, labelPools.getKeys(), labelPools.count());
|
|
labelPools.insert(ins, pool_hash);
|
|
LabelPool &subPool = labelPools.getValue(ins);
|
|
subPool.pool_name = label;
|
|
subPool.numRanges = 1;
|
|
subPool.depth = 0;
|
|
subPool.start = addr;
|
|
subPool.end = addr+bytes;
|
|
}
|
|
return error;
|
|
} else { return ERROR_LABEL_POOL_REDECLARATION; }
|
|
}
|
|
return ERROR_POOL_RANGE_EXPRESSION_EVAL;
|
|
}
|
|
strref type = label;
|
|
uint32_t bytes = 1;
|
|
int sz = label.find_at( '.', 1 );
|
|
if (sz > 0) {
|
|
label = type.split( sz );
|
|
++type;
|
|
if (strref::is_number(type.get_first())) {
|
|
bytes = (uint32_t)type.atoi();
|
|
} else {
|
|
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; }
|
|
uint32_t addr;
|
|
StatusCode error = pool.Reserve(bytes, addr, (uint16_t)brace_depth);
|
|
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;
|
|
pLabel->referenced = false;
|
|
bool local = false;
|
|
|
|
if (label[ 0 ] == '.' || label[ 0 ] == '@' || label[ 0 ] == '!' || label[ 0 ] == ':' || label.get_last() == '$') {
|
|
local = true;
|
|
MarkLabelLocal( label, true );
|
|
}
|
|
LabelAdded(pLabel, local);
|
|
return error;
|
|
}
|
|
|
|
// Request a label from a pool
|
|
StatusCode sLabelPool::Reserve(uint32_t numBytes, uint32_t &ret_addr, uint16_t scope) {
|
|
if (numBytes>(end-start)||depth==MAX_SCOPE_DEPTH) { return ERROR_OUT_OF_LABELS_IN_POOL; }
|
|
if (!depth||scope!=scopeUsed[depth-1][1]) {
|
|
scopeUsed[depth][0] = end;
|
|
scopeUsed[depth][1] = scope;
|
|
++depth;
|
|
}
|
|
end -= numBytes;
|
|
ret_addr = end;
|
|
return STATUS_OK;
|
|
}
|
|
|
|
// Release a label from a pool (at scope closure)
|
|
void sLabelPool::ExitScope(uint16_t scope) {
|
|
if (depth && scopeUsed[depth-1][1]==scope) {
|
|
end = scopeUsed[--depth][0];
|
|
}
|
|
}
|
|
|
|
// Check if a label is marked as an xdef
|
|
bool Asm::MatchXDEF(strref label) {
|
|
uint32_t hash = label.fnv1a();
|
|
uint32_t 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 expression, bool make_constant) {
|
|
expression.trim_whitespace();
|
|
int val = 0;
|
|
struct EvalContext etx;
|
|
SetEvalCtxDefaults(etx);
|
|
StatusCode status = EvalExpression(expression, 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->referenced = false; }
|
|
|
|
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], expression, 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());
|
|
pLabel->referenced = false; // if this label already exists but is changed then it may already have been referenced
|
|
} 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()=='$';
|
|
if (directive_scope_depth > 0) { local = true; }
|
|
LabelAdded(pLabel, local); // TODO: in named scopes the label can still be referenced outside the scope directive
|
|
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();
|
|
strref symstart = symfile;
|
|
if (strref symline = symfile.line()) {
|
|
int scope_start = symline.find('{');
|
|
if (scope_start != 0) {
|
|
strref symdef = symline.get_substr(0, scope_start);
|
|
symdef.clip_trailing_whitespace();
|
|
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);
|
|
}
|
|
if (scope_start >= 0) {
|
|
symfile = symstart + scope_start;
|
|
symfile.scoped_block_skip();
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
uint32_t string_hash = string_name.fnv1a();
|
|
uint32_t 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) {
|
|
uint32_t string_hash = string_name.fnv1a();
|
|
uint32_t 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 sStringSymbols::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(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);
|
|
}
|
|
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());
|
|
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)
|
|
{
|
|
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(uint32_t hash, OPLookup *lookup, int count)
|
|
{
|
|
int first = 0;
|
|
while (count!=first) {
|
|
int index = (first+count)/2;
|
|
uint32_t 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)
|
|
{
|
|
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 (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 (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 read_source = contextStack.curr().read_source.get_skip_ws();
|
|
if (!Merlin() && read_source.is_substr(line.get()))
|
|
read_source.skip(strl_t(line.get()-read_source.get()));
|
|
if (read_source) {
|
|
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_Function(strref line)
|
|
{
|
|
line.skip_whitespace();
|
|
strref function_name = line.split_label(), params;
|
|
|
|
line.skip_whitespace();
|
|
if (line.get_first() == '(') {
|
|
params = line.scoped_block_comment_skip();
|
|
params.trim_whitespace();
|
|
}
|
|
|
|
line.skip_whitespace();
|
|
userFunctions.Add(function_name, params, line);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
|
|
StatusCode Asm::Directive_Undef(strref line)
|
|
{
|
|
strref name = line.split_range_trim(Merlin() ? label_end_char_range_merlin : label_end_char_range);
|
|
uint32_t name_hash = name.fnv1a();
|
|
uint32_t 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 = LoadText(file, size);
|
|
if (buffer) {
|
|
loadedData.push_back(buffer);
|
|
strref src(buffer, strl_t(size));
|
|
PushContext(file, src, src);
|
|
} else if (Merlin()) {
|
|
// MERLIN include file name rules
|
|
if (file[0] >= '!' && file[0] <= '&')
|
|
buffer = LoadText(file + 1, size);
|
|
if (buffer) {
|
|
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");
|
|
buffer = LoadText(fileadd.get_strref(), size);
|
|
if (buffer) {
|
|
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);
|
|
buffer = LoadText(fileadd.get_strref(), size);
|
|
if (buffer) {
|
|
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 (len && bin_size>len)
|
|
bin_size = len;
|
|
if (bin_size>0)
|
|
AddBin((const uint8_t*)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.split_lang();
|
|
param.trim_whitespace();
|
|
if (param[0]==',') {
|
|
++param;
|
|
param.skip_whitespace();
|
|
if (param) {
|
|
struct EvalContext etx;
|
|
SetEvalCtxDefaults(etx);
|
|
EvalExpression(param.split_token_trim_track_parens(','), etx, skip);
|
|
if (param) { EvalExpression(param, etx, len); }
|
|
}
|
|
}
|
|
}
|
|
|
|
if (line[0]=='"')
|
|
return Directive_Incbin(line, skip, len);
|
|
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
|
|
Section &currSection = CurrSection();
|
|
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;
|
|
}
|
|
|
|
StatusCode Asm::Directive_MERGE(strref line)
|
|
{
|
|
int first_section = -1;
|
|
strref section_name = line.split_label();
|
|
|
|
// get the first section that matches the first name and has an assigned address
|
|
for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) {
|
|
const Section §ion = allSections[section_id];
|
|
if (!section.IsMergedSection()&§ion.name.same_str(section_name)) {
|
|
if (first_section<0||!allSections[first_section].IsRelativeSection()) {
|
|
first_section = (int)section_id;
|
|
}
|
|
}
|
|
}
|
|
if (first_section<0) { return ERROR_NOT_A_SECTION; }
|
|
|
|
// merge all sections as defined by the line
|
|
while (section_name) {
|
|
for (size_t section_id = 0; section_id!=allSections.size(); ++section_id) {
|
|
const Section §ion = allSections[section_id];
|
|
if (section_id!=first_section&&!section.IsMergedSection()&§ion.IsRelativeSection()) {
|
|
if (section.name.same_str(section_name)) {
|
|
StatusCode result = MergeSections(first_section, (int)section_id);
|
|
if (result!=STATUS_OK) { return result; }
|
|
}
|
|
}
|
|
}
|
|
if (line[0]==',') { ++line; }
|
|
section_name = line.split_label();
|
|
}
|
|
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(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 != '$') {
|
|
uint32_t hash = xdef.fnv1a();
|
|
uint32_t 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 = true;
|
|
pLabelXREF->constant = false;
|
|
pLabelXREF->reference = true;
|
|
pLabelXREF->referenced = false; // referenced is only within the current object file
|
|
}
|
|
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, bool little_endian)
|
|
{
|
|
struct EvalContext etx;
|
|
SetEvalCtxDefaults(etx);
|
|
line.trim_whitespace();
|
|
while (strref exp_dc = line.split_token_trim_track_parens(',')) {
|
|
int value = 0;
|
|
if (!CurrSection().IsDummySection()) {
|
|
if (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) {
|
|
static LateEval::Type sizes[] = {
|
|
LateEval::LET_BYTE,
|
|
LateEval::LET_ABS_REF,
|
|
LateEval::LET_ABS_L_REF,
|
|
LateEval::LET_ABS_4_REF
|
|
};
|
|
LateEval::Type type = sizes[width - 1];
|
|
if (!little_endian && width == 2) {
|
|
type = LateEval::LET_DBL_BYTE;
|
|
}
|
|
|
|
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], exp_dc, source_file, type);
|
|
} else if (error == STATUS_RELATIVE_SECTION) {
|
|
value = 0;
|
|
if (little_endian || width == 1) {
|
|
CurrSection().AddReloc(lastEvalValue, CurrSection().DataOffset(), lastEvalSection, (int8_t)width, (int8_t)lastEvalShift);
|
|
} else {
|
|
// big endian needs 1 reloc for each byte
|
|
int shift = lastEvalShift + 8 - width * 8;
|
|
for (int i = 0; i < width; ++i, shift += 8) {
|
|
CurrSection().AddReloc(lastEvalValue, CurrSection().DataOffset() + i, lastEvalSection, 1, (int8_t)shift);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (little_endian) {
|
|
uint8_t bytes[4] = {
|
|
(uint8_t)value, (uint8_t)(value >> 8),
|
|
(uint8_t)(value >> 16), (uint8_t)(value >> 24) };
|
|
AddBin(bytes, width);
|
|
} else {
|
|
uint8_t bytes[4] = {
|
|
(uint8_t)(value >> 24), (uint8_t)(value >> 16),
|
|
(uint8_t)(value >> 8), (uint8_t)value };
|
|
AddBin(bytes + 4 - width, 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_track_parens(',');
|
|
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(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)
|
|
{
|
|
uint8_t 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:
|
|
if (import_means_xref) { return Directive_XDEF(line); }
|
|
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_MERGE:
|
|
return Directive_MERGE(line);
|
|
|
|
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(
|
|
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_DBL_BYTES: // DDB: Merlin store 2-byte word, big endian format.
|
|
return Directive_DC(line, 2, source_file, false);
|
|
|
|
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;
|
|
if (line[0]=='[') {
|
|
strref str = line.scoped_block_skip().get_trimmed_ws();
|
|
if (StringSymbol *StringSym = GetString(str)) {
|
|
line.skip_whitespace();
|
|
if (line[0] == '"')
|
|
line = line.between('"', '"');
|
|
CurrSection().AddIndexText(StringSym, line);
|
|
break;
|
|
}
|
|
}
|
|
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);
|
|
break;
|
|
|
|
case AD_INCLUDE: // assemble another file in place
|
|
return Directive_Include(line);
|
|
|
|
case AD_INCBIN:
|
|
return Directive_Incbin(line);
|
|
|
|
case AD_IMPORT:
|
|
if (import_means_xref) { return Directive_XREF(line); }
|
|
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_FUNCTION:
|
|
return Directive_Function(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
|
|
// ifdef doesn't need to evaluate the value, just determine if it exists or not
|
|
strref label = line.split_range_trim(label_end_char_range);
|
|
if( GetLabel(label, etx.file_ref) )
|
|
ConsumeConditional();
|
|
else
|
|
SetConditional();
|
|
}
|
|
break;
|
|
|
|
case AD_IFNDEF:
|
|
if (NewConditional()) { // Start new conditional block
|
|
CheckConditionalDepth(); // Check if nesting
|
|
// ifdef doesn't need to evaluate the value, just determine if it exists or not
|
|
strref label = line.split_range_trim(label_end_char_range);
|
|
if (!GetLabel(label, etx.file_ref))
|
|
ConsumeConditional();
|
|
else
|
|
SetConditional();
|
|
}
|
|
break;
|
|
|
|
case AD_IFCONST:
|
|
if (NewConditional()) { // Start new conditional block
|
|
CheckConditionalDepth(); // Check if nesting
|
|
// ifdef doesn't need to evaluate the value, just determine if it exists or not
|
|
strref label_name = line.split_range_trim(label_end_char_range);
|
|
if (Label* label = GetLabel(label_name, etx.file_ref)) {
|
|
if (label->constant) { ConsumeConditional(); }
|
|
else { SetConditional(); }
|
|
}
|
|
else { SetConditional(); }
|
|
}
|
|
break;
|
|
|
|
case AD_IFBLANK:
|
|
if (NewConditional()) { // Start new conditional block
|
|
CheckConditionalDepth(); // Check if nesting
|
|
line.trim_whitespace();
|
|
if (line.is_empty())
|
|
ConsumeConditional();
|
|
else
|
|
SetConditional();
|
|
}
|
|
break;
|
|
|
|
case AD_IFNBLANK:
|
|
if (NewConditional()) { // Start new conditional block
|
|
CheckConditionalDepth(); // Check if nesting
|
|
line.trim_whitespace();
|
|
if (!line.is_empty())
|
|
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);
|
|
|
|
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);
|
|
|
|
case AD_SCOPE:
|
|
directive_scope_depth++;
|
|
return EnterScope();
|
|
|
|
case AD_ENDSCOPE:
|
|
directive_scope_depth--;
|
|
return ExitScope();
|
|
|
|
case AD_PUSH:
|
|
line.trim_whitespace();
|
|
if (Label *label = GetLabel(line)) {
|
|
symbolStacks.PushSymbol(label);
|
|
return STATUS_OK;
|
|
} else if( StringSymbol* string = GetString(line)) {
|
|
symbolStacks.PushSymbol(string);
|
|
return STATUS_OK;
|
|
}
|
|
return ERROR_UNABLE_TO_PROCESS;
|
|
|
|
case AD_PULL:
|
|
line.trim_whitespace();
|
|
if (Label *label = GetLabel(line)) {
|
|
return symbolStacks.PullSymbol(label);
|
|
} else if (StringSymbol* string = GetString(line)) {
|
|
return symbolStacks.PullSymbol(string);
|
|
}
|
|
return ERROR_UNABLE_TO_PROCESS;
|
|
|
|
}
|
|
return error;
|
|
}
|
|
|
|
// Make an educated guess at the intended address mode from an opcode argument
|
|
StatusCode Asm::GetAddressMode(strref line, bool flipXY, uint32_t &validModes, AddrMode &addrMode, int &len, strref &expression)
|
|
{
|
|
bool force_zp = false;
|
|
bool force_24 = false;
|
|
bool force_abs = false;
|
|
bool need_more = true;
|
|
bool first = true;
|
|
strref arg, deco;
|
|
|
|
len = 0;
|
|
while (need_more) {
|
|
need_more = false;
|
|
line.skip_whitespace();
|
|
uint8_t c = line.get_first();
|
|
if (!c) { addrMode = AMB_NON; }
|
|
if( c == '[' || c == '(' ) {
|
|
strref block_suffix( line.get(), line.scoped_block_comment_len() );
|
|
strref suffix = line.get_skipped( block_suffix.get_len() );
|
|
suffix.trim_whitespace();
|
|
if( suffix.get_first() == ',' ) { ++suffix; suffix.skip_whitespace(); }
|
|
else { suffix.clear(); }
|
|
++block_suffix; block_suffix.clip(1); block_suffix.trim_whitespace();
|
|
++line; line.skip_whitespace();
|
|
strref block = block_suffix.split_token_trim_track_parens( ',' );
|
|
validModes &= AMM_ZP_REL_X | AMM_ZP_Y_REL | AMM_REL | AMM_ZP_REL | AMM_REL_X | AMM_ZP_REL_L | AMM_ZP_REL_Y_L | AMM_STK_REL_Y | AMM_REL_L;
|
|
if( line.get_first() == '>' ) { // [>$aaaa]
|
|
if( c == '[' ) { addrMode = AMB_REL_L; validModes &= AMM_REL_L; expression = block+1; }
|
|
} else if( line.get_first() == '|' || (line.get_first() == '!' && c == '(' )) { // (|$aaaa) or (|$aaaa,x)
|
|
strref arg = block.after( ',' ); arg.skip_whitespace();
|
|
if( arg && ( arg.get_first() == 'x' || arg.get_first() == 'X' ) ) {
|
|
addrMode = AMB_REL_X; validModes &= AMM_REL_X; expression = block.before( ',' ); }
|
|
else { addrMode = AMB_REL; validModes &= AMM_REL; expression = block; }
|
|
} else if( line.get_first() == '<' ) { // (<$aa) (<$aa),y (<$aa,x) (<$aa,s),y [<$aa] [<$aa],y
|
|
if( suffix ) {
|
|
if( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) {
|
|
if( c == '(' ) { // (<$aa),y or (<$aa,s),y
|
|
if( block_suffix && ( block_suffix.get_first() == 's' || block_suffix.get_first() == 'S' ) ) {
|
|
expression = block+1;
|
|
addrMode = AMB_STK_REL_Y; validModes &= AMM_STK_REL_Y;
|
|
} else {
|
|
expression = block+1;
|
|
addrMode = AMB_ZP_Y_REL; validModes &= AMM_ZP_Y_REL;
|
|
}
|
|
} else { // [<$aa],y
|
|
expression = block+1;
|
|
addrMode = AMB_ZP_REL_Y_L; validModes &= AMM_ZP_REL_Y_L;
|
|
}
|
|
} else { return ERROR_BAD_ADDRESSING_MODE; }
|
|
} else { // (<$aa) (<$aa,x) [<$aa]
|
|
if( c == '[' ) {
|
|
if( block.find( ',' ) >= 0 || suffix.get_first() == ',' ) { return ERROR_BAD_ADDRESSING_MODE; }
|
|
expression = block+1;
|
|
addrMode = AMB_ZP_REL_L; validModes &= AMM_ZP_REL_L;
|
|
} else {
|
|
if( block_suffix ) {
|
|
if( block_suffix.get_first() != 'x' && block_suffix.get_first() != 'X' ) { return ERROR_BAD_ADDRESSING_MODE; }
|
|
expression = block+1;
|
|
addrMode = AMB_ZP_REL_X; validModes &= AMM_ZP_REL_X;
|
|
} else {
|
|
expression = block+1;
|
|
addrMode = AMB_ZP_REL; validModes &= AMM_ZP_REL;
|
|
}
|
|
}
|
|
}
|
|
} else { // no <, |, ! or > decorator inside (...) or [...]
|
|
if( c == '[' && ( block_suffix.get_first() == 's' || block_suffix.get_first() == 'S' ) ) {
|
|
if( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) {
|
|
expression = block;
|
|
addrMode = AMB_STK_REL_Y; validModes &= AMM_STK_REL_Y;
|
|
} else { return ERROR_BAD_ADDRESSING_MODE; }
|
|
} else if( block_suffix.get_first() == 'x' || block_suffix.get_first() == 'X' ) { // ($aa,x) ($aaaa,x)
|
|
if( c == '[' ) { return ERROR_BAD_ADDRESSING_MODE; }
|
|
expression = block;
|
|
switch( validModes & ( AMM_ZP_REL_X | AMM_REL_X ) ) {
|
|
case AMM_ZP_REL_X: addrMode = AMB_ZP_REL_X; validModes = AMM_ZP_REL_X; break;
|
|
case AMM_REL_X: addrMode = AMB_REL_X; validModes = AMM_REL_X; break;
|
|
default: addrMode = force_zp ? AMB_ZP_REL_X : AMB_REL_X; validModes &= force_zp ? AMM_ZP_REL_X : ( force_abs ? AMM_ZP_REL_X : ( AMM_ZP_REL_X | AMM_REL_X ) );
|
|
break;
|
|
}
|
|
} else if( suffix && ( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) ) {
|
|
if( c == '[' ) {
|
|
expression = block;
|
|
addrMode = AMB_ZP_REL_Y_L; validModes &= AMM_ZP_REL_Y_L;
|
|
} else { // ($aa),y
|
|
expression = block;
|
|
addrMode = AMB_ZP_Y_REL; validModes &= AMM_ZP_Y_REL;
|
|
}
|
|
} else { // ($aa), ($aaaa), [$aa], [$aaaa]
|
|
if( c == '[' ) { // [$aa], [$aaaa]
|
|
expression = block;
|
|
addrMode = force_zp ? AMB_ZP_REL_L : AMB_REL_L; validModes &= force_zp ? AMM_ZP_REL_L : ( force_abs ? AMM_REL_L : ( AMM_ZP_REL_L | AMM_REL_L ) );
|
|
}
|
|
else { // ($aa), ($aaaa)
|
|
expression = block;
|
|
addrMode = force_zp ? AMB_ZP_REL : AMB_REL; validModes &= force_zp ? AMM_ZP_REL : ( force_abs ? AMM_REL : ( AMM_ZP_REL | AMM_REL ) );
|
|
}
|
|
}
|
|
}
|
|
expression.trim_whitespace();
|
|
} else if (c == '<' ) { // force zero page not indirect
|
|
++line; line.trim_whitespace();
|
|
strref suffix = line.after(','); suffix.skip_whitespace();
|
|
expression = line.before_or_full(','); expression.trim_whitespace();
|
|
if( suffix ) {
|
|
if( suffix.get_first() == 's' || suffix.get_first() == 'S' ) {
|
|
addrMode = AMB_STK; validModes &= AMM_STK; // not correct usage of < but I'll allow it.
|
|
} else if( suffix.get_first() == 'x' || suffix.get_first() == 'X' ) {
|
|
addrMode = AMB_ZP_X; validModes &= AMM_ZP_X;
|
|
} else { return ERROR_BAD_ADDRESSING_MODE; }
|
|
} else {
|
|
addrMode = AMB_ZP; validModes &= AMM_ZP;
|
|
}
|
|
} else if (c == '<') {
|
|
validModes &= AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ZP_Y_REL |
|
|
AMM_ZP_REL | AMM_ZP_ABS | AMM_ZP_REL_L | AMM_ZP_REL_Y_L | AMM_FLIPXY;
|
|
} else if( cpu == CPU_65816 && ( c == '|' /*|| c == '!'*/ ) ) { // disabling ! for now since it conflicts with scope start
|
|
++line; line.trim_whitespace();
|
|
strref suffix = line.after( ',' ); suffix.skip_whitespace();
|
|
expression = line.before_or_full( ',' ); expression.trim_whitespace();
|
|
addrMode = AMB_NON;
|
|
if( suffix ) {
|
|
if( suffix.get_first() == 'x' || suffix.get_first() == 'X' ) {
|
|
addrMode = AMB_ABS_X; validModes &= AMM_ABS_X;
|
|
} else if( suffix.get_first() == 'y' || suffix.get_first() == 'Y' ) {
|
|
addrMode = AMB_ABS_Y; validModes &= AMM_ABS_Y;
|
|
}
|
|
} else {
|
|
addrMode = AMB_ABS; validModes &= AMM_ABS;
|
|
}
|
|
} else if( cpu == CPU_65816 && c == '>' ) {
|
|
++line; line.trim_whitespace();
|
|
strref suffix = line.after( ',' ); suffix.skip_whitespace();
|
|
expression = line.before_or_full( ',' ); expression.trim_whitespace();
|
|
if( suffix ) {
|
|
if( suffix.get_first() == 'x' || suffix.get_first() == 'X' ) {
|
|
addrMode = AMB_ABS_L_X; validModes &= AMM_ABS_L_X;
|
|
}
|
|
else { return ERROR_BAD_ADDRESSING_MODE; }
|
|
}
|
|
else {
|
|
addrMode = AMB_ABS_L; validModes &= AMM_ABS_L;
|
|
}
|
|
} else if (c == '#') {
|
|
++line;
|
|
addrMode = AMB_IMM;
|
|
validModes &= AMM_IMM | AMM_IMM_DBL_A | AMM_IMM_DBL_XY;
|
|
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; validModes &= ~(AMM_IMM_DBL_A | AMM_IMM_DBL_XY); 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_track_parens(',');
|
|
if( force_abs ) { validModes &= AMM_ABS | AMM_ABS_X | AMM_ABS_Y | AMM_REL | AMM_REL_X; }
|
|
if( force_zp ) { validModes &= AMM_ZP | AMM_ZP_X | AMM_ZP_REL_X | AMM_ZP_Y_REL |
|
|
AMM_ZP_REL | AMM_ZP_ABS | AMM_ZP_REL_L | AMM_ZP_REL_Y_L | AMM_FLIPXY; }
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
first = false;
|
|
}
|
|
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
|
|
uint32_t 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_track_parens(',');
|
|
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_track_parens(',');
|
|
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;
|
|
int8_t 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 (Merlin()&&addrMode==AMB_IMM && validModes==AMM_ABS) {
|
|
addrMode = AMB_ABS; // Merlin seems to allow this
|
|
} else if (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) {
|
|
uint8_t 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
|
|
codeArg = CA_ONE_BYTE;
|
|
// check for double immediate
|
|
if( validModes & ( AMM_IMM_DBL_A | AMM_IMM_DBL_XY ) ) {
|
|
if( op_param ) { codeArg = op_param == 2 ? CA_TWO_BYTES : CA_ONE_BYTE; }
|
|
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: {
|
|
// second operand stored first.
|
|
StatusCode error = STATUS_OK;
|
|
int value = 0;
|
|
|
|
struct EvalContext etx;
|
|
SetEvalCtxDefaults(etx);
|
|
etx.pc = CurrSection().GetPC()-1;
|
|
line.split_token_trim_track_parens(',');
|
|
error = EvalExpression(line, etx, value);
|
|
if (error==STATUS_NOT_READY || error == STATUS_XREF_DEPENDENT)
|
|
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC()-1, scope_address[scope_depth], line, source_file, LateEval::LET_BYTE);
|
|
else if (error == STATUS_RELATIVE_SECTION) {
|
|
CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), lastEvalSection, 1, lastEvalShift);
|
|
}
|
|
|
|
AddByte(value);
|
|
}
|
|
|
|
if (evalLater)
|
|
AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC()-2, 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_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 : (uint8_t)((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 : (uint8_t)((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, strref source) {
|
|
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 if (source) { errorText.sprintf_append("Error (%d): ", source.count_lines(line)); }
|
|
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 (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 (KickAsm()&&line.get_first()==':') { ++line; } // Kick Assembler macro prefix (incompatible with merlin and sane syntax)
|
|
strref line_nocom = line;
|
|
strref operation = line.split_range(Merlin() ? label_end_char_range_merlin : label_end_char_range);
|
|
if( operation.get_last() == ':' ) { operation.clip( 1 ); }
|
|
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 && Merlin()&&(line||operation)) { // MERLIN fixes and PoP does some naughty stuff like 'and = 0'
|
|
force_label = (!strref::is_ws(char0)&&char0!='{' && char0!='}')||char1==']'||charE=='?';
|
|
} /*else if (!Merlin()&&line[0]==':') { force_label = true; }*/
|
|
if (!operation && !force_label) {
|
|
if (ConditionalAsm()) {
|
|
// scope open / close
|
|
switch (line[0]) {
|
|
case '{':
|
|
error = EnterScope();
|
|
brace_depth++;
|
|
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();
|
|
for (LabelPool *pool = labelPools.getValues(), *end = pool+labelPools.count(); pool!=end; pool++) {
|
|
pool->ExitScope((uint16_t)brace_depth);
|
|
}
|
|
brace_depth--;
|
|
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 ((!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 {
|
|
uint32_t nameHash = label.fnv1a();
|
|
uint32_t 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) {
|
|
uint32_t 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 (Merlin() && strref::is_ws(line_start[0])) {
|
|
error = ERROR_UNDEFINED_CODE;
|
|
} else if (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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( error >= ERROR_STOP_PROCESSING_ON_HIGHER ) { return error; }
|
|
// Check for unterminated condition in source
|
|
if (!contextStack.curr().next_source &&
|
|
(!ConditionalAsm() || ConditionalConsumed() || !conditional_depth)) {
|
|
if (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;
|
|
int16_t plus, a16, x16, dp;
|
|
void clr() { base = 0; plus = a16 = x16 = dp = 0; }
|
|
void add(uint8_t 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(uint8_t c) { return (c & 0xf) >> 1; }
|
|
static int sum_plus(uint8_t 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::ListTassStyle( strref filename ) {
|
|
// NOTE: This file needs more information to be useful for things
|
|
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
|
|
uint8_t mnemonic[256];
|
|
uint8_t 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)) {
|
|
uint8_t op = opcode_table[i].aCodes[j];
|
|
if (addrmode[op]==255) {
|
|
mnemonic[op] = (uint8_t)i;
|
|
addrmode[op] = (uint8_t)j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
strref first_src;
|
|
for( std::vector<Section>::iterator si = allSections.begin(); si != allSections.end() && !first_src; ++si ) {
|
|
if( !si->pListing )
|
|
continue;
|
|
for(auto & li : *si->pListing) {
|
|
if( li.source_name ) { first_src = li.source_name; break; }
|
|
}
|
|
}
|
|
fprintf( f, ";6502/65C02/65816/CPU64/DTV Turbo Assembler V1.4x listing file of \"" STRREF_FMT "\"\n;done on WkDay Month Date Time\n\n", STRREF_ARG( first_src ) );
|
|
|
|
fprintf( f, ";Offset\t;Hex\t\t;Monitor\t;Source\n\n" );
|
|
|
|
for(auto &si : allSections) {
|
|
if( !si.pListing || si.type == ST_REMOVED || !si.pListing ) { continue; }
|
|
for(const struct ListLine &lst : *si.pListing) {
|
|
/*if( lst.size && lst.wasMnemonic() )*/ {
|
|
strown<256> out;
|
|
/*if( lst.size )*/ { out.sprintf_append( ".%04x\t", 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 ] );
|
|
}
|
|
}
|
|
out.append( "\t\t" );
|
|
if (lst.size && lst.wasMnemonic()) {
|
|
//out.pad_to(' ', 21);
|
|
uint8_t *buf = si.output + lst.address;
|
|
uint8_t op = mnemonic[*buf];
|
|
uint8_t 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, (int16_t)(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[2], buf[1]);
|
|
} 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]);
|
|
}
|
|
}
|
|
} else { out.append('\t');}
|
|
// out.pad_to( ' ', 36 );
|
|
out.append( '\t' );
|
|
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 ) );
|
|
}
|
|
}
|
|
}
|
|
fprintf( f, "\n;****** end of code\n" );
|
|
if( opened ) { fclose( f ); }
|
|
return true;
|
|
}
|
|
|
|
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
|
|
uint8_t mnemonic[256];
|
|
uint8_t 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)) {
|
|
uint8_t op = opcode_table[i].aCodes[j];
|
|
if (addrmode[op]==255) {
|
|
mnemonic[op] = (uint8_t)i;
|
|
addrmode[op] = (uint8_t)j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct cycleCnt cycles[MAX_DEPTH_CYCLE_COUNTER];
|
|
int16_t cycles_depth = 0;
|
|
memset(cycles, 0, sizeof(cycles));
|
|
|
|
|
|
// show merged sections
|
|
for (size_t i = 0, n = allSections.size(); i < n; ++i)
|
|
{
|
|
Section& s = allSections[i];
|
|
if (s.type != ST_REMOVED) {
|
|
if (s.include_from) {
|
|
fprintf(f, "Section " STRREF_FMT " from " STRREF_FMT " $%04x - $%04x ($%04x)\n",
|
|
STRREF_ARG(s.name), STRREF_ARG(s.include_from), s.start_address, s.address, s.address - s.start_address);
|
|
} else {
|
|
fprintf(f, "Section " STRREF_FMT " $%04x - $%04x ($%04x)\n",
|
|
STRREF_ARG(s.name), s.start_address, s.address, s.address - s.start_address);
|
|
}
|
|
for (size_t j = 0; j < n; ++j)
|
|
{
|
|
Section& s2 = allSections[j];
|
|
if (s2.type == ST_REMOVED && s2.merged_into >= 0)
|
|
{
|
|
int offset = s2.merged_at;
|
|
int parent = s2.merged_into;
|
|
while (parent != i && parent >= 0) {
|
|
offset += allSections[parent].merged_at;
|
|
parent = allSections[parent].merged_into;
|
|
}
|
|
if (parent == i) {
|
|
if (s2.include_from) {
|
|
fprintf(f, " + " STRREF_FMT " from " STRREF_FMT " $%04x - $%04x ($%04x) at offset 0x%04x\n",
|
|
STRREF_ARG(s2.name), STRREF_ARG(s2.include_from), s2.merged_at + s.start_address,
|
|
s2.merged_at + s.start_address + s2.merged_size, s2.merged_size, s2.merged_at);
|
|
} else {
|
|
fprintf(f, " + " STRREF_FMT " $%04x - $%04x ($%04x) at offset 0x%04x\n",
|
|
STRREF_ARG(s2.name), s2.merged_at + s.start_address,
|
|
s2.merged_at + s.start_address + s2.merged_size, s2.merged_size, s2.merged_at);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
strref prev_src;
|
|
int prev_offs = 0;
|
|
for (std::vector<Section>::iterator si = allSections.begin(); si != allSections.end(); ++si) {
|
|
if (si->merged_into >= 0) {
|
|
fprintf(f, STRREF_FMT " from " STRREF_FMT " merged into " STRREF_FMT " at offset 0x%04x\n",
|
|
STRREF_ARG(si->name), STRREF_ARG(si->include_from), STRREF_ARG(allSections[si->merged_into].name), si->merged_at); }
|
|
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.pad_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.pad_to(' ', 6); out.sprintf_append("c>%d", cycles_depth);
|
|
}
|
|
if (lst.stopClock()) {
|
|
out.pad_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.pad_to(' ', 18);
|
|
uint8_t *buf = si->output + lst.address;
|
|
uint8_t op = mnemonic[*buf];
|
|
uint8_t 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, (int16_t)(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[2], buf[1]);
|
|
} 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.pad_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.pad_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++) {
|
|
uint32_t 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( error >= ERROR_STOP_PROCESSING_ON_HIGHER ) { break; }
|
|
if (contextStack.curr().complete()) {
|
|
error = PopContext();
|
|
} else {
|
|
error = FlushLocalLabels(scope_depth);
|
|
if (error>=FIRST_ERROR) { break; }
|
|
contextStack.curr().restart();
|
|
error = STATUS_OK;
|
|
}
|
|
}
|
|
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.has_work() ?
|
|
contextStack.curr().read_source.get_line() : strref(), error);
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
// OBJECT FILE HANDLING
|
|
//
|
|
//
|
|
|
|
struct ObjFileHeader {
|
|
int16_t id; // 'x6'
|
|
int16_t sections;
|
|
int16_t relocs;
|
|
int16_t labels;
|
|
int16_t late_evals;
|
|
int16_t map_symbols;
|
|
uint32_t 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;
|
|
int16_t next_group; // next section of group
|
|
int16_t first_group; // first section of group
|
|
int16_t relocs;
|
|
SectionType type;
|
|
int8_t flags;
|
|
};
|
|
|
|
struct ObjFileReloc {
|
|
int base_value;
|
|
int section_offset;
|
|
int16_t target_section;
|
|
int8_t bytes;
|
|
int8_t 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)
|
|
int16_t section; // -1 if resolved, file section # if section rel
|
|
int16_t 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
|
|
int16_t section; // section to target
|
|
int16_t rept; // value of rept for this late eval
|
|
int16_t scope; // PC start of scope
|
|
int16_t type; // label, byte, branch, word (LateEval::Type)
|
|
};
|
|
|
|
struct ObjFileMapSymbol {
|
|
struct ObjFileStr name; // symbol name
|
|
int value;
|
|
int16_t 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<uint32_t, int> *pLookup, char **strPool, uint32_t &strPoolSize, uint32_t &strPoolCap) {
|
|
if (!str.get()||!str.get_len()) { return -1; } // empty string
|
|
|
|
uint32_t hash = str.fnv1a();
|
|
uint32_t 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 + (uint32_t)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 += (uint32_t)str.get_len()+1;
|
|
pLookup->insert(index, hash);
|
|
pLookup->getValues()[index] = strOffs;
|
|
}
|
|
return strOffs;
|
|
}
|
|
|
|
void Asm::ShowReferences()
|
|
{
|
|
size_t num = labels.count();
|
|
const Label* lbl = labels.getValues();
|
|
printf("LABEL REFERENCE SUMMARY:\n");
|
|
for (size_t l = 0; l < num; ++l) {
|
|
if (lbl[l].external) {
|
|
printf(" * >" STRREF_FMT " Sect: " STRREF_FMT " Offs: $%0x\n",
|
|
STRREF_ARG(lbl[l].label_name), STRREF_ARG(lbl[l].section >= 0 ? allSections[lbl[l].section].name : strref("fixed")), lbl[l].value);
|
|
}
|
|
if (lbl[l].reference) {
|
|
printf(" * <" STRREF_FMT " Sect: " STRREF_FMT " Offs: $%0x\n",
|
|
STRREF_ARG(lbl[l].label_name), STRREF_ARG(lbl[l].section >= 0 ? allSections[lbl[l].section].name : strref("fixed")), lbl[l].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = (int16_t)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 += int16_t(s->pRelocs->size()); }
|
|
hdr.bindata += s->size();
|
|
}
|
|
}
|
|
hdr.late_evals = (int16_t)lateEval.size();
|
|
hdr.map_symbols = (int16_t)map.size();
|
|
hdr.stringdata = 0;
|
|
|
|
// labels don't include XREF labels
|
|
hdr.labels = 0;
|
|
for (uint32_t l = 0; l<labels.count(); l++) {
|
|
if (!labels.getValue(l).reference) { hdr.labels++; }
|
|
}
|
|
|
|
int16_t *aRemapSects = hdr.sections ? (int16_t*)malloc(sizeof(int16_t) * 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 += (int16_t)el->labels.count();
|
|
}
|
|
char *stringPool = nullptr;
|
|
uint32_t stringPoolCap = 0;
|
|
pairArray<uint32_t, 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(int16_t) * 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]] = (int16_t)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 ? (int16_t)(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 = (int16_t)sect;
|
|
|
|
// write out labels
|
|
if (hdr.labels) {
|
|
for (uint32_t 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 = (int16_t)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 (uint32_t 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 = (int16_t)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 = (int16_t)lei->target;
|
|
le.address = lei->address;
|
|
le.scope = (int16_t)lei->scope;
|
|
le.type = (int16_t)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 ((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();
|
|
int16_t *aSctRmp = (int16_t*)malloc(hdr.sections * sizeof(int16_t));
|
|
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;
|
|
}
|
|
|
|
// sections
|
|
for (int si = 0; si < hdr.sections; si++) {
|
|
int16_t 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 = (uint8_t*)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] = (int16_t)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);
|
|
int16_t f = (int16_t)l.flags;
|
|
int external = f & ObjFileLabel::OFL_XDEF;
|
|
if (external == ObjFileLabel::OFL_XDEF) {
|
|
if (!lbl) { lbl = AddLabel(name.fnv1a()); lbl->referenced = false; } // 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());
|
|
}
|
|
uint32_t hash = name.fnv1a();
|
|
uint32_t 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;
|
|
}
|
|
// no protected labels => don't track as separate file
|
|
if (file_index==(int)externals.size()) { file_index = -1; }
|
|
|
|
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,
|
|
};
|
|
|
|
struct OMFSegHdr {
|
|
uint8_t SegTotal[4]; // + Segment Segment Header size Body size
|
|
uint8_t ResSpc[4]; // Number of 0x00 to add to the end of Body
|
|
uint8_t Length[4]; // Memory Size Segment
|
|
uint8_t pad1[1];
|
|
uint8_t LabLen[1]; // Length Names: 10
|
|
uint8_t NumLen[1]; // Size = 4 numbers uint8s
|
|
uint8_t Version[1]; // OMF Version: 2
|
|
uint8_t BankSize[4]; // Size of a Bank: 64 KB code if, between 0 and 64 KB for Data
|
|
uint8_t Kind[2]; // Type Segment
|
|
uint8_t pad2[2];
|
|
uint8_t Org[4];
|
|
uint8_t Align[4]; // Alignment: 0, 64 or 256 KB
|
|
uint8_t NumSEx[1]; // Little Endian: 0 for IIgs
|
|
uint8_t pad3[1];
|
|
uint8_t SegNum[2]; // Segment Number: 1-> N
|
|
uint8_t EntryPointOffset[4]; // Entry point in the Segment
|
|
uint8_t DispNameOffset[2]; // Where the offset is located LoadName
|
|
uint8_t DispDataOffset[2]; // Offset begins the Body Segment
|
|
uint8_t tempOrg[4]; //
|
|
};
|
|
|
|
// write a number of bytes of a value
|
|
static void _writeNBytes(uint8_t *dest, int bytes, int value) {
|
|
while (bytes--) {
|
|
*dest++ = (uint8_t)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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// reloc_max needs to greater than zero
|
|
if (reloc_max<1) { return ERROR_ABORTED; }
|
|
|
|
for (std::vector<int>::iterator i = SegNum.begin(); i!=SegNum.end(); ++i) {
|
|
SegLookup[*i] = (int)(&*i-&SegNum[0]);
|
|
}
|
|
uint8_t *instructions = (uint8_t*)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 + (int)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 = -1;
|
|
int inst_curr = instruction_offs;
|
|
instructions[inst_curr++] = OMFR_SUPER;
|
|
int len_offs = inst_curr;
|
|
inst_curr += 4;
|
|
instructions[inst_curr++] = (uint8_t)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++] = uint8_t(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++] = (uint8_t)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++] = uint8_t(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++] = uint8_t(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 + (int)segName.get_len() + num_bytes_file + instruction_offs;
|
|
if (num_bytes_file==0) { segSize -= 5; }
|
|
_writeNBytes(hdr.SegTotal, 4, segSize);
|
|
uint8_t lenSegName = (uint8_t)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 = (char)segName.get_len();
|
|
_writeNBytes(hdr.SegNum, 2, (int)SegNum.size()+1);
|
|
_writeNBytes(hdr.Kind, 2, 0x12);
|
|
_writeNBytes(hdr.DispDataOffset, 2, sizeof(hdr) + 10 + 1 + (int)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 + (int)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) {
|
|
|
|
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;
|
|
bool list_output = false;
|
|
bool tass_list_output = false;
|
|
bool show_refs_before_link = 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, *cmdarg_tass_labels_file = nullptr;
|
|
strref list_file, allinstr_file;
|
|
strref tass_list_file;
|
|
for (int a = 1; a<argc; a++) {
|
|
if (argv[a][0]=='-') {
|
|
strref arg(argv[a]+1);
|
|
if (arg.get_first()=='i') { assembler.AddIncludeFolder(arg+1); }
|
|
else if (arg.same_str(cmdarg_kickasm) ) { assembler.syntax = SYNTAX_KICKASM; }
|
|
else if (arg.same_str(cmdarg_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(cmdarg_c64)) {
|
|
load_header = true;
|
|
size_header = false;
|
|
} else if (arg.same_str(cmdarg_a2b)) {
|
|
assembler.default_org = 0x0803;
|
|
load_header = true;
|
|
size_header = true;
|
|
} else if (arg.same_str(cmdarg_bin)) {
|
|
load_header = false;
|
|
size_header = false;
|
|
} else if (arg.same_str(cmdarg_a2p)) {
|
|
assembler.default_org = 0x2000;
|
|
load_header = false;
|
|
size_header = false;
|
|
} else if (arg.same_str(cmdarg_a2o)) {
|
|
gs_os_reloc = true;
|
|
} else if (arg.same_str(cmdarg_mrg)) {
|
|
force_merge_sections = true;
|
|
} else if (arg.same_str(cmdarg_sect)) {
|
|
info = true;
|
|
} else if (arg.same_str(cmdarg_endmacro)) {
|
|
assembler.end_macro_directive = true;
|
|
} else if (arg.same_str(cmdarg_xrefimp)) {
|
|
assembler.import_means_xref = true;
|
|
} else if (arg.same_str(cmdarg_references)) {
|
|
show_refs_before_link = true;
|
|
} else if (arg.has_prefix(cmdarg_listing)&&(arg.get_len()==cmdarg_listing.get_len()||arg[cmdarg_listing.get_len()]=='=')) {
|
|
assembler.list_assembly = true;
|
|
list_output = true;
|
|
list_file = arg.after( '=' );
|
|
} else if (arg.has_prefix(cmdarg_tass_listing)&&(arg.get_len()==cmdarg_listing.get_len()||arg[cmdarg_listing.get_len()]=='=')) {
|
|
assembler.list_assembly = true;
|
|
tass_list_output = true;
|
|
tass_list_file = arg.after( '=' );
|
|
} else if (arg.has_prefix(cmdarg_allinstr)&&(arg.get_len()==cmdarg_allinstr.get_len()||arg[cmdarg_allinstr.get_len()]=='=')) {
|
|
gen_allinstr = true;
|
|
allinstr_file = arg.after('=');
|
|
} else if (arg.has_prefix(cmdarg_org)) {
|
|
arg = arg.after('=');
|
|
if (arg && arg.get_first()=='$' && arg.get_len()>1) {
|
|
assembler.default_org = (int)(arg+1).ahextoui();
|
|
} else if (arg.is_number()) { assembler.default_org = (int)arg.atoi(); }
|
|
// force the current section to be org'd
|
|
assembler.AssignAddressToSection(assembler.SectionId(), assembler.default_org);
|
|
} else if (arg.has_prefix(cmdarg_acc)&&arg[cmdarg_acc.get_len()]=='=') {
|
|
assembler.accumulator_16bit = arg.after('=').atoi()==16;
|
|
} else if (arg.has_prefix(cmdarg_xy)&&arg[cmdarg_xy.get_len()]=='=') {
|
|
assembler.index_reg_16bit = arg.after('=').atoi()==16;
|
|
} else if (arg.has_prefix(cmdarg_cpu)&&(arg.get_len()==cmdarg_cpu.get_len()||arg[cmdarg_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(cmdarg_sym)&&(a+1)<argc) {
|
|
sym_file = argv[++a];
|
|
} else if (arg.same_str(cmdarg_obj)&&(a+1)<argc) {
|
|
obj_out_file = argv[++a];
|
|
} else if (arg.same_str(cmdarg_vice)&&(a+1)<argc) {
|
|
vs_file = argv[++a];
|
|
} else if (arg.same_str(cmdarg_tass_labels)&&(a+1)<argc) {
|
|
cmdarg_tass_labels_file = argv[++a];
|
|
} else { printf("Unexpected option " STRREF_FMT "\n", STRREF_ARG(arg)); }
|
|
} else if (!source_filename) { source_filename = argv[a]; }
|
|
else if (!binary_out_name) { binary_out_name = argv[a]; }
|
|
}
|
|
for (int a = 1; a < argc; a++) {
|
|
strref arg(argv[a]);
|
|
printf(STRREF_FMT "\n", STRREF_ARG(arg));
|
|
}
|
|
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: force fixed address code at address\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 (show_refs_before_link) {
|
|
assembler.ShowReferences();
|
|
}
|
|
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_export;
|
|
int addr;
|
|
if (uint8_t *buf = assembler.BuildExport(aAppendNames[e], size_export, addr)) {
|
|
if (FILE *f = fopen(file.c_str(), "wb")) {
|
|
if (load_header) {
|
|
uint8_t load_addr[2] = { (uint8_t)addr, (uint8_t)(addr >> 8) };
|
|
fwrite(load_addr, 2, 1, f);
|
|
}
|
|
if (size_header) {
|
|
uint8_t byte_size[2] = { (uint8_t)size_export, (uint8_t)(size_export >> 8) };
|
|
fwrite(byte_size, 2, 1, f);
|
|
}
|
|
fwrite(buf, size_export, 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 rel = s.pRelocs->begin(); rel != s.pRelocs->end(); ++rel)
|
|
printf("\tReloc value $%x at offs $%x section %d\n", rel->base_value, rel->section_offset, rel->target_section);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// listing after export since addresses are now resolved
|
|
if ( list_output )
|
|
assembler.List(list_file);
|
|
|
|
if( tass_list_output )
|
|
assembler.ListTassStyle(tass_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) {
|
|
uint32_t value = (uint32_t)i->value;
|
|
if (size_t(i->section) < assembler.allSections.size()) { value += assembler.allSections[i->section].start_address; }
|
|
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 monitor commands
|
|
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) {
|
|
uint32_t value = (uint32_t)i->value;
|
|
if (size_t(i->section) < assembler.allSections.size()) { value += assembler.allSections[i->section].start_address; }
|
|
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);
|
|
}
|
|
}
|
|
// export tass labels
|
|
if( cmdarg_tass_labels_file && !srcname.same_str( cmdarg_tass_labels_file ) && !assembler.map.empty() ) {
|
|
if( FILE *f = fopen( cmdarg_tass_labels_file, "w" ) ) {
|
|
for( MapSymbolArray::iterator i = assembler.map.begin(); i != assembler.map.end(); ++i ) {
|
|
uint32_t value = ( uint32_t )i->value;
|
|
if( size_t( i->section ) < assembler.allSections.size() ) { value += assembler.allSections[ i->section ].start_address; }
|
|
if( i->name.same_str( "debugbreak" ) ) {}
|
|
else {
|
|
strown<256> line;
|
|
line.append( i->name );
|
|
line.sprintf_append( "\t= $%04x\n", value);
|
|
fputs(line.c_str(), f);
|
|
}
|
|
}
|
|
fclose( f );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
// free some memory
|
|
assembler.Cleanup();
|
|
}
|
|
}
|
|
return return_value;
|
|
}
|
|
|