use crate::collections::HashMap;
use crate::context::Context;
use crate::context::Item;
use crate::hash::Hash;
use crate::vm::{Inst, VmError};
use std::fmt;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CompilationUnitError {
#[error("conflicting function signature already exists `{existing}`")]
FunctionConflict {
existing: UnitFnSignature,
},
#[error("conflicting use already exists `{existing}`")]
ImportConflict {
existing: Item,
},
#[error("missing static string for hash `{hash}` and slot `{slot}`")]
StaticStringMissing {
hash: Hash,
slot: usize,
},
#[error(
"conflicting static string for hash `{hash}` between `{existing:?}` and `{current:?}`"
)]
StaticStringHashConflict {
hash: Hash,
current: String,
existing: String,
},
#[error("missing static object keys for hash `{hash}` and slot `{slot}`")]
StaticObjectKeysMissing {
hash: Hash,
slot: usize,
},
#[error(
"conflicting static object keys for hash `{hash}` between `{existing:?}` and `{current:?}`"
)]
StaticObjectKeysHashConflict {
hash: Hash,
current: Box<[String]>,
existing: Box<[String]>,
},
#[error("duplicate label `{label}`")]
DuplicateLabel {
label: Label,
},
#[error("missing label `{label}`")]
MissingLabel {
label: Label,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn with_start(self, start: usize) -> Self {
Self {
start,
end: self.end,
}
}
pub fn with_end(self, end: usize) -> Self {
Self {
start: self.start,
end,
}
}
pub fn overlaps(self, other: Span) -> bool {
self.start <= other.start && self.end >= other.end
}
pub const fn empty() -> Self {
Self { start: 0, end: 0 }
}
pub fn len(self) -> usize {
self.end.saturating_sub(self.start)
}
pub fn join(self, other: Self) -> Self {
Self {
start: usize::min(self.start, other.start),
end: usize::max(self.end, other.end),
}
}
pub fn point(pos: usize) -> Self {
Self {
start: pos,
end: pos,
}
}
pub fn narrow(self, amount: usize) -> Self {
Self {
start: self.start.saturating_add(amount),
end: self.end.saturating_sub(amount),
}
}
pub fn trim_start(self, amount: usize) -> Self {
Self {
start: usize::min(self.start.saturating_add(amount), self.end),
end: self.end,
}
}
}
impl fmt::Display for Span {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}:{}", self.start, self.end)
}
}
#[derive(Debug)]
pub struct UnitFnInfo {
pub offset: usize,
pub signature: UnitFnSignature,
}
#[derive(Debug)]
pub struct UnitFnSignature {
pub path: Item,
pub args: usize,
}
impl UnitFnSignature {
pub fn new(path: Item, args: usize) -> Self {
Self {
path: path.to_owned(),
args,
}
}
}
impl fmt::Display for UnitFnSignature {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}(", self.path)?;
let mut it = 0..self.args;
let last = it.next_back();
for _ in it {
write!(fmt, "arg, ")?;
}
if last.is_some() {
write!(fmt, "arg")?;
}
write!(fmt, ")")?;
Ok(())
}
}
#[derive(Debug)]
pub struct DebugInfo {
pub span: Span,
pub comment: Option<Box<str>>,
pub label: Option<Label>,
}
#[derive(Debug)]
pub struct CompilationUnit {
instructions: Vec<Inst>,
imports: HashMap<String, Item>,
functions: HashMap<Hash, UnitFnInfo>,
functions_rev: HashMap<usize, Hash>,
static_strings: Vec<String>,
static_string_rev: HashMap<Hash, usize>,
static_object_keys: Vec<Box<[String]>>,
static_object_keys_rev: HashMap<Hash, usize>,
debug: Vec<DebugInfo>,
label_count: usize,
required_functions: HashMap<Hash, Vec<Span>>,
}
impl CompilationUnit {
pub fn new() -> Self {
Self {
instructions: Vec::new(),
imports: HashMap::new(),
functions: HashMap::new(),
functions_rev: HashMap::new(),
static_strings: Vec::new(),
static_string_rev: HashMap::new(),
static_object_keys: Vec::new(),
static_object_keys_rev: HashMap::new(),
debug: Vec::new(),
label_count: 0,
required_functions: HashMap::new(),
}
}
pub fn with_default_prelude() -> Self {
let mut this = Self::new();
this.imports
.insert(String::from("dbg"), Item::of(&["std", "dbg"]));
this.imports
.insert(String::from("unit"), Item::of(&["std", "unit"]));
this.imports
.insert(String::from("bool"), Item::of(&["std", "bool"]));
this.imports
.insert(String::from("char"), Item::of(&["std", "char"]));
this.imports
.insert(String::from("int"), Item::of(&["std", "int"]));
this.imports
.insert(String::from("float"), Item::of(&["std", "float"]));
this.imports.insert(
String::from("Object"),
Item::of(&["std", "object", "Object"]),
);
this.imports
.insert(String::from("Array"), Item::of(&["std", "array", "Array"]));
this.imports.insert(
String::from("String"),
Item::of(&["std", "string", "String"]),
);
this
}
pub fn function_at(&self, n: usize) -> Option<(Hash, &UnitFnInfo)> {
let hash = self.functions_rev.get(&n).copied()?;
Some((hash, self.functions.get(&hash)?))
}
pub fn debug_info_at(&self, n: usize) -> Option<&DebugInfo> {
self.debug.get(n)
}
pub fn instruction_at(&self, ip: usize) -> Option<&Inst> {
self.instructions.get(ip)
}
pub fn iter_static_strings(&self) -> impl Iterator<Item = (Hash, &str)> + '_ {
let mut it = self.static_strings.iter();
std::iter::from_fn(move || {
let s = it.next()?;
Some((Hash::of(s), s.as_str()))
})
}
pub fn iter_static_object_keys(&self) -> impl Iterator<Item = (Hash, &[String])> + '_ {
let mut it = self.static_object_keys.iter();
std::iter::from_fn(move || {
let s = it.next()?;
Some((Hash::object_keys(&s[..]), &s[..]))
})
}
pub fn iter_instructions(&self) -> impl Iterator<Item = Inst> + '_ {
self.instructions.iter().copied()
}
pub fn iter_functions(&self) -> impl Iterator<Item = (Hash, &UnitFnInfo)> + '_ {
let mut it = self.functions.iter();
std::iter::from_fn(move || {
let (k, v) = it.next()?;
Some((*k, v))
})
}
pub fn iter_imports<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a Item)> + '_ {
let mut it = self.imports.iter();
std::iter::from_fn(move || {
let (k, v) = it.next()?;
Some((k.as_str(), v))
})
}
pub fn lookup_string(&self, slot: usize) -> Result<&str, VmError> {
Ok(self
.static_strings
.get(slot)
.ok_or_else(|| VmError::MissingStaticString { slot })?
.as_str())
}
pub fn lookup_object_keys(&self, slot: usize) -> Option<&[String]> {
self.static_object_keys.get(slot).map(|keys| &keys[..])
}
pub fn new_static_string(&mut self, current: &str) -> Result<usize, CompilationUnitError> {
let hash = Hash::of(¤t);
if let Some(existing_slot) = self.static_string_rev.get(&hash).copied() {
let existing = self.static_strings.get(existing_slot).ok_or_else(|| {
CompilationUnitError::StaticStringMissing {
hash,
slot: existing_slot,
}
})?;
if existing != current {
return Err(CompilationUnitError::StaticStringHashConflict {
hash,
current: current.to_owned(),
existing: existing.clone(),
});
}
return Ok(existing_slot);
}
let new_slot = self.static_strings.len();
self.static_strings.push(current.to_owned());
self.static_string_rev.insert(hash, new_slot);
Ok(new_slot)
}
pub fn new_static_object_keys(
&mut self,
current: &[String],
) -> Result<usize, CompilationUnitError> {
let current = current.to_vec().into_boxed_slice();
let hash = Hash::object_keys(¤t[..]);
if let Some(existing_slot) = self.static_object_keys_rev.get(&hash).copied() {
let existing = self.static_object_keys.get(existing_slot).ok_or_else(|| {
CompilationUnitError::StaticObjectKeysMissing {
hash,
slot: existing_slot,
}
})?;
if *existing != current {
return Err(CompilationUnitError::StaticObjectKeysHashConflict {
hash,
current,
existing: existing.clone(),
});
}
return Ok(existing_slot);
}
let new_slot = self.static_object_keys.len();
self.static_object_keys.push(current);
self.static_object_keys_rev.insert(hash, new_slot);
Ok(new_slot)
}
pub fn lookup(&self, hash: Hash) -> Option<&UnitFnInfo> {
self.functions.get(&hash)
}
pub fn lookup_offset(&self, hash: Hash) -> Option<usize> {
Some(self.functions.get(&hash)?.offset)
}
pub fn lookup_import_by_name(&self, name: &str) -> Option<&Item> {
self.imports.get(name)
}
pub fn new_import<I>(&mut self, path: I) -> Result<(), CompilationUnitError>
where
I: Copy + IntoIterator,
I::Item: AsRef<str>,
{
let path = Item::of(path);
if let Some(last) = path.last() {
if let Some(old) = self.imports.insert(last.to_owned(), path) {
return Err(CompilationUnitError::ImportConflict { existing: old });
}
}
Ok(())
}
pub fn new_assembly(&mut self) -> Assembly {
Assembly::new(self.label_count)
}
pub fn new_function<I>(
&mut self,
path: I,
args: usize,
assembly: Assembly,
) -> Result<(), CompilationUnitError>
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let offset = self.instructions.len();
let path = Item::of(path);
let hash = Hash::function(&path);
self.functions_rev.insert(offset, hash);
let info = UnitFnInfo {
offset,
signature: UnitFnSignature::new(path, args),
};
if let Some(old) = self.functions.insert(hash, info) {
return Err(CompilationUnitError::FunctionConflict {
existing: old.signature,
});
}
self.add_assembly(assembly)?;
Ok(())
}
fn add_assembly(&mut self, assembly: Assembly) -> Result<(), CompilationUnitError> {
self.label_count = assembly.label_count;
self.required_functions.extend(assembly.required_functions);
for (pos, (inst, span)) in assembly.instructions.into_iter().enumerate() {
let mut comment = None;
let label = assembly.labels_rev.get(&pos).copied();
match inst {
AssemblyInst::Jump { label } => {
comment = Some(format!("label:{}", label).into_boxed_str());
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::Jump { offset });
}
AssemblyInst::JumpIf { label } => {
comment = Some(format!("label:{}", label).into_boxed_str());
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::JumpIf { offset });
}
AssemblyInst::JumpIfNot { label } => {
comment = Some(format!("label:{}", label).into_boxed_str());
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::JumpIfNot { offset });
}
AssemblyInst::JumpIfBranch { branch, label } => {
comment = Some(format!("label:{}", label).into_boxed_str());
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions
.push(Inst::JumpIfBranch { branch, offset });
}
AssemblyInst::Raw { raw } => {
self.instructions.push(raw);
}
}
self.debug.push(DebugInfo {
span,
comment,
label,
});
}
return Ok(());
fn translate_offset(
base: usize,
label: Label,
labels: &HashMap<Label, usize>,
) -> Result<isize, CompilationUnitError> {
let base = base as isize;
let offset =
labels
.get(&label)
.copied()
.ok_or_else(|| CompilationUnitError::MissingLabel {
label: label.to_owned(),
})?;
Ok((offset as isize) - base)
}
}
pub fn link(&self, context: &Context, errors: &mut LinkerErrors) -> bool {
for (hash, spans) in &self.required_functions {
if self.functions.get(hash).is_none() && context.lookup(*hash).is_none() {
errors.errors.push(LinkerError::MissingFunction {
hash: *hash,
spans: spans.clone(),
});
}
}
errors.errors.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Label {
name: &'static str,
ident: usize,
}
impl fmt::Display for Label {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}_{}", self.name, self.ident)
}
}
#[derive(Debug, Clone)]
enum AssemblyInst {
Jump { label: Label },
JumpIf { label: Label },
JumpIfNot { label: Label },
JumpIfBranch { branch: usize, label: Label },
Raw { raw: Inst },
}
#[derive(Debug, Clone, Default)]
pub struct Assembly {
labels: HashMap<Label, usize>,
labels_rev: HashMap<usize, Label>,
instructions: Vec<(AssemblyInst, Span)>,
label_count: usize,
required_functions: HashMap<Hash, Vec<Span>>,
}
impl Assembly {
fn new(label_count: usize) -> Self {
Self {
labels: Default::default(),
labels_rev: Default::default(),
instructions: Default::default(),
label_count,
required_functions: Default::default(),
}
}
pub fn new_label(&mut self, name: &'static str) -> Label {
let label = Label {
name,
ident: self.label_count,
};
self.label_count += 1;
label
}
pub fn label(&mut self, label: Label) -> Result<Label, CompilationUnitError> {
let offset = self.instructions.len();
if let Some(_) = self.labels.insert(label, offset) {
return Err(CompilationUnitError::DuplicateLabel { label });
}
self.labels_rev.insert(offset, label);
Ok(label)
}
pub fn jump(&mut self, label: Label, span: Span) {
self.instructions.push((AssemblyInst::Jump { label }, span));
}
pub fn jump_if(&mut self, label: Label, span: Span) {
self.instructions
.push((AssemblyInst::JumpIf { label }, span));
}
pub fn jump_if_not(&mut self, label: Label, span: Span) {
self.instructions
.push((AssemblyInst::JumpIfNot { label }, span));
}
pub fn jump_if_branch(&mut self, branch: usize, label: Label, span: Span) {
self.instructions
.push((AssemblyInst::JumpIfBranch { branch, label }, span));
}
pub fn push(&mut self, raw: Inst, span: Span) {
match raw {
Inst::Call { hash, .. } => {
self.required_functions.entry(hash).or_default().push(span);
}
_ => (),
}
self.instructions.push((AssemblyInst::Raw { raw }, span));
}
}
#[derive(Debug)]
pub enum LinkerError {
MissingFunction {
hash: Hash,
spans: Vec<Span>,
},
}
#[derive(Debug)]
pub struct LinkerErrors {
errors: Vec<LinkerError>,
}
impl LinkerErrors {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn errors(self) -> impl Iterator<Item = LinkerError> {
self.errors.into_iter()
}
}
impl<'a> IntoIterator for &'a LinkerErrors {
type IntoIter = std::slice::Iter<'a, LinkerError>;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
self.errors.iter()
}
}