summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuebinm2024-04-16 18:51:24 +0200
committerstuebinm2024-04-16 18:51:24 +0200
commit238dabec513eac8af699756281d5aec12720686c (patch)
tree91db54c21027c96d73add8e4702327ac1a812293
parenta0459645638fe1397aa4f5e01a8f9093911ccf6c (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.rs6
-rw-r--r--src/main.rs22
-rw-r--r--src/pipeline.rs210
-rw-r--r--src/queries.rs129
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})
+
+ }
+}