use alloc::{
collections::BTreeSet,
string::{String, ToString},
sync::Arc,
};
use miden_assembly_syntax_cst::{
Parse as CstParse, SyntaxKind, SyntaxToken,
ast::{AstNode, Path as CstPath, Visibility as CstVisibility},
rowan,
};
use miden_debug_types::{SourceFile, SourceSpan, Span};
use crate::{Path, ast, parser::ParsingError};
pub(super) struct LoweringContext<'a> {
parse: CstParse,
interned: &'a mut BTreeSet<Arc<str>>,
}
impl<'a> LoweringContext<'a> {
pub(super) fn new(parse: CstParse, interned: &'a mut BTreeSet<Arc<str>>) -> Self {
Self { parse, interned }
}
pub(super) fn parse(&self) -> &CstParse {
&self.parse
}
pub(super) fn source_file(&self) -> &SourceFile {
self.parse.source()
}
pub(super) fn source_text(&self, span: SourceSpan) -> &str {
self.source_file()
.source_slice(span.into_slice_index())
.expect("cst spans should always refer to valid source slices")
}
pub(super) fn lower_visibility(&self, visibility: Option<CstVisibility>) -> ast::Visibility {
if visibility.is_some() {
ast::Visibility::Public
} else {
ast::Visibility::Private
}
}
pub(super) fn lower_ident_token(
&mut self,
token: &SyntaxToken,
) -> Result<ast::Ident, ParsingError> {
let span = self.parse.span_for_token(token);
let raw = match token.kind() {
SyntaxKind::QuotedIdent if token.text().len() >= 2 => {
&token.text()[1..token.text().len() - 1]
},
_ => token.text(),
};
self.lower_ident_text(span, raw)
}
pub(super) fn lower_escaped_ident_token(
&mut self,
token: &SyntaxToken,
) -> Result<ast::Ident, ParsingError> {
let span = self.parse.span_for_token(token);
let text = token.text();
let raw = text.strip_prefix('"').and_then(|text| text.strip_suffix('"')).unwrap_or(text);
let unescaped = unescape(raw).ok_or_else(|| ParsingError::InvalidSyntax {
span,
message: "invalid escape sequence in identifier".to_string(),
})?;
self.lower_ident_text(span, &unescaped)
}
pub(super) fn lower_ident_or_string_token(
&mut self,
token: &SyntaxToken,
) -> Result<ast::Ident, ParsingError> {
let span = self.parse.span_for_token(token);
let raw = match token.kind() {
SyntaxKind::QuotedIdent | SyntaxKind::QuotedString if token.text().len() >= 2 => {
&token.text()[1..token.text().len() - 1]
},
_ => token.text(),
};
self.lower_ident_text(span, raw)
}
pub(super) fn lower_procedure_name_token(
&mut self,
token: &SyntaxToken,
) -> Result<ast::ProcedureName, ParsingError> {
let span = self.parse.span_for_token(token);
ast::ProcedureName::new_with_span(span, token.text())
.map_err(|error| ParsingError::InvalidIdentifier { error, span })
}
pub(super) fn lower_constant_ident_token(
&mut self,
token: &SyntaxToken,
) -> Result<ast::Ident, ParsingError> {
let span = self.parse.span_for_token(token);
if token.kind() != SyntaxKind::Ident {
return Err(ParsingError::InvalidIdentifier {
error: ast::IdentError::Casing(ast::CaseKindError::Screaming),
span,
});
}
let ident = self.lower_ident_token(token)?;
if ident.is_constant_ident() {
Ok(ident)
} else {
Err(ParsingError::InvalidIdentifier {
error: ast::IdentError::Casing(ast::CaseKindError::Screaming),
span,
})
}
}
pub(super) fn lower_ident_text(
&mut self,
span: SourceSpan,
text: &str,
) -> Result<ast::Ident, ParsingError> {
let interned = self.intern(text);
ast::Ident::validate(interned.as_ref())
.map_err(|error| ParsingError::InvalidIdentifier { error, span })?;
Ok(ast::Ident::from_raw_parts(Span::new(span, interned)))
}
pub(super) fn lower_string_text(&mut self, span: SourceSpan, text: &str) -> ast::Ident {
let interned = self.intern(text);
ast::Ident::from_raw_parts(Span::new(span, interned))
}
pub(super) fn lower_path(&mut self, path: &CstPath) -> Result<Span<Arc<Path>>, ParsingError> {
let span = self.parse.span_for_node(path.syntax());
let mut raw = String::new();
for token in path.syntax().children_with_tokens().filter_map(rowan::NodeOrToken::into_token)
{
if !token.kind().is_trivia() {
raw.push_str(token.text());
}
}
self.lower_raw_path(span, &raw)
}
pub(super) fn lower_raw_path(
&mut self,
span: SourceSpan,
raw: &str,
) -> Result<Span<Arc<Path>>, ParsingError> {
let path = ast::PathBuf::new(&raw).map_err(|error| ParsingError::InvalidLibraryPath {
span,
message: error.to_string(),
})?;
Ok(Span::new(span, Arc::<Path>::from(path)))
}
fn intern(&mut self, text: &str) -> Arc<str> {
self.interned.get(text).cloned().unwrap_or_else(|| {
let interned = Arc::<str>::from(text.to_string().into_boxed_str());
self.interned.insert(interned.clone());
interned
})
}
}
fn unescape(text: &str) -> Option<String> {
let mut unescaped = String::with_capacity(text.len());
let mut chars = text.chars();
while let Some(ch) = chars.next() {
if ch != '\\' {
unescaped.push(ch);
continue;
}
let ch = match chars.next()? {
escaped @ ('\\' | '"' | '\'') => escaped,
'u' => {
let (hex, rest) = chars.as_str().strip_prefix('{')?.split_once('}')?;
chars = rest.chars();
char::from_u32(u32::from_str_radix(hex, 16).ok()?)?
},
_ => return None,
};
unescaped.push(ch);
}
Some(unescaped)
}