gitrevset 0.2.0

A domain-specific-language to select commits in a git repo. Similar to Mercurial's revset.
Documentation
use crate::ast::Expr;

// Binary Operator Precedence (highest to lowest):
// BinOp1: : ..
// BinOp2: & and % -
// BinOp3: | + or

grammar;

pub Expr = Expr4;

Expr4: Expr = {
    <a:Expr4> <op:BinOp3> <b:Expr3> => Expr::Fn(op.into(), vec![a, b]),
    Expr3,
};

BinOp3: &'static str = {
    "|" => "union",
    "+" => "union",
    "or" => "union",
};

Expr3: Expr = {
    <a:Expr3> <op:BinOp2> <b:Expr2> => Expr::Fn(op.into(), vec![a, b]),
    Expr2,
};

BinOp2: &'static str = {
    "&" => "intersection",
    "and" => "intersection",
    "-" => "difference",
    "%" => "only",
};

Expr2: Expr = {
    <a:Expr2> <op:BinOp1> <b:Expr15> => Expr::Fn(op.into(), vec![a, b]),
    Expr15,
};

BinOp1: &'static str = {
    ":" => "range",
    ".." => "range",
};

Expr15: Expr = {
    <pre:(<Prefix>)*> <e:Expr1> => {
        let mut e = e;
        for v in pre { e = Expr::Fn(v.into(), vec![e]); }
        e
    },
}

Expr1: Expr = {
    <e:Expr0> <post:(<Postfix>)*> => {
        let mut e = e;
        for v in post { e = Expr::Fn(v.into(), vec![e]); }
        e
    },
}

Expr0: Expr = {
    Symbol2 => Expr::Name(<>),
    <f:Symbol1> "(" <args:(Expr ",")*> <last:Expr?> ")" => {
        // Function call.
        let mut arg_list: Vec<Expr> = args.into_iter().map(|(e, _)| e).collect();
        if let Some(last_arg) = last { arg_list.push(last_arg); }
        Expr::Fn(f.into(), arg_list)
    },
    "(" <Expr> ")"
};

Prefix: &'static str = {
    "!" => "negate",
    "not " => "negate",
    "::" => "ancestors",
}

Postfix: &'static str = {
    "::" => "descendants",
    "^" => "parents",
}

Symbol2: String = {
    Symbol1 => <>,
    <escaped:r"\x22([^\x22\x5c]|\x5c.)*\x22"> => {
        // Escaped string.
        let mut result = String::with_capacity(escaped.len());
        let mut prev = '_';
        for ch in escaped[1..escaped.len()-1].chars() {
            match (prev, ch) {
                ('\\', 'n') => result.push('\n'),
                ('\\', _) => result.push(ch),
                (_, '\\') => (),
                (_, _) => result.push(ch),
            }
            prev = ch;
        }
        result
    },
}

Symbol1: String = {
    r"[a-zA-Z0-9/_$@.]+" => <>.to_string(),
}