Use a hand-coded parser for keymap context predicates

This commit is contained in:
Max Brunsfeld 2023-01-16 14:41:24 -08:00
parent 96186a3dae
commit f62d13de21
17 changed files with 240 additions and 1766 deletions

View file

@ -1,11 +1,5 @@
use anyhow::anyhow;
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet};
use tree_sitter::{Language, Node, Parser};
extern "C" {
fn tree_sitter_context_predicate() -> Language;
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct KeymapContext {
@ -35,70 +29,13 @@ pub enum KeymapContextPredicate {
}
impl KeymapContextPredicate {
pub fn parse(source: &str) -> anyhow::Result<Self> {
let mut parser = Parser::new();
let language = unsafe { tree_sitter_context_predicate() };
parser.set_language(language).unwrap();
let source = source.as_bytes();
let tree = parser.parse(source, None).unwrap();
Self::from_node(tree.root_node(), source)
}
fn from_node(node: Node, source: &[u8]) -> anyhow::Result<Self> {
let parse_error = "error parsing context predicate";
let kind = node.kind();
match kind {
"source" => Self::from_node(node.child(0).ok_or_else(|| anyhow!(parse_error))?, source),
"identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())),
"not" => {
let child = Self::from_node(
node.child_by_field_name("expression")
.ok_or_else(|| anyhow!(parse_error))?,
source,
)?;
Ok(Self::Not(Box::new(child)))
}
"and" | "or" => {
let left = Box::new(Self::from_node(
node.child_by_field_name("left")
.ok_or_else(|| anyhow!(parse_error))?,
source,
)?);
let right = Box::new(Self::from_node(
node.child_by_field_name("right")
.ok_or_else(|| anyhow!(parse_error))?,
source,
)?);
if kind == "and" {
Ok(Self::And(left, right))
} else {
Ok(Self::Or(left, right))
}
}
"equal" | "not_equal" => {
let left = node
.child_by_field_name("left")
.ok_or_else(|| anyhow!(parse_error))?
.utf8_text(source)?
.into();
let right = node
.child_by_field_name("right")
.ok_or_else(|| anyhow!(parse_error))?
.utf8_text(source)?
.into();
if kind == "equal" {
Ok(Self::Equal(left, right))
} else {
Ok(Self::NotEqual(left, right))
}
}
"parenthesized" => Self::from_node(
node.child_by_field_name("expression")
.ok_or_else(|| anyhow!(parse_error))?,
source,
),
_ => Err(anyhow!(parse_error)),
pub fn parse(source: &str) -> Result<Self> {
let source = Self::skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}"))
} else {
Ok(predicate)
}
}
@ -120,4 +57,236 @@ impl KeymapContextPredicate {
Self::Or(left, right) => left.eval(context) || right.eval(context),
}
}
fn parse_expr(
mut source: &str,
min_precedence: u32,
) -> anyhow::Result<(KeymapContextPredicate, &str)> {
type Op =
fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
let (mut predicate, rest) = Self::parse_primary(source)?;
source = rest;
'parse: loop {
for (operator, precedence, constructor) in [
("&&", PRECEDENCE_AND, KeymapContextPredicate::new_and as Op),
("||", PRECEDENCE_OR, KeymapContextPredicate::new_or as Op),
("==", PRECEDENCE_EQ, KeymapContextPredicate::new_eq as Op),
("!=", PRECEDENCE_EQ, KeymapContextPredicate::new_neq as Op),
] {
if source.starts_with(operator) && precedence >= min_precedence {
source = Self::skip_whitespace(&source[operator.len()..]);
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
predicate = constructor(predicate, right)?;
source = rest;
continue 'parse;
}
}
break;
}
Ok((predicate, source))
}
fn parse_primary(mut source: &str) -> anyhow::Result<(KeymapContextPredicate, &str)> {
let next = source
.chars()
.next()
.ok_or_else(|| anyhow!("unexpected eof"))?;
match next {
'(' => {
source = Self::skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
source = Self::skip_whitespace(&rest[1..]);
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
let source = Self::skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
}
_ if next.is_alphanumeric() || next == '_' => {
let len = source
.find(|c: char| !(c.is_alphanumeric() || c == '_'))
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
source = Self::skip_whitespace(rest);
Ok((
KeymapContextPredicate::Identifier(identifier.into()),
source,
))
}
_ => Err(anyhow!("unexpected character {next:?}")),
}
}
fn skip_whitespace(source: &str) -> &str {
let len = source
.find(|c: char| !c.is_whitespace())
.unwrap_or(source.len());
&source[len..]
}
fn new_or(self, other: Self) -> Result<Self> {
Ok(Self::Or(Box::new(self), Box::new(other)))
}
fn new_and(self, other: Self) -> Result<Self> {
Ok(Self::And(Box::new(self), Box::new(other)))
}
fn new_eq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::Equal(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
fn new_neq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::NotEqual(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
}
const PRECEDENCE_OR: u32 = 1;
const PRECEDENCE_AND: u32 = 2;
const PRECEDENCE_EQ: u32 = 3;
const PRECEDENCE_NOT: u32 = 4;
#[cfg(test)]
mod tests {
use super::KeymapContextPredicate::{self, *};
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
KeymapContextPredicate::parse("abc12").unwrap(),
Identifier("abc12".into())
);
assert_eq!(
KeymapContextPredicate::parse("_1a").unwrap(),
Identifier("_1a".into())
);
}
#[test]
fn test_parse_negations() {
assert_eq!(
KeymapContextPredicate::parse("!abc").unwrap(),
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
KeymapContextPredicate::parse(" ! ! abc").unwrap(),
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
#[test]
fn test_parse_equality_operators() {
assert_eq!(
KeymapContextPredicate::parse("a == b").unwrap(),
Equal("a".into(), "b".into())
);
assert_eq!(
KeymapContextPredicate::parse("c!=d").unwrap(),
NotEqual("c".into(), "d".into())
);
assert_eq!(
KeymapContextPredicate::parse("c == !d")
.unwrap_err()
.to_string(),
"operands must be identifiers"
);
}
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
KeymapContextPredicate::parse("a || b").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
KeymapContextPredicate::parse("a || !b && c").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(And(
Box::new(Not(Box::new(Identifier("b".into())))),
Box::new(Identifier("c".into()))
))
)
);
assert_eq!(
KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
Or(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(And(
Box::new(Identifier("c".into())),
Box::new(Identifier("d".into()))
))
)
);
assert_eq!(
KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
Or(
Box::new(And(
Box::new(Equal("a".into(), "b".into())),
Box::new(Identifier("c".into()))
)),
Box::new(And(
Box::new(Equal("d".into(), "e".into())),
Box::new(Identifier("f".into()))
))
)
);
assert_eq!(
KeymapContextPredicate::parse("a && b && c && d").unwrap(),
And(
Box::new(And(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(Identifier("c".into())),
)),
Box::new(Identifier("d".into()))
),
);
}
#[test]
fn test_parse_parenthesized_expressions() {
assert_eq!(
KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
And(
Box::new(Identifier("a".into())),
Box::new(Or(
Box::new(Equal("b".into(), "c".into())),
Box::new(NotEqual("d".into(), "e".into())),
)),
),
);
assert_eq!(
KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
)
);
}
}