398 lines
14 KiB
Rust
398 lines
14 KiB
Rust
use anyhow::{bail, Result};
|
|
use itertools::Itertools;
|
|
use radix_fmt::radix;
|
|
use rust_embed::RustEmbed;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use std::vec;
|
|
|
|
use crate::arg;
|
|
use crate::lexer;
|
|
use crate::parser;
|
|
|
|
pub trait ToCode {
|
|
fn to_code(&self) -> String;
|
|
}
|
|
|
|
pub trait ByteResolvable<T> {
|
|
fn resolve_number(&self, data: &mut T) -> Result<u16>;
|
|
|
|
fn resolve_bytes(&self, data: &mut T) -> Result<Vec<u8>>;
|
|
|
|
fn resolve_string(&self, data: &mut T) -> Result<String>;
|
|
}
|
|
|
|
pub const PROGRAM_SIZE: u16 = 32 * 1024;
|
|
pub type Program = [u8; PROGRAM_SIZE as usize];
|
|
|
|
#[derive(RustEmbed)]
|
|
#[folder = "lib/"]
|
|
#[prefix = "$lib/"]
|
|
pub struct Lib;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Macro {
|
|
pub args: Vec<String>,
|
|
pub body: parser::ast::Body,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Data {
|
|
pub dir: String,
|
|
pub ast: parser::ast::AST,
|
|
pub program: Program,
|
|
pub offset: u16,
|
|
pub body_stack: Vec<parser::ast::Node>,
|
|
pub constants: HashMap<String, arg::Arg>,
|
|
pub macros: HashMap<String, Macro>,
|
|
pub includes: HashSet<String>,
|
|
pub tmp: Vec<u8>,
|
|
pub tmp_offset: u16,
|
|
pub tmp_enabled: bool,
|
|
}
|
|
|
|
impl Data {
|
|
pub fn new(dir: String, ast: parser::ast::AST) -> Self {
|
|
Self {
|
|
dir,
|
|
body_stack: ast.body.iter().rev().cloned().collect(),
|
|
ast,
|
|
program: [0; PROGRAM_SIZE as usize],
|
|
offset: 0,
|
|
constants: HashMap::new(),
|
|
macros: HashMap::new(),
|
|
includes: HashSet::new(),
|
|
tmp: vec![],
|
|
tmp_offset: 0,
|
|
tmp_enabled: false,
|
|
}
|
|
}
|
|
|
|
pub fn resolve_node(&mut self) -> Result<Option<Vec<u8>>> {
|
|
let node = match self.body_stack.pop() {
|
|
Some(x) => x,
|
|
None => bail!("Body stack empty. No more nodes to resolve"),
|
|
};
|
|
|
|
match node {
|
|
parser::ast::Node::Comment(_) => Ok(None),
|
|
parser::ast::Node::Label(x) => {
|
|
self.constants.insert(x, arg::Arg::Number(self.offset));
|
|
|
|
Ok(None)
|
|
}
|
|
parser::ast::Node::Call { name, arg } => {
|
|
let opcode: u8 = match name.as_str() {
|
|
"nop" => 0x00,
|
|
"push" => 0x01,
|
|
"pop" => 0x02,
|
|
"ts" => 0x03,
|
|
"tsr" => 0x04,
|
|
"tss" => 0x05,
|
|
"tlr" => 0x06,
|
|
"tlrc" => 0x07,
|
|
"tls" => 0x08,
|
|
"dbg" => 0x09,
|
|
"alu" => 0x0a,
|
|
"get" => 0x0b,
|
|
"set" => 0x0c,
|
|
_ => bail!("Unknown opcode: {}", name),
|
|
};
|
|
let a = match arg {
|
|
Some(x) => x.resolve_number(self)?,
|
|
None => 0,
|
|
};
|
|
if a == 0 {
|
|
Ok(Some(vec![opcode | 0b10000000]))
|
|
} else {
|
|
Ok(Some(vec![opcode, (a >> 8) as u8, a as u8]))
|
|
}
|
|
}
|
|
parser::ast::Node::MacroCall { name, args } => match name.as_str() {
|
|
"debug" => {
|
|
for arg in args {
|
|
let assembly = arg.to_code().replace('\n', "\\n");
|
|
let num = arg.resolve_number(self)?;
|
|
let bytes = arg.resolve_bytes(self)?;
|
|
|
|
println!("{}", assembly);
|
|
let bin_num = radix(num, 2).to_string();
|
|
println!(
|
|
" => 0b{}{}",
|
|
"0000000000000000"[..16 - bin_num.len()].to_owned(),
|
|
bin_num
|
|
);
|
|
println!(" => {}", num);
|
|
let hex_num = radix(num, 16).to_string();
|
|
println!(
|
|
" => 0x{}{}",
|
|
"0000"[..4 - hex_num.len()].to_owned(),
|
|
hex_num
|
|
);
|
|
println!(
|
|
" => [{}]",
|
|
bytes
|
|
.iter()
|
|
.map(|n| {
|
|
let num = radix(*n, 16).to_string();
|
|
|
|
format!(
|
|
"0x{}{}",
|
|
"00".chars().take(2 - num.len()).collect::<String>(),
|
|
num
|
|
)
|
|
})
|
|
.join(", ")
|
|
);
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
"define" => {
|
|
let name = match &args[0] {
|
|
arg::Arg::Variable(x) | arg::Arg::String(x) => x,
|
|
_ => {
|
|
bail!("First argument of define macro needs to be a variable")
|
|
}
|
|
};
|
|
self.constants.insert(name.clone(), args[1].clone());
|
|
|
|
Ok(None)
|
|
}
|
|
"define_num_nl" => {
|
|
let name = match &args[0] {
|
|
arg::Arg::Variable(x) => x,
|
|
_ => {
|
|
bail!("First argument of define macro needs to be a variable")
|
|
}
|
|
};
|
|
let a = arg::Arg::Number(args[1].resolve_number(self)?);
|
|
self.constants.insert(name.clone(), a);
|
|
|
|
Ok(None)
|
|
}
|
|
"macro" => {
|
|
let name = match &args[0] {
|
|
arg::Arg::Variable(x) | arg::Arg::String(x) => x,
|
|
_ => {
|
|
bail!("First argument of define macro needs to be a literal-like value")
|
|
}
|
|
};
|
|
let args = args[1..]
|
|
.iter()
|
|
.map(|a| match a {
|
|
arg::Arg::Variable(x) => Ok(x.clone()),
|
|
_ => bail!("Macro arguments need to be variables"),
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
let mut depth: usize = 1;
|
|
let body: Vec<_> = self
|
|
.body_stack
|
|
.iter()
|
|
.rev()
|
|
.take_while(|n| match n {
|
|
parser::ast::Node::MacroCall { name, args: _ } => match name.as_str() {
|
|
"macro" => {
|
|
depth += 1;
|
|
true
|
|
}
|
|
"endmacro" => {
|
|
depth -= 1;
|
|
depth != 0
|
|
}
|
|
_ => true,
|
|
},
|
|
_ => true,
|
|
})
|
|
.cloned()
|
|
.collect();
|
|
self.body_stack
|
|
.drain((self.body_stack.len() - body.len() - 1)..)
|
|
.for_each(drop);
|
|
self.macros.insert(name.clone(), Macro { args, body });
|
|
|
|
Ok(None)
|
|
}
|
|
"if" => {
|
|
let cond = (args[0].resolve_number(self)? & 1) == 1;
|
|
dbg!(cond);
|
|
|
|
let mut if_depth: usize = 1;
|
|
let body: Vec<_> = self
|
|
.body_stack
|
|
.iter()
|
|
.rev()
|
|
.take_while(|n| match n {
|
|
parser::ast::Node::MacroCall { name, args: _ } => match name.as_str() {
|
|
"if" => {
|
|
if_depth += 1;
|
|
true
|
|
}
|
|
"endif" => {
|
|
if_depth -= 1;
|
|
if_depth != 0
|
|
}
|
|
_ => true,
|
|
},
|
|
_ => true,
|
|
})
|
|
.cloned()
|
|
.collect();
|
|
self.body_stack
|
|
.drain((self.body_stack.len() - body.len() - 1)..)
|
|
.for_each(drop);
|
|
dbg!(&body);
|
|
|
|
let mut iter = body.iter().enumerate();
|
|
if let Some(pos) = loop {
|
|
if let Some(pos) = iter.next() {
|
|
if let parser::ast::Node::MacroCall { name, args: _ } = pos.1 {
|
|
match name.as_str() {
|
|
"if" => {
|
|
break None;
|
|
}
|
|
"else" => {
|
|
break Some(pos);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
} else {
|
|
break None;
|
|
}
|
|
} {
|
|
self.body_stack.extend(if cond {
|
|
body[..pos.0].to_vec()
|
|
} else {
|
|
body[(pos.0 + 1)..].to_vec()
|
|
});
|
|
} else if cond {
|
|
self.body_stack.extend(body.into_iter().rev());
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
"org" => {
|
|
self.offset = args[0].resolve_number(self)?;
|
|
|
|
Ok(None)
|
|
}
|
|
"org_add" => {
|
|
self.offset = self.offset.wrapping_add(args[0].resolve_number(self)?);
|
|
|
|
Ok(None)
|
|
}
|
|
"org_sub" => {
|
|
self.offset = self.offset.wrapping_sub(args[0].resolve_number(self)?);
|
|
|
|
Ok(None)
|
|
}
|
|
"bytes" => {
|
|
let mut data: Vec<u8> = vec![];
|
|
for arg in args {
|
|
data.append(&mut arg.resolve_bytes(self)?);
|
|
}
|
|
|
|
if data.is_empty() {
|
|
Ok(None)
|
|
} else {
|
|
Ok(Some(data))
|
|
}
|
|
}
|
|
"embed" => {
|
|
let arg = args[0].resolve_string(self)?;
|
|
let data = if Path::new(&arg).starts_with("$lib") {
|
|
match Lib::get(arg.as_str()) {
|
|
Some(x) => x.data.to_vec(),
|
|
None => bail!("Virtual lib file couldn't be found with path: {}", arg),
|
|
}
|
|
} else {
|
|
let path = Path::new(&self.dir).join(arg);
|
|
fs::read(path)?
|
|
};
|
|
|
|
Ok(Some(data))
|
|
}
|
|
"requires" => {
|
|
let arg = args[0].resolve_string(self)?;
|
|
if !self.includes.contains(&arg) {
|
|
bail!("File requires include of not included file: {}", arg);
|
|
}
|
|
Ok(None)
|
|
}
|
|
"include" => {
|
|
let arg = args[0].resolve_string(self)?;
|
|
let source = if Path::new(&arg).starts_with("$lib") {
|
|
match Lib::get(arg.as_str()) {
|
|
Some(x) => std::str::from_utf8(&x.data)?.to_string(),
|
|
None => bail!("Virtual lib file couldn't be found with path: {}", arg),
|
|
}
|
|
} else {
|
|
let path = Path::new(&self.dir).join(&arg);
|
|
fs::read_to_string(path)?
|
|
};
|
|
|
|
self.includes.insert(arg);
|
|
let body = parser::parse(lexer::lex(&source)?)?.body;
|
|
self.body_stack
|
|
.extend(body.into_iter().rev().collect::<Vec<_>>());
|
|
|
|
Ok(None)
|
|
}
|
|
_ => match self.macros.get(&name) {
|
|
Some(m) => {
|
|
if m.args.len() != args.len() {
|
|
bail!(
|
|
"Macro call argument signature does not match: (.macro {name}{macro_args}) != (.{name}{call_args})",
|
|
macro_args = if m.args.is_empty() {
|
|
"".to_string()
|
|
} else {
|
|
format!(" {}", m.args.join(", "))
|
|
},
|
|
call_args = if args.is_empty() {
|
|
"".to_string()
|
|
} else {
|
|
format!(" {}", args.iter().map(|a| a.to_code()).join(", "))
|
|
}
|
|
);
|
|
}
|
|
// dbg!(&args);
|
|
for (i, arg) in args.iter().enumerate() {
|
|
self.constants.insert(m.args[i].clone(), arg.clone());
|
|
}
|
|
self.body_stack
|
|
.append(&mut m.body.iter().rev().cloned().collect());
|
|
|
|
Ok(None)
|
|
}
|
|
None => bail!("Unknown macro: {}", name),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn assemble(&mut self) -> Result<Program> {
|
|
let offset_name = "OFFSET".to_string();
|
|
while !self.body_stack.is_empty() {
|
|
self.constants
|
|
.insert(offset_name.clone(), arg::Arg::Number(self.offset));
|
|
if let Some(x) = self.resolve_node()? {
|
|
if self.offset + (x.len() as u16) > 32 * 1024 {
|
|
bail!(
|
|
"Offset out of bounds: 0x{} > 0x8000",
|
|
radix(self.offset, 16)
|
|
);
|
|
}
|
|
for byte in x {
|
|
self.program[self.offset as usize] = byte;
|
|
self.offset += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(self.program)
|
|
}
|
|
}
|