gigatron/rom/Contrib/at67/assembler.cpp
2025-01-28 19:17:01 +03:00

2968 lines
119 KiB
C++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmath>
#include <vector>
#include <stack>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <iterator>
#include <algorithm>
#include <cstdarg>
#include "memory.h"
#include "cpu.h"
#include "audio.h"
#include "editor.h"
#include "loader.h"
#include "assembler.h"
#include "expression.h"
#ifndef STAND_ALONE
#include "cpu.h"
#include "editor.h"
#endif
#define BRANCH_ADJUSTMENT 2
#define MAX_DASM_LINES 30
namespace Assembler
{
enum ParseType {PreProcessPass=0, MnemonicPass, CodePass, NumParseTypes};
enum EvaluateResult {Failed=-1, NotFound, Reserved, Duplicate, Skipped, Success};
enum AddressMode {D_AC=0b00000000, X_AC=0b00000100, YD_AC=0b00001000, YX_AC=0b00001100, D_X=0b00010000, D_Y=0b00010100, D_OUT=0b00011000, YXpp_OUT=0b00011100};
enum BusMode {D=0b00000000, RAM=0b00000001, AC=0b00000010, IN=0b00000011};
enum ReservedWords {CallTable=0, StartAddress, SingleStepWatch, DisableUpload, CpuUsageAddressA, CpuUsageAddressB, INCLUDE, MACRO, ENDM, GPRINTF, NumReservedWords};
struct Label
{
uint16_t _address;
std::string _name;
};
struct Equate
{
bool _isCustomAddress;
uint16_t _operand;
std::string _name;
};
struct Instruction
{
bool _isRomAddress;
bool _isCustomAddress;
ByteSize _byteSize;
uint8_t _opcode;
uint8_t _operand0;
uint8_t _operand1;
uint16_t _address;
OpcodeType _opcodeType;
};
struct CallTableEntry
{
uint8_t _operand;
uint16_t _address;
};
struct Macro
{
bool _complete = false;
bool _fromInclude = false;
int _fileStartLine;
std::string _name;
std::string _filename;
std::vector<std::string> _params;
std::vector<std::string> _lines;
};
int _lineNumber;
uint8_t _vSpMin = 0x00;
uint16_t _byteCount = 0;
uint16_t _callTablePtr = 0x0000;
uint16_t _startAddress = DEFAULT_EXEC_ADDRESS;
uint16_t _currentAddress = _startAddress;
uint16_t _currDasmByteCount = 1, _prevDasmByteCount = 1;
uint16_t _currDasmPageByteCount = 0, _prevDasmPageByteCount = 0;
std::string _includePath = ".";
std::vector<Label> _labels;
std::vector<Equate> _equates;
std::vector<Instruction> _instructions;
std::vector<ByteCode> _byteCode;
std::vector<CallTableEntry> _callTableEntries;
std::vector<std::string> _reservedWords;
std::vector<DasmCode> _disassembledCode;
std::map<std::string, InstructionType> _asmOpcodes;
std::map<uint8_t, InstructionDasm> _vcpuOpcodes;
std::map<uint8_t, InstructionDasm> _nativeOpcodes;
std::map<uint16_t, Gprintf> _gprintfs;
std::map<std::string, Define> _defines;
std::stack<std::string> _currentDefine;
uint8_t getvSpMin(void) {return _vSpMin;}
uint16_t getStartAddress(void) {return _startAddress;}
int getPrevDasmByteCount(void) {return _prevDasmByteCount;}
int getCurrDasmByteCount(void) {return _currDasmByteCount;}
int getPrevDasmPageByteCount(void) {return _prevDasmPageByteCount;}
int getCurrDasmPageByteCount(void) {return _currDasmPageByteCount;}
int getDisassembledCodeSize(void) {return int(_disassembledCode.size());}
const std::string& getIncludePath(void) {return _includePath;}
DasmCode* getDisassembledCode(int index) {return &_disassembledCode[index % _disassembledCode.size()];}
void setvSpMin(uint8_t vSpMin) {_vSpMin = vSpMin;}
void setIncludePath(const std::string& includePath) {_includePath = includePath;}
int getAsmOpcodeSize(const std::string& opcodeStr)
{
if(opcodeStr[0] == ';') return 0;
if(_asmOpcodes.find(opcodeStr) != _asmOpcodes.end())
{
return _asmOpcodes[opcodeStr]._byteSize;
}
return 0;
}
int getAsmOpcodeSizeText(const std::string& textStr)
{
for(auto it=_asmOpcodes.begin(); it!=_asmOpcodes.end(); ++it)
{
if(textStr.find(it->first) != std::string::npos && _asmOpcodes.find(it->first) != _asmOpcodes.end())
{
return _asmOpcodes[it->first]._byteSize;
}
}
return 0;
}
void initialiseOpcodes(void)
{
// Gigatron vCPU instructions
_asmOpcodes["ST" ] = {0x5E, 0x00, TwoBytes, vCpu};
_asmOpcodes["STW" ] = {0x2B, 0x00, TwoBytes, vCpu};
_asmOpcodes["STLW" ] = {0xEC, 0x00, TwoBytes, vCpu};
_asmOpcodes["LD" ] = {0x1A, 0x00, TwoBytes, vCpu};
_asmOpcodes["LDI" ] = {0x59, 0x00, TwoBytes, vCpu};
_asmOpcodes["LDWI" ] = {0x11, 0x00, ThreeBytes, vCpu};
_asmOpcodes["LDW" ] = {0x21, 0x00, TwoBytes, vCpu};
_asmOpcodes["LDLW" ] = {0xEE, 0x00, TwoBytes, vCpu};
_asmOpcodes["ADDW" ] = {0x99, 0x00, TwoBytes, vCpu};
_asmOpcodes["SUBW" ] = {0xB8, 0x00, TwoBytes, vCpu};
_asmOpcodes["ADDI" ] = {0xE3, 0x00, TwoBytes, vCpu};
_asmOpcodes["SUBI" ] = {0xE6, 0x00, TwoBytes, vCpu};
_asmOpcodes["LSLW" ] = {0xE9, 0x00, OneByte, vCpu};
_asmOpcodes["INC" ] = {0x93, 0x00, TwoBytes, vCpu};
_asmOpcodes["ANDI" ] = {0x82, 0x00, TwoBytes, vCpu};
_asmOpcodes["ANDW" ] = {0xF8, 0x00, TwoBytes, vCpu};
_asmOpcodes["ORI" ] = {0x88, 0x00, TwoBytes, vCpu};
_asmOpcodes["ORW" ] = {0xFA, 0x00, TwoBytes, vCpu};
_asmOpcodes["XORI" ] = {0x8C, 0x00, TwoBytes, vCpu};
_asmOpcodes["XORW" ] = {0xFC, 0x00, TwoBytes, vCpu};
_asmOpcodes["PEEK" ] = {0xAD, 0x00, OneByte, vCpu};
_asmOpcodes["DEEK" ] = {0xF6, 0x00, OneByte, vCpu};
_asmOpcodes["POKE" ] = {0xF0, 0x00, TwoBytes, vCpu};
_asmOpcodes["DOKE" ] = {0xF3, 0x00, TwoBytes, vCpu};
_asmOpcodes["LUP" ] = {0x7F, 0x00, TwoBytes, vCpu};
_asmOpcodes["BRA" ] = {0x90, 0x00, TwoBytes, vCpu};
_asmOpcodes["CALL" ] = {0xCF, 0x00, TwoBytes, vCpu};
_asmOpcodes["RET" ] = {0xFF, 0x00, OneByte, vCpu};
_asmOpcodes["PUSH" ] = {0x75, 0x00, OneByte, vCpu};
_asmOpcodes["POP" ] = {0x63, 0x00, OneByte, vCpu};
_asmOpcodes["ALLOC"] = {0xDF, 0x00, TwoBytes, vCpu};
_asmOpcodes["SYS" ] = {0xB4, 0x00, TwoBytes, vCpu};
_asmOpcodes["HALT" ] = {0xB4, 0x80, TwoBytes, vCpu};
_asmOpcodes["DEF" ] = {0xCD, 0x00, TwoBytes, vCpu};
_asmOpcodes["CALLI"] = {0x85, 0x00, ThreeBytes, vCpu};
_asmOpcodes["CMPHS"] = {0x1F, 0x00, TwoBytes, vCpu};
_asmOpcodes["CMPHU"] = {0x97, 0x00, TwoBytes, vCpu};
// Gigatron vCPU branch instructions
_asmOpcodes["BEQ"] = {0x35, 0x3F, ThreeBytes, vCpu};
_asmOpcodes["BNE"] = {0x35, 0x72, ThreeBytes, vCpu};
_asmOpcodes["BLT"] = {0x35, 0x50, ThreeBytes, vCpu};
_asmOpcodes["BGT"] = {0x35, 0x4D, ThreeBytes, vCpu};
_asmOpcodes["BLE"] = {0x35, 0x56, ThreeBytes, vCpu};
_asmOpcodes["BGE"] = {0x35, 0x53, ThreeBytes, vCpu};
// Reserved assembler opcodes
_asmOpcodes["DB" ] = {0x00, 0x00, TwoBytes, ReservedDB };
_asmOpcodes["DW" ] = {0x00, 0x00, ThreeBytes, ReservedDW };
_asmOpcodes["DBR"] = {0x00, 0x00, TwoBytes, ReservedDBR};
_asmOpcodes["DWR"] = {0x00, 0x00, ThreeBytes, ReservedDWR};
// Gigatron native instructions
_asmOpcodes[".LD" ] = {0x00, 0x00, TwoBytes, Native};
_asmOpcodes[".NOP" ] = {0x02, 0x00, TwoBytes, Native};
_asmOpcodes[".ANDA"] = {0x20, 0x00, TwoBytes, Native};
_asmOpcodes[".ORA" ] = {0x40, 0x00, TwoBytes, Native};
_asmOpcodes[".XORA"] = {0x60, 0x00, TwoBytes, Native};
_asmOpcodes[".ADDA"] = {0x80, 0x00, TwoBytes, Native};
_asmOpcodes[".SUBA"] = {0xA0, 0x00, TwoBytes, Native};
_asmOpcodes[".ST" ] = {0xC0, 0x00, TwoBytes, Native};
_asmOpcodes[".JMP" ] = {0xE0, 0x00, TwoBytes, Native};
_asmOpcodes[".BGT" ] = {0xE4, 0x00, TwoBytes, Native};
_asmOpcodes[".BLT" ] = {0xE8, 0x00, TwoBytes, Native};
_asmOpcodes[".BNE" ] = {0xEC, 0x00, TwoBytes, Native};
_asmOpcodes[".BEQ" ] = {0xF0, 0x00, TwoBytes, Native};
_asmOpcodes[".BGE" ] = {0xF4, 0x00, TwoBytes, Native};
_asmOpcodes[".BLE" ] = {0xF8, 0x00, TwoBytes, Native};
_asmOpcodes[".BRA" ] = {0xFC, 0x00, TwoBytes, Native};
// Gigatron vCPU instructions
_vcpuOpcodes[0x5E] = {0x5E, 0x00, TwoBytes, vCpu, "ST" };
_vcpuOpcodes[0x2B] = {0x2B, 0x00, TwoBytes, vCpu, "STW" };
_vcpuOpcodes[0xEC] = {0xEC, 0x00, TwoBytes, vCpu, "STLW" };
_vcpuOpcodes[0x1A] = {0x1A, 0x00, TwoBytes, vCpu, "LD" };
_vcpuOpcodes[0x59] = {0x59, 0x00, TwoBytes, vCpu, "LDI" };
_vcpuOpcodes[0x11] = {0x11, 0x00, ThreeBytes, vCpu, "LDWI" };
_vcpuOpcodes[0x21] = {0x21, 0x00, TwoBytes, vCpu, "LDW" };
_vcpuOpcodes[0xEE] = {0xEE, 0x00, TwoBytes, vCpu, "LDLW" };
_vcpuOpcodes[0x99] = {0x99, 0x00, TwoBytes, vCpu, "ADDW" };
_vcpuOpcodes[0xB8] = {0xB8, 0x00, TwoBytes, vCpu, "SUBW" };
_vcpuOpcodes[0xE3] = {0xE3, 0x00, TwoBytes, vCpu, "ADDI" };
_vcpuOpcodes[0xE6] = {0xE6, 0x00, TwoBytes, vCpu, "SUBI" };
_vcpuOpcodes[0xE9] = {0xE9, 0x00, OneByte, vCpu, "LSLW" };
_vcpuOpcodes[0x93] = {0x93, 0x00, TwoBytes, vCpu, "INC" };
_vcpuOpcodes[0x82] = {0x82, 0x00, TwoBytes, vCpu, "ANDI" };
_vcpuOpcodes[0xF8] = {0xF8, 0x00, TwoBytes, vCpu, "ANDW" };
_vcpuOpcodes[0x88] = {0x88, 0x00, TwoBytes, vCpu, "ORI" };
_vcpuOpcodes[0xFA] = {0xFA, 0x00, TwoBytes, vCpu, "ORW" };
_vcpuOpcodes[0x8C] = {0x8C, 0x00, TwoBytes, vCpu, "XORI" };
_vcpuOpcodes[0xFC] = {0xFC, 0x00, TwoBytes, vCpu, "XORW" };
_vcpuOpcodes[0xAD] = {0xAD, 0x00, OneByte, vCpu, "PEEK" };
_vcpuOpcodes[0xF6] = {0xF6, 0x00, OneByte, vCpu, "DEEK" };
_vcpuOpcodes[0xF0] = {0xF0, 0x00, TwoBytes, vCpu, "POKE" };
_vcpuOpcodes[0xF3] = {0xF3, 0x00, TwoBytes, vCpu, "DOKE" };
_vcpuOpcodes[0x7F] = {0x7F, 0x00, TwoBytes, vCpu, "LUP" };
_vcpuOpcodes[0x90] = {0x90, 0x00, TwoBytes, vCpu, "BRA" };
_vcpuOpcodes[0xCF] = {0xCF, 0x00, TwoBytes, vCpu, "CALL" };
_vcpuOpcodes[0xFF] = {0xFF, 0x00, OneByte, vCpu, "RET" };
_vcpuOpcodes[0x75] = {0x75, 0x00, OneByte, vCpu, "PUSH" };
_vcpuOpcodes[0x63] = {0x63, 0x00, OneByte, vCpu, "POP" };
_vcpuOpcodes[0xDF] = {0xDF, 0x00, TwoBytes, vCpu, "ALLOC"};
_vcpuOpcodes[0xB4] = {0xB4, 0x00, TwoBytes, vCpu, "SYS" };
_vcpuOpcodes[0xCD] = {0xCD, 0x00, TwoBytes, vCpu, "DEF" };
_vcpuOpcodes[0x85] = {0x85, 0x00, ThreeBytes, vCpu, "CALLI"};
_vcpuOpcodes[0x1F] = {0x1F, 0x00, TwoBytes, vCpu, "CMPHS"};
_vcpuOpcodes[0x97] = {0x97, 0x00, TwoBytes, vCpu, "CMPHU"};
// Gigatron vCPU branch instructions, (this works because condition code is still unique compared to opcodes)
_vcpuOpcodes[0x3F] = {OPCODE_V_BCC, 0x3F, ThreeBytes, vCpu, "BEQ"};
_vcpuOpcodes[0x72] = {OPCODE_V_BCC, 0x72, ThreeBytes, vCpu, "BNE"};
_vcpuOpcodes[0x50] = {OPCODE_V_BCC, 0x50, ThreeBytes, vCpu, "BLT"};
_vcpuOpcodes[0x4D] = {OPCODE_V_BCC, 0x4D, ThreeBytes, vCpu, "BGT"};
_vcpuOpcodes[0x56] = {OPCODE_V_BCC, 0x56, ThreeBytes, vCpu, "BLE"};
_vcpuOpcodes[0x53] = {OPCODE_V_BCC, 0x53, ThreeBytes, vCpu, "BGE"};
// Gigatron native instructions
_nativeOpcodes[0x00] = {0x00, 0x00, TwoBytes, Native, "LD" };
_nativeOpcodes[0x02] = {0x02, 0x00, TwoBytes, Native, "NOP" };
_nativeOpcodes[0x20] = {0x20, 0x00, TwoBytes, Native, "ANDA"};
_nativeOpcodes[0x40] = {0x40, 0x00, TwoBytes, Native, "ORA" };
_nativeOpcodes[0x60] = {0x60, 0x00, TwoBytes, Native, "XORA"};
_nativeOpcodes[0x80] = {0x80, 0x00, TwoBytes, Native, "ADDA"};
_nativeOpcodes[0xA0] = {0xA0, 0x00, TwoBytes, Native, "SUBA"};
_nativeOpcodes[0xC0] = {0xC0, 0x00, TwoBytes, Native, "ST" };
_nativeOpcodes[0xE0] = {0xE0, 0x00, TwoBytes, Native, "JMP" };
_nativeOpcodes[0xE4] = {0xE4, 0x00, TwoBytes, Native, "BGT" };
_nativeOpcodes[0xE8] = {0xE8, 0x00, TwoBytes, Native, "BLT" };
_nativeOpcodes[0xEC] = {0xEC, 0x00, TwoBytes, Native, "BNE" };
_nativeOpcodes[0xF0] = {0xF0, 0x00, TwoBytes, Native, "BEQ" };
_nativeOpcodes[0xF4] = {0xF4, 0x00, TwoBytes, Native, "BGE" };
_nativeOpcodes[0xF8] = {0xF8, 0x00, TwoBytes, Native, "BLE" };
_nativeOpcodes[0xFC] = {0xFC, 0x00, TwoBytes, Native, "BRA" };
}
void initialise(void)
{
_reservedWords.push_back("_CALLTABLE_");
_reservedWords.push_back("_BREAKPOINT_");
_reservedWords.push_back("_STARTADDRESS_");
_reservedWords.push_back("_SINGLESTEPWATCH_");
_reservedWords.push_back("_DISABLEUPLOAD_");
_reservedWords.push_back("_CPUUSAGEADDRESSA_");
_reservedWords.push_back("_CPUUSAGEADDRESSB_");
_reservedWords.push_back("%INCLUDE");
_reservedWords.push_back("%DEFINE");
_reservedWords.push_back("%IF");
_reservedWords.push_back("%ELSE");
_reservedWords.push_back("%ELSEIF");
_reservedWords.push_back("%ENDIF");
_reservedWords.push_back("%MACRO");
_reservedWords.push_back("%ENDM");
_reservedWords.push_back("%SUB");
_reservedWords.push_back("%ENDS");
_reservedWords.push_back("GPRINTF");
initialiseOpcodes();
}
#ifndef STAND_ALONE
void getDasmCurrAndPrevByteSize(uint16_t address, ByteSize byteSize)
{
// Save current and previous instruction lengths
if(_disassembledCode.size() == 0)
{
_currDasmByteCount = uint16_t(byteSize);
// Attempt to get bytesize of previous instruction
for(uint16_t addr=address-1; addr>=address-3; --addr)
{
uint8_t size = uint8_t(address - addr); // no instruction is longer than 3 bytes
uint8_t inst = Cpu::getRAM(addr);
if(inst == OPCODE_V_BCC) inst = Cpu::getRAM(addr + 1);
if(_vcpuOpcodes.find(inst) != _vcpuOpcodes.end() && _vcpuOpcodes[inst]._opcodeType == vCpu && _vcpuOpcodes[inst]._byteSize == size)
{
_prevDasmByteCount = size;
break;
}
}
}
}
void getDasmCurrAndPrevPageByteSize(int pageSize)
{
// Current page size
_currDasmPageByteCount = 0;
for(int i=0; i<pageSize; i++) _currDasmPageByteCount += _disassembledCode[i]._byteSize;
// Previous page size
_prevDasmPageByteCount = 0;
uint16_t address = _disassembledCode[0]._address;
for(int i=0; i<pageSize; i++)
{
// Get bytesize of previous page worth of instructions
bool foundInstruction = false;
if(address < 4) continue;
for(uint16_t addr=address-1; addr>=address-3; --addr)
{
uint8_t size = uint8_t(address - addr); // no instruction is longer than 3 bytes
uint8_t inst = Cpu::getRAM(addr);
if(inst == OPCODE_V_BCC) inst = Cpu::getRAM(addr + 1);
if(_vcpuOpcodes.find(inst) != _vcpuOpcodes.end() && _vcpuOpcodes[inst]._opcodeType == vCpu && _vcpuOpcodes[inst]._byteSize == size)
{
foundInstruction = true;
_prevDasmPageByteCount += size;
address -= size;
break;
}
}
if(!foundInstruction)
{
_prevDasmPageByteCount++;
address--;
}
}
}
std::string removeBrackets(const char* str)
{
std::string string = str;
Expression::stripChars(string, "[]");
return string;
}
// Adapted from disassemble() in Core\asm.py
bool getNativeMnemonic(uint8_t instruction, uint8_t data, char* mnemonic)
{
uint8_t inst, addr, bus;
// Special case NOP
if(instruction == 0x02 && data == 0x00)
{
strcpy(mnemonic, _nativeOpcodes[instruction]._mnemonic.c_str());
return true;
}
inst = instruction & 0xE0;
addr = instruction & 0x1C;
bus = instruction & 0x03;
bool store = (inst == 0xC0);
bool jump = (inst == 0xE0);
if(_nativeOpcodes.find(inst) == _nativeOpcodes.end()) return false;
// Instruction mnemonic, jump = 0xE0 + (condition codes)
char instStr[8];
(!jump) ? strcpy(instStr, _nativeOpcodes[inst]._mnemonic.c_str()) : strcpy(instStr, _nativeOpcodes[0xE0 + addr]._mnemonic.c_str());
// Effective address string
char addrStr[12];
char regStr[4];
if(!jump)
{
switch(addr)
{
case EA_0D_AC: sprintf(addrStr, "[$%02x]", data); sprintf(regStr, "AC"); break;
case EA_0X_AC: sprintf(addrStr, "[X]"); sprintf(regStr, "AC"); break;
case EA_YD_AC: sprintf(addrStr, "[Y,$%02x]", data); sprintf(regStr, "AC"); break;
case EA_YX_AC: sprintf(addrStr, "[Y,X]"); sprintf(regStr, "AC"); break;
case EA_0D_X: sprintf(addrStr, "[$%02x]", data); sprintf(regStr, "X"); break;
case EA_0D_Y: sprintf(addrStr, "[$%02x]", data); sprintf(regStr, "Y"); break;
case EA_0D_OUT: sprintf(addrStr, "[$%02x]", data); sprintf(regStr, "OUT"); break;
case EA_YX_OUTIX: sprintf(addrStr, "[Y,X++]"); sprintf(regStr, "OUT"); break;
default: break;
}
}
else
{
sprintf(addrStr, "[$%02x]", data);
}
// Bus string
char busStr[8];
switch(bus)
{
case BUS_D: sprintf(busStr, "$%02x", data); break;
case BUS_RAM: (!store) ? strcpy(busStr, addrStr) : strcpy(busStr, ""); break;
case BUS_AC: strcpy(busStr, "AC"); break;
case BUS_IN: strcpy(busStr, "IN"); break;
default: break;
}
// Compose instruction string
if(!jump)
{
if(store)
{
char storeStr[32];
(bus == BUS_AC) ? sprintf(storeStr, "%-4s %s", instStr, addrStr) : sprintf(storeStr, "%-4s %s,%s", instStr, busStr, addrStr);
if(bus == BUS_RAM) sprintf(storeStr, "CTRL %s", removeBrackets(addrStr).c_str());
if(addr == EA_0D_X || addr == EA_0D_Y) sprintf(mnemonic, "%s,%s", storeStr, regStr);
else strcpy(mnemonic, storeStr);
}
else
{
// if reg == AC
(addr <= EA_YX_AC) ? sprintf(mnemonic, "%-4s %s", instStr, busStr) : sprintf(mnemonic, "%-4s %s,%s", instStr, busStr, regStr);
}
}
// Compose jump string
else
{
char jumpStr[32];
switch(addr)
{
case BRA_CC_FAR: sprintf(jumpStr, "%-4s Y,", instStr); break;
default: sprintf(jumpStr, "%-4s ", instStr); break;
}
sprintf(mnemonic, "%-4s%s", jumpStr, busStr);
}
return true;
}
int disassemble(uint16_t address)
{
_disassembledCode.clear();
_currDasmByteCount = 1;
_prevDasmByteCount = 1;
while(_disassembledCode.size() < MAX_DASM_LINES)
{
char dasmText[32];
DasmCode dasmCode;
ByteSize byteSize = OneByte;
uint8_t instruction = 0, data0 = 0, data1 = 0;
Editor::MemoryMode memoryMode = Editor::getMemoryMode();
switch(memoryMode)
{
// Native instructions
case Editor::ROM0:
case Editor::ROM1:
{
instruction = Cpu::getROM(address, 0);
data0 = Cpu::getROM(address, 1);
data1 = 0;
char mnemonic[24];
if(!getNativeMnemonic(instruction, data0, mnemonic))
{
sprintf(dasmText, "%04x $%02x $%02x", address, instruction, data0);
dasmCode._address = address;
address++;
break;
}
sprintf(dasmText, "%04x %s", address, mnemonic);
dasmCode._address = address;
address++;
}
break;
// vCPU instructions
case Editor::RAM:
{
instruction = Cpu::getRAM(address);
data0 = Cpu::getRAM(address + 1);
data1 = Cpu::getRAM(address + 2);
// Invalid instruction or invalid address space
if((_vcpuOpcodes.find(instruction) == _vcpuOpcodes.end() && instruction != OPCODE_V_BCC) ||
(address >= GIGA_CH0_WAV_A && address <= GIGA_CH0_OSC_H) || (address >= GIGA_CH1_WAV_A && address <= GIGA_CH1_OSC_H) ||
(address >= GIGA_CH2_WAV_A && address <= GIGA_CH2_OSC_H) || (address >= GIGA_CH3_WAV_A && address <= GIGA_CH3_OSC_H))
{
sprintf(dasmText, "%04x $%02x", address, instruction);
dasmCode._address = address;
address = (address + 1) & (Memory::getSizeRAM() - 1);
break;
}
// Branch instructions
bool foundBranch = false;
if(instruction == OPCODE_V_BCC)
{
instruction = data0;
if(_vcpuOpcodes.find(instruction) == _vcpuOpcodes.end())
{
sprintf(dasmText, "%04x $%02x", address, instruction);
dasmCode._address = address;
address = (address + 1) & (Memory::getSizeRAM() - 1);
break;
}
foundBranch = true;
}
// Halt instruction
if(instruction == OPCODE_V_HALT && data0 == OPERAND_V_HALT)
{
byteSize = TwoBytes;
sprintf(dasmText, "%04x %-5s", address, "HALT");
}
// Valid instruction
else
{
byteSize = _vcpuOpcodes[instruction]._byteSize;
switch(byteSize)
{
case OneByte: sprintf(dasmText, "%04x %-5s", address, _vcpuOpcodes[instruction]._mnemonic.c_str()); break;
case TwoBytes: sprintf(dasmText, "%04x %-5s $%02x", address, _vcpuOpcodes[instruction]._mnemonic.c_str(), data0); break;
case ThreeBytes: (foundBranch) ? sprintf(dasmText, "%04x %-5s $%02x", address, _vcpuOpcodes[instruction]._mnemonic.c_str(), data1) : sprintf(dasmText, "%04x %-5s $%02x%02x", address, _vcpuOpcodes[instruction]._mnemonic.c_str(), data1, data0); break;
default: break;
}
}
dasmCode._address = address;
address = uint16_t((address + byteSize) & (Memory::getSizeRAM() - 1));
// Save current and previous instruction sizes to allow scrolling
getDasmCurrAndPrevByteSize(dasmCode._address, byteSize);
}
break;
default: break;
}
std::string dasmCodeText = std::string(dasmText);
dasmCode._instruction = instruction;
dasmCode._byteSize = uint8_t(byteSize);
dasmCode._data0 = data0;
dasmCode._data1 = data1;
dasmCode._text = (memoryMode == Editor::RAM) ? Expression::strToUpper(dasmCodeText) : Expression::strToLower(dasmCodeText);
_disassembledCode.push_back(dasmCode);
}
// Save current and previous page instruction sizes to allow page scrolling
if(Editor::getMemoryMode() == Editor::RAM)
{
getDasmCurrAndPrevPageByteSize(MAX_DASM_LINES);
}
else
{
_currDasmPageByteCount = MAX_DASM_LINES;
_prevDasmPageByteCount = MAX_DASM_LINES;
}
return int(_disassembledCode.size());
}
#endif
// Returns true when finished
bool getNextAssembledByte(ByteCode& byteCode, bool debug)
{
static bool isUserCode = false;
if(_byteCount >= _byteCode.size())
{
_byteCount = 0;
if(debug && isUserCode) fprintf(stderr, "\n");
return true;
}
static uint16_t address = 0x0000;
static uint16_t customAddress = 0x0000;
// Get next byte
if(_byteCount == 0) address = _startAddress;
byteCode = _byteCode[_byteCount++];
// New section
if(byteCode._isCustomAddress)
{
address = byteCode._address;
customAddress = byteCode._address;
}
// User code is RAM code or ROM code in user ROM space
isUserCode = !byteCode._isRomAddress || (byteCode._isRomAddress && customAddress >= USER_ROMv1_ADDRESS);
// Seperate sections
if(debug && byteCode._isCustomAddress && isUserCode) fprintf(stderr, "\n");
// 16bit for ROM, 8bit for RAM
if(debug && isUserCode)
{
if(byteCode._isRomAddress)
{
if((address & 0x0001) == 0x0000)
{
fprintf(stderr, "Assembler::getNextAssembledByte() : ROM : %04X %02X", customAddress + (LO_BYTE(address)>>1), byteCode._data);
}
else
{
fprintf(stderr, "%02X\n", byteCode._data);
}
}
else
{
fprintf(stderr, "Assembler::getNextAssembledByte() : RAM : %04X %02X\n", address, byteCode._data);
}
}
address++;
return false;
}
bool parseDefineOffset(const std::string& token, uint16_t& offset, size_t& lbra)
{
size_t rbra;
if(Expression::findMatchingBrackets(token, 0, lbra, rbra))
{
Expression::Numeric value;
if(Expression::parse(token.substr(lbra + 1, rbra - (lbra + 1)), _lineNumber, value))
{
offset = uint16_t(std::lround(value._value));
return true;
}
}
return false;
}
InstructionType getOpcode(const std::string& opcodeStr, uint16_t& offset)
{
offset = 0;
std::string opcode = opcodeStr;
Expression::strToUpper(opcode);
if(_asmOpcodes.find(opcode) != _asmOpcodes.end())
{
return _asmOpcodes[opcode];
}
size_t lbra;
if(parseDefineOffset(opcode, offset, lbra))
{
if(_asmOpcodes.find(opcode.substr(0, lbra)) != _asmOpcodes.end())
{
return _asmOpcodes[opcode.substr(0, lbra)];
}
}
return {0x00, 0x00, BadSize, vCpu};
}
void preProcessExpression(const std::vector<std::string>& tokens, int tokenIndex, std::string& input, bool stripWhiteSpace)
{
input.clear();
// Pre-process
for(int j=tokenIndex; j<int(tokens.size()); j++)
{
// Strip comments
if(tokens[j].find_first_of(";#") != std::string::npos) break;
// Concatenate
input += tokens[j];
}
// Strip white space
if(stripWhiteSpace) Expression::stripWhitespace(input);
}
size_t findSymbol(const std::string& input, const std::string& symbol, size_t pos=0)
{
const size_t len = input.length();
if(pos >= len) return std::string::npos;
const std::string separators = "+-*/().,!?;#'[]<> \t\n\r";
const std::vector<std::string> operators = {"**", ">>", "<<", "==", "!=", "<=", ">="};
for(;;)
{
size_t sep = input.find_first_of(separators, pos);
bool eos = (sep == std::string::npos);
if(eos)
{
for(int i=0; i<int(operators.size()); i++)
{
sep = input.find(operators[i]);
eos = (sep == std::string::npos);
if(eos) break;
}
}
size_t end = eos ? len : sep;
if(input.substr(pos, end-pos) == symbol)
{
break;
}
else if(eos)
{
pos = std::string::npos;
break;
}
pos = sep + 1;
}
return pos;
}
bool applyEquatesToExpression(std::string& expression, const std::vector<Equate>& equates)
{
bool modified = false;
for(int i=0; i<int(equates.size()); i++)
{
for(;;)
{
size_t pos = findSymbol(expression, equates[i]._name);
if(pos == std::string::npos) break; // not found
modified = true;
expression.replace(pos, equates[i]._name.size(), std::to_string(equates[i]._operand));
}
}
return modified;
}
bool applyLabelsToExpression(std::string& expression, const std::vector<Label>& labels, bool nativeCode)
{
bool modified = false;
for(int i=0; i<int(labels.size()); i++)
{
for(;;)
{
size_t pos = findSymbol(expression, labels[i]._name);
if (pos == std::string::npos) break; // not found
modified = true;
uint16_t address = (nativeCode) ? labels[i]._address >>1 : labels[i]._address;
expression.replace(pos, labels[i]._name.size(), std::to_string(address));
}
}
return modified;
}
bool evaluateExpression(std::string input, bool nativeCode, int16_t& result)
{
// Replace equates
applyEquatesToExpression(input, _equates);
// Replace labels
applyLabelsToExpression(input, _labels, nativeCode);
// Strip white space
input.erase(remove_if(input.begin(), input.end(), isspace), input.end());
// Parse expression and return with a result
Expression::Numeric numeric;
bool valid = Expression::parse(input, _lineNumber, numeric);
result = int16_t(std::lround(numeric._value));
return valid;
}
bool searchEquate(const std::string& token, Equate& equate)
{
bool success = false;
for(int i=0; i<int(_equates.size()); i++)
{
if(_equates[i]._name == token)
{
equate = _equates[i];
success = true;
break;
}
}
return success;
}
bool evaluateEquateOperand(const std::string& token, Equate& equate)
{
// Expression equates
Expression::ExpressionType expressionType = Expression::isExpression(token);
if(expressionType == Expression::IsInvalid) return false;
if(expressionType == Expression::HasOperators)
{
int16_t value;
if(!evaluateExpression(token, false, value)) return false;
equate._operand = value;
return true;
}
// Check for existing equate
return searchEquate(token, equate);
}
bool evaluateEquateOperand(const std::vector<std::string>& tokens, int tokenIndex, Equate& equate, bool compoundInstruction)
{
if(tokenIndex >= int(tokens.size())) return false;
// Expression equates
std::string token;
if(compoundInstruction)
{
token = tokens[tokenIndex];
}
else
{
preProcessExpression(tokens, tokenIndex, token, false);
}
return evaluateEquateOperand(token, equate);
}
EvaluateResult evaluateEquates(const std::vector<std::string>& tokens, ParseType parse)
{
std::string token0 = Expression::strUpper(tokens[0]);
std::string token1 = Expression::strUpper(tokens[1]);
if(token1 == "EQU")
{
if(parse == MnemonicPass)
{
Equate equate = {false, 0x0000, tokens[0]};
if(!Expression::stringToU16(tokens[2], equate._operand))
{
if(!evaluateEquateOperand(tokens, 2, equate, false)) return NotFound;
}
// Reserved word, (equate), _callTable_
if(token0 == "_CALLTABLE_")
{
_callTablePtr = equate._operand;
}
// Reserved word, (equate), _startAddress_
else if(token0 == "_STARTADDRESS_")
{
_startAddress = equate._operand;
_currentAddress = _startAddress;
}
#ifndef STAND_ALONE
// Disable upload of the current assembler module
else if(token0 == "_DISABLEUPLOAD_")
{
Loader::disableUploads(equate._operand != 0);
}
// Reserved word, (equate), _singleStepWatch_
else if(token0 == "_SINGLESTEPWATCH_")
{
Editor::setSingleStepAddress(equate._operand);
}
// Start address of vCPU exclusion zone
else if(token0 == "_CPUUSAGEADDRESSA_")
{
Editor::setCpuUsageAddressA(equate._operand);
}
// End address of vCPU exclusion zone
else if(token0 == "_CPUUSAGEADDRESSB_")
{
Editor::setCpuUsageAddressB(equate._operand);
}
#endif
// Standard equates
else
{
// Check for duplicate
equate._name = tokens[0];
if(searchEquate(tokens[0], equate)) return Duplicate;
_equates.push_back(equate);
}
}
else if(parse == CodePass)
{
}
return Success;
}
return Failed;
}
bool searchLabel(const std::string& token, Label& label)
{
bool success = false;
for(int i=0; i<int(_labels.size()); i++)
{
if(token == _labels[i]._name)
{
success = true;
label = _labels[i];
break;
}
}
return success;
}
bool evaluateLabelOperand(const std::string& token, Label& label)
{
// Expression labels
Expression::ExpressionType expressionType = Expression::isExpression(token);
if(expressionType == Expression::IsInvalid) return false;
if(expressionType == Expression::HasOperators)
{
int16_t value;
if(!evaluateExpression(token, false, value)) return false;
label._address = value;
return true;
}
// Check for existing label
return searchLabel(token, label);
}
bool evaluateLabelOperand(const std::vector<std::string>& tokens, int tokenIndex, Label& label, bool compoundInstruction)
{
if(tokenIndex >= int(tokens.size())) return false;
// Expression labels
std::string token;
if(compoundInstruction)
{
token = tokens[tokenIndex];
}
else
{
preProcessExpression(tokens, tokenIndex, token, false);
}
return evaluateLabelOperand(token, label);
}
EvaluateResult EvaluateLabels(const std::vector<std::string>& tokens, ParseType parse, int tokenIndex)
{
if(parse == MnemonicPass)
{
// Check reserved words
for(int i=0; i<int(_reservedWords.size()); i++)
{
if(tokens[tokenIndex] == _reservedWords[i]) return Reserved;
}
Label label;
if(searchLabel(tokens[tokenIndex], label)) return Duplicate;
// Check equates for a custom start address
for(int i=0; i<int(_equates.size()); i++)
{
if(_equates[i]._name == tokens[tokenIndex])
{
_equates[i]._isCustomAddress = true;
_currentAddress = _equates[i]._operand;
break;
}
}
// Normal labels
label = {_currentAddress, tokens[tokenIndex]};
_labels.push_back(label);
}
else if(parse == CodePass)
{
}
return Success;
}
bool handleDefineByte(std::vector<std::string>& tokens, int tokenIndex, const Instruction& instruction, bool createInstruction, uint16_t defineOffset, int& dbSize)
{
bool success = false;
// Handle case where first operand is a string
size_t quote1 = tokens[tokenIndex].find_first_of("'");
size_t quote2 = tokens[tokenIndex].find_last_of("'");
bool quotes = (quote1 != std::string::npos && quote2 != std::string::npos && (quote2 - quote1 > 1));
if(quotes)
{
// Remove escape sequence
std::string token = tokens[tokenIndex].substr(quote1+1, quote2 - (quote1+1));
Expression::stripChars(token, "\\");
if(createInstruction)
{
for(int j=1; j<int(token.size()); j++) // First instruction was created by callee
{
Instruction inst = {instruction._isRomAddress, false, OneByte, uint8_t(token[j]), 0x00, 0x00, 0x0000, instruction._opcodeType};
_instructions.push_back(inst);
}
}
dbSize += int(token.size()) - 1; // First instruction was created by callee
success = true;
}
for(int i=tokenIndex+1; i<int(tokens.size()); i++)
{
// Handle all other variations of strings
quote1 = tokens[i].find_first_of("'");
quote2 = tokens[i].find_last_of("'");
quotes = (quote1 != std::string::npos && quote2 != std::string::npos);
if(quotes)
{
// Remove escape sequence
std::string token = tokens[i].substr(quote1+1, quote2 - (quote1+1));
Expression::stripChars(token, "\\");
if(createInstruction)
{
for(int j=0; j<int(token.size()); j++)
{
Instruction inst = {instruction._isRomAddress, false, OneByte, uint8_t(token[j]), 0x00, 0x00, 0x0000, instruction._opcodeType};
_instructions.push_back(inst);
}
}
dbSize += int(token.size());
success = true;
}
// Non string tokens
else
{
// Strip comments
if(tokens[i].find_first_of(";#") != std::string::npos) break;
uint8_t operand;
success = Expression::stringToU8(tokens[i], operand);
if(!success)
{
// Search equates
Equate equate;
Label label;
if((success = evaluateEquateOperand(tokens[i], equate)) == true)
{
operand = uint8_t(equate._operand);
}
// Search labels
else if((success = evaluateLabelOperand(tokens[i], label)) == true)
{
operand = uint8_t(label._address);
}
else
{
// Normal expression
if(Expression::isExpression(tokens[i]) == Expression::HasOperators)
{
Expression::Numeric value;
if(Expression::parse(tokens[i], _lineNumber, value))
{
operand = uint8_t(std::lround(value._value));
success = true;
}
}
else
{
break;
}
}
}
if(createInstruction)
{
bool hasDefineOffset = (defineOffset != 0);
uint16_t address = (hasDefineOffset) ? _currentAddress : 0x0000;
Instruction inst = {instruction._isRomAddress, hasDefineOffset, OneByte, operand, 0x00, 0x00, address, instruction._opcodeType};
_currentAddress += defineOffset;
_instructions.push_back(inst);
}
dbSize++;
}
}
return success;
}
bool handleDefineWord(const std::vector<std::string>& tokens, int tokenIndex, const Instruction& instruction, bool createInstruction, uint16_t defineOffset, int& dwSize)
{
bool success = false;
for(int i=tokenIndex+1; i<int(tokens.size()); i++)
{
// Strip comments
if(tokens[i].find_first_of(";#") != std::string::npos)
{
success = true;
break;
}
uint16_t operand;
success = Expression::stringToU16(tokens[i], operand);
if(!success)
{
// Search equates
Equate equate;
Label label;
if((success = evaluateEquateOperand(tokens[i], equate)) == true)
{
operand = equate._operand;
}
// Search labels
else if((success = evaluateLabelOperand(tokens[i], label)) == true)
{
operand = label._address;
}
else
{
// Normal expression
if(Expression::isExpression(tokens[i]) == Expression::HasOperators)
{
Expression::Numeric value;
if(Expression::parse(tokens[i], _lineNumber, value))
{
operand = int16_t(std::lround(value._value));
success = true;
}
}
else
{
break;
}
}
}
if(createInstruction)
{
bool hasDefineOffset = (defineOffset != 0);
uint16_t address = (hasDefineOffset) ? _currentAddress : 0x0000;
Instruction inst = {instruction._isRomAddress, hasDefineOffset, TwoBytes, uint8_t(LO_BYTE(operand)), uint8_t(HI_BYTE(operand)), 0x00, address, instruction._opcodeType};
_currentAddress += defineOffset * 2;
_instructions.push_back(inst);
}
dwSize += 2;
}
return success;
}
bool handleNativeOperand(const std::string& token, uint8_t& operand)
{
Expression::ExpressionType expressionType = Expression::isExpression(token);
if(expressionType == Expression::IsInvalid) return false;
if(expressionType == Expression::HasOperators)
{
// Parse expression and return with a result
int16_t value;
if(!evaluateExpression(token, true, value)) return false;
operand = uint8_t(value);
return true;
}
Label label;
if(searchLabel(token, label))
{
operand = uint8_t(LO_BYTE(label._address >>1));
return true;
}
Equate equate;
if(searchEquate(token, equate))
{
operand = uint8_t(equate._operand);
return true;
}
return Expression::stringToU8(token, operand);
}
bool handleNativeInstruction(const std::vector<std::string>& tokens, int tokenIndex, uint8_t& opcode, uint8_t& operand)
{
std::string input, token;
preProcessExpression(tokens, tokenIndex, input, true);
size_t openBracket = input.find_first_of("[");
size_t closeBracket = input.find_first_of("]");
bool noBrackets = (openBracket == std::string::npos && closeBracket == std::string::npos);
bool validBrackets = (openBracket != std::string::npos && closeBracket != std::string::npos && closeBracket > openBracket);
size_t comma1 = input.find_first_of(",");
size_t comma2 = input.find_first_of(",", comma1+1);
bool noCommas = (comma1 == std::string::npos && comma2 == std::string::npos);
bool oneComma = (comma1 != std::string::npos && comma2 == std::string::npos);
bool twoCommas = (comma1 != std::string::npos && comma2 != std::string::npos);
operand = 0x00;
// NOP
if(opcode == 0x02) return true;
// Accumulator
if(input == "AC" || input == "ac")
{
opcode |= BusMode::AC;
return true;
}
// Jump
if(opcode == 0xE0)
{
// y,[D]
if(validBrackets && oneComma && comma1 < openBracket)
{
opcode |= BusMode::RAM;
token = input.substr(openBracket+1, closeBracket - (openBracket+1));
return handleNativeOperand(token, operand);
}
// y,D
if(noBrackets && oneComma)
{
token = input.substr(comma1+1, input.size() - (comma1+1));
return handleNativeOperand(token, operand);
}
return false;
}
// Branch
if(opcode >= 0xE4)
{
token = input;
if(validBrackets) {opcode |= BusMode::RAM; token = input.substr(openBracket+1, closeBracket - (openBracket+1));}
if(Expression::stringToU8(token, operand)) return true;
return handleNativeOperand(token, operand);
}
// IN or IN,[D]
if(input.find("IN") != std::string::npos || input.find("in") != std::string::npos)
{
opcode |= BusMode::IN;
// IN,[D]
if(validBrackets && oneComma && comma1 < openBracket)
{
token = input.substr(openBracket+1, closeBracket - (openBracket+1));
return handleNativeOperand(token, operand);
}
// IN
return true;
}
// D
if(noBrackets && noCommas) return handleNativeOperand(input, operand);
// Read or write
(opcode != 0xC0) ? opcode |= BusMode::RAM : opcode |= BusMode::AC;
// [D] or [X]
if(validBrackets && noCommas)
{
token = input.substr(openBracket+1, closeBracket - (openBracket+1));
if(token == "X" || token == "x") {opcode |= AddressMode::X_AC; return true;}
return handleNativeOperand(token, operand);
}
// AC,X or AC,Y or AC,OUT or D,X or D,Y or D,OUT or [D],X or [D],Y or [D],OUT or D,[D] or D,[X] or D,[Y] or [Y,D] or [Y,X] or [Y,X++]
if(oneComma)
{
token = input.substr(comma1+1, input.size() - (comma1+1));
if(token == "X" || token == "x") opcode |= AddressMode::D_X;
if(token == "Y" || token == "y") opcode |= AddressMode::D_Y;
if(token == "OUT" || token == "out") opcode |= AddressMode::D_OUT;
token = input.substr(0, comma1);
// AC,X or AC,Y or AC,OUT
if(token == "AC" || token == "ac") {opcode &= 0xFC; opcode |= BusMode::AC; return true;}
// D,X or D,Y or D,OUT
if(noBrackets)
{
opcode &= 0xFC; return handleNativeOperand(token, operand);
}
// [D],X or [D],Y or [D],OUT or D,[D] or D,[X] or D,[Y] or [Y,D] or [Y,X] or [Y,X++]
if(validBrackets)
{
if(comma1 > closeBracket) token = input.substr(openBracket+1, closeBracket - (openBracket+1));
else if(comma1 < openBracket) {opcode &= 0xFC; token = input.substr(0, comma1);}
else if(comma1 > openBracket && comma1 < closeBracket)
{
token = input.substr(openBracket+1, comma1 - (openBracket+1));
if(token != "Y" && token != "y") return false;
token = input.substr(comma1+1, closeBracket - (comma1+1));
if(token == "X" || token == "x") {opcode |= AddressMode::YX_AC; return true;}
if(token == "X++" || token == "x++") {opcode |= AddressMode::YXpp_OUT; return true;}
opcode |= AddressMode::YD_AC;
}
return handleNativeOperand(token, operand);
}
return false;
}
// D,[Y,X] or D,[Y,X++]
if(validBrackets && twoCommas && comma1 < openBracket && comma2 > openBracket && comma2 < closeBracket)
{
token = input.substr(0, comma1);
if(!handleNativeOperand(token, operand)) return false;
token = input.substr(openBracket+1, comma2 - (openBracket+1));
if(token != "Y" && token != "y") return false;
opcode &= 0xFC; // reset bus bits to D
token = input.substr(comma2+1, closeBracket - (comma2+1));
if(token == "X" || token == "x") {opcode |= YX_AC; return true;}
if(token == "X++" || token == "x++") {opcode |= YXpp_OUT; return true;}
return false;
}
// [Y,X++],out
if(validBrackets && twoCommas && comma1 > openBracket && comma2 > closeBracket)
{
token = input.substr(openBracket+1, comma1 - (openBracket+1));
if(token != "Y" && token != "y") return false;
token = input.substr(comma1+1, closeBracket - (comma1+1));
if(token == "X" || token == "x") {opcode |= YX_AC; return true;}
if(token == "X++" || token == "x++") {opcode |= YXpp_OUT; return true;}
return false;
}
return false;
}
void packByteCode(Instruction& instruction, ByteCode& byteCode)
{
switch(instruction._byteSize)
{
case OneByte:
{
byteCode._isRomAddress = instruction._isRomAddress;
byteCode._isCustomAddress = instruction._isCustomAddress;
byteCode._data = instruction._opcode;
byteCode._address = instruction._address;
_byteCode.push_back(byteCode);
}
break;
case TwoBytes:
{
byteCode._isRomAddress = instruction._isRomAddress;
byteCode._isCustomAddress = instruction._isCustomAddress;
byteCode._data = instruction._opcode;
byteCode._address = instruction._address;
_byteCode.push_back(byteCode);
byteCode._isRomAddress = instruction._isRomAddress;
byteCode._isCustomAddress = false;
byteCode._data = instruction._operand0;
byteCode._address = 0x0000;
_byteCode.push_back(byteCode);
}
break;
case ThreeBytes:
{
byteCode._isRomAddress = instruction._isRomAddress;
byteCode._isCustomAddress = instruction._isCustomAddress;
byteCode._data = instruction._opcode;
byteCode._address = instruction._address;
_byteCode.push_back(byteCode);
byteCode._isRomAddress = instruction._isRomAddress;
byteCode._isCustomAddress = false;
byteCode._data = instruction._operand0;
byteCode._address = 0x0000;
_byteCode.push_back(byteCode);
byteCode._isRomAddress = instruction._isRomAddress;
byteCode._isCustomAddress = false;
byteCode._data = instruction._operand1;
byteCode._address = 0x0000;
_byteCode.push_back(byteCode);
}
break;
default: break;
}
}
void packByteCodeBuffer(void)
{
// Pack instructions
ByteCode byteCode;
uint16_t segmentOffset = 0x0000;
uint16_t segmentAddress = 0x0000;
for(int i=0; i<int(_instructions.size()); i++)
{
// Segment RAM instructions into 256 byte pages for .gt1 file format
if(!_instructions[i]._isRomAddress)
{
// Save start of segment
if(_instructions[i]._isCustomAddress)
{
segmentOffset = 0x0000;
segmentAddress = _instructions[i]._address;
}
// Force a new segment, (this could fail if an instruction straddles a page boundary, but
// the page boundary crossing detection logic will stop the assembler before we get here)
if(!_instructions[i]._isCustomAddress && (HI_BYTE(segmentAddress + segmentOffset) != HI_BYTE(segmentAddress)))
{
segmentAddress += segmentOffset;
segmentOffset = 0x0000;
_instructions[i]._isCustomAddress = true;
_instructions[i]._address = segmentAddress;
}
segmentOffset += uint16_t(_instructions[i]._byteSize);
}
packByteCode(_instructions[i], byteCode);
}
// Append call table
if(_callTablePtr && _callTableEntries.size())
{
// _callTable grows downwards, pointer is 2 bytes below the bottom of the table by the time we get here
for(int i=int(_callTableEntries.size())-1; i>=0; i--)
{
byteCode._isRomAddress = false;
byteCode._isCustomAddress = true; // calltable entries can be non-sequential because of 0x80, (ONE_CONST_ADDRESS)
byteCode._data = LO_BYTE(_callTableEntries[i]._address);
byteCode._address = LO_BYTE(_callTableEntries[i]._operand);
_byteCode.push_back(byteCode);
byteCode._isRomAddress = false;
byteCode._isCustomAddress = false;
byteCode._data = HI_BYTE(_callTableEntries[i]._address);
byteCode._address = LO_BYTE(_callTableEntries[i]._operand + 1);
_byteCode.push_back(byteCode);
}
}
}
bool checkInvalidAddress(ParseType parse, uint16_t currentAddress, uint16_t instructionSize, const Instruction& instruction, const LineToken& lineToken, const std::string& filename, int lineNumber)
{
// Check for audio channel stomping
if(parse == CodePass && !instruction._isRomAddress)
{
uint16_t start = currentAddress;
uint16_t end = currentAddress + instructionSize - 1;
if((start >= GIGA_CH0_WAV_A && start <= GIGA_CH0_OSC_H) || (end >= GIGA_CH0_WAV_A && end <= GIGA_CH0_OSC_H) ||
(start >= GIGA_CH1_WAV_A && start <= GIGA_CH1_OSC_H) || (end >= GIGA_CH1_WAV_A && end <= GIGA_CH1_OSC_H) ||
(start >= GIGA_CH2_WAV_A && start <= GIGA_CH2_OSC_H) || (end >= GIGA_CH2_WAV_A && end <= GIGA_CH2_OSC_H) ||
(start >= GIGA_CH3_WAV_A && start <= GIGA_CH3_OSC_H) || (end >= GIGA_CH3_WAV_A && end <= GIGA_CH3_OSC_H))
{
fprintf(stderr, "Assembler::checkInvalidAddress() : Warning, audio channel boundary compromised, (if you've disabled the audio channels, then ignore this warning) ");
fprintf(stderr, "Assembler::checkInvalidAddress() : '%s:%d' : 0x%04X <-> 0x%04X\nAssembler::checkInvalidAddress() : '%s'\nAssembler::checkInvalidAddress()\n",
filename.c_str(), lineNumber+1, start, end, lineToken._text.c_str());
}
}
// Check for page boundary crossings
if(parse == CodePass && (instruction._opcodeType == vCpu || instruction._opcodeType == Native))
{
static uint16_t customAddress = 0x0000;
if(instruction._isCustomAddress) customAddress = instruction._address;
uint16_t oldAddress = (instruction._isRomAddress) ? customAddress + (LO_BYTE(currentAddress)>>1) : currentAddress;
currentAddress += instructionSize - 1;
uint16_t newAddress = (instruction._isRomAddress) ? customAddress + (LO_BYTE(currentAddress)>>1) : currentAddress;
if((oldAddress >>8) != (newAddress >>8))
{
fprintf(stderr, "Assembler::checkInvalidAddress() : '%s:%d' : page boundary compromised : %04X : %04X : '%s'\n", filename.c_str(), lineNumber+1, oldAddress, newAddress, lineToken._text.c_str());
return false;
}
}
return true;
}
bool handleInclude(const std::vector<std::string>& tokens, const std::string& lineToken, int lineIndex, std::vector<LineToken>& includeLineTokens)
{
// Check include syntax
if(tokens.size() != 2)
{
fprintf(stderr, "Assembler::handleInclude() : '%s:%d' : bad %%include statement\n", lineToken.c_str(), lineIndex);
return false;
}
std::string filepath = _includePath + "/" + tokens[1];
std::replace(filepath.begin(), filepath.end(), '\\', '/');
std::ifstream infile(filepath);
if(!infile.is_open())
{
fprintf(stderr, "Assembler::handleInclude() : failed to open file '%s'\n", filepath.c_str());
return false;
}
// Collect lines from include file
int lineNumber = lineIndex;
while(!infile.eof())
{
LineToken includeLineToken = {true, lineNumber++ - lineIndex, "", filepath};
std::getline(infile, includeLineToken._text);
includeLineTokens.push_back(includeLineToken);
if(!infile.good() && !infile.eof())
{
fprintf(stderr, "Assembler::handleInclude() : '%s:%d' : bad lineToken '%s'\n", filepath.c_str(), lineNumber - lineIndex, includeLineToken._text.c_str());
return false;
}
}
return true;
}
bool handleMacros(const std::vector<Macro>& macros, std::vector<LineToken>& lineTokens)
{
// Incomplete macros
for(int i=0; i<int(macros.size()); i++)
{
if(!macros[i]._complete)
{
fprintf(stderr, "Assembler::handleMacros() : '%s:%d' : bad macro, missing 'ENDM'\n", macros[i]._filename.c_str(), macros[i]._fileStartLine);
return false;
}
}
// Delete original macros
auto filter = [](LineToken& lineToken)
{
static bool foundMacro = false;
if(lineToken._text.find("%MACRO") != std::string::npos)
{
foundMacro = true;
return true;
}
if(foundMacro)
{
if(lineToken._text.find("%ENDM") != std::string::npos) foundMacro = false;
return true;
}
return false;
};
lineTokens.erase(std::remove_if(lineTokens.begin(), lineTokens.end(), filter), lineTokens.end());
// Find and expand macro
int macroInstanceId = 0;
for(int m=0; m<int(macros.size()); m++)
{
bool macroMissing = true;
bool macroMissingParams = true;
Macro macro = macros[m];
for(auto itLine=lineTokens.begin(); itLine!=lineTokens.end();)
{
// Lines containing only white space are skipped
LineToken lineToken = *itLine;
size_t nonWhiteSpace = lineToken._text.find_first_not_of(" \n\r\f\t\v");
if(nonWhiteSpace == std::string::npos)
{
++itLine;
continue;
}
// Tokenise current line
std::vector<std::string> tokens = Expression::tokeniseLine(lineToken._text);
// Find macro
bool macroSuccess = false;
for(int t=0; t<int(tokens.size()); t++)
{
if(tokens[t] == macro._name)
{
macroMissing = false;
if(tokens.size() - t > macro._params.size())
{
macroMissingParams = false;
std::vector<std::string> labels;
std::vector<LineToken> macroLines;
// Create substitute lines
for(int ml=0; ml<int(macro._lines.size()); ml++)
{
// Tokenise macro line
std::vector<std::string> mtokens = Expression::tokeniseLine(macro._lines[ml]);
// Save labels
nonWhiteSpace = macro._lines[ml].find_first_not_of(" \n\r\f\t\v");
if(nonWhiteSpace == 0) labels.push_back(mtokens[0]);
// Replace parameters
for(int mt=0; mt<int(mtokens.size()); mt++)
{
for(int p=0; p<int(macro._params.size()); p++)
{
//if(mtokens[mt] == macro._params[p]) mtokens[mt] = tokens[t + 1 + p];
size_t param = mtokens[mt].find(macro._params[p]);
if(param != std::string::npos)
{
mtokens[mt].erase(param, macro._params[p].size());
mtokens[mt].insert(param, tokens[t + 1 + p]);
}
}
}
// New macro line using any existing label
LineToken macroLine = {false, 0, "", ""};
macroLine._text = (t > 0 && ml == 0) ? tokens[0] : "";
// Append to macro line
for(int mt=0; mt<int(mtokens.size()); mt++)
{
// Don't prefix macro labels with a space
if(nonWhiteSpace != 0 || mt != 0) macroLine._text += " ";
macroLine._text += mtokens[mt];
}
macroLines.push_back(macroLine);
}
// Insert substitute lines
for(int ml=0; ml<int(macro._lines.size()); ml++)
{
// Delete macro caller
if(ml == 0) itLine = lineTokens.erase(itLine);
// Each instance of a macro's labels are made unique
for(int i=0; i<int(labels.size()); i++)
{
size_t labelFoundPos = macroLines[ml]._text.find(labels[i]);
if(labelFoundPos != std::string::npos) macroLines[ml]._text.insert(labelFoundPos + labels[i].size(), std::to_string(macroInstanceId));
}
// Insert macro lines
itLine = lineTokens.insert(itLine, macroLines[ml]);
++itLine;
}
macroInstanceId++;
macroSuccess = true;
}
}
}
if(!macroSuccess) ++itLine;
}
if(macroMissing)
{
//fprintf(stderr, "Assembler::handleMacros() : '%s:%d' : warning, macro '%s' is never called\n", macro._filename.c_str(), macro._fileStartLine, macro._name.c_str());
continue;
}
if(macroMissingParams)
{
fprintf(stderr, "Assembler::handleMacros() : '%s:%d' : missing macro parameters for '%s'\n", macro._filename.c_str(), macro._fileStartLine, macro._name.c_str());
return false;
}
}
return true;
}
bool handleMacroStart(const std::string& filename, const LineToken& lineToken, const std::vector<std::string>& tokens, Macro& macro, int adjustedLineIndex)
{
int lineNumber = (lineToken._fromInclude) ? lineToken._includeLineNumber + 1 : adjustedLineIndex + 1;
std::string macroFileName = (lineToken._fromInclude) ? lineToken._includeName : filename;
// Check macro syntax
if(tokens.size() < 2)
{
fprintf(stderr, "Assembler::handleMacroStart() : '%s:%d' : bad macro, missing name\n", macroFileName.c_str(), lineNumber);
return false;
}
macro._name = tokens[1];
macro._fromInclude = lineToken._fromInclude;
macro._fileStartLine = lineNumber;
macro._filename = macroFileName;
// Save params
for(int i=2; i<int(tokens.size()); i++) macro._params.push_back(tokens[i]);
return true;
}
bool handleMacroEnd(std::vector<Macro>& macros, Macro& macro)
{
// Check for duplicates
for(int i=0; i<int(macros.size()); i++)
{
if(macro._name == macros[i]._name)
{
fprintf(stderr, "Assembler::handleMacroEnd() : '%s:%d' : bad macro, duplicate name '%s'\n", macro._filename.c_str(), macro._fileStartLine, macro._name.c_str());
return false;
}
}
macro._complete = true;
macros.push_back(macro);
macro._name = "";
macro._lines.clear();
macro._params.clear();
macro._complete = false;
return true;
}
void clearDefines(void)
{
_defines.clear();
while(!_currentDefine.empty()) _currentDefine.pop();
}
bool createDefine(const std::string& filename, const std::vector<std::string>& tokens, int adjustedLineIndex)
{
if(tokens.size() < 2 || tokens.size() > 3)
{
fprintf(stderr, "Assembler::createDefine() : '%s:%d' : %%define requires one or two params\n", filename.c_str(), adjustedLineIndex);
return false;
}
// Define name
std::string defineName = tokens[1];
if(_defines.find(defineName) != _defines.end())
{
fprintf(stderr, "Assembler::createDefine() : '%s:%d' : found duplicate define '%s'\n", filename.c_str(), adjustedLineIndex, defineName.c_str());
return false;
}
// Define value
int16_t defineValue = 0;
if(tokens.size() == 3)
{
if(!evaluateExpression(tokens[2], false, defineValue))
{
fprintf(stderr, "Assembler::createDefine() : '%s:%d' : found invalid define value '%s'\n", filename.c_str(), adjustedLineIndex, tokens[2].c_str());
return false;
}
}
_defines[defineName] = {true, false, defineValue, defineName};
return true;
}
bool handleIfDefine(const std::string& filename, const std::vector<std::string>& tokens, int adjustedLineIndex)
{
if(tokens.size() != 2)
{
fprintf(stderr, "Assembler::handleIfDefine() : '%s:%d' : %%if requires one param\n", filename.c_str(), adjustedLineIndex);
return false;
}
std::string define = tokens[1];
if(_defines.find(define) == _defines.end())
{
_defines[define] = {false, false, 0, define};
}
// Push current define to stack
_currentDefine.push(define);
return true;
}
bool handleEndIfDefine(const std::string& filename, const std::vector<std::string>& tokens, int adjustedLineIndex)
{
if(tokens.size() != 1)
{
fprintf(stderr, "Assembler::handleEndIfDefine() : '%s:%d' : %%endif requires no params\n", filename.c_str(), adjustedLineIndex);
return false;
}
if(_currentDefine.empty())
{
fprintf(stderr, "Assembler::handleEndIfDefine() : '%s:%d' : syntax error, no valid define\n", filename.c_str(), adjustedLineIndex);
return false;
}
// If enabled was toggled by an else then reset it
std::string define = _currentDefine.top();
if(_defines[define]._toggle == true)
{
_defines[define]._enabled = !_defines[define]._enabled;
_defines[define]._toggle = false;
}
// Pop current define from stack
_currentDefine.pop();
return true;
}
bool handleElseDefine(const std::string& filename, const std::vector<std::string>& tokens, int adjustedLineIndex)
{
if(tokens.size() != 1)
{
fprintf(stderr, "Assembler::handleElseDefine() : '%s:%d' : %%else requires no params\n", filename.c_str(), adjustedLineIndex);
return false;
}
if(_currentDefine.empty())
{
fprintf(stderr, "Assembler::handleElseDefine() : '%s:%d' : syntax error, no valid define\n", filename.c_str(), adjustedLineIndex);
return false;
}
// Toggle current define
std::string define = _currentDefine.top();
_defines[define]._enabled = !_defines[define]._enabled;
_defines[define]._toggle = true;
return true;
}
bool isCurrentDefineDisabled(void)
{
// Check top of define stack
if(!_currentDefine.empty())
{
std::string define = _currentDefine.top();
if(_defines.find(define) != _defines.end() && !_defines[define]._enabled)
{
return true;
}
}
return false;
}
int16_t getRuntimeVersion(void)
{
int16_t runtimeVersion = 0;
if(_defines.find("RUNTIME_VERSION") != _defines.end())
{
runtimeVersion = _defines["RUNTIME_VERSION"]._value;
}
return runtimeVersion;
}
bool preProcess(const std::string& filename, std::vector<LineToken>& lineTokens, bool doMacros)
{
Macro macro;
std::vector<Macro> macros;
bool buildingMacro = false;
int adjustedLineIndex = 0;
for(auto itLine=lineTokens.begin(); itLine != lineTokens.end();)
{
// Lines containing only white space are skipped
LineToken lineToken = *itLine;
size_t nonWhiteSpace = lineToken._text.find_first_not_of(" \n\r\f\t\v");
if(nonWhiteSpace == std::string::npos)
{
++itLine;
++adjustedLineIndex;
continue;
}
bool eraseLine = false;
int lineIndex = int(itLine - lineTokens.begin()) + 1;
// Strip comments
size_t commentPos = lineToken._text.find_first_of(";#");
if(commentPos != std::string::npos)
{
lineToken._text = lineToken._text.substr(0, commentPos);
}
// Tokenise current line
std::vector<std::string> tokens = Expression::tokeniseLine(lineToken._text);
// Valid pre-processor commands
if(tokens.size() > 0)
{
Expression::strToUpper(tokens[0]);
// Remove subroutine header and footer
if(tokens[0] == "%SUB" || tokens[0] == "%ENDS")
{
itLine = lineTokens.erase(itLine);
continue;
}
// Include
if(tokens[0] == "%INCLUDE")
{
std::vector<LineToken> includeLineTokens;
if(!handleInclude(tokens, lineToken._text, lineIndex, includeLineTokens)) return false;
// Recursively include everything in order
if(!preProcess(filename, includeLineTokens, false))
{
fprintf(stderr, "Assembler::preProcess() : '%s:%d' : bad include file : '%s'\n", filename.c_str(), adjustedLineIndex, tokens[1].c_str());
return false;
}
// Remove original include line and replace with include text
itLine = lineTokens.erase(itLine);
itLine = lineTokens.insert(itLine, includeLineTokens.begin(), includeLineTokens.end());
++adjustedLineIndex -= int(includeLineTokens.end() - includeLineTokens.begin());
eraseLine = true;
}
// Include path
else if(tokens[0] == "%INCLUDEPATH" && tokens.size() > 1)
{
if(Expression::isStringValid(tokens[1]))
{
// Strip quotes
std::string includePath = tokens[1];
includePath.erase(0, 1);
includePath.erase(includePath.size()-1, 1);
// Prepend current file path to relative paths
if(includePath.find(":") == std::string::npos && includePath[0] != '/')
{
std::string filepath = Loader::getFilePath();
size_t slash = filepath.find_last_of("\\/");
filepath = (slash != std::string::npos) ? filepath.substr(0, slash) : ".";
includePath = filepath + "/" + includePath;
}
_includePath = includePath;
itLine = lineTokens.erase(itLine);
eraseLine = true;
}
}
// Define
else if(tokens[0] == "%DEFINE")
{
if(!createDefine(filename, tokens, 3)) return false;
itLine = lineTokens.erase(itLine);
eraseLine = true;
}
// If
else if(tokens[0] == "%IF")
{
if(!handleIfDefine(filename, tokens, adjustedLineIndex)) return false;
itLine = lineTokens.erase(itLine);
eraseLine = true;
}
// EndIf
else if(tokens[0] == "%ENDIF")
{
if(!handleEndIfDefine(filename, tokens, adjustedLineIndex)) return false;
itLine = lineTokens.erase(itLine);
eraseLine = true;
}
// Else
else if(tokens[0] == "%ELSE")
{
if(!handleElseDefine(filename, tokens, adjustedLineIndex)) return false;
itLine = lineTokens.erase(itLine);
eraseLine = true;
}
// Check if there is a current define and delete code if define is disabled
else if(!_currentDefine.empty())
{
std::string define = _currentDefine.top();
if(_defines.find(define) == _defines.end())
{
fprintf(stderr, "Assembler::preProcess() : '%s:%d' : define '%s' does not exist\n", filename.c_str(), adjustedLineIndex, define.c_str());
return false;
}
if(!_defines[define]._enabled)
{
itLine = lineTokens.erase(itLine);
eraseLine = true;
}
}
// Build macro
else if(doMacros)
{
if(tokens[0] == "%MACRO" && !buildingMacro)
{
if(!handleMacroStart(filename, lineToken, tokens, macro, adjustedLineIndex)) return false;
buildingMacro = true;
}
else if(buildingMacro && tokens[0] == "%ENDM")
{
if(!handleMacroEnd(macros, macro)) return false;
buildingMacro = false;
}
else if(buildingMacro)
{
macro._lines.push_back(lineToken._text);
}
}
}
if(!eraseLine)
{
++itLine;
++adjustedLineIndex;
}
}
// Check matching %if %endif
if(!_currentDefine.empty())
{
fprintf(stderr, "Assembler::preProcess() : '%s' : missing '%%endif'\n", filename.c_str());
return false;
}
// Handle complete macros
if(doMacros && !handleMacros(macros, lineTokens)) return false;
return true;
}
int getAsmOpcodeSize(const std::string& filename, std::vector<LineToken>& lineTokens)
{
// Pre-processor
if(!preProcess(filename, lineTokens, true)) return -1;
// Tokenise lines
int vasmSize = 0;
for(int i=0; i<int(lineTokens.size()); i++)
{
std::vector<std::string> tokens = Expression::tokeniseLine(lineTokens[i]._text);
for(int j=0; j<int(tokens.size()); j++)
{
std::string token = Expression::strToUpper(tokens[j]);
vasmSize += getAsmOpcodeSize(token);
}
}
return vasmSize;
}
int getAsmOpcodeSizeFile(const std::string& filename)
{
std::ifstream infile(filename);
if(!infile.is_open())
{
fprintf(stderr, "Assembler::getAsmOpcodeSizeFile() : failed to open file : '%s'\n", filename.c_str());
return -1;
}
// Get file
int numLines = 0;
LineToken lineToken;
std::vector<LineToken> lineTokens;
while(!infile.eof())
{
std::getline(infile, lineToken._text);
lineTokens.push_back(lineToken);
if(!infile.good() && !infile.eof())
{
fprintf(stderr, "Assembler::getAsmOpcodeSizeFile() : '%s:%d' : bad lineToken '%s'\n", filename.c_str(), numLines+1, lineToken._text.c_str());
return -1;
}
numLines++;
}
return getAsmOpcodeSize(filename, lineTokens);
}
#ifndef STAND_ALONE
bool handleBreakPoints(ParseType parse, const std::string& lineToken, int lineNumber)
{
UNREFERENCED_PARAM(lineNumber);
std::string input = lineToken;
Expression::strToUpper(input);
if(input.find("_BREAKPOINT_") != std::string::npos)
{
if(parse == MnemonicPass) Editor::addVpcBreakPoint(_currentAddress);
return true;
}
return false;
}
bool addGprintf(const Gprintf& gprintf, uint16_t address)
{
if(_gprintfs.find(address) != _gprintfs.end()) return false;
_gprintfs[address] = gprintf;
return true;
}
bool parseGprintfFormat(const std::string& fmtstr, const std::vector<std::string>& variables, std::vector<Gprintf::Var>& vars, std::vector<std::string>& subs)
{
const char* fmt = fmtstr.c_str();
std::string sub;
char chr;
int width = 0, index = 0;
bool foundToken = false;
while((chr = *fmt++) != 0)
{
if(index + 1 > int(variables.size())) return false;
if(chr == '%' || foundToken)
{
foundToken = true;
Gprintf::Format format = Gprintf::Int;
sub += chr;
switch(chr)
{
case '0':
{
// Maximum field width of 16 digits
width = strtol(fmt, nullptr, 10) % 17;
}
break;
case 'c': format = Gprintf::Chr; break;
case 'd': format = Gprintf::Int; break;
case 'b': format = Gprintf::Bin; break;
case 'q':
case 'o': format = Gprintf::Oct; break;
case 'x': format = Gprintf::Hex; break;
case 's': format = Gprintf::Str; break;
default: break;
}
if(chr == 'c' || chr == 'd' || chr == 'b' || chr == 'q' || chr == 'o' || chr == 'x' || chr == 's')
{
foundToken = false;
Gprintf::Var var = {0, format, width, 0x0000, variables[index++]};
vars.push_back(var);
subs.push_back(sub);
sub.clear();
width = 0;
}
}
}
return true;
}
bool handleGprintf(ParseType parse, const std::string& lineToken, int lineNumber)
{
std::string input = lineToken;
Expression::strToUpper(input);
// Handle non commented GPRINTF
size_t gprintfPos = input.find("GPRINTF");
if(gprintfPos != std::string::npos && input.find_last_of(';', gprintfPos) == std::string::npos)
{
size_t lbra, rbra;
if(Expression::findMatchingBrackets(lineToken, 0, lbra, rbra))
{
size_t quote1 = lineToken.find_first_of("\"", lbra+1);
size_t quote2 = lineToken.find_last_of("\"", rbra-1);
bool quotes = (quote1 != std::string::npos && quote2 != std::string::npos && (quote2 - quote1 > 0));
if(quotes)
{
if(parse == MnemonicPass)
{
std::string formatText = lineToken.substr(quote1+1, quote2 - (quote1+1));
std::string variableText = lineToken.substr(quote2+1, rbra - (quote2+1));
std::vector<Gprintf::Var> vars;
std::vector<std::string> subs;
std::vector<std::string> variables = Expression::tokenise(variableText, ',');
parseGprintfFormat(formatText, variables, vars, subs);
Gprintf gprintf = {_currentAddress, lineNumber, lineToken, formatText, vars, subs};
if(_gprintfs.find(_currentAddress) != _gprintfs.end())
{
fprintf(stderr, "Assembler::handleGprintf() : '%s:%d' : gprintf at address 0x%0x4 already exists\n", lineToken.c_str(), lineNumber, _currentAddress);
return false;
}
_gprintfs[_currentAddress] = gprintf;
}
return true;
}
}
fprintf(stderr, "Assembler::handleGprintf() : '%s:%d' : bad gprintf format\n", lineToken.c_str(), lineNumber);
return false;
}
return false;
}
bool parseGprintfVar(const std::string& param, uint16_t& data)
{
bool success = Expression::stringToU16(param, data);
if(!success)
{
std::vector<std::string> tokens;
tokens.push_back(param);
// Search equates
Equate equate;
Label label;
if((success = evaluateEquateOperand(tokens, 0, equate, false)) == true)
{
data = equate._operand;
}
// Search labels
else if((success = evaluateLabelOperand(tokens, 0, label, false)) == true)
{
data = label._address;
}
// Normal expression
else
{
if(Expression::isExpression(param) == Expression::HasOperators)
{
Expression::Numeric value;
if(Expression::parse(param, _lineNumber, value))
{
data = int16_t(std::lround(value._value));
success = true;
}
}
}
}
return success;
}
bool parseGprintfs(void)
{
for(auto it=_gprintfs.begin(); it!=_gprintfs.end(); it++ )
{
Gprintf& gprintf = it->second;
for(int j=0; j<int(gprintf._vars.size()); j++)
{
uint16_t addr = 0x0000;
std::string token = gprintf._vars[j]._text;
// Strip white space
token.erase(remove_if(token.begin(), token.end(), isspace), token.end());
gprintf._vars[j]._text = token;
// Check for indirection
size_t asterisk = token.find_first_of("*");
if(asterisk != std::string::npos)
{
// Don't overwrites gtBASIC vars
if(gprintf._vars[j]._indirection == 0) gprintf._vars[j]._indirection = 1;
token = token.substr(asterisk + 1);
}
if(!parseGprintfVar(token, addr))
{
fprintf(stderr, "Assembler::parseGprintfs() : '%s:%d' : error in gprintf(), missing label or equate '%s'\n", gprintf._lineToken.c_str(), gprintf._lineNumber, token.c_str());
_gprintfs.erase(it);
return false;
}
gprintf._vars[j]._data = addr;
}
}
return true;
}
bool getGprintfString(const Gprintf& gprintf, std::string& gstring)
{
gstring = gprintf._format;
size_t subIndex = 0;
for(int i=0; i<int(gprintf._vars.size()); i++)
{
char token[256];
// Use indirection if required
uint16_t data = (gprintf._vars[i]._indirection) ? Cpu::getRAM16(gprintf._vars[i]._data) : gprintf._vars[i]._data;
// Maximum field width of 16 digits
uint8_t width = gprintf._vars[i]._width % 17;
std::string fieldWidth = "%";
if(width) fieldWidth = std::string("%0" + std::to_string(width));
switch(gprintf._vars[i]._format)
{
case Gprintf::Chr: fieldWidth += "c"; sprintf(token, fieldWidth.c_str(), data); break;
case Gprintf::Int: fieldWidth += "d"; sprintf(token, fieldWidth.c_str(), data); break;
case Gprintf::Oct: fieldWidth += "o"; sprintf(token, fieldWidth.c_str(), data); break;
case Gprintf::Hex: fieldWidth += "x"; sprintf(token, fieldWidth.c_str(), data); break;
// Strings are always indirect and assume that length is first byte
case Gprintf::Str:
{
// gtBASIC strings are dereferenced by 2 levels of indirection
uint16_t addr = (gprintf._vars[i]._indirection == 2) ? Cpu::getRAM16(gprintf._vars[i]._data) : gprintf._vars[i]._data;
// Maximum length of 254, first byte is length, last byte is '0' trailing delimiter
uint8_t length = Cpu::getRAM(addr) % 254;
for(uint16_t j=0; j<length; j++) token[j] = Cpu::getRAM(addr + j + 1);
token[length] = 0;
}
break;
case Gprintf::Bin:
{
for(int j=width-1; j>=0; j--)
{
token[width-1 - j] = '0' + ((data >> j) & 1);
}
token[width] = 0;
}
break;
default: return false;
}
// Replace substrings
subIndex = gstring.find(gprintf._subs[i], subIndex);
if(subIndex != std::string::npos)
{
gstring.erase(subIndex, gprintf._subs[i].size());
gstring.insert(subIndex, token);
}
}
return true;
}
void handleGprintfs(void)
{
if(_gprintfs.size() == 0) return;
if(_gprintfs.find(Cpu::getVPC()) != _gprintfs.end())
{
std::string gstring;
const Gprintf& gprintf = _gprintfs[Cpu::getVPC()];
getGprintfString(gprintf, gstring);
fprintf(stderr, "gprintf() : 0x%04X : %s\n", gprintf._address, gstring.c_str());
}
}
#endif
uint8_t sysHelper(const std::string& opcodeStr, uint16_t operand, const std::string& filename, int lineNumber)
{
std::string opcode = opcodeStr;
Expression::strToUpper(opcode);
if(opcode != "SYS") return uint8_t(operand);
if((operand & 0x0001) || operand < 28 || operand > 284)
{
fprintf(stderr, "Assembler::sysHelper() : '%s:%d' : SYS operand '%d' must be an even constant in [28, 284]\n", filename.c_str(), lineNumber, operand);
return uint8_t(operand);
}
return uint8_t((270 - operand / 2) & 0x00FF);
}
void clearAssembler(bool dontClearGprintfs)
{
_byteCode.clear();
_labels.clear();
_equates.clear();
_instructions.clear();
_callTableEntries.clear();
if(!dontClearGprintfs) _gprintfs.clear();
clearDefines();
_vSpMin = 0x00;
_callTablePtr = 0x0000;
Expression::setExprFunc(Expression::expression);
#ifndef STAND_ALONE
Editor::clearVpcBreakPoints();
#endif
}
bool assemble(const std::string& filename, uint16_t startAddress, bool dontClearGprintfs)
{
std::ifstream infile(filename);
if(!infile.is_open())
{
fprintf(stderr, "Assembler::assemble() : failed to open file '%s'\n", filename.c_str());
return false;
}
clearAssembler(dontClearGprintfs);
_startAddress = startAddress;
_currentAddress = _startAddress;
#ifndef STAND_ALONE
Loader::disableUploads(false);
#endif
// Get file
int numLines = 0;
LineToken lineToken;
std::vector<LineToken> lineTokens;
while(!infile.eof())
{
std::getline(infile, lineToken._text);
lineTokens.push_back(lineToken);
if(!infile.good() && !infile.eof())
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : bad lineToken '%s'\n", filename.c_str(), numLines+1, lineToken._text.c_str());
return false;
}
numLines++;
}
// Pre-processor
if(!preProcess(filename, lineTokens, true)) return false;
fprintf(stderr, "\n*******************************************************\n");
fprintf(stderr, "* Assembling file : '%s'\n", filename.c_str());
fprintf(stderr, "*******************************************************\n");
numLines = int(lineTokens.size());
// The mnemonic pass we evaluate all the equates and labels, the code pass is for the opcodes and operands
for(int parse=MnemonicPass; parse<NumParseTypes; parse++)
{
for(_lineNumber=0; _lineNumber<numLines; _lineNumber++)
{
lineToken = lineTokens[_lineNumber];
// Lines containing only white space are skipped
size_t nonWhiteSpace = lineToken._text.find_first_not_of(" \n\r\f\t\v");
if(nonWhiteSpace == std::string::npos) continue;
int tokenIndex = 0;
// Tokenise current line
std::vector<std::string> tokens = Expression::tokeniseLine(lineToken._text);
if(tokens.size() == 0)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : unknown error '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
// Comments
if(tokens.size() > 0 && tokens[0].find_first_of(";#") != std::string::npos) continue;
#ifndef STAND_ALONE
// Gprintf lines are skipped
if(handleGprintf(ParseType(parse), lineToken._text, _lineNumber+1)) continue;
// _breakPoint_ lines are skipped
if(handleBreakPoints(ParseType(parse), lineToken._text, _lineNumber+1)) continue;
#endif
// Starting address, labels and equates
if(nonWhiteSpace == 0)
{
if(tokens.size() >= 2)
{
EvaluateResult result = evaluateEquates(tokens, (ParseType)parse);
if(result == NotFound)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : missing equate '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
else if(result == Duplicate)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : duplicate equate '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
// Skip equate lines
else if(result == Success)
{
continue;
}
// Labels
result = EvaluateLabels(tokens, (ParseType)parse, tokenIndex);
if(result == Reserved)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : can't use a reserved word in label '%s'\n", filename.c_str(), _lineNumber+1, tokens[tokenIndex].c_str());
return false;
}
else if(result == Duplicate)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : duplicate label '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
}
// On to the next token even if we failed with this one
if(tokens.size() > 1) tokenIndex++;
}
// Opcode
bool operandValid = false;
uint16_t defineOffset;
std::string opcodeStr = tokens[tokenIndex++];
InstructionType instructionType = getOpcode(opcodeStr, defineOffset);
uint8_t opcode0 = instructionType._opcode0;
uint8_t opcode1 = instructionType._opcode1;
int outputSize = instructionType._byteSize;
OpcodeType opcodeType = instructionType._opcodeType;
Instruction instruction = {false, false, ByteSize(outputSize), opcode0, 0x00, 0x00, _currentAddress, opcodeType};
if(outputSize == BadSize)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : bad Opcode '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
// Compound instructions that require a Mnemonic pass
bool compoundInstruction = false;
if(opcodeType == ReservedDB || opcodeType == ReservedDBR)
{
compoundInstruction = true;
if(parse == MnemonicPass)
{
outputSize = OneByte; // first instruction has already been parsed
if(tokenIndex + 1 < int(tokens.size()))
{
if(!handleDefineByte(tokens, tokenIndex, instruction, false, defineOffset, outputSize))
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : bad DB data '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
}
}
}
else if(opcodeType == ReservedDW || opcodeType == ReservedDWR)
{
compoundInstruction = true;
if(parse == MnemonicPass)
{
outputSize = TwoBytes; // first instruction has already been parsed
if(tokenIndex + 1 < int(tokens.size()))
{
if(!handleDefineWord(tokens, tokenIndex, instruction, false, defineOffset, outputSize))
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : bad DW data '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
}
}
}
if(parse == CodePass)
{
// Native NOP
if(opcodeType == Native && opcode0 == 0x02)
{
operandValid = true;
}
// Missing operand
else if((outputSize == TwoBytes || outputSize == ThreeBytes) && int(tokens.size()) <= tokenIndex)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : missing operand/s '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
// First instruction inherits start address
if(_instructions.size() == 0)
{
instruction._address = _startAddress;
instruction._isCustomAddress = true;
_currentAddress = _startAddress;
}
// Custom address
for(int i=0; i<int(_equates.size()); i++)
{
if(_equates[i]._name == tokens[0] && _equates[i]._isCustomAddress)
{
instruction._address = _equates[i]._operand;
instruction._isCustomAddress = true;
_currentAddress = _equates[i]._operand;
}
}
// Operand
switch(outputSize)
{
case OneByte:
{
_instructions.push_back(instruction);
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(outputSize), instruction, lineToken, filename, _lineNumber)) return false;
}
break;
case TwoBytes:
{
uint8_t operand = 0x00;
// HALT
if(opcodeType == vCpu && opcode0 == OPCODE_V_HALT && opcode1 == OPERAND_V_HALT)
{
operand = opcode1;
operandValid = true;
}
// BRA
else if(opcodeType == vCpu && opcode0 == OPCODE_V_BRA)
{
// Search for branch label
Label label;
if(evaluateLabelOperand(tokens, tokenIndex, label, false))
{
operandValid = true;
operand = uint8_t(label._address) - BRANCH_ADJUSTMENT;
}
// Allow branches to raw hex values, lets hope the user knows what he is doing
else if(Expression::stringToU8(tokens[tokenIndex], operand))
{
operandValid = true;
operand -= BRANCH_ADJUSTMENT;
}
else
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : label '%s' missing\n", filename.c_str(), _lineNumber+1, tokens[tokenIndex].c_str());
return false;
}
}
// CALL+
else if(opcodeType == vCpu && opcode0 == OPCODE_V_CALL)
{
// Search for call label
if(_callTablePtr && operand != GIGA_V_AC)
{
Label label;
if(evaluateLabelOperand(tokens, tokenIndex, label, false))
{
// Search for address
bool newLabel = true;
uint16_t address = uint16_t(label._address);
for(int i=0; i<int(_callTableEntries.size()); i++)
{
if(_callTableEntries[i]._address == address)
{
operandValid = true;
operand = _callTableEntries[i]._operand;
newLabel = false;
break;
}
}
// Found a new call address label, put it's address into the call table and point the call instruction to the call table
if(newLabel)
{
operandValid = true;
operand = uint8_t(LO_BYTE(_callTablePtr));
CallTableEntry entry = {operand, address};
_callTableEntries.push_back(entry);
_callTablePtr -= 0x0002;
// Avoid ONE_CONST_ADDRESS
if(_callTablePtr == ONE_CONST_ADDRESS)
{
fprintf(stderr, "Assembler::assemble() : warning, (safe to ignore), Calltable : 0x%02x : collided with : 0x%02x : on line %d\n", _callTablePtr, ONE_CONST_ADDRESS, _lineNumber+1);
_callTablePtr -= 0x0002;
}
else if(_callTablePtr+1 == ONE_CONST_ADDRESS)
{
fprintf(stderr, "Assembler::assemble() : warning, (safe to ignore), Calltable : 0x%02x : collided with : 0x%02x : on line %d\n", _callTablePtr+1, ONE_CONST_ADDRESS, _lineNumber+1);
_callTablePtr -= 0x0001;
}
}
}
}
// CALL that doesn't use the call table, ('CALL GIGA_V_AC', usually to save zero page memory at the expense of code size and code speed).
else
{
Equate equate;
if((operandValid = evaluateEquateOperand(tokens, tokenIndex, equate, false)) == true)
{
operand = uint8_t(equate._operand);
}
else
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : label '%s' missing\n", filename.c_str(), _lineNumber+1, tokens[tokenIndex].c_str());
return false;
}
}
}
// All other non native 2 byte instructions
if(opcodeType != Native && !operandValid)
{
Label label;
Equate equate;
// String
size_t quote1 = tokens[tokenIndex].find_first_of("'");
size_t quote2 = tokens[tokenIndex].find_last_of("'");
bool quotes = (quote1 != std::string::npos && quote2 != std::string::npos && (quote2 - quote1 > 1));
if(quotes)
{
operand = sysHelper(opcodeStr, uint16_t(tokens[tokenIndex][quote1+1]), filename, _lineNumber+1);
}
// Search equates
else if((operandValid = evaluateEquateOperand(tokens, tokenIndex, equate, compoundInstruction)) == true)
{
operand = sysHelper(opcodeStr, equate._operand, filename, _lineNumber+1);
}
// Search labels
else if((operandValid = evaluateLabelOperand(tokens, tokenIndex, label, compoundInstruction)) == true)
{
operand = sysHelper(opcodeStr, label._address, filename, _lineNumber+1);
}
else if(Expression::isExpression(tokens[tokenIndex]) == Expression::HasOperators)
{
Expression::Numeric value;
std::string input;
preProcessExpression(tokens, tokenIndex, input, true);
if(!Expression::parse(input, _lineNumber, value)) return false;
operand = sysHelper(opcodeStr, uint16_t(std::lround(value._value)), filename, _lineNumber+1);
operandValid = true;
}
else
{
uint16_t operandU16 = 0;
operandValid = Expression::stringToU16(tokens[tokenIndex], operandU16);
if(!operandValid)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : invalid label/Equate '%s'\n", filename.c_str(), _lineNumber+1, tokens[tokenIndex].c_str());
return false;
}
operand = sysHelper(opcodeStr, operandU16, filename, _lineNumber+1);
}
}
// Native instructions
if(opcodeType == Native)
{
if(!operandValid)
{
if(!handleNativeInstruction(tokens, tokenIndex, opcode0, operand))
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : native instruction '%s' is malformed\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
}
instruction._isRomAddress = true;
instruction._opcode = opcode0;
instruction._operand0 = uint8_t(LO_BYTE(operand));
_instructions.push_back(instruction);
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(outputSize), instruction, lineToken, filename, _lineNumber)) return false;
#ifndef STAND_ALONE
uint16_t add = instruction._address>>1;
uint8_t opc = Cpu::getROM(add, 0);
uint8_t ope = Cpu::getROM(add, 1);
if(instruction._opcode != opc || instruction._operand0 != ope)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : ROM Native instruction mismatch : 0x%04X : ASM=0x%02X%02X : ROM=0x%02X%02X\n", filename.c_str(), _lineNumber+1, add,
instruction._opcode, instruction._operand0,
opc, ope);
// Fix mismatched instruction?
//instruction._opcode = opc;
//instruction._operand0 = ope;
//_instructions.back() = instruction;
}
#endif
}
// Reserved assembler opcode DB, (define byte)
else if(opcodeType == ReservedDB || opcodeType == ReservedDBR)
{
// Push first operand
outputSize = OneByte;
instruction._isRomAddress = (opcodeType == ReservedDBR) ? true : false;
instruction._byteSize = ByteSize(outputSize);
instruction._opcode = uint8_t(LO_BYTE(operand));
if(defineOffset != 0)
{
instruction._address = _currentAddress;
instruction._isCustomAddress = true;
_currentAddress += defineOffset;
}
_instructions.push_back(instruction);
// Push any remaining operands
if(tokenIndex + 1 < int(tokens.size()))
{
if(!handleDefineByte(tokens, tokenIndex, instruction, true, defineOffset, outputSize))
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : bad DB data '%s'\n", filename.c_str(), _lineNumber+1, lineToken._text.c_str());
return false;
}
}
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(outputSize), instruction, lineToken, filename, _lineNumber)) return false;
}
// Normal instructions
else
{
instruction._operand0 = operand;
_instructions.push_back(instruction);
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(outputSize), instruction, lineToken, filename, _lineNumber)) return false;
}
}
break;
case ThreeBytes:
{
// BCC
if(opcode1)
{
// Search for branch label
Label label;
uint8_t operand = 0x00;
if(evaluateLabelOperand(tokens, tokenIndex, label, false))
{
operand = uint8_t(label._address) - BRANCH_ADJUSTMENT;
}
else
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : label '%s' missing\n", filename.c_str(), _lineNumber+1, tokens[tokenIndex].c_str());
return false;
}
instruction._operand0 = opcode1;
instruction._operand1 = LO_BYTE(operand);
_instructions.push_back(instruction);
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(outputSize), instruction, lineToken, filename, _lineNumber)) return false;
}
// All other 3 byte instructions
else
{
uint16_t operand = 0x0000;
Label label;
Equate equate;
// Search equates
if((operandValid = evaluateEquateOperand(tokens, tokenIndex, equate, compoundInstruction)) == true)
{
operand = equate._operand;
}
// Search labels
else if((operandValid = evaluateLabelOperand(tokens, tokenIndex, label, compoundInstruction)) == true)
{
operand = label._address;
}
else if(Expression::isExpression(tokens[tokenIndex]) == Expression::HasOperators)
{
Expression::Numeric value;
std::string input;
preProcessExpression(tokens, tokenIndex, input, true);
if(!Expression::parse((char*)input.c_str(), _lineNumber, value)) return false;
operand = int16_t(std::lround(value._value));
operandValid = true;
}
else
{
operandValid = Expression::stringToU16(tokens[tokenIndex], operand);
if(!operandValid)
{
fprintf(stderr, "Assembler::assemble() : '%s:%d' : invalid label/equate '%s'\n", filename.c_str(), _lineNumber+1, tokens[tokenIndex].c_str());
return false;
}
}
// Reserved assembler opcode DW, (define word)
if(opcodeType == ReservedDW || opcodeType == ReservedDWR)
{
// Push first operand
outputSize = TwoBytes;
instruction._isRomAddress = (opcodeType == ReservedDWR) ? true : false;
instruction._byteSize = ByteSize(outputSize);
instruction._opcode = uint8_t(LO_BYTE(operand));
instruction._operand0 = uint8_t(HI_BYTE(operand));
if(defineOffset != 0)
{
instruction._address = _currentAddress;
instruction._isCustomAddress = true;
_currentAddress += defineOffset * 2;
}
_instructions.push_back(instruction);
// Push any remaining operands
if(tokenIndex + 1 < int(tokens.size())) handleDefineWord(tokens, tokenIndex, instruction, true, defineOffset, outputSize);
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(outputSize), instruction, lineToken, filename, _lineNumber)) return false;
}
// Normal instructions
else
{
instruction._operand0 = uint8_t(LO_BYTE(operand));
instruction._operand1 = uint8_t(HI_BYTE(operand));
_instructions.push_back(instruction);
if(!checkInvalidAddress(ParseType(parse), _currentAddress, uint16_t(instruction._byteSize), instruction, lineToken, filename, _lineNumber)) return false;
}
}
}
break;
default: break;
}
}
_currentAddress += uint16_t(outputSize);
}
}
// Pack byte code buffer from instruction buffer
packByteCodeBuffer();
#ifndef STAND_ALONE
// Parse gprintf labels, equates and expressions
if(!parseGprintfs()) return false;
#endif
return true;
}
}