feat(interpreter): interpreter with code editor UI

egui
flavien 2023-01-03 17:38:54 +01:00
parent 056d304b73
commit 252c23e889
13 changed files with 359 additions and 217 deletions

View File

@ -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
```

View File

@ -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 {

View File

@ -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(),
)),
}
}
}

View File

@ -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");
}
}

View File

@ -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)
}
}

View File

@ -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),
}

View File

@ -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(())
}
}

View File

@ -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);

View File

@ -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)
}

View File

@ -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);
}
}

View File

@ -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 ?

View File

@ -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)?;

View File

@ -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))),
);