summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/batchmode.rs38
-rw-r--r--src/main.rs206
-rw-r--r--src/pipeline.rs203
-rw-r--r--src/queries.rs233
4 files changed, 419 insertions, 261 deletions
diff --git a/src/batchmode.rs b/src/batchmode.rs
index 8398bce..84b0dff 100644
--- a/src/batchmode.rs
+++ b/src/batchmode.rs
@@ -1,30 +1,43 @@
use std::{path::PathBuf, fs, sync::{Arc, Mutex}};
+use rowan::ast::AstNode;
use threadpool::ThreadPool;
-use crate::status_reporter::*;
+use crate::{status_reporter::*, queries::{SyntaxNode, Parse}, parse_nexp, apply_changes};
-// TODO: make this usable
-// (this module just here to keep old code around for a bit)
-pub enum Task {}
#[allow(unreachable_code, unused)]
-pub fn batchmode(tasks: Vec<(PathBuf, Task)>) {
+pub fn batchmode(tasks: Vec<PathBuf>, query: Parse, debug: bool) {
+ fn do_task(path: PathBuf, query: Parse, debug: bool) -> anyhow::Result<(PathBuf, Option<String>)> {
+
+ let (content, nexp) = match parse_nexp(&path) {
+ Err(e) => {
+ anyhow::bail!("could not parse file {path:?}")
+ },
+ Ok(exp) => exp
+ };
+
+ let (changes, _) = query.apply(&content, nexp.syntax().clone())?;
+
+ let changed = apply_changes(&content, changes, debug);
+
+ Ok((path, if changed != content { Some(changed) } else { None }))
+ }
let pool = ThreadPool::new(16);
let results = Arc::new(Mutex::new(vec![]));
let printer = Arc::new(StatusReport::new(tasks.len(), tasks.len()));
- for (path, task) in tasks {
+ for path in tasks {
pool.execute({
let results = Arc::clone(&results);
let printer = Arc::clone(&printer);
+ let query = query.clone();
move || {
printer.enter_file(&format!("{path:?}"));
- let result: anyhow::Result<(PathBuf, String)> = todo!();
-
+ let result = do_task(path, query, debug);
results.lock().unwrap().push(result);
}
});
@@ -35,18 +48,20 @@ pub fn batchmode(tasks: Vec<(PathBuf, Task)>) {
println!("\n\nSummary:");
let mut c_errors = 0;
let mut c_total = 0;
+ let mut c_changes = 0;
for r in results.lock().unwrap().iter() {
match r {
Err(e) => {
println!(" {}", e);
c_errors += 1;
},
+ Ok((_, Some(_))) => c_changes += 1,
_ => ()
}
c_total += 1;
}
- println!("\n ({c_total} sites total, {c_errors} errors, generated {} edits)", c_total - c_errors);
+ println!("\n ({c_total} sites total, {c_errors} errors, generated {} edits)", c_changes);
let edits: Vec<_> = Arc::into_inner(results).unwrap().into_inner().unwrap()
.into_iter()
@@ -55,7 +70,8 @@ pub fn batchmode(tasks: Vec<(PathBuf, Task)>) {
println!("applying changes ...");
for (filename, content) in edits {
- fs::write(&filename, content.as_bytes()).unwrap();
- // println!("{}", content);
+ if let Some(content) = content {
+ fs::write(&filename, content.as_bytes()).unwrap();
+ }
}
}
diff --git a/src/main.rs b/src/main.rs
index a09f4ef..2bc44ab 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
use std::{path::PathBuf, fs, process::exit};
-use rowan::{ast::AstNode, TextSize};
-use clap::{arg, command, value_parser};
+use pipeline::{Change, ChangeKind};
+use rowan::{ast::AstNode, TextSize, NodeOrToken};
+use clap::{arg, Parser};
#[allow(dead_code)]
mod queries;
@@ -9,6 +10,19 @@ mod status_reporter;
mod batchmode;
#[allow(dead_code)]
mod util;
+#[allow(dead_code)]
+mod pipeline;
+
+
+#[derive(clap::Parser)]
+struct Args {
+ query: String,
+ path: Vec<PathBuf>,
+ #[arg(short, long)]
+ debug: bool,
+ #[arg(long)]
+ batchmode: bool
+}
fn parse_nexp(path: &PathBuf) -> anyhow::Result<(String, rnix::Root)> {
@@ -23,24 +37,9 @@ fn parse_nexp(path: &PathBuf) -> anyhow::Result<(String, rnix::Root)> {
fn main() {
- let matches = command!()
- .arg(arg!(--batchmode "run in batch mode")
- .required(false)
- )
- .arg(arg!([query] "query to run")
- .required(true))
- .arg(arg!([file] "file to operate on")
- .required(true)
- .value_parser(value_parser!(PathBuf))
- )
- .arg(arg!(--edit <operation> "what to do")
- .required(false))
- .get_matches();
-
- let query_string = matches.get_one::<String>("query").unwrap();
- let files = matches.get_one::<PathBuf>("file").unwrap();
-
- let parse = queries::parse(query_string);
+ let args = Args::parse();
+
+ let parse = queries::parse(&args.query);
if parse.errors.len() != 0 {
eprintln!(
"syntax {}: \n {}",
@@ -50,61 +49,144 @@ fn main() {
exit(1);
}
- let (content, nexp) = match parse_nexp(files) {
- Err(e) => {
- eprintln!("could not parse file: {e}");
- exit(2);
- },
- Ok(exp) => exp
- };
-
// println!("{nexp:#?}");
- let results = parse.apply(&content, nexp.syntax().clone()).unwrap();
-
+ if args.batchmode {
+ batchmode::batchmode(args.path, parse, args.debug);
+ } else {
+ let (content, nexp) = match parse_nexp(&args.path[0]) {
+ Err(e) => {
+ eprintln!("could not parse file: {e}");
+ exit(2);
+ },
+ Ok(exp) => exp
+ };
+
+ let (changes, results) = parse.apply(&content, nexp.syntax().clone()).unwrap();
+
+ if args.debug {
+ println!("{changes:?}");
+ }
- if let Some(op) = matches.get_one::<String>("edit") {
- match &op[..] {
- "remove" => {
- let new = remove_nodes(content, &results);
- println!("{new}");
+ if changes.len() == 0 {
+ for result in results {
+ println!("{result}");
}
- _ => ()
+ } else {
+ let changed = apply_changes(&content, changes, args.debug);
+
+ println!("{changed}");
}
- } else {
- for result in &results {
- println!("{result}");
+ }
+}
+
+
+fn apply_changes(content: &str, mut changes: Vec<Change>, debug: bool) -> String {
+ let mut last_pos = 0;
+ let mut ncontent = String::new();
+
+ changes.sort_by_key(|change| change.node.text_range().start());
+
+ for change in changes {
+ match change.kind {
+ ChangeKind::Remove => {
+ let (before, after) = remove_node(&change.node);
+ if last_pos > before { continue }
+ ncontent += &content[last_pos..before];
+ if debug {
+ println!("removing: {}", &content[before..after])
+ }
+ last_pos = after;
+ }
+ ChangeKind::Keep => {
+ // let (before, after) = surrounding_whitespace(&change.node);
+ let (before, after) = (change.node.text_range().start().into(), change.node.text_range().end().into());
+ if debug {
+ println!("keeping: {}", &content[before..after])
+ };
+ ncontent += &content[before..after];
+ // last_pos = after;
+ }
}
}
+ ncontent += &content[last_pos..];
+ ncontent
}
-fn remove_nodes(content: String, results: &Vec<rnix::SyntaxNode>) -> String {
-
- assert!(results.len() == 1);
+#[allow(unused)]
+fn surrounding_whitespace(node: &rnix::SyntaxNode) -> (usize, usize) {
+ let before = node
+ .prev_sibling_or_token()
+ .filter(|n| n.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE)
+ .map(|n| n.text_range().start().into())
+ .unwrap_or_else(|| node.text_range().start().into());
+ let after = node
+ .next_sibling_or_token()
+ .filter(|n| n.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE)
+ .map(|n| n.text_range().end().into())
+ .unwrap_or_else(|| node.text_range().end().into());
+
+ (before, after)
+}
- let span = &results[0];
- let (before, after) = match (span.prev_sibling_or_token(), span.next_sibling_or_token()) {
- (Some(prev), Some(next))
- if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE
- && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE
- => {
- if prev.to_string().lines().count() < next.to_string().lines().count() {
- (prev.text_range().len(), TextSize::new(0))
- } else {
- (TextSize::new(0), next.text_range().len())
+fn remove_node(span: &rnix::SyntaxNode) -> (usize, usize) {
+
+ // TODO: how to remove brackets around the node?
+
+ fn eat_to_bracket(node: &rnix::SyntaxNode, backwards: bool) -> (TextSize, usize) {
+ let mut span = NodeOrToken::Node(node.clone());
+ let mut bracket = span.clone();
+ let mut brackets = 0;
+ loop {
+ match if backwards { span.prev_sibling_or_token() } else { span.next_sibling_or_token() } {
+ Some(NodeOrToken::Token(t)) => match t.kind() {
+ rnix::SyntaxKind::TOKEN_WHITESPACE => span = NodeOrToken::Token(t),
+ rnix::SyntaxKind::TOKEN_L_PAREN if backwards => {
+ span = NodeOrToken::Token(t);
+ bracket = span.clone();
+ brackets += 1;
+ },
+ rnix::SyntaxKind::TOKEN_R_PAREN if !backwards => {
+ span = NodeOrToken::Token(t);
+ bracket = span.clone();
+ brackets += 1;
+ },
+ _ => break,
+ },
+ _ => break,
}
}
- _ => (TextSize::default(),TextSize::default())
+ (if backwards { bracket.text_range().start() } else { bracket.text_range().end() }, brackets)
+ }
+
+ let (before, after) = match (eat_to_bracket(span, true), eat_to_bracket(span, false)) {
+ ((before, n1), (after, n2)) if n1 == n2 && n1 != 0 => (before.into(), after.into()),
+ _ => match (span.prev_sibling_or_token(), span.next_sibling_or_token()) {
+ (Some(prev), Some(next))
+ if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE
+ && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE
+ => {
+ if prev.to_string().lines().count() < next.to_string().lines().count() {
+ (Into::<usize>::into(span.text_range().start() - prev.text_range().len()) + previous_indentation(&span).unwrap_or(0)
+ , Into::<usize>::into(span.text_range().end()) + next_indentation(&span).unwrap_or(0))
+ } else {
+ (Into::<usize>::into(span.text_range().start()) - previous_indentation(&span).unwrap_or(0),
+ Into::<usize>::into(span.text_range().end() + next.text_range().len()) - next_indentation(&span).unwrap_or(0))
+ }
+ }
+ _ => (span.text_range().start().into(), span.text_range().end().into())
+ }
};
- String::new()
- + &content[..Into::<usize>::into(span.text_range().start() - before) - previous_indentation(span).unwrap_or(0)]
- + &content[(span.text_range().end() + after).into()..]
-}
+ (before, after)
+ // ( Into::<usize>::into(span.text_range().start()) - before
+ // , Into::<usize>::into(span.text_range().end()) + after
+ // )
+}
fn previous_indentation(node: &rnix::SyntaxNode) -> Option<usize> {
@@ -116,3 +198,13 @@ fn previous_indentation(node: &rnix::SyntaxNode) -> Option<usize> {
None
}
}
+
+fn next_indentation(node: &rnix::SyntaxNode) -> Option<usize> {
+ let whitespace_token = node.next_sibling_or_token()?;
+
+ if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE {
+ Some(whitespace_token.to_string().lines().last().unwrap().len())
+ } else {
+ None
+ }
+}
diff --git a/src/pipeline.rs b/src/pipeline.rs
new file mode 100644
index 0000000..cba046f
--- /dev/null
+++ b/src/pipeline.rs
@@ -0,0 +1,203 @@
+
+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<dyn Iterator<Item = rnix::SyntaxNode>>;
+type Pipe = (Vec<Change>, 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<Self> {
+ 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<Self> {
+ 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<Item = Qexp> + '_ {
+ self.0.children().filter_map(Qexp::cast)
+ }
+}
+
+enum Op {
+ Down,
+ DownRecursive,
+ Up,
+ UpRecursive,
+ NixSyntaxNode(rnix::SyntaxKind),
+ Named(String)
+}
+
+impl Atom {
+ fn eval(&self) -> Option<i64> {
+ self.text().parse().ok()
+ }
+ fn as_op(&self) -> Option<Op> {
+ 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<ChangeKind> {
+ let change = match self.text().as_str() {
+ "remove" => ChangeKind::Remove,
+ "keep" => ChangeKind::Keep,
+ _ => return None
+ };
+ Some(change)
+ }
+ fn iter_args(&self) -> impl Iterator<Item = Atom> {
+ 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<Item = Qexp> + '_ {
+ self.0.children().filter_map(Qexp::cast)
+ }
+
+ fn iter(&self) -> impl Iterator<Item = Atom> {
+ 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<Change>, Vec<rnix::SyntaxNode>)> {
+
+ 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()))
+ }
+}
diff --git a/src/queries.rs b/src/queries.rs
index b07224a..c1e16df 100644
--- a/src/queries.rs
+++ b/src/queries.rs
@@ -1,9 +1,43 @@
// this is mostly based on the s-exp tutorial
// https://github.com/rust-analyzer/rowan/blob/master/examples/s_expressions.rs
-use rnix::{match_ast, ast};
-use rowan::{GreenNode, GreenNodeBuilder, ast::AstNode};
+use rowan::{GreenNode, GreenNodeBuilder};
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[allow(non_camel_case_types)]
+#[repr(u16)]
+pub enum SyntaxKind {
+ L_BRACKET = 0, // '['
+ R_BRACKET, // ']'
+ WORD, // 'Attrset', 'meta', '.', '>', ...
+ WHITESPACE, // whitespaces is explicit
+ ERROR, // as well as errors
+
+ // composite nodes
+ LIST, // `[..]`
+ ATOM, // wraps WORD
+ ROOT, // top-level (a complete query)
+}
+use SyntaxKind::*;
+
+impl From<SyntaxKind> for rowan::SyntaxKind {
+ fn from(kind: SyntaxKind) -> Self {
+ Self(kind as u16)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum QueryLang {}
+impl rowan::Language for QueryLang {
+ type Kind = SyntaxKind;
+ fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
+ assert!(raw.0 <= ROOT as u16);
+ unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
+ }
+ fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
+ kind.into()
+ }
+}
fn lex(text: &str) -> Vec<(SyntaxKind, String)> {
fn tok(t: SyntaxKind) -> m_lexer::TokenKind {
@@ -43,42 +77,7 @@ fn lex(text: &str) -> Vec<(SyntaxKind, String)> {
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-#[allow(non_camel_case_types)]
-#[repr(u16)]
-enum SyntaxKind {
- L_BRACKET = 0, // '['
- R_BRACKET, // ']'
- WORD, // 'Attrset', 'meta', '.', '>', ...
- WHITESPACE, // whitespaces is explicit
- ERROR, // as well as errors
-
- // composite nodes
- LIST, // `[..]`
- ATOM, // wraps WORD
- ROOT, // top-level (a complete query)
-}
-use SyntaxKind::*;
-
-impl From<SyntaxKind> for rowan::SyntaxKind {
- fn from(kind: SyntaxKind) -> Self {
- Self(kind as u16)
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-enum Lang {}
-impl rowan::Language for Lang {
- type Kind = SyntaxKind;
- fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
- assert!(raw.0 <= ROOT as u16);
- unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
- }
- fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
- kind.into()
- }
-}
-
+#[derive(Clone)]
pub struct Parse {
pub green_node: GreenNode,
pub errors: Vec<String>,
@@ -207,14 +206,14 @@ pub fn parse(text: &str) -> Parse {
/// but it contains parent pointers, offsets, and
/// has identity semantics.
-type SyntaxNode = rowan::SyntaxNode<Lang>;
+pub type SyntaxNode = rowan::SyntaxNode<QueryLang>;
#[allow(unused)]
-type SyntaxToken = rowan::SyntaxToken<Lang>;
+type SyntaxToken = rowan::SyntaxToken<QueryLang>;
#[allow(unused)]
type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
impl Parse {
- fn syntax(&self) -> SyntaxNode {
+ pub fn syntax(&self) -> SyntaxNode {
SyntaxNode::new_root(self.green_node.clone())
}
}
@@ -257,155 +256,3 @@ fn test_parser() {
-type NixExprs = Box<dyn Iterator<Item = rnix::SyntaxNode>>;
-
-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<Self> {
- if node.kind() == $kind {
- Some(Self(node))
- } else {
- None
- }
- }
- }
- };
-}
-
-ast_node!(Root, ROOT);
-ast_node!(Atom, ATOM);
-ast_node!(List, LIST);
-
-// Sexp is slightly different, so let's do it by hand.
-#[derive(PartialEq, Eq, Hash, Debug)]
-#[repr(transparent)]
-struct Qexp(SyntaxNode);
-
-enum QexpKind {
- Atom(Atom),
- List(List),
-}
-
-impl Qexp {
- fn cast(node: SyntaxNode) -> Option<Self> {
- 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: NixExprs) -> NixExprs {
- todo!()
- }
-}
-
-// Let's enhance AST nodes with ancillary functions and
-// eval.
-impl Root {
- fn qexps(&self) -> impl Iterator<Item = Qexp> + '_ {
- self.0.children().filter_map(Qexp::cast)
- }
-}
-
-enum Op {
- Down,
- DownRecursive,
- Up,
- UpRecursive,
- Named(String)
-}
-
-impl Atom {
- fn eval(&self) -> Option<i64> {
- self.text().parse().ok()
- }
- fn as_op(&self) -> Option<Op> {
- let op = match self.text().as_str() {
- ">" => Op::Down,
- ">>" => Op::DownRecursive,
- "<" => Op::Up,
- "<<" => Op::UpRecursive,
- name => Op::Named(name.to_owned()),
- };
- Some(op)
- }
- fn text(&self) -> String {
- match self.0.green().children().next() {
- Some(rowan::NodeOrToken::Token(token)) => token.text().to_string(),
- _ => unreachable!(),
- }
- }
- fn apply(&self, acc: NixExprs) -> 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()),
- 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
- ast::Ident(value) => {
- name == value.to_string()
- },
- _ => false
- }})),
- _ => todo!()
- }
- }
-}
-
-impl List {
- fn sexps(&self) -> impl Iterator<Item = Qexp> + '_ {
- self.0.children().filter_map(Qexp::cast)
- }
-}
-
-
-impl Parse {
- fn root(&self) -> Root {
- Root::cast(self.syntax()).unwrap()
- }
-
- pub fn apply(&self, _content: &str, nexp: rnix::SyntaxNode) -> anyhow::Result<Vec<rnix::SyntaxNode>> {
-
- let mut acc: NixExprs = Box::new(std::iter::once(nexp));
-
- for qexp in self.root().qexps() {
- match qexp.kind() {
- QexpKind::Atom(filter) => {
- acc = filter.apply(acc);
- }
- _ => panic!("???")
- }
- }
-
- // let results =
- // acc.map(|node| content[node.text_range().start().into()..node.text_range().end().into()].to_owned())
- // .collect();
-
- Ok(acc.collect())
- }
-}