use itertools::Itertools; use rnix::{match_ast, ast}; use rowan::ast::AstNode; use crate::queries::*; use crate::queries::SyntaxKind::*; #[derive(Debug)] pub struct Change { pub node: rnix::SyntaxNode, pub kind: ChangeKind } #[derive(Copy, Clone, Debug)] pub enum ChangeKind { Remove, Keep } type NixExprs = Box>; type Pipe = (Vec, NixExprs); macro_rules! ast_node { ($ast:ident, $kind:ident) => { #[derive(PartialEq, Eq, Hash)] #[repr(transparent)] struct $ast(SyntaxNode); impl $ast { #[allow(unused)] fn cast(node: SyntaxNode) -> Option { if node.kind() == $kind { Some(Self(node)) } else { None } } } }; } ast_node!(Root, ROOT); ast_node!(Atom, ATOM); ast_node!(List, LIST); #[derive(PartialEq, Eq, Hash, Debug)] #[repr(transparent)] struct Qexp(SyntaxNode); enum QexpKind { Atom(Atom), List(List), } impl Qexp { fn cast(node: SyntaxNode) -> Option { if Atom::cast(node.clone()).is_some() || List::cast(node.clone()).is_some() { Some(Qexp(node)) } else { None } } fn kind(&self) -> QexpKind { Atom::cast(self.0.clone()) .map(QexpKind::Atom) .or_else(|| List::cast(self.0.clone()).map(QexpKind::List)) .unwrap() } fn apply(&self, _acc: Pipe) -> Pipe { todo!() } } impl Root { fn qexps(&self) -> impl Iterator + '_ { self.0.children().filter_map(Qexp::cast) } } enum Op { Down, DownRecursive, Up, UpRecursive, NixSyntaxNode(rnix::SyntaxKind), Named(String) } impl Atom { fn eval(&self) -> Option { self.text().parse().ok() } fn as_op(&self) -> Option { let op = match self.text().as_str() { ">" => Op::Down, ">>" => Op::DownRecursive, "<" => Op::Up, "<<" => Op::UpRecursive, "Inherit" => Op::NixSyntaxNode(rnix::SyntaxKind::NODE_INHERIT), "String" => Op::NixSyntaxNode(rnix::SyntaxKind::NODE_STRING), // TODO other syntax nodes name => Op::Named(name.to_owned()), }; Some(op) } fn as_change(&self) -> Option { let change = match self.text().as_str() { "remove" => ChangeKind::Remove, "keep" => ChangeKind::Keep, _ => return None }; Some(change) } fn iter_args(&self) -> impl Iterator { self.0.children().find_map(List::cast).into_iter().map(|arglist| arglist.iter()).flatten() } fn text(&self) -> String { match self.0.green().children().next() { Some(rowan::NodeOrToken::Token(token)) => token.text().to_string(), _ => unreachable!(), } } fn apply(&self, (mut changes, acc): Pipe) -> Pipe { let mut acc: NixExprs = match self.as_op() { Some(Op::Down) => Box::new(acc.map(|s| s.children()).flatten()), Some(Op::DownRecursive) => Box::new(acc.map(|s| s.descendants()).flatten()), Some(Op::Up) => Box::new(acc.filter_map(|s| s.parent())), Some(Op::UpRecursive) => Box::new(acc.map(|s| s.ancestors()).flatten()), // TODO: how to select roles relative to previous node? Some(Op::NixSyntaxNode(kind)) => Box::new(acc.filter(move |s| s.kind() == kind)), Some(Op::Named(name)) => Box::new(acc .filter(move |node| match_ast! { match node { ast::AttrpathValue(value) => { name == value.attrpath().unwrap().to_string() }, ast::Apply(value) => { // TODO: special case lambda = NODE_SELECT here? name == value.lambda().unwrap().to_string() }, // TODO: this is difficult — I want to use free-form names // to select things below, too, but that might not always be // possible. perhaps it is possible to skip over descendants? ast::Ident(value) => { name == value.to_string() }, _ => false }})), _ => todo!() }; if let Ok(arg) = self.iter_args().exactly_one() { if let Some(change) = arg.as_change() { let (mut nchanges, nacc): (Vec<_>, Vec<_>) = acc .map(|node| (Change { node: node.clone(), kind: change }, node)) .unzip(); acc = Box::new(nacc.into_iter()); changes.append(&mut nchanges); } } (changes, acc) } } impl List { fn sexps(&self) -> impl Iterator + '_ { self.0.children().filter_map(Qexp::cast) } fn iter(&self) -> impl Iterator { self.0.children().filter_map(Atom::cast) } } impl Parse { fn root(&self) -> Root { Root::cast(self.syntax()).unwrap() } pub fn apply(&self, _content: &str, nexp: rnix::SyntaxNode) -> anyhow::Result<(Vec, Vec)> { let mut pipe: Pipe = (Vec::new(), Box::new(std::iter::once(nexp))); for qexp in self.root().qexps() { match qexp.kind() { QexpKind::Atom(filter) => { pipe = filter.apply(pipe); } _ => panic!("???") } } // let results = // acc.map(|node| content[node.text_range().start().into()..node.text_range().end().into()].to_owned()) // .collect(); Ok((pipe.0, pipe.1.collect())) } }