use std::str::FromStr;
use super::ast::*;
use super::template_string::TemplateString;
use super::lex::*;
use std::collections::BTreeMap;
use lalrpop_util::ErrorRecovery;
use ordered_float::NotNan;
use crate::diagnostic::span;
use crate::path::{PathPrefix, OwnedTargetPath, OwnedValuePath, OwnedSegment};
use crate::parser::ast::Expr;
use crate::value::KeyString;
grammar<'err, 'input>(input: &'input str);
extern {
type Location = usize;
type Error = Error;
enum Tok<'input> {
"identifier" => Token::Identifier(<&'input str>),
"path field" => Token::PathField(<&'input str>),
"string literal" => Token::StringLiteral(<StringLiteralToken<&'input str>>),
"raw string literal" => Token::RawStringLiteral(<RawStringLiteralToken<&'input str>>),
"integer literal" => Token::IntegerLiteral(<i64>),
"float literal" => Token::FloatLiteral(<NotNan<f64>>),
"regex literal" => Token::RegexLiteral(<&'input str>),
"timestamp literal" => Token::TimestampLiteral(<&'input str>),
"function call" => Token::FunctionCall(<&'input str>),
"invalid token" => Token::InvalidToken(<char>),
"reserved identifier" => Token::ReservedIdentifier(<&'input str>),
LQuery => Token::LQuery,
RQuery => Token::RQuery,
"if" => Token::If,
"else" => Token::Else,
"null" => Token::Null,
"true" => Token::True,
"false" => Token::False,
"abort" => Token::Abort,
"return" => Token::Return,
";" => Token::SemiColon,
"\n" => Token::Newline,
"|" => Token::Pipe,
"=" => Token::Equals,
"|=" => Token::MergeEquals,
"," => Token::Comma,
"_" => Token::Underscore,
":" => Token::Colon,
"." => Token::Dot,
"&" => Token::Ampersand,
"!" => Token::Bang,
"->" => Token::Arrow,
"%" => Token::Percent,
"+" => Token::Operator("+"),
"*" => Token::Operator("*"),
"-" => Token::Operator("-"),
"/" => Token::Operator("/"),
"<" => Token::Operator("<"),
"<=" => Token::Operator("<="),
">" => Token::Operator(">"),
">=" => Token::Operator(">="),
"==" => Token::Operator("=="),
"!=" => Token::Operator("!="),
"|" => Token::Operator("|"),
"&&" => Token::Operator("&&"),
"||" => Token::Operator("||"),
"??" => Token::Operator("??"),
"[" => Token::LBracket,
"{" => Token::LBrace,
"(" => Token::LParen,
"]" => Token::RBracket,
"}" => Token::RBrace,
")" => Token::RParen,
}
}
// -----------------------------------------------------------------------------
// parser entry points
// -----------------------------------------------------------------------------
// The main entrypoint into a VRL program.
//
// A program consists of one or more expressions.
pub Program: Program = NonterminalNewline* <RootExprs> => Program(<>);
// -----------------------------------------------------------------------------
// root expressions
// -----------------------------------------------------------------------------
#[inline]
RootExprs: Vec<Node<RootExpr>> = {
RootExpr => vec![<>],
(<RootExpr> EndOfExpression)*,
<v:(<RootExpr> EndOfExpression)+> <e:RootExpr> => {
let mut v = v;
v.push(e);
v
},
};
RootExpr: Node<RootExpr> = {
Expr => Node::new(<>.span(), RootExpr::Expr(<>)),
// Root expressions are allowed to fail. The parser will continue with the
// next expression in the program.
Sp<!> => {
let Node { span, node } = <>;
let ErrorRecovery { error: source, dropped_tokens } = node;
let reserved_keyword = dropped_tokens
.first()
.cloned()
.into_iter()
.find_map(|(start, t, end)| match t {
Token::ReservedIdentifier(s) => Some((start, s.to_owned(), end)),
_ => None,
});
if let Some((start, keyword, end)) = reserved_keyword {
let error = Error::ReservedKeyword {
start,
keyword: keyword,
end,
};
return Node::new(span, RootExpr::Error(error));
}
let source = source
.map_token(|t| t.map(|s| s.to_owned()))
.map_error(|e| e.to_string());
let dropped_tokens = dropped_tokens
.into_iter()
.map(|(l, t, r)| (l, t.map(|s| s.to_owned()), r))
.collect();
let error = Error::ParseError {
span,
source,
dropped_tokens,
};
Node::new(span, RootExpr::Error(error))
},
};
// -----------------------------------------------------------------------------
// expressions
// -----------------------------------------------------------------------------
#[inline]
Exprs: Vec<Node<Expr>> = {
Expr => vec![<>],
<v:(<Expr> EndOfExpression)+> <e:(<Expr>)?> => match e {
None => v,
Some(e) => {
let mut v = v;
v.push(e);
v
}
}
};
Expr: Node<Expr> = {
Sp<IfStatement> => Node::new(<>.span(), Expr::IfStatement(<>)),
Sp<AbortExpr>,
Sp<ReturnExpr>,
AssignmentExpr,
};
EndOfExpression: () = {
"\n"+ => (),
";" "\n"* => (),
};
NonterminalNewline: () = "\n";
Ident: Ident = "identifier" => Ident(<>.to_owned());
AbortExpr: Expr = {
Sp<"abort"> => Expr::Abort(<>.map(|_| Abort { message: None })),
<n: Sp<"abort">> <message: Expr> => Expr::Abort(n.map(|_| Abort { message: Some(Box::new(message.clone())) })),
}
ReturnExpr: Expr = {
<ret: Sp<"return">> <expr: Expr> => {
// let span = span(ret.span().start(), expr.span().end());
// let ret = Return(Node::new(span, Return(expr)));
// Expr::Return(ret)
Expr::Return(
ret.map(|_| Return { expr: Box::new(expr.clone()) })
)
},
};
// An identifier that is allowed to include reserved keywords.
#[inline]
AnyIdent: Ident = {
"identifier" => Ident(<>.to_owned()),
"reserved identifier" => Ident(<>.to_owned()),
"if" => Ident("if".to_owned()),
"else" => Ident("else".to_owned()),
"null" => Ident("null".to_owned()),
"true" => Ident("true".to_owned()),
"false" => Ident("false".to_owned()),
"abort" => Ident("abort".to_owned()),
"return" => Ident("return".to_owned()),
};
// -----------------------------------------------------------------------------
// assignment
// -----------------------------------------------------------------------------
AssignmentExpr: Node<Expr> = {
Assignment => Node::new(<>.span(), Expr::Assignment(<>)),
ArithmeticExpr,
};
#[inline]
Assignment: Node<Assignment> = {
Sp<AssignmentSingle>,
Sp<AssignmentInfallible>,
};
AssignmentOp: AssignmentOp = {
"=" => AssignmentOp::Assign,
"|=" => AssignmentOp::Merge,
}
#[inline]
AssignmentSingle: Assignment = {
<target: Sp<AssignmentTarget>>
<op: AssignmentOp>
NonterminalNewline*
<expr: Box<Expr>> => Assignment::Single { target, op, expr },
}
#[inline]
AssignmentInfallible: Assignment = {
<ok: Sp<AssignmentTarget>> ","
<err: Sp<AssignmentTarget>>
<op: AssignmentOp>
NonterminalNewline*
<expr: Box<Expr>> => Assignment::Infallible{ ok, err, op, expr},
}
#[inline]
AssignmentTarget: AssignmentTarget = {
"_" => AssignmentTarget::Noop,
Query => AssignmentTarget::Query(<>),
Ident => AssignmentTarget::Internal(<>, None),
};
// -----------------------------------------------------------------------------
// arithmetic
// -----------------------------------------------------------------------------
ArithmeticExpr: Node<Expr> = {
Sp<ErrorCoalesce>,
};
ErrorCoalesce: Expr = {
Op<ErrorCoalesce, "??", Logical>,
Logical,
};
Logical: Expr = {
Op<Logical, "||", Equal>,
Op<Logical, "&&", Equal>,
Equal,
};
Equal: Expr = {
Op<Equal, "!=", Compare>,
Op<Equal, "==", Compare>,
Compare,
};
Compare: Expr = {
Op<Compare, ">=", Merge>,
Op<Compare, ">", Merge>,
Op<Compare, "<=", Merge>,
Op<Compare, "<", Merge>,
Merge,
};
Merge: Expr = {
Op<Merge, "|", Add>,
Add,
}
Add: Expr = {
Op<Add, "+", Factor>,
Op<Add, "-", Factor>,
Factor,
};
Factor: Expr = {
Op<Factor, "*", Not>,
Op<Factor, "/", Not>,
Not,
};
Not: Expr = {
<bang: Sp<"!">> <expr: Box<Sp<Not>>> => {
let span = span(bang.span().start(), expr.span().end());
let not = Unary::Not(Node::new(span, Not(bang.map(|_| ()), expr)));
Expr::Unary(Node::new(span, not))
},
Term,
};
#[inline]
Term: Expr = {
Sp<Literal> => Expr::Literal(<>),
Sp<Container> => Expr::Container(<>),
Sp<Query> => Expr::Query(<>),
Sp<FunctionCall> => Expr::FunctionCall(<>),
Sp<Ident> => Expr::Variable(<>),
};
// -----------------------------------------------------------------------------
// query
// -----------------------------------------------------------------------------
pub Query: Query = {
LQuery <Sp<".">> RQuery => {
let span = <>.span();
let target = Node::new(span, QueryTarget::External(PathPrefix::Event));
let path = Node::new(span, OwnedValuePath::root());
Query { target, path }
},
LQuery <Sp<"%">> RQuery => {
let span = <>.span();
let target = Node::new(span, QueryTarget::External(PathPrefix::Metadata));
let path = Node::new(span, OwnedValuePath::root());
Query { target, path }
},
LQuery <target: Sp<QueryTarget>> <path: Sp<Path>> RQuery => Query { target, path: path.map(OwnedValuePath::from) },
};
#[inline]
QueryTarget: QueryTarget = {
Ident => QueryTarget::Internal(<>),
"." => QueryTarget::External(PathPrefix::Event),
"%" => QueryTarget::External(PathPrefix::Metadata),
FunctionCall => QueryTarget::FunctionCall(<>),
Container => QueryTarget::Container(<>),
};
// -----------------------------------------------------------------------------
// path
// -----------------------------------------------------------------------------
pub Field: KeyString = {
AnyIdent => <>.to_string().into(),
PathField => <>.to_string().into(),
String => <>.to_string().into(),
}
#[inline]
Path: OwnedValuePath = PathSegment+ => OwnedValuePath::from(<>);
#[inline]
PathSegment: OwnedSegment = {
"."? <Field> => OwnedSegment::field(&<>),
"[" <Integer> "]" => OwnedSegment::index(<> as isize),
};
#[inline]
PathField: Ident = "path field" => Ident(<>.to_owned());
// -----------------------------------------------------------------------------
// function call
// -----------------------------------------------------------------------------
FunctionCall: FunctionCall = {
<ident: Sp<"function call">> <abort_on_error: "!"?> "("
NonterminalNewline*
<arguments: CommaMultiline<Sp<FunctionArgument>>?>
")" <closure: Sp<FunctionClosure>?> => {
let ident = ident.map(|s| Ident(s.to_owned()));
let abort_on_error = abort_on_error.is_some();
let arguments = arguments.unwrap_or_default();
FunctionCall { ident, abort_on_error, arguments, closure }
},
};
#[inline]
FunctionArgument: FunctionArgument = {
<ident: (<Sp<AnyIdent>> ":")?> <expr: ArithmeticExpr> => FunctionArgument { <> },
};
#[inline]
FunctionClosure: FunctionClosure = {
"->" <variables: ClosureVariables> NonterminalNewline* <block: Sp<Block>> =>
FunctionClosure { variables, block }
};
#[inline]
ClosureVariables: Vec<Node<Ident>> = {
"||" => vec![],
"|" <variables: CommaMultiline<ClosureVariable>?> "|" =>
variables.unwrap_or_default()
};
#[inline]
ClosureVariable: Node<Ident> = {
Sp<Ident> => <>,
Sp<"_"> => <>.map(|s| Ident("".to_owned())),
};
// -----------------------------------------------------------------------------
// if statement
// -----------------------------------------------------------------------------
IfStatement: IfStatement =
"if"
<predicate: Sp<Predicate>>
NonterminalNewline*
<consequent: Sp<Block>>
<mut alternatives: (Sp<ElseIf>)*>
<alternative: ("else" NonterminalNewline* <Sp<Block>>)?> => {
let mut alternative = alternative;
alternatives.reverse();
for Node { span, mut node } in alternatives {
node.else_node = alternative;
let node = Node::new(span, Expr::IfStatement(Node::new(span, node)));
alternative = Some(Node::new(node.span(), Block(vec![node])));
}
IfStatement { predicate, if_node: consequent, else_node: alternative }
};
#[inline]
ElseIf: IfStatement =
"else" NonterminalNewline* "if"
<predicate: Sp<Predicate>>
NonterminalNewline*
<consequent: Sp<Block>> => {
IfStatement { predicate, if_node: consequent, else_node: None }
};
#[inline]
StatementSeparator: () = {
NonterminalNewline, ";"
};
Predicate: Predicate = {
Box<ArithmeticExpr> => Predicate::One(<>),
"(" NonterminalNewline* <v:(<AssignmentExpr> StatementSeparator+)+> <e:(<AssignmentExpr>)?> ")" => {
let expressions = match e {
None => v,
Some(e) => {
let mut v = v;
v.push(e);
v
}
};
Predicate::Many(expressions)
},
};
// -----------------------------------------------------------------------------
// containers
// -----------------------------------------------------------------------------
Container: Container = {
Box<Sp<Group>> => Container::Group(<>),
Sp<Block> => Container::Block(<>),
Sp<Array> => Container::Array(<>),
Sp<Object> => Container::Object(<>),
};
#[inline]
Group: Group = "(" <AssignmentExpr> ")" => Group(<>);
Block: Block = "{" NonterminalNewline* <Exprs> "}" => Block(<>);
#[inline]
Array: Array = {
"[" NonterminalNewline* "]" => Array(vec![]),
"[" NonterminalNewline* <CommaMultiline<ArithmeticExpr>> "]" => Array(<>),
};
#[inline]
Object: Object = {
"{" NonterminalNewline* "}" => Object(BTreeMap::default()),
"{" NonterminalNewline* <CommaMultiline<(<Sp<ObjectKey>> ":" <ArithmeticExpr>)>> "}" => {
let object =<>.into_iter().collect::<BTreeMap<_, _>>();
Object(object)
},
};
#[inline]
ObjectKey: String = "string literal" => <>.unescape();
// -----------------------------------------------------------------------------
// literals
// -----------------------------------------------------------------------------
pub Literal: Literal = {
String => Literal::String(<>),
RawString => Literal::RawString(<>),
Integer => Literal::Integer(<>),
Float => Literal::Float(<>),
Boolean => Literal::Boolean(<>),
Null => Literal::Null,
Regex => Literal::Regex(<>),
Timestamp => Literal::Timestamp(<>),
};
String: TemplateString = Sp<"string literal"> => {
let Node { span, node } = <>;
node.template(span)
};
RawString: String = "raw string literal" => <>.unescape();
Integer: i64 = "integer literal";
Float: NotNan<f64> = "float literal";
Boolean: bool = { "true" => true, "false" => false };
Null: () = "null";
Regex: String = "regex literal" => <>.replace("\\'", "'");
Timestamp: String = "timestamp literal" => <>.replace("\\'", "'");
// -----------------------------------------------------------------------------
// macros
// -----------------------------------------------------------------------------
Box<T>: Box<T> = T => Box::new(<>);
Sp<T>: Node<T> = <l: @L> <rule: T> <r: @R> => Node::new(span(l, r), rule);
Op<L, Code, R>: Expr = Sp<(<Sp<L>> <Sp<Code>> NonterminalNewline* <Sp<R>>)> => {
let op = <>.map(|(lhs, code, rhs)| {
let (span, code) = code.take();
let op = match code {
Token::Operator(s) => Opcode::from_str(s).unwrap(),
_ => panic!("must always be an operator"),
};
Op(Box::new(lhs), Node::new(span, op), Box::new(rhs))
});
Expr::Op(op)
};
CommaMultiline<T>: Vec<T> = {
<T> NonterminalNewline* => vec![<>],
<v:(<T> "," NonterminalNewline*)+> <e:(<T> NonterminalNewline*)?> => match e {
None => v,
Some(e) => {
let mut v = v;
v.push(e);
v
}
}
};