feat(break-keyword): Break keyword impl + tests + pre-commit
parent
9aa7619638
commit
a41eba361d
|
@ -0,0 +1,15 @@
|
|||
repos:
|
||||
- repo: https://github.com/doublify/pre-commit-rust
|
||||
rev: v1.0
|
||||
hooks:
|
||||
- id: fmt
|
||||
- id: cargo-check
|
||||
- id: clippy
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: tests
|
||||
name: tests
|
||||
entry: cargo test
|
||||
language: system
|
||||
pass_filenames: false
|
||||
files: ^src/|^test_programs/
|
15
README.md
15
README.md
|
@ -20,7 +20,8 @@ if (x) {
|
|||
y = 5;
|
||||
while (x) {
|
||||
if (!y && z % 2) {
|
||||
x = false;
|
||||
z = 8;
|
||||
break;
|
||||
}
|
||||
y = y - 1;
|
||||
}
|
||||
|
@ -31,18 +32,17 @@ if (x) {
|
|||
```
|
||||
Final state of the program environment:
|
||||
```
|
||||
var: z => val: 15
|
||||
var: y => val: -1
|
||||
var: x => val: false
|
||||
var: y => val: 0
|
||||
var: x => val: true
|
||||
var: z => val: 8
|
||||
```
|
||||
<br>
|
||||
|
||||
## TODO
|
||||
### syntax
|
||||
- parsing-execution tests
|
||||
- else if
|
||||
- break
|
||||
- types declaration => manage type errors parsing time
|
||||
- refactor parsing error printing
|
||||
- types declaration
|
||||
- functions
|
||||
- unit tests
|
||||
- more keywords => do while
|
||||
|
@ -53,6 +53,7 @@ var: x => val: false
|
|||
- structs
|
||||
- iterator, for, continue keyword
|
||||
- list
|
||||
- import from other files
|
||||
### tools
|
||||
- live interpreter
|
||||
- LLVM IR translation
|
||||
|
|
|
@ -4,9 +4,11 @@ if (x) {
|
|||
y = 5;
|
||||
while (x) {
|
||||
if (!y && z % 2) {
|
||||
x = false;
|
||||
z = 8;
|
||||
break;
|
||||
}
|
||||
y = y - 1;
|
||||
z = 5;
|
||||
}
|
||||
} else {
|
||||
y = -8;
|
|
@ -1,14 +1,15 @@
|
|||
use std::{io::Read, env, fs::File};
|
||||
use ducklang::parser::ast::build_ast;
|
||||
use ducklang::interpreter::machine::Machine;
|
||||
use ducklang::parser::ast::build_ast;
|
||||
use std::{env, fs::File, io::Read};
|
||||
|
||||
pub fn main() {
|
||||
for arg in env::args().skip(1) {
|
||||
let mut f = File::open(&arg).expect(&format!("file {} not found", arg));
|
||||
let mut f = File::open(&arg).unwrap_or_else(|_| panic!("file {} not found", arg));
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content).expect(&format!("Error in reading file {}", arg));
|
||||
f.read_to_string(&mut content)
|
||||
.unwrap_or_else(|_| panic!("Error in reading file {}", arg));
|
||||
let ast = build_ast(&content);
|
||||
let mut machine = Machine::new_with_empty_env(ast);
|
||||
machine.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
use std::fmt::{Display, Result, Formatter};
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
use crate::parser::syntax::Node;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Environment {
|
||||
pub vars: HashMap<String, Box<Node>>
|
||||
pub vars: HashMap<String, Box<Node>>,
|
||||
}
|
||||
|
||||
impl Default for Environment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Environment {
|
||||
Environment{ vars: HashMap::new() }
|
||||
Environment {
|
||||
vars: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name: &str, node: Box<Node>) {
|
||||
|
@ -27,11 +35,11 @@ impl Environment {
|
|||
|
||||
impl Display for Environment {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
let vars: Vec<String> = self.vars.iter()
|
||||
.map(|(key, val)| {
|
||||
format!("var: {0} => val: {1}", key, val)
|
||||
})
|
||||
let vars: Vec<String> = self
|
||||
.vars
|
||||
.iter()
|
||||
.map(|(key, val)| format!("var: {0} => val: {1}", key, val))
|
||||
.collect();
|
||||
write!(f, "{}", vars.join("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::parser::syntax::Node;
|
||||
use super::environment::Environment;
|
||||
use crate::parser::syntax::Node;
|
||||
|
||||
pub trait Evaluate {
|
||||
fn evaluate(&self, environment: &mut Environment) -> Box<Node>;
|
||||
|
@ -8,15 +8,9 @@ pub trait Evaluate {
|
|||
impl Evaluate for Node {
|
||||
fn evaluate(&self, environment: &mut Environment) -> Box<Node> {
|
||||
match *self {
|
||||
Node::Number(v) => {
|
||||
Node::number(v)
|
||||
}
|
||||
Node::Boolean(v) => {
|
||||
Node::boolean(v)
|
||||
}
|
||||
Node::DoNothing => {
|
||||
Node::donothing()
|
||||
}
|
||||
Node::Number(v) => Node::number(v),
|
||||
Node::Boolean(v) => Node::boolean(v),
|
||||
Node::DoNothing => Node::donothing(),
|
||||
Node::Add(ref l, ref r) => {
|
||||
Node::number(l.evaluate(environment).value() + r.evaluate(environment).value())
|
||||
}
|
||||
|
@ -32,9 +26,7 @@ impl Evaluate for Node {
|
|||
Node::Modulo(ref l, ref r) => {
|
||||
Node::number(l.evaluate(environment).value() % r.evaluate(environment).value())
|
||||
}
|
||||
Node::Minus(ref t) => {
|
||||
Node::number(-t.evaluate(environment).value())
|
||||
}
|
||||
Node::Minus(ref t) => Node::number(-t.evaluate(environment).value()),
|
||||
Node::LessThan(ref l, ref r) => {
|
||||
Node::boolean(l.evaluate(environment).value() < r.evaluate(environment).value())
|
||||
}
|
||||
|
@ -59,15 +51,11 @@ impl Evaluate for Node {
|
|||
Node::And(ref l, ref r) => {
|
||||
Node::boolean(l.evaluate(environment).truth() && r.evaluate(environment).truth())
|
||||
}
|
||||
Node::Not(ref t) => {
|
||||
Node::boolean(!t.evaluate(environment).truth())
|
||||
}
|
||||
Node::Variable(ref name) => {
|
||||
environment.get(&name)
|
||||
}
|
||||
Node::Not(ref t) => Node::boolean(!t.evaluate(environment).truth()),
|
||||
Node::Variable(ref name) => environment.get(name),
|
||||
Node::Assign(ref name, ref expr) => {
|
||||
let reduce = expr.evaluate(environment);
|
||||
environment.add(name, reduce.clone());
|
||||
environment.add(name, reduce);
|
||||
Node::donothing()
|
||||
}
|
||||
Node::If(ref condition, ref consequence, ref alternative) => {
|
||||
|
@ -78,18 +66,23 @@ impl Evaluate for Node {
|
|||
}
|
||||
}
|
||||
Node::Sequence(ref head, ref more) => {
|
||||
head.evaluate(environment);
|
||||
more.evaluate(environment);
|
||||
Node::donothing()
|
||||
}
|
||||
Node::While(ref cond, ref body) => {
|
||||
if cond.evaluate(environment).truth() {
|
||||
body.evaluate(environment);
|
||||
self.evaluate(environment)
|
||||
} else {
|
||||
Node::donothing()
|
||||
if let Node::Break = *head.evaluate(environment) {
|
||||
return Node::break_keyword();
|
||||
}
|
||||
match *more.evaluate(environment) {
|
||||
Node::Break => Node::break_keyword(),
|
||||
_ => Node::donothing(),
|
||||
}
|
||||
}
|
||||
Node::While(ref cond, ref body) => {
|
||||
while cond.evaluate(environment).truth() {
|
||||
if let Node::Break = *body.evaluate(environment) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Node::donothing()
|
||||
}
|
||||
Node::Break => Node::break_keyword(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@ use crate::parser::syntax::Node;
|
|||
|
||||
pub struct Machine {
|
||||
pub expression: Box<Node>,
|
||||
pub environment: Environment
|
||||
pub environment: Environment,
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub fn new(expression: Box<Node>, environment: Environment) -> Machine {
|
||||
Machine{
|
||||
Machine {
|
||||
expression,
|
||||
environment,
|
||||
}
|
||||
|
@ -18,12 +18,15 @@ impl Machine {
|
|||
pub fn new_with_empty_env(expression: Box<Node>) -> Machine {
|
||||
Machine {
|
||||
expression,
|
||||
environment: Environment::new(),
|
||||
environment: Environment::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
self.expression.evaluate(&mut self.environment);
|
||||
println!("Final state of the program environment:\n{}", self.environment);
|
||||
println!(
|
||||
"Final state of the program environment:\n{}",
|
||||
self.environment
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,233 @@
|
|||
pub mod machine;
|
||||
pub mod environment;
|
||||
pub mod evaluate;
|
||||
pub mod environment;
|
||||
pub mod machine;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parser::{ast::build_ast, syntax::Node};
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
use super::{environment::Environment, machine::Machine};
|
||||
|
||||
// modify to inclue in main
|
||||
fn run_file(path: &str) -> Environment {
|
||||
let mut f = File::open(path).expect(&format!("file {} not found", path));
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.expect(&format!("Error in reading file {}", path));
|
||||
let ast = build_ast(&content);
|
||||
let mut machine = Machine::new_with_empty_env(ast);
|
||||
machine.run();
|
||||
machine.environment
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assign() {
|
||||
let mut environment = run_file("test_programs/assign.duck");
|
||||
let x = match *environment.get("x") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("x should be Node::Boolean"),
|
||||
};
|
||||
assert!(x == true);
|
||||
let y = match *environment.get("y") {
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("y should be Node::Number"),
|
||||
};
|
||||
assert!(y == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic_expr() {
|
||||
let mut environment = run_file("test_programs/arithmetic_expr.duck");
|
||||
let x = match *environment.get("x") {
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("x should be Node::Number"),
|
||||
};
|
||||
assert!(x == 2);
|
||||
let y = match *environment.get("y") {
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("y should be Node::Number"),
|
||||
};
|
||||
assert!(y == 3 + 4 * (x + 1));
|
||||
let z = match *environment.get("z") {
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("z should be Node::Number"),
|
||||
};
|
||||
assert!(z == x % (-3 / 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_expr() {
|
||||
let mut environment = run_file("test_programs/boolean_expr.duck");
|
||||
let a = match *environment.get("a") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("a should be Node::Boolean"),
|
||||
};
|
||||
assert!(a == (false || false));
|
||||
let b = match *environment.get("b") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("b should be Node::Boolean"),
|
||||
};
|
||||
assert!(b == (true || false));
|
||||
let c = match *environment.get("c") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("c should be Node::Boolean"),
|
||||
};
|
||||
assert!(c == (false || true));
|
||||
let d = match *environment.get("d") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("d should be Node::Boolean"),
|
||||
};
|
||||
assert!(d == (true || true));
|
||||
let e = match *environment.get("e") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("e should be Node::Boolean"),
|
||||
};
|
||||
assert!(e == (false && false));
|
||||
let f = match *environment.get("f") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("f should be Node::Boolean"),
|
||||
};
|
||||
assert!(f == (true && false));
|
||||
let g = match *environment.get("g") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("g should be Node::Boolean"),
|
||||
};
|
||||
assert!(g == (false && true));
|
||||
let h = match *environment.get("h") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("h should be Node::Boolean"),
|
||||
};
|
||||
assert!(h == (true && true));
|
||||
let i = match *environment.get("i") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("i should be Node::Boolean"),
|
||||
};
|
||||
assert!(i == !true);
|
||||
let j = match *environment.get("j") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("j should be Node::Boolean"),
|
||||
};
|
||||
assert!(j == !false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_expr() {
|
||||
let mut environment = run_file("test_programs/compare_expr.duck");
|
||||
let a = match *environment.get("a") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("a should be Node::Boolean"),
|
||||
};
|
||||
assert!(a == (1 < 2));
|
||||
let b = match *environment.get("b") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("b should be Node::Boolean"),
|
||||
};
|
||||
assert!(b == (2 < 1));
|
||||
let c = match *environment.get("c") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("c should be Node::Boolean"),
|
||||
};
|
||||
assert!(c == (2 < 2));
|
||||
let d = match *environment.get("d") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("d should be Node::Boolean"),
|
||||
};
|
||||
assert!(d == (1 > 2));
|
||||
let e = match *environment.get("e") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("e should be Node::Boolean"),
|
||||
};
|
||||
assert!(e == (2 > 1));
|
||||
let f = match *environment.get("f") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("f should be Node::Boolean"),
|
||||
};
|
||||
assert!(f == (2 > 2));
|
||||
let g = match *environment.get("g") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("g should be Node::Boolean"),
|
||||
};
|
||||
assert!(g == (1 <= 2));
|
||||
let h = match *environment.get("h") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("h should be Node::Boolean"),
|
||||
};
|
||||
assert!(h == (2 <= 1));
|
||||
let i = match *environment.get("i") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("i should be Node::Boolean"),
|
||||
};
|
||||
assert!(i == (2 <= 2));
|
||||
let j = match *environment.get("j") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("j should be Node::Boolean"),
|
||||
};
|
||||
assert!(j == (1 >= 2));
|
||||
let k = match *environment.get("k") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("k should be Node::Boolean"),
|
||||
};
|
||||
assert!(k == (2 >= 1));
|
||||
let l = match *environment.get("l") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("l should be Node::Boolean"),
|
||||
};
|
||||
assert!(l == (2 >= 2));
|
||||
let m = match *environment.get("m") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("m should be Node::Boolean"),
|
||||
};
|
||||
assert!(m == (1 == 2));
|
||||
let n = match *environment.get("n") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("n should be Node::Boolean"),
|
||||
};
|
||||
assert!(n == (2 == 2));
|
||||
let o = match *environment.get("o") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("o should be Node::Boolean"),
|
||||
};
|
||||
assert!(o == (1 != 2));
|
||||
let p = match *environment.get("p") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("p should be Node::Boolean"),
|
||||
};
|
||||
assert!(p == (2 != 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conditional() {
|
||||
let mut environment = run_file("test_programs/conditional.duck");
|
||||
let x = match *environment.get("x") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("x should be Node::Boolean"),
|
||||
};
|
||||
assert!(x == true);
|
||||
let y = match *environment.get("y") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("y should be Node::Boolean"),
|
||||
};
|
||||
assert!(y == false);
|
||||
let z = match *environment.get("z") {
|
||||
Node::Boolean(value) => value,
|
||||
_ => panic!("z should be Node::Boolean"),
|
||||
};
|
||||
assert!(z == true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_while_loop() {
|
||||
let mut environment = run_file("test_programs/while_loop.duck");
|
||||
let x = match *environment.get("x") {
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("x should be Node::Number"),
|
||||
};
|
||||
assert!(x == 5);
|
||||
let y = match *environment.get("y") {
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("y should be Node::Number"),
|
||||
};
|
||||
assert!(y == 5);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,4 @@ extern crate pest;
|
|||
extern crate pest_derive;
|
||||
|
||||
pub mod interpreter;
|
||||
pub mod parser;
|
||||
pub mod parser;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use pest::iterators::Pair;
|
||||
use pest::Parser;
|
||||
use pest::pratt_parser::PrattParser;
|
||||
use pest::Parser;
|
||||
|
||||
use super::syntax::Node;
|
||||
|
||||
|
@ -35,40 +35,44 @@ fn build_expr(pair: Pair<Rule>) -> Box<Node> {
|
|||
// TODO Rule::boolean
|
||||
rule => unreachable!("SimpleParser::climb expected atom, found {:?}", rule),
|
||||
})
|
||||
.map_infix(|lhs, op, rhs| {
|
||||
match op.as_rule() {
|
||||
Rule::op_add => Node::add(lhs, rhs),
|
||||
Rule::op_sub => Node::sub(lhs, rhs),
|
||||
Rule::op_mul => Node::mult(lhs, rhs),
|
||||
Rule::op_div => Node::div(lhs, rhs),
|
||||
Rule::op_mod => Node::modulo(lhs, rhs),
|
||||
Rule::op_lt => Node::lessthan(lhs, rhs),
|
||||
Rule::op_gt => Node::greatthan(lhs, rhs),
|
||||
Rule::op_eq => Node::equal(lhs, rhs),
|
||||
Rule::op_ge => Node::greatequal(lhs, rhs),
|
||||
Rule::op_le => Node::lessequal(lhs, rhs),
|
||||
Rule::op_ne => Node::notequal(lhs, rhs),
|
||||
Rule::op_or => Node::or(lhs, rhs),
|
||||
Rule::op_and => Node::and(lhs, rhs),
|
||||
rule => unreachable!("SimpleParser::climb expected infix operation, found {:?}", rule),
|
||||
}
|
||||
.map_infix(|lhs, op, rhs| match op.as_rule() {
|
||||
Rule::op_add => Node::add(lhs, rhs),
|
||||
Rule::op_sub => Node::sub(lhs, rhs),
|
||||
Rule::op_mul => Node::mult(lhs, rhs),
|
||||
Rule::op_div => Node::div(lhs, rhs),
|
||||
Rule::op_mod => Node::modulo(lhs, rhs),
|
||||
Rule::op_lt => Node::lessthan(lhs, rhs),
|
||||
Rule::op_gt => Node::greatthan(lhs, rhs),
|
||||
Rule::op_eq => Node::equal(lhs, rhs),
|
||||
Rule::op_ge => Node::greatequal(lhs, rhs),
|
||||
Rule::op_le => Node::lessequal(lhs, rhs),
|
||||
Rule::op_ne => Node::notequal(lhs, rhs),
|
||||
Rule::op_or => Node::or(lhs, rhs),
|
||||
Rule::op_and => Node::and(lhs, rhs),
|
||||
rule => unreachable!(
|
||||
"SimpleParser::climb expected infix operation, found {:?}",
|
||||
rule
|
||||
),
|
||||
})
|
||||
.map_prefix(|op, rhs| match op.as_rule() {
|
||||
Rule::op_neg => Node::minus(rhs),
|
||||
Rule::op_not => Node::not(rhs),
|
||||
rule => unreachable!("SimpleParser::climb expected prefix operation, found {:?}", rule)
|
||||
rule => unreachable!(
|
||||
"SimpleParser::climb expected prefix operation, found {:?}",
|
||||
rule
|
||||
),
|
||||
})
|
||||
.parse(pair.into_inner())
|
||||
}
|
||||
|
||||
fn build_if_cond(pair: Pair<Rule>) -> Box<Node> {
|
||||
fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Box<Node> {
|
||||
// println!("build_if_cond::{:#?}", pair);
|
||||
let mut inner = pair.into_inner();
|
||||
let cond = build_expr(inner.next().unwrap());
|
||||
let thenstmt = build_stats(inner.next().unwrap());
|
||||
let thenstmt = build_stats(inner.next().unwrap(), in_loop);
|
||||
match inner.next() {
|
||||
Some(stmt) => Node::if_cond(cond, thenstmt, build_stats(stmt)),
|
||||
None => Node::if_cond(cond, thenstmt, Node::donothing())
|
||||
Some(stmt) => Node::if_cond(cond, thenstmt, build_stats(stmt, in_loop)),
|
||||
None => Node::if_cond(cond, thenstmt, Node::donothing()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +80,7 @@ fn build_while_loop(pair: Pair<Rule>) -> Box<Node> {
|
|||
// println!("build_while_loop::{:#?}", pair);
|
||||
let mut inner = pair.into_inner();
|
||||
let cond = build_expr(inner.next().unwrap());
|
||||
let stmt = build_stats(inner.next().unwrap());
|
||||
let stmt = build_stats(inner.next().unwrap(), true);
|
||||
Node::while_loop(cond, stmt)
|
||||
}
|
||||
|
||||
|
@ -88,28 +92,60 @@ fn build_assign(pair: Pair<Rule>) -> Box<Node> {
|
|||
Node::assign(lhs, rhs)
|
||||
}
|
||||
|
||||
fn build_stat(pair: Pair<Rule>) -> Box<Node> {
|
||||
// get parsing errors out of this file
|
||||
// create a error print for DuckParser error
|
||||
// todo => put filename
|
||||
fn parsing_error(pair: Pair<Rule>, error: &str) -> String {
|
||||
let span = pair.as_span();
|
||||
|
||||
let (err_line, err_col) = span.start_pos().line_col();
|
||||
let col_whitespaces = " ".repeat(err_line.to_string().len());
|
||||
|
||||
let span_len = span.end() - span.start();
|
||||
let underline_chars = "^".repeat(span_len);
|
||||
|
||||
let error_print = vec![
|
||||
format!("\nParsing error at --> {}:{}", err_line, err_col),
|
||||
format!("{} |", col_whitespaces),
|
||||
format!("{} | {}", err_line, span.as_str()),
|
||||
format!("{} | {}", col_whitespaces, underline_chars),
|
||||
format!("{} |", col_whitespaces),
|
||||
format!("{} = {}", col_whitespaces, error),
|
||||
];
|
||||
error_print.join("\n")
|
||||
}
|
||||
|
||||
fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Box<Node> {
|
||||
match pair.as_rule() {
|
||||
Rule::stat_assign => build_assign(pair),
|
||||
Rule::stat_if => build_if_cond(pair),
|
||||
Rule::stat_if => build_if_cond(pair, in_loop),
|
||||
Rule::stat_while => build_while_loop(pair),
|
||||
Rule::stat_break => {
|
||||
if in_loop {
|
||||
return Node::break_keyword();
|
||||
}
|
||||
panic!("{}", parsing_error(pair, "break keyword while not in loop"))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_stats(pair: Pair<Rule>) -> Box<Node> {
|
||||
fn build_stats(pair: Pair<Rule>, in_loop: bool) -> Box<Node> {
|
||||
let inner = pair.into_inner();
|
||||
let nodes : Vec<_> = inner
|
||||
let nodes: Vec<_> = inner
|
||||
.into_iter()
|
||||
.map(|pair| build_stat(pair))
|
||||
.map(|pair| build_stat(pair, in_loop))
|
||||
.collect();
|
||||
// println!("{}", nodes.iter().fold(String::new(), |acc, node| acc + &node.to_string() + "; "));
|
||||
nodes.iter().rev().fold(Node::donothing(), |acc, node| Node::sequence(node.clone(), acc))
|
||||
nodes.iter().rev().fold(Node::donothing(), |acc, node| {
|
||||
Node::sequence(node.clone(), acc)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_ast(content: &str) -> Box<Node> {
|
||||
let pair = DuckParser::parse(Rule::program, content)
|
||||
.unwrap_or_else(|e| panic!("{}", e))
|
||||
.next().unwrap();
|
||||
return build_stats(pair);
|
||||
}
|
||||
.unwrap_or_else(|e| panic!("{}", e))
|
||||
.next()
|
||||
.unwrap();
|
||||
build_stats(pair, false)
|
||||
}
|
||||
|
|
|
@ -47,12 +47,13 @@ value = _{ boolean | number | variable | "(" ~ expr ~ ")" }
|
|||
term = { op_unary* ~ value }
|
||||
expr = { term ~ (op_binary ~ term)* }
|
||||
|
||||
stat_break = { "break" ~ ";" }
|
||||
stat_assign = { variable ~ "=" ~ expr ~ ";" }
|
||||
stat_while = { "while" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" }
|
||||
stat_if = { ("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~ stats ~ "}" ) |
|
||||
("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}") }
|
||||
|
||||
stat = _{ ( stat_if | stat_while | stat_assign ) }
|
||||
stat = _{ ( stat_if | stat_while | stat_assign | stat_break ) }
|
||||
|
||||
stats = { (stat)* }
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::{io::Read, env, fs::File};
|
||||
use ducklang::parser::ast::build_ast;
|
||||
use std::{env, fs::File, io::Read};
|
||||
|
||||
pub fn main() {
|
||||
for arg in env::args().skip(1) {
|
||||
let mut f = File::open(&arg).expect(&format!("file {} not found", arg));
|
||||
let mut f = File::open(&arg).unwrap_or_else(|_| panic!("file {} not found", arg));
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content).expect(&format!("Error in reading file {}", arg));
|
||||
f.read_to_string(&mut content)
|
||||
.unwrap_or_else(|_| panic!("Error in reading file {}", arg));
|
||||
build_ast(&content);
|
||||
println!("{} is a valid ducklang program", arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,54 @@
|
|||
pub mod ast;
|
||||
pub mod syntax;
|
||||
pub mod ast;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parser::ast::build_ast;
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
// modify to inclue in main
|
||||
fn parse_file(path: &str) {
|
||||
let mut f = File::open(path).expect(&format!("file {} not found", path));
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.expect(&format!("Error in reading file {}", path));
|
||||
build_ast(&content);
|
||||
}
|
||||
|
||||
// can loop with some tests libs ?
|
||||
#[test]
|
||||
fn test_assign() {
|
||||
parse_file("test_programs/assign.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic_expr() {
|
||||
parse_file("test_programs/arithmetic_expr.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_expr() {
|
||||
parse_file("test_programs/boolean_expr.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_expr() {
|
||||
parse_file("test_programs/compare_expr.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conditional() {
|
||||
parse_file("test_programs/conditional.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_while_loop() {
|
||||
parse_file("test_programs/while_loop.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_break_not_loop() {
|
||||
parse_file("test_programs/break_not_loop.duck");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![allow(clippy::should_implement_trait)]
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -25,6 +26,7 @@ pub enum Node {
|
|||
If(Box<Node>, Box<Node>, Box<Node>),
|
||||
Sequence(Box<Node>, Box<Node>),
|
||||
While(Box<Node>, Box<Node>),
|
||||
Break,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
|
@ -88,7 +90,11 @@ impl Node {
|
|||
pub fn assign(name: &str, expr: Box<Node>) -> Box<Node> {
|
||||
Box::new(Node::Assign(name.to_string(), expr))
|
||||
}
|
||||
pub fn if_cond(condition: Box<Node>, consequence: Box<Node>, alternative: Box<Node>) -> Box<Node> {
|
||||
pub fn if_cond(
|
||||
condition: Box<Node>,
|
||||
consequence: Box<Node>,
|
||||
alternative: Box<Node>,
|
||||
) -> Box<Node> {
|
||||
Box::new(Node::If(condition, consequence, alternative))
|
||||
}
|
||||
pub fn sequence(head: Box<Node>, more: Box<Node>) -> Box<Node> {
|
||||
|
@ -97,19 +103,22 @@ impl Node {
|
|||
pub fn while_loop(cond: Box<Node>, body: Box<Node>) -> Box<Node> {
|
||||
Box::new(Node::While(cond, body))
|
||||
}
|
||||
pub fn break_keyword() -> Box<Node> {
|
||||
Box::new(Node::Break)
|
||||
}
|
||||
|
||||
pub fn value(&self) -> i64 {
|
||||
match *self {
|
||||
Node::Number(value) => { value },
|
||||
_ => panic!("Not a number: {:#?}", *self)
|
||||
Node::Number(value) => value,
|
||||
_ => panic!("Not a number: {:#?}", *self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truth(&self) -> bool {
|
||||
match *self {
|
||||
Node::Boolean(b) => { b },
|
||||
Node::Number(value) => { value != 0 },
|
||||
_ => panic!("Cannot eval truth: {:#?}", *self)
|
||||
Node::Boolean(b) => b,
|
||||
Node::Number(value) => value != 0,
|
||||
_ => panic!("Cannot eval truth: {:#?}", *self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,9 +146,14 @@ impl Display for Node {
|
|||
Node::Variable(ref value) => write!(f, "{}", value),
|
||||
Node::DoNothing => write!(f, "do-nothing"),
|
||||
Node::Assign(ref name, ref expr) => write!(f, "{0} = {1}", name, expr),
|
||||
Node::If(ref condition, ref consequence, ref alternative) => write!(f, "if ({0}) {1} else {2}", condition, consequence, alternative),
|
||||
Node::If(ref condition, ref consequence, ref alternative) => write!(
|
||||
f,
|
||||
"if ({0}) {1} else {2}",
|
||||
condition, consequence, alternative
|
||||
),
|
||||
Node::Sequence(ref head, ref more) => write!(f, "{0}; {1}", head, more),
|
||||
Node::While(ref cond, ref body) => write!(f, "while ({0}) {1}", cond, body)
|
||||
Node::While(ref cond, ref body) => write!(f, "while ({0}) {1}", cond, body),
|
||||
Node::Break => write!(f, "break"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
x = 2;
|
||||
y = 3 + 4 * (x + 1);
|
||||
z = x % (-3 / 2);
|
|
@ -0,0 +1,2 @@
|
|||
x = true;
|
||||
y = 1;
|
|
@ -0,0 +1,10 @@
|
|||
a = false || false;
|
||||
b = true || false;
|
||||
c = false || true;
|
||||
d = true || true;
|
||||
e = false && false;
|
||||
f = true && false;
|
||||
g = false && true;
|
||||
h = true && true;
|
||||
i = !true;
|
||||
j = !false;
|
|
@ -0,0 +1,5 @@
|
|||
x = 2;
|
||||
while (x) {
|
||||
x = x - 1;
|
||||
}
|
||||
break;
|
|
@ -0,0 +1,16 @@
|
|||
a = 1 < 2;
|
||||
b = 2 < 1;
|
||||
c = 2 < 2;
|
||||
d = 1 > 2;
|
||||
e = 2 > 1;
|
||||
f = 2 > 2;
|
||||
g = 1 <= 2;
|
||||
h = 2 <= 1;
|
||||
i = 2 <= 2;
|
||||
j = 1 >= 2;
|
||||
k = 2 >= 1;
|
||||
l = 2 >= 2;
|
||||
m = 1 == 2;
|
||||
n = 2 == 2;
|
||||
o = 1 != 2;
|
||||
p = 2 != 2;
|
|
@ -0,0 +1,14 @@
|
|||
if (true) {
|
||||
x = true;
|
||||
} else {
|
||||
x = false;
|
||||
}
|
||||
if (false) {
|
||||
y = true;
|
||||
} else {
|
||||
y = false;
|
||||
}
|
||||
z = false;
|
||||
if (true) {
|
||||
z = true;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
x = 0;
|
||||
y = 0;
|
||||
|
||||
while (x < 5) {
|
||||
x = x + 1;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
y = y + 1;
|
||||
if (y == 5) {
|
||||
break;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue