Refactoring
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "vasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
vcore = { path = "../vcore" }
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
@@ -0,0 +1,11 @@
|
||||
extern crate pest;
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
|
||||
mod parse_error;
|
||||
mod vasm_line;
|
||||
mod vasm_parser;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use vcore::opcodes::InvalidOpcode;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ParseError<'a>(pub &'a str);
|
||||
|
||||
impl<'a> From<InvalidOpcode> for ParseError<'a> {
|
||||
fn from(_: InvalidOpcode) -> Self {
|
||||
Self("Invalid opcode")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
WHITESPACE = _{ " " | "\t" }
|
||||
|
||||
// A comment will start with a semicolon and go to the end of the line. Actually everything is parsed
|
||||
// line by line, so anything that starts with a semicolon is a comment:
|
||||
COMMENT = _{ ";" ~ ANY* ~ EOI }
|
||||
|
||||
// Numbers are more complicated. We'll support three formats:
|
||||
// - Decimal numbers like 42
|
||||
// - Hexadecimal like 0x2a
|
||||
// - Binary like 0b00101010
|
||||
// - Decimal zero needs its own pattern: it's not a decimal because
|
||||
// it starts with a 0, but it has to be matched after hex and bin
|
||||
// because otherwise any "0x" will parse as "decimal 0 followed
|
||||
// by unparseable x"
|
||||
dec_number = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
|
||||
neg_number = ${ "-" ~ dec_number }
|
||||
hex_number = ${ "0x" ~ ASCII_HEX_DIGIT+ }
|
||||
bin_number = ${ "0b" ~ ASCII_BIN_DIGIT+ }
|
||||
dec_zero = @{ "0" }
|
||||
number = { dec_number | hex_number | bin_number | dec_zero | neg_number }
|
||||
|
||||
// A label can be any sequence of C-identifier-y characters, as long as it doesn't start with
|
||||
// a digit:
|
||||
label_char = { ASCII_ALPHA_LOWER | ASCII_ALPHA_UPPER | "_" }
|
||||
label = @{ label_char ~ (label_char | ASCII_DIGIT | "$")* }
|
||||
|
||||
// To make relative jumps easier, we'll also allow an '@' at the start of a label, and interpret
|
||||
// that as meaning "relative to the first byte of this instruction:"
|
||||
relative_label = ${ "@" ~ label }
|
||||
|
||||
// We'll also have a special form, $+nnn (and $-nnn) which is the first byte of the an earlier or later line:
|
||||
absolute_line_offset = { "$" ~ sign ~ dec_number }
|
||||
|
||||
// And the relative form of that, @+nnn and @-nnn:
|
||||
relative_line_offset = { "@" ~ sign ~ dec_number }
|
||||
|
||||
opcode = @{ ASCII_ALPHA_LOWER+ } // TODO: replace with actual list
|
||||
|
||||
// The assembler will support some directives:
|
||||
// - .org to set the current address
|
||||
// - .db to embed some data
|
||||
// - .equ to define some constants
|
||||
directive = { ".org" | ".db" | ".equ" }
|
||||
|
||||
// The .equ directive isn't much use without the ability to have expressions based on
|
||||
// symbols, so, a quick arithmetic expression parser:
|
||||
sign = { "+" | "-" }
|
||||
term_op = { "/" | "*" | "%" }
|
||||
expr = { term ~ (sign ~ term)* }
|
||||
term = { fact ~ (term_op ~ fact)* }
|
||||
fact = { ("(" ~ expr ~ ")") | (number | relative_line_offset | relative_label | absolute_line_offset | label) }
|
||||
|
||||
// Likewise, .db would get tedious quick without a string syntax, so, let's define one of those. An escape
|
||||
// sequence is a backslash followed by certain other characters:
|
||||
escape = @{ "\\" ~ ("t" | "r" | "n" | "0" | "\\" | "\"") }
|
||||
|
||||
// And a string is a quoted sequence of escapes or other characters:
|
||||
string_inner = ${ !("\"" | "\\") ~ ANY | escape }
|
||||
string = ${ "\"" ~ string_inner* ~ "\"" }
|
||||
|
||||
// Parsing a line
|
||||
// Normally an assembly line will be a sequence of "label, opcode, argument, comment."
|
||||
// However, most of these elements are optional. An opcode is only required if an
|
||||
// argument exists. Comments are already handled by the COMMENT pattern
|
||||
|
||||
|
||||
// Some sub-patterns for the portions of a line:
|
||||
label_group = { label ~ ":" }
|
||||
argument_group = { expr | string }
|
||||
|
||||
// An opcode might be an actual opcode, or a directive
|
||||
instruction_group = { (opcode | directive) ~ argument_group? }
|
||||
|
||||
// Finally the entire pattern for an assembly line:
|
||||
line = { SOI ~ label_group? ~ instruction_group? ~ EOI }
|
||||
@@ -0,0 +1,61 @@
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum Directive { Db, Equ, Org }
|
||||
|
||||
impl From<&str> for Directive {
|
||||
fn from(value: &str) -> Self {
|
||||
use Directive::*;
|
||||
match value {
|
||||
".db" => Db,
|
||||
".equ" => Equ,
|
||||
".org" => Org,
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub struct VASMLine<'a> {
|
||||
label: Option<&'a str>,
|
||||
opcode: Option<Opcode>,
|
||||
directive: Option<Directive>
|
||||
}
|
||||
|
||||
impl<'a> VASMLine<'a> {
|
||||
pub fn blank() -> VASMLine<'a> {
|
||||
VASMLine {
|
||||
label: None,
|
||||
opcode: None,
|
||||
directive: None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_opcode(opcode: Opcode) -> VASMLine<'a> {
|
||||
Self::blank().with_opcode(opcode)
|
||||
}
|
||||
|
||||
pub fn with_opcode(self, opcode: Opcode) -> VASMLine<'a> {
|
||||
VASMLine {
|
||||
label: self.label,
|
||||
opcode: Some(opcode),
|
||||
directive: self.directive
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_directive(self, directive: Directive) -> VASMLine<'a> {
|
||||
VASMLine {
|
||||
label: self.label,
|
||||
opcode: self.opcode,
|
||||
directive: Some(directive)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_label(self, label: &'a str) -> Self {
|
||||
VASMLine {
|
||||
label: Some(label),
|
||||
opcode: self.opcode,
|
||||
directive: self.directive
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
use crate::parse_error::ParseError;
|
||||
use crate::vasm_line::{Directive, VASMLine};
|
||||
|
||||
use pest::{Parser};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[grammar = "vasm.pest"]
|
||||
struct VASMParser;
|
||||
|
||||
fn parse_vasm_line(line: &str) -> Result<VASMLine, ParseError<'_>> {
|
||||
let mut pairs = VASMParser::parse(Rule::line, line)
|
||||
.map_err(|_| ParseError("Failed to parse line"))?
|
||||
.next()
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.peekable();
|
||||
|
||||
let mut vasm_line = VASMLine::blank();
|
||||
|
||||
if let Some(label_group) = pairs.next_if(|pair| pair.as_rule() == Rule::label_group) {
|
||||
let label = label_group.into_inner().next().unwrap();
|
||||
vasm_line = vasm_line.with_label(label.as_str())
|
||||
}
|
||||
|
||||
if let Some(instruction_group) = pairs.next_if(|pair| pair.as_rule() == Rule::instruction_group) {
|
||||
let instruction_field = instruction_group.into_inner().next().unwrap();
|
||||
vasm_line = match instruction_field.as_rule() {
|
||||
Rule::opcode => vasm_line.with_opcode(Opcode::try_from(instruction_field.as_str())?),
|
||||
Rule::directive => vasm_line.with_directive(Directive::from(instruction_field.as_str())),
|
||||
_ => unreachable!()
|
||||
}
|
||||
// something something argument here
|
||||
}
|
||||
|
||||
Ok(vasm_line)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
assert_eq!(parse_vasm_line("add"), Ok(VASMLine::for_opcode(Opcode::Add)));
|
||||
assert_eq!(parse_vasm_line("sub"), Ok(VASMLine::for_opcode(Opcode::Sub)));
|
||||
assert_eq!(parse_vasm_line("blah"), Err(ParseError("Invalid opcode")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_labels() {
|
||||
assert_eq!(parse_vasm_line("foo: add"), Ok(VASMLine::for_opcode(Opcode::Add).with_label("foo")));
|
||||
assert_eq!(parse_vasm_line("bar:"), Ok(VASMLine::blank().with_label("bar")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_directives() {
|
||||
assert_eq!(parse_vasm_line("foo: .equ"), Ok(VASMLine::blank().with_label("foo").with_directive(Directive::Equ)));
|
||||
assert_eq!(parse_vasm_line(".db"), Ok(VASMLine::blank().with_directive(Directive::Db)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user