diff options
author | stuebinm | 2024-04-12 16:17:06 +0200 |
---|---|---|
committer | stuebinm | 2024-04-12 16:17:06 +0200 |
commit | 422dbd5209fddac75412d8e7de0137f732c7dbc4 (patch) | |
tree | e315575c8f3e62df29ea1ab5f6c4886b2cbb2767 | |
parent | 0567f916d4365c8dc0be99d194fe6d157befbc81 (diff) |
some restructuring, arg handling, re-enable batchmode
-rw-r--r-- | Cargo.lock | 147 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/batchmode.rs | 38 | ||||
-rw-r--r-- | src/main.rs | 206 | ||||
-rw-r--r-- | src/pipeline.rs | 203 | ||||
-rw-r--r-- | src/queries.rs | 233 |
6 files changed, 567 insertions, 262 deletions
@@ -12,12 +12,54 @@ dependencies = [ ] [[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] name = "anyhow" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -36,6 +78,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -44,8 +87,22 @@ version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -55,6 +112,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] name = "countme" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -73,6 +136,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -262,6 +331,12 @@ dependencies = [ ] [[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] name = "syn" version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -292,3 +367,75 @@ name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" @@ -16,4 +16,4 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } itertools = "0.12.1" m_lexer = "0.0.4" -clap = { version = "4.5", features = [ "cargo", "help", "std" ], default-features = false } +clap = { version = "4.5", features = [ "derive" ] } 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()) - } -} |