Compare commits

...

4 Commits

22 changed files with 2813 additions and 341 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

2
.gitignore vendored
View File

@ -1 +1,3 @@
/target
/.vscode
/dist

1961
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,14 @@ edition = "2021"
[dependencies]
lazy_static = "1.4.0"
pest = "2.0"
pest_derive = "2.0"
pest = "2.7.5"
pest_derive = "2.7.5"
# web
egui = "0.24.0"
eframe = { version = "0.24.0", default-features = false, features = [
"default_fonts", # Embed the default egui fonts.
"glow", # Use the glow rendering backend. Alternative: "wgpu".
] }
[lib]
name = "ducklang"
@ -21,3 +27,21 @@ path = "src/parser/duck_parser.rs"
[[bin]]
name = "duck_interpreter"
path = "src/interpreter/duck_interpreter.rs"
[[bin]]
name = "duck_web"
path = "src/web/main.rs"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3.18"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
tracing-wasm = "0.2.1"
wasm-bindgen-futures = "0.4.39"
[profile.release]
opt-level = 's'
lto = true

View File

@ -12,11 +12,14 @@ 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
- compile time reading of the example file
- syntax color
- line numbers
- better panel title
- panel tabs
- print / string => language
- console for print
### Example
```
@ -53,21 +56,20 @@ 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, continue keyword
- list
- import from other files
- refactor parsing error printing
- unit tests
### tools
- live interpreter
- repl
- LLVM IR translation
- JIT
- compilation

View File

@ -8,10 +8,12 @@ if (x) {
y = 5;
while (x) {
if (!y && z % 2) {
z = 8;
break;
} else if (y == 3) {
} else if (y == 3) { // never reached because of continue
z = 5;
} else if (y == 4) {
y = y - 2;
continue;
}
y = y - 1;
}

38
index.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-bin="duck_web" data-wasm-opt="2" />
<style>
html {
/* Remove touch delay: */
touch-action: manipulation;
}
html,
body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
height: 100%;
width: 100%;
}
canvas {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, 0%);
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -2,69 +2,79 @@ 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, env: &mut dyn 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, env: &mut dyn Environment, expr: &Expr) -> Result<Value, String> {
Ok(match self {
UnaryOp::Minus => Value::Number(-expr.evaluate(env)?.value()?),
UnaryOp::Not => Value::Boolean(!expr.evaluate(env)?.truth()?),
})
}
}
pub trait BinaryCompute {
fn compute(&self, environment: &mut Environment, left: &Expr, right: &Expr) -> Value;
fn compute(
&self,
env: &mut dyn 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,
env: &mut dyn 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(env)?.value()? + right.evaluate(env)?.value()?,
)),
BinaryOp::Subtraction => Ok(Value::Number(
left.evaluate(env)?.value()? - right.evaluate(env)?.value()?,
)),
BinaryOp::Multiplication => Ok(Value::Number(
left.evaluate(env)?.value()? * right.evaluate(env)?.value()?,
)),
BinaryOp::Division => Ok(Value::Number(
left.evaluate(env)?.value()? / right.evaluate(env)?.value()?,
)),
BinaryOp::Modulo => Ok(Value::Number(
left.evaluate(env)?.value()? % right.evaluate(env)?.value()?,
)),
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)) {
(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(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 => 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(env)?.value() >= right.evaluate(env)?.value(),
)),
BinaryOp::GreaterThan => Ok(Value::Boolean(
left.evaluate(env)?.value() > right.evaluate(env)?.value(),
)),
BinaryOp::LesserEqual => Ok(Value::Boolean(
left.evaluate(env)?.value() <= right.evaluate(env)?.value(),
)),
BinaryOp::LesserThan => Ok(Value::Boolean(
left.evaluate(env)?.value() < right.evaluate(env)?.value(),
)),
BinaryOp::And => Ok(Value::Boolean(
left.evaluate(env)?.truth()? && right.evaluate(env)?.truth()?,
)),
BinaryOp::Or => Ok(Value::Boolean(
left.evaluate(env)?.truth()? || right.evaluate(env)?.truth()?,
)),
}
}
}

View File

@ -1,15 +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) {
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);
let mut machine = Machine::new();
machine.run(ast);
}
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,45 +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;
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) -> Value {
match self.vars.get(name) {
Some(value) => *value,
None => panic!("Variable {} not found", 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();
write!(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,20 +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) -> Value;
fn evaluate(&self, env: &mut dyn Environment) -> Result<Value, String>;
}
impl Evaluate for Expr {
fn evaluate(&self, environment: &mut Environment) -> Value {
fn evaluate(&self, env: &mut dyn Environment) -> Result<Value, String> {
match *self {
Expr::Litteral(ref value) => *value,
Expr::Variable(ref name) => environment.get(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::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(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,57 +1,74 @@
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) -> ControlFlow;
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String>;
}
impl Execution for Vec<Stat> {
fn execute(&self, environment: &mut dyn Environment) -> Result<ControlFlow, String> {
for statement in self {
match statement.execute(environment)? {
ControlFlow::Next => (),
flow @ (ControlFlow::Break | ControlFlow::Continue) => return Ok(flow),
ControlFlow::Return(value) => return Ok(ControlFlow::Return(value)),
}
}
Ok(ControlFlow::Next)
}
}
impl Execution for Stat {
fn execute(&self, environment: &mut Environment) -> ControlFlow {
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() {
for statement in consequence {
if let ControlFlow::Break = statement.execute(environment) {
return ControlFlow::Break;
}
}
if condition.evaluate(environment)?.truth()? {
return consequence.execute(environment);
} else if let Some(statements) = alternative {
for statement in statements {
if let ControlFlow::Break = statement.execute(environment) {
return ControlFlow::Break;
}
}
return statements.execute(environment);
}
ControlFlow::Next
Ok(ControlFlow::Next)
}
Stat::Loop(ref cond, ref body) => {
while cond.evaluate(environment).truth() {
for statement in body {
if let ControlFlow::Break = statement.execute(environment) {
return ControlFlow::Next;
}
while cond.evaluate(environment)?.truth()? {
if let ControlFlow::Break = body.execute(environment)? {
break;
}
}
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
// println!("environment:\n{}", environment);
Ok(ControlFlow::Next)
}
Stat::Break => ControlFlow::Break,
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 {
@ -63,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>) {
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();
}
}
println!("Environment state:\n{}", self.environment);
Ok(0)
}
}

View File

@ -8,30 +8,33 @@ 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)
.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
}
#[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") {
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);
@ -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") {
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));
@ -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") {
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);
@ -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") {
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));
@ -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") {
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);
@ -229,16 +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") {
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);
let z = match environment.get("z") {
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,108 +47,199 @@ 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_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 => 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::call => build_call(primary),
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(Box::new(lhs), Box::new(rhs), BinaryOp::Or))
}
(Ok(lhs), Rule::op_and, Ok(rhs)) => {
Ok(Expr::binary_op(Box::new(lhs), Box::new(rhs), BinaryOp::And))
}
(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
),
)),
})
.map_prefix(|op, rhs| match (op.as_rule(), rhs) {
(_, Err(err)) => Err(err),
(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
)),
})
.parse(pair.into_inner())
}
fn build_assign(pair: Pair<Rule>) -> Box<Stat> {
// println!("build_assign::{:#?}", pair);
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());
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<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<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 statement expected, 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_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),
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::stat_continue => {
if in_loop {
return Ok(Stat::continue_keyword());
}
Err(parsing_error(pair, "continue keyword while not in loop"))
}
Rule::stat_define => build_define(pair),
Rule::stat_return => build_return(pair),
rule => Err(format!("Statement expected, 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

@ -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,15 @@ 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 ~ ";" }
stat_while = { "while" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" }
@ -58,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 = _{ ( stat_if | stat_while | stat_assign | stat_break | stat_continue | stat_define | stat_return ) }
stats = { (stat)* }

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 ?
@ -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::{Display, Formatter, Result};
// 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,
@ -24,7 +22,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, "-"),
@ -43,13 +41,14 @@ impl Display for BinaryOp {
}
}
#[derive(Clone, Copy, Debug)]
pub enum UnaryOp {
Minus,
Not,
}
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, "!"),
@ -57,113 +56,146 @@ 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 {
pub fn number(raw: &str) -> Value {
Value::Number(raw.parse::<i64>().unwrap())
}
pub fn boolean(raw: &str) -> Value {
Value::Boolean(raw.parse::<bool>().unwrap())
}
pub fn value(&self) -> i64 {
match self {
Value::Number(n) => *n,
_ => panic!("Value::as_number: not a number"),
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 truth(&self) -> bool {
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)),
}
}
// TODO move to compute.rs
pub fn value(&self) -> Result<i64, String> {
match self {
Value::Boolean(b) => *b,
Value::Number(value) => *value != 0,
// _ => panic!("Value::as_boolean: not a boolean"),
Value::Number(n) => Ok(*n),
_ => Err(format!("{} is not a number", self)),
}
}
// TODO move to compute.rs
pub fn truth(&self) -> Result<bool, String> {
match self {
Value::Boolean(b) => Ok(*b),
Value::Number(value) => Ok(*value != 0),
_ => Err(format!("{} is not a logical value", self)),
}
}
}
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),
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)
}
}
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),
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 while_loop(condition: Box<Expr>, body: Vec<Stat>) -> Box<Stat> {
Box::new(Stat::Loop(condition, body))
pub fn continue_keyword() -> Stat {
Stat::Continue
}
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 while_loop(condition: Expr, body: Vec<Stat>) -> Stat {
Stat::Loop(condition, 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)
}
}
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)?;
write!(f, "if ({}) {{", condition)?;
for statement in body {
write!(f, "{}", statement)?;
write!(f, " {} ", statement)?;
}
write!(f, "}}")?;
if let Some(else_body) = else_body {
@ -176,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)?;
}
@ -184,6 +216,17 @@ 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),
}
}
}

182
src/web/main.rs Normal file
View File

@ -0,0 +1,182 @@
use ducklang::{interpreter::machine::Machine, parser::ast::build_ast};
const EXAMPLE_CODE: &str = r#"/*
An example of program with most of
the supported features
*/
x = true;
z = 3 + 4 * (2 + 1); // good order of op
if (x) {
y = 5;
while (x) {
if (!y && z % 2) {
break;
} else if (y == 3) { // never reached because of continue
z = 5;
} else if (y == 4) {
y = y - 2;
continue;
}
y = y - 1;
}
} else {
// never reached
y = -8;
x = true;
}"#;
pub struct MyEguiApp {
code: String,
machine: Machine,
error: String,
}
impl MyEguiApp {
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
Self {
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);
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
// - reset env
// - run code
// - ...
// central panel:
// - code editor
// side panel:
// - env
}
}
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(|fonts| 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.global.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`.
console_error_panic_hook::set_once();
tracing_wasm::set_as_global_default();
let web_options = eframe::WebOptions::default();
wasm_bindgen_futures::spawn_local(async {
eframe::start_web(
// this is the id of the `<canvas>` element we have
// in our `index.html`
"canvas",
web_options,
Box::new(|cc| Box::new(MyEguiApp::new(cc))),
)
.await
.expect("failed to start eframe");
});
}
#[cfg(not(target_arch = "wasm32"))]
fn main() {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([1280.0, 720.0])
.with_resizable(true),
..Default::default()
};
let _run_native = eframe::run_native(
"Duckscript Playground",
options,
Box::new(|cc| Box::new(MyEguiApp::new(cc))),
);
}

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;

View File

@ -1,8 +1,13 @@
x = 0;
y = 0;
z = 0;
while (x < 5) {
x = x + 1;
if (x % 2) {
continue;
}
z = z + 1;
}
while (true) {