diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c8266b04608df2303428520c0c40a85feb971203..b890df02db1dd0ff315ba1c81227df458faf256e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -50,6 +50,18 @@ "kind": "build", "isDefault": true } + }, + { + "type": "shell", + "label": "cargo run --example tmp", + "command": "cargo run --example tmp", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + } } ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2e2899c867e521b006534f256466ce84c5ec7f0a..701c2b4afb31729ad1531c5e6c4535bd04d95608 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "first" +name = "crust" version = "0.1.0" dependencies = [ "nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 5c023919cd2950b14c34c928319e263a6924dec6..7ee290b5dc918f4666d365b25d09891e041dda55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "first" +name = "crust" version = "0.1.0" authors = ["Per Lindgren <per.lindgren@ltu.se>"] edition = "2018" diff --git a/examples/tmp.rs b/examples/tmp.rs new file mode 100644 index 0000000000000000000000000000000000000000..ec7393d6cf7d267561468839a8a9a05f1ff2f81e --- /dev/null +++ b/examples/tmp.rs @@ -0,0 +1,185 @@ +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), + } +} + +fn main() { + test("- -1 + + 1", - -1 + 1); // rust does not allow + as a unary op (I do ;) + test("(-1-1)+(-1+3)", (-1 - 1) + (-1) + 3); + // just to check that right associative works (you don't need to implement pow) + test("2+3**2**3*5+1", 2 + 3i32.pow(2u32.pow(3)) * 5 + 1); + test("(12*2)/3-4", (12 * 2) / 3 - 4); + test("1*2+3", 1 * 2 + 3); + // just to check that we get a parse error + 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!(), + } +} diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000000000000000000000000000000000000..3c2f8b41459bac8bfe773ef894b26a9499f4fa64 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,33 @@ +// AST + +use nom_locate::LocatedSpan; + +type Span<'a> = LocatedSpan<&'a str>; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Op { + Eq, + Neq, + And, + Or, + Add, + Sub, + Mul, + Div, + Pow, + Not, +} + +type SpanOp<'a> = (Span<'a>, Op); + +#[derive(Debug, Clone, PartialEq)] +pub enum Expr { + Num(i32), + Par(Box<Expr>), + // Identifier + // Function application + BinOp(Op, Box<Expr>, Box<Expr>), + UnaryOp(Op, Box<Expr>), +} + +type SpanExpr<'a> = (Span<'a>, Expr); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..95787c26b88a62b43f78980ce41580778a495ae1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +// lib + +pub mod ast; \ No newline at end of file