feat(function): function implementation

egui
flavien 2023-12-11 20:16:24 +01:00
parent 8092cf761f
commit 20a1c526e0
No known key found for this signature in database
13 changed files with 460 additions and 216 deletions

View File

@ -56,19 +56,18 @@ var: z => val: 8
## TODO
### syntax
- functions OK
- strings
- print statement
- types declaration
- refactor parsing error printing
- functions
- unit tests
- more keywords => do while
- string
- array
- types inference
- array / list / string / map => iterators
- read / write
- program arguments
- structs
- iterator, for keyword
- list
- import from other files
- refactor parsing error printing
- unit tests
### tools
- repl
- LLVM IR translation

View File

@ -2,14 +2,14 @@ use super::{environment::Environment, evaluate::Evaluate};
use crate::parser::syntax::{BinaryOp, Expr, UnaryOp, Value};
pub trait UnaryCompute {
fn compute(&self, environment: &mut Environment, expr: &Expr) -> Result<Value, String>;
fn compute(&self, env: &mut dyn Environment, expr: &Expr) -> Result<Value, String>;
}
impl UnaryCompute for UnaryOp {
fn compute(&self, environment: &mut Environment, expr: &Expr) -> Result<Value, String> {
fn compute(&self, env: &mut dyn Environment, expr: &Expr) -> Result<Value, String> {
Ok(match self {
UnaryOp::Minus => Value::Number(-expr.evaluate(environment)?.value()?),
UnaryOp::Not => Value::Boolean(!expr.evaluate(environment)?.truth()),
UnaryOp::Minus => Value::Number(-expr.evaluate(env)?.value()?),
UnaryOp::Not => Value::Boolean(!expr.evaluate(env)?.truth()?),
})
}
}
@ -17,7 +17,7 @@ impl UnaryCompute for UnaryOp {
pub trait BinaryCompute {
fn compute(
&self,
environment: &mut Environment,
env: &mut dyn Environment,
left: &Expr,
right: &Expr,
) -> Result<Value, String>;
@ -27,54 +27,53 @@ pub trait BinaryCompute {
impl BinaryCompute for BinaryOp {
fn compute(
&self,
environment: &mut Environment,
env: &mut dyn Environment,
left: &Expr,
right: &Expr,
) -> Result<Value, String> {
match self {
BinaryOp::Addition => Ok(Value::Number(
left.evaluate(environment)?.value()? + right.evaluate(environment)?.value()?,
left.evaluate(env)?.value()? + right.evaluate(env)?.value()?,
)),
BinaryOp::Subtraction => Ok(Value::Number(
left.evaluate(environment)?.value()? - right.evaluate(environment)?.value()?,
left.evaluate(env)?.value()? - right.evaluate(env)?.value()?,
)),
BinaryOp::Multiplication => Ok(Value::Number(
left.evaluate(environment)?.value()? * right.evaluate(environment)?.value()?,
left.evaluate(env)?.value()? * right.evaluate(env)?.value()?,
)),
BinaryOp::Division => Ok(Value::Number(
left.evaluate(environment)?.value()? / right.evaluate(environment)?.value()?,
left.evaluate(env)?.value()? / right.evaluate(env)?.value()?,
)),
BinaryOp::Modulo => Ok(Value::Number(
left.evaluate(environment)?.value()? % right.evaluate(environment)?.value()?,
left.evaluate(env)?.value()? % right.evaluate(env)?.value()?,
)),
BinaryOp::Equal => match (left.evaluate(environment)?, right.evaluate(environment)?) {
BinaryOp::Equal => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Boolean(l == r)),
(Value::Boolean(l), Value::Boolean(r)) => Ok(Value::Boolean(l == r)),
_ => Err("Cannot compare different types".to_string()),
},
BinaryOp::NotEqual => match (left.evaluate(environment)?, right.evaluate(environment)?)
{
BinaryOp::NotEqual => match (left.evaluate(env)?, right.evaluate(env)?) {
(Value::Number(l), Value::Number(r)) => Ok(Value::Boolean(l != r)),
(Value::Boolean(l), Value::Boolean(r)) => Ok(Value::Boolean(l != r)),
_ => Err("Cannot compare different types".to_string()),
},
BinaryOp::GreaterEqual => Ok(Value::Boolean(
left.evaluate(environment)?.value() >= right.evaluate(environment)?.value(),
left.evaluate(env)?.value() >= right.evaluate(env)?.value(),
)),
BinaryOp::GreaterThan => Ok(Value::Boolean(
left.evaluate(environment)?.value() > right.evaluate(environment)?.value(),
left.evaluate(env)?.value() > right.evaluate(env)?.value(),
)),
BinaryOp::LesserEqual => Ok(Value::Boolean(
left.evaluate(environment)?.value() <= right.evaluate(environment)?.value(),
left.evaluate(env)?.value() <= right.evaluate(env)?.value(),
)),
BinaryOp::LesserThan => Ok(Value::Boolean(
left.evaluate(environment)?.value() < right.evaluate(environment)?.value(),
left.evaluate(env)?.value() < right.evaluate(env)?.value(),
)),
BinaryOp::And => Ok(Value::Boolean(
left.evaluate(environment)?.truth() && right.evaluate(environment)?.truth(),
left.evaluate(env)?.truth()? && right.evaluate(env)?.truth()?,
)),
BinaryOp::Or => Ok(Value::Boolean(
left.evaluate(environment)?.truth() || right.evaluate(environment)?.truth(),
left.evaluate(env)?.truth()? || right.evaluate(env)?.truth()?,
)),
}
}

View File

@ -1,16 +1,17 @@
use ducklang::interpreter::machine::Machine;
use ducklang::parser::ast::build_ast;
use std::{env, fs::File, io::Read};
use std::{env, fs::File, io::Read, process::exit};
// better cli
// --debug flag
pub fn main() {
for arg in env::args().skip(1) {
// if arg match --debug flag
let mut f = File::open(&arg).unwrap_or_else(|_| panic!("file {} not found", arg));
let mut content = String::new();
f.read_to_string(&mut content)
.unwrap_or_else(|_| panic!("Error in reading file {}", arg));
let ast = build_ast(&content).unwrap();
let mut machine = Machine::new();
machine.run(ast).expect("execution failed");
}
let path = env::args().nth(1).expect("no file provided");
let mut f = File::open(&path).unwrap_or_else(|_| panic!("file {} not found", path));
let mut content = String::new();
f.read_to_string(&mut content)
.unwrap_or_else(|_| panic!("Error in reading file {}", path));
let ast = build_ast(&content).unwrap();
let mut machine = Machine::new();
let ret = machine.run(ast).expect("execution failed");
exit(ret as i32);
}

View File

@ -1,43 +1,85 @@
use std::fmt::{Display, Formatter, Result};
use std::{
fmt::{Display, Formatter, Result},
rc::Rc,
};
use crate::parser::syntax::Value;
use std::collections::HashMap;
#[derive(Clone)]
pub struct Environment {
pub vars: HashMap<String, Value>,
pub trait Environment {
fn add(&mut self, name: &str, node: Value);
fn get(&self, name: &str) -> Option<Value>;
fn share_global(&mut self) -> &mut HashMap<String, Value>;
}
impl Default for Environment {
fn default() -> Self {
Self::new()
}
#[derive(Default)]
pub struct GlobalScope {
pub global: HashMap<String, Value>,
}
impl Environment {
pub fn new() -> Environment {
Environment {
vars: HashMap::new(),
}
}
pub fn add(&mut self, name: &str, node: Value) {
self.vars.insert(name.to_string(), node);
}
pub fn get(&mut self, name: &str) -> Option<&Value> {
self.vars.get(name)
}
}
impl Display for Environment {
impl Display for GlobalScope {
fn fmt(&self, f: &mut Formatter) -> Result {
let vars: Vec<String> = self
.vars
writeln!(f, "global: ")?;
let global_vars: Vec<String> = self
.global
.iter()
.map(|(key, val)| format!("var: {0} => val: {1}", key, val))
.collect();
writeln!(f, "{}", vars.join("\n"))
writeln!(f, "{}", global_vars.join("\n"))?;
Ok(())
}
}
impl Environment for GlobalScope {
fn add(&mut self, name: &str, node: Value) {
self.global.insert(name.to_string(), node);
}
fn get(&self, name: &str) -> Option<Value> {
self.global.get(name).cloned()
}
fn share_global(&mut self) -> &mut HashMap<String, Value> {
&mut self.global
}
}
pub struct LocalScope<'a> {
pub global: &'a mut HashMap<String, Value>,
pub vars: HashMap<String, Value>,
}
impl LocalScope<'_> {
pub fn new<'a>(
env: &'a mut (dyn Environment + 'a),
arg_names: &Rc<Vec<String>>,
args: &[Value],
) -> LocalScope<'a> {
let mut vars = HashMap::new();
for (arg_name, arg) in arg_names.iter().zip(args.iter()) {
vars.insert(arg_name.clone(), arg.clone());
}
LocalScope {
global: env.share_global(),
vars,
}
}
}
impl<'a> Environment for LocalScope<'a> {
fn add(&mut self, name: &str, node: Value) {
self.vars.insert(name.to_string(), node);
}
fn get(&self, name: &str) -> Option<Value> {
match self.vars.get(name) {
Some(value) => Some(value.clone()),
None => self.global.get(name).cloned(),
}
}
fn share_global(&mut self) -> &mut HashMap<String, Value> {
self.global
}
}

View File

@ -1,23 +1,52 @@
use super::{
compute::{BinaryCompute, UnaryCompute},
environment::Environment,
environment::{Environment, LocalScope},
machine::{ControlFlow, Execution},
};
use crate::parser::syntax::{Expr, Value};
pub trait Evaluate {
fn evaluate(&self, environment: &mut Environment) -> Result<Value, String>;
fn evaluate(&self, env: &mut dyn Environment) -> Result<Value, String>;
}
impl Evaluate for Expr {
fn evaluate(&self, environment: &mut Environment) -> Result<Value, String> {
fn evaluate(&self, env: &mut dyn Environment) -> Result<Value, String> {
match *self {
Expr::Litteral(ref value) => Ok(*value),
Expr::Variable(ref name) => match environment.get(name) {
Some(value) => Ok(*value),
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::UnaryOp(ref term, ref op) => op.compute(environment, term),
Expr::BinaryOp(ref lhs, ref rhs, ref op) => op.compute(environment, lhs, rhs),
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) => {
let function = env
.get(name)
.ok_or_else(|| format!("function {} not found", name))?;
match function {
Value::Function(ref arg_names, ref body) => {
let args =
args.iter()
.map(|arg| arg.evaluate(env))
.collect::<Result<Vec<Value>, String>>()?;
if arg_names.len() != args.len() {
return Err(format!(
"function {} takes {} arguments, found {}",
name,
arg_names.len(),
args.len()
));
}
let mut fn_env = LocalScope::new(env, arg_names, &args);
if let ControlFlow::Return(value) = body.execute(&mut fn_env)? {
Ok(value)
} else {
Ok(Value::None)
}
}
_ => Err(format!("{} is not a function", name)),
}
}
}
}
}

View File

@ -1,24 +1,28 @@
use super::{environment::Environment, evaluate::Evaluate};
use crate::parser::syntax::Stat;
use super::{
environment::{Environment, GlobalScope},
evaluate::Evaluate,
};
use crate::parser::syntax::{Stat, Value};
// can be used to add continue/exit
pub enum ControlFlow {
Next,
Break,
Continue,
Return(Value),
}
pub trait Execution {
fn execute(&self, environment: &mut Environment) -> Result<ControlFlow, String>;
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String>;
}
impl Execution for Vec<Stat> {
fn execute(&self, environment: &mut Environment) -> Result<ControlFlow, String> {
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String> {
for statement in self {
if let flow @ (ControlFlow::Break | ControlFlow::Continue) =
statement.execute(environment)?
{
return Ok(flow);
match statement.execute(environment)? {
ControlFlow::Next => (),
flow @ (ControlFlow::Break | ControlFlow::Continue) => return Ok(flow),
ControlFlow::Return(value) => return Ok(ControlFlow::Return(value)),
}
}
Ok(ControlFlow::Next)
@ -26,11 +30,11 @@ impl Execution for Vec<Stat> {
}
impl Execution for Stat {
fn execute(&self, environment: &mut Environment) -> Result<ControlFlow, String> {
println!("statement: {}", &self);
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String> {
// println!("statement: {}", &self);
match *self {
Stat::Condition(ref condition, ref consequence, ref alternative) => {
if condition.evaluate(environment)?.truth() {
if condition.evaluate(environment)?.truth()? {
return consequence.execute(environment);
} else if let Some(statements) = alternative {
return statements.execute(environment);
@ -38,7 +42,7 @@ impl Execution for Stat {
Ok(ControlFlow::Next)
}
Stat::Loop(ref cond, ref body) => {
while cond.evaluate(environment)?.truth() {
while cond.evaluate(environment)?.truth()? {
if let ControlFlow::Break = body.execute(environment)? {
break;
}
@ -48,17 +52,23 @@ impl Execution for Stat {
Stat::Assignment(ref name, ref expr) => {
let value = expr.evaluate(environment)?;
environment.add(name, value);
println!("environment:\n{}", environment);
// println!("environment:\n{}", environment);
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()));
// println!("environment:\n{}", environment);
Ok(ControlFlow::Next)
}
Stat::Return(ref expr) => Ok(ControlFlow::Return(expr.evaluate(environment)?)),
}
}
}
pub struct Machine {
pub environment: Environment,
pub environment: GlobalScope,
}
impl Default for Machine {
@ -70,18 +80,20 @@ impl Default for Machine {
impl Machine {
pub fn new() -> Machine {
Machine {
environment: Environment::default(),
environment: GlobalScope::default(),
}
}
pub fn new_with_env(environment: Environment) -> Machine {
pub fn new_with_env(environment: GlobalScope) -> Machine {
Machine { environment }
}
pub fn run(&mut self, statements: Vec<Stat>) -> Result<(), String> {
pub fn run(&mut self, statements: Vec<Stat>) -> Result<i64, String> {
for statement in statements {
statement.execute(&mut self.environment)?;
if let ControlFlow::Return(val) = statement.execute(&mut self.environment)? {
return val.value();
}
}
Ok(())
Ok(0)
}
}

View File

@ -8,10 +8,13 @@ mod tests {
use crate::parser::{ast::build_ast, syntax::Value};
use std::{fs::File, io::Read};
use super::{environment::Environment, machine::Machine};
use super::{
environment::{Environment, GlobalScope},
machine::Machine,
};
// modify to inclue in main
fn run_file(path: &str) -> Environment {
fn run_file(path: &str) -> GlobalScope {
let mut f = File::open(path).expect(&format!("file {} not found", path));
let mut content = String::new();
f.read_to_string(&mut content)
@ -24,14 +27,14 @@ mod tests {
#[test]
fn test_assign() {
let mut environment = run_file("test_programs/assign.duck");
let environment = run_file("test_programs/assign.duck");
let x = match environment.get("x") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("x should be Value::Boolean"),
};
assert!(x == true);
let y = match environment.get("y") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 1);
@ -39,19 +42,19 @@ mod tests {
#[test]
fn test_arithmetic_expr() {
let mut environment = run_file("test_programs/arithmetic_expr.duck");
let environment = run_file("test_programs/arithmetic_expr.duck");
let x = match environment.get("x") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert!(x == 2);
let y = match environment.get("y") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 3 + 4 * (x + 1));
let z = match environment.get("z") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("z should be Value::Number"),
};
assert!(z == x % (-3 / 2));
@ -59,54 +62,54 @@ mod tests {
#[test]
fn test_boolean_expr() {
let mut environment = run_file("test_programs/boolean_expr.duck");
let environment = run_file("test_programs/boolean_expr.duck");
let a = match environment.get("a") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("a should be Value::Boolean"),
};
assert!(a == (false || false));
let b = match environment.get("b") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("b should be Value::Boolean"),
};
assert!(b == (true || false));
let c = match environment.get("c") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("c should be Value::Boolean"),
};
assert!(c == (false || true));
let d = match environment.get("d") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("d should be Value::Boolean"),
};
assert!(d == (true || true));
let e = match environment.get("e") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("e should be Value::Boolean"),
};
assert!(e == (false && false));
let f = match environment.get("f") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("f should be Value::Boolean"),
};
assert!(f == (true && false));
let g = match environment.get("g") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("g should be Value::Boolean"),
};
assert!(g == (false && true));
let h = match environment.get("h") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("h should be Value::Boolean"),
};
assert!(h == (true && true));
let i = match environment.get("i") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("i should be Value::Boolean"),
};
assert!(i == !true);
let j = match environment.get("j") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("j should be Value::Boolean"),
};
assert!(j == !false);
@ -114,84 +117,84 @@ mod tests {
#[test]
fn test_compare_expr() {
let mut environment = run_file("test_programs/compare_expr.duck");
let environment = run_file("test_programs/compare_expr.duck");
let a = match environment.get("a") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("a should be Value::Boolean"),
};
assert!(a == (1 < 2));
let b = match environment.get("b") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("b should be Value::Boolean"),
};
assert!(b == (2 < 1));
let c = match environment.get("c") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("c should be Value::Boolean"),
};
assert!(c == (2 < 2));
let d = match environment.get("d") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("d should be Value::Boolean"),
};
assert!(d == (1 > 2));
let e = match environment.get("e") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("e should be Value::Boolean"),
};
assert!(e == (2 > 1));
let f = match environment.get("f") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("f should be Value::Boolean"),
};
assert!(f == (2 > 2));
let g = match environment.get("g") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("g should be Value::Boolean"),
};
assert!(g == (1 <= 2));
let h = match environment.get("h") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("h should be Value::Boolean"),
};
assert!(h == (2 <= 1));
let i = match environment.get("i") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("i should be Value::Boolean"),
};
assert!(i == (2 <= 2));
let j = match environment.get("j") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("j should be Value::Boolean"),
};
assert!(j == (1 >= 2));
let k = match environment.get("k") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("k should be Value::Boolean"),
};
assert!(k == (2 >= 1));
let l = match environment.get("l") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("l should be Value::Boolean"),
};
assert!(l == (2 >= 2));
let m = match environment.get("m") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("m should be Value::Boolean"),
};
assert!(m == (1 == 2));
let n = match environment.get("n") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("n should be Value::Boolean"),
};
assert!(n == (2 == 2));
let o = match environment.get("o") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("o should be Value::Boolean"),
};
assert!(o == (1 != 2));
let p = match environment.get("p") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("p should be Value::Boolean"),
};
assert!(p == (2 != 2));
@ -199,29 +202,29 @@ mod tests {
#[test]
fn test_conditional() {
let mut environment = run_file("test_programs/conditional.duck");
let environment = run_file("test_programs/conditional.duck");
let x = match environment.get("x") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("x should be Value::Boolean"),
};
assert!(x == true);
let y = match environment.get("y") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("y should be Value::Boolean"),
};
assert!(y == false);
let z = match environment.get("z") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("z should be Value::Boolean"),
};
assert!(z == true);
let a = match environment.get("a") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("a should be Value::Boolean"),
};
assert!(a == true);
let b = match environment.get("b") {
Some(Value::Boolean(value)) => *value,
Some(Value::Boolean(value)) => value,
_ => panic!("b should be Value::Boolean"),
};
assert!(b == true);
@ -229,21 +232,41 @@ mod tests {
#[test]
fn test_while_loop() {
let mut environment = run_file("test_programs/while_loop.duck");
let environment = run_file("test_programs/while_loop.duck");
let x = match environment.get("x") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert!(x == 5);
let y = match environment.get("y") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 5);
let z = match environment.get("z") {
Some(Value::Number(value)) => *value,
Some(Value::Number(value)) => value,
_ => panic!("z should be Value::Number"),
};
assert!(z == 2);
}
#[test]
fn test_functions() {
let environment = run_file("test_programs/functions.duck");
let x = match environment.get("x") {
Some(Value::Number(value)) => value,
_ => panic!("x should be Value::Number"),
};
assert!(x == 6765);
let y = match environment.get("y") {
Some(Value::Number(value)) => value,
_ => panic!("y should be Value::Number"),
};
assert!(y == 2);
let z = match environment.get("z") {
Some(Value::Number(value)) => value,
_ => panic!("z should be Value::Number"),
};
assert!(z == 0);
}
}

View File

@ -47,13 +47,25 @@ fn parsing_error(pair: Pair<Rule>, error: &str) -> String {
error_print.join("\n")
}
fn build_expr(pair: Pair<Rule>) -> Result<Box<Expr>, String> {
fn build_call(pair: Pair<Rule>) -> Result<Expr, 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_exprs = args
.into_iter()
.map(|arg| build_expr(arg).unwrap())
.collect();
Ok(Expr::call(name, arg_exprs))
}
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())),
Rule::call => build_call(primary),
rule => Err(format!(
"atom expected number, boolean or variable, found {:?}",
rule
@ -61,25 +73,67 @@ fn build_expr(pair: Pair<Rule>) -> Result<Box<Expr>, String> {
})
.map_infix(|lhs, op, rhs| match (lhs, op.as_rule(), rhs) {
(Err(err), _, _) | (_, _, Err(err)) => Err(err),
(Ok(lhs), Rule::op_or, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::Or)),
(Ok(lhs), Rule::op_and, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::And)),
(Ok(lhs), Rule::op_lt, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::LesserThan)),
(Ok(lhs), Rule::op_gt, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::GreaterThan)),
(Ok(lhs), Rule::op_eq, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::Equal)),
(Ok(lhs), Rule::op_ge, Ok(rhs)) => {
Ok(Expr::binary_op(lhs, rhs, BinaryOp::GreaterEqual))
(Ok(lhs), Rule::op_or, Ok(rhs)) => {
Ok(Expr::binary_op(Box::new(lhs), Box::new(rhs), BinaryOp::Or))
}
(Ok(lhs), Rule::op_le, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::LesserEqual)),
(Ok(lhs), Rule::op_ne, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::NotEqual)),
(Ok(lhs), Rule::op_add, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::Addition)),
(Ok(lhs), Rule::op_sub, Ok(rhs)) => {
Ok(Expr::binary_op(lhs, rhs, BinaryOp::Subtraction))
(Ok(lhs), Rule::op_and, Ok(rhs)) => {
Ok(Expr::binary_op(Box::new(lhs), Box::new(rhs), BinaryOp::And))
}
(Ok(lhs), Rule::op_mul, Ok(rhs)) => {
Ok(Expr::binary_op(lhs, rhs, BinaryOp::Multiplication))
}
(Ok(lhs), Rule::op_div, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::Division)),
(Ok(lhs), Rule::op_mod, Ok(rhs)) => Ok(Expr::binary_op(lhs, rhs, BinaryOp::Modulo)),
(Ok(lhs), Rule::op_lt, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::LesserThan,
)),
(Ok(lhs), Rule::op_gt, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::GreaterThan,
)),
(Ok(lhs), Rule::op_eq, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::Equal,
)),
(Ok(lhs), Rule::op_ge, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::GreaterEqual,
)),
(Ok(lhs), Rule::op_le, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::LesserEqual,
)),
(Ok(lhs), Rule::op_ne, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::NotEqual,
)),
(Ok(lhs), Rule::op_add, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::Addition,
)),
(Ok(lhs), Rule::op_sub, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::Subtraction,
)),
(Ok(lhs), Rule::op_mul, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::Multiplication,
)),
(Ok(lhs), Rule::op_div, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::Division,
)),
(Ok(lhs), Rule::op_mod, Ok(rhs)) => Ok(Expr::binary_op(
Box::new(lhs),
Box::new(rhs),
BinaryOp::Modulo,
)),
(_, rule, _) => Err(format!(
"binary operation expected operator, found {:?}",
rule
@ -87,8 +141,8 @@ fn build_expr(pair: Pair<Rule>) -> Result<Box<Expr>, String> {
})
.map_prefix(|op, rhs| match (op.as_rule(), rhs) {
(_, Err(err)) => Err(err),
(Rule::op_neg, Ok(rhs)) => Ok(Expr::unary_op(rhs, UnaryOp::Minus)),
(Rule::op_not, Ok(rhs)) => Ok(Expr::unary_op(rhs, UnaryOp::Not)),
(Rule::op_neg, Ok(rhs)) => Ok(Expr::unary_op(Box::new(rhs), UnaryOp::Minus)),
(Rule::op_not, Ok(rhs)) => Ok(Expr::unary_op(Box::new(rhs), UnaryOp::Not)),
(rule, _) => Err(format!(
"unary operation expected operator, found {:?}",
rule
@ -97,21 +151,21 @@ fn build_expr(pair: Pair<Rule>) -> Result<Box<Expr>, String> {
.parse(pair.into_inner())
}
fn build_assign(pair: Pair<Rule>) -> Result<Box<Stat>, String> {
fn build_assign(pair: Pair<Rule>) -> Result<Stat, String> {
let mut inner = pair.into_inner();
let lhs = inner.next().unwrap().as_str();
let rhs = build_expr(inner.next().unwrap())?;
Ok(Stat::assign(lhs, rhs))
}
fn build_while_loop(pair: Pair<Rule>) -> Result<Box<Stat>, String> {
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::while_loop(cond, stmt))
}
fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Result<Box<Stat>, String> {
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)?;
@ -125,15 +179,32 @@ fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Result<Box<Stat>, String> {
Rule::stat_elif => Ok(Stat::if_cond(
cond,
thenstmt,
Some(vec![*build_stat(stmt, in_loop)?]),
Some(vec![build_stat(stmt, in_loop)?]),
)),
rule => Err(format!("conditional expected statements, found {:?}", rule)),
rule => Err(format!("conditional statement expected, found {:?}", rule)),
},
None => Ok(Stat::if_cond(cond, thenstmt, None)),
}
}
fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Result<Box<Stat>, String> {
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, arg_names, stmt))
}
fn build_return(pair: Pair<Rule>) -> Result<Stat, String> {
let expr = build_expr(pair.into_inner().next().unwrap())?;
Ok(Stat::return_keyword(expr))
}
fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Result<Stat, String> {
match pair.as_rule() {
Rule::stat_assign => build_assign(pair),
Rule::stat_if | Rule::stat_elif => build_if_cond(pair, in_loop),
@ -150,10 +221,9 @@ fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Result<Box<Stat>, String> {
}
Err(parsing_error(pair, "continue keyword while not in loop"))
}
rule => Err(format!(
"statement expected assign, if, while or break, found {:?}",
rule
)),
Rule::stat_define => build_define(pair),
Rule::stat_return => build_return(pair),
rule => Err(format!("Statement expected, found {:?}", rule)),
}
}
@ -161,7 +231,7 @@ fn build_stats(pair: Pair<Rule>, in_loop: bool) -> Result<Vec<Stat>, String> {
let inner = pair.into_inner();
let mut stats = Vec::new();
for pair in inner {
stats.push(*build_stat(pair, in_loop)?);
stats.push(build_stat(pair, in_loop)?);
}
Ok(stats)
}

View File

@ -6,6 +6,8 @@ COMMENT = _{ block_comment | line_comment }
line_comment = @{ "//" ~ (!("\r" | "\n") ~ ANY)* ~ ("\n" | "\r\n" | "\r" | EOI) }
block_comment = @{ "/*" ~ ((!("*/") ~ ANY) | block_comment)* ~ "*/" }
args_call = { (expr ~ ("," ~ expr)*)? }
call = { variable ~ "(" ~ args_call ~ ")" }
variable = @ { (alpha) ~ (alpha | digit)* }
number = @ { (digit)+ }
boolean = @ { "true" | "false" }
@ -46,10 +48,14 @@ op_unary = _{
op_not
}
value = _{ boolean | number | variable | "(" ~ expr ~ ")" }
value = _{ call | boolean | number | variable | "(" ~ expr ~ ")" }
term = { op_unary* ~ value }
expr = { term ~ (op_binary ~ term)* }
args_define = { (variable ~ ("," ~ variable)*)? }
stat_return = { "return" ~ expr ~ ";" }
stat_define = { "fn" ~ variable ~ "(" ~ args_define ~ ")" ~ "{" ~ stats ~ "}" }
stat_continue = { "continue" ~ ";" }
stat_break = { "break" ~ ";" }
stat_assign = { variable ~ "=" ~ expr ~ ";" }
@ -59,7 +65,7 @@ stat_elif = { ("else if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~
stat_if = { ("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~ stats ~ "}" ) |
("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
stat = _{ ( stat_if | stat_while | stat_assign | stat_break | stat_continue ) }
stat = _{ ( stat_if | stat_while | stat_assign | stat_break | stat_continue | stat_define | stat_return ) }
stats = { (stat)* }

View File

@ -51,4 +51,9 @@ mod tests {
fn test_break_not_loop() {
parse_file("test_programs/break_not_loop.duck");
}
#[test]
fn test_functions() {
parse_file("test_programs/functions.duck");
}
}

View File

@ -1,12 +1,10 @@
#![allow(clippy::should_implement_trait)]
use std::fmt::{self, Display, Formatter};
// REFACTORING
// TODO:
// - better op name
// - split in multiple files
// - unbox stat and expr ?
use std::{
fmt::{self, Display, Formatter},
rc::Rc,
};
#[derive(Clone, Copy, Debug)]
pub enum BinaryOp {
Addition,
Subtraction,
@ -43,6 +41,7 @@ impl Display for BinaryOp {
}
}
#[derive(Clone, Copy, Debug)]
pub enum UnaryOp {
Minus,
Not,
@ -57,10 +56,13 @@ impl Display for UnaryOp {
}
}
#[derive(Clone, Copy)]
// Rc all the heavy stuff
#[derive(Clone, Debug)]
pub enum Value {
Number(i64),
Boolean(bool),
Function(Rc<Vec<String>>, Rc<Vec<Stat>>),
None,
}
impl Value {
@ -76,16 +78,19 @@ impl Value {
Err(_) => Err(format!("{} is not a boolean", raw)),
}
}
// TODO move to compute.rs
pub fn value(&self) -> Result<i64, String> {
match self {
Value::Number(n) => Ok(*n),
_ => Err(format!("{} is not a number", self)),
}
}
pub fn truth(&self) -> bool {
// TODO move to compute.rs
pub fn truth(&self) -> Result<bool, String> {
match self {
Value::Boolean(b) => *b,
Value::Number(value) => *value != 0,
Value::Boolean(b) => Ok(*b),
Value::Number(value) => Ok(*value != 0),
_ => Err(format!("{} is not a logical value", self)),
}
}
}
@ -95,29 +100,37 @@ impl Display for Value {
match self {
Value::Number(n) => write!(f, "{}", n),
Value::Boolean(b) => write!(f, "{}", b),
Value::Function(args, _) => write!(f, "fn({})", args.join(", ")),
Value::None => write!(f, "None"),
}
}
}
// box because of recursive definition
#[derive(Clone, Debug)]
pub enum Expr {
Litteral(Value),
Variable(String),
BinaryOp(Box<Expr>, Box<Expr>, BinaryOp),
UnaryOp(Box<Expr>, UnaryOp),
Call(String, Vec<Expr>),
}
impl Expr {
pub fn litteral(value: Value) -> Box<Expr> {
Box::new(Expr::Litteral(value))
pub fn litteral(value: Value) -> Expr {
Expr::Litteral(value)
}
pub fn variable(name: &str) -> Box<Expr> {
Box::new(Expr::Variable(name.to_string()))
pub fn variable(name: &str) -> Expr {
Expr::Variable(name.to_string())
}
pub fn binary_op(left: Box<Expr>, right: Box<Expr>, op: BinaryOp) -> Box<Expr> {
Box::new(Expr::BinaryOp(left, right, op))
pub fn binary_op(left: Box<Expr>, right: Box<Expr>, op: BinaryOp) -> Expr {
Expr::BinaryOp(left, right, op)
}
pub fn unary_op(term: Box<Expr>, op: UnaryOp) -> Box<Expr> {
Box::new(Expr::UnaryOp(term, op))
pub fn unary_op(term: Box<Expr>, op: UnaryOp) -> Expr {
Expr::UnaryOp(term, op)
}
pub fn call(name: String, args: Vec<Expr>) -> Expr {
Expr::Call(name, args)
}
}
@ -128,39 +141,51 @@ impl Display for Expr {
Expr::Variable(name) => write!(f, "{}", name),
Expr::BinaryOp(left, right, op) => write!(f, "{} {} {}", left, op, right),
Expr::UnaryOp(term, op) => write!(f, "{}{}", op, term),
Expr::Call(name, args) => {
write!(f, "{}(", name)?;
let expressions = args
.iter()
.map(|arg| arg.to_string())
.collect::<Vec<String>>();
write!(f, "{}", expressions.join(", "))?;
write!(f, ")")
}
}
}
}
#[derive(Clone, Debug)]
pub enum Stat {
Condition(Box<Expr>, Vec<Stat>, Option<Vec<Stat>>),
Loop(Box<Expr>, Vec<Stat>),
Assignment(String, Box<Expr>),
Condition(Expr, Vec<Stat>, Option<Vec<Stat>>),
Loop(Expr, Vec<Stat>),
Assignment(String, Expr),
Break,
Continue, // declare
// call
// return
Continue,
Define(String, Rc<Vec<String>>, Rc<Vec<Stat>>),
Return(Expr),
}
impl Stat {
pub fn assign(name: &str, expr: Box<Expr>) -> Box<Stat> {
Box::new(Stat::Assignment(name.to_string(), expr))
pub fn assign(name: &str, expr: Expr) -> Stat {
Stat::Assignment(name.to_string(), expr)
}
pub fn break_keyword() -> Box<Stat> {
Box::new(Stat::Break)
pub fn break_keyword() -> Stat {
Stat::Break
}
pub fn continue_keyword() -> Box<Stat> {
Box::new(Stat::Continue)
pub fn continue_keyword() -> Stat {
Stat::Continue
}
pub fn while_loop(condition: Box<Expr>, body: Vec<Stat>) -> Box<Stat> {
Box::new(Stat::Loop(condition, body))
pub fn while_loop(condition: Expr, body: Vec<Stat>) -> Stat {
Stat::Loop(condition, body)
}
pub fn if_cond(
condition: Box<Expr>,
body: Vec<Stat>,
else_body: Option<Vec<Stat>>,
) -> Box<Stat> {
Box::new(Stat::Condition(condition, body, else_body))
pub fn if_cond(condition: Expr, body: Vec<Stat>, else_body: Option<Vec<Stat>>) -> Stat {
Stat::Condition(condition, body, else_body)
}
pub fn define(name: String, args: Vec<String>, body: Vec<Stat>) -> Stat {
Stat::Define(name, Rc::new(args), Rc::new(body))
}
pub fn return_keyword(expr: Expr) -> Stat {
Stat::Return(expr)
}
}
@ -168,7 +193,7 @@ impl Display for Stat {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Stat::Condition(condition, body, else_body) => {
write!(f, "if {} {{", condition)?;
write!(f, "if ({}) {{", condition)?;
for statement in body {
write!(f, " {} ", statement)?;
}
@ -183,7 +208,7 @@ impl Display for Stat {
Ok(())
}
Stat::Loop(condition, body) => {
write!(f, "while {} {{", condition)?;
write!(f, "while ({}) {{", condition)?;
for statement in body {
write!(f, " {} ", statement)?;
}
@ -192,6 +217,16 @@ impl Display for Stat {
Stat::Assignment(name, expr) => write!(f, "{} = {};", name, expr),
Stat::Break => write!(f, "break;"),
Stat::Continue => write!(f, "continue;"),
Stat::Define(name, args, body) => {
write!(f, "fn {}( ", name)?;
write!(f, "{}", args.join(", "))?;
write!(f, " ) {{")?;
for statement in body.as_ref() {
write!(f, " {} ", statement)?;
}
write!(f, "}}")
}
Stat::Return(expr) => write!(f, "return {};", 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.vars.iter() {
for (name, value) in self.machine.environment.global.iter() {
ui.label(format!("{}: {}", name, value));
}
});

View File

@ -0,0 +1,23 @@
fn add(a, b) {
return a + b;
}
fn sub(a, b) {
return a - b;
}
fn do(f, n) {
return f(n, n);
}
fn fib(n) {
if (n < 2) {
return n;
}
return add(fib(sub(n, 1)), fib(sub(n, 2)));
}
x = fib(20);
y = do(add, 1);
z = do(sub, 1);
return y + z;