feat(escaping): escaping + WIP better tests
parent
43b564ab0d
commit
6aec24670d
|
@ -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
|
||||
|
|
|
@ -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)?) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
))
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 ) }
|
||||
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
|
@ -1,2 +0,0 @@
|
|||
x = true;
|
||||
y = 1;
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
x = 2;
|
||||
var x = 2;
|
||||
while (x) {
|
||||
x = x - 1;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
var x = true;
|
||||
var y;
|
||||
y = 1;
|
||||
print x;
|
||||
print y;
|
|
@ -0,0 +1,7 @@
|
|||
fn test(n) {
|
||||
print n;
|
||||
}
|
||||
|
||||
fn test(a, b) {
|
||||
print a + b;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
var x = 0;
|
||||
var x;
|
|
@ -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;
|
||||
|
|
|
@ -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 '\'';
|
|
@ -0,0 +1,2 @@
|
|||
var x = 2;
|
||||
x = y + 2;
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue