feat(escaping): escaping + WIP better tests

main
flavien 2023-12-14 23:12:30 +01:00
parent 43b564ab0d
commit 6aec24670d
No known key found for this signature in database
27 changed files with 763 additions and 346 deletions

View File

@ -58,7 +58,8 @@ var: z => val: 8
### syntax
- functions OK
- print statement OK
- string / char => indexing OK
- string / char => indexing OK / escaping OK
- local scope for { statements } ?
- array / map => iterators
- read / write
- types declaration

View File

@ -14,7 +14,7 @@ impl ValueCompute for Value {
fn int(&self) -> Result<i64, String> {
match self {
Value::Number(n) => Ok(*n),
_ => Err(format!("{} is not a number", self)),
_ => Err(format!("number intended here, not {}", self.get_type())),
}
}
fn truth(&self) -> Result<bool, String> {
@ -23,19 +23,19 @@ impl ValueCompute for Value {
Value::Number(value) => Ok(*value != 0),
Value::String(string) => Ok(!string.is_empty()),
Value::Char(c) => Ok(*c != b'\0'),
_ => Err(format!("{} is not a logical value", self)),
_ => Err(format!("boolean intended here, not {}", self.get_type())),
}
}
fn str(&self) -> Result<Rc<Vec<u8>>, String> {
match self {
Value::String(s) => Ok(s.clone()),
_ => Err(format!("{} is not a string", self)),
_ => Err(format!("string intended here, not {}", self.get_type())),
}
}
fn char(&self) -> Result<u8, String> {
match self {
Value::Char(c) => Ok(*c),
_ => Err(format!("{} is not a char", self)),
_ => Err(format!("char intended here, not {}", self.get_type())),
}
}
}
@ -49,18 +49,6 @@ impl UnaryCompute for UnaryOp {
Ok(match self {
UnaryOp::Minus => Value::Number(-expr.evaluate(env)?.int()?),
UnaryOp::Not => Value::Boolean(!expr.evaluate(env)?.truth()?),
UnaryOp::Index(index) => {
let index = index.evaluate(env)?.int()?;
let value = expr.evaluate(env)?.str()?;
if index < 0 || index >= value.len() as i64 {
return Err(format!(
"Index {} out of bounds for string of length {}",
index,
value.len()
));
}
Value::Char(value[index as usize])
}
})
}
}
@ -84,7 +72,10 @@ impl BinaryCompute for BinaryOp {
) -> Result<Value, String> {
match self {
BinaryOp::Addition => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l + r)),
(Value::Number(l), Value::Number(r)) => match l.checked_add(r) {
Some(n) => Ok(Value::Number(n)),
None => Err(format!("Integer overflow: {} + {}", l, r)),
},
(Value::String(l), Value::Char(r)) => {
let mut copy = Vec::with_capacity(l.len() + 1);
copy.extend_from_slice(&l);
@ -98,20 +89,25 @@ impl BinaryCompute for BinaryOp {
Ok(Value::String(Rc::new(copy)))
}
(Value::Char(l), Value::Char(r)) => Ok(Value::Char(l + r)),
(Value::Char(l), Value::Number(r)) => Ok(Value::Char(l + r as u8)),
(Value::Number(l), Value::Char(r)) => Ok(Value::Char(l as u8 + r)),
(Value::Char(c), Value::Number(n)) | (Value::Number(n), Value::Char(c)) => {
match c.checked_add(n as u8) {
Some(c) => Ok(Value::Char(c)),
None => Err(format!("Char overflow: '{}' + {}", char::from(c), n)),
}
}
(Value::String(l), Value::String(r)) => {
let new = l.iter().chain(r.iter()).copied().collect();
Ok(Value::String(Rc::new(new)))
}
(l, r) => Err(format!("Cannot add {} to {}", l, r)),
},
// TODO check overflow
BinaryOp::Subtraction => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l - r)),
(Value::Char(l), Value::Char(r)) => Ok(Value::Char(l - r)),
(Value::Char(l), Value::Number(r)) => Ok(Value::Char(l - r as u8)),
(Value::Number(l), Value::Char(r)) => Ok(Value::Char(l as u8 - r)),
(l, r) => Err(format!("Cannot subtract {} from {}", r, l)),
(l, r) => Err(format!("Cannot subtract {} from {}", l, r)),
},
BinaryOp::Multiplication => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l * r)),
@ -124,11 +120,23 @@ impl BinaryCompute for BinaryOp {
(l, r) => Err(format!("Cannot multiply {} by {}", l, r)),
},
BinaryOp::Division => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l / r)),
(Value::Number(l), Value::Number(r)) => {
if r == 0 {
Err("attempt to divide by zero".to_string())
} else {
Ok(Value::Number(l / r))
}
}
(l, r) => Err(format!("Cannot divide {} by {}", l, r)),
},
BinaryOp::Modulo => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l % r)),
(Value::Number(l), Value::Number(r)) => {
if r == 0 {
Err("attempt to calculate the remainder with a divisor of zero".to_string())
} else {
Ok(Value::Number(l % r))
}
}
(l, r) => Err(format!("Cannot modulo {} by {}", l, r)),
},
BinaryOp::Equal => match (left.evaluate(env)?, right.evaluate(env)?) {

View File

@ -18,6 +18,11 @@ pub fn main() {
}
};
let mut machine = Machine::new();
let ret = machine.run(ast).expect("execution failed");
exit(ret as i32);
match machine.run(ast) {
Ok(ret) => exit(ret as i32),
Err(e) => {
eprintln!("Runtime error: {}", e);
exit(1)
}
};
}

View File

@ -13,7 +13,7 @@ pub trait Environment {
fn share_global(&mut self) -> &mut HashMap<String, Value>;
}
#[derive(Default)]
#[derive(Default, Debug)]
pub struct GlobalScope {
pub global: HashMap<String, Value>,
}

View File

@ -1,5 +1,5 @@
use super::{
compute::{BinaryCompute, UnaryCompute},
compute::{BinaryCompute, UnaryCompute, ValueCompute},
environment::{Environment, LocalScope},
machine::{ControlFlow, Execution},
};
@ -13,10 +13,26 @@ impl Evaluate for Expr {
fn evaluate(&self, env: &mut dyn Environment) -> Result<Value, String> {
match *self {
Expr::Litteral(ref value) => Ok(value.clone()),
Expr::Variable(ref name) => match env.get(name) {
Some(value) => Ok(value.clone()),
None => Err(format!("Variable {} not found", name)),
},
Expr::Variable(ref name, ref i_expr) => {
if let Some(i_expr) = i_expr {
let i = i_expr.evaluate(env)?.int()?;
let value = env
.get(name)
.ok_or_else(|| format!("Variable {} not found", name))?
.str()?;
if i < 0 || i >= value.len() as i64 {
return Err(format!(
"Index {} out of bounds for string of length {}",
i,
value.len()
));
}
Ok(Value::Char(value[i as usize]))
} else {
env.get(name)
.ok_or_else(|| format!("Variable {} not found", name))
}
}
Expr::UnaryOp(ref term, ref op) => op.compute(env, term),
Expr::BinaryOp(ref lhs, ref rhs, ref op) => op.compute(env, lhs, rhs),
Expr::Call(ref name, ref args) => {

View File

@ -16,13 +16,13 @@ pub enum ControlFlow {
}
pub trait Execution {
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String>;
fn execute(&self, env: &mut dyn Environment) -> Result<ControlFlow, String>;
}
impl Execution for Vec<Stat> {
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String> {
for statement in self {
match statement.execute(environment)? {
fn execute(&self, env: &mut dyn Environment) -> Result<ControlFlow, String> {
for stat in self {
match stat.execute(env)? {
ControlFlow::Next => (),
flow @ (ControlFlow::Break | ControlFlow::Continue) => return Ok(flow),
ControlFlow::Return(value) => return Ok(ControlFlow::Return(value)),
@ -33,57 +33,74 @@ impl Execution for Vec<Stat> {
}
impl Execution for Stat {
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String> {
fn execute(&self, env: &mut dyn Environment) -> Result<ControlFlow, String> {
match *self {
Stat::Condition(ref condition, ref consequence, ref alternative) => {
if condition.evaluate(environment)?.truth()? {
return consequence.execute(environment);
} else if let Some(statements) = alternative {
return statements.execute(environment);
Stat::Condition(ref cond, ref then, ref alt) => {
if cond.evaluate(env)?.truth()? {
return then.execute(env);
} else if let Some(stats) = alt {
return stats.execute(env);
}
Ok(ControlFlow::Next)
}
Stat::Loop(ref cond, ref body) => {
while cond.evaluate(environment)?.truth()? {
if let ControlFlow::Break = body.execute(environment)? {
while cond.evaluate(env)?.truth()? {
if let ControlFlow::Break = body.execute(env)? {
break;
}
}
Ok(ControlFlow::Next)
}
Stat::Assignment(ref name, ref index, ref expr) => {
if let Some(index) = index {
let index = index.evaluate(environment)?.int()?;
let value = expr.evaluate(environment)?.char()?;
let original = environment
Stat::Assignment(ref name, ref i_expr, ref expr) => {
if let Some(i_expr) = i_expr {
let string = env
.get(name)
.ok_or_else(|| format!("Variable {} not found", name))?
.str()?;
if index < 0 || index >= original.len() as i64 {
let i = i_expr.evaluate(env)?.int()?;
if i < 0 || i >= string.len() as i64 {
return Err(format!(
"Index {} out of bounds for string of length {}",
index,
original.len()
i,
string.len()
));
}
let mut copy = (*original).clone();
copy[index as usize] = value;
environment.add(name, Value::String(Rc::new(copy)));
let mut copy = (*string).clone();
copy[i as usize] = expr.evaluate(env)?.char()?;
env.add(name, Value::String(Rc::new(copy)));
} else {
let value = expr.evaluate(environment)?;
environment.add(name, value);
if env.get(name).is_none() {
return Err(format!("Variable {} not found", name));
}
let value = expr.evaluate(env)?;
env.add(name, value);
}
Ok(ControlFlow::Next)
}
Stat::Break => Ok(ControlFlow::Break),
Stat::Continue => Ok(ControlFlow::Continue),
Stat::Define(ref name, ref args, ref body) => {
environment.add(name, Value::Function(args.clone(), body.clone()));
Stat::FunDefine(ref name, ref args, ref body) => {
if env.get(name).is_some() {
return Err(format!("Function {} already defined", name));
}
env.add(name, Value::Function(args.clone(), body.clone()));
Ok(ControlFlow::Next)
}
Stat::Return(ref expr) => Ok(ControlFlow::Return(expr.evaluate(environment)?)),
Stat::VarDefine(ref name, ref expr) => {
if env.get(name).is_some() {
return Err(format!("Variable {} already defined", name));
}
if let Some(expr) = expr {
let value = expr.evaluate(env)?;
env.add(name, value);
} else {
env.add(name, Value::None);
}
Ok(ControlFlow::Next)
}
Stat::Return(ref expr) => Ok(ControlFlow::Return(expr.evaluate(env)?)),
Stat::Print(ref expr) => {
println!("{}", expr.evaluate(environment)?);
println!("{}", expr.evaluate(env)?);
Ok(ControlFlow::Next)
}
}
@ -91,7 +108,7 @@ impl Execution for Stat {
}
pub struct Machine {
pub environment: GlobalScope,
pub env: GlobalScope,
}
impl Default for Machine {
@ -103,17 +120,17 @@ impl Default for Machine {
impl Machine {
pub fn new() -> Machine {
Machine {
environment: GlobalScope::default(),
env: GlobalScope::default(),
}
}
pub fn new_with_env(environment: GlobalScope) -> Machine {
Machine { environment }
pub fn new_with_env(env: GlobalScope) -> Machine {
Machine { env }
}
pub fn run(&mut self, statements: Vec<Stat>) -> Result<i64, String> {
for statement in statements {
if let ControlFlow::Return(val) = statement.execute(&mut self.environment)? {
pub fn run(&mut self, stats: Vec<Stat>) -> Result<i64, String> {
for stat in stats {
if let ControlFlow::Return(val) = stat.execute(&mut self.env)? {
return val.int();
}
}

View File

@ -13,290 +13,482 @@ mod tests {
machine::Machine,
};
// modify to inclue in main
fn run_file(path: &str) -> GlobalScope {
fn run_file(path: &str) -> Result<GlobalScope, String> {
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).unwrap();
let mut machine = Machine::new();
machine.run(ast).expect("execution failed");
machine.environment
match machine.run(ast) {
Ok(_) => Ok(machine.env),
Err(error) => Err(error),
}
}
fn run_string(string: &str) -> Result<GlobalScope, String> {
let ast = build_ast(string).unwrap();
let mut machine = Machine::new();
match machine.run(ast) {
Ok(_) => Ok(machine.env),
Err(error) => Err(error),
}
}
#[test]
fn test_assign() {
let environment = run_file("test_programs/assign.duck");
fn test_define_assign() {
let environment = run_file("test_programs/define_assign.duck").unwrap();
let x = match environment.get("x") {
Some(Value::Boolean(value)) => value,
_ => panic!("x should be Value::Boolean"),
};
assert!(x == true);
assert_eq!(x, true);
let y = match environment.get("y") {
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 1);
assert_eq!(y, 1);
}
#[test]
fn test_unknown_variable() {
let environment = run_file("test_programs/unknown_variable.duck");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Variable y not found")
}
#[test]
fn test_double_define_var() {
let environment = run_file("test_programs/double_define_var.duck");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Variable x already defined")
}
#[test]
fn test_double_define_fn() {
let environment = run_file("test_programs/double_define_fn.duck");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Function test already defined")
}
#[test]
fn test_arithmetic_expr() {
let environment = run_file("test_programs/arithmetic_expr.duck");
let environment = run_file("test_programs/arithmetic_expr.duck").unwrap();
let x = match environment.get("x") {
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert!(x == 2);
assert_eq!(x, 2);
let y = match environment.get("y") {
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 3 + 4 * (x + 1));
assert_eq!(y, 3 + 4 * (x + 1));
let z = match environment.get("z") {
Some(Value::Number(value)) => value,
_ => panic!("z should be Value::Number"),
};
assert!(z == x % (-3 / 2));
assert_eq!(z, x % (-3 / 2));
}
#[test]
fn test_arithmetic_add_nb_nb() {
let environment = run_string("var x = 3 + 4;").unwrap();
let x = match environment.get("x") {
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(x, 7);
}
#[test]
fn test_arithmetic_add_nb_nb_overflow() {
let environment = run_string("var x = 4611686018427387904 + 4611686018427387904;");
assert!(environment.is_err());
assert_eq!(
environment.unwrap_err(),
"Integer overflow: 4611686018427387904 + 4611686018427387904"
)
}
#[test]
fn test_arithmetic_add_char_nb() {
let environment = run_string("var x = 'a' + 3;").unwrap();
let x = match environment.get("x") {
Some(Value::Char(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(x, b'a' + 3);
}
#[test]
fn test_arithmetic_add_char_nb_overflow() {
let environment = run_string("var x = 'z' + 200;");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Char overflow: 'z' + 200")
}
#[test]
fn test_arithmetic_add_char_char() {
let environment = run_string("var x = 'a' + 'b';").unwrap();
let x = match environment.get("x") {
Some(Value::Char(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(x, b'a' + b'b');
}
#[test]
fn test_arithmetic_add_str_str() {
let environment = run_string("var x = \"hello\" + \"world\";").unwrap();
let x = match environment.get("x") {
Some(Value::String(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(str::from_utf8(&x).unwrap(), "helloworld");
}
#[test]
fn test_arithmetic_add_str_char() {
let environment = run_string("var x = \"hello\" + 'a';").unwrap();
let x = match environment.get("x") {
Some(Value::String(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(str::from_utf8(&x).unwrap(), "helloa");
}
#[test]
fn test_arithmetic_add_char_str() {
let environment = run_string("var x = 'a' + \"hello\";").unwrap();
let x = match environment.get("x") {
Some(Value::String(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(str::from_utf8(&x).unwrap(), "ahello");
}
#[test]
fn test_arithmetic_add_nb_str() {
let environment = run_string("var x = 3 + \"hello\";");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Cannot add 3 to hello")
}
#[test]
fn test_arithmetic_add_bool_str() {
let environment = run_string("var x = true + \"hello\";");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Cannot add true to hello")
}
#[test]
fn test_arithmetic_add_nb_bool() {
let environment = run_string("var x = 3 + true;");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Cannot add 3 to true")
}
#[test]
fn test_arithmetic_add_bool_bool() {
let environment = run_string("var x = true + false;");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Cannot add true to false")
}
#[test]
fn test_arithmetic_sub_nb_nb() {
let environment = run_string("var x = 3 - 4;").unwrap();
let x = match environment.get("x") {
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert_eq!(x, -1);
}
// TODO continue arithmetic sub tests
// #[test]
// fn test_arithmetic_sub_nb_char() {
// let environment = run_string("var x = 108 - 'a';").unwrap();
// let x = match environment.get("x") {
// Some(Value::Char(value)) => value,
// _ => panic!("x should be Value::Number"),
// };
// assert_eq!(x, 108 - b'a');
// }
// #[test]
// fn test_arithmetic_sub_char_char() {
// let environment = run_string("var x = 'a' - 'b';").unwrap();
// let x = match environment.get("x") {
// Some(Value::Char(value)) => value,
// _ => panic!("x should be Value::Number"),
// };
// assert_eq!(x, b'a' - b'b');
// }
#[test]
fn test_arithmetic_sub_str_str() {
let environment = run_string("var x = \"hello\" - \"world\";");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "Cannot subtract hello from world")
}
#[test]
fn test_arithmetic_error_div_zero() {
let environment = run_string("var x = 3 / 0;");
assert!(environment.is_err());
assert_eq!(environment.unwrap_err(), "attempt to divide by zero")
}
#[test]
fn test_arithmetic_error_mod_zero() {
let environment = run_string("var x = 3 % 0;");
assert!(environment.is_err());
assert_eq!(
environment.unwrap_err(),
"attempt to calculate the remainder with a divisor of zero"
)
}
#[test]
fn test_boolean_expr() {
let environment = run_file("test_programs/boolean_expr.duck");
let environment = run_file("test_programs/boolean_expr.duck").unwrap();
let a = match environment.get("a") {
Some(Value::Boolean(value)) => value,
_ => panic!("a should be Value::Boolean"),
};
assert!(a == (false || false));
assert_eq!(a, (false || false));
let b = match environment.get("b") {
Some(Value::Boolean(value)) => value,
_ => panic!("b should be Value::Boolean"),
};
assert!(b == (true || false));
assert_eq!(b, (true || false));
let c = match environment.get("c") {
Some(Value::Boolean(value)) => value,
_ => panic!("c should be Value::Boolean"),
};
assert!(c == (false || true));
assert_eq!(c, (false || true));
let d = match environment.get("d") {
Some(Value::Boolean(value)) => value,
_ => panic!("d should be Value::Boolean"),
};
assert!(d == (true || true));
assert_eq!(d, (true || true));
let e = match environment.get("e") {
Some(Value::Boolean(value)) => value,
_ => panic!("e should be Value::Boolean"),
};
assert!(e == (false && false));
assert_eq!(e, (false && false));
let f = match environment.get("f") {
Some(Value::Boolean(value)) => value,
_ => panic!("f should be Value::Boolean"),
};
assert!(f == (true && false));
assert_eq!(f, (true && false));
let g = match environment.get("g") {
Some(Value::Boolean(value)) => value,
_ => panic!("g should be Value::Boolean"),
};
assert!(g == (false && true));
assert_eq!(g, (false && true));
let h = match environment.get("h") {
Some(Value::Boolean(value)) => value,
_ => panic!("h should be Value::Boolean"),
};
assert!(h == (true && true));
assert_eq!(h, (true && true));
let i = match environment.get("i") {
Some(Value::Boolean(value)) => value,
_ => panic!("i should be Value::Boolean"),
};
assert!(i == !true);
assert_eq!(i, !true);
let j = match environment.get("j") {
Some(Value::Boolean(value)) => value,
_ => panic!("j should be Value::Boolean"),
};
assert!(j == !false);
assert_eq!(j, !false);
}
#[test]
fn test_compare_expr() {
let environment = run_file("test_programs/compare_expr.duck");
let environment = run_file("test_programs/compare_expr.duck").unwrap();
let a = match environment.get("a") {
Some(Value::Boolean(value)) => value,
_ => panic!("a should be Value::Boolean"),
};
assert!(a == (1 < 2));
assert_eq!(a, (1 < 2));
let b = match environment.get("b") {
Some(Value::Boolean(value)) => value,
_ => panic!("b should be Value::Boolean"),
};
assert!(b == (2 < 1));
assert_eq!(b, (2 < 1));
let c = match environment.get("c") {
Some(Value::Boolean(value)) => value,
_ => panic!("c should be Value::Boolean"),
};
assert!(c == (2 < 2));
assert_eq!(c, (2 < 2));
let d = match environment.get("d") {
Some(Value::Boolean(value)) => value,
_ => panic!("d should be Value::Boolean"),
};
assert!(d == (1 > 2));
assert_eq!(d, (1 > 2));
let e = match environment.get("e") {
Some(Value::Boolean(value)) => value,
_ => panic!("e should be Value::Boolean"),
};
assert!(e == (2 > 1));
assert_eq!(e, (2 > 1));
let f = match environment.get("f") {
Some(Value::Boolean(value)) => value,
_ => panic!("f should be Value::Boolean"),
};
assert!(f == (2 > 2));
assert_eq!(f, (2 > 2));
let g = match environment.get("g") {
Some(Value::Boolean(value)) => value,
_ => panic!("g should be Value::Boolean"),
};
assert!(g == (1 <= 2));
assert_eq!(g, (1 <= 2));
let h = match environment.get("h") {
Some(Value::Boolean(value)) => value,
_ => panic!("h should be Value::Boolean"),
};
assert!(h == (2 <= 1));
assert_eq!(h, (2 <= 1));
let i = match environment.get("i") {
Some(Value::Boolean(value)) => value,
_ => panic!("i should be Value::Boolean"),
};
assert!(i == (2 <= 2));
assert_eq!(i, (2 <= 2));
let j = match environment.get("j") {
Some(Value::Boolean(value)) => value,
_ => panic!("j should be Value::Boolean"),
};
assert!(j == (1 >= 2));
assert_eq!(j, (1 >= 2));
let k = match environment.get("k") {
Some(Value::Boolean(value)) => value,
_ => panic!("k should be Value::Boolean"),
};
assert!(k == (2 >= 1));
assert_eq!(k, (2 >= 1));
let l = match environment.get("l") {
Some(Value::Boolean(value)) => value,
_ => panic!("l should be Value::Boolean"),
};
assert!(l == (2 >= 2));
assert_eq!(l, (2 >= 2));
let m = match environment.get("m") {
Some(Value::Boolean(value)) => value,
_ => panic!("m should be Value::Boolean"),
};
assert!(m == (1 == 2));
assert_eq!(m, (1 == 2));
let n = match environment.get("n") {
Some(Value::Boolean(value)) => value,
_ => panic!("n should be Value::Boolean"),
};
assert!(n == (2 == 2));
assert_eq!(n, (2 == 2));
let o = match environment.get("o") {
Some(Value::Boolean(value)) => value,
_ => panic!("o should be Value::Boolean"),
};
assert!(o == (1 != 2));
assert_eq!(o, (1 != 2));
let p = match environment.get("p") {
Some(Value::Boolean(value)) => value,
_ => panic!("p should be Value::Boolean"),
};
assert!(p == (2 != 2));
assert_eq!(p, (2 != 2));
}
#[test]
fn test_conditional() {
let environment = run_file("test_programs/conditional.duck");
let environment = run_file("test_programs/conditional.duck").unwrap();
let x = match environment.get("x") {
Some(Value::Boolean(value)) => value,
_ => panic!("x should be Value::Boolean"),
};
assert!(x == true);
assert_eq!(x, true);
let y = match environment.get("y") {
Some(Value::Boolean(value)) => value,
_ => panic!("y should be Value::Boolean"),
};
assert!(y == false);
assert_eq!(y, false);
let z = match environment.get("z") {
Some(Value::Boolean(value)) => value,
_ => panic!("z should be Value::Boolean"),
};
assert!(z == true);
assert_eq!(z, true);
let a = match environment.get("a") {
Some(Value::Boolean(value)) => value,
_ => panic!("a should be Value::Boolean"),
};
assert!(a == true);
assert_eq!(a, true);
let b = match environment.get("b") {
Some(Value::Boolean(value)) => value,
_ => panic!("b should be Value::Boolean"),
};
assert!(b == true);
assert_eq!(b, true);
}
#[test]
fn test_while_loop() {
let environment = run_file("test_programs/while_loop.duck");
let environment = run_file("test_programs/while_loop.duck").unwrap();
let x = match environment.get("x") {
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert!(x == 5);
assert_eq!(x, 5);
let y = match environment.get("y") {
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 5);
assert_eq!(y, 5);
let z = match environment.get("z") {
Some(Value::Number(value)) => value,
_ => panic!("z should be Value::Number"),
};
assert!(z == 2);
assert_eq!(z, 2);
}
#[test]
fn test_functions() {
let environment = run_file("test_programs/functions.duck");
let environment = run_file("test_programs/functions.duck").unwrap();
let x = match environment.get("x") {
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert!(x == 6765);
assert_eq!(x, 6765);
let y = match environment.get("y") {
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 2);
assert_eq!(y, 2);
let z = match environment.get("z") {
Some(Value::Number(value)) => value,
_ => panic!("z should be Value::Number"),
};
assert!(z == 0);
assert_eq!(z, 0);
}
#[test]
fn test_string_char() {
let environment = run_file("test_programs/string_char.duck");
let environment = run_file("test_programs/string_char.duck").unwrap();
let x = match environment.get("x") {
Some(Value::String(value)) => value,
_ => panic!("x should be Value::String"),
};
assert!(str::from_utf8(&x).unwrap() == "hallo ");
assert_eq!(str::from_utf8(&x).unwrap(), "hallo ");
let y = match environment.get("y") {
Some(Value::String(value)) => value,
_ => panic!("y should be Value::String"),
};
assert!(str::from_utf8(&y).unwrap() == "world");
assert_eq!(str::from_utf8(&y).unwrap(), "world");
let z = match environment.get("z") {
Some(Value::String(value)) => value,
_ => panic!("z should be Value::String"),
};
assert!(str::from_utf8(&z).unwrap() == "hello world!");
assert_eq!(str::from_utf8(&z).unwrap(), "hello world!");
let a = match environment.get("a") {
Some(Value::String(value)) => value,
_ => panic!("a should be Value::String"),
};
assert!(str::from_utf8(&a).unwrap() == "!!!");
assert_eq!(str::from_utf8(&a).unwrap(), "!!!");
let b = match environment.get("b") {
Some(Value::Char(value)) => value,
_ => panic!("b should be Value::Char"),
};
assert!(b == b'l');
assert_eq!(b, b'l');
}
}

View File

@ -4,7 +4,11 @@ use pest::iterators::Pair;
use pest::pratt_parser::PrattParser;
use pest::Parser;
use super::syntax::{BinaryOp, Expr, Stat, UnaryOp, Value};
use super::{
error::{parsing_error, renamed_parsing_error_rules},
escape::{interpret_escaped_char, interpret_escaped_string, EscapeError},
syntax::{BinaryOp, Expr, Stat, UnaryOp, Value},
};
#[derive(Parser)]
#[grammar = "parser/duck.pest"]
@ -23,33 +27,9 @@ lazy_static::lazy_static! {
.op(Op::infix(op_add, Left) | Op::infix(op_sub, Left))
.op(Op::infix(op_mul, Left) | Op::infix(op_div, Left) | Op::infix(op_mod, Left))
.op(Op::prefix(op_neg) | Op::prefix(op_not))
.op(Op::postfix(op_index))
};
}
// 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_call(pair: Pair<Rule>) -> Result<Expr, String> {
let mut inner = pair.into_inner();
let name = inner.next().unwrap().as_str().to_string();
@ -61,20 +41,59 @@ fn build_call(pair: Pair<Rule>) -> Result<Expr, String> {
Ok(Expr::Call(name, arg_exprs))
}
fn build_variable(pair: Pair<Rule>) -> Result<(String, Option<Box<Expr>>), String> {
let mut inner = pair.into_inner();
let name = inner.next().unwrap().as_str().to_string();
let index = match inner.next() {
Some(index) => Some(Box::new(build_expr(index)?)),
None => None,
};
Ok((name, index))
}
fn build_expr(pair: Pair<Rule>) -> Result<Expr, String> {
PRATT_PARSER
.map_primary(|primary| match primary.as_rule() {
Rule::term | Rule::expr => build_expr(primary),
Rule::number => Ok(Expr::Litteral(Value::number(primary.as_str())?)),
Rule::boolean => Ok(Expr::Litteral(Value::boolean(primary.as_str())?)),
Rule::variable => Ok(Expr::Variable(primary.as_str().to_string())),
Rule::variable => {
let (name, index) = build_variable(primary)?;
Ok(Expr::Variable(name, index))
}
Rule::call => build_call(primary),
Rule::string => {
let litteral = primary.as_str();
let value = litteral[1..litteral.len() - 1].as_bytes().to_vec();
let value = match interpret_escaped_string(&litteral[1..litteral.len() - 1]) {
Ok(value) => value,
Err(EscapeError::EscapeAtEndOfString) => {
return Err(parsing_error(primary, "escape character at end of string"))
}
Err(EscapeError::InvalidEscapedChar(c)) => {
return Err(parsing_error(
primary,
&format!("invalid escape character {}", c),
))
}
};
Ok(Expr::Litteral(Value::String(Rc::new(value))))
}
Rule::char => Ok(Expr::Litteral(Value::Char(primary.as_str().as_bytes()[1]))),
Rule::char => {
let litteral = primary.as_str();
let value = match interpret_escaped_char(&litteral[1..litteral.len() - 1]) {
Ok(value) => value,
Err(EscapeError::EscapeAtEndOfString) => {
return Err(parsing_error(primary, "escape character at end of string"))
}
Err(EscapeError::InvalidEscapedChar(c)) => {
return Err(parsing_error(
primary,
&format!("invalid escape character {}", c),
))
}
};
Ok(Expr::Litteral(Value::Char(value)))
}
rule => Err(format!(
"atom expected number, boolean or variable, found {:?}",
rule
@ -157,84 +176,79 @@ fn build_expr(pair: Pair<Rule>) -> Result<Expr, String> {
rule
)),
})
.map_postfix(|lhs, op| match (lhs, op.as_rule()) {
(Err(err), _) => Err(err),
(Ok(lhs), Rule::op_index) => {
let index_expr = build_expr(op.into_inner().next().unwrap())?;
Ok(Expr::UnaryOp(
Box::new(lhs),
UnaryOp::Index(Box::new(index_expr)),
))
}
(Ok(_), rule) => Err(format!(
"indexing operation expected operator, found {:?}",
rule
)),
})
.parse(pair.into_inner())
}
fn build_assign(pair: Pair<Rule>) -> Result<Stat, String> {
let mut inner = pair.into_inner();
let token_len = inner.len();
let lhs = inner.next().unwrap().as_str();
if token_len == 2 {
let rhs = build_expr(inner.next().unwrap())?;
return Ok(Stat::Assignment(lhs.to_string(), None, rhs));
}
let index = build_expr(inner.next().unwrap())?;
let rhs = build_expr(inner.next().unwrap())?;
Ok(Stat::Assignment(lhs.to_string(), Some(index), rhs))
let (name, index) = build_variable(inner.next().unwrap())?;
let expr = build_expr(inner.nth(1).unwrap())?;
Ok(Stat::Assignment(name, index, expr))
}
fn build_while_loop(pair: Pair<Rule>) -> Result<Stat, String> {
let mut inner = pair.into_inner();
let cond = build_expr(inner.next().unwrap())?;
let stmt = build_stats(inner.next().unwrap(), true)?;
Ok(Stat::Loop(cond, stmt))
let cond = build_expr(inner.nth(1).unwrap())?;
let stat = build_stats(inner.next().unwrap(), true)?;
Ok(Stat::Loop(cond, stat))
}
fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Result<Stat, String> {
let mut inner = pair.into_inner();
let cond = build_expr(inner.next().unwrap())?;
let thenstmt = build_stats(inner.next().unwrap(), in_loop)?;
let cond = build_expr(inner.nth(1).unwrap())?;
let then = build_stats(inner.next().unwrap(), in_loop)?;
match inner.next() {
Some(stmt) => match stmt.as_rule() {
Rule::stats => Ok(Stat::Condition(
Some(pair) => match pair.as_rule() {
Rule::else_keyword => Ok(Stat::Condition(
cond,
thenstmt,
Some(build_stats(stmt, in_loop)?),
then,
Some(build_stats(inner.next().unwrap(), in_loop)?),
)),
Rule::stat_elif => Ok(Stat::Condition(
cond,
thenstmt,
Some(vec![build_stat(stmt, in_loop)?]),
then,
Some(vec![build_if_cond(pair, in_loop)?]),
)),
rule => Err(format!("conditional statement expected, found {:?}", rule)),
},
None => Ok(Stat::Condition(cond, thenstmt, None)),
None => Ok(Stat::Condition(cond, then, None)),
}
}
fn build_define(pair: Pair<Rule>) -> Result<Stat, String> {
let mut inner = pair.into_inner();
let name = inner.next().unwrap().as_str().to_string();
let args = inner.next().unwrap().into_inner();
let arg_names = args
.into_iter()
.map(|arg| arg.as_str().to_string())
.collect();
let stmt = build_stats(inner.next().unwrap(), false)?;
Ok(Stat::Define(name, Rc::new(arg_names), Rc::new(stmt)))
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::function_define => {
let mut inner = inner.into_inner();
let name = inner.nth(1).unwrap().as_str().to_string();
let args = inner.next().unwrap().into_inner();
let arg_names = args
.into_iter()
.map(|arg| arg.as_str().to_string())
.collect();
let stat = build_stats(inner.next().unwrap(), false)?;
Ok(Stat::FunDefine(name, Rc::new(arg_names), Rc::new(stat)))
}
Rule::variable_define => {
let mut inner = inner.into_inner();
let name = inner.nth(1).unwrap().as_str().to_string();
if inner.len() == 1 {
return Ok(Stat::VarDefine(name, None));
}
let expr = build_expr(inner.nth(1).unwrap())?;
Ok(Stat::VarDefine(name, Some(expr)))
}
rule => Err(format!("definition expected, found {:?}", rule)),
}
}
fn build_return(pair: Pair<Rule>) -> Result<Stat, String> {
let expr = build_expr(pair.into_inner().next().unwrap())?;
let expr = build_expr(pair.into_inner().nth(1).unwrap())?;
Ok(Stat::Return(expr))
}
fn build_print(pair: Pair<Rule>) -> Result<Stat, String> {
let expr = build_expr(pair.into_inner().next().unwrap())?;
let expr = build_expr(pair.into_inner().nth(1).unwrap())?;
Ok(Stat::Print(expr))
}
@ -277,50 +291,7 @@ pub fn build_ast(content: &str) -> Result<Vec<Stat>, String> {
Err(err) => {
return Err(format!(
"{}",
err.renamed_rules(|rule| {
match *rule {
Rule::program => "program".to_owned(),
Rule::stat => "statement".to_owned(),
Rule::stat_assign => "assignment".to_owned(),
Rule::stat_if => "if statement".to_owned(),
Rule::stat_elif => "elif statement".to_owned(),
Rule::stat_while => "while statement".to_owned(),
Rule::stat_break => "break statement".to_owned(),
Rule::stat_continue => "continue statement".to_owned(),
Rule::stat_define => "define statement".to_owned(),
Rule::stat_return => "return statement".to_owned(),
Rule::stat_print => "print statement".to_owned(),
Rule::expr => "expression".to_owned(),
Rule::term => "term".to_owned(),
Rule::call => "function call".to_owned(),
Rule::variable => "variable".to_owned(),
Rule::number => "number".to_owned(),
Rule::boolean => "boolean".to_owned(),
Rule::op_or => "||".to_owned(),
Rule::op_and => "&&".to_owned(),
Rule::op_lt => "<".to_owned(),
Rule::op_gt => ">".to_owned(),
Rule::op_eq => "==".to_owned(),
Rule::op_ge => ">=".to_owned(),
Rule::op_le => "<=".to_owned(),
Rule::op_ne => "!=".to_owned(),
Rule::op_add => "+".to_owned(),
Rule::op_sub => "-".to_owned(),
Rule::op_mul => "*".to_owned(),
Rule::op_div => "/".to_owned(),
Rule::op_mod => "%".to_owned(),
Rule::op_neg => "-".to_owned(),
Rule::op_not => "!".to_owned(),
Rule::op_index => "[]".to_owned(),
Rule::string => "string".to_owned(),
Rule::ascii_string => "ascii string".to_owned(),
Rule::ascii_range => "ascii range".to_owned(),
Rule::WHITESPACE => "whitespace".to_owned(),
Rule::COMMENT => "comment".to_owned(),
Rule::EOI => "end of input".to_owned(),
_ => unreachable!(),
}
})
err.renamed_rules(renamed_parsing_error_rules)
))
}
};

View File

@ -1,22 +1,26 @@
// TODO : reorganize the grammar
alpha = { 'a'..'z' | 'A'..'Z' }
digit = { '0'..'9' }
ascii_range = { '\x00'..'\x7F' }
ascii_char = ${ !("'") ~ ascii_range }
ascii_string = ${ (!("\"") ~ ascii_range)* }
ascii_char = ${ "\\'" | !("'") ~ ascii_range }
ascii_string = ${ ("\\\"" | (!("\"") ~ ascii_range))* }
WHITESPACE = _{ " " | "\n" | "\t" }
COMMENT = _{ block_comment | line_comment }
line_comment = @{ "//" ~ (!("\r" | "\n") ~ ANY)* ~ ("\n" | "\r\n" | "\r" | EOI) }
block_comment = @{ "/*" ~ ((!("*/") ~ ANY) | block_comment)* ~ "*/" }
identifier = ${ (alpha) ~ (alpha | digit)* }
args_call = { (expr ~ ("," ~ expr)*)? }
call = { variable ~ "(" ~ args_call ~ ")" }
call = { identifier ~ "(" ~ args_call ~ ")" }
variable = { identifier ~ op_index? }
string = ${ "\"" ~ ascii_string ~ "\"" }
char = ${ "'" ~ ascii_char ~ "'" }
variable = ${ (alpha) ~ (alpha | digit)* }
number = ${ (digit)+ }
boolean = ${ "true" | "false" }
op_assign = { "=" }
op_add = { "+" }
op_sub = { "-" }
op_mul = { "*" }
@ -55,21 +59,43 @@ op_unary = _{
}
value = _{ call | boolean | number | variable | string | char | "(" ~ expr ~ ")" }
term = { op_unary* ~ value ~ op_index?}
term = { op_unary* ~ value }
expr = { term ~ (op_binary ~ term)* }
args_define = { (variable ~ ("," ~ variable)*)? }
stat_print = { "print" ~ expr ~ ";" }
stat_return = { "return" ~ expr ~ ";" }
stat_define = { "fn" ~ variable ~ "(" ~ args_define ~ ")" ~ "{" ~ stats ~ "}" }
stat_continue = { "continue" ~ ";" }
stat_break = { "break" ~ ";" }
stat_assign = { variable ~ op_index? ~ "=" ~ expr ~ ";" }
stat_while = { "while" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" }
stat_elif = { ("else if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~ stats ~ "}" ) |
("else if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
stat_if = { ("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~ stats ~ "}" ) |
("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
end_of_stat = { ";" }
print_keyword = { "print" }
stat_print = { print_keyword ~ expr ~ end_of_stat }
return_keyword = { "return" }
stat_return = { return_keyword ~ expr ~ end_of_stat }
function_keyword = { "fn" }
args_define = { (identifier ~ ("," ~ identifier)*)? }
function_define = { function_keyword ~ identifier ~ "(" ~ args_define ~ ")" ~ "{" ~ stats ~ "}" }
var_keyword = { "var" }
variable_define = { var_keyword ~ identifier ~ (op_assign ~ expr)? ~ end_of_stat }
stat_define = { function_define | variable_define}
stat_assign = { variable ~ op_assign ~ expr ~ end_of_stat }
continue_keyword = { "continue" }
stat_continue = { continue_keyword ~ end_of_stat }
break_keyword = { "break" }
stat_break = { break_keyword ~ end_of_stat }
while_keyword = { "while" }
stat_while = { while_keyword ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" }
if_keyword = { "if" }
else_keyword = { "else" }
else_if_keyword = { "else if" }
stat_elif = { (else_if_keyword ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ else_keyword ~ "{" ~ stats ~ "}" ) |
(else_if_keyword ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
stat_if = { (if_keyword ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ else_keyword ~ "{" ~ stats ~ "}" ) |
(if_keyword ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
stat = _{ ( stat_if | stat_while | stat_assign | stat_break | stat_continue | stat_define | stat_return | stat_print ) }

81
src/parser/error.rs Normal file
View File

@ -0,0 +1,81 @@
use super::ast::Rule;
use pest::iterators::Pair;
// get parsing errors out of this file
// create a error print for DuckParser error
// todo => put filename
pub 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")
}
pub fn renamed_parsing_error_rules(rule: &Rule) -> String {
match *rule {
Rule::program => "program".to_owned(),
Rule::stat => "statement".to_owned(),
Rule::stat_assign => "assignment".to_owned(),
Rule::stat_if => "if statement".to_owned(),
Rule::if_keyword => "if keyword".to_owned(),
Rule::else_keyword => "else keyword".to_owned(),
Rule::stat_elif => "elif statement".to_owned(),
Rule::else_if_keyword => "else if keyword".to_owned(),
Rule::stat_while => "while statement".to_owned(),
Rule::while_keyword => "while keyword".to_owned(),
Rule::stat_break => "break statement".to_owned(),
Rule::break_keyword => "break keyword".to_owned(),
Rule::stat_continue => "continue statement".to_owned(),
Rule::continue_keyword => "continue keyword".to_owned(),
Rule::stat_define => "define statement".to_owned(),
Rule::stat_return => "return statement".to_owned(),
Rule::return_keyword => "return keyword".to_owned(),
Rule::stat_print => "print statement".to_owned(),
Rule::print_keyword => "print keyword".to_owned(),
Rule::end_of_stat => ";".to_owned(),
Rule::expr => "expression".to_owned(),
Rule::term => "term".to_owned(),
Rule::call => "function call".to_owned(),
Rule::variable => "variable".to_owned(),
Rule::number => "number".to_owned(),
Rule::boolean => "boolean".to_owned(),
Rule::op_assign => "=".to_owned(),
Rule::op_or => "||".to_owned(),
Rule::op_and => "&&".to_owned(),
Rule::op_lt => "<".to_owned(),
Rule::op_gt => ">".to_owned(),
Rule::op_eq => "==".to_owned(),
Rule::op_ge => ">=".to_owned(),
Rule::op_le => "<=".to_owned(),
Rule::op_ne => "!=".to_owned(),
Rule::op_add => "+".to_owned(),
Rule::op_sub => "-".to_owned(),
Rule::op_mul => "*".to_owned(),
Rule::op_div => "/".to_owned(),
Rule::op_mod => "%".to_owned(),
Rule::op_neg => "-".to_owned(),
Rule::op_not => "!".to_owned(),
Rule::op_index => "[]".to_owned(),
Rule::string => "string".to_owned(),
Rule::ascii_string => "ascii string".to_owned(),
Rule::ascii_range => "ascii range".to_owned(),
Rule::WHITESPACE => "whitespace".to_owned(),
Rule::COMMENT => "comment".to_owned(),
Rule::EOI => "end of input".to_owned(),
_ => unreachable!(),
}
}

52
src/parser/escape.rs Normal file
View File

@ -0,0 +1,52 @@
#[derive(Debug, PartialEq)]
pub enum EscapeError {
EscapeAtEndOfString,
InvalidEscapedChar(char),
}
struct InterpretEscapedString<'a> {
s: std::str::Chars<'a>,
}
fn convert_escape(c: char) -> Result<u8, EscapeError> {
match c {
'0' => Ok(b'\0'),
'a' => Ok(b'\x07'),
'b' => Ok(b'\x08'),
'f' => Ok(b'\x0c'),
'n' => Ok(b'\n'),
'r' => Ok(b'\r'),
't' => Ok(b'\t'),
'v' => Ok(b'\x0b'),
'\'' => Ok(b'\''),
'"' => Ok(b'"'),
'?' => Ok(b'?'),
'\\' => Ok(b'\\'),
c => Err(EscapeError::InvalidEscapedChar(c)),
}
}
impl<'a> Iterator for InterpretEscapedString<'a> {
type Item = Result<u8, EscapeError>;
fn next(&mut self) -> Option<Self::Item> {
self.s.next().map(|c| match c {
'\\' => match self.s.next() {
Some(c) => convert_escape(c),
None => Err(EscapeError::EscapeAtEndOfString),
},
c => Ok(c as u8),
})
}
}
pub fn interpret_escaped_string(s: &str) -> Result<Vec<u8>, EscapeError> {
(InterpretEscapedString { s: s.chars() }).collect()
}
pub fn interpret_escaped_char(s: &str) -> Result<u8, EscapeError> {
match (InterpretEscapedString { s: s.chars() }).next() {
Some(res) => res,
None => Err(EscapeError::EscapeAtEndOfString),
}
}

View File

@ -1,4 +1,6 @@
pub mod ast;
pub mod error;
pub mod escape;
pub mod syntax;
#[cfg(test)]
@ -6,7 +8,6 @@ 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();
@ -15,10 +16,25 @@ mod tests {
build_ast(&content).unwrap();
}
// can loop with some tests libs ?
// TODO test_directly the ast of a program
#[test]
fn test_assign() {
parse_file("test_programs/assign.duck");
fn test_define_assign() {
parse_file("test_programs/define_assign.duck");
}
#[test]
fn test_unknown_variable() {
parse_file("test_programs/unknown_variable.duck");
}
#[test]
fn double_define_fn() {
parse_file("test_programs/double_define_fn.duck");
}
#[test]
fn test_double_define_var() {
parse_file("test_programs/double_define_var.duck");
}
#[test]

View File

@ -46,7 +46,6 @@ impl Display for BinaryOp {
pub enum UnaryOp {
Minus,
Not,
Index(Box<Expr>),
}
impl Display for UnaryOp {
@ -54,7 +53,6 @@ impl Display for UnaryOp {
match self {
UnaryOp::Minus => write!(f, "-"),
UnaryOp::Not => write!(f, "!"),
UnaryOp::Index(expr) => write!(f, "[{}]", expr),
}
}
}
@ -83,6 +81,16 @@ impl Value {
Err(_) => Err(format!("{} is not a boolean", raw)),
}
}
pub fn get_type(&self) -> &'static str {
match self {
Value::Number(_) => "number",
Value::Boolean(_) => "boolean",
Value::Function(_, _) => "function",
Value::String(_) => "string",
Value::Char(_) => "char",
Value::None => "none",
}
}
}
impl Display for Value {
@ -92,7 +100,7 @@ impl Display for Value {
Value::Boolean(b) => write!(f, "{}", b),
Value::Function(args, _) => write!(f, "fn({})", args.join(", ")),
Value::String(s) => unsafe { write!(f, "{}", str::from_utf8_unchecked(s)) },
Value::Char(c) => write!(f, "{}", c),
Value::Char(c) => write!(f, "'{}'", char::from(*c)),
Value::None => write!(f, "None"),
}
}
@ -102,7 +110,7 @@ impl Display for Value {
#[derive(Clone, Debug)]
pub enum Expr {
Litteral(Value),
Variable(String),
Variable(String, Option<Box<Expr>>),
BinaryOp(Box<Expr>, Box<Expr>, BinaryOp),
UnaryOp(Box<Expr>, UnaryOp),
Call(String, Vec<Expr>),
@ -112,19 +120,22 @@ impl Display for Expr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Expr::Litteral(value) => write!(f, "{}", value),
Expr::Variable(name) => write!(f, "{}", name),
Expr::Variable(name, index) => {
if let Some(index) = index {
write!(f, "{}[{}]", name, index)
} else {
write!(f, "{}", name)
}
}
Expr::BinaryOp(left, right, op) => write!(f, "{} {} {}", left, op, right),
Expr::UnaryOp(term, op) => match op {
UnaryOp::Index(index) => write!(f, "{}{}", term, index),
_ => write!(f, "{}{}", op, term),
},
Expr::UnaryOp(term, op) => write!(f, "{}{}", op, term),
Expr::Call(name, args) => {
write!(f, "{}(", name)?;
let expressions = args
let exprs = args
.iter()
.map(|arg| arg.to_string())
.collect::<Vec<String>>();
write!(f, "{}", expressions.join(", "))?;
write!(f, "{}", exprs.join(", "))?;
write!(f, ")")
}
}
@ -135,10 +146,11 @@ impl Display for Expr {
pub enum Stat {
Condition(Expr, Vec<Stat>, Option<Vec<Stat>>),
Loop(Expr, Vec<Stat>),
Assignment(String, Option<Expr>, Expr),
Assignment(String, Option<Box<Expr>>, Expr),
Break,
Continue,
Define(String, Rc<Vec<String>>, Rc<Vec<Stat>>),
FunDefine(String, Rc<Vec<String>>, Rc<Vec<Stat>>),
VarDefine(String, Option<Expr>),
Return(Expr),
Print(Expr),
}
@ -146,25 +158,25 @@ pub enum Stat {
impl Display for Stat {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Stat::Condition(condition, body, else_body) => {
write!(f, "if ({}) {{", condition)?;
for statement in body {
write!(f, " {} ", statement)?;
Stat::Condition(cond, then, alt) => {
write!(f, "if ({}) {{", cond)?;
for stat in then {
write!(f, " {} ", stat)?;
}
write!(f, "}}")?;
if let Some(else_body) = else_body {
if let Some(alt_stats) = alt {
write!(f, " else {{")?;
for statement in else_body {
write!(f, " {} ", statement)?;
for stat in alt_stats {
write!(f, " {} ", stat)?;
}
write!(f, "}}")?;
}
Ok(())
}
Stat::Loop(condition, body) => {
write!(f, "while ({}) {{", condition)?;
for statement in body {
write!(f, " {} ", statement)?;
Stat::Loop(cond, body) => {
write!(f, "while ({}) {{", cond)?;
for stat in body {
write!(f, " {} ", stat)?;
}
write!(f, "}}")
}
@ -177,7 +189,7 @@ impl Display for Stat {
}
Stat::Break => write!(f, "break;"),
Stat::Continue => write!(f, "continue;"),
Stat::Define(name, args, body) => {
Stat::FunDefine(name, args, body) => {
write!(f, "fn {}( ", name)?;
write!(f, "{}", args.join(", "))?;
write!(f, " ) {{")?;
@ -186,6 +198,10 @@ impl Display for Stat {
}
write!(f, "}}")
}
Stat::VarDefine(name, expr) => match expr {
Some(expr) => write!(f, "var {} = {};", name, expr),
None => write!(f, "var {};", name),
},
Stat::Return(expr) => write!(f, "return {};", expr),
Stat::Print(expr) => write!(f, "print {};", expr),
}

View File

@ -93,7 +93,7 @@ impl MyEguiApp {
let side_panel = egui::SidePanel::left("side_panel").show(ctx, |ui| {
ui.heading("Environment");
ui.separator();
for (name, value) in self.machine.environment.global.iter() {
for (name, value) in self.machine.env.global.iter() {
ui.label(format!("{}: {}", name, value));
}
});

View File

@ -1,3 +1,3 @@
x = 2;
y = 3 + 4 * (x + 1);
z = x % (-3 / 2);
var x = 2;
var y = 3 + 4 * (x + 1);
var z = x % (-3 / 2);

View File

@ -1,2 +0,0 @@
x = true;
y = 1;

View File

@ -1,10 +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;
var a = false || false;
var b = true || false;
var c = false || true;
var d = true || true;
var e = false && false;
var f = true && false;
var g = false && true;
var h = true && true;
var i = !true;
var j = !false;

View File

@ -1,4 +1,4 @@
x = 2;
var x = 2;
while (x) {
x = x - 1;
}

View File

@ -1,16 +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;
var a = 1 < 2;
var b = 2 < 1;
var c = 2 < 2;
var d = 1 > 2;
var e = 2 > 1;
var f = 2 > 2;
var g = 1 <= 2;
var h = 2 <= 1;
var i = 2 <= 2;
var j = 1 >= 2;
var k = 2 >= 1;
var l = 2 >= 2;
var m = 1 == 2;
var n = 2 == 2;
var o = 1 != 2;
var p = 2 != 2;

View File

@ -1,28 +1,28 @@
if (true) {
x = true;
var x = true;
} else {
x = false;
var x = false;
}
if (false) {
y = true;
var y = true;
} else {
y = false;
var y = false;
}
z = false;
var z = false;
if (true) {
z = true;
}
if (false) {
a = false;
var a = false;
} else if (true) {
a = true;
var a = true;
}
if (false) {
b = false;
var b = false;
} else if (false) {
b = false;
var b = false;
} else if (false) {
b = false;
var b = false;
} else {
b = true;
var b = true;
}

View File

@ -0,0 +1,5 @@
var x = true;
var y;
y = 1;
print x;
print y;

View File

@ -0,0 +1,7 @@
fn test(n) {
print n;
}
fn test(a, b) {
print a + b;
}

View File

@ -0,0 +1,2 @@
var x = 0;
var x;

View File

@ -17,7 +17,7 @@ fn fib(n) {
return add(fib(sub(n, 1)), fib(sub(n, 2)));
}
x = fib(20);
y = do(add, 1);
z = do(sub, 1);
var x = fib(20);
var y = do(add, 1);
var z = do(sub, 1);
return y + z;

View File

@ -1,8 +1,10 @@
x = "hello ";
y = "world";
a = '!';
z = x + y + a;
var x = "hello ";
var y = "world";
var a = '!';
var z = x + y + a;
print z ;
a = a * 3;
x[1] = 'a';
b = x[2];
var b = x[2];
print "{\n\ttest: \"test\"\n}\n";
print '\'';

View File

@ -0,0 +1,2 @@
var x = 2;
x = y + 2;

View File

@ -1,6 +1,6 @@
x = 0;
y = 0;
z = 0;
var x = 0;
var y = 0;
var z = 0;
while (x < 5) {
x = x + 1;