refactor(natu): rename xatu
This commit was merged in pull request #97.
This commit is contained in:
239
src/natu/parser.rs
Normal file
239
src/natu/parser.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
use rnix::ast::{Attr, HasEntry};
|
||||
use rowan::ast::AstNode;
|
||||
|
||||
use crate::{FileId, ast};
|
||||
|
||||
pub fn parse_file(file_id: FileId, source: &str) -> ast::ParsedFile {
|
||||
let parse = rnix::Root::parse(source);
|
||||
let source_len = source.len();
|
||||
|
||||
let syntax = parse.syntax();
|
||||
let root = rnix::ast::Root::cast(syntax.clone()).expect("parser root is a root node");
|
||||
let root = convert_expr(root.expr().expect("parser root has an expression"));
|
||||
let (nodes, tokens) = convert_syntax(syntax);
|
||||
|
||||
ast::ParsedFile {
|
||||
file_id,
|
||||
root,
|
||||
nodes,
|
||||
tokens,
|
||||
errors: parse
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|error| convert_parse_error(error, source_len))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_syntax(syntax: rnix::SyntaxNode) -> (Vec<ast::Node>, Vec<ast::Token>) {
|
||||
let mut nodes = Vec::new();
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
for element in syntax.descendants_with_tokens() {
|
||||
match element {
|
||||
rnix::NodeOrToken::Node(node) => nodes.extend(convert_node(node)),
|
||||
rnix::NodeOrToken::Token(token) => tokens.extend(convert_token(token)),
|
||||
}
|
||||
}
|
||||
|
||||
(nodes, tokens)
|
||||
}
|
||||
|
||||
fn convert_expr(expr: rnix::ast::Expr) -> ast::Expr {
|
||||
match expr {
|
||||
rnix::ast::Expr::Ident(ident) => ast::Expr::Ident(ident.into()),
|
||||
rnix::ast::Expr::LetIn(let_in) => ast::Expr::Let {
|
||||
bindings: let_in
|
||||
.attrpath_values()
|
||||
.filter_map(convert_binding_ast)
|
||||
.collect(),
|
||||
body: Box::new(convert_expr(
|
||||
let_in.body().expect("let expressions have bodies"),
|
||||
)),
|
||||
},
|
||||
rnix::ast::Expr::AttrSet(attr_set) => ast::Expr::AttrSet(
|
||||
attr_set
|
||||
.attrpath_values()
|
||||
.filter_map(convert_binding_ast)
|
||||
.collect(),
|
||||
),
|
||||
rnix::ast::Expr::Lambda(lambda) => ast::Expr::Lambda {
|
||||
parameters: lambda_parameters(&lambda),
|
||||
body: Box::new(convert_expr(lambda.body().expect("lambdas have bodies"))),
|
||||
},
|
||||
expr => ast::Expr::Other(
|
||||
expr.syntax()
|
||||
.children()
|
||||
.filter_map(rnix::ast::Expr::cast)
|
||||
.map(convert_expr)
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_binding_ast(binding: rnix::ast::AttrpathValue) -> Option<ast::Binding> {
|
||||
let mut attrs = binding.attrpath()?.attrs();
|
||||
let Attr::Ident(key) = attrs.next()? else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if attrs.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ast::Binding {
|
||||
range: binding.syntax().text_range().into(),
|
||||
key: key.into(),
|
||||
value: convert_expr(binding.value()?),
|
||||
})
|
||||
}
|
||||
|
||||
fn lambda_parameters(lambda: &rnix::ast::Lambda) -> Vec<ast::Ident> {
|
||||
match lambda.param() {
|
||||
Some(rnix::ast::Param::IdentParam(parameter)) => {
|
||||
parameter.ident().map(Into::into).into_iter().collect()
|
||||
}
|
||||
Some(rnix::ast::Param::Pattern(pattern)) => pattern
|
||||
.pat_entries()
|
||||
.filter_map(|entry| entry.ident())
|
||||
.chain(pattern.pat_bind().and_then(|binding| binding.ident()))
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_node(node: rnix::SyntaxNode) -> Option<ast::Node> {
|
||||
match node.kind() {
|
||||
rnix::SyntaxKind::NODE_ATTRPATH_VALUE => convert_binding(node),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_binding(node: rnix::SyntaxNode) -> Option<ast::Node> {
|
||||
let binding = rnix::ast::AttrpathValue::cast(node)?;
|
||||
let range = binding.syntax().text_range().into();
|
||||
let data = binding.try_into().ok()?;
|
||||
|
||||
Some(ast::Node {
|
||||
range,
|
||||
data: ast::NodeData::Binding(data),
|
||||
})
|
||||
}
|
||||
|
||||
impl TryFrom<rnix::ast::AttrpathValue> for ast::Binding {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(binding: rnix::ast::AttrpathValue) -> Result<Self, Self::Error> {
|
||||
let mut attrs = binding.attrpath().ok_or(())?.attrs();
|
||||
let key = match attrs.next().ok_or(())? {
|
||||
Attr::Ident(ident) => ident.into(),
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
if attrs.next().is_some() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let value = binding.value().ok_or(())?.try_into()?;
|
||||
|
||||
Ok(Self {
|
||||
range: binding.syntax().text_range().into(),
|
||||
key,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<rnix::ast::Expr> for ast::Expr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(expr: rnix::ast::Expr) -> Result<Self, Self::Error> {
|
||||
match expr {
|
||||
rnix::ast::Expr::Ident(ident) => Ok(Self::Ident(ident.into())),
|
||||
rnix::ast::Expr::Select(select) => Ok(Self::Select(select.try_into()?)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rnix::ast::Ident> for ast::Ident {
|
||||
fn from(ident: rnix::ast::Ident) -> Self {
|
||||
Self {
|
||||
name: ident.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<rnix::ast::Select> for ast::Select {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(select: rnix::ast::Select) -> Result<Self, Self::Error> {
|
||||
if select.or_token().is_some() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut attrs = select.attrpath().ok_or(())?.attrs();
|
||||
let attr = match attrs.next().ok_or(())? {
|
||||
Attr::Ident(ident) => ident.into(),
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
if attrs.next().is_some() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
receiver: select.expr().ok_or(())?.syntax().to_string(),
|
||||
attr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_token(token: rnix::SyntaxToken) -> Option<ast::Token> {
|
||||
match token.kind() {
|
||||
rnix::SyntaxKind::TOKEN_URI => Some(ast::Token {
|
||||
range: token.text_range().into(),
|
||||
data: ast::TokenData::UnquotedUri(token.text().to_string()),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_parse_error(error: &rnix::ParseError, source_len: usize) -> ast::ParseError {
|
||||
ast::ParseError {
|
||||
range: parse_error_range(error, source_len),
|
||||
message: error.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_error_range(error: &rnix::ParseError, source_len: usize) -> ast::SourceRange {
|
||||
match error {
|
||||
rnix::ParseError::Unexpected(range)
|
||||
| rnix::ParseError::UnexpectedExtra(range)
|
||||
| rnix::ParseError::UnexpectedWanted(_, range, _)
|
||||
| rnix::ParseError::UnexpectedDoubleBind(range)
|
||||
| rnix::ParseError::DuplicatedArgs(range, _) => (*range).into(),
|
||||
|
||||
rnix::ParseError::UnexpectedEOF
|
||||
| rnix::ParseError::UnexpectedEOFWanted(_)
|
||||
| rnix::ParseError::RecursionLimitExceeded => ast::SourceRange {
|
||||
start: source_len,
|
||||
end: source_len,
|
||||
},
|
||||
|
||||
_ => ast::SourceRange {
|
||||
start: source_len,
|
||||
end: source_len,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rnix::TextRange> for ast::SourceRange {
|
||||
fn from(range: rnix::TextRange) -> Self {
|
||||
Self {
|
||||
start: usize::from(range.start()),
|
||||
end: usize::from(range.end()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user