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, Vec) { 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::Select(select) => convert_select_expr(select), rnix::ast::Expr::Apply(apply) => ast::Expr::Apply(ast::Apply { lambda: Box::new(convert_expr(apply.lambda().expect("applications have a lambda"))), argument: Box::new(convert_expr( apply.argument().expect("applications have an argument"), )), }), rnix::ast::Expr::LetIn(let_in) => ast::Expr::Let { range: let_in.syntax().text_range().into(), 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(convert_attr_set(attr_set)), 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_select_expr(select: rnix::ast::Select) -> ast::Expr { select.clone().try_into().map(ast::Expr::Select).unwrap_or_else(|_| { ast::Expr::Other( select .syntax() .children() .filter_map(rnix::ast::Expr::cast) .map(convert_expr) .collect(), ) }) } fn convert_binding_ast(binding: rnix::ast::AttrpathValue) -> Option { let mut attrs = binding.attrpath()?.attrs(); let Attr::Ident(key) = attrs.next()? else { return None; }; Some(ast::Binding { range: binding.syntax().text_range().into(), key: key.into(), value: convert_expr(binding.value()?), }) } fn convert_attr_set(attr_set: rnix::ast::AttrSet) -> Vec { attr_set .entries() .flat_map(|entry| match entry { rnix::ast::Entry::AttrpathValue(binding) => { convert_binding_ast(binding).into_iter().collect() } rnix::ast::Entry::Inherit(inherit) => convert_inherit(inherit), }) .collect() } fn convert_inherit(inherit: rnix::ast::Inherit) -> Vec { let value = inherit .from() .and_then(|from| from.expr()) .map(convert_expr); inherit .attrs() .filter_map(|attr| match attr { Attr::Ident(ident) => Some(ident), _ => None, }) .map(|key| ast::Binding { range: key.syntax().text_range().into(), value: value .clone() .unwrap_or_else(|| ast::Expr::Ident(key.clone().into())), key: key.into(), }) .collect() } fn lambda_parameters(lambda: &rnix::ast::Lambda) -> Vec { 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 { match node.kind() { rnix::SyntaxKind::NODE_ATTRPATH_VALUE => convert_binding(node), _ => None, } } fn convert_binding(node: rnix::SyntaxNode) -> Option { let binding = rnix::ast::AttrpathValue::cast(node)?; let range = binding.syntax().text_range().into(); let mut attrs = binding.attrpath()?.attrs(); let Attr::Ident(key) = attrs.next()? else { return None; }; if attrs.next().is_some() { return None; } Some(ast::Node { range, data: ast::NodeData::Binding(ast::Binding { range, key: key.into(), value: convert_expr(binding.value()?), }), }) } impl TryFrom for ast::Binding { type Error = (); fn try_from(binding: rnix::ast::AttrpathValue) -> Result { 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 for ast::Expr { type Error = (); fn try_from(expr: rnix::ast::Expr) -> Result { 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 for ast::Ident { fn from(ident: rnix::ast::Ident) -> Self { Self { name: ident.to_string(), } } } impl TryFrom for ast::Select { type Error = (); fn try_from(select: rnix::ast::Select) -> Result { 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 { 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 for ast::SourceRange { fn from(range: rnix::TextRange) -> Self { Self { start: usize::from(range.start()), end: usize::from(range.end()), } } }