diff options
author | stuebinm | 2024-04-16 18:51:24 +0200 |
---|---|---|
committer | stuebinm | 2024-04-16 18:51:24 +0200 |
commit | 238dabec513eac8af699756281d5aec12720686c (patch) | |
tree | 91db54c21027c96d73add8e4702327ac1a812293 | |
parent | a0459645638fe1397aa4f5e01a8f9093911ccf6c (diff) |
add an actual AST for the query language
this incidentally also moves a lot of the parsing logic out of
piplines.rs and instead keeps it in queries.rs, where it should probably
be anyways, so the pipeline module can focus on just … well, applying
the filter pipeline
-rw-r--r-- | src/batchmode.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 22 | ||||
-rw-r--r-- | src/pipeline.rs | 210 | ||||
-rw-r--r-- | src/queries.rs | 129 |
4 files changed, 174 insertions, 193 deletions
diff --git a/src/batchmode.rs b/src/batchmode.rs index 84b0dff..3e83694 100644 --- a/src/batchmode.rs +++ b/src/batchmode.rs @@ -2,13 +2,13 @@ use std::{path::PathBuf, fs, sync::{Arc, Mutex}}; use rowan::ast::AstNode; use threadpool::ThreadPool; -use crate::{status_reporter::*, queries::{SyntaxNode, Parse}, parse_nexp, apply_changes}; +use crate::{status_reporter::*, queries::Query, parse_nexp, apply_changes}; #[allow(unreachable_code, unused)] -pub fn batchmode(tasks: Vec<PathBuf>, query: Parse, debug: bool) { +pub fn batchmode(tasks: Vec<PathBuf>, query: Query, debug: bool) { - fn do_task(path: PathBuf, query: Parse, debug: bool) -> anyhow::Result<(PathBuf, Option<String>)> { + fn do_task(path: PathBuf, query: Query, debug: bool) -> anyhow::Result<(PathBuf, Option<String>)> { let (content, nexp) = match parse_nexp(&path) { Err(e) => { diff --git a/src/main.rs b/src/main.rs index 1b0ce7d..54f17a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use std::{path::PathBuf, fs, process::exit}; -use pipeline::{Change, ChangeKind}; -use rnix::SyntaxNode; -use rowan::{ast::AstNode, TextSize, NodeOrToken, TextRange}; +use pipeline::Change; +use rowan::{ast::AstNode, NodeOrToken}; use clap::{arg, Parser}; #[allow(dead_code)] @@ -50,10 +49,18 @@ fn main() { exit(1); } + let query = match parse.to_query() { + Err(es) => { + eprintln!("{}", es.join("\n")); + exit(1); + }, + Ok(query) => query + }; + // println!("{nexp:#?}"); if args.batchmode { - batchmode::batchmode(args.path, parse, args.debug); + batchmode::batchmode(args.path, query, args.debug); } else { let (content, nexp) = match parse_nexp(&args.path[0]) { Err(e) => { @@ -63,7 +70,7 @@ fn main() { Ok(exp) => exp }; - let (changes, results) = parse.apply(&content, nexp.syntax().clone()).unwrap(); + let (changes, results) = query.apply(&content, nexp.syntax().clone()).unwrap(); if args.debug { println!("{changes:?}"); @@ -89,8 +96,9 @@ fn apply_changes(content: &str, mut changes: Vec<Change>, debug: bool) -> String changes.sort_by_key(|change| change.node.text_range().start()); for change in changes { + use queries::Operator::*; match change.kind { - ChangeKind::Remove => { + Remove => { let (before, after) = remove_node(&change.node); if last_pos > before { continue } ncontent += &content[last_pos..before]; @@ -99,7 +107,7 @@ fn apply_changes(content: &str, mut changes: Vec<Change>, debug: bool) -> String } last_pos = after; } - ChangeKind::Keep => { + Keep => { let (before, after) = surrounding_noise(&change.node); let (before, after) = eat_whitespace(&before, &after); // let (before, after) = (change.node.text_range().start().into(), change.node.text_range().end().into()); diff --git a/src/pipeline.rs b/src/pipeline.rs index cce3e31..c111275 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,168 +1,36 @@ - -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) - } -} - -// address nodes by their role relative to their parent -enum NixSyntaxRole { - Argument, - Function, - Attribute, - // TODO -} - -impl NixSyntaxRole { - fn from_str(from: &str) -> Option<NixSyntaxRole> { - use NixSyntaxRole::*; - Some(match from { - "Argument" => Argument, - "Function" => Function, - "Attribute" => Attribute, - _ => return None - }) - } -} - -enum Op { - Down, - DownRecursive, - Up, - UpRecursive, - NixSyntaxNode(rnix::SyntaxKind), - NixSyntaxRole(NixSyntaxRole), - Named(String) +#[derive(Debug)] +pub struct Change { + pub node: rnix::SyntaxNode, + pub kind: Operator } -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 => if let Some(role) = NixSyntaxRole::from_str(name) { - Op::NixSyntaxRole(role) - } else { - 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!(), - } - } +impl Filter { 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()), + use Selector::*; + + // several of these closures take ownership of the operator, hence clone + let mut acc: NixExprs = match self.selector.clone() { + Down => Box::new(acc.map(|s| s.children()).flatten()), + DownRecursive => Box::new(acc.map(|s| s.descendants()).flatten()), + Up => Box::new(acc.filter_map(|s| s.parent())), + 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::NixSyntaxRole(role)) => {use NixSyntaxRole::*; match role { + NixSyntaxNode(kind) => Box::new(acc.filter(move |s| s.kind() == kind)), + NixSyntaxRole(role) => {use crate::queries::NixSyntaxRole::*; match role { Argument => Box::new(acc.filter_map(move |s| match_ast! { match s { ast::Apply(value) => value.argument().map(|s| s.syntax().to_owned()), _ => None }})), _ => todo!() }} - Some(Op::Named(name)) => - Box::new(acc + Named(name) => Box::new(acc .filter(move |node| match_ast! { match node { ast::AttrpathValue(value) => { name == value.attrpath().unwrap().to_string() @@ -178,52 +46,28 @@ impl Atom { 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); - } + if let Some(operator) = &self.args { + let (mut nchanges, nacc): (Vec<_>, Vec<_>) = acc + .map(|node| (Change { node: node.clone(), kind: operator.clone() }, 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() - } - +impl Query { 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!("???") - } + for filter in &self.filters { + pipe = filter.apply(pipe); } // let results = diff --git a/src/queries.rs b/src/queries.rs index c1e16df..3f7be8e 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -1,6 +1,7 @@ // this is mostly based on the s-exp tutorial // https://github.com/rust-analyzer/rowan/blob/master/examples/s_expressions.rs +use itertools::Itertools; use rowan::{GreenNode, GreenNodeBuilder}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -255,4 +256,132 @@ fn test_parser() { } +#[derive(Debug, Clone)] +pub struct Query { + pub filters: Vec<Filter> +} + +#[derive(Debug, Clone)] +pub struct Filter { + pub selector: Selector, + pub args: Option<Operator> +} + +#[derive(Debug, Clone)] +pub enum Selector { + Down, + DownRecursive, + Up, + UpRecursive, + NixSyntaxNode(rnix::SyntaxKind), + NixSyntaxRole(NixSyntaxRole), + Named(String) +} + +#[derive(Debug, Clone)] +pub enum NixSyntaxRole { + Argument, + Function, + Attribute, + // TODO +} + +#[derive(Debug, Clone)] +pub enum Operator { + Remove, + Keep +} + +type ParseError = String; +type ParseResult<T> = Result<T, Vec<ParseError>>; + +impl NixSyntaxRole { + fn from_str(from: &str) -> Option<NixSyntaxRole> { + use NixSyntaxRole::*; + Some(match from { + "Argument" => Argument, + "Function" => Function, + "Attribute" => Attribute, + _ => return None + }) + } +} + +impl Parse { + + pub fn to_query(&self) -> ParseResult<Query> { + + fn parse_operator(node: SyntaxNode) -> ParseResult<Operator> { + match node.to_string().as_str() { + "remove" => Ok(Operator::Remove), + "keep" => Ok(Operator::Keep), + unknown => Err(vec![format!("unknown operator {unknown}")]) + } + } + fn parse_args(node: SyntaxNode) -> ParseResult<Option<Operator>> { + let list_node = node + .children() + .find(|n| n.kind() == LIST); + + if let Some(node) = list_node { + let args = node + .children() + .map(|child| { + match child.kind() { + ATOM => parse_operator(child), + _ => unreachable!() + } + }) + .collect::<ParseResult<Vec<Operator>>>(); + + match args { + Err(e) => Err(e), + Ok(ops) if ops.len() == 1 => Ok(Some(ops.into_iter().exactly_one().unwrap())), + _ => Err(vec!["cannot have multiple operators at the same node (for now)".to_string()]) + } + } else { + Ok(None) + } + } + fn parse_filter(node: SyntaxNode) -> ParseResult<Filter> { + let text = match node.green().children().next() { + Some(rowan::NodeOrToken::Token(token)) => token.text().to_string(), + _ => unreachable!(), + }; + + use Selector::*; + let selector = match text.as_str() { + ">" => Down, + ">>" => DownRecursive, + "<" => Up, + "<<" => UpRecursive, + "Inherit" => NixSyntaxNode(rnix::SyntaxKind::NODE_INHERIT), + "String" => NixSyntaxNode(rnix::SyntaxKind::NODE_STRING), + // TODO other syntax nodes + name => if let Some(role) = self::NixSyntaxRole::from_str(name) { + NixSyntaxRole(role) + } else { + Named(name.to_owned()) + }, + }; + + let args = parse_args(node)?; + + let filter = Filter { + selector, + args + }; + Ok(filter) + } + + let root = self.syntax(); + assert_eq!(root.kind(), ROOT); + + root.children() + .map(parse_filter) + .collect::<Result<Vec<Filter>, Vec<ParseError>>>() + .map(|filters| Query {filters}) + + } +} |