use alloc::{borrow::Cow, collections::VecDeque, format};
use core::fmt;
use crate::{
FunctionIdent, SmallVec, SymbolName,
diagnostics::{Diagnostic, miette},
interner, smallvec,
};
#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum InvalidSymbolPathError {
#[error("invalid symbol path: cannot be empty")]
Empty,
#[error("invalid symbol path: invalid format")]
#[diagnostic(help(
"The grammar for symbols is `<namespace>:<package>[/<export>]*[@<version>]"
))]
InvalidFormat,
#[error("invalid symbol path: missing package")]
#[diagnostic(help(
"A fully-qualified symbol must namespace packages, i.e. `<namespace>:<package>`, but \
you've only provided one of these"
))]
MissingPackage,
#[error("invalid symbol path: only fully-qualified symbols can be versioned")]
UnexpectedVersion,
#[error("invalid symbol path: unexpected character '{token}' at byte {pos}")]
UnexpectedToken { token: char, pos: usize },
#[error("invalid symbol path: no leaf component was provided")]
MissingLeaf,
#[error("invalid symbol path: unexpected components found after leaf")]
UnexpectedTrailingComponents,
#[error("invalid symbol path: only one root component is allowed, and it must come first")]
UnexpectedRootPlacement,
}
#[derive(Clone)]
pub struct SymbolPath {
pub path: SmallVec<[SymbolNameComponent; 3]>,
}
impl FromIterator<SymbolNameComponent> for SymbolPath {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = SymbolNameComponent>,
{
Self {
path: SmallVec::from_iter(iter),
}
}
}
impl SymbolPath {
pub fn new<I>(components: I) -> Result<Self, InvalidSymbolPathError>
where
I: IntoIterator<Item = SymbolNameComponent>,
{
let mut path = SmallVec::default();
let mut components = components.into_iter();
match components.next() {
None => return Err(InvalidSymbolPathError::Empty),
Some(component @ (SymbolNameComponent::Root | SymbolNameComponent::Component(_))) => {
path.push(component);
}
Some(component @ SymbolNameComponent::Leaf(_)) => {
if components.next().is_some() {
return Err(InvalidSymbolPathError::UnexpectedTrailingComponents);
}
path.push(component);
return Ok(Self { path });
}
};
while let Some(component) = components.next() {
match component {
SymbolNameComponent::Root => {
return Err(InvalidSymbolPathError::UnexpectedRootPlacement);
}
component @ SymbolNameComponent::Component(_) => {
path.push(component);
}
component @ SymbolNameComponent::Leaf(_) => {
path.push(component);
if components.next().is_some() {
return Err(InvalidSymbolPathError::UnexpectedTrailingComponents);
}
}
}
}
Ok(Self { path })
}
pub fn from_masm_function_id(id: FunctionIdent) -> Self {
let mut path = Self::from_masm_module_id(id.module.as_str());
path.path.push(SymbolNameComponent::Leaf(id.function.as_symbol()));
path
}
pub fn from_masm_module_id(id: &str) -> Self {
let parts = id.split("::");
Self::from_iter(
core::iter::once(SymbolNameComponent::Root)
.chain(parts.map(SymbolName::intern).map(SymbolNameComponent::Component)),
)
}
pub fn name(&self) -> SymbolName {
match self.path.last().expect("expected non-empty symbol path") {
SymbolNameComponent::Leaf(name) => *name,
component => panic!("invalid symbol path: expected leaf node, got: {component:?}"),
}
}
pub fn set_name(&mut self, name: SymbolName) {
match self.path.last_mut() {
Some(SymbolNameComponent::Leaf(prev_name)) => {
*prev_name = name;
}
_ => {
self.path.push(SymbolNameComponent::Leaf(name));
}
}
}
pub fn namespace(&self) -> Option<SymbolName> {
if self.is_absolute() {
match self.path[1] {
SymbolNameComponent::Component(ns) => Some(ns),
SymbolNameComponent::Leaf(_) => None,
SymbolNameComponent::Root => unreachable!(
"malformed symbol path: root components may only occur at the start of a path"
),
}
} else {
None
}
}
pub fn to_library_path(&self) -> midenc_session::LibraryPath {
use midenc_session::LibraryPath;
let components = self.path.iter();
let mut path = LibraryPath::default();
for component in components {
if component.is_root() {
path.push_component("::");
continue;
} else {
path.push_component(component.as_symbol_name().as_str());
}
}
path
}
pub fn is_absolute(&self) -> bool {
matches!(&self.path[0], SymbolNameComponent::Root)
}
pub fn has_parent(&self) -> bool {
if self.is_absolute() {
self.path.len() > 2
} else {
self.path.len() > 1
}
}
pub fn is_prefix_of(&self, other: &Self) -> bool {
other.is_prefixed_by(&self.path)
}
pub fn is_prefixed_by(&self, prefix: &[SymbolNameComponent]) -> bool {
let mut a = prefix.iter();
let mut b = self.path.iter();
let mut index = 0;
loop {
match (a.next(), b.next()) {
(Some(part_a), Some(part_b)) if part_a == part_b => {
index += 1;
}
(None, Some(_)) => break index > 0,
_ => break false,
}
}
}
pub fn components(&self) -> impl ExactSizeIterator<Item = SymbolNameComponent> + '_ {
self.path.iter().copied()
}
pub fn parent(&self) -> Option<SymbolPath> {
match self.path.split_last()? {
(SymbolNameComponent::Root, []) => None,
(_, rest) => Some(SymbolPath {
path: SmallVec::from_slice(rest),
}),
}
}
pub fn without_leaf(&self) -> Cow<'_, SymbolPath> {
match self.path.split_last() {
Some((SymbolNameComponent::Leaf(_), rest)) => Cow::Owned(SymbolPath {
path: SmallVec::from_slice(rest),
}),
_ => Cow::Borrowed(self),
}
}
}
impl fmt::Display for SymbolPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use core::fmt::Write;
let mut components = self.path.iter();
if self.is_absolute() {
let _ = components.next();
}
match components.next() {
Some(component) => f.write_str(component.as_symbol_name().as_str())?,
None => return Ok(()),
}
for component in components {
f.write_char('/')?;
f.write_str(component.as_symbol_name().as_str())?;
}
Ok(())
}
}
impl fmt::Debug for SymbolPath {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SymbolPath")
.field_with("path", |f| f.debug_list().entries(self.path.iter()).finish())
.finish()
}
}
impl crate::formatter::PrettyPrint for SymbolPath {
fn render(&self) -> crate::formatter::Document {
use crate::formatter::*;
display(self)
}
}
impl Eq for SymbolPath {}
impl PartialEq for SymbolPath {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl PartialOrd for SymbolPath {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SymbolPath {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.path.cmp(&other.path)
}
}
impl core::hash::Hash for SymbolPath {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.path.hash(state);
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum SymbolNameComponent {
Root,
Component(SymbolName),
Leaf(SymbolName),
}
impl SymbolNameComponent {
pub fn as_symbol_name(&self) -> SymbolName {
match self {
Self::Root => interner::symbols::Empty,
Self::Component(name) | Self::Leaf(name) => *name,
}
}
#[inline]
pub fn is_root(&self) -> bool {
matches!(self, Self::Root)
}
#[inline]
pub fn is_leaf(&self) -> bool {
matches!(self, Self::Leaf(_))
}
}
impl fmt::Debug for SymbolNameComponent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Root => f.write_str("Root"),
Self::Component(name) => {
f.debug_tuple("Component").field_with(|f| f.write_str(name.as_str())).finish()
}
Self::Leaf(name) => {
f.debug_tuple("Leaf").field_with(|f| f.write_str(name.as_str())).finish()
}
}
}
}
impl Ord for SymbolNameComponent {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
use core::cmp::Ordering;
if self == other {
return Ordering::Equal;
}
match (self, other) {
(Self::Root, _) => Ordering::Less,
(_, Self::Root) => Ordering::Greater,
(Self::Component(x), Self::Component(y)) => x.cmp(y),
(Self::Component(_), _) => Ordering::Less,
(_, Self::Component(_)) => Ordering::Greater,
(Self::Leaf(x), Self::Leaf(y)) => x.cmp(y),
}
}
}
impl PartialOrd for SymbolNameComponent {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub struct SymbolNameComponents {
parts: VecDeque<&'static str>,
name: SymbolName,
absolute: bool,
done: bool,
}
impl SymbolNameComponents {
pub fn from_component_model_symbol(symbol: SymbolName) -> Result<Self, crate::Report> {
use core::{iter::Peekable, str::CharIndices};
let mut parts = VecDeque::default();
if symbol == interner::symbols::Empty {
let done = symbol == interner::symbols::Empty;
return Ok(Self {
parts,
name: symbol,
done,
absolute: false,
});
}
#[inline(always)]
fn is_valid_id_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '-'
}
fn lex_id<'a>(
s: &'a str,
start: usize,
lexer: &mut Peekable<CharIndices<'a>>,
) -> Option<(usize, &'a str)> {
let mut end = start;
while let Some((i, c)) = lexer.next_if(|(_, c)| is_valid_id_char(*c)) {
end = i + c.len_utf8();
}
if end == start {
return None;
}
Some((end, unsafe { core::str::from_utf8_unchecked(&s.as_bytes()[start..end]) }))
}
let input = symbol.as_str();
let mut chars = input.char_indices().peekable();
let mut pos = 0;
let mut absolute = false;
let package_end = loop {
let (new_pos, _) = lex_id(input, pos, &mut chars).ok_or_else(|| {
crate::Report::msg(format!(
"invalid component model symbol: '{symbol}' contains invalid characters"
))
})?;
pos = new_pos;
if let Some((new_pos, c)) = chars.next_if(|(_, c)| *c == ':') {
pos = new_pos + c.len_utf8();
absolute = true;
} else {
break pos;
}
};
if chars.peek().is_none() {
let symbol =
unsafe { core::str::from_utf8_unchecked(&input.as_bytes()[pos..package_end]) };
return Ok(Self {
parts,
name: SymbolName::intern(symbol),
done: false,
absolute,
});
}
let package_name =
unsafe { core::str::from_utf8_unchecked(&input.as_bytes()[pos..package_end]) };
parts.push_back(package_name);
match chars.next_if(|(_, c)| *c == '/') {
None => {
if chars.next_if(|(_, c)| *c == '@').is_some() {
if !absolute {
return Err(crate::Report::msg(
"invalid component model symbol: unqualified symbols cannot be \
versioned",
));
}
parts.clear();
return Ok(Self {
parts,
name: SymbolName::intern(package_name),
done: false,
absolute,
});
} else {
return Err(crate::Report::msg(format!(
"invalid component model symbol: unexpected character in '{symbol}' \
starting at byte {pos}"
)));
}
}
Some((new_pos, c)) => {
pos = new_pos + c.len_utf8();
}
}
loop {
let (new_pos, id) = lex_id(input, pos, &mut chars).ok_or_else(|| {
crate::Report::msg(format!(
"invalid component model symbol: '{symbol}' contains invalid characters"
))
})?;
pos = new_pos;
if let Some((new_pos, c)) = chars.next_if(|(_, c)| *c == '/') {
pos = new_pos + c.len_utf8();
parts.push_back(id);
} else {
break;
}
}
if chars.next_if(|(_, c)| *c == '@').is_some() {
let name = SymbolName::intern(parts.pop_back().unwrap());
return Ok(Self {
parts,
name,
done: false,
absolute,
});
}
if chars.peek().is_none() {
let name = SymbolName::intern(parts.pop_back().unwrap());
Ok(Self {
parts,
name,
done: false,
absolute,
})
} else {
Err(crate::Report::msg(format!(
"invalid component model symbol: '{symbol}' contains invalid character starting \
at byte {pos}"
)))
}
}
pub fn into_symbol_name(self) -> Option<SymbolName> {
let attr = self.into_symbol_path()?;
Some(SymbolName::intern(attr))
}
pub fn into_symbol_path(self) -> Option<SymbolPath> {
if self.name == interner::symbols::Empty {
return None;
}
if self.parts.is_empty() {
return Some(SymbolPath {
path: smallvec![SymbolNameComponent::Leaf(self.name)],
});
}
let mut path = SmallVec::<[_; 3]>::with_capacity(self.parts.len() + 1);
let mut parts = self.parts.into_iter();
if let Some(part) = parts.next() {
if part == "::" {
path.push(SymbolNameComponent::Root);
} else {
path.push(SymbolNameComponent::Component(SymbolName::intern(part)));
}
}
path.extend(parts.map(SymbolName::intern).map(SymbolNameComponent::Component));
path.push(SymbolNameComponent::Leaf(self.name));
Some(SymbolPath { path })
}
}
impl core::iter::FusedIterator for SymbolNameComponents {}
impl Iterator for SymbolNameComponents {
type Item = SymbolNameComponent;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if self.absolute {
self.absolute = false;
return Some(SymbolNameComponent::Root);
}
if let Some(part) = self.parts.pop_front() {
return Some(SymbolNameComponent::Component(part.into()));
}
self.done = true;
Some(SymbolNameComponent::Leaf(self.name))
}
}
impl ExactSizeIterator for SymbolNameComponents {
fn len(&self) -> usize {
if self.done || self.name == interner::symbols::Empty {
0
} else {
self.parts.len() + 1 + usize::from(self.absolute)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn symbol_name_components_len_matches_iteration_count() {
let iter = SymbolNameComponents {
parts: alloc::collections::VecDeque::from(["foo", "bar"]),
name: SymbolName::intern("baz"),
absolute: true,
done: false,
};
assert_eq!(iter.len(), 4);
let iter = SymbolNameComponents {
parts: alloc::collections::VecDeque::from(["foo"]),
name: SymbolName::intern("bar"),
absolute: false,
done: false,
};
assert_eq!(iter.len(), 2);
let iter = SymbolNameComponents {
parts: alloc::collections::VecDeque::new(),
name: SymbolName::intern("x"),
absolute: false,
done: true,
};
assert_eq!(iter.len(), 0);
let mut iter = SymbolNameComponents {
parts: alloc::collections::VecDeque::from(["a"]),
name: SymbolName::intern("b"),
absolute: true,
done: false,
};
assert_eq!(iter.len(), 3); iter.next();
assert_eq!(iter.len(), 2); iter.next();
assert_eq!(iter.len(), 1); iter.next();
assert_eq!(iter.len(), 0); assert!(iter.next().is_none());
}
}