// 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, TextRange}; type ParseError = (TextRange, String); type ParseResult = Result>; #[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 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::(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 { m_lexer::TokenKind(rowan::SyntaxKind::from(t).0) } fn kind(t: m_lexer::TokenKind) -> SyntaxKind { match t.0 { 0 => L_BRACKET, 1 => R_BRACKET, 2 => WORD, 3 => WHITESPACE, 4 => ERROR, _ => unreachable!(), } } let lexer = m_lexer::LexerBuilder::new() .error_token(tok(ERROR)) .tokens(&[ (tok(L_BRACKET), r"\["), (tok(R_BRACKET), r"\]"), (tok(WORD), r"[^\s\[\]]+"), (tok(WHITESPACE), r"\s+"), ]) .build(); lexer .tokenize(text) .into_iter() .map(|t| (t.len, kind(t.kind))) .scan(0usize, |start_offset, (len, kind)| { let s: String = text[*start_offset..*start_offset + len].into(); *start_offset += len; Some((kind, s)) }) .collect() } #[derive(Clone)] pub struct Parse { pub green_node: GreenNode, pub errors: Vec, } pub fn parse(text: &str) -> Parse { struct Parser { tokens: Vec<(SyntaxKind, String)>, builder: GreenNodeBuilder<'static>, errors: Vec, } #[derive(Debug)] enum QexpRes { Ok, Eof, RBracket, LBracket } impl Parser { fn parse(mut self) -> Parse { // Make sure that the root node covers all source self.builder.start_node(ROOT.into()); // Parse zero or more S-expressions loop { match self.word() { QexpRes::Eof => break, QexpRes::Ok => (), unmatched_bracket => { self.builder.start_node(ERROR.into()); self.bump(); // be sure to chug along in case of error self.builder.finish_node(); self.errors.push(format!("lone `{:?}`", unmatched_bracket)); } } } // eat remaining whitespace self.skip_ws(); self.builder.finish_node(); Parse { green_node: self.builder.finish(), errors: self.errors } } fn list(&mut self) { assert_eq!(self.current(), Some(L_BRACKET)); // Start the list node self.builder.start_node(LIST.into()); self.bump(); // '[' loop { match self.word() { QexpRes::Eof => { self.builder.start_node(ERROR.into()); self.errors.push("expected `]`".to_string()); self.builder.finish_node(); break; } QexpRes::RBracket => { self.bump(); break; } QexpRes::LBracket => { self.builder.start_node(ERROR.into()); self.errors.push("unexpected list".to_string()); self.bump(); self.builder.finish_node(); } QexpRes::Ok => (), } } // close the list node self.builder.finish_node(); } fn word(&mut self) -> QexpRes { // Eat leading whitespace self.skip_ws(); // Either a list, an atom, a closing paren, // or an eof. let t = match self.current() { None => return QexpRes::Eof, Some(R_BRACKET) => return QexpRes::RBracket, Some(L_BRACKET) => return QexpRes::LBracket, Some(t) => t, }; match t { WORD => { self.builder.start_node(ATOM.into()); self.bump(); self.skip_ws(); if Some(L_BRACKET) == self.current() { self.list(); } self.builder.finish_node(); } ERROR => self.bump(), _ => unreachable!(), } QexpRes::Ok } /// Advance one token, adding it to the current branch of the tree builder. fn bump(&mut self) { let (kind, text) = self.tokens.pop().unwrap(); self.builder.token(kind.into(), text.as_str()); } /// Peek at the first unprocessed token fn current(&self) -> Option { self.tokens.last().map(|(kind, _)| *kind) } fn skip_ws(&mut self) { while self.current() == Some(WHITESPACE) { self.bump() } } } let mut tokens = lex(text); tokens.reverse(); Parser { tokens, builder: GreenNodeBuilder::new(), errors: Vec::new() }.parse() } /// To work with the parse results we need a view into the /// green tree - the Syntax tree. /// It is also immutable, like a GreenNode, /// but it contains parent pointers, offsets, and /// has identity semantics. pub type SyntaxNode = rowan::SyntaxNode; #[allow(unused)] type SyntaxToken = rowan::SyntaxToken; #[allow(unused)] type SyntaxElement = rowan::NodeOrToken; impl Parse { pub fn syntax(&self) -> SyntaxNode { SyntaxNode::new_root(self.green_node.clone()) } } /// Let's check that the parser works as expected #[test] fn test_parser() { let text = "Inherit > mdDoc[something]"; let node = parse(text).syntax(); assert_eq!( format!("{:?}", node), "ROOT@0..26" ); assert_eq!(node.children().count(), 3); let children = node .descendants_with_tokens() .map(|child| format!("{:?}@{:?}", child.kind(), child.text_range())) .collect::>(); assert_eq!( children, vec![ "ROOT@0..26".to_string(), "ATOM@0..8".to_string(), "WORD@0..7".to_string(), "WHITESPACE@7..8".to_string(), // note, explicit whitespace! "ATOM@8..10".to_string(), "WORD@8..9".to_string(), "WHITESPACE@9..10".to_string(), "ATOM@10..26".to_string(), "WORD@10..15".to_string(), "LIST@15..26".to_string(), "L_BRACKET@15..16".to_string(), "ATOM@16..25".to_string(), "WORD@16..25".to_string(), "R_BRACKET@25..26".to_string() ] ); } #[derive(Debug, Clone)] pub struct Query { pub filters: Vec } #[derive(Debug, Clone)] pub struct Filter { pub selector: Selector, pub args: Option } #[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 } impl NixSyntaxRole { fn from_str(from: &str) -> Option { use NixSyntaxRole::*; Some(match from { "Argument" => Argument, "Function" => Function, "Attribute" => Attribute, _ => return None }) } } impl Parse { pub fn to_query(&self) -> ParseResult { fn parse_operator(node: SyntaxNode) -> ParseResult { match node.to_string().as_str() { "remove" => Ok(Operator::Remove), "keep" => Ok(Operator::Keep), unknown => Err(vec![(node.text_range(), format!("unknown operator {unknown}"))]) } } fn parse_args(node: SyntaxNode) -> ParseResult> { 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::>>(); match args { Err(e) => Err(e), Ok(ops) if ops.len() == 1 => Ok(Some(ops.into_iter().exactly_one().unwrap())), _ => Err(vec![(node.text_range(), "cannot have multiple operators at the same node (for now)".to_string())]) } } else { Ok(None) } } fn parse_filter(node: SyntaxNode) -> ParseResult { 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); let (errors, filters): (Vec<_>, Vec<_>) = root.children() .map(parse_filter) .partition_map(From::from); if errors.len() == 0 { Ok(Query { filters }) } else { Err(errors.concat()) } } } pub fn print_query_errors(content: &str, mut errors: Vec) { errors.sort_by_key(|(range, _)| range.start()); let mut errors_iter = errors.into_iter().peekable(); eprintln!("Errors in Query:"); let mut line_start = 0usize; for line in content.split('\n') { eprintln!("| {line}"); let line_end: usize = line_start + line.len(); while Some(true) == errors_iter.peek().map(|e| Into::::into(e.0.end()) < line_end) { match errors_iter.next() { None => return, Some((range, msg)) => { eprintln!( "| {pad}{line} {msg}", pad = " ".repeat(Into::::into(range.start()).checked_sub(line_start).unwrap_or(0)), line = "^".repeat(range.len().into()) ) } } } line_start += line_end + 1; } }