Skip to content
Snippets Groups Projects
Commit 11b7935d authored by Per's avatar Per
Browse files

refactored climbing example using spans

parent 753a465a
No related branches found
No related tags found
No related merge requests found
extern crate nom;
use std::iter::Peekable;
use std::slice::Iter;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::char,
character::complete::{digit1, multispace0},
combinator::{cut, map},
error::ParseError,
multi::many1,
sequence::{delimited, preceded},
IResult,
};
use crust::ast::{Op, Expr};
pub fn parse_i32(i: &str) -> IResult<&str, i32> {
map(digit1, |digit_str: &str| digit_str.parse::<i32>().unwrap())(i)
}
fn parse_op(i: &str) -> IResult<&str, Op> {
alt((
map(tag("=="), |_| Op::Eq),
map(tag("!="), |_| Op::Neq),
map(tag("**"), |_| Op::Pow),
map(tag("&&"), |_| Op::And),
map(tag("||"), |_| Op::Or),
map(tag("+"), |_| Op::Add),
map(tag("-"), |_| Op::Sub),
map(tag("*"), |_| Op::Mul),
map(tag("/"), |_| Op::Div),
map(tag("!"), |_| Op::Not),
))(i)
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Num(i32),
Par(Vec<Token>),
Op(Op),
}
fn parse_terminal(i: &str) -> IResult<&str, Token> {
alt((
map(parse_i32, |v| Token::Num(v)),
map(parse_par(parse_tokens), |tokens| Token::Par(tokens)),
))(i)
}
fn parse_token(i: &str) -> IResult<&str, Token> {
preceded(
multispace0,
alt((map(parse_op, |op| Token::Op(op)), parse_terminal)),
)(i)
}
fn parse_tokens(i: &str) -> IResult<&str, Vec<Token>> {
many1(parse_token)(i)
}
fn compute_atom(t: &mut Peekable<Iter<Token>>) -> Expr {
match t.next() {
Some(Token::Num(i)) => Expr::Num(*i),
Some(Token::Par(v)) => climb(&mut v.iter().peekable(), 0),
Some(Token::Op(op)) => Expr::UnaryOp(*op, Box::new(climb(t, 4))), // assume highest precedence
_ => panic!("error in compute atom"),
}
}
fn climb(t: &mut Peekable<Iter<Token>>, min_prec: u8) -> Expr {
let mut result = compute_atom(t);
loop {
match t.peek() {
Some(Token::Op(op)) => {
let (prec, ass) = get_prec(op);
if prec < min_prec {
break;
};
let next_prec = prec
+ match ass {
Ass::Left => 1,
_ => 0,
};
t.next();
let rhs = climb(t, next_prec);
result = Expr::BinOp(*op, Box::new(result), Box::new(rhs))
}
_ => {
break;
}
}
}
result
}
fn test(s: &str, v: i32) {
match parse_tokens(s) {
Ok(("", t)) => {
let mut t = t.iter().peekable();
println!("{:?}", &t);
let e = climb(&mut t, 0);
println!("{:?}", &e);
println!("eval {} {}", math_eval(&e), v);
assert_eq!(math_eval(&e), v);
}
Ok((s, t)) => println!(
"parse incomplete, \n parsed tokens \t{:?}, \n remaining \t{:?}",
t, s
),
Err(err) => println!("{:?}", err),
}
}
use crust::parse::test;
fn main() {
test("- -1 + + 1", - -1 + 1); // rust does not allow + as a unary op (I do ;)
......@@ -127,59 +14,4 @@ fn main() {
test("1*2+3+3*21-a12+2", 1 * 2 + 3 + 3 * 21 - 12 + 2);
}
// helpers
fn parse_par<'a, O, F, E>(
inner: F,
) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
F: Fn(&'a str) -> IResult<&'a str, O, E>,
E: ParseError<&'a str>,
{
// delimited allows us to split up the input
// cut allwos us to consume the input (and prevent backtracking)
delimited(char('('), preceded(multispace0, inner), cut(char(')')))
}
fn math_eval(e: &Expr) -> i32 {
match e {
Expr::Num(i) => *i,
Expr::BinOp(op, l, r) => {
let lv = math_eval(l);
let rv = math_eval(r);
match op {
Op::Add => lv + rv,
Op::Sub => lv - rv,
Op::Mul => lv * rv,
Op::Div => lv / rv,
Op::Pow => lv.pow(rv as u32),
_ => unimplemented!(),
}
}
Expr::UnaryOp(op, e) => {
let e = math_eval(e);
match op {
Op::Add => e,
Op::Sub => -e,
_ => unimplemented!(),
}
}
_ => unimplemented!(),
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum Ass {
Left,
Right,
}
fn get_prec(op: &Op) -> (u8, Ass) {
match op {
Op::Add => (1, Ass::Left),
Op::Sub => (1, Ass::Left),
Op::Mul => (2, Ass::Left),
Op::Div => (2, Ass::Left),
Op::Pow => (3, Ass::Right),
_ => unimplemented!(),
}
}
......@@ -2,7 +2,7 @@
use nom_locate::LocatedSpan;
type Span<'a> = LocatedSpan<&'a str>;
pub type Span<'a> = LocatedSpan<&'a str>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Op {
......@@ -21,13 +21,13 @@ pub enum Op {
type SpanOp<'a> = (Span<'a>, Op);
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
pub enum Expr<'a> {
Num(i32),
Par(Box<Expr>),
Par(Box<SpanExpr<'a>>),
// Identifier
// Function application
BinOp(Op, Box<Expr>, Box<Expr>),
UnaryOp(Op, Box<Expr>),
BinOp(Op, Box<SpanExpr<'a>>, Box<SpanExpr<'a>>),
UnaryOp(Op, Box<SpanExpr<'a>>),
}
type SpanExpr<'a> = (Span<'a>, Expr);
pub type SpanExpr<'a> = (Span<'a>, Expr<'a>);
// lib
pub mod ast;
\ No newline at end of file
pub mod ast;
pub mod parse;
extern crate nom;
use std::iter::Peekable;
use std::slice::Iter;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::char,
character::complete::{digit1, multispace0},
combinator::{cut, map},
error::ParseError,
multi::many1,
sequence::{delimited, preceded},
IResult,
};
use crate::ast::{Expr, Op, Span, SpanExpr};
pub fn parse_i32(i: Span) -> IResult<Span, (Span, i32)> {
map(digit1, |digit_str: Span| {
(digit_str, digit_str.fragment.parse::<i32>().unwrap())
})(i)
}
fn parse_op(i: Span) -> IResult<Span, (Span, Op)> {
alt((
map(tag("=="), |s| (s, Op::Eq)),
map(tag("!="), |s| (s, Op::Neq)),
map(tag("**"), |s| (s, Op::Pow)),
map(tag("&&"), |s| (s, Op::And)),
map(tag("||"), |s| (s, Op::Or)),
map(tag("+"), |s| (s, Op::Add)),
map(tag("-"), |s| (s, Op::Sub)),
map(tag("*"), |s| (s, Op::Mul)),
map(tag("/"), |s| (s, Op::Div)),
map(tag("!"), |s| (s, Op::Not)),
))(i)
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token<'a> {
Num(i32),
Par(Vec<SpanToken<'a>>),
Op(Op),
}
type SpanToken<'a> = (Span<'a>, Token<'a>);
fn parse_terminal(i: Span) -> IResult<Span, SpanToken> {
alt((
map(parse_i32, |(s, v)| (s, Token::Num(v))),
map(parse_par(parse_tokens), |(s, tokens)| {
(s, Token::Par(tokens))
}),
))(i)
}
fn parse_token(i: Span) -> IResult<Span, SpanToken> {
preceded(
multispace0,
alt((map(parse_op, |(s, op)| (s, Token::Op(op))), parse_terminal)),
)(i)
}
// I think the outer span is wrong
fn parse_tokens(i: Span) -> IResult<Span, (Span, Vec<SpanToken>)> {
map(many1(parse_token), |tokens| (i, tokens))(i)
}
fn compute_atom<'a>(t: &mut Peekable<Iter<SpanToken<'a>>>) -> SpanExpr<'a> {
match t.next() {
Some((s, Token::Num(i))) => (*s, Expr::Num(*i)),
Some((_, Token::Par(v))) => climb(&mut v.iter().peekable(), 0),
Some((s, Token::Op(op))) => (*s, Expr::UnaryOp(*op, Box::new(climb(t, 4)))), // assume highest precedence
_ => panic!("error in compute atom"),
}
}
fn climb<'a>(
t: &mut Peekable<Iter<SpanToken<'a>>>,
min_prec: u8,
) -> SpanExpr<'a> {
let mut result: SpanExpr = compute_atom(t);
loop {
match t.peek() {
Some((s, Token::Op(op))) => {
let (prec, ass) = get_prec(op);
if prec < min_prec {
break;
};
let next_prec = prec
+ match ass {
Ass::Left => 1,
_ => 0,
};
t.next();
let rhs = climb(t, next_prec);
result = (*s, Expr::BinOp(*op, Box::new(result), Box::new(rhs)))
}
_ => {
break;
}
}
}
result
}
pub fn test(s: &str, v: i32) {
match parse_tokens(Span::new(s)) {
Ok((Span { fragment: "", .. }, (_, t))) => {
let mut t = t.iter().peekable();
println!("{:?}", &t);
let e = climb(&mut t, 0);
println!("{:?}", &e);
println!("eval {} {}", math_eval(&e), v);
assert_eq!(math_eval(&e), v);
}
Ok((s, t)) => println!(
"parse incomplete, \n parsed tokens \t{:?}, \n remaining \t{:?}",
t, s
),
Err(err) => println!("{:?}", err),
}
}
// helpers
fn parse_par<'a, O, F, E>(
inner: F,
) -> impl Fn(Span<'a>) -> IResult<Span<'a>, O, E>
where
F: Fn(Span<'a>) -> IResult<Span<'a>, O, E>,
E: ParseError<Span<'a>>,
{
// delimited allows us to split up the input
// cut allwos us to consume the input (and prevent backtracking)
delimited(char('('), preceded(multispace0, inner), cut(char(')')))
}
fn math_eval(e: &SpanExpr) -> i32 {
match e.clone().1 {
Expr::Num(i) => i,
Expr::BinOp(op, l, r) => {
let lv = math_eval(&l);
let rv = math_eval(&r);
match op {
Op::Add => lv + rv,
Op::Sub => lv - rv,
Op::Mul => lv * rv,
Op::Div => lv / rv,
Op::Pow => lv.pow(rv as u32),
_ => unimplemented!(),
}
}
Expr::UnaryOp(op, e) => {
let e = math_eval(&e);
match op {
Op::Add => e,
Op::Sub => -e,
_ => unimplemented!(),
}
}
_ => unimplemented!(),
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum Ass {
Left,
Right,
}
fn get_prec(op: &Op) -> (u8, Ass) {
match op {
Op::Add => (1, Ass::Left),
Op::Sub => (1, Ass::Left),
Op::Mul => (2, Ass::Left),
Op::Div => (2, Ass::Left),
Op::Pow => (3, Ass::Right),
_ => unimplemented!(),
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment