use crate::collections::HashMap;
use crate::{
Call, Component, Context, Hash, Inst, Item, Meta, Names, StaticString, ValueType, VmError,
VmErrorKind,
};
use std::fmt;
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum UnitError {
#[error("conflicting function signature already exists `{existing}`")]
FunctionConflict {
existing: UnitFnSignature,
},
#[error("conflicting type already exists `{existing}`")]
TypeConflict {
existing: Item,
},
#[error("unsupported meta type for item `{existing}`")]
UnsupportedMeta {
existing: Item,
},
#[error("trying to insert `{current}` but conflicting meta `{existing}` already exists")]
MetaConflict {
current: Meta,
existing: Meta,
},
#[error("missing static string for hash `{hash}` and slot `{slot}`")]
StaticStringMissing {
hash: Hash,
slot: usize,
},
#[error("missing static byte string for hash `{hash}` and slot `{slot}`")]
StaticBytesMissing {
hash: Hash,
slot: usize,
},
#[error(
"conflicting static string for hash `{hash}` between `{existing:?}` and `{current:?}`"
)]
StaticStringHashConflict {
hash: Hash,
current: String,
existing: String,
},
#[error(
"conflicting static string for hash `{hash}` between `{existing:?}` and `{current:?}`"
)]
StaticBytesHashConflict {
hash: Hash,
current: Vec<u8>,
existing: Vec<u8>,
},
#[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,
},
#[error("base offset overflow")]
BaseOverflow,
#[error("offset overflow")]
OffsetOverflow,
}
#[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 is_empty(self) -> bool {
self.start == self.end
}
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,
}
}
pub fn trim_end(self, amount: usize) -> Self {
Self {
start: self.start,
end: usize::max(self.end.saturating_sub(amount), self.start),
}
}
}
impl fmt::Display for Span {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}:{}", self.start, self.end)
}
}
#[derive(Debug, Clone, Copy)]
pub enum UnitFnKind {
Offset {
offset: usize,
call: Call,
},
Tuple {
hash: Hash,
},
TupleVariant {
enum_hash: Hash,
hash: Hash,
},
}
#[derive(Debug, Clone)]
pub struct UnitFnInfo {
pub kind: UnitFnKind,
pub signature: UnitFnSignature,
}
#[derive(Debug, Clone)]
pub struct UnitFnSignature {
pub path: Item,
pub args: usize,
}
impl UnitFnSignature {
pub fn new(path: Item, args: usize) -> Self {
Self { path, 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<String>,
pub label: Option<Label>,
}
#[derive(Debug)]
pub struct UnitTypeInfo {
pub hash: Hash,
pub value_type: ValueType,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ImportKey {
pub item: Item,
pub component: Component,
}
impl ImportKey {
pub fn new<C>(item: Item, component: C) -> Self
where
C: Into<Component>,
{
Self {
item,
component: component.into(),
}
}
pub fn component<C>(component: C) -> Self
where
C: Into<Component>,
{
Self {
item: Item::empty(),
component: component.into(),
}
}
}
#[derive(Debug)]
pub struct ImportEntry {
pub item: Item,
pub span: Option<Span>,
}
impl ImportEntry {
pub fn of<I>(iter: I) -> Self
where
I: IntoIterator,
I::Item: Into<Component>,
{
Self {
item: Item::of(iter),
span: None,
}
}
}
#[derive(Debug, Default)]
pub struct Unit {
instructions: Vec<Inst>,
imports: HashMap<ImportKey, ImportEntry>,
meta: HashMap<Item, Meta>,
functions: HashMap<Hash, Arc<UnitFnInfo>>,
types: HashMap<Hash, UnitTypeInfo>,
functions_rev: HashMap<usize, Hash>,
static_strings: Vec<Arc<StaticString>>,
static_string_rev: HashMap<Hash, usize>,
static_bytes: Vec<Vec<u8>>,
static_bytes_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>>,
names: Names,
}
impl Unit {
pub fn new() -> Self {
Self::default()
}
pub fn with_default_prelude() -> Self {
let mut this = Self::new();
this.imports.insert(
ImportKey::component("dbg"),
ImportEntry::of(&["std", "dbg"]),
);
this.imports.insert(
ImportKey::component("drop"),
ImportEntry::of(&["std", "drop"]),
);
this.imports.insert(
ImportKey::component("is_readable"),
ImportEntry::of(&["std", "is_readable"]),
);
this.imports.insert(
ImportKey::component("is_writable"),
ImportEntry::of(&["std", "is_writable"]),
);
this.imports.insert(
ImportKey::component("panic"),
ImportEntry::of(&["std", "panic"]),
);
this.imports.insert(
ImportKey::component("print"),
ImportEntry::of(&["std", "print"]),
);
this.imports.insert(
ImportKey::component("println"),
ImportEntry::of(&["std", "println"]),
);
this.imports.insert(
ImportKey::component("unit"),
ImportEntry::of(&["std", "unit"]),
);
this.imports.insert(
ImportKey::component("bool"),
ImportEntry::of(&["std", "bool"]),
);
this.imports.insert(
ImportKey::component("byte"),
ImportEntry::of(&["std", "byte"]),
);
this.imports.insert(
ImportKey::component("char"),
ImportEntry::of(&["std", "char"]),
);
this.imports.insert(
ImportKey::component("int"),
ImportEntry::of(&["std", "int"]),
);
this.imports.insert(
ImportKey::component("float"),
ImportEntry::of(&["std", "float"]),
);
this.imports.insert(
ImportKey::component("Object"),
ImportEntry::of(&["std", "object", "Object"]),
);
this.imports.insert(
ImportKey::component("Vec"),
ImportEntry::of(&["std", "vec", "Vec"]),
);
this.imports.insert(
ImportKey::component("String"),
ImportEntry::of(&["std", "string", "String"]),
);
this.imports.insert(
ImportKey::component("Result"),
ImportEntry::of(&["std", "result", "Result"]),
);
this.imports.insert(
ImportKey::component("Err"),
ImportEntry::of(&["std", "result", "Result", "Err"]),
);
this.imports.insert(
ImportKey::component("Ok"),
ImportEntry::of(&["std", "result", "Result", "Ok"]),
);
this.imports.insert(
ImportKey::component("Option"),
ImportEntry::of(&["std", "option", "Option"]),
);
this.imports.insert(
ImportKey::component("Some"),
ImportEntry::of(&["std", "option", "Option", "Some"]),
);
this.imports.insert(
ImportKey::component("None"),
ImportEntry::of(&["std", "option", "Option", "None"]),
);
this
}
pub fn contains_name(&self, item: &Item) -> bool {
self.names.contains(item)
}
pub fn contains_prefix(&self, item: &Item) -> bool {
self.names.contains_prefix(item)
}
pub fn iter_components<'a, I>(&'a self, iter: I) -> impl Iterator<Item = &'a Component>
where
I: IntoIterator,
I::Item: Into<Component>,
{
self.names.iter_components(iter)
}
pub fn lookup_meta(&self, name: &Item) -> Option<Meta> {
self.meta.get(name).cloned()
}
pub fn lookup_type(&self, hash: Hash) -> Option<&UnitTypeInfo> {
self.types.get(&hash)
}
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 = &Arc<StaticString>> + '_ {
self.static_strings.iter()
}
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, &Arc<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 ImportKey, &'a ImportEntry)> + '_ {
self.imports.iter()
}
pub fn lookup_string(&self, slot: usize) -> Result<&Arc<StaticString>, VmError> {
Ok(self
.static_strings
.get(slot)
.ok_or_else(|| VmError::from(VmErrorKind::MissingStaticString { slot }))?)
}
pub fn lookup_bytes(&self, slot: usize) -> Result<&[u8], VmError> {
Ok(self
.static_bytes
.get(slot)
.ok_or_else(|| VmError::from(VmErrorKind::MissingStaticString { slot }))?
.as_ref())
}
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, UnitError> {
let current = StaticString::new(current);
let hash = current.hash();
if let Some(existing_slot) = self.static_string_rev.get(&hash).copied() {
let existing = self.static_strings.get(existing_slot).ok_or_else(|| {
UnitError::StaticStringMissing {
hash,
slot: existing_slot,
}
})?;
if &***existing != &*current {
return Err(UnitError::StaticStringHashConflict {
hash,
current: (*current).clone(),
existing: (***existing).clone(),
});
}
return Ok(existing_slot);
}
let new_slot = self.static_strings.len();
self.static_strings.push(Arc::new(current));
self.static_string_rev.insert(hash, new_slot);
Ok(new_slot)
}
pub fn new_static_bytes(&mut self, current: &[u8]) -> Result<usize, UnitError> {
let hash = Hash::of(¤t);
if let Some(existing_slot) = self.static_bytes_rev.get(&hash).copied() {
let existing = self.static_bytes.get(existing_slot).ok_or_else(|| {
UnitError::StaticBytesMissing {
hash,
slot: existing_slot,
}
})?;
if &**existing != current {
return Err(UnitError::StaticBytesHashConflict {
hash,
current: current.to_owned(),
existing: existing.clone(),
});
}
return Ok(existing_slot);
}
let new_slot = self.static_bytes.len();
self.static_bytes.push(current.to_owned());
self.static_bytes_rev.insert(hash, new_slot);
Ok(new_slot)
}
pub fn new_static_object_keys(&mut self, current: &[String]) -> Result<usize, UnitError> {
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(|| {
UnitError::StaticObjectKeysMissing {
hash,
slot: existing_slot,
}
})?;
if *existing != current {
return Err(UnitError::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<&Arc<UnitFnInfo>> {
self.functions.get(&hash)
}
pub fn lookup_import(&self, key: &ImportKey) -> Option<&ImportEntry> {
self.imports.get(&key)
}
pub fn new_import<I>(&mut self, item: Item, path: I, span: Span) -> Result<(), UnitError>
where
I: Copy + IntoIterator,
I::Item: Into<Component>,
{
let path = Item::of(path);
if let Some(last) = path.last() {
let entry = ImportEntry {
item: path.clone(),
span: Some(span),
};
self.imports
.insert(ImportKey::new(item, last.clone()), entry);
}
Ok(())
}
pub fn insert_meta(&mut self, meta: Meta) -> Result<(), UnitError> {
let item = match &meta {
Meta::MetaTuple { tuple, .. } => {
let info = Arc::new(UnitFnInfo {
kind: UnitFnKind::Tuple { hash: tuple.hash },
signature: UnitFnSignature {
path: tuple.item.clone(),
args: tuple.args,
},
});
if let Some(old) = self.functions.insert(tuple.hash, info) {
return Err(UnitError::FunctionConflict {
existing: old.signature.clone(),
});
}
let info = UnitTypeInfo {
hash: tuple.hash,
value_type: ValueType::Type(tuple.hash),
};
if self.types.insert(tuple.hash, info).is_some() {
return Err(UnitError::TypeConflict {
existing: tuple.item.clone(),
});
}
tuple.item.clone()
}
Meta::MetaVariantTuple {
enum_item, tuple, ..
} => {
let enum_hash = Hash::type_hash(enum_item);
let info = Arc::new(UnitFnInfo {
kind: UnitFnKind::TupleVariant {
enum_hash,
hash: tuple.hash,
},
signature: UnitFnSignature {
path: tuple.item.clone(),
args: tuple.args,
},
});
if let Some(old) = self.functions.insert(tuple.hash, info) {
return Err(UnitError::FunctionConflict {
existing: old.signature.clone(),
});
}
let info = UnitTypeInfo {
hash: tuple.hash,
value_type: ValueType::Type(enum_hash),
};
if self.types.insert(tuple.hash, info).is_some() {
return Err(UnitError::TypeConflict {
existing: tuple.item.clone(),
});
}
tuple.item.clone()
}
Meta::MetaStruct { object, .. } => {
let hash = Hash::type_hash(&object.item);
let info = UnitTypeInfo {
hash,
value_type: ValueType::Type(hash),
};
if self.types.insert(hash, info).is_some() {
return Err(UnitError::TypeConflict {
existing: object.item.clone(),
});
}
object.item.clone()
}
Meta::MetaVariantStruct {
enum_item, object, ..
} => {
let hash = Hash::type_hash(&object.item);
let enum_hash = Hash::type_hash(enum_item);
let info = UnitTypeInfo {
hash,
value_type: ValueType::Type(enum_hash),
};
if self.types.insert(hash, info).is_some() {
return Err(UnitError::TypeConflict {
existing: object.item.clone(),
});
}
object.item.clone()
}
Meta::MetaEnum { item, .. } => {
let hash = Hash::type_hash(item);
let info = UnitTypeInfo {
hash,
value_type: ValueType::Type(hash),
};
if self.types.insert(hash, info).is_some() {
return Err(UnitError::TypeConflict {
existing: item.clone(),
});
}
item.clone()
}
Meta::MetaFunction { item, .. } => item.clone(),
Meta::MetaClosure { item, .. } => item.clone(),
};
self.names.insert(&item);
if let Some(existing) = self.meta.insert(item.clone(), meta.clone()) {
return Err(UnitError::MetaConflict {
current: meta,
existing,
});
}
Ok(())
}
pub fn new_assembly(&self) -> Assembly {
Assembly::new(self.label_count)
}
pub fn new_function(
&mut self,
path: Item,
args: usize,
assembly: Assembly,
call: Call,
) -> Result<(), UnitError> {
let offset = self.instructions.len();
let hash = Hash::type_hash(&path);
self.functions_rev.insert(offset, hash);
let info = Arc::new(UnitFnInfo {
kind: UnitFnKind::Offset { offset, call },
signature: UnitFnSignature::new(path, args),
});
if let Some(old) = self.functions.insert(hash, info) {
return Err(UnitError::FunctionConflict {
existing: old.signature.clone(),
});
}
self.add_assembly(assembly)?;
Ok(())
}
pub fn new_instance_function(
&mut self,
path: Item,
value_type: ValueType,
name: &str,
args: usize,
assembly: Assembly,
call: Call,
) -> Result<(), UnitError> {
log::trace!("instance fn: {}", path);
let offset = self.instructions.len();
let instance_fn = Hash::of(name);
let instance_fn = Hash::instance_function(value_type, instance_fn);
let hash = Hash::type_hash(&path);
let info = Arc::new(UnitFnInfo {
kind: UnitFnKind::Offset { offset, call },
signature: UnitFnSignature::new(path, args),
});
if let Some(old) = self.functions.insert(instance_fn, info.clone()) {
return Err(UnitError::FunctionConflict {
existing: old.signature.clone(),
});
}
if let Some(old) = self.functions.insert(hash, info) {
return Err(UnitError::FunctionConflict {
existing: old.signature.clone(),
});
}
self.functions_rev.insert(offset, hash);
self.add_assembly(assembly)?;
Ok(())
}
fn add_assembly(&mut self, assembly: Assembly) -> Result<(), UnitError> {
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));
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::Jump { offset });
}
AssemblyInst::JumpIf { label } => {
comment = Some(format!("label:{}", label));
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::JumpIf { offset });
}
AssemblyInst::JumpIfNot { label } => {
comment = Some(format!("label:{}", label));
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::JumpIfNot { offset });
}
AssemblyInst::JumpIfBranch { branch, label } => {
comment = Some(format!("label:{}", label));
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions
.push(Inst::JumpIfBranch { branch, offset });
}
AssemblyInst::PopAndJumpIf { count, label } => {
comment = Some(format!("label:{}", label));
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions.push(Inst::PopAndJumpIf { count, offset });
}
AssemblyInst::PopAndJumpIfNot { count, label } => {
comment = Some(format!("label:{}", label));
let offset = translate_offset(pos, label, &assembly.labels)?;
self.instructions
.push(Inst::PopAndJumpIfNot { count, offset });
}
AssemblyInst::Raw { raw } => {
self.instructions.push(raw);
}
}
if let Some(comments) = assembly.comments.get(&pos) {
let actual = comment
.take()
.into_iter()
.chain(comments.iter().cloned())
.collect::<Vec<_>>()
.join("; ");
comment = Some(actual)
}
self.debug.push(DebugInfo {
span,
comment,
label,
});
}
return Ok(());
fn translate_offset(
base: usize,
label: Label,
labels: &HashMap<Label, usize>,
) -> Result<isize, UnitError> {
use std::convert::TryFrom as _;
let offset = labels
.get(&label)
.copied()
.ok_or_else(|| UnitError::MissingLabel { label })?;
let base = isize::try_from(base).map_err(|_| UnitError::BaseOverflow)?;
let offset = isize::try_from(offset).map_err(|_| UnitError::OffsetOverflow)?;
let (base, _) = base.overflowing_add(1);
let (offset, _) = offset.overflowing_sub(base);
Ok(offset)
}
}
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: i64, label: Label },
PopAndJumpIf { count: usize, label: Label },
PopAndJumpIfNot { count: 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)>,
comments: HashMap<usize, Vec<String>>,
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(),
comments: 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, UnitError> {
let offset = self.instructions.len();
if self.labels.insert(label, offset).is_some() {
return Err(UnitError::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: i64, label: Label, span: Span) {
self.instructions
.push((AssemblyInst::JumpIfBranch { branch, label }, span));
}
pub fn pop_and_jump_if(&mut self, count: usize, label: Label, span: Span) {
self.instructions
.push((AssemblyInst::PopAndJumpIf { count, label }, span));
}
pub fn pop_and_jump_if_not(&mut self, count: usize, label: Label, span: Span) {
self.instructions
.push((AssemblyInst::PopAndJumpIfNot { count, label }, span));
}
pub fn push(&mut self, raw: Inst, span: Span) {
if let Inst::Call { hash, .. } = raw {
self.required_functions.entry(hash).or_default().push(span);
}
self.instructions.push((AssemblyInst::Raw { raw }, span));
}
pub fn push_with_comment<C>(&mut self, raw: Inst, span: Span, comment: C)
where
C: AsRef<str>,
{
let pos = self.instructions.len();
self.comments
.entry(pos)
.or_default()
.push(comment.as_ref().to_owned());
self.push(raw, span);
}
}
#[derive(Debug)]
pub enum LinkerError {
MissingFunction {
hash: Hash,
spans: Vec<Span>,
},
}
#[derive(Debug, Default)]
pub struct LinkerErrors {
errors: Vec<LinkerError>,
}
impl LinkerErrors {
pub fn new() -> Self {
Self::default()
}
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()
}
}