assembler nearly works
This commit is contained in:
parent
8cfcc54e12
commit
680e952d04
13
Makefile
13
Makefile
@ -10,11 +10,18 @@ CXXFLAGS += $(shell pkgconf sdl2 --libs)
|
||||
build_dir = build
|
||||
obj_dir = $(build_dir)/obj
|
||||
|
||||
sources := $(shell find src/ -name *.cpp)
|
||||
sources := $(shell find src/ -name *.cpp -and -not -name *main.cpp)
|
||||
|
||||
all: $(build_dir)/vc5
|
||||
vc5_sources := $(sources) src/main.cpp
|
||||
asm_sources := $(sources) src/asm_main.cpp
|
||||
|
||||
$(build_dir)/vc5: $(sources:%.cpp=$(obj_dir)/%.o)
|
||||
all: $(build_dir)/vc5 $(build_dir)/asm
|
||||
|
||||
$(build_dir)/vc5: $(vc5_sources:%.cpp=$(obj_dir)/%.o)
|
||||
@mkdir -p $(dir $@)
|
||||
g++ $^ -o $@ $(CXXFLAGS) $(LDFLAGS)
|
||||
|
||||
$(build_dir)/asm: $(asm_sources:%.cpp=$(obj_dir)/%.o)
|
||||
@mkdir -p $(dir $@)
|
||||
g++ $^ -o $@ $(CXXFLAGS) $(LDFLAGS)
|
||||
|
||||
|
||||
87
programs/boot.vc5asm
Normal file
87
programs/boot.vc5asm
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
|
||||
const KBD_STATUS 0x1ffc
|
||||
const KBD_CODE 0x1ffe
|
||||
const VCD 0x2000
|
||||
|
||||
const FL_EQ 0x2
|
||||
const FL_EQ 0x4
|
||||
|
||||
const KBD_FLAG_IS_RELEASE 0x1
|
||||
|
||||
mov rsp, 0x1000
|
||||
mov rbp, rsp
|
||||
|
||||
mov r0, 512
|
||||
mov r1, 1
|
||||
dskr r0, r1
|
||||
|
||||
; setup video character display (VCD)
|
||||
; descriptor table structure:
|
||||
; [addr + 0] memory map base
|
||||
mov r0, VCD
|
||||
mov [0x700], r0
|
||||
lvcd 0x700
|
||||
|
||||
; setup keyboard (KBD)
|
||||
; descriptor table structure:
|
||||
; [addr + 0] status address
|
||||
; [addr + 2] keycode address
|
||||
; [addr + 4] keyboard interrupt handler
|
||||
mov r0, KBD_STATUS
|
||||
mov [0x702], r0
|
||||
mov r0, KBD_CODE
|
||||
mov [0x704], r0
|
||||
mov r0, key_press_int
|
||||
mov [0x706], r0
|
||||
lkbd 0x702
|
||||
|
||||
; character counter
|
||||
mov r0, 0
|
||||
mov [0x600], r0
|
||||
|
||||
main_loop:
|
||||
hlt
|
||||
jmp main_loop
|
||||
|
||||
key_press_int:
|
||||
mov r0, [KBD_STATUS]
|
||||
and r0, r0, KBD_FLAG_IS_RELEASE
|
||||
jnz r0, .leave
|
||||
|
||||
mov r0, [0x600]
|
||||
add r0, r0, VCD
|
||||
mov r1, [KBC_CODE]
|
||||
cmp r1, 44 ; spacebar
|
||||
mov r2, rfl
|
||||
and r2, r2, FL_EQ
|
||||
jnz r2, .incr
|
||||
add r1, r1, 61
|
||||
mov byte [r0], r1
|
||||
.incr:
|
||||
mov r0, [0x600]
|
||||
add r0, r0, 1
|
||||
mov [0x600], r0
|
||||
.leave:
|
||||
reti
|
||||
|
||||
;scancode_to_char:
|
||||
; cmp r1, 61
|
||||
;
|
||||
; mov r2, rfl
|
||||
; and r2, r2, FL_LT
|
||||
; jnz .not_letter
|
||||
;
|
||||
; ; if r1 > 86 { goto .not_letter }
|
||||
; cmp r1, 86
|
||||
; mov r2, rfl
|
||||
; xor r2, 0xffff
|
||||
; and r2, r2, FL_EQ | FL_LT
|
||||
;
|
||||
;
|
||||
; mov r4, rfl
|
||||
; and r4, r4, FL_EQ
|
||||
; shr r4, r4, 2
|
||||
|
||||
|
||||
|
||||
58
src/asm_main.cpp
Normal file
58
src/asm_main.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "assembler.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <print>
|
||||
#include <string_view>
|
||||
|
||||
using namespace std::literals;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto input_filename = std::string_view();
|
||||
auto output_filename = std::string_view();
|
||||
|
||||
int i = 0;
|
||||
while (i < argc) {
|
||||
auto arg = std::string_view(argv[i]);
|
||||
if (arg == "--help") {
|
||||
std::println("asm <input file> -o <output file>");
|
||||
return EXIT_SUCCESS;
|
||||
} else if (arg == "-o") {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
std::println(stderr, "error: expected filename after -o");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
output_filename = argv[i];
|
||||
i += 1;
|
||||
} else if (arg.starts_with("-")) {
|
||||
std::println(stderr, "error: unknown flag \"{}\"", arg);
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
input_filename = argv[i];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (input_filename.empty()) {
|
||||
std::println(stderr, "error: no input file specified");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
auto input_path = fs::path(input_filename);
|
||||
auto output_path = fs::path(output_filename);
|
||||
|
||||
if (output_path.empty()) {
|
||||
output_path = input_path;
|
||||
output_path.replace_extension("o");
|
||||
}
|
||||
|
||||
auto result = vc5::tools::assemble_file(input_path, output_path);
|
||||
|
||||
if (not result) {
|
||||
std::println(stderr, "error: {}", result.error());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <print>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@ -48,9 +49,14 @@ auto vc5::tools::assemble_file(fs::path input_path, fs::path output_path)
|
||||
input_file.read(&text[0], size);
|
||||
}
|
||||
|
||||
auto assembler = Assembler(text);
|
||||
auto input_filename = std::string(input_path);
|
||||
|
||||
auto assembler = Assembler(input_filename, text);
|
||||
auto result = assembler.assemble_file();
|
||||
|
||||
if (not result)
|
||||
return std::unexpected(result.error());
|
||||
|
||||
auto& program = *result;
|
||||
|
||||
{
|
||||
@ -72,15 +78,20 @@ auto vc5::tools::assemble_file(fs::path input_path, fs::path output_path)
|
||||
auto Assembler::assemble_file()
|
||||
-> std::expected<std::vector<uint8_t>, std::string>
|
||||
{
|
||||
auto parser = Parser(m_text);
|
||||
auto parser = Parser(m_filename, m_text);
|
||||
auto lines = std::vector<std::unique_ptr<Line>>();
|
||||
|
||||
while (true) {
|
||||
parser.eat_whitespace();
|
||||
if (parser.is_done()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (parser.next_is_const()) {
|
||||
auto stmt = parser.parse_const();
|
||||
if (not stmt) {
|
||||
return std::unexpected("parsing failed");
|
||||
parser.try_recover();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value = eval_operand_to_imm(*stmt->expr);
|
||||
@ -94,7 +105,8 @@ auto Assembler::assemble_file()
|
||||
} else if (parser.next_is_align()) {
|
||||
auto stmt = parser.parse_align();
|
||||
if (not stmt) {
|
||||
return std::unexpected("parsing failed");
|
||||
parser.try_recover();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value = eval_operand_to_imm(*stmt->expr);
|
||||
@ -132,11 +144,15 @@ auto Assembler::assemble_file()
|
||||
stmt->loc, nullptr, "db", std::move(ops)));
|
||||
|
||||
} else {
|
||||
|
||||
auto line = parser.parse_line();
|
||||
if (not line) {
|
||||
if (parser.is_done()) {
|
||||
break;
|
||||
}
|
||||
|
||||
parser.try_recover();
|
||||
continue;
|
||||
}
|
||||
lines.push_back(std::move(line));
|
||||
}
|
||||
}
|
||||
@ -148,6 +164,19 @@ auto Assembler::assemble_file()
|
||||
m_builder.set_ip(0);
|
||||
m_second_pass = true;
|
||||
|
||||
for (const auto& line : lines) {
|
||||
std::println("[{}] = {}", m_builder.ip(), line->ident);
|
||||
assemble_line(*line);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 64; i += 4) {
|
||||
std::println("{:02x} {:02x} {:02x} {:02x}",
|
||||
m_program[i],
|
||||
m_program[i + 1],
|
||||
m_program[i + 2],
|
||||
m_program[i + 3]);
|
||||
}
|
||||
|
||||
return std::move(m_program);
|
||||
}
|
||||
|
||||
@ -187,8 +216,8 @@ static const auto mnemonic_map
|
||||
{ "nop", M::nop }, { "hlt", M::hlt },
|
||||
{ "jmp", M::jmp }, { "jnz", M::jnz },
|
||||
{ "mov", M::mov }, { "cmp", M::cmp },
|
||||
{ "or_", M::or_ }, { "and_", M::and_ },
|
||||
{ "xor_", M::xor_ }, { "shl", M::shl },
|
||||
{ "or", M::or_ }, { "and", M::and_ },
|
||||
{ "xor", M::xor_ }, { "shl", M::shl },
|
||||
{ "rshl", M::rshl }, { "shr", M::shr },
|
||||
{ "rshr", M::rshr }, { "add", M::add },
|
||||
{ "sub", M::sub }, { "rsub", M::rsub },
|
||||
@ -204,13 +233,13 @@ void Assembler::assemble_line(const Line& line)
|
||||
{
|
||||
auto loc = line.loc;
|
||||
|
||||
constexpr auto Reg = EOT::Reg;
|
||||
constexpr auto Imm = EOT::Imm;
|
||||
constexpr auto Str = EOT::Str;
|
||||
constexpr auto MemByteImm = EOT::MemByteImm;
|
||||
constexpr auto MemByteReg = EOT::MemByteReg;
|
||||
constexpr auto MemWordImm = EOT::MemWordImm;
|
||||
constexpr auto MemWordReg = EOT::MemWordReg;
|
||||
[[maybe_unused]] constexpr auto Reg = EOT::Reg;
|
||||
[[maybe_unused]] constexpr auto Imm = EOT::Imm;
|
||||
[[maybe_unused]] constexpr auto Str = EOT::Str;
|
||||
[[maybe_unused]] constexpr auto MemByteImm = EOT::MemByteImm;
|
||||
[[maybe_unused]] constexpr auto MemByteReg = EOT::MemByteReg;
|
||||
[[maybe_unused]] constexpr auto MemWordImm = EOT::MemWordImm;
|
||||
[[maybe_unused]] constexpr auto MemWordReg = EOT::MemWordReg;
|
||||
|
||||
auto& l = m_builder;
|
||||
|
||||
@ -269,6 +298,9 @@ void Assembler::assemble_line(const Line& line)
|
||||
if (arg_count_wrong(line, 1))
|
||||
return;
|
||||
auto op = eval_operand(*line.args[0]);
|
||||
if (not op)
|
||||
return;
|
||||
|
||||
if (op->is_reg()) {
|
||||
l.jmp_reg(op->as_reg());
|
||||
} else if (op->is_imm()) {
|
||||
@ -279,10 +311,14 @@ void Assembler::assemble_line(const Line& line)
|
||||
break;
|
||||
}
|
||||
case M::jnz: {
|
||||
if (arg_count_wrong(line, 1))
|
||||
if (arg_count_wrong(line, 2))
|
||||
return;
|
||||
auto op1 = eval_operand(*line.args[0]);
|
||||
auto op2 = eval_operand(*line.args[1]);
|
||||
|
||||
if (not op1 or op2)
|
||||
return;
|
||||
|
||||
if (not op1->is_reg())
|
||||
operation_not_supported();
|
||||
if (op2->is_reg()) {
|
||||
@ -300,6 +336,9 @@ void Assembler::assemble_line(const Line& line)
|
||||
auto dst = eval_operand(*line.args[0]);
|
||||
auto src = eval_operand(*line.args[1]);
|
||||
|
||||
if (not dst or src)
|
||||
return;
|
||||
|
||||
auto dst_ty = dst->ty;
|
||||
auto src_ty = src->ty;
|
||||
|
||||
@ -338,14 +377,19 @@ void Assembler::assemble_line(const Line& line)
|
||||
auto op1 = eval_operand(*line.args[0]);
|
||||
auto op2 = eval_operand(*line.args[1]);
|
||||
|
||||
if (not op1 or not op2)
|
||||
return;
|
||||
|
||||
if (not op1->is_reg())
|
||||
operation_not_supported();
|
||||
|
||||
if (op2->is_reg()) {
|
||||
|
||||
l.cmp_reg(op1->as_reg(), op2->as_reg());
|
||||
} else if (op2->is_imm()) {
|
||||
|
||||
l.cmp_imm(op1->as_reg(), op2->as_imm());
|
||||
} else {
|
||||
operation_not_supported();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case M::or_:
|
||||
@ -357,13 +401,168 @@ void Assembler::assemble_line(const Line& line)
|
||||
case M::rshr:
|
||||
case M::add:
|
||||
case M::sub:
|
||||
case M::rsub:
|
||||
case M::reti:
|
||||
case M::lvcd:
|
||||
case M::lkbd:
|
||||
case M::dskr:
|
||||
case M::dskw:
|
||||
case M::rsub: {
|
||||
if (arg_count_wrong(line, 3))
|
||||
return;
|
||||
auto dst = eval_operand(*line.args[0]);
|
||||
auto op1 = eval_operand(*line.args[1]);
|
||||
auto op2 = eval_operand(*line.args[2]);
|
||||
|
||||
if (not dst or not op1 or not op2)
|
||||
return;
|
||||
|
||||
if (not dst->is_reg())
|
||||
operation_not_supported();
|
||||
if (not op1->is_reg())
|
||||
operation_not_supported();
|
||||
|
||||
if (op2->is_reg()) {
|
||||
switch (m) {
|
||||
case Mnemonic::or_:
|
||||
l.or_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::and_:
|
||||
l.and_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::xor_:
|
||||
l.xor_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::shl:
|
||||
l.shl_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::rshl:
|
||||
l.rshl_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::shr:
|
||||
l.shr_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::rshr:
|
||||
l.rshr_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::add:
|
||||
l.add_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::sub:
|
||||
l.sub_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
case Mnemonic::rsub:
|
||||
l.rsub_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
|
||||
break;
|
||||
default:
|
||||
assert(false && "unhandled");
|
||||
}
|
||||
} else if (op2->is_imm()) {
|
||||
switch (m) {
|
||||
case Mnemonic::or_:
|
||||
l.or_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::and_:
|
||||
l.and_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::xor_:
|
||||
l.xor_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::shl:
|
||||
l.shl_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::rshl:
|
||||
l.rshl_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::shr:
|
||||
l.shr_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::rshr:
|
||||
l.rshr_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::add:
|
||||
l.add_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::sub:
|
||||
l.sub_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
case Mnemonic::rsub:
|
||||
l.rsub_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
|
||||
break;
|
||||
default:
|
||||
assert(false && "unhandled");
|
||||
}
|
||||
} else {
|
||||
operation_not_supported();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M::reti: {
|
||||
if (arg_count_wrong(line, 0))
|
||||
return;
|
||||
l.reti();
|
||||
break;
|
||||
}
|
||||
case M::lvcd: {
|
||||
if (arg_count_wrong(line, 1))
|
||||
return;
|
||||
|
||||
auto op1 = eval_operand(*line.args[0]);
|
||||
if (not op1)
|
||||
return;
|
||||
|
||||
if (op1->is_reg()) {
|
||||
l.lvcd_reg(op1->as_reg());
|
||||
} else if (op1->is_imm()) {
|
||||
l.lvcd_imm(op1->as_imm());
|
||||
} else {
|
||||
operation_not_supported();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M::lkbd: {
|
||||
if (arg_count_wrong(line, 1))
|
||||
return;
|
||||
|
||||
auto op1 = eval_operand(*line.args[0]);
|
||||
if (not op1)
|
||||
return;
|
||||
|
||||
if (op1->is_reg()) {
|
||||
l.lkbd_reg(op1->as_reg());
|
||||
} else if (op1->is_imm()) {
|
||||
l.lkbd_imm(op1->as_imm());
|
||||
} else {
|
||||
operation_not_supported();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case M::dskr: {
|
||||
if (arg_count_wrong(line, 2))
|
||||
return;
|
||||
|
||||
auto dst = eval_operand(*line.args[0]);
|
||||
auto op1 = eval_operand(*line.args[1]);
|
||||
|
||||
if (not dst or not op1)
|
||||
return;
|
||||
|
||||
if (not dst->is_reg() or not op1->is_reg())
|
||||
operation_not_supported();
|
||||
|
||||
l.dskr(dst->as_reg(), op1->as_reg());
|
||||
break;
|
||||
}
|
||||
case M::dskw: {
|
||||
if (arg_count_wrong(line, 2))
|
||||
return;
|
||||
|
||||
auto dst = eval_operand(*line.args[0]);
|
||||
auto op1 = eval_operand(*line.args[1]);
|
||||
|
||||
if (not dst or not op1)
|
||||
return;
|
||||
|
||||
if (not dst->is_reg() or not op1->is_reg())
|
||||
operation_not_supported();
|
||||
|
||||
l.dskw(dst->as_reg(), op1->as_reg());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,8 +602,9 @@ auto Assembler::eval_operand(const Expr& expr) -> std::unique_ptr<EvaledOperand>
|
||||
case Expr::Ty::Shl:
|
||||
case Expr::Ty::Shr:
|
||||
case Expr::Ty::Add:
|
||||
case Expr::Ty::Sub:
|
||||
break;
|
||||
case Expr::Ty::Sub: {
|
||||
return eval_operand_to_imm(expr);
|
||||
}
|
||||
}
|
||||
assert(false && "unexhaustive");
|
||||
}
|
||||
@ -535,6 +735,7 @@ auto Assembler::eval_operand_to_imm(const Expr& expr)
|
||||
}
|
||||
case Expr::Ty::Reg:
|
||||
error(loc, "registers cannot be part of an expression");
|
||||
return nullptr;
|
||||
case Expr::Ty::Int:
|
||||
return std::make_unique<EO>(
|
||||
expr.loc, EOT::Imm, static_cast<uint16_t>(expr.as_int()));
|
||||
@ -574,10 +775,9 @@ auto Assembler::eval_operand_to_imm(const Expr& expr)
|
||||
return std::make_unique<EO>(loc,
|
||||
EOT::Imm,
|
||||
binary_op(expr.ty, left->as_imm(), right->as_imm()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(false && "unexhaustive");
|
||||
assert(false);
|
||||
}
|
||||
|
||||
auto Assembler::binary_op(Expr::Ty exprTy, uint16_t left, uint16_t right)
|
||||
@ -608,7 +808,7 @@ auto Assembler::binary_op(Expr::Ty exprTy, uint16_t left, uint16_t right)
|
||||
// return (uint16_t)((int16_t)left % (int16_t)right);
|
||||
|
||||
default:
|
||||
assert(false && "unexhaustive");
|
||||
assert(false && "unhandled");
|
||||
}
|
||||
}
|
||||
|
||||
@ -616,15 +816,32 @@ bool Assembler::arg_count_wrong(const Line& ins, size_t count)
|
||||
{
|
||||
if (ins.args.size() != count) {
|
||||
error(ins.loc, std::format("expected {} operands", count));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Assembler::error(Loc loc, std::string_view message)
|
||||
{
|
||||
m_failed = true;
|
||||
loc.print_error(m_text, message);
|
||||
loc.print_error(m_text, message, m_filename);
|
||||
}
|
||||
|
||||
void Parser::eat_whitespace()
|
||||
{
|
||||
while (eat('\n')) { }
|
||||
}
|
||||
|
||||
void Parser::try_recover()
|
||||
{
|
||||
while (not test(TT::Eof) and not test('\n')) {
|
||||
step();
|
||||
}
|
||||
}
|
||||
|
||||
auto Parser::is_done() const -> bool
|
||||
{
|
||||
return test(TT::Eof);
|
||||
}
|
||||
|
||||
auto Parser::next_is_const() const -> bool
|
||||
@ -636,6 +853,7 @@ auto Parser::parse_const() -> std::unique_ptr<Const>
|
||||
{
|
||||
assert(test(TT::KwConst));
|
||||
auto loc = m_tok.loc;
|
||||
step();
|
||||
if (!test(TT::Ident)) {
|
||||
error(current_loc(), "expected identifier");
|
||||
return nullptr;
|
||||
@ -643,6 +861,8 @@ auto Parser::parse_const() -> std::unique_ptr<Const>
|
||||
auto ident = m_tok.text;
|
||||
step();
|
||||
auto expr = parse_expr();
|
||||
if (not expr)
|
||||
return nullptr;
|
||||
return std::make_unique<Const>(loc, ident, std::move(expr));
|
||||
}
|
||||
|
||||
@ -655,6 +875,7 @@ auto Parser::parse_align() -> std::unique_ptr<Align>
|
||||
{
|
||||
assert(test(TT::KwAlign));
|
||||
auto loc = m_tok.loc;
|
||||
step();
|
||||
if (!test(TT::Ident)) {
|
||||
error(current_loc(), "expected identifier");
|
||||
return nullptr;
|
||||
@ -662,6 +883,8 @@ auto Parser::parse_align() -> std::unique_ptr<Align>
|
||||
auto ident = m_tok.text;
|
||||
step();
|
||||
auto expr = parse_expr();
|
||||
if (not expr)
|
||||
return nullptr;
|
||||
return std::make_unique<Align>(loc, ident, std::move(expr));
|
||||
}
|
||||
|
||||
@ -674,15 +897,15 @@ auto Parser::parse_line() -> std::unique_ptr<Line>
|
||||
auto ident = std::string_view();
|
||||
|
||||
while (true) {
|
||||
while (eat('\n')) { }
|
||||
eat_whitespace();
|
||||
|
||||
if (test(TT::Ident) or eat('.')) {
|
||||
if (test(TT::Ident) or test('.')) {
|
||||
bool is_local = false;
|
||||
if (eat('.')) {
|
||||
is_local = true;
|
||||
}
|
||||
if (not test(TT::Ident)) {
|
||||
error(current_loc(), "expected ident");
|
||||
error(current_loc(), "expected identifier");
|
||||
return nullptr;
|
||||
}
|
||||
ident = m_tok.text;
|
||||
@ -702,19 +925,27 @@ auto Parser::parse_line() -> std::unique_ptr<Line>
|
||||
labels->push_back(Label { loc, ident, is_local });
|
||||
continue;
|
||||
} else {
|
||||
error(current_loc(), "expected identifier");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto args = Line::Args();
|
||||
|
||||
auto first = true;
|
||||
while (not test(TT::Eof) and not eat('\n')) {
|
||||
if (not first and not eat(',')) {
|
||||
if (not test(TT::Eof) and not test('\n')) {
|
||||
|
||||
auto arg = parse_expr();
|
||||
if (not arg) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
args.push_back(std::move(arg));
|
||||
while (not test(TT::Eof) and not test('\n')) {
|
||||
|
||||
if (not eat(',')) {
|
||||
error(current_loc(), "expected ','");
|
||||
return nullptr;
|
||||
}
|
||||
first = false;
|
||||
|
||||
auto arg = parse_expr();
|
||||
if (not arg) {
|
||||
@ -722,9 +953,11 @@ auto Parser::parse_line() -> std::unique_ptr<Line>
|
||||
}
|
||||
args.push_back(std::move(arg));
|
||||
}
|
||||
}
|
||||
|
||||
if (not test(TT::Eof) and not eat('\n')) {
|
||||
error(current_loc(), "expected line ending");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<Line>(
|
||||
@ -790,17 +1023,17 @@ auto Parser::parse_prefix() -> std::unique_ptr<Expr>
|
||||
|
||||
namespace {
|
||||
|
||||
const auto reg_idents = std::unordered_map<std::string_view, vc5::Reg> {
|
||||
{ "R0", vc5::Reg::R0 },
|
||||
{ "R1", vc5::Reg::R1 },
|
||||
{ "R2", vc5::Reg::R2 },
|
||||
{ "R3", vc5::Reg::R3 },
|
||||
{ "R4", vc5::Reg::R4 },
|
||||
{ "R5", vc5::Reg::R5 },
|
||||
{ "Rbp", vc5::Reg::Rbp },
|
||||
{ "Rsp", vc5::Reg::Rsp },
|
||||
{ "Rfl", vc5::Reg::Rfl },
|
||||
{ "Rip", vc5::Reg::Rip },
|
||||
const auto reg_idents = std::unordered_map<TT, vc5::Reg> {
|
||||
{ TT::KwR0, vc5::Reg::R0 },
|
||||
{ TT::KwR1, vc5::Reg::R1 },
|
||||
{ TT::KwR2, vc5::Reg::R2 },
|
||||
{ TT::KwR3, vc5::Reg::R3 },
|
||||
{ TT::KwR4, vc5::Reg::R4 },
|
||||
{ TT::KwR5, vc5::Reg::R5 },
|
||||
{ TT::KwRbp, vc5::Reg::Rbp },
|
||||
{ TT::KwRsp, vc5::Reg::Rsp },
|
||||
{ TT::KwRfl, vc5::Reg::Rfl },
|
||||
{ TT::KwRip, vc5::Reg::Rip },
|
||||
};
|
||||
|
||||
}
|
||||
@ -811,12 +1044,11 @@ auto Parser::parse_operand() -> std::unique_ptr<Expr>
|
||||
if (test(TT::Ident)) {
|
||||
auto ident = m_tok.text;
|
||||
step();
|
||||
if (reg_idents.contains(ident)) {
|
||||
return std::make_unique<Expr>(
|
||||
loc, Expr::Ty::Reg, reg_idents.at(ident));
|
||||
} else {
|
||||
return std::make_unique<Expr>(loc, Expr::Ty::Ident, ident);
|
||||
}
|
||||
} else if (reg_idents.contains(m_tok.ty)) {
|
||||
auto ty = m_tok.ty;
|
||||
step();
|
||||
return std::make_unique<Expr>(loc, Expr::Ty::Reg, reg_idents.at(ty));
|
||||
} else if (eat('.')) {
|
||||
if (!test(TT::Ident)) {
|
||||
error(current_loc(), "expected identifier");
|
||||
@ -824,6 +1056,7 @@ auto Parser::parse_operand() -> std::unique_ptr<Expr>
|
||||
}
|
||||
auto value
|
||||
= std::string_view(m_tok.text.data() - 1, m_tok.text.size() + 1);
|
||||
step();
|
||||
|
||||
return std::make_unique<Expr>(loc, Expr::Ty::SubLabel, value);
|
||||
} else if (test(TT::Int)) {
|
||||
@ -862,6 +1095,8 @@ auto Parser::parse_operand() -> std::unique_ptr<Expr>
|
||||
return std::make_unique<Expr>(loc, Expr::Ty::Str, std::move(value));
|
||||
} else if (eat('(')) {
|
||||
auto expr = parse_expr();
|
||||
if (not expr)
|
||||
return nullptr;
|
||||
if (not eat(')')) {
|
||||
error(current_loc(), "expected ')'");
|
||||
return nullptr;
|
||||
@ -869,30 +1104,34 @@ auto Parser::parse_operand() -> std::unique_ptr<Expr>
|
||||
return expr;
|
||||
} else if (eat('[')) {
|
||||
auto expr = parse_expr();
|
||||
if (not expr)
|
||||
return nullptr;
|
||||
if (not eat(']')) {
|
||||
error(current_loc(), "expected ']'");
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<Expr>(loc, Expr::Ty::Mem, std::move(expr));
|
||||
} else if (test(TT::Ident) and m_tok.text == "byte") {
|
||||
step();
|
||||
} else if (eat(TT::KwByte)) {
|
||||
if (not eat('[')) {
|
||||
error(current_loc(), "expected '['");
|
||||
return nullptr;
|
||||
}
|
||||
auto expr = parse_expr();
|
||||
if (not expr)
|
||||
return nullptr;
|
||||
if (not eat(']')) {
|
||||
error(current_loc(), "expected ']'");
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<Expr>(loc, Expr::Ty::MemByte, std::move(expr));
|
||||
} else if (test(TT::Ident) and m_tok.text == "word") {
|
||||
step();
|
||||
} else if (eat(TT::KwWord)) {
|
||||
if (not eat('[')) {
|
||||
error(current_loc(), "expected '['");
|
||||
return nullptr;
|
||||
}
|
||||
auto expr = parse_expr();
|
||||
if (not expr)
|
||||
return nullptr;
|
||||
if (not eat(']')) {
|
||||
error(current_loc(), "expected ']'");
|
||||
return nullptr;
|
||||
@ -941,5 +1180,5 @@ auto Parser::current_loc() const -> Loc
|
||||
void Parser::error(Loc loc, std::string_view message)
|
||||
{
|
||||
m_error_occured = true;
|
||||
loc.print_error(m_text, message);
|
||||
loc.print_error(m_text, message, m_filename);
|
||||
}
|
||||
|
||||
@ -105,13 +105,18 @@ namespace asmer {
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(std::string_view text)
|
||||
: m_text(text)
|
||||
, m_lexer(text, Scanner::Kind::Assembler)
|
||||
explicit Parser(std::string_view filename, std::string_view text)
|
||||
: m_filename(filename)
|
||||
, m_text(text)
|
||||
, m_lexer(m_filename, text, Scanner::Kind::Assembler)
|
||||
, m_tok(m_lexer.next())
|
||||
{
|
||||
}
|
||||
|
||||
void eat_whitespace();
|
||||
void try_recover();
|
||||
auto is_done() const -> bool;
|
||||
|
||||
auto next_is_const() const -> bool;
|
||||
auto parse_const() -> std::unique_ptr<Const>;
|
||||
|
||||
@ -141,6 +146,7 @@ namespace asmer {
|
||||
|
||||
void error(Loc loc, std::string_view message);
|
||||
|
||||
std::string_view m_filename;
|
||||
std::string_view m_text;
|
||||
Scanner m_lexer;
|
||||
Tok m_tok;
|
||||
@ -193,8 +199,9 @@ namespace asmer {
|
||||
|
||||
class Assembler {
|
||||
public:
|
||||
explicit Assembler(std::string_view text)
|
||||
: m_text(text)
|
||||
explicit Assembler(std::string_view filename, std::string_view text)
|
||||
: m_filename(filename)
|
||||
, m_text(text)
|
||||
, m_program(65536)
|
||||
, m_builder(m_program.data())
|
||||
{
|
||||
@ -226,6 +233,7 @@ namespace asmer {
|
||||
|
||||
void error(Loc loc, std::string_view message);
|
||||
|
||||
std::string_view m_filename;
|
||||
std::string_view m_text;
|
||||
std::vector<uint8_t> m_program;
|
||||
Builder m_builder;
|
||||
|
||||
25
src/main.cpp
25
src/main.cpp
@ -3,6 +3,7 @@
|
||||
#include "io_device.hpp"
|
||||
#include "vm.hpp"
|
||||
#include <SDL2/SDL_main.h>
|
||||
#include <cstdint>
|
||||
#include <print>
|
||||
#include <string>
|
||||
|
||||
@ -11,14 +12,31 @@ using namespace std::chrono_literals;
|
||||
using namespace vc5;
|
||||
using namespace vc5::regs;
|
||||
|
||||
int main()
|
||||
static void make_program(uint8_t* data);
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto device = IoDevice::create().value();
|
||||
device->set_title("vc5");
|
||||
|
||||
if (argc < 2) {
|
||||
auto disk = MemoryDisk(128);
|
||||
|
||||
auto l = tools::Builder(disk.data());
|
||||
make_program(disk.data());
|
||||
|
||||
auto vm = VM(*device, disk);
|
||||
vm.run();
|
||||
} else {
|
||||
auto disk = FileDisk(argv[1]);
|
||||
|
||||
auto vm = VM(*device, disk);
|
||||
vm.run();
|
||||
}
|
||||
}
|
||||
|
||||
void make_program(uint8_t* data)
|
||||
{
|
||||
auto l = tools::Builder(data);
|
||||
|
||||
l.mov_imm(rsp, 0x1000);
|
||||
l.mov_reg(rbp, rsp);
|
||||
@ -77,9 +95,6 @@ int main()
|
||||
l.push(key_press_incr);
|
||||
l.set_ip(to_set_key_press_int_leave + 2);
|
||||
l.push(key_press_int_leave);
|
||||
|
||||
auto vm = VM(*device, disk);
|
||||
vm.run();
|
||||
}
|
||||
|
||||
extern "C" const char* __asan_default_options(void)
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
using namespace vc5::tools;
|
||||
using namespace std::literals;
|
||||
@ -130,7 +131,7 @@ auto Scanner::next() -> Tok
|
||||
}
|
||||
return tok(TT::Gt, loc);
|
||||
}
|
||||
if (test_in("\n()[],:|^+-!")) {
|
||||
if (test_in("\n()[].,:|^+-!")) {
|
||||
auto ty = static_cast<TT>(current());
|
||||
step();
|
||||
return tok(ty, loc);
|
||||
@ -140,9 +141,21 @@ auto Scanner::next() -> Tok
|
||||
return next();
|
||||
}
|
||||
|
||||
const auto Scanner::keywords_kind_assembler = KeywordMap {
|
||||
const Scanner::KeywordMap Scanner::keywords_kind_assembler = {
|
||||
{ "const", TT::KwConst },
|
||||
{ "align", TT::KwAlign },
|
||||
{ "word", TT::KwWord },
|
||||
{ "byte", TT::KwByte },
|
||||
{ "r0", TT::KwR0 },
|
||||
{ "r1", TT::KwR1 },
|
||||
{ "r2", TT::KwR2 },
|
||||
{ "r3", TT::KwR3 },
|
||||
{ "r4", TT::KwR4 },
|
||||
{ "r5", TT::KwR5 },
|
||||
{ "rbp", TT::KwRbp },
|
||||
{ "rsp", TT::KwRsp },
|
||||
{ "rfl", TT::KwRfl },
|
||||
{ "rip", TT::KwRip },
|
||||
};
|
||||
|
||||
void Scanner::step()
|
||||
@ -209,19 +222,22 @@ auto Scanner::is_assembler() const -> bool
|
||||
void Scanner::error(Loc loc, std::string_view message)
|
||||
{
|
||||
m_error_occured = true;
|
||||
loc.print_error(m_text, message);
|
||||
loc.print_error(m_text, message, m_filename);
|
||||
}
|
||||
|
||||
void Loc::print_error(std::string_view text, std::string_view message) const
|
||||
void Loc::print_error(std::string_view text,
|
||||
std::string_view message,
|
||||
std::string_view file) const
|
||||
{
|
||||
constexpr auto type = "error"sv;
|
||||
|
||||
constexpr auto clear = "\x1b[0m"sv;
|
||||
constexpr auto bold_red = "\x1b[1;31m"sv;
|
||||
constexpr auto bold_white = "\x1b[1;37m"sv;
|
||||
constexpr auto bold_red = "\x1b[1;91m"sv;
|
||||
constexpr auto bold_white = "\x1b[1;97m"sv;
|
||||
constexpr auto cyan = "\x1b[0;36m"sv;
|
||||
constexpr auto gray = "\x1b[0;37m"sv;
|
||||
constexpr auto light_gray = "\x1b[1;30m"sv;
|
||||
constexpr auto gray = "\x1b[0;90m"sv;
|
||||
constexpr auto light_gray = "\x1b[0;37m"sv;
|
||||
constexpr auto green = "\x1b[0;32m"sv;
|
||||
|
||||
auto start = text.find_last_of('\n', idx) + 1;
|
||||
auto end = text.find_first_of('\n', idx);
|
||||
@ -240,13 +256,13 @@ void Loc::print_error(std::string_view text, std::string_view message) const
|
||||
" {8: <{9}}{10}|\n"
|
||||
" {11}{12}{13}|{14}{15}\n"
|
||||
" {16: <{17}}"
|
||||
"{18}|${19: <{20}}{21}^ {22}{23}{24}",
|
||||
"{18}|{19: <{20}}{21}^ {22}{23}{24}\n",
|
||||
bold_red,
|
||||
type,
|
||||
bold_white,
|
||||
message,
|
||||
cyan,
|
||||
"<file>",
|
||||
file,
|
||||
line,
|
||||
col,
|
||||
"",
|
||||
@ -255,7 +271,7 @@ void Loc::print_error(std::string_view text, std::string_view message) const
|
||||
light_gray,
|
||||
linenr_str,
|
||||
gray,
|
||||
light_gray,
|
||||
green,
|
||||
line_text,
|
||||
"",
|
||||
linenr_str.size(),
|
||||
|
||||
@ -11,7 +11,9 @@ struct Loc {
|
||||
int line;
|
||||
int col;
|
||||
|
||||
void print_error(std::string_view text, std::string_view message) const;
|
||||
void print_error(std::string_view text,
|
||||
std::string_view message,
|
||||
std::string_view file = std::string_view("<file>")) const;
|
||||
};
|
||||
|
||||
struct Tok {
|
||||
@ -25,12 +27,27 @@ struct Tok {
|
||||
Str,
|
||||
KwConst,
|
||||
KwAlign,
|
||||
KwWord,
|
||||
Newline = '\n',
|
||||
KwByte,
|
||||
KwR0,
|
||||
KwR1,
|
||||
KwR2,
|
||||
KwR3,
|
||||
KwR4,
|
||||
KwR5,
|
||||
KwRbp,
|
||||
KwRsp,
|
||||
KwRfl,
|
||||
KwRip,
|
||||
LtLt,
|
||||
GtGt,
|
||||
LParen = '(',
|
||||
RParen = ')',
|
||||
LBracket = '[',
|
||||
RBracket = ']',
|
||||
Comma = ',',
|
||||
Dot = '.',
|
||||
Colon = ':',
|
||||
Pipe = '|',
|
||||
Hat = '^',
|
||||
@ -40,8 +57,6 @@ struct Tok {
|
||||
Exclam = '!',
|
||||
Lt = '<',
|
||||
Gt = '>',
|
||||
LtLt,
|
||||
GtGt,
|
||||
};
|
||||
|
||||
std::string_view text;
|
||||
@ -53,8 +68,10 @@ class Scanner {
|
||||
public:
|
||||
enum class Kind { Assembler };
|
||||
|
||||
explicit Scanner(std::string_view text, Kind kind)
|
||||
: m_text(text)
|
||||
explicit Scanner(
|
||||
std::string_view filename, std::string_view text, Kind kind)
|
||||
: m_filename(filename)
|
||||
, m_text(text)
|
||||
, m_keywords(&keywords_kind_assembler)
|
||||
, m_kind(kind)
|
||||
{
|
||||
@ -85,6 +102,7 @@ private:
|
||||
using KeywordMap = std::unordered_map<std::string_view, Tok::Ty>;
|
||||
static const KeywordMap keywords_kind_assembler;
|
||||
|
||||
std::string_view m_filename;
|
||||
std::string_view m_text;
|
||||
const KeywordMap* m_keywords;
|
||||
size_t m_idx = 0;
|
||||
|
||||
2
vim/ftdetect/vc5asm.vim
Normal file
2
vim/ftdetect/vc5asm.vim
Normal file
@ -0,0 +1,2 @@
|
||||
autocmd BufNewFile,BufRead *.vc5asm setfiletype vc5asm
|
||||
|
||||
2
vim/ftplugin/vc5asm.vim
Normal file
2
vim/ftplugin/vc5asm.vim
Normal file
@ -0,0 +1,2 @@
|
||||
setlocal commentstring=;%s
|
||||
|
||||
37
vim/syntax/vc5asm.vim
Normal file
37
vim/syntax/vc5asm.vim
Normal file
@ -0,0 +1,37 @@
|
||||
" Vim syntax file
|
||||
" Language: vc5asm
|
||||
" Maintainer: SFJ
|
||||
" Latest Revision: 1 January 1984
|
||||
|
||||
if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
syn keyword Keyword const align db dw
|
||||
syn keyword Keyword nop hlt jmp jnz mov cmp or and xor shl rshl shr rshr add sub rsub
|
||||
syn keyword Keyword reti lkbd lvcd dskr dskw
|
||||
syn keyword Operator r0 r1 r2 r3 r4 r5 rbp rsp rfl rip
|
||||
|
||||
syn match Operator '+'
|
||||
syn match Operator '-'
|
||||
syn match Operator '='
|
||||
syn match Operator '<'
|
||||
syn match Operator '>'
|
||||
syn match Operator '|'
|
||||
syn match Operator '^'
|
||||
syn match Operator '&'
|
||||
|
||||
|
||||
syn match Number '0'
|
||||
syn match Number '[1-9][0-9]*'
|
||||
syn match Number '0b[01]*'
|
||||
syn match Number '0x[0-9a-fA-F]*'
|
||||
|
||||
syn region String start=+"+ skip=+\\\\\|\\"+ end=+"+
|
||||
|
||||
syn match Comment ";.*$"
|
||||
|
||||
syn match Identifier '[a-zA-Z_]\w*'
|
||||
|
||||
let b:current_syntax = "vc5asm"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user