feat(string): string, chars and indexing
parent
20a1c526e0
commit
312aef4475
12
README.md
12
README.md
|
@ -57,14 +57,16 @@ var: z => val: 8
|
|||
## TODO
|
||||
### syntax
|
||||
- functions OK
|
||||
- strings
|
||||
- print statement
|
||||
- types declaration
|
||||
- types inference
|
||||
- array / list / string / map => iterators
|
||||
- print statement OK
|
||||
- string / char => indexing
|
||||
- array / list / map => iterators
|
||||
- read / write
|
||||
- types declaration
|
||||
- type checking before runtime
|
||||
- types inference
|
||||
- program arguments
|
||||
- structs
|
||||
- range
|
||||
- import from other files
|
||||
- refactor parsing error printing
|
||||
- unit tests
|
||||
|
|
|
@ -7,6 +7,7 @@ z = 3 + 4 * (2 + 1); // good order of op
|
|||
if (x) {
|
||||
y = 5;
|
||||
while (x) {
|
||||
print y;
|
||||
if (!y && z % 2) {
|
||||
break;
|
||||
} else if (y == 3) { // never reached because of continue
|
||||
|
@ -21,4 +22,10 @@ if (x) {
|
|||
// never reached
|
||||
y = -8;
|
||||
x = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn hello(name) {
|
||||
return "Hello, " + name + '!';
|
||||
}
|
||||
|
||||
print hello("flavien");
|
|
@ -1,6 +1,45 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use super::{environment::Environment, evaluate::Evaluate};
|
||||
use crate::parser::syntax::{BinaryOp, Expr, UnaryOp, Value};
|
||||
|
||||
pub trait ValueCompute {
|
||||
fn int(&self) -> Result<i64, String>;
|
||||
fn truth(&self) -> Result<bool, String>;
|
||||
fn str(&self) -> Result<Rc<String>, String>;
|
||||
fn char(&self) -> Result<char, String>;
|
||||
}
|
||||
|
||||
impl ValueCompute for Value {
|
||||
fn int(&self) -> Result<i64, String> {
|
||||
match self {
|
||||
Value::Number(n) => Ok(*n),
|
||||
_ => Err(format!("{} is not a number", self)),
|
||||
}
|
||||
}
|
||||
fn truth(&self) -> Result<bool, String> {
|
||||
match self {
|
||||
Value::Boolean(b) => Ok(*b),
|
||||
Value::Number(value) => Ok(*value != 0),
|
||||
Value::String(string) => Ok(!string.is_empty()),
|
||||
Value::Char(c) => Ok(*c != '\0'),
|
||||
_ => Err(format!("{} is not a logical value", self)),
|
||||
}
|
||||
}
|
||||
fn str(&self) -> Result<Rc<String>, String> {
|
||||
match self {
|
||||
Value::String(s) => Ok(s.clone()),
|
||||
_ => Err(format!("{} is not a string", self)),
|
||||
}
|
||||
}
|
||||
fn char(&self) -> Result<char, String> {
|
||||
match self {
|
||||
Value::Char(c) => Ok(*c),
|
||||
_ => Err(format!("{} is not a char", self)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UnaryCompute {
|
||||
fn compute(&self, env: &mut dyn Environment, expr: &Expr) -> Result<Value, String>;
|
||||
}
|
||||
|
@ -8,8 +47,20 @@ pub trait UnaryCompute {
|
|||
impl UnaryCompute for UnaryOp {
|
||||
fn compute(&self, env: &mut dyn Environment, expr: &Expr) -> Result<Value, String> {
|
||||
Ok(match self {
|
||||
UnaryOp::Minus => Value::Number(-expr.evaluate(env)?.value()?),
|
||||
UnaryOp::Minus => Value::Number(-expr.evaluate(env)?.int()?),
|
||||
UnaryOp::Not => Value::Boolean(!expr.evaluate(env)?.truth()?),
|
||||
UnaryOp::Index(index) => {
|
||||
let index = index.evaluate(env)?.int()?;
|
||||
let value = expr.evaluate(env)?.str()?;
|
||||
if index < 0 || index >= value.len() as i64 {
|
||||
return Err(format!(
|
||||
"Index {} out of bounds for string of length {}",
|
||||
index,
|
||||
value.len()
|
||||
));
|
||||
}
|
||||
Value::Char(value.chars().nth(index as usize).unwrap())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -32,43 +83,88 @@ impl BinaryCompute for BinaryOp {
|
|||
right: &Expr,
|
||||
) -> Result<Value, String> {
|
||||
match self {
|
||||
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::Addition => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l + r)),
|
||||
(Value::String(l), Value::Char(r)) => {
|
||||
Ok(Value::String(Rc::new(format!("{}{}", l, r))))
|
||||
}
|
||||
(Value::Char(l), Value::String(r)) => {
|
||||
Ok(Value::String(Rc::new(format!("{}{}", l, r))))
|
||||
}
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Char((l as u8 + r as u8) as char)),
|
||||
(Value::Char(l), Value::Number(r)) => Ok(Value::Char((l as u8 + r as u8) as char)),
|
||||
(Value::Number(l), Value::Char(r)) => Ok(Value::Char((l as u8 + r as u8) as char)),
|
||||
(Value::String(l), Value::String(r)) => {
|
||||
Ok(Value::String(Rc::new(format!("{}{}", l, r))))
|
||||
}
|
||||
(l, r) => Err(format!("Cannot add {} to {}", l, r)),
|
||||
},
|
||||
BinaryOp::Subtraction => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l - r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Char((l as u8 - r as u8) as char)),
|
||||
(Value::Char(l), Value::Number(r)) => Ok(Value::Char((l as u8 - r as u8) as char)),
|
||||
(Value::Number(l), Value::Char(r)) => Ok(Value::Char((l as u8 - r as u8) as char)),
|
||||
(l, r) => Err(format!("Cannot subtract {} from {}", r, l)),
|
||||
},
|
||||
BinaryOp::Multiplication => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l * r)),
|
||||
(Value::Char(l), Value::Number(r)) => Ok(Value::String(Rc::new(
|
||||
(0..r).map(|_| l).collect::<String>(),
|
||||
))),
|
||||
(Value::Number(l), Value::Char(r)) => Ok(Value::String(Rc::new(
|
||||
(0..l).map(|_| r).collect::<String>(),
|
||||
))),
|
||||
(l, r) => Err(format!("Cannot multiply {} by {}", l, r)),
|
||||
},
|
||||
BinaryOp::Division => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l / r)),
|
||||
(l, r) => Err(format!("Cannot divide {} by {}", l, r)),
|
||||
},
|
||||
BinaryOp::Modulo => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Number(l % r)),
|
||||
(l, r) => Err(format!("Cannot modulo {} by {}", l, r)),
|
||||
},
|
||||
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()),
|
||||
(Value::String(l), Value::String(r)) => Ok(Value::Boolean(l == r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Boolean(l == r)),
|
||||
(l, r) => Err(format!("Cannot evaluate equality between {} and {}", l, r)),
|
||||
},
|
||||
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()),
|
||||
(Value::String(l), Value::String(r)) => Ok(Value::Boolean(l != r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Boolean(l != r)),
|
||||
(l, r) => Err(format!(
|
||||
"Cannot evaluate inequality between {} and {}",
|
||||
l, r
|
||||
)),
|
||||
},
|
||||
BinaryOp::GreaterEqual => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Boolean(l >= r)),
|
||||
(Value::String(l), Value::String(r)) => Ok(Value::Boolean(l >= r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Boolean(l >= r)),
|
||||
(l, r) => Err(format!("Cannot compare {} and {}", l, r)),
|
||||
},
|
||||
BinaryOp::GreaterThan => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Boolean(l > r)),
|
||||
(Value::String(l), Value::String(r)) => Ok(Value::Boolean(l > r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Boolean(l > r)),
|
||||
(l, r) => Err(format!("Cannot compare {} and {}", l, r)),
|
||||
},
|
||||
BinaryOp::LesserEqual => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Boolean(l <= r)),
|
||||
(Value::String(l), Value::String(r)) => Ok(Value::Boolean(l <= r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Boolean(l <= r)),
|
||||
(l, r) => Err(format!("Cannot compare {} and {}", l, r)),
|
||||
},
|
||||
BinaryOp::LesserThan => match (left.evaluate(env)?, right.evaluate(env)?) {
|
||||
(Value::Number(l), Value::Number(r)) => Ok(Value::Boolean(l < r)),
|
||||
(Value::String(l), Value::String(r)) => Ok(Value::Boolean(l < r)),
|
||||
(Value::Char(l), Value::Char(r)) => Ok(Value::Boolean(l < r)),
|
||||
(l, r) => Err(format!("Cannot compare {} and {}", l, r)),
|
||||
},
|
||||
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()?,
|
||||
)),
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use super::{
|
||||
compute::ValueCompute,
|
||||
environment::{Environment, GlobalScope},
|
||||
evaluate::Evaluate,
|
||||
};
|
||||
|
@ -31,7 +34,6 @@ impl Execution for Vec<Stat> {
|
|||
|
||||
impl Execution for Stat {
|
||||
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()? {
|
||||
|
@ -49,20 +51,42 @@ impl Execution for Stat {
|
|||
}
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
Stat::Assignment(ref name, ref expr) => {
|
||||
let value = expr.evaluate(environment)?;
|
||||
environment.add(name, value);
|
||||
// println!("environment:\n{}", environment);
|
||||
Stat::Assignment(ref name, ref index, ref expr) => {
|
||||
if let Some(index) = index {
|
||||
let index = index.evaluate(environment)?.int()?;
|
||||
let value = expr.evaluate(environment)?.char()?;
|
||||
let mut chars: Vec<char> = environment
|
||||
.get(name)
|
||||
.ok_or_else(|| format!("Variable {} not found", name))?
|
||||
.str()?
|
||||
.chars()
|
||||
.collect();
|
||||
if index < 0 || index >= chars.len() as i64 {
|
||||
return Err(format!(
|
||||
"Index {} out of bounds for string of length {}",
|
||||
index,
|
||||
chars.len()
|
||||
));
|
||||
}
|
||||
chars[index as usize] = value;
|
||||
environment.add(name, Value::String(Rc::new(chars.into_iter().collect())));
|
||||
} else {
|
||||
let value = expr.evaluate(environment)?;
|
||||
environment.add(name, value);
|
||||
}
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
Stat::Break => Ok(ControlFlow::Break),
|
||||
Stat::Continue => Ok(ControlFlow::Continue),
|
||||
Stat::Define(ref name, ref args, ref body) => {
|
||||
environment.add(name, Value::Function(args.clone(), body.clone()));
|
||||
// println!("environment:\n{}", environment);
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
Stat::Return(ref expr) => Ok(ControlFlow::Return(expr.evaluate(environment)?)),
|
||||
Stat::Print(ref expr) => {
|
||||
println!("{}", expr.evaluate(environment)?);
|
||||
Ok(ControlFlow::Next)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +115,7 @@ impl Machine {
|
|||
pub fn run(&mut self, statements: Vec<Stat>) -> Result<i64, String> {
|
||||
for statement in statements {
|
||||
if let ControlFlow::Return(val) = statement.execute(&mut self.environment)? {
|
||||
return val.value();
|
||||
return val.int();
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
|
|
|
@ -269,4 +269,34 @@ mod tests {
|
|||
};
|
||||
assert!(z == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_char() {
|
||||
let environment = run_file("test_programs/string_char.duck");
|
||||
let x = match environment.get("x") {
|
||||
Some(Value::String(value)) => value,
|
||||
_ => panic!("x should be Value::String"),
|
||||
};
|
||||
assert!(*x == "hallo ");
|
||||
let y = match environment.get("y") {
|
||||
Some(Value::String(value)) => value,
|
||||
_ => panic!("y should be Value::String"),
|
||||
};
|
||||
assert!(*y == "world");
|
||||
let z = match environment.get("z") {
|
||||
Some(Value::String(value)) => value,
|
||||
_ => panic!("z should be Value::String"),
|
||||
};
|
||||
assert!(*z == "hello world!");
|
||||
let a = match environment.get("a") {
|
||||
Some(Value::String(value)) => value,
|
||||
_ => panic!("a should be Value::String"),
|
||||
};
|
||||
assert!(*a == "!!!");
|
||||
let b = match environment.get("b") {
|
||||
Some(Value::Char(value)) => value,
|
||||
_ => panic!("b should be Value::Char"),
|
||||
};
|
||||
assert!(b == 'l');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use pest::iterators::Pair;
|
||||
use pest::pratt_parser::PrattParser;
|
||||
use pest::Parser;
|
||||
|
@ -21,6 +23,7 @@ lazy_static::lazy_static! {
|
|||
.op(Op::infix(op_add, Left) | Op::infix(op_sub, Left))
|
||||
.op(Op::infix(op_mul, Left) | Op::infix(op_div, Left) | Op::infix(op_mod, Left))
|
||||
.op(Op::prefix(op_neg) | Op::prefix(op_not))
|
||||
.op(Op::postfix(op_index))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -55,17 +58,25 @@ fn build_call(pair: Pair<Rule>) -> Result<Expr, String> {
|
|||
.into_iter()
|
||||
.map(|arg| build_expr(arg).unwrap())
|
||||
.collect();
|
||||
Ok(Expr::call(name, arg_exprs))
|
||||
Ok(Expr::Call(name, arg_exprs))
|
||||
}
|
||||
|
||||
fn build_expr(pair: Pair<Rule>) -> Result<Expr, String> {
|
||||
PRATT_PARSER
|
||||
.map_primary(|primary| match primary.as_rule() {
|
||||
Rule::term | Rule::expr => build_expr(primary),
|
||||
Rule::number => Ok(Expr::litteral(Value::number(primary.as_str())?)),
|
||||
Rule::boolean => Ok(Expr::litteral(Value::boolean(primary.as_str())?)),
|
||||
Rule::variable => Ok(Expr::variable(primary.as_str())),
|
||||
Rule::number => Ok(Expr::Litteral(Value::number(primary.as_str())?)),
|
||||
Rule::boolean => Ok(Expr::Litteral(Value::boolean(primary.as_str())?)),
|
||||
Rule::variable => Ok(Expr::Variable(primary.as_str().to_string())),
|
||||
Rule::call => build_call(primary),
|
||||
Rule::string => {
|
||||
let litteral = primary.as_str();
|
||||
let value = litteral[1..litteral.len() - 1].to_string();
|
||||
Ok(Expr::Litteral(Value::String(Rc::new(value))))
|
||||
}
|
||||
Rule::char => Ok(Expr::Litteral(Value::Char(
|
||||
primary.as_str().chars().nth(1).unwrap(),
|
||||
))),
|
||||
rule => Err(format!(
|
||||
"atom expected number, boolean or variable, found {:?}",
|
||||
rule
|
||||
|
@ -74,62 +85,62 @@ fn build_expr(pair: Pair<Rule>) -> Result<Expr, String> {
|
|||
.map_infix(|lhs, op, rhs| match (lhs, op.as_rule(), rhs) {
|
||||
(Err(err), _, _) | (_, _, Err(err)) => Err(err),
|
||||
(Ok(lhs), Rule::op_or, Ok(rhs)) => {
|
||||
Ok(Expr::binary_op(Box::new(lhs), Box::new(rhs), BinaryOp::Or))
|
||||
Ok(Expr::BinaryOp(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(Expr::BinaryOp(Box::new(lhs), Box::new(rhs), BinaryOp::And))
|
||||
}
|
||||
(Ok(lhs), Rule::op_lt, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_lt, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::LesserThan,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_gt, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_gt, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::GreaterThan,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_eq, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_eq, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::Equal,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_ge, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_ge, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::GreaterEqual,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_le, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_le, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::LesserEqual,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_ne, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_ne, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::NotEqual,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_add, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_add, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::Addition,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_sub, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_sub, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::Subtraction,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_mul, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_mul, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::Multiplication,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_div, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_div, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::Division,
|
||||
)),
|
||||
(Ok(lhs), Rule::op_mod, Ok(rhs)) => Ok(Expr::binary_op(
|
||||
(Ok(lhs), Rule::op_mod, Ok(rhs)) => Ok(Expr::BinaryOp(
|
||||
Box::new(lhs),
|
||||
Box::new(rhs),
|
||||
BinaryOp::Modulo,
|
||||
|
@ -141,28 +152,48 @@ fn build_expr(pair: Pair<Rule>) -> Result<Expr, String> {
|
|||
})
|
||||
.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::op_neg, Ok(rhs)) => Ok(Expr::UnaryOp(Box::new(rhs), UnaryOp::Minus)),
|
||||
(Rule::op_not, Ok(rhs)) => Ok(Expr::UnaryOp(Box::new(rhs), UnaryOp::Not)),
|
||||
(rule, _) => Err(format!(
|
||||
"unary operation expected operator, found {:?}",
|
||||
rule
|
||||
)),
|
||||
})
|
||||
.map_postfix(|lhs, op| match (lhs, op.as_rule()) {
|
||||
(Err(err), _) => Err(err),
|
||||
(Ok(lhs), Rule::op_index) => {
|
||||
let index_expr = build_expr(op.into_inner().next().unwrap())?;
|
||||
Ok(Expr::UnaryOp(
|
||||
Box::new(lhs),
|
||||
UnaryOp::Index(Box::new(index_expr)),
|
||||
))
|
||||
}
|
||||
(Ok(_), rule) => Err(format!(
|
||||
"indexing operation expected operator, found {:?}",
|
||||
rule
|
||||
)),
|
||||
})
|
||||
.parse(pair.into_inner())
|
||||
}
|
||||
|
||||
fn build_assign(pair: Pair<Rule>) -> Result<Stat, String> {
|
||||
let mut inner = pair.into_inner();
|
||||
let token_len = inner.len();
|
||||
let lhs = inner.next().unwrap().as_str();
|
||||
if token_len == 2 {
|
||||
let rhs = build_expr(inner.next().unwrap())?;
|
||||
return Ok(Stat::Assignment(lhs.to_string(), None, rhs));
|
||||
}
|
||||
let index = build_expr(inner.next().unwrap())?;
|
||||
let rhs = build_expr(inner.next().unwrap())?;
|
||||
Ok(Stat::assign(lhs, rhs))
|
||||
Ok(Stat::Assignment(lhs.to_string(), Some(index), rhs))
|
||||
}
|
||||
|
||||
fn build_while_loop(pair: Pair<Rule>) -> Result<Stat, String> {
|
||||
let mut inner = pair.into_inner();
|
||||
let cond = build_expr(inner.next().unwrap())?;
|
||||
let stmt = build_stats(inner.next().unwrap(), true)?;
|
||||
Ok(Stat::while_loop(cond, stmt))
|
||||
Ok(Stat::Loop(cond, stmt))
|
||||
}
|
||||
|
||||
fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Result<Stat, String> {
|
||||
|
@ -171,19 +202,19 @@ fn build_if_cond(pair: Pair<Rule>, in_loop: bool) -> Result<Stat, String> {
|
|||
let thenstmt = build_stats(inner.next().unwrap(), in_loop)?;
|
||||
match inner.next() {
|
||||
Some(stmt) => match stmt.as_rule() {
|
||||
Rule::stats => Ok(Stat::if_cond(
|
||||
Rule::stats => Ok(Stat::Condition(
|
||||
cond,
|
||||
thenstmt,
|
||||
Some(build_stats(stmt, in_loop)?),
|
||||
)),
|
||||
Rule::stat_elif => Ok(Stat::if_cond(
|
||||
Rule::stat_elif => Ok(Stat::Condition(
|
||||
cond,
|
||||
thenstmt,
|
||||
Some(vec![build_stat(stmt, in_loop)?]),
|
||||
)),
|
||||
rule => Err(format!("conditional statement expected, found {:?}", rule)),
|
||||
},
|
||||
None => Ok(Stat::if_cond(cond, thenstmt, None)),
|
||||
None => Ok(Stat::Condition(cond, thenstmt, None)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,12 +227,17 @@ fn build_define(pair: Pair<Rule>) -> Result<Stat, String> {
|
|||
.map(|arg| arg.as_str().to_string())
|
||||
.collect();
|
||||
let stmt = build_stats(inner.next().unwrap(), false)?;
|
||||
Ok(Stat::define(name, arg_names, stmt))
|
||||
Ok(Stat::Define(name, Rc::new(arg_names), Rc::new(stmt)))
|
||||
}
|
||||
|
||||
fn build_return(pair: Pair<Rule>) -> Result<Stat, String> {
|
||||
let expr = build_expr(pair.into_inner().next().unwrap())?;
|
||||
Ok(Stat::return_keyword(expr))
|
||||
Ok(Stat::Return(expr))
|
||||
}
|
||||
|
||||
fn build_print(pair: Pair<Rule>) -> Result<Stat, String> {
|
||||
let expr = build_expr(pair.into_inner().next().unwrap())?;
|
||||
Ok(Stat::Print(expr))
|
||||
}
|
||||
|
||||
fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Result<Stat, String> {
|
||||
|
@ -211,18 +247,19 @@ fn build_stat(pair: Pair<Rule>, in_loop: bool) -> Result<Stat, String> {
|
|||
Rule::stat_while => build_while_loop(pair),
|
||||
Rule::stat_break => {
|
||||
if in_loop {
|
||||
return Ok(Stat::break_keyword());
|
||||
return Ok(Stat::Break);
|
||||
}
|
||||
Err(parsing_error(pair, "break keyword while not in loop"))
|
||||
}
|
||||
Rule::stat_continue => {
|
||||
if in_loop {
|
||||
return Ok(Stat::continue_keyword());
|
||||
return Ok(Stat::Continue);
|
||||
}
|
||||
Err(parsing_error(pair, "continue keyword while not in loop"))
|
||||
}
|
||||
Rule::stat_define => build_define(pair),
|
||||
Rule::stat_return => build_return(pair),
|
||||
Rule::stat_print => build_print(pair),
|
||||
rule => Err(format!("Statement expected, found {:?}", rule)),
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +276,52 @@ fn build_stats(pair: Pair<Rule>, in_loop: bool) -> Result<Vec<Stat>, String> {
|
|||
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)),
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"{}",
|
||||
err.renamed_rules(|rule| {
|
||||
match *rule {
|
||||
Rule::program => "program".to_owned(),
|
||||
Rule::stat => "statement".to_owned(),
|
||||
Rule::stat_assign => "assignment".to_owned(),
|
||||
Rule::stat_if => "if statement".to_owned(),
|
||||
Rule::stat_elif => "elif statement".to_owned(),
|
||||
Rule::stat_while => "while statement".to_owned(),
|
||||
Rule::stat_break => "break statement".to_owned(),
|
||||
Rule::stat_continue => "continue statement".to_owned(),
|
||||
Rule::stat_define => "define statement".to_owned(),
|
||||
Rule::stat_return => "return statement".to_owned(),
|
||||
Rule::stat_print => "print statement".to_owned(),
|
||||
Rule::expr => "expression".to_owned(),
|
||||
Rule::term => "term".to_owned(),
|
||||
Rule::call => "function call".to_owned(),
|
||||
Rule::variable => "variable".to_owned(),
|
||||
Rule::number => "number".to_owned(),
|
||||
Rule::boolean => "boolean".to_owned(),
|
||||
Rule::op_or => "||".to_owned(),
|
||||
Rule::op_and => "&&".to_owned(),
|
||||
Rule::op_lt => "<".to_owned(),
|
||||
Rule::op_gt => ">".to_owned(),
|
||||
Rule::op_eq => "==".to_owned(),
|
||||
Rule::op_ge => ">=".to_owned(),
|
||||
Rule::op_le => "<=".to_owned(),
|
||||
Rule::op_ne => "!=".to_owned(),
|
||||
Rule::op_add => "+".to_owned(),
|
||||
Rule::op_sub => "-".to_owned(),
|
||||
Rule::op_mul => "*".to_owned(),
|
||||
Rule::op_div => "/".to_owned(),
|
||||
Rule::op_mod => "%".to_owned(),
|
||||
Rule::op_neg => "-".to_owned(),
|
||||
Rule::op_not => "!".to_owned(),
|
||||
Rule::op_index => "[]".to_owned(),
|
||||
Rule::WHITESPACE => "whitespace".to_owned(),
|
||||
Rule::COMMENT => "comment".to_owned(),
|
||||
Rule::EOI => "end of input".to_owned(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
};
|
||||
build_stats(pair, false)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ block_comment = @{ "/*" ~ ((!("*/") ~ ANY) | block_comment)* ~ "*/" }
|
|||
|
||||
args_call = { (expr ~ ("," ~ expr)*)? }
|
||||
call = { variable ~ "(" ~ args_call ~ ")" }
|
||||
string = @{ "\"" ~ (!("\"") ~ ANY)* ~ "\"" }
|
||||
char = @{ "'" ~ (!("'") ~ ANY) ~ "'" }
|
||||
variable = @ { (alpha) ~ (alpha | digit)* }
|
||||
number = @ { (digit)+ }
|
||||
boolean = @ { "true" | "false" }
|
||||
|
@ -27,6 +29,7 @@ op_or = { "||" }
|
|||
op_and = { "&&" }
|
||||
op_not = { "!" }
|
||||
op_neg = { "-" }
|
||||
op_index = { "[" ~ expr ~ "]" }
|
||||
|
||||
op_binary = _{
|
||||
op_add |
|
||||
|
@ -48,24 +51,24 @@ op_unary = _{
|
|||
op_not
|
||||
}
|
||||
|
||||
value = _{ call | boolean | number | variable | "(" ~ expr ~ ")" }
|
||||
term = { op_unary* ~ value }
|
||||
value = _{ call | boolean | number | variable | string | char | "(" ~ expr ~ ")" }
|
||||
term = { op_unary* ~ value ~ op_index?}
|
||||
expr = { term ~ (op_binary ~ term)* }
|
||||
args_define = { (variable ~ ("," ~ variable)*)? }
|
||||
|
||||
|
||||
stat_print = { "print" ~ expr ~ ";" }
|
||||
stat_return = { "return" ~ expr ~ ";" }
|
||||
stat_define = { "fn" ~ variable ~ "(" ~ args_define ~ ")" ~ "{" ~ stats ~ "}" }
|
||||
stat_continue = { "continue" ~ ";" }
|
||||
stat_break = { "break" ~ ";" }
|
||||
stat_assign = { variable ~ "=" ~ expr ~ ";" }
|
||||
stat_assign = { variable ~ op_index? ~ "=" ~ expr ~ ";" }
|
||||
stat_while = { "while" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" }
|
||||
stat_elif = { ("else if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~ stats ~ "}" ) |
|
||||
("else if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
|
||||
stat_if = { ("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ "else" ~ "{" ~ stats ~ "}" ) |
|
||||
("if" ~ "(" ~ expr ~ ")" ~ "{" ~ stats ~ "}" ~ (stat_elif)?) }
|
||||
|
||||
stat = _{ ( stat_if | stat_while | stat_assign | stat_break | stat_continue | stat_define | stat_return ) }
|
||||
stat = _{ ( stat_if | stat_while | stat_assign | stat_break | stat_continue | stat_define | stat_return | stat_print ) }
|
||||
|
||||
stats = { (stat)* }
|
||||
|
||||
|
|
|
@ -56,4 +56,9 @@ mod tests {
|
|||
fn test_functions() {
|
||||
parse_file("test_programs/functions.duck");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_char() {
|
||||
parse_file("test_programs/string_char.duck");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,11 @@ impl Display for BinaryOp {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum UnaryOp {
|
||||
Minus,
|
||||
Not,
|
||||
Index(Box<Expr>),
|
||||
}
|
||||
|
||||
impl Display for UnaryOp {
|
||||
|
@ -52,6 +53,7 @@ impl Display for UnaryOp {
|
|||
match self {
|
||||
UnaryOp::Minus => write!(f, "-"),
|
||||
UnaryOp::Not => write!(f, "!"),
|
||||
UnaryOp::Index(expr) => write!(f, "[{}]", expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +64,8 @@ pub enum Value {
|
|||
Number(i64),
|
||||
Boolean(bool),
|
||||
Function(Rc<Vec<String>>, Rc<Vec<Stat>>),
|
||||
String(Rc<String>),
|
||||
Char(char),
|
||||
None,
|
||||
}
|
||||
|
||||
|
@ -78,21 +82,6 @@ impl Value {
|
|||
Err(_) => Err(format!("{} is not a boolean", raw)),
|
||||
}
|
||||
}
|
||||
// TODO move to compute.rs
|
||||
pub fn value(&self) -> Result<i64, String> {
|
||||
match self {
|
||||
Value::Number(n) => Ok(*n),
|
||||
_ => Err(format!("{} is not a number", self)),
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
|
@ -101,6 +90,8 @@ impl Display for Value {
|
|||
Value::Number(n) => write!(f, "{}", n),
|
||||
Value::Boolean(b) => write!(f, "{}", b),
|
||||
Value::Function(args, _) => write!(f, "fn({})", args.join(", ")),
|
||||
Value::String(s) => write!(f, "{}", s),
|
||||
Value::Char(c) => write!(f, "{}", c),
|
||||
Value::None => write!(f, "None"),
|
||||
}
|
||||
}
|
||||
|
@ -116,31 +107,16 @@ pub enum Expr {
|
|||
Call(String, Vec<Expr>),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn litteral(value: Value) -> Expr {
|
||||
Expr::Litteral(value)
|
||||
}
|
||||
pub fn variable(name: &str) -> Expr {
|
||||
Expr::Variable(name.to_string())
|
||||
}
|
||||
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) -> 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) -> 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::UnaryOp(term, op) => match op {
|
||||
UnaryOp::Index(index) => write!(f, "{}{}", term, index),
|
||||
_ => write!(f, "{}{}", op, term),
|
||||
},
|
||||
Expr::Call(name, args) => {
|
||||
write!(f, "{}(", name)?;
|
||||
let expressions = args
|
||||
|
@ -158,35 +134,12 @@ impl Display for Expr {
|
|||
pub enum Stat {
|
||||
Condition(Expr, Vec<Stat>, Option<Vec<Stat>>),
|
||||
Loop(Expr, Vec<Stat>),
|
||||
Assignment(String, Expr),
|
||||
Assignment(String, Option<Expr>, Expr),
|
||||
Break,
|
||||
Continue,
|
||||
Define(String, Rc<Vec<String>>, Rc<Vec<Stat>>),
|
||||
Return(Expr),
|
||||
}
|
||||
|
||||
impl Stat {
|
||||
pub fn assign(name: &str, expr: Expr) -> Stat {
|
||||
Stat::Assignment(name.to_string(), expr)
|
||||
}
|
||||
pub fn break_keyword() -> Stat {
|
||||
Stat::Break
|
||||
}
|
||||
pub fn continue_keyword() -> Stat {
|
||||
Stat::Continue
|
||||
}
|
||||
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)
|
||||
}
|
||||
Print(Expr),
|
||||
}
|
||||
|
||||
impl Display for Stat {
|
||||
|
@ -214,7 +167,13 @@ impl Display for Stat {
|
|||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
Stat::Assignment(name, expr) => write!(f, "{} = {};", name, expr),
|
||||
Stat::Assignment(name, index, expr) => {
|
||||
if let Some(index) = index {
|
||||
write!(f, "{}[{}] = {};", name, index, expr)
|
||||
} else {
|
||||
write!(f, "{} = {};", name, expr)
|
||||
}
|
||||
}
|
||||
Stat::Break => write!(f, "break;"),
|
||||
Stat::Continue => write!(f, "continue;"),
|
||||
Stat::Define(name, args, body) => {
|
||||
|
@ -227,6 +186,7 @@ impl Display for Stat {
|
|||
write!(f, "}}")
|
||||
}
|
||||
Stat::Return(expr) => write!(f, "return {};", expr),
|
||||
Stat::Print(expr) => write!(f, "print {};", expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
x = "hello ";
|
||||
y = "world";
|
||||
a = '!';
|
||||
z = x + y + a;
|
||||
print z ;
|
||||
a = a * 3;
|
||||
x[1] = 'a';
|
||||
b = x[2];
|
Loading…
Reference in New Issue