feat(interpreter): interpreter with code editor UI
parent
056d304b73
commit
252c23e889
12
README.md
12
README.md
|
@ -12,11 +12,13 @@ Written in Rust with pest PEG parser.
|
|||
|
||||
<br>
|
||||
|
||||
### Branches
|
||||
I have currently 3 branches that start from the main branch:
|
||||
- `egui`: an attempt to create a code editor and interpreter in wasm for web embedding
|
||||
- `features`: new features for the language but only implemented with interpreter
|
||||
- `compiler`: an attempt to create a compiler from LLVM
|
||||
### TODO playground
|
||||
- syntax color
|
||||
- line numbers
|
||||
- better panel title
|
||||
- panel tabs
|
||||
- print / string => language
|
||||
- console for print
|
||||
|
||||
### Example
|
||||
```
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="2" />
|
||||
<link data-trunk rel="rust" data-bin="duck_web" data-wasm-opt="2" />
|
||||
|
||||
<style>
|
||||
html {
|
||||
|
|
|
@ -2,69 +2,80 @@ 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) -> Value;
|
||||
fn compute(&self, environment: &mut Environment, expr: &Expr) -> Result<Value, String>;
|
||||
}
|
||||
|
||||
impl UnaryCompute for UnaryOp {
|
||||
fn compute(&self, environment: &mut Environment, expr: &Expr) -> Value {
|
||||
match self {
|
||||
UnaryOp::Minus => Value::Number(-expr.evaluate(environment).value()),
|
||||
UnaryOp::Not => Value::Boolean(!expr.evaluate(environment).truth()),
|
||||
}
|
||||
fn compute(&self, environment: &mut 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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BinaryCompute {
|
||||
fn compute(&self, environment: &mut Environment, left: &Expr, right: &Expr) -> Value;
|
||||
fn compute(
|
||||
&self,
|
||||
environment: &mut Environment,
|
||||
left: &Expr,
|
||||
right: &Expr,
|
||||
) -> Result<Value, String>;
|
||||
}
|
||||
|
||||
// add visitor to match types
|
||||
impl BinaryCompute for BinaryOp {
|
||||
fn compute(&self, environment: &mut Environment, left: &Expr, right: &Expr) -> Value {
|
||||
fn compute(
|
||||
&self,
|
||||
environment: &mut Environment,
|
||||
left: &Expr,
|
||||
right: &Expr,
|
||||
) -> Result<Value, String> {
|
||||
match self {
|
||||
BinaryOp::Addition => Value::Number(
|
||||
left.evaluate(environment).value() + right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::Subtraction => Value::Number(
|
||||
left.evaluate(environment).value() - right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::Multiplication => Value::Number(
|
||||
left.evaluate(environment).value() * right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::Division => Value::Number(
|
||||
left.evaluate(environment).value() / right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::Modulo => Value::Number(
|
||||
left.evaluate(environment).value() % right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::Equal => match (left.evaluate(environment), right.evaluate(environment)) {
|
||||
(Value::Number(l), Value::Number(r)) => Value::Boolean(l == r),
|
||||
(Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(l == r),
|
||||
_ => panic!("Cannot compare different types"),
|
||||
BinaryOp::Addition => Ok(Value::Number(
|
||||
left.evaluate(environment)?.value()? + right.evaluate(environment)?.value()?,
|
||||
)),
|
||||
BinaryOp::Subtraction => Ok(Value::Number(
|
||||
left.evaluate(environment)?.value()? - right.evaluate(environment)?.value()?,
|
||||
)),
|
||||
BinaryOp::Multiplication => Ok(Value::Number(
|
||||
left.evaluate(environment)?.value()? * right.evaluate(environment)?.value()?,
|
||||
)),
|
||||
BinaryOp::Division => Ok(Value::Number(
|
||||
left.evaluate(environment)?.value()? / right.evaluate(environment)?.value()?,
|
||||
)),
|
||||
BinaryOp::Modulo => Ok(Value::Number(
|
||||
left.evaluate(environment)?.value()? % right.evaluate(environment)?.value()?,
|
||||
)),
|
||||
BinaryOp::Equal => match (left.evaluate(environment)?, right.evaluate(environment)?) {
|
||||
(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)) {
|
||||
(Value::Number(l), Value::Number(r)) => Value::Boolean(l != r),
|
||||
(Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(l != r),
|
||||
_ => panic!("Cannot compare different types"),
|
||||
BinaryOp::NotEqual => match (left.evaluate(environment)?, right.evaluate(environment)?)
|
||||
{
|
||||
(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 => Value::Boolean(
|
||||
left.evaluate(environment).value() >= right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::GreaterThan => Value::Boolean(
|
||||
left.evaluate(environment).value() > right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::LesserEqual => Value::Boolean(
|
||||
left.evaluate(environment).value() <= right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::LesserThan => Value::Boolean(
|
||||
left.evaluate(environment).value() < right.evaluate(environment).value(),
|
||||
),
|
||||
BinaryOp::And => Value::Boolean(
|
||||
left.evaluate(environment).truth() && right.evaluate(environment).truth(),
|
||||
),
|
||||
BinaryOp::Or => Value::Boolean(
|
||||
left.evaluate(environment).truth() || right.evaluate(environment).truth(),
|
||||
),
|
||||
BinaryOp::GreaterEqual => Ok(Value::Boolean(
|
||||
left.evaluate(environment)?.value() >= right.evaluate(environment)?.value(),
|
||||
)),
|
||||
BinaryOp::GreaterThan => Ok(Value::Boolean(
|
||||
left.evaluate(environment)?.value() > right.evaluate(environment)?.value(),
|
||||
)),
|
||||
BinaryOp::LesserEqual => Ok(Value::Boolean(
|
||||
left.evaluate(environment)?.value() <= right.evaluate(environment)?.value(),
|
||||
)),
|
||||
BinaryOp::LesserThan => Ok(Value::Boolean(
|
||||
left.evaluate(environment)?.value() < right.evaluate(environment)?.value(),
|
||||
)),
|
||||
BinaryOp::And => Ok(Value::Boolean(
|
||||
left.evaluate(environment)?.truth() && right.evaluate(environment)?.truth(),
|
||||
)),
|
||||
BinaryOp::Or => Ok(Value::Boolean(
|
||||
left.evaluate(environment)?.truth() || right.evaluate(environment)?.truth(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ pub fn main() {
|
|||
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);
|
||||
let ast = build_ast(&content).unwrap();
|
||||
let mut machine = Machine::new();
|
||||
machine.run(ast);
|
||||
machine.run(ast).expect("execution failed");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::parser::syntax::Value;
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Environment {
|
||||
pub vars: HashMap<String, Value>,
|
||||
}
|
||||
|
@ -25,11 +26,8 @@ impl Environment {
|
|||
self.vars.insert(name.to_string(), node);
|
||||
}
|
||||
|
||||
pub fn get(&mut self, name: &str) -> Value {
|
||||
match self.vars.get(name) {
|
||||
Some(value) => *value,
|
||||
None => panic!("Variable {} not found", name),
|
||||
}
|
||||
pub fn get(&mut self, name: &str) -> Option<&Value> {
|
||||
self.vars.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,17 @@ use super::{
|
|||
use crate::parser::syntax::{Expr, Value};
|
||||
|
||||
pub trait Evaluate {
|
||||
fn evaluate(&self, environment: &mut Environment) -> Value;
|
||||
fn evaluate(&self, environment: &mut Environment) -> Result<Value, String>;
|
||||
}
|
||||
|
||||
impl Evaluate for Expr {
|
||||
fn evaluate(&self, environment: &mut Environment) -> Value {
|
||||
fn evaluate(&self, environment: &mut Environment) -> Result<Value, String> {
|
||||
match *self {
|
||||
Expr::Litteral(ref value) => *value,
|
||||
Expr::Variable(ref name) => environment.get(name),
|
||||
Expr::Litteral(ref value) => Ok(*value),
|
||||
Expr::Variable(ref name) => match environment.get(name) {
|
||||
Some(value) => Ok(*value),
|
||||
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),
|
||||
}
|
||||
|
|
|
@ -8,44 +8,44 @@ pub enum ControlFlow {
|
|||
}
|
||||
|
||||
pub trait Execution {
|
||||
fn execute(&self, environment: &mut Environment) -> ControlFlow;
|
||||
fn execute(&self, environment: &mut Environment) -> Result<ControlFlow, String>;
|
||||
}
|
||||
|
||||
impl Execution for Stat {
|
||||
fn execute(&self, environment: &mut Environment) -> ControlFlow {
|
||||
fn execute(&self, environment: &mut Environment) -> Result<ControlFlow, String> {
|
||||
match *self {
|
||||
Stat::Condition(ref condition, ref consequence, ref alternative) => {
|
||||
if condition.evaluate(environment).truth() {
|
||||
if condition.evaluate(environment)?.truth() {
|
||||
for statement in consequence {
|
||||
if let ControlFlow::Break = statement.execute(environment) {
|
||||
return ControlFlow::Break;
|
||||
if let ControlFlow::Break = statement.execute(environment)? {
|
||||
return Ok(ControlFlow::Break);
|
||||
}
|
||||
}
|
||||
} else if let Some(statements) = alternative {
|
||||
for statement in statements {
|
||||
if let ControlFlow::Break = statement.execute(environment) {
|
||||
return ControlFlow::Break;
|
||||
if let ControlFlow::Break = statement.execute(environment)? {
|
||||
return Ok(ControlFlow::Break);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Next
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
Stat::Loop(ref cond, ref body) => {
|
||||
while cond.evaluate(environment).truth() {
|
||||
while cond.evaluate(environment)?.truth() {
|
||||
for statement in body {
|
||||
if let ControlFlow::Break = statement.execute(environment) {
|
||||
return ControlFlow::Next;
|
||||
if let ControlFlow::Break = statement.execute(environment)? {
|
||||
return Ok(ControlFlow::Next);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Next
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
Stat::Assignment(ref name, ref expr) => {
|
||||
let value = expr.evaluate(environment);
|
||||
let value = expr.evaluate(environment)?;
|
||||
environment.add(name, value);
|
||||
ControlFlow::Next
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
Stat::Break => ControlFlow::Break,
|
||||
Stat::Break => Ok(ControlFlow::Break),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,10 +71,10 @@ impl Machine {
|
|||
Machine { environment }
|
||||
}
|
||||
|
||||
pub fn run(&mut self, statements: Vec<Stat>) {
|
||||
pub fn run(&mut self, statements: Vec<Stat>) -> Result<(), String> {
|
||||
for statement in statements {
|
||||
statement.execute(&mut self.environment);
|
||||
statement.execute(&mut self.environment)?;
|
||||
}
|
||||
println!("Environment state:\n{}", self.environment);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ mod tests {
|
|||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.expect(&format!("Error in reading file {}", path));
|
||||
let ast = build_ast(&content);
|
||||
let ast = build_ast(&content).unwrap();
|
||||
let mut machine = Machine::new();
|
||||
machine.run(ast);
|
||||
machine.run(ast).expect("execution failed");
|
||||
machine.environment
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,12 @@ mod tests {
|
|||
fn test_assign() {
|
||||
let mut environment = run_file("test_programs/assign.duck");
|
||||
let x = match environment.get("x") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("x should be Value::Boolean"),
|
||||
};
|
||||
assert!(x == true);
|
||||
let y = match environment.get("y") {
|
||||
Value::Number(value) => value,
|
||||
Some(Value::Number(value)) => *value,
|
||||
_ => panic!("y should be Value::Number"),
|
||||
};
|
||||
assert!(y == 1);
|
||||
|
@ -41,17 +41,17 @@ mod tests {
|
|||
fn test_arithmetic_expr() {
|
||||
let mut environment = run_file("test_programs/arithmetic_expr.duck");
|
||||
let x = match environment.get("x") {
|
||||
Value::Number(value) => value,
|
||||
Some(Value::Number(value)) => *value,
|
||||
_ => panic!("x should be Value::Number"),
|
||||
};
|
||||
assert!(x == 2);
|
||||
let y = match environment.get("y") {
|
||||
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") {
|
||||
Value::Number(value) => value,
|
||||
Some(Value::Number(value)) => *value,
|
||||
_ => panic!("z should be Value::Number"),
|
||||
};
|
||||
assert!(z == x % (-3 / 2));
|
||||
|
@ -61,52 +61,52 @@ mod tests {
|
|||
fn test_boolean_expr() {
|
||||
let mut environment = run_file("test_programs/boolean_expr.duck");
|
||||
let a = match environment.get("a") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("i should be Value::Boolean"),
|
||||
};
|
||||
assert!(i == !true);
|
||||
let j = match environment.get("j") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("j should be Value::Boolean"),
|
||||
};
|
||||
assert!(j == !false);
|
||||
|
@ -116,82 +116,82 @@ mod tests {
|
|||
fn test_compare_expr() {
|
||||
let mut environment = run_file("test_programs/compare_expr.duck");
|
||||
let a = match environment.get("a") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("p should be Value::Boolean"),
|
||||
};
|
||||
assert!(p == (2 != 2));
|
||||
|
@ -201,27 +201,27 @@ mod tests {
|
|||
fn test_conditional() {
|
||||
let mut environment = run_file("test_programs/conditional.duck");
|
||||
let x = match environment.get("x") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("x should be Value::Boolean"),
|
||||
};
|
||||
assert!(x == true);
|
||||
let y = match environment.get("y") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("y should be Value::Boolean"),
|
||||
};
|
||||
assert!(y == false);
|
||||
let z = match environment.get("z") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("z should be Value::Boolean"),
|
||||
};
|
||||
assert!(z == true);
|
||||
let a = match environment.get("a") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("a should be Value::Boolean"),
|
||||
};
|
||||
assert!(a == true);
|
||||
let b = match environment.get("b") {
|
||||
Value::Boolean(value) => value,
|
||||
Some(Value::Boolean(value)) => *value,
|
||||
_ => panic!("b should be Value::Boolean"),
|
||||
};
|
||||
assert!(b == true);
|
||||
|
@ -231,12 +231,12 @@ mod tests {
|
|||
fn test_while_loop() {
|
||||
let mut environment = run_file("test_programs/while_loop.duck");
|
||||
let x = match environment.get("x") {
|
||||
Value::Number(value) => value,
|
||||
Some(Value::Number(value)) => *value,
|
||||
_ => panic!("x should be Value::Number"),
|
||||
};
|
||||
assert!(x == 5);
|
||||
let y = match environment.get("y") {
|
||||
Value::Number(value) => value,
|
||||
Some(Value::Number(value)) => *value,
|
||||
_ => panic!("y should be Value::Number"),
|
||||
};
|
||||
assert!(y == 5);
|
||||
|
|
|
@ -47,108 +47,123 @@ fn parsing_error(pair: Pair<Rule>, error: &str) -> String {
|
|||
error_print.join("\n")
|
||||
}
|
||||
|
||||
fn build_expr(pair: Pair<Rule>) -> Box<Expr> {
|
||||
// println!("build_expr::{:#?}", pair);
|
||||
fn build_expr(pair: Pair<Rule>) -> Result<Box<Expr>, String> {
|
||||
PRATT_PARSER
|
||||
.map_primary(|primary| match primary.as_rule() {
|
||||
Rule::term | Rule::expr => build_expr(primary),
|
||||
Rule::number => Expr::litteral(Value::number(primary.as_str())),
|
||||
Rule::boolean => Expr::litteral(Value::boolean(primary.as_str())),
|
||||
Rule::variable => Expr::variable(primary.as_str()),
|
||||
rule => unreachable!("SimpleParser::climb expected atom, found {:?}", rule),
|
||||
})
|
||||
.map_infix(|lhs, op, rhs| match op.as_rule() {
|
||||
Rule::op_add => Expr::binary_op(lhs, rhs, BinaryOp::Addition),
|
||||
Rule::op_sub => Expr::binary_op(lhs, rhs, BinaryOp::Subtraction),
|
||||
Rule::op_mul => Expr::binary_op(lhs, rhs, BinaryOp::Multiplication),
|
||||
Rule::op_div => Expr::binary_op(lhs, rhs, BinaryOp::Division),
|
||||
Rule::op_mod => Expr::binary_op(lhs, rhs, BinaryOp::Modulo),
|
||||
Rule::op_lt => Expr::binary_op(lhs, rhs, BinaryOp::LesserThan),
|
||||
Rule::op_gt => Expr::binary_op(lhs, rhs, BinaryOp::GreaterThan),
|
||||
Rule::op_eq => Expr::binary_op(lhs, rhs, BinaryOp::Equal),
|
||||
Rule::op_ge => Expr::binary_op(lhs, rhs, BinaryOp::GreaterEqual),
|
||||
Rule::op_le => Expr::binary_op(lhs, rhs, BinaryOp::LesserEqual),
|
||||
Rule::op_ne => Expr::binary_op(lhs, rhs, BinaryOp::NotEqual),
|
||||
Rule::op_or => Expr::binary_op(lhs, rhs, BinaryOp::Or),
|
||||
Rule::op_and => Expr::binary_op(lhs, rhs, BinaryOp::And),
|
||||
rule => unreachable!(
|
||||
"SimpleParser::climb expected infix operation, found {:?}",
|
||||
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 => Err(format!(
|
||||
"atom expected number, boolean or variable, found {:?}",
|
||||
rule
|
||||
),
|
||||
)),
|
||||
})
|
||||
.map_prefix(|op, rhs| match op.as_rule() {
|
||||
Rule::op_neg => Expr::unary_op(rhs, UnaryOp::Minus),
|
||||
Rule::op_not => Expr::unary_op(rhs, UnaryOp::Not),
|
||||
rule => unreachable!(
|
||||
"SimpleParser::climb expected prefix operation, found {:?}",
|
||||
.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_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_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)),
|
||||
(_, rule, _) => Err(format!(
|
||||
"binary operation expected operator, found {:?}",
|
||||
rule
|
||||
),
|
||||
)),
|
||||
})
|
||||
.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, _) => Err(format!(
|
||||
"unary operation expected operator, found {:?}",
|
||||
rule
|
||||
)),
|
||||
})
|
||||
.parse(pair.into_inner())
|
||||
}
|
||||
|
||||
fn build_assign(pair: Pair<Rule>) -> Box<Stat> {
|
||||
// println!("build_assign::{:#?}", pair);
|
||||
fn build_assign(pair: Pair<Rule>) -> Result<Box<Stat>, String> {
|
||||
let mut inner = pair.into_inner();
|
||||
let lhs = inner.next().unwrap().as_str();
|
||||
let rhs = build_expr(inner.next().unwrap());
|
||||
Stat::assign(lhs, rhs)
|
||||
let rhs = build_expr(inner.next().unwrap())?;
|
||||
Ok(Stat::assign(lhs, rhs))
|
||||
}
|
||||
|
||||
fn build_while_loop(pair: Pair<Rule>) -> Box<Stat> {
|
||||
// println!("build_while_loop::{:#?}", pair);
|
||||
fn build_while_loop(pair: Pair<Rule>) -> Result<Box<Stat>, String> {
|
||||
let mut inner = pair.into_inner();
|
||||
let cond = build_expr(inner.next().unwrap());
|
||||
let stmt = build_stats(inner.next().unwrap(), true);
|
||||
Stat::while_loop(cond, stmt)
|
||||
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) -> Box<Stat> {
|
||||
//println!("build_if_cond::{:#?}", pair);
|
||||
fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Result<Box<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.next().unwrap())?;
|
||||
let thenstmt = build_stats(inner.next().unwrap(), in_loop)?;
|
||||
match inner.next() {
|
||||
Some(stmt) => match stmt.as_rule() {
|
||||
Rule::stats => Stat::if_cond(cond, thenstmt, Some(build_stats(stmt, in_loop))),
|
||||
Rule::stat_elif => {
|
||||
Stat::if_cond(cond, thenstmt, Some(vec![*build_stat(stmt, in_loop)]))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
Rule::stats => Ok(Stat::if_cond(
|
||||
cond,
|
||||
thenstmt,
|
||||
Some(build_stats(stmt, in_loop)?),
|
||||
)),
|
||||
Rule::stat_elif => Ok(Stat::if_cond(
|
||||
cond,
|
||||
thenstmt,
|
||||
Some(vec![*build_stat(stmt, in_loop)?]),
|
||||
)),
|
||||
rule => Err(format!("conditional expected statements, found {:?}", rule)),
|
||||
},
|
||||
None => Stat::if_cond(cond, thenstmt, None),
|
||||
None => Ok(Stat::if_cond(cond, thenstmt, None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Box<Stat> {
|
||||
// println!("build_stat: {:#?}", pair);
|
||||
fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Result<Box<Stat>, String> {
|
||||
match pair.as_rule() {
|
||||
Rule::stat_assign => build_assign(pair),
|
||||
Rule::stat_if | Rule::stat_elif => build_if_cond(pair, in_loop),
|
||||
Rule::stat_while => build_while_loop(pair),
|
||||
Rule::stat_break => {
|
||||
if in_loop {
|
||||
return Stat::break_keyword();
|
||||
return Ok(Stat::break_keyword());
|
||||
}
|
||||
panic!("{}", parsing_error(pair, "break keyword while not in loop"))
|
||||
Err(parsing_error(pair, "break keyword while not in loop"))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
rule => Err(format!(
|
||||
"statement expected assign, if, while or break, found {:?}",
|
||||
rule
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_stats(pair: Pair<Rule>, in_loop: bool) -> Vec<Stat> {
|
||||
// println!("build_stats::{:#?}", pair);
|
||||
fn build_stats(pair: Pair<Rule>, in_loop: bool) -> Result<Vec<Stat>, String> {
|
||||
let inner = pair.into_inner();
|
||||
inner
|
||||
.into_iter()
|
||||
.map(|pair| *build_stat(pair, in_loop))
|
||||
.collect()
|
||||
let mut stats = Vec::new();
|
||||
for pair in inner {
|
||||
stats.push(*build_stat(pair, in_loop)?);
|
||||
}
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
pub fn build_ast(content: &str) -> Vec<Stat> {
|
||||
let pair = DuckParser::parse(Rule::program, content)
|
||||
.unwrap_or_else(|e| panic!("{}", e))
|
||||
.next()
|
||||
.unwrap();
|
||||
pub fn build_ast(content: &str) -> Result<Vec<Stat>, String> {
|
||||
let pair = match DuckParser::parse(Rule::program, content) {
|
||||
Ok(mut pairs) => pairs.next().unwrap(),
|
||||
Err(err) => return Err(format!("{}", err)),
|
||||
};
|
||||
build_stats(pair, false)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ pub fn main() {
|
|||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.unwrap_or_else(|_| panic!("Error in reading file {}", arg));
|
||||
build_ast(&content);
|
||||
build_ast(&content).expect("parsing failed");
|
||||
println!("{} is a valid ducklang program", arg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ mod tests {
|
|||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.expect(&format!("Error in reading file {}", path));
|
||||
build_ast(&content);
|
||||
build_ast(&content).unwrap();
|
||||
}
|
||||
|
||||
// can loop with some tests libs ?
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::should_implement_trait)]
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
// REFACTORING
|
||||
// TODO:
|
||||
|
@ -24,7 +24,7 @@ pub enum BinaryOp {
|
|||
}
|
||||
|
||||
impl Display for BinaryOp {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
BinaryOp::Addition => write!(f, "+"),
|
||||
BinaryOp::Subtraction => write!(f, "-"),
|
||||
|
@ -49,7 +49,7 @@ pub enum UnaryOp {
|
|||
}
|
||||
|
||||
impl Display for UnaryOp {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
UnaryOp::Minus => write!(f, "-"),
|
||||
UnaryOp::Not => write!(f, "!"),
|
||||
|
@ -64,29 +64,34 @@ pub enum Value {
|
|||
}
|
||||
|
||||
impl Value {
|
||||
pub fn number(raw: &str) -> Value {
|
||||
Value::Number(raw.parse::<i64>().unwrap())
|
||||
pub fn number(raw: &str) -> Result<Value, String> {
|
||||
match raw.parse::<i64>() {
|
||||
Ok(value) => Ok(Value::Number(value)),
|
||||
Err(_) => Err(format!("{} is not a number", raw)),
|
||||
}
|
||||
}
|
||||
pub fn boolean(raw: &str) -> Value {
|
||||
Value::Boolean(raw.parse::<bool>().unwrap())
|
||||
pub fn boolean(raw: &str) -> Result<Value, String> {
|
||||
match raw.parse::<bool>() {
|
||||
Ok(value) => Ok(Value::Boolean(value)),
|
||||
Err(_) => Err(format!("{} is not a boolean", raw)),
|
||||
}
|
||||
}
|
||||
pub fn value(&self) -> i64 {
|
||||
pub fn value(&self) -> Result<i64, String> {
|
||||
match self {
|
||||
Value::Number(n) => *n,
|
||||
_ => panic!("Value::as_number: not a number"),
|
||||
Value::Number(n) => Ok(*n),
|
||||
_ => Err(format!("{} is not a number", self)),
|
||||
}
|
||||
}
|
||||
pub fn truth(&self) -> bool {
|
||||
match self {
|
||||
Value::Boolean(b) => *b,
|
||||
Value::Number(value) => *value != 0,
|
||||
// _ => panic!("Value::as_boolean: not a boolean"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Value::Number(n) => write!(f, "{}", n),
|
||||
Value::Boolean(b) => write!(f, "{}", b),
|
||||
|
@ -117,7 +122,7 @@ impl Expr {
|
|||
}
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Expr::Litteral(value) => write!(f, "{}", value),
|
||||
Expr::Variable(name) => write!(f, "{}", name),
|
||||
|
@ -158,7 +163,7 @@ impl Stat {
|
|||
}
|
||||
|
||||
impl Display for Stat {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Stat::Condition(condition, body, else_body) => {
|
||||
write!(f, "if {} {{", condition)?;
|
||||
|
|
132
src/web/main.rs
132
src/web/main.rs
|
@ -1,25 +1,47 @@
|
|||
// const CANVAS_WIDTH: f32 = 1280.0;
|
||||
// const CANVAS_HEIGHT: f32 = 720.0;
|
||||
use ducklang::{interpreter::machine::Machine, parser::ast::build_ast};
|
||||
|
||||
const EXAMPLE_CODE: &str = r#"// example code
|
||||
x = true;
|
||||
z = 3 + 4 * (2 + 1); // good order of op
|
||||
if (x) {
|
||||
y = 5;
|
||||
while (x) {
|
||||
if (!y && z % 2) {
|
||||
z = 8;
|
||||
break;
|
||||
} else if (y == 3) {
|
||||
z = 5;
|
||||
}
|
||||
y = y - 1;
|
||||
}
|
||||
} else {
|
||||
// never reached
|
||||
y = -8;
|
||||
x = true;
|
||||
}"#;
|
||||
|
||||
pub struct MyEguiApp {
|
||||
_code: String,
|
||||
code: String,
|
||||
machine: Machine,
|
||||
error: String,
|
||||
}
|
||||
|
||||
impl MyEguiApp {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
Self {
|
||||
_code: String::new(),
|
||||
code: String::from(EXAMPLE_CODE),
|
||||
machine: Machine::new(),
|
||||
error: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for MyEguiApp {
|
||||
fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// let toolbar = self.render_toolbar(ctx);
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// frame.set_window_size(egui::vec2(CANVAS_WIDTH, CANVAS_HEIGHT + toolbar.height()));
|
||||
// ctx.request_repaint();
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
let _toolbar = self.render_toolbar(ctx);
|
||||
let _side_panel = self.render_side_panel(ctx);
|
||||
let _error_panel = self.render_error_panel(ctx);
|
||||
let _central_panel = self.render_central_panel(ctx);
|
||||
// TODO
|
||||
// toolbar:
|
||||
// - reset code and env
|
||||
|
@ -33,6 +55,91 @@ impl eframe::App for MyEguiApp {
|
|||
}
|
||||
}
|
||||
|
||||
impl MyEguiApp {
|
||||
fn render_error_panel(&mut self, ctx: &egui::Context) -> egui::Rect {
|
||||
let error_panel = egui::TopBottomPanel::bottom("error_panel").show(ctx, |ui| {
|
||||
ui.heading("Error");
|
||||
ui.separator();
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.error.as_str()).desired_width(f32::INFINITY),
|
||||
);
|
||||
});
|
||||
error_panel.response.rect
|
||||
}
|
||||
|
||||
fn render_central_panel(&mut self, ctx: &egui::Context) -> egui::Rect {
|
||||
let central_panel = egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// calculate desired rows based on the height of the window
|
||||
let font_selection = egui::FontSelection::default();
|
||||
let font_id = font_selection.resolve(ui.style());
|
||||
let row_height = ui.fonts().row_height(&font_id);
|
||||
let rows = (ui.available_size().y / row_height) as usize;
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut self.code)
|
||||
.code_editor()
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(rows),
|
||||
);
|
||||
});
|
||||
central_panel.response.rect
|
||||
}
|
||||
|
||||
fn render_side_panel(&mut self, ctx: &egui::Context) -> egui::Rect {
|
||||
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() {
|
||||
ui.label(format!("{}: {}", name, value));
|
||||
}
|
||||
});
|
||||
side_panel.response.rect
|
||||
}
|
||||
|
||||
fn render_toolbar(&mut self, ctx: &egui::Context) -> egui::Rect {
|
||||
let mut frame = egui::Frame::side_top_panel(ctx.style().as_ref());
|
||||
frame.inner_margin.bottom += 1.5;
|
||||
let toolbar = egui::TopBottomPanel::top("toolbar")
|
||||
.frame(frame)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.style_mut().spacing.interact_size.y *= 1.25;
|
||||
ui.style_mut()
|
||||
.text_styles
|
||||
.get_mut(&egui::TextStyle::Button)
|
||||
.unwrap()
|
||||
.size *= 1.25;
|
||||
if ui.button("Reset").clicked() {
|
||||
self.reset();
|
||||
}
|
||||
if ui.button("Run").clicked() {
|
||||
self.run();
|
||||
}
|
||||
});
|
||||
});
|
||||
toolbar.response.rect
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.machine = Machine::new();
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
match build_ast(&self.code) {
|
||||
Ok(ast) => match self.machine.run(ast) {
|
||||
Ok(_) => {
|
||||
self.error = String::new();
|
||||
}
|
||||
Err(e) => {
|
||||
self.error = e;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
self.error = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
// Make sure panics are logged using `console.error`.
|
||||
|
@ -57,11 +164,12 @@ fn main() {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
resizable: false,
|
||||
initial_window_size: Some(egui::vec2(1280.0, 720.0)),
|
||||
resizable: true,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"Game of Life",
|
||||
"Duckscript Playground",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyEguiApp::new(cc))),
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue