363 lines
12 KiB
Rust
363 lines
12 KiB
Rust
pub mod ast;
|
|
|
|
use self::ast::*;
|
|
use pest::Parser;
|
|
|
|
#[derive(Parser)]
|
|
#[grammar = "vm/parsing/grammar.pest"]
|
|
struct ExpressionParser;
|
|
|
|
pub fn parse_relation(expression: &str) -> Result<ast::BooleanExpression, String> {
|
|
let parse_result = ExpressionParser::parse(Rule::relation, expression);
|
|
|
|
if let Err(e) = parse_result {
|
|
return Err(parse_error_to_human(expression, e));
|
|
}
|
|
|
|
let main_expr = parse_result
|
|
.unwrap()
|
|
.next()
|
|
.unwrap()
|
|
.into_inner()
|
|
.next()
|
|
.unwrap();
|
|
let main_expr = match main_expr.as_rule() {
|
|
Rule::boolean_expression => parse_boolean_expression(main_expr),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
Ok(main_expr)
|
|
}
|
|
|
|
pub fn parse_arithmetic(expression: &str) -> Result<ast::ArithmeticExpression, String> {
|
|
let parse_result = ExpressionParser::parse(Rule::conjecture, expression);
|
|
|
|
if let Err(e) = parse_result {
|
|
return Err(parse_error_to_human(expression, e));
|
|
}
|
|
|
|
let main_expr = parse_result
|
|
.unwrap()
|
|
.next()
|
|
.unwrap()
|
|
.into_inner()
|
|
.next()
|
|
.unwrap();
|
|
let main_expr = match main_expr.as_rule() {
|
|
Rule::arithmetic_expression => parse_arithmetic_expression(main_expr),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
Ok(main_expr)
|
|
}
|
|
|
|
fn parse_error_to_human(expression: &str, error: pest::error::Error<Rule>) -> String {
|
|
let mut error_msg = String::new();
|
|
error_msg.push_str("Invalid expression:\n");
|
|
match error.line_col {
|
|
pest::error::LineColLocation::Pos((line, col)) => {
|
|
with_caret(error.line(), line, col, "In ", &mut error_msg);
|
|
error_msg.push('\n');
|
|
}
|
|
pest::error::LineColLocation::Span((sline, scol), (eline, ecol)) => {
|
|
let (start_line, end_line) = {
|
|
let mut lines = expression.lines();
|
|
(lines.nth(sline).unwrap(), lines.nth(eline - sline).unwrap())
|
|
};
|
|
with_caret(start_line, sline, scol, "Starting at ", &mut error_msg);
|
|
with_caret(end_line, eline, ecol, "until ", &mut error_msg);
|
|
error_msg.push('\n');
|
|
}
|
|
}
|
|
match error.variant {
|
|
pest::error::ErrorVariant::ParsingError {
|
|
positives,
|
|
negatives,
|
|
} => {
|
|
match positives.len() {
|
|
0 => {}
|
|
1 => {
|
|
error_msg.push_str("Expected ");
|
|
error_msg.push_str(rule_as_text(&positives[0]));
|
|
error_msg.push('.');
|
|
}
|
|
2 => {
|
|
error_msg.push_str("Expected either ");
|
|
error_msg.push_str(rule_as_text(&positives[0]));
|
|
error_msg.push_str("or ");
|
|
error_msg.push_str(rule_as_text(&positives[1]));
|
|
error_msg.push('.');
|
|
}
|
|
_ => {
|
|
error_msg.push_str("Expected one of ");
|
|
for rule in &positives[..positives.len() - 1] {
|
|
error_msg.push_str(rule_as_text(rule));
|
|
error_msg.push_str(", ");
|
|
}
|
|
error_msg.push_str("or ");
|
|
error_msg.push_str(rule_as_text(&positives.last().unwrap()));
|
|
error_msg.push('.');
|
|
}
|
|
}
|
|
match negatives.len() {
|
|
0 => {}
|
|
1 => {
|
|
error_msg.push_str("Did not expect ");
|
|
error_msg.push_str(rule_as_text(&negatives[0]));
|
|
error_msg.push('.');
|
|
}
|
|
_ => {
|
|
error_msg.push_str("Did not expect any of ");
|
|
for rule in &negatives[..negatives.len() - 1] {
|
|
error_msg.push_str(rule_as_text(rule));
|
|
error_msg.push_str(", ");
|
|
}
|
|
error_msg.push_str("nor ");
|
|
error_msg.push_str(rule_as_text(&negatives.last().unwrap()));
|
|
error_msg.push('.');
|
|
}
|
|
}
|
|
}
|
|
pest::error::ErrorVariant::CustomError { message } => {
|
|
error_msg.push_str(&message);
|
|
}
|
|
}
|
|
return error_msg;
|
|
}
|
|
|
|
fn parse_boolean_expression(rule: pest::iterators::Pair<Rule>) -> BooleanExpression {
|
|
let inner = rule.into_inner().next().unwrap();
|
|
match inner.as_rule() {
|
|
Rule::binary_boolean_conjunction => BooleanExpression::BinaryBooleanConjunction(Box::new(
|
|
parse_binary_boolean_conjunction(inner),
|
|
)),
|
|
Rule::unary_boolean_conjunction => BooleanExpression::UnaryBooleanConjunction(Box::new(
|
|
parse_unary_boolean_conjunction(inner),
|
|
)),
|
|
Rule::comparison_expression => {
|
|
BooleanExpression::ComparisonConjunction(Box::new(parse_comparison_expression(inner)))
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_comparison_expression(rule: pest::iterators::Pair<Rule>) -> ComparisonConjunction {
|
|
let mut inner = rule.into_inner();
|
|
let operator = parse_comparison_operator(inner.next().unwrap());
|
|
let left_operand = parse_arithmetic_operand(inner.next().unwrap());
|
|
let right_operand = parse_arithmetic_operand(inner.next().unwrap());
|
|
let conjunction = ComparisonConjunction {
|
|
operator,
|
|
left_operand,
|
|
right_operand,
|
|
};
|
|
conjunction
|
|
}
|
|
|
|
fn parse_comparison_operator(rule: pest::iterators::Pair<Rule>) -> ComparisonOperator {
|
|
match rule.as_str().trim() {
|
|
">=" => ComparisonOperator::GreaterOrEqual,
|
|
"<=" => ComparisonOperator::LessOrEqual,
|
|
">" => ComparisonOperator::GreaterThan,
|
|
"<" => ComparisonOperator::LessThan,
|
|
"=" => ComparisonOperator::Equal,
|
|
"!=" => ComparisonOperator::NotEqual,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_binary_arithmetic_conjunction(
|
|
rule: pest::iterators::Pair<Rule>,
|
|
) -> BinaryArithmeticConjunction {
|
|
let mut inner = rule.into_inner();
|
|
let operator = parse_binary_arithmetic_operator(inner.next().unwrap());
|
|
let left_operand = parse_arithmetic_operand(inner.next().unwrap());
|
|
let right_operand = parse_arithmetic_operand(inner.next().unwrap());
|
|
let conjunction = BinaryArithmeticConjunction {
|
|
operator,
|
|
left_operand,
|
|
right_operand,
|
|
};
|
|
conjunction
|
|
}
|
|
|
|
fn parse_arithmetic_operand(rule: pest::iterators::Pair<Rule>) -> ArithmeticOperand {
|
|
let inner = rule.into_inner().next().unwrap();
|
|
match inner.as_rule() {
|
|
Rule::number_literal => {
|
|
let inner = inner.as_str().trim().parse::<i64>().unwrap();
|
|
ArithmeticOperand::Literal(inner)
|
|
}
|
|
Rule::arithmetic_expression => {
|
|
ArithmeticOperand::Expression(Box::new(parse_arithmetic_expression(inner)))
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_arithmetic_expression(rule: pest::iterators::Pair<Rule>) -> ArithmeticExpression {
|
|
let inner = rule.into_inner().next().unwrap();
|
|
match inner.as_rule() {
|
|
Rule::variable => ArithmeticExpression::Variable(parse_variable(inner)),
|
|
Rule::unary_arithmetic_conjunction => ArithmeticExpression::UnaryArithmeticConjunction(
|
|
Box::new(parse_unary_arithmetic_conjunction(inner)),
|
|
),
|
|
Rule::binary_arithmetic_conjunction => ArithmeticExpression::BinaryArithmeticConjunction(
|
|
Box::new(parse_binary_arithmetic_conjunction(inner)),
|
|
),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_unary_arithmetic_conjunction(
|
|
rule: pest::iterators::Pair<Rule>,
|
|
) -> UnaryArithmeticConjunction {
|
|
let mut inner = rule.into_inner();
|
|
let operator = parse_unary_arithmetic_operator(inner.next().unwrap());
|
|
let operand = parse_arithmetic_operand(inner.next().unwrap());
|
|
let conjunction = UnaryArithmeticConjunction { operator, operand };
|
|
conjunction
|
|
}
|
|
|
|
fn parse_unary_arithmetic_operator(
|
|
rule: pest::iterators::Pair<Rule>,
|
|
) -> ast::UnaryArithmeticOperator {
|
|
match rule.as_str().trim() {
|
|
"neg" => ast::UnaryArithmeticOperator::Negative,
|
|
"ham" => ast::UnaryArithmeticOperator::Ham,
|
|
"sqrt" => ast::UnaryArithmeticOperator::Sqrt,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_variable(rule: pest::iterators::Pair<Rule>) -> ast::Variable {
|
|
match rule.as_str().trim() {
|
|
"x" => ast::Variable::X,
|
|
"y" => ast::Variable::Y,
|
|
"n" => ast::Variable::N,
|
|
"p" => ast::Variable::P,
|
|
"k" => ast::Variable::K,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_binary_arithmetic_operator(rule: pest::iterators::Pair<Rule>) -> BinaryArithmeticOperator {
|
|
match rule.as_str().trim() {
|
|
"*" => BinaryArithmeticOperator::Times,
|
|
"/" => BinaryArithmeticOperator::Divide,
|
|
"+" => BinaryArithmeticOperator::Plus,
|
|
"-" => BinaryArithmeticOperator::Minus,
|
|
"^" => BinaryArithmeticOperator::Xor,
|
|
"pow" => BinaryArithmeticOperator::Pow,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_unary_boolean_conjunction(rule: pest::iterators::Pair<Rule>) -> UnaryBooleanConjunction {
|
|
let mut inner = rule.into_inner();
|
|
let operator = parse_unary_boolean_operator(inner.next().unwrap());
|
|
let operand = parse_boolean_expression(inner.next().unwrap());
|
|
let conjunction = UnaryBooleanConjunction { operator, operand };
|
|
conjunction
|
|
}
|
|
|
|
fn parse_unary_boolean_operator(rule: pest::iterators::Pair<Rule>) -> UnaryBooleanOperator {
|
|
match rule.as_str().trim() {
|
|
"not" => UnaryBooleanOperator::Not,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn parse_binary_boolean_conjunction(rule: pest::iterators::Pair<Rule>) -> BinaryBooleanConjunction {
|
|
let mut inner = rule.into_inner();
|
|
let operator = parse_binary_boolean_operator(inner.next().unwrap());
|
|
let left_operand = parse_boolean_expression(inner.next().unwrap());
|
|
let right_operand = parse_boolean_expression(inner.next().unwrap());
|
|
let conjunction = ast::BinaryBooleanConjunction {
|
|
operator,
|
|
left_operand,
|
|
right_operand,
|
|
};
|
|
conjunction
|
|
}
|
|
|
|
fn parse_binary_boolean_operator(rule: pest::iterators::Pair<Rule>) -> ast::BinaryBooleanOperator {
|
|
match rule.as_str().trim() {
|
|
"and" => ast::BinaryBooleanOperator::And,
|
|
"or" => ast::BinaryBooleanOperator::Or,
|
|
"xor" => ast::BinaryBooleanOperator::Xor,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn with_caret(line: &str, line_number: usize, column: usize, preamble: &str, into: &mut String) {
|
|
into.push_str(&format!("{}l.{}: {}\n", preamble, line_number, line));
|
|
let padding = preamble.len() + 2 + (line_number % 10) + 2;
|
|
for _ in 0..(column - 1 + padding) {
|
|
into.push(' ');
|
|
}
|
|
into.push('^');
|
|
}
|
|
|
|
fn rule_as_text(rule: &Rule) -> &'static str {
|
|
match rule {
|
|
Rule::EOI => "end of input",
|
|
Rule::WHITESPACE => "whitespace",
|
|
Rule::relation => "relation (boolean) expression",
|
|
Rule::conjecture => "conjecture (arithmetic) expression",
|
|
Rule::boolean_expression => "boolean expression",
|
|
Rule::binary_boolean_conjunction => "binary boolean relation",
|
|
Rule::binary_boolean_operator => "binary boolean operator",
|
|
Rule::unary_boolean_conjunction => "unary boolean expression",
|
|
Rule::boolean_unary_operator => "unary boolean operator",
|
|
Rule::binary_arithmetic_conjunction => "binary arithmetic expression",
|
|
Rule::binary_arithmetic_operator => "binary arithmetic operator",
|
|
Rule::arithmetic_operand => "arithmetic value",
|
|
Rule::number_literal => "number literal",
|
|
Rule::arithmetic_expression => "arithmetic expression",
|
|
Rule::variable => "x, y, n, or p",
|
|
Rule::unary_arithmetic_conjunction => "unary arithmetic expression",
|
|
Rule::unary_arithmetic_operator => "unary arithmetic operator",
|
|
Rule::comparison_expression => "arithmetic comparison",
|
|
Rule::comparison_operator => "arithmetic comparison operator",
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parse_bad() {
|
|
match parse_relation("< ham x cheese") {
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
}
|
|
Ok(ast) => {
|
|
println!("{:?}", ast);
|
|
panic!();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parse_test() {
|
|
match parse_relation("< ham x 3") {
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
panic!();
|
|
}
|
|
Ok(ast) => {
|
|
println!("{:#?}", ast)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parse_weird_test() {
|
|
match parse_relation("and and < x 1 != 1 y < ham x k") {
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
panic!();
|
|
}
|
|
Ok(ast) => {
|
|
println!("{:?}", ast)
|
|
}
|
|
}
|
|
} |