A big refactor, step 2.2
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
use crate::ast::{Block, Statement};
|
||||
use crate::parser::{AstNode, Pair, PestRule};
|
||||
|
||||
impl AstNode for Block {
|
||||
const RULE: PestRule = PestRule::block;
|
||||
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
Self(pair.into_inner().map(Statement::from_pair_located).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::ast::{Call, Expr};
|
||||
use crate::parser::Parseable;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_block() {
|
||||
let block = Block::from_str("{ foo(); bar(); }").unwrap();
|
||||
let statements: Vec<_> = block.0.into_iter().map(|s| s.ast).collect();
|
||||
assert_eq!(
|
||||
statements,
|
||||
vec![
|
||||
Statement::Expr(Expr::Call(Call { target: "foo".into(), args: vec![] })),
|
||||
Statement::Expr(Expr::Call(Call { target: "bar".into(), args: vec![] })),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use crate::ast::{Block, Conditional, Expr};
|
||||
use crate::parser::{AstNode, Pair, PestRule};
|
||||
|
||||
impl AstNode for Conditional {
|
||||
const RULE: PestRule = PestRule::conditional;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner();
|
||||
let condition = Expr::from_pair(inner.next().unwrap());
|
||||
let body = Block::from_pair(inner.next().unwrap());
|
||||
let alternative = inner.next().map(Block::from_pair);
|
||||
Self {
|
||||
condition,
|
||||
body,
|
||||
alternative,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::ast::Statement;
|
||||
use crate::parser::Parseable;
|
||||
use crate::parser::ast_nodes::test_utils::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_conditional() {
|
||||
if let Ok(Statement::Conditional(Conditional { condition, body, alternative })) =
|
||||
Statement::from_str("if(cond) { foo(); }") {
|
||||
assert_eq!(condition, Expr::from_str("cond").unwrap());
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
assert_eq!(alternative, None);
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_else_conditional() {
|
||||
if let Ok(Statement::Conditional(Conditional { condition, body, alternative })) =
|
||||
Statement::from_str("if(cond) { foo(); } else { bar(); }") {
|
||||
assert_eq!(condition, Expr::from_str("cond").unwrap());
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
assert_eq!(alternative.unwrap().0[0].ast, Block::from_str("{ bar(); }").unwrap().0[0].ast);
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
use crate::ast::{Call, Expr, Operator};
|
||||
use crate::parser::{AstNode, Pair, PairExt, PestRule, PRATT_PARSER};
|
||||
|
||||
impl AstNode for Expr {
|
||||
const RULE: PestRule = PestRule::expr;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
// The way this works is, the rule has to be of the form:
|
||||
// expr = { prefix* ~ val ~ suffix* ~ (operator ~ prefix* ~ val ~ suffix*)* }
|
||||
// Each of these map methods turns a thing into an expr. Which means expr HAS
|
||||
// to be an enum with the different possible forms these things can take:
|
||||
// - if it's a term, it goes into map_primary and returns an Expr::Number or Name
|
||||
// - If it's a prefix or suffix, it goes into map_prefix or map_postfix, and
|
||||
// returns an Expr::Prefix or Expr::Suffix
|
||||
// - Operators go into map_infix along with two exprs for the left and right
|
||||
// sides
|
||||
// The output of all this is an Expr, containing a tree of other Exprs of
|
||||
// various forms.
|
||||
PRATT_PARSER
|
||||
.map_primary(|term| match term.as_rule() {
|
||||
PestRule::number => Expr::Number(term.into_number()),
|
||||
PestRule::name => Expr::Name(String::from(term.as_str())),
|
||||
PestRule::expr => Expr::from_pair(term),
|
||||
PestRule::string => Self::String(term.into_quoted_string()),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.map_infix(|lhs, op, rhs| Expr::Infix(lhs.into(), Operator::from_pair(op), rhs.into()))
|
||||
.map_prefix(|prefix, expr| match prefix.as_str() {
|
||||
"-" => Expr::Neg(expr.into()),
|
||||
"!" => Expr::Not(expr.into()),
|
||||
"*" => Expr::Deref(expr.into()),
|
||||
"&" => Expr::Address(expr.into()),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.map_postfix(|expr, suffix| match suffix.as_rule() {
|
||||
PestRule::arglist => Expr::Call(
|
||||
Call {
|
||||
target: expr.into(),
|
||||
args: suffix.into_inner().map(Expr::from_pair).collect(),
|
||||
}
|
||||
),
|
||||
PestRule::subscript => {
|
||||
Expr::Subscript(expr.into(), Expr::from_pair(suffix.first()).into())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.parse(pair.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parser::Parseable;
|
||||
use super::*;
|
||||
use Expr::Infix;
|
||||
use Operator::*;
|
||||
use crate::ast::Statement;
|
||||
|
||||
#[test]
|
||||
fn parse_basic_exprs() {
|
||||
// A very, very basic expression
|
||||
assert_eq!(Expr::from_str("23"), Ok(Expr::Number(23)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infix_operator() {
|
||||
// Two vals with an operator
|
||||
assert_eq!(
|
||||
Expr::from_str("23 + 5"),
|
||||
Ok(Infix(23.into(), Add, 5.into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_operators() {
|
||||
// Multiple terms at the same precedence level
|
||||
assert_eq!(
|
||||
Expr::from_str("1 + 2 + 3"),
|
||||
Ok(Infix(Infix(1.into(), Add, 2.into()).into(), Add, 3.into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_prefix() {
|
||||
// Simple prefix
|
||||
assert_eq!(Expr::from_str("-5"), Ok(Expr::Neg(5.into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_prefixes() {
|
||||
// Multiple prefixes
|
||||
assert_eq!(
|
||||
Expr::from_str("!-foo"),
|
||||
Ok(Expr::Not(Expr::Neg("foo".into()).into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_suffix() {
|
||||
// Simple suffix
|
||||
assert_eq!(
|
||||
Expr::from_str("foo[10]"),
|
||||
Ok(Expr::Subscript("foo".into(), 10.into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_suffixes() {
|
||||
// Multi-suffix
|
||||
assert_eq!(
|
||||
Expr::from_str("foo[10][3]"),
|
||||
Ok(Expr::Subscript(
|
||||
Expr::Subscript("foo".into(), 10.into()).into(),
|
||||
3.into()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precedence_levels() {
|
||||
// Higher precedence levels
|
||||
assert_eq!(
|
||||
Expr::from_str("1 + 2 * 3"),
|
||||
Ok(Infix(1.into(), Add, Infix(2.into(), Mul, 3.into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(Expr::from_str("2 * 3"), Ok(Infix(2.into(), Mul, 3.into())));
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("2 * 3 + 4"),
|
||||
Ok(Infix(Infix(2.into(), Mul, 3.into()).into(), Add, 4.into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weird_ops() {
|
||||
// Various operators
|
||||
assert_eq!(
|
||||
Expr::from_str("1 || 2 && 3"),
|
||||
Ok(Infix(1.into(), Or, Infix(2.into(), And, 3.into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("2 && &blah"),
|
||||
Ok(Infix(2.into(), And, Expr::Address("blah".into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("2 & &blah"),
|
||||
Ok(Infix(2.into(), BitAnd, Expr::Address("blah".into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("1 | 2 ^ 3"),
|
||||
Ok(Infix(
|
||||
1.into(),
|
||||
BitOr,
|
||||
Infix(2.into(), Xor, 3.into()).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("x == y > z"),
|
||||
Ok(Infix(
|
||||
"x".into(),
|
||||
Eq,
|
||||
Infix("y".into(), Gt, "z".into()).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("1 << 6"),
|
||||
Ok(Infix(1.into(), Lshift, 6.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("!a - -3"),
|
||||
Ok(Infix(
|
||||
Expr::Not("a".into()).into(),
|
||||
Sub,
|
||||
Expr::Neg(3.into()).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("-(4 * 5)"),
|
||||
Ok(Expr::Neg(Infix(4.into(), Mul, 5.into()).into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens() {
|
||||
// Parens
|
||||
assert_eq!(
|
||||
Expr::from_str("(1 + 2) * 3"),
|
||||
Ok(Infix(Infix(1.into(), Add, 2.into()).into(), Mul, 3.into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addresses() {
|
||||
// Addresses
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo[7]"),
|
||||
Ok(Expr::Address(Expr::Subscript("foo".into(), 7.into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo + 7"),
|
||||
Ok(Expr::Infix(
|
||||
Expr::Address("foo".into()).into(),
|
||||
Add,
|
||||
7.into()
|
||||
))
|
||||
);
|
||||
|
||||
// This is an example of a thing that will parse but not compile. This parses as an address
|
||||
// of a call, which doesn't make sense, but because Expr::Address contains an Lvalue the
|
||||
// compiler can detect this and error at that stage.
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo()"),
|
||||
Ok(Expr::Address(Expr::Call(Call { target: "foo".into(), args: vec![] }).into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dereferencing() {
|
||||
// Dereferencing
|
||||
assert_eq!(
|
||||
Expr::from_str("*foo"),
|
||||
Ok(Expr::Deref("foo".into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("*foo[3]"), // The subscript happens before the dereference
|
||||
Ok(Expr::Deref(Expr::Subscript("foo".into(), 3.into()).into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_call() {
|
||||
let blah = Expr::Call(Call { target: "blah".into(), args: vec![] });
|
||||
|
||||
// Can Expr parse a call?
|
||||
assert_eq!(Expr::from_str("blah()"), Ok(blah.clone()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statement_call() {
|
||||
let blah = Expr::Call(Call { target: "blah".into(), args: vec![] });
|
||||
|
||||
// Can Statement parse a call?
|
||||
assert_eq!(Statement::from_str("blah();"), Ok(Statement::Expr(blah)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calls_with_args() {
|
||||
// Calls with args
|
||||
assert_eq!(
|
||||
Expr::from_str("blah(1, 2)"),
|
||||
Ok(Expr::Call(Call { target: "blah".into(), args: vec![1.into(), 2.into()] }))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calls_with_strings() {
|
||||
//Calls with strings
|
||||
assert_eq!(
|
||||
Expr::from_str("blah(\"foo\", 2)"),
|
||||
Ok(Expr::Call(Call { target: "blah".into(), args: vec![Expr::String("foo".into()), 2.into()] }))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,13 @@ mod function_prototype;
|
||||
mod statement;
|
||||
mod asm;
|
||||
mod r#return;
|
||||
mod assignment;
|
||||
mod assignment;
|
||||
mod var_decl;
|
||||
mod block;
|
||||
mod conditional;
|
||||
mod test_utils;
|
||||
mod while_loop;
|
||||
mod repeat_loop;
|
||||
mod program;
|
||||
mod operator;
|
||||
mod expr;
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::ast::Operator;
|
||||
use crate::parser::{AstNode, Pair, PestRule};
|
||||
|
||||
impl AstNode for Operator {
|
||||
const RULE: PestRule = PestRule::operator;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
match pair.as_str() {
|
||||
"+" => Self::Add,
|
||||
"-" => Self::Sub,
|
||||
"*" => Self::Mul,
|
||||
"/" => Self::Div,
|
||||
"%" => Self::Mod,
|
||||
"&&" => Self::And,
|
||||
"||" => Self::Or,
|
||||
"&" => Self::BitAnd,
|
||||
"|" => Self::BitOr,
|
||||
"^" => Self::Xor,
|
||||
">" => Self::Gt,
|
||||
">=" => Self::Ge,
|
||||
"<" => Self::Lt,
|
||||
"<=" => Self::Le,
|
||||
"==" => Self::Eq,
|
||||
"!=" => Self::Ne,
|
||||
"<<" => Self::Lshift,
|
||||
">>" => Self::Rshift,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use crate::ast::{Declaration, Program};
|
||||
use crate::parser::{AstNode, Pair, PestRule};
|
||||
|
||||
impl AstNode for Program {
|
||||
const RULE: PestRule = PestRule::program;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
// Program captures EOI, to make sure that it's parsing the entire stream. We need to
|
||||
// ignore that though:
|
||||
Self(
|
||||
pair.into_inner()
|
||||
.filter(|p| p.as_rule() != PestRule::EOI)
|
||||
.map(Declaration::from_pair_located)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parser::ast_nodes::test_utils::dislocate;
|
||||
use crate::parser::Parseable;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_program() {
|
||||
let prog = Program::from_str("global foo; const blah = 3;").unwrap();
|
||||
let decls: Vec<_> = dislocate(prog.0);
|
||||
assert_eq!(
|
||||
decls,
|
||||
vec![
|
||||
Declaration::from_str("global foo;").unwrap(),
|
||||
Declaration::from_str("const blah = 3;").unwrap(),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use crate::ast::{Block, Expr, RepeatLoop};
|
||||
use crate::parser::{AstNode, Pair, PairsExt, PestRule};
|
||||
|
||||
impl AstNode for RepeatLoop {
|
||||
const RULE: PestRule = PestRule::repeat_loop;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner().peekable();
|
||||
let count = Expr::from_pair(inner.next().unwrap());
|
||||
let name = inner
|
||||
.next_if_rule(PestRule::name)
|
||||
.map(|p| String::from(p.as_str()));
|
||||
let body = Block::from_pair(inner.next().unwrap());
|
||||
Self { count, name, body }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::ast::Statement;
|
||||
use crate::parser::ast_nodes::test_utils::{dislocate, dislocated_block};
|
||||
use crate::parser::Parseable;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_repeat_loops_with_name() {
|
||||
assert!(match Statement::from_str("repeat(10) x { foo(x); }") {
|
||||
Ok(Statement::RepeatLoop(RepeatLoop { count, name, body })) => {
|
||||
assert_eq!(count, 10.into());
|
||||
assert_eq!(name, Some("x".into()));
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(x); }"));
|
||||
true
|
||||
},
|
||||
_ => false
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_anon_repeat_loops() {
|
||||
assert!(match Statement::from_str("repeat(10) { foo(); }") {
|
||||
Ok(Statement::RepeatLoop(RepeatLoop { count, name, body })) => {
|
||||
assert_eq!(count, 10.into());
|
||||
assert_eq!(name, None);
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
true
|
||||
},
|
||||
_ => false
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::ast::{Block, Located, Statement};
|
||||
use crate::parser::Parseable;
|
||||
|
||||
pub fn dislocate<T>(block: Vec<Located<T>>) -> Vec<T> {
|
||||
block.into_iter().map(|l| l.ast).collect()
|
||||
}
|
||||
|
||||
pub fn dislocated_block(src: &str) -> Vec<Statement> {
|
||||
dislocate(Block::from_str(src).unwrap().0)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use crate::ast::{Expr, VarDecl};
|
||||
use crate::parser::{AstNode, Pair, PairExt, PestRule};
|
||||
|
||||
impl AstNode for VarDecl {
|
||||
const RULE: PestRule = PestRule::var_decl;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner();
|
||||
let name = String::from(inner.next().unwrap().as_str());
|
||||
let mut size = None;
|
||||
let mut initial = None;
|
||||
|
||||
for p in inner {
|
||||
match p.as_rule() {
|
||||
PestRule::size => { size = Some(Expr::from_pair(p.first())) }
|
||||
PestRule::expr => { initial = Some(Expr::from_pair(p)) }
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
Self { name, size, initial }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::ast::Statement;
|
||||
use crate::parser::Parseable;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_var_decl() {
|
||||
assert_eq!(
|
||||
Statement::from_str("var blah;"),
|
||||
Ok(Statement::VarDecl(VarDecl {
|
||||
name: "blah".into(),
|
||||
size: None,
|
||||
initial: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_var_decl_with_value() {
|
||||
assert_eq!(
|
||||
VarDecl::from_str("var blah[7] = 35"),
|
||||
Ok(VarDecl {
|
||||
name: "blah".into(),
|
||||
size: Some(7.into()),
|
||||
initial: Some(35.into()),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use crate::ast::{Block, Expr, WhileLoop};
|
||||
use crate::parser::{AstNode, Pair, PestRule};
|
||||
|
||||
impl AstNode for WhileLoop {
|
||||
const RULE: PestRule = PestRule::while_loop;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner();
|
||||
Self {
|
||||
condition: Expr::from_pair(inner.next().unwrap()),
|
||||
body: Block::from_pair(inner.next().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::ast::Statement;
|
||||
use crate::parser::ast_nodes::test_utils::{dislocate, dislocated_block};
|
||||
use crate::parser::Parseable;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_while_loops() {
|
||||
assert!(match Statement::from_str("while(cond) { foo(); }") {
|
||||
Ok(Statement::WhileLoop(WhileLoop { condition, body })) => {
|
||||
assert_eq!(condition, Expr::from_str("cond").unwrap());
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
true
|
||||
}
|
||||
_ => false
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -81,484 +81,9 @@ mod ast_nodes;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for VarDecl {
|
||||
const RULE: PestRule = PestRule::var_decl;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner();
|
||||
let name = String::from(inner.next().unwrap().as_str());
|
||||
let mut size = None;
|
||||
let mut initial = None;
|
||||
|
||||
for p in inner {
|
||||
match p.as_rule() {
|
||||
PestRule::size => { size = Some(Expr::from_pair(p.first())) }
|
||||
PestRule::expr => { initial = Some(Expr::from_pair(p)) }
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
Self { name, size, initial }
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for Block {
|
||||
const RULE: PestRule = PestRule::block;
|
||||
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
Self(pair.into_inner().map(Statement::from_pair_located).collect())
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for Conditional {
|
||||
const RULE: PestRule = PestRule::conditional;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner();
|
||||
let condition = Expr::from_pair(inner.next().unwrap());
|
||||
let body = Block::from_pair(inner.next().unwrap());
|
||||
let alternative = inner.next().map(Block::from_pair);
|
||||
Self {
|
||||
condition,
|
||||
body,
|
||||
alternative,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for WhileLoop {
|
||||
const RULE: PestRule = PestRule::while_loop;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner();
|
||||
Self {
|
||||
condition: Expr::from_pair(inner.next().unwrap()),
|
||||
body: Block::from_pair(inner.next().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for RepeatLoop {
|
||||
const RULE: PestRule = PestRule::repeat_loop;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
let mut inner = pair.into_inner().peekable();
|
||||
let count = Expr::from_pair(inner.next().unwrap());
|
||||
let name = inner
|
||||
.next_if_rule(PestRule::name)
|
||||
.map(|p| String::from(p.as_str()));
|
||||
let body = Block::from_pair(inner.next().unwrap());
|
||||
Self { count, name, body }
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for Expr {
|
||||
const RULE: PestRule = PestRule::expr;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
// The way this works is, the rule has to be of the form:
|
||||
// expr = { prefix* ~ val ~ suffix* ~ (operator ~ prefix* ~ val ~ suffix*)* }
|
||||
// Each of these map methods turns a thing into an expr. Which means expr HAS
|
||||
// to be an enum with the different possible forms these things can take:
|
||||
// - if it's a term, it goes into map_primary and returns an Expr::Number or Name
|
||||
// - If it's a prefix or suffix, it goes into map_prefix or map_postfix, and
|
||||
// returns an Expr::Prefix or Expr::Suffix
|
||||
// - Operators go into map_infix along with two exprs for the left and right
|
||||
// sides
|
||||
// The output of all this is an Expr, containing a tree of other Exprs of
|
||||
// various forms.
|
||||
PRATT_PARSER
|
||||
.map_primary(|term| match term.as_rule() {
|
||||
PestRule::number => Expr::Number(term.into_number()),
|
||||
PestRule::name => Expr::Name(String::from(term.as_str())),
|
||||
PestRule::expr => Expr::from_pair(term),
|
||||
PestRule::string => Self::String(term.into_quoted_string()),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.map_infix(|lhs, op, rhs| Expr::Infix(lhs.into(), Operator::from_pair(op), rhs.into()))
|
||||
.map_prefix(|prefix, expr| match prefix.as_str() {
|
||||
"-" => Expr::Neg(expr.into()),
|
||||
"!" => Expr::Not(expr.into()),
|
||||
"*" => Expr::Deref(expr.into()),
|
||||
"&" => Expr::Address(expr.into()),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.map_postfix(|expr, suffix| match suffix.as_rule() {
|
||||
PestRule::arglist => Expr::Call(
|
||||
Call {
|
||||
target: expr.into(),
|
||||
args: suffix.into_inner().map(Expr::from_pair).collect(),
|
||||
}
|
||||
),
|
||||
PestRule::subscript => {
|
||||
Expr::Subscript(expr.into(), Expr::from_pair(suffix.first()).into())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.parse(pair.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
// todo: can this die? it's only used in compiler tests but needs to be here for visibility reasons
|
||||
impl Expr {
|
||||
pub(crate) fn parse(src: &str) -> Result<Self, ParseError> {
|
||||
Self::from_str(src)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for Operator {
|
||||
const RULE: PestRule = PestRule::operator;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
match pair.as_str() {
|
||||
"+" => Self::Add,
|
||||
"-" => Self::Sub,
|
||||
"*" => Self::Mul,
|
||||
"/" => Self::Div,
|
||||
"%" => Self::Mod,
|
||||
"&&" => Self::And,
|
||||
"||" => Self::Or,
|
||||
"&" => Self::BitAnd,
|
||||
"|" => Self::BitOr,
|
||||
"^" => Self::Xor,
|
||||
">" => Self::Gt,
|
||||
">=" => Self::Ge,
|
||||
"<" => Self::Lt,
|
||||
"<=" => Self::Le,
|
||||
"==" => Self::Eq,
|
||||
"!=" => Self::Ne,
|
||||
"<<" => Self::Lshift,
|
||||
">>" => Self::Rshift,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
impl AstNode for Program {
|
||||
const RULE: PestRule = PestRule::program;
|
||||
fn from_pair(pair: Pair) -> Self {
|
||||
// Program captures EOI, to make sure that it's parsing the entire stream. We need to
|
||||
// ignore that though:
|
||||
Self(
|
||||
pair.into_inner()
|
||||
.filter(|p| p.as_rule() != PestRule::EOI)
|
||||
.map(Declaration::from_pair_located)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_exprs() {
|
||||
use Expr::Infix;
|
||||
use Operator::*;
|
||||
|
||||
// A very, very basic expression
|
||||
assert_eq!(Expr::from_str("23"), Ok(Expr::Number(23)));
|
||||
|
||||
// Two vals with an operator
|
||||
assert_eq!(
|
||||
Expr::from_str("23 + 5"),
|
||||
Ok(Infix(23.into(), Add, 5.into()))
|
||||
);
|
||||
|
||||
// Multiple terms at the same precedence level
|
||||
assert_eq!(
|
||||
Expr::from_str("1 + 2 + 3"),
|
||||
Ok(Infix(Infix(1.into(), Add, 2.into()).into(), Add, 3.into()))
|
||||
);
|
||||
|
||||
// Simple prefix
|
||||
assert_eq!(Expr::from_str("-5"), Ok(Expr::Neg(5.into())));
|
||||
|
||||
// Multiple prefixes
|
||||
assert_eq!(
|
||||
Expr::from_str("!-foo"),
|
||||
Ok(Expr::Not(Expr::Neg("foo".into()).into()))
|
||||
);
|
||||
|
||||
// Simple suffix
|
||||
assert_eq!(
|
||||
Expr::from_str("foo[10]"),
|
||||
Ok(Expr::Subscript("foo".into(), 10.into()))
|
||||
);
|
||||
|
||||
// Multi-suffix
|
||||
assert_eq!(
|
||||
Expr::from_str("foo[10][3]"),
|
||||
Ok(Expr::Subscript(
|
||||
Expr::Subscript("foo".into(), 10.into()).into(),
|
||||
3.into()
|
||||
))
|
||||
);
|
||||
|
||||
// Higher precedence levels
|
||||
assert_eq!(
|
||||
Expr::from_str("1 + 2 * 3"),
|
||||
Ok(Infix(1.into(), Add, Infix(2.into(), Mul, 3.into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(Expr::from_str("2 * 3"), Ok(Infix(2.into(), Mul, 3.into())));
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("2 * 3 + 4"),
|
||||
Ok(Infix(Infix(2.into(), Mul, 3.into()).into(), Add, 4.into()))
|
||||
);
|
||||
|
||||
// Various operators
|
||||
assert_eq!(
|
||||
Expr::from_str("1 || 2 && 3"),
|
||||
Ok(Infix(1.into(), Or, Infix(2.into(), And, 3.into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("2 && &blah"),
|
||||
Ok(Infix(2.into(), And, Expr::Address("blah".into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("2 & &blah"),
|
||||
Ok(Infix(2.into(), BitAnd, Expr::Address("blah".into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("1 | 2 ^ 3"),
|
||||
Ok(Infix(
|
||||
1.into(),
|
||||
BitOr,
|
||||
Infix(2.into(), Xor, 3.into()).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("x == y > z"),
|
||||
Ok(Infix(
|
||||
"x".into(),
|
||||
Eq,
|
||||
Infix("y".into(), Gt, "z".into()).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("1 << 6"),
|
||||
Ok(Infix(1.into(), Lshift, 6.into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("!a - -3"),
|
||||
Ok(Infix(
|
||||
Expr::Not("a".into()).into(),
|
||||
Sub,
|
||||
Expr::Neg(3.into()).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("-(4 * 5)"),
|
||||
Ok(Expr::Neg(Infix(4.into(), Mul, 5.into()).into()))
|
||||
);
|
||||
|
||||
// Parens
|
||||
assert_eq!(
|
||||
Expr::from_str("(1 + 2) * 3"),
|
||||
Ok(Infix(Infix(1.into(), Add, 2.into()).into(), Mul, 3.into()))
|
||||
);
|
||||
|
||||
// Addresses
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo[7]"),
|
||||
Ok(Expr::Address(Expr::Subscript("foo".into(), 7.into()).into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo + 7"),
|
||||
Ok(Expr::Infix(
|
||||
Expr::Address("foo".into()).into(),
|
||||
Add,
|
||||
7.into()
|
||||
))
|
||||
);
|
||||
|
||||
// This is an example of a thing that will parse but not compile. This parses as an address
|
||||
// of a call, which doesn't make sense, but because Expr::Address contains an Lvalue the
|
||||
// compiler can detect this and error at that stage.
|
||||
assert_eq!(
|
||||
Expr::from_str("&foo()"),
|
||||
Ok(Expr::Address(Expr::Call(Call { target: "foo".into(), args: vec![] }).into()))
|
||||
);
|
||||
|
||||
// Dereferencing
|
||||
assert_eq!(
|
||||
Expr::from_str("*foo"),
|
||||
Ok(Expr::Deref("foo".into()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Expr::from_str("*foo[3]"), // The subscript happens before the dereference
|
||||
Ok(Expr::Deref(Expr::Subscript("foo".into(), 3.into()).into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_calls() {
|
||||
let blah = Expr::Call(Call { target: "blah".into(), args: vec![] });
|
||||
|
||||
// Can Node parse a call?
|
||||
assert_eq!(Expr::from_str("blah()"), Ok(blah.clone()));
|
||||
|
||||
// Can Statement parse a call?
|
||||
assert_eq!(Statement::from_str("blah();"), Ok(Statement::Expr(blah)));
|
||||
|
||||
// Calls with args
|
||||
assert_eq!(
|
||||
Expr::from_str("blah(1, 2)"),
|
||||
Ok(Expr::Call(Call { target: "blah".into(), args: vec![1.into(), 2.into()] }))
|
||||
);
|
||||
|
||||
//Calls with strings
|
||||
assert_eq!(
|
||||
Expr::from_str("blah(\"foo\", 2)"),
|
||||
Ok(Expr::Call(Call { target: "blah".into(), args: vec![Expr::String("foo".into()), 2.into()] }))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_var_decl() {
|
||||
assert_eq!(
|
||||
Statement::from_str("var blah;"),
|
||||
Ok(Statement::VarDecl(VarDecl {
|
||||
name: "blah".into(),
|
||||
size: None,
|
||||
initial: None,
|
||||
}))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
VarDecl::from_str("var blah[7] = 35"),
|
||||
Ok(VarDecl {
|
||||
name: "blah".into(),
|
||||
size: Some(7.into()),
|
||||
initial: Some(35.into()),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_block() {
|
||||
let block = Block::from_str("{ foo(); bar(); }").unwrap();
|
||||
let statements: Vec<_> = block.0.into_iter().map(|s| s.ast).collect();
|
||||
assert_eq!(
|
||||
statements,
|
||||
vec![
|
||||
Statement::Expr(Expr::Call(Call { target: "foo".into(), args: vec![] })),
|
||||
Statement::Expr(Expr::Call(Call { target: "bar".into(), args: vec![] })),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
fn dislocate<T>(block: Vec<Located<T>>) -> Vec<T> {
|
||||
block.into_iter().map(|l| l.ast).collect()
|
||||
}
|
||||
|
||||
fn dislocated_block(src: &str) -> Vec<Statement> {
|
||||
dislocate(Block::from_str(src).unwrap().0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_conditional() {
|
||||
if let Ok(Statement::Conditional(Conditional { condition, body, alternative })) =
|
||||
Statement::from_str("if(cond) { foo(); }") {
|
||||
assert_eq!(condition, Expr::from_str("cond").unwrap());
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
assert_eq!(alternative, None);
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
|
||||
if let Ok(Statement::Conditional(Conditional { condition, body, alternative })) =
|
||||
Statement::from_str("if(cond) { foo(); } else { bar(); }") {
|
||||
assert_eq!(condition, Expr::from_str("cond").unwrap());
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
assert_eq!(alternative.unwrap().0[0].ast, Block::from_str("{ bar(); }").unwrap().0[0].ast);
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_while_loops() {
|
||||
assert!(match Statement::from_str("while(cond) { foo(); }") {
|
||||
Ok(Statement::WhileLoop(WhileLoop { condition, body })) => {
|
||||
assert_eq!(condition, Expr::from_str("cond").unwrap());
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
true
|
||||
}
|
||||
_ => false
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_repeat_loops() {
|
||||
assert!(match Statement::from_str("repeat(10) x { foo(x); }") {
|
||||
Ok(Statement::RepeatLoop(RepeatLoop { count, name, body })) => {
|
||||
assert_eq!(count, 10.into());
|
||||
assert_eq!(name, Some("x".into()));
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(x); }"));
|
||||
true
|
||||
},
|
||||
_ => false
|
||||
});
|
||||
|
||||
assert!(match Statement::from_str("repeat(10) { foo(); }") {
|
||||
Ok(Statement::RepeatLoop(RepeatLoop { count, name, body })) => {
|
||||
assert_eq!(count, 10.into());
|
||||
assert_eq!(name, None);
|
||||
assert_eq!(dislocate(body.0), dislocated_block("{ foo(); }"));
|
||||
true
|
||||
},
|
||||
_ => false
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_program() {
|
||||
let prog = Program::from_str("global foo; const blah = 3;").unwrap();
|
||||
let decls: Vec<_> = dislocate(prog.0);
|
||||
assert_eq!(
|
||||
decls,
|
||||
vec![
|
||||
Declaration::from_str("global foo;").unwrap(),
|
||||
Declaration::from_str("const blah = 3;").unwrap(),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user