Update assembler infrastructure to be more modular
Assembler has more discrete passes now, but things are kind of bare on the error message front. But, everything is working as it did previously, so we can move on to more interesting things. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -107,13 +107,11 @@ fn get_writer(path: impl AsRef<Path>) -> Result<Box<dyn Write>> {
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
use vm::{
|
use vm::{
|
||||||
state::State,
|
state::State,
|
||||||
obj::assemble::AsmSession,
|
obj::assemble,
|
||||||
};
|
};
|
||||||
|
|
||||||
let opt = Options::from_args();
|
let opt = Options::from_args();
|
||||||
let mut asm_session = AsmSession::default();
|
let object = assemble::assemble_path(&opt.input)?;
|
||||||
asm_session.include(&opt.input)?;
|
|
||||||
let object = asm_session.assemble()?;
|
|
||||||
|
|
||||||
if opt.compile_only {
|
if opt.compile_only {
|
||||||
let outfile = opt.out.clone().unwrap_or_else(|| {
|
let outfile = opt.out.clone().unwrap_or_else(|| {
|
||||||
|
|||||||
@@ -1,184 +1,34 @@
|
|||||||
|
pub mod error;
|
||||||
|
pub mod session;
|
||||||
|
mod includes;
|
||||||
|
mod names;
|
||||||
|
|
||||||
|
use self::{error::*, session::AsmSession};
|
||||||
use crate::vm::{
|
use crate::vm::{
|
||||||
addr::*,
|
addr::Addr,
|
||||||
inst,
|
inst,
|
||||||
obj::{
|
obj::{
|
||||||
obj,
|
obj::{self, Object},
|
||||||
syn::{ast::*, lexer, parser},
|
syn::ast::*,
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
use byteorder::{WriteBytesExt, LE};
|
use byteorder::{WriteBytesExt, LE};
|
||||||
use snafu::Snafu;
|
use std::{collections::HashMap, path::Path};
|
||||||
use std::{
|
|
||||||
collections::{BTreeSet, HashMap},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type LexError = lrpar::LexError;
|
pub trait Asm {
|
||||||
pub type ParseError = lrpar::ParseError<u32>;
|
|
||||||
pub type LexParseError = lrpar::LexParseError<u32>;
|
|
||||||
|
|
||||||
pub trait Assemble {
|
|
||||||
type Out;
|
type Out;
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out>;
|
fn assemble(&self, asm: &mut AsmSession) -> Result<Self::Out>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A shared session for the assembler.
|
pub fn assemble_path(path: impl AsRef<Path>) -> Result<Object> {
|
||||||
#[derive(Debug, Default)]
|
let mut session = AsmSession::default();
|
||||||
pub struct AsmSession {
|
session.include_path(path)?;
|
||||||
include_paths: Vec<PathBuf>,
|
session.assemble()
|
||||||
included_files: BTreeSet<PathBuf>,
|
|
||||||
include_stack: Vec<PathBuf>,
|
|
||||||
directives: Vec<Directive>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsmSession {
|
impl Asm for Vec<&'_ Directive> {
|
||||||
pub fn assemble(self) -> Result<obj::Object> {
|
|
||||||
let mut asm = Asm::default();
|
|
||||||
self.directives.assemble(&mut asm)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn include(&mut self, path: impl AsRef<Path>) -> Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let path = path.canonicalize()
|
|
||||||
.map_err(|_| AssembleError::BadPath { path: path.to_path_buf() })?;
|
|
||||||
if self.include_paths.contains(&path) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = std::fs::read_to_string(&path)
|
|
||||||
.map_err(|_| AssembleError::BadPath { path: path.to_path_buf() })?;
|
|
||||||
|
|
||||||
// Add file to included paths and the path stack
|
|
||||||
self.included_files.insert(path.clone());
|
|
||||||
self.include_stack.push(path);
|
|
||||||
|
|
||||||
self.include_text(&text)?;
|
|
||||||
|
|
||||||
self.include_stack.pop();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn include_stack(&self) -> &Vec<PathBuf> {
|
|
||||||
&self.include_stack
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_include_path(&self) -> Option<&Path> {
|
|
||||||
self.include_stack.last().map(PathBuf::as_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn include_text(&mut self, text: &str) -> Result<()> {
|
|
||||||
let lexerdef = lexer::lexerdef();
|
|
||||||
let lexer = lexerdef.lexer(&text);
|
|
||||||
let (res, errors) = parser::parse(&lexer);
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
return Err(AssembleError::Syntax {
|
|
||||||
source: errors.into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let ast = res.unwrap();
|
|
||||||
self.include_ast(ast)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn include_ast(&mut self, ast: Vec<Directive>) -> Result<()> {
|
|
||||||
for dir in ast.iter() {
|
|
||||||
if let Directive::Include(path) = dir {
|
|
||||||
let path = self.resolve_include_path(path)
|
|
||||||
.ok_or_else(|| AssembleError::BadPath { path: path.into() })?;
|
|
||||||
self.include(path)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.directives.extend(ast);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_include_path(&self, path: impl AsRef<Path>) -> Option<PathBuf> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
self.current_include_path()
|
|
||||||
.and_then(|last_path| last_path.parent())
|
|
||||||
.map(|last_dir| last_dir.join(path))
|
|
||||||
.or_else(|| self.include_paths
|
|
||||||
.iter()
|
|
||||||
.filter_map(|include| include.join(path).canonicalize().ok())
|
|
||||||
.next())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct Asm {
|
|
||||||
names: Vec<HashMap<String, Addr>>,
|
|
||||||
pos: Addr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Asm {
|
|
||||||
/// Gets an address value from a name, if it exists. Searches local -> global.
|
|
||||||
fn lookup_name(&self, name: &str) -> Result<Addr> {
|
|
||||||
self.names
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.filter_map(|names| names.get(name).copied())
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| AssembleError::UnknownName {
|
|
||||||
name: name.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets all names defined in a data section, their positions, and puts them into a hashmap.
|
|
||||||
fn gather_names(&self, section: &DataSection) -> Result<HashMap<String, Addr>> {
|
|
||||||
let mut names = HashMap::new();
|
|
||||||
let mut addr = Addr(section.org.start());
|
|
||||||
for line in section.lines.iter() {
|
|
||||||
match line {
|
|
||||||
DataLine::ValueDef(v) => addr += v.len(),
|
|
||||||
DataLine::Inst(inst) => addr += inst.len(),
|
|
||||||
DataLine::Export(_) => {}
|
|
||||||
DataLine::Label(label) => {
|
|
||||||
if let Some(_) = names.insert(label.to_string(), addr) {
|
|
||||||
return Err(AssembleError::DuplicateLabel {
|
|
||||||
name: label.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(addr, Addr(section.org.start() + (section.len() as u64)));
|
|
||||||
Ok(names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Assemble for Vec<Directive> {
|
|
||||||
type Out = obj::Object;
|
type Out = obj::Object;
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, asm: &mut AsmSession) -> Result<Self::Out> {
|
||||||
// collect globals
|
|
||||||
let mut globals = HashMap::new();
|
|
||||||
for section in self.iter() {
|
|
||||||
let section = if let Directive::Data(d) = section {
|
|
||||||
d
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let names = asm.gather_names(section)?;
|
|
||||||
for export in section.exports() {
|
|
||||||
let addr = *names
|
|
||||||
.get(export)
|
|
||||||
.ok_or_else(|| AssembleError::UnknownExport {
|
|
||||||
name: export.to_string(),
|
|
||||||
})?;
|
|
||||||
if globals.contains_key(export) {
|
|
||||||
return Err(AssembleError::DuplicateExport {
|
|
||||||
name: export.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
globals.insert(export.to_string(), addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO : detect section overlap
|
|
||||||
// TODO : single meta section
|
|
||||||
asm.names.clear();
|
|
||||||
asm.names.push(globals);
|
|
||||||
|
|
||||||
let sections = self
|
let sections = self
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|section| section.assemble(asm).transpose())
|
.filter_map(|section| section.assemble(asm).transpose())
|
||||||
@@ -190,10 +40,10 @@ impl Assemble for Vec<Directive> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for Directive {
|
impl Asm for Directive {
|
||||||
type Out = Option<obj::Section>;
|
type Out = Option<obj::Section>;
|
||||||
|
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, asm: &mut AsmSession) -> Result<Self::Out> {
|
||||||
match self {
|
match self {
|
||||||
Directive::Data(section) => Ok(Some(obj::Section::Data(section.assemble(asm)?))),
|
Directive::Data(section) => Ok(Some(obj::Section::Data(section.assemble(asm)?))),
|
||||||
Directive::Meta(section) => section.assemble(asm).map(Some),
|
Directive::Meta(section) => section.assemble(asm).map(Some),
|
||||||
@@ -202,24 +52,24 @@ impl Assemble for Directive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for DataSection {
|
impl Asm for DataSection {
|
||||||
type Out = obj::DataSection;
|
type Out = obj::DataSection;
|
||||||
|
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||||
let names = asm.gather_names(self)?;
|
let names = names::get_section_names(self)?;
|
||||||
asm.names.push(names);
|
session.name_stack.push(names);
|
||||||
let section_len = self.len() as u64;
|
let section_len = self.len() as u64;
|
||||||
let (start, end) = match self.org {
|
let (start, end) = match self.org {
|
||||||
SectionOrg::Start(start) => (start, start + (section_len as u64)),
|
SectionOrg::Start(start) => (start, start + (section_len as u64)),
|
||||||
SectionOrg::StartEnd(start, end) => (start, end),
|
SectionOrg::StartEnd(start, end) => (start, end),
|
||||||
};
|
};
|
||||||
asm.pos = Addr(start);
|
session.pos = Addr(start);
|
||||||
if start > end {
|
if start > end {
|
||||||
return Err(AssembleError::StartGreaterThanEnd { start, end });
|
return Err(AsmError::StartGreaterThanEnd { start, end });
|
||||||
}
|
}
|
||||||
let len = end - start - 1;
|
let len = end - start - 1;
|
||||||
if len > section_len {
|
if len > section_len {
|
||||||
return Err(AssembleError::SectionTooShort {
|
return Err(AsmError::SectionTooShort {
|
||||||
section_end: end,
|
section_end: end,
|
||||||
section_size: start + section_len,
|
section_size: start + section_len,
|
||||||
});
|
});
|
||||||
@@ -227,8 +77,8 @@ impl Assemble for DataSection {
|
|||||||
|
|
||||||
let mut contents = Vec::with_capacity(section_len as usize);
|
let mut contents = Vec::with_capacity(section_len as usize);
|
||||||
for line in self.lines.iter() {
|
for line in self.lines.iter() {
|
||||||
contents.extend(line.assemble(asm)?);
|
contents.extend(line.assemble(session)?);
|
||||||
asm.pos += line.len();
|
session.pos += line.len();
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
contents.len() as u64,
|
contents.len() as u64,
|
||||||
@@ -236,7 +86,7 @@ impl Assemble for DataSection {
|
|||||||
"in section {}",
|
"in section {}",
|
||||||
self.name
|
self.name
|
||||||
);
|
);
|
||||||
asm.names.pop();
|
session.name_stack.pop();
|
||||||
Ok(obj::DataSection {
|
Ok(obj::DataSection {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
start,
|
start,
|
||||||
@@ -246,22 +96,23 @@ impl Assemble for DataSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for MetaSection {
|
impl Asm for MetaSection {
|
||||||
type Out = obj::Section;
|
type Out = obj::Section;
|
||||||
|
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||||
let mut entries = HashMap::new();
|
let mut entries = HashMap::new();
|
||||||
for line in self.lines.iter() {
|
for line in self.lines.iter() {
|
||||||
if entries.contains_key(&line.name) {
|
if entries.contains_key(&line.name) {
|
||||||
return Err(AssembleError::DuplicateMetaName {
|
return Err(AsmError::DuplicateMetaName {
|
||||||
name: line.name.to_string(),
|
name: line.name.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let value = match &line.value {
|
let value = match &line.value {
|
||||||
Value::Int(i) => *i,
|
Value::Int(i) => *i,
|
||||||
Value::Name(s) => asm.lookup_name(s.as_str())?.0,
|
Value::Name(s) => session.lookup_name(s.as_str())
|
||||||
|
.ok_or_else(|| AsmError::UnknownName { name: s.to_string() })?.addr.0,
|
||||||
Value::Reg(_) | Value::Here | Value::Addr(_, _) => {
|
Value::Reg(_) | Value::Here | Value::Addr(_, _) => {
|
||||||
return Err(AssembleError::IllegalMetaValue {
|
return Err(AsmError::IllegalMetaValue {
|
||||||
name: line.name.to_string(),
|
name: line.name.to_string(),
|
||||||
value: line.value.clone(),
|
value: line.value.clone(),
|
||||||
})
|
})
|
||||||
@@ -275,22 +126,22 @@ impl Assemble for MetaSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for DataLine {
|
impl Asm for DataLine {
|
||||||
type Out = Vec<u8>;
|
type Out = Vec<u8>;
|
||||||
|
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||||
match self {
|
match self {
|
||||||
DataLine::ValueDef(v) => v.assemble(asm),
|
DataLine::ValueDef(v) => v.assemble(session),
|
||||||
DataLine::Inst(i) => i.assemble(asm),
|
DataLine::Inst(i) => i.assemble(session),
|
||||||
DataLine::Export(_) | DataLine::Label(_) => Ok(Vec::new()),
|
DataLine::Export(_) | DataLine::Label(_) => Ok(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for ValueDef {
|
impl Asm for ValueDef {
|
||||||
type Out = Vec<u8>;
|
type Out = Vec<u8>;
|
||||||
|
|
||||||
fn assemble(&self, _: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, _: &mut AsmSession) -> Result<Self::Out> {
|
||||||
match self {
|
match self {
|
||||||
ValueDef::Int(x, s) => Ok(x.to_le_bytes().iter().copied().take(s.len()).collect()),
|
ValueDef::Int(x, s) => Ok(x.to_le_bytes().iter().copied().take(s.len()).collect()),
|
||||||
ValueDef::String(s) => {
|
ValueDef::String(s) => {
|
||||||
@@ -309,10 +160,10 @@ impl Assemble for ValueDef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for Inst {
|
impl Asm for Inst {
|
||||||
type Out = Vec<u8>;
|
type Out = Vec<u8>;
|
||||||
|
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||||
let len = self.len();
|
let len = self.len();
|
||||||
|
|
||||||
macro_rules! map_inst {
|
macro_rules! map_inst {
|
||||||
@@ -322,7 +173,7 @@ impl Assemble for Inst {
|
|||||||
let dest = $dest;
|
let dest = $dest;
|
||||||
let dest_encoding =
|
let dest_encoding =
|
||||||
dest.dest_encoding()
|
dest.dest_encoding()
|
||||||
.ok_or_else(|| AssembleError::IllegalDestValue {
|
.ok_or_else(|| AsmError::IllegalDestValue {
|
||||||
value: dest.clone(),
|
value: dest.clone(),
|
||||||
})?;
|
})?;
|
||||||
let source = $source;
|
let source = $source;
|
||||||
@@ -330,8 +181,8 @@ impl Assemble for Inst {
|
|||||||
bytes
|
bytes
|
||||||
.write_u8((dest_encoding << 4) | source_encoding)
|
.write_u8((dest_encoding << 4) | source_encoding)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bytes.extend(dest.assemble(asm)?);
|
bytes.extend(dest.assemble(session)?);
|
||||||
bytes.extend(source.assemble(asm)?);
|
bytes.extend(source.assemble(session)?);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.len(),
|
self.len(),
|
||||||
bytes.len(),
|
bytes.len(),
|
||||||
@@ -349,7 +200,7 @@ impl Assemble for Inst {
|
|||||||
let source = $source;
|
let source = $source;
|
||||||
let source_encoding = source.source_encoding() << 4;
|
let source_encoding = source.source_encoding() << 4;
|
||||||
bytes.write_u8(source_encoding).unwrap();
|
bytes.write_u8(source_encoding).unwrap();
|
||||||
bytes.extend(source.assemble(asm)?);
|
bytes.extend(source.assemble(session)?);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.len(),
|
self.len(),
|
||||||
bytes.len(),
|
bytes.len(),
|
||||||
@@ -404,7 +255,7 @@ impl Assemble for Inst {
|
|||||||
bytes.write_u16::<LE>(inst::POP).unwrap();
|
bytes.write_u16::<LE>(inst::POP).unwrap();
|
||||||
let dest_encoding = dest.source_encoding() << 4;
|
let dest_encoding = dest.source_encoding() << 4;
|
||||||
bytes.write_u8(dest_encoding).unwrap();
|
bytes.write_u8(dest_encoding).unwrap();
|
||||||
bytes.extend(dest.assemble(asm)?);
|
bytes.extend(dest.assemble(session)?);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.len(),
|
self.len(),
|
||||||
bytes.len(),
|
bytes.len(),
|
||||||
@@ -422,149 +273,45 @@ impl Assemble for Inst {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assemble for Value {
|
impl Asm for Value {
|
||||||
type Out = Vec<u8>;
|
type Out = Vec<u8>;
|
||||||
|
|
||||||
fn assemble(&self, asm: &mut Asm) -> Result<Self::Out> {
|
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||||
match self {
|
match self {
|
||||||
Value::Int(i) => Ok(i.to_le_bytes().to_vec()),
|
Value::Int(i) => Ok(i.to_le_bytes().to_vec()),
|
||||||
Value::Reg(r) => Ok(vec![*r]),
|
Value::Reg(r) => Ok(vec![*r]),
|
||||||
Value::Name(name) => {
|
Value::Name(name) => {
|
||||||
let value = asm.lookup_name(name.as_str())?;
|
let value = session.lookup_name(name.as_str())
|
||||||
Ok(value.0.to_le_bytes().to_vec())
|
.ok_or_else(|| AsmError::UnknownName { name: name.to_string() })?;
|
||||||
|
Ok(value.addr.0.to_le_bytes().to_vec())
|
||||||
}
|
}
|
||||||
Value::Here => Ok(asm.pos.0.to_le_bytes().to_vec()),
|
Value::Here => Ok(session.pos.0.to_le_bytes().to_vec()),
|
||||||
Value::Addr(v, _) => {
|
Value::Addr(v, _) => {
|
||||||
if let Value::Addr(_, _) = &**v {
|
if let Value::Addr(_, _) = &**v {
|
||||||
// double deref is not allowed
|
// double deref is not allowed
|
||||||
todo!()
|
todo!()
|
||||||
} else {
|
} else {
|
||||||
v.assemble(asm)
|
v.assemble(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum AssembleError {
|
|
||||||
#[snafu(display("unknown name: {}", name))]
|
|
||||||
UnknownName {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("unknown export name: {}", name))]
|
|
||||||
UnknownExport {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("duplicate label definition: {}", name))]
|
|
||||||
DuplicateLabel {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("duplicate meta entry name: {}", name))]
|
|
||||||
DuplicateMetaName {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("illegal meta value for entry name {}: {:?}", name, value))]
|
|
||||||
IllegalMetaValue {
|
|
||||||
name: String,
|
|
||||||
value: Value,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("duplicate exported name: {}", name))]
|
|
||||||
DuplicateExport {
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("section start ({:#x}) is greater than end ({:#x})", start, end))]
|
|
||||||
StartGreaterThanEnd {
|
|
||||||
start: u64,
|
|
||||||
end: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display(
|
|
||||||
"section end ({:#x}) too short for section content size ({:#x})",
|
|
||||||
section_end,
|
|
||||||
section_size
|
|
||||||
))]
|
|
||||||
SectionTooShort {
|
|
||||||
section_end: u64,
|
|
||||||
section_size: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("illegal instruction destination value: {:?}", value))]
|
|
||||||
IllegalDestValue {
|
|
||||||
value: Value,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("deref of a deref value is not allowed"))]
|
|
||||||
DoubleDeref {
|
|
||||||
value: Value,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[snafu(display("could not read path: {}", path.display()))]
|
|
||||||
BadPath {
|
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
|
|
||||||
Syntax {
|
|
||||||
source: SyntaxError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum SyntaxError {
|
|
||||||
Lex { source: LexError },
|
|
||||||
Parse { source: ParseError },
|
|
||||||
Multi { errors: Vec<LexParseError> },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LexError> for SyntaxError {
|
|
||||||
fn from(source: LexError) -> Self {
|
|
||||||
SyntaxError::Lex { source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ParseError> for SyntaxError {
|
|
||||||
fn from(source: ParseError) -> Self {
|
|
||||||
SyntaxError::Parse { source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<LexParseError>> for SyntaxError {
|
|
||||||
fn from(errors: Vec<LexParseError>) -> Self {
|
|
||||||
SyntaxError::Multi { errors }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LexParseError> for SyntaxError {
|
|
||||||
fn from(source: LexParseError) -> Self {
|
|
||||||
match source {
|
|
||||||
LexParseError::LexError(e) => SyntaxError::Lex { source: e },
|
|
||||||
LexParseError::ParseError(e) => SyntaxError::Parse { source: e },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T, E = AssembleError> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inst_len() {
|
fn test_inst_len() {
|
||||||
let mut asm = Asm::default();
|
let mut session = AsmSession::default();
|
||||||
asm.names
|
//asm.names
|
||||||
.push(vec![("test".to_string(), Addr(0u64))].into_iter().collect());
|
//.push(vec![("test".to_string(), Addr(0u64))].into_iter().collect());
|
||||||
|
|
||||||
macro_rules! assert_len {
|
macro_rules! assert_len {
|
||||||
($inst:expr) => {{
|
($inst:expr) => {{
|
||||||
let inst = $inst;
|
let inst = $inst;
|
||||||
let asm_size = $inst.assemble(&mut asm).unwrap().len();
|
let asm_size = $inst.assemble(&mut session).unwrap().len();
|
||||||
assert_eq!(inst.len(), asm_size, "Instruction {:?}.len() indicates it should be {} bytes long but was assembled as {} bytes", inst, inst.len(), asm_size);
|
assert_eq!(inst.len(), asm_size, "Instruction {:?}.len() indicates it should be {} bytes long but was assembled as {} bytes", inst, inst.len(), asm_size);
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@@ -575,18 +322,18 @@ mod test {
|
|||||||
Value::Reg(0),
|
Value::Reg(0),
|
||||||
Value::Addr(Box::new(Value::Reg(0)), IntSize::U8),
|
Value::Addr(Box::new(Value::Reg(0)), IntSize::U8),
|
||||||
Value::Addr(Box::new(Value::Here), IntSize::U16),
|
Value::Addr(Box::new(Value::Here), IntSize::U16),
|
||||||
Value::Addr(Box::new(Value::Name("test".to_string())), IntSize::U32),
|
//Value::Addr(Box::new(Value::Name("test".to_string())), IntSize::U32),
|
||||||
Value::Addr(Box::new(Value::Int(0)), IntSize::U64),
|
Value::Addr(Box::new(Value::Int(0)), IntSize::U64),
|
||||||
];
|
];
|
||||||
|
|
||||||
let dummy_sources = &[
|
let dummy_sources = &[
|
||||||
Value::Int(0),
|
Value::Int(0),
|
||||||
Value::Reg(0),
|
Value::Reg(0),
|
||||||
Value::Name("test".to_string()),
|
//Value::Name("test".to_string()),
|
||||||
Value::Here,
|
Value::Here,
|
||||||
Value::Addr(Box::new(Value::Reg(0)), IntSize::U8),
|
Value::Addr(Box::new(Value::Reg(0)), IntSize::U8),
|
||||||
Value::Addr(Box::new(Value::Here), IntSize::U16),
|
Value::Addr(Box::new(Value::Here), IntSize::U16),
|
||||||
Value::Addr(Box::new(Value::Name("test".to_string())), IntSize::U32),
|
//Value::Addr(Box::new(Value::Name("test".to_string())), IntSize::U32),
|
||||||
Value::Addr(Box::new(Value::Int(0)), IntSize::U32),
|
Value::Addr(Box::new(Value::Int(0)), IntSize::U32),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
114
src/vm/obj/assemble/error.rs
Normal file
114
src/vm/obj/assemble/error.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
use crate::vm::obj::syn::ast::*;
|
||||||
|
use snafu::Snafu;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub type LexError = lrpar::LexError;
|
||||||
|
pub type ParseError = lrpar::ParseError<u32>;
|
||||||
|
pub type LexParseError = lrpar::LexParseError<u32>;
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
pub enum AsmError {
|
||||||
|
#[snafu(display("unknown name: {}", name))]
|
||||||
|
UnknownName {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("unknown export name: {}", name))]
|
||||||
|
UnknownExport {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("duplicate label definition: {}", name))]
|
||||||
|
DuplicateLabel {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("duplicate meta entry name: {}", name))]
|
||||||
|
DuplicateMetaName {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("illegal meta value for entry name {}: {:?}", name, value))]
|
||||||
|
IllegalMetaValue {
|
||||||
|
name: String,
|
||||||
|
value: Value,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("duplicate exported name: {}", name))]
|
||||||
|
DuplicateExport {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("section start ({:#x}) is greater than end ({:#x})", start, end))]
|
||||||
|
StartGreaterThanEnd {
|
||||||
|
start: u64,
|
||||||
|
end: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display(
|
||||||
|
"section end ({:#x}) too short for section content size ({:#x})",
|
||||||
|
section_end,
|
||||||
|
section_size
|
||||||
|
))]
|
||||||
|
SectionTooShort {
|
||||||
|
section_end: u64,
|
||||||
|
section_size: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("illegal instruction destination value: {:?}", value))]
|
||||||
|
IllegalDestValue {
|
||||||
|
value: Value,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("deref of a deref value is not allowed"))]
|
||||||
|
DoubleDeref {
|
||||||
|
value: Value,
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO(asm) : Path error wrapper for assembling things
|
||||||
|
#[snafu(display("could not read path: {}", path.display()))]
|
||||||
|
BadPath {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
Syntax {
|
||||||
|
source: SyntaxError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
pub enum SyntaxError {
|
||||||
|
Lex { source: LexError },
|
||||||
|
Parse { source: ParseError },
|
||||||
|
Multi { errors: Vec<LexParseError> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LexError> for SyntaxError {
|
||||||
|
fn from(source: LexError) -> Self {
|
||||||
|
SyntaxError::Lex { source }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseError> for SyntaxError {
|
||||||
|
fn from(source: ParseError) -> Self {
|
||||||
|
SyntaxError::Parse { source }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<LexParseError>> for SyntaxError {
|
||||||
|
fn from(errors: Vec<LexParseError>) -> Self {
|
||||||
|
SyntaxError::Multi { errors }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LexParseError> for SyntaxError {
|
||||||
|
fn from(source: LexParseError) -> Self {
|
||||||
|
match source {
|
||||||
|
LexParseError::LexError(e) => SyntaxError::Lex { source: e },
|
||||||
|
LexParseError::ParseError(e) => SyntaxError::Parse { source: e },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T, E = AsmError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
68
src/vm/obj/assemble/includes.rs
Normal file
68
src/vm/obj/assemble/includes.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use crate::vm::obj::{
|
||||||
|
assemble::{error::*, session::AsmSession, Asm},
|
||||||
|
syn::{ast::Directive, lexer, parser},
|
||||||
|
};
|
||||||
|
use std::{path::PathBuf, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GetIncludes {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetIncludes {
|
||||||
|
pub fn assemble_from_path(path: PathBuf, session: &mut AsmSession) -> Result<()> {
|
||||||
|
let path = path
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|_| AsmError::BadPath { path })?;
|
||||||
|
GetIncludes { path }.assemble(session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Asm for GetIncludes {
|
||||||
|
type Out = ();
|
||||||
|
|
||||||
|
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||||
|
assert!(self.path.is_absolute());
|
||||||
|
if session.includes.contains_key(&self.path) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = std::fs::read_to_string(&self.path).map_err(|_| AsmError::BadPath {
|
||||||
|
path: self.path.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let lexerdef = lexer::lexerdef();
|
||||||
|
let lexer = lexerdef.lexer(&text);
|
||||||
|
let (res, errors) = parser::parse(&lexer);
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return Err(AsmError::Syntax {
|
||||||
|
source: errors.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert a dummy AST in the includes and replace it when we're done with the actual AST
|
||||||
|
Rc::get_mut(&mut session.includes)
|
||||||
|
.unwrap()
|
||||||
|
.insert(self.path.clone(), Default::default());
|
||||||
|
session.include_stack.push(self.path.clone());
|
||||||
|
let ast = res.unwrap();
|
||||||
|
for directive in ast.iter() {
|
||||||
|
if let Directive::Include(include_path) = directive {
|
||||||
|
GetIncludes::assemble_from_path(PathBuf::from(include_path), session)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.include_stack.pop();
|
||||||
|
|
||||||
|
let dummy = Rc::get_mut(&mut session.includes)
|
||||||
|
.unwrap()
|
||||||
|
.insert(self.path.clone(), ast)
|
||||||
|
.expect("ast was removed after its inclusion (why did you do that?)");
|
||||||
|
assert!(
|
||||||
|
dummy.is_empty(),
|
||||||
|
"ast was modified after its inclusion (why did you do that?)"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/vm/obj/assemble/names.rs
Normal file
104
src/vm/obj/assemble/names.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use crate::vm::{
|
||||||
|
addr::Addr,
|
||||||
|
obj::{
|
||||||
|
assemble::{
|
||||||
|
session::AsmSession,
|
||||||
|
error::*,
|
||||||
|
},
|
||||||
|
syn::ast::{DataSection, DataLine, Directive},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{collections::{HashMap, HashSet}, rc::Rc};
|
||||||
|
|
||||||
|
// TODO(asm) make custom Names type that has "merge" that will catch errors with duplicate names
|
||||||
|
pub type Names = HashMap<String, Name>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Name {
|
||||||
|
pub name: String,
|
||||||
|
pub addr: Addr,
|
||||||
|
pub export: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_section_names(section: &DataSection) -> Result<Names> {
|
||||||
|
let mut pos = Addr(section.org.start());
|
||||||
|
let mut names = HashMap::new();
|
||||||
|
let mut exports = HashSet::new();
|
||||||
|
|
||||||
|
// This isn't immediately straightforward in code, so this is what's happening.
|
||||||
|
//
|
||||||
|
// A name may be specified exactly once. It is added to the "names" mapping. If the name is
|
||||||
|
// specified again, it is a duplicate error and handled as such. A name may be flagged as
|
||||||
|
// being exported.
|
||||||
|
//
|
||||||
|
// Exports are gathered at the beginning into a set. Afterwards, as name definitions
|
||||||
|
// are gathered, exports are removed from their set if they exist. Since duplicate names
|
||||||
|
// cause an error, export names will only be removed once.
|
||||||
|
//
|
||||||
|
// At the end of name gathering, if any exports have not been removed, then we know those
|
||||||
|
// are exports whose names are undefined and we can return an UnknownExport error.
|
||||||
|
|
||||||
|
// get exported names
|
||||||
|
for line in section.lines.iter() {
|
||||||
|
if let DataLine::Export(name) = line {
|
||||||
|
exports.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get names
|
||||||
|
for line in section.lines.iter() {
|
||||||
|
if let DataLine::Label(name) = line {
|
||||||
|
if names.contains_key(name) {
|
||||||
|
return Err(AsmError::DuplicateLabel { name: name.clone() });
|
||||||
|
}
|
||||||
|
let export = exports.remove(name);
|
||||||
|
|
||||||
|
names.insert(name.clone(), Name {
|
||||||
|
name: name.clone(),
|
||||||
|
addr: pos,
|
||||||
|
export,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pos += line.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
// all exports map 1:1 with names
|
||||||
|
if exports.is_empty() {
|
||||||
|
Ok(names)
|
||||||
|
} else {
|
||||||
|
Err(AsmError::UnknownExport { name: exports.iter().next().unwrap().to_string() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exports(session: &mut AsmSession) -> Result<Names> {
|
||||||
|
let includes = Rc::clone(&session.includes);
|
||||||
|
let mut all_exports = Vec::new();
|
||||||
|
|
||||||
|
// get *all* exports first
|
||||||
|
for (_, ast) in includes.iter() {
|
||||||
|
for directive in ast.iter() {
|
||||||
|
if let Directive::Data(section) = directive {
|
||||||
|
let names: HashMap<_, _> = get_section_names(section)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, name)| name.export)
|
||||||
|
.collect();
|
||||||
|
all_exports.push(names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut exports = HashSet::new();
|
||||||
|
|
||||||
|
// make a set of all export names, erroring on duplicate export
|
||||||
|
for name_map in all_exports.iter() {
|
||||||
|
let names_set: HashSet<_> = name_map.keys().collect();
|
||||||
|
if let Some(dupe) = exports.intersection(&names_set).next() {
|
||||||
|
return Err(AsmError::DuplicateExport { name: dupe.to_string() });
|
||||||
|
}
|
||||||
|
exports.extend(names_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this can probably be done with a fancy combinator chain
|
||||||
|
Ok(all_exports.into_iter()
|
||||||
|
.fold(Names::new(), |mut acc, val| { acc.extend(val); acc }))
|
||||||
|
}
|
||||||
72
src/vm/obj/assemble/session.rs
Normal file
72
src/vm/obj/assemble/session.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
use crate::vm::{
|
||||||
|
addr::Addr,
|
||||||
|
obj::{
|
||||||
|
assemble::{
|
||||||
|
Asm,
|
||||||
|
includes::GetIncludes,
|
||||||
|
names::{self, Name, Names},
|
||||||
|
error::*,
|
||||||
|
},
|
||||||
|
obj::Object,
|
||||||
|
syn::ast::Ast,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A shared session for the assembler.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AsmSession {
|
||||||
|
pub (in super) includes: Rc<BTreeMap<PathBuf, Ast>>,
|
||||||
|
pub (in super) include_search_paths: Vec<PathBuf>,
|
||||||
|
pub (in super) include_stack: Vec<PathBuf>,
|
||||||
|
pub (in super) name_stack: Vec<Names>,
|
||||||
|
pub (in super) pos: Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsmSession {
|
||||||
|
pub fn assemble(&mut self) -> Result<Object> {
|
||||||
|
let includes = Rc::clone(&self.includes);
|
||||||
|
let sections: Vec<_> = includes.iter()
|
||||||
|
.flat_map(|(_, ast)| ast)
|
||||||
|
.collect();
|
||||||
|
sections.assemble(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn include_path(&mut self, path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
self.include_stack.clear();
|
||||||
|
let path = path.as_ref().to_path_buf();
|
||||||
|
|
||||||
|
GetIncludes::assemble_from_path(path, self)?;
|
||||||
|
let exports = names::get_exports(self)?;
|
||||||
|
// TODO(asm) - merge exports
|
||||||
|
self.name_stack.push(exports);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub (in super) fn current_include_path(&self) -> Option<&Path> {
|
||||||
|
self.include_stack.last().map(PathBuf::as_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub (in super) fn resolve_include_path(&self, path: impl AsRef<Path>) -> Option<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.current_include_path()
|
||||||
|
.and_then(|last_path| last_path.parent())
|
||||||
|
.map(|last_dir| last_dir.join(path))
|
||||||
|
.or_else(|| self.include_search_paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|include| include.join(path).canonicalize().ok())
|
||||||
|
.next())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup_name(&self, name: &str) -> Option<&Name> {
|
||||||
|
self.name_stack
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|names| names.get(name))
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ use crate::vm::{
|
|||||||
reg::Reg,
|
reg::Reg,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub type Ast = Vec<Directive>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Directive {
|
pub enum Directive {
|
||||||
Meta(MetaSection),
|
Meta(MetaSection),
|
||||||
|
|||||||
Reference in New Issue
Block a user