Skip to content

Commit

Permalink
Add compound arithmetic assignment operators
Browse files Browse the repository at this point in the history
Close #5.
  • Loading branch information
polybuildr committed May 19, 2017
1 parent 7699f05 commit 688935a
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub struct IfThenStmt {
#[derive(Debug, Clone)]
pub enum Stmt {
Assign(LhsExprNode, ExprNode),
AssignOp(LhsExprNode, BinOp, ExprNode),
VarDecl(Variable, ExprNode),
Expr(ExprNode),
Block(Vec<StmtNode>),
Expand Down
39 changes: 39 additions & 0 deletions src/ast_walk_interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ impl AstWalkInterpreter {
match s.data {
Stmt::VarDecl(ref variable, ref expr) => self.eval_stmt_var_decl(variable, expr),
Stmt::Assign(ref lhs_expr, ref expr) => self.eval_stmt_assign(lhs_expr, expr),
Stmt::AssignOp(ref lhs_expr, ref op, ref expr) => {
self.eval_stmt_assign_with_op(lhs_expr, op, expr, s)
}
Stmt::Block(ref statements) => self.eval_stmt_block(statements),
Stmt::Expr(ref expr) => {
let val = self.eval_expr(expr)?;
Expand Down Expand Up @@ -149,6 +152,42 @@ impl AstWalkInterpreter {
Ok(StmtResult::None)
}

fn eval_stmt_assign_with_op(&mut self,
lhs_expr: &LhsExprNode,
op: &BinOp,
expr: &ExprNode,
stmt: &StmtNode)
-> Result<StmtResult, RuntimeErrorWithPosition> {
let val = self.eval_expr_as_value(expr)?;
match lhs_expr.data {
LhsExpr::Identifier(ref id) => {
let prev_expr_val = match self.env.borrow_mut().get_value(id) {
Some(v) => v,
None => {
return Err((RuntimeError::ReferenceError(id.to_owned()), lhs_expr.pos));
}
};
let retval = match *op {
BinOp::Add => operations::add(prev_expr_val, val),
BinOp::Sub => operations::subtract(prev_expr_val, val),
BinOp::Mul => operations::multiply(prev_expr_val, val),
BinOp::Div => operations::divide(prev_expr_val, val),
BinOp::Mod => operations::modulo(prev_expr_val, val),
BinOp::Lt | BinOp::Lte | BinOp::Gt | BinOp::Gte | BinOp::Eq => unreachable!(),
};
let new_val = match retval {
Ok(val) => val,
Err(e) => {
return Err((e, stmt.pos));
}
};
// id must exist, because it was checked above
self.env.borrow_mut().set(id, new_val);
}
};
Ok(StmtResult::None)
}

fn eval_stmt_block(&mut self,
statements: &[StmtNode])
-> Result<StmtResult, RuntimeErrorWithPosition> {
Expand Down
10 changes: 10 additions & 0 deletions src/grammar.rustpeg
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ assignment_statement -> Stmt
= lpos:#position i:identifier rpos:#position __ EQUALS __ e:expr_node __ TERMINATOR {
Stmt::Assign(LhsExprNode { pos: (lpos, rpos), data: LhsExpr::Identifier(i) }, e)
}
/ lpos:#position i:identifier rpos:#position __ op:assign_with_op __ e:expr_node __ TERMINATOR {
Stmt::AssignOp(LhsExprNode { pos: (lpos, rpos), data: LhsExpr::Identifier(i) }, op, e)
}

assign_with_op -> BinOp
= OP_PLUS __ EQUALS { BinOp::Add }
/ OP_MINUS __ EQUALS { BinOp::Sub }
/ OP_ASTERISK __ EQUALS { BinOp::Mul }
/ OP_SLASH __ EQUALS { BinOp::Div }
/ OP_MOD __ EQUALS { BinOp::Mod }

variable_declaration -> Stmt
= b:binding_type __ i:identifier __ EQUALS __ e:expr_node __ TERMINATOR {
Expand Down
45 changes: 45 additions & 0 deletions src/typechecker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ impl TypeChecker {
self.check_statement_assignment(lhs_expr, expr);
StmtEffect::None
}
Stmt::AssignOp(ref lhs_expr, ref op, ref expr) => {
self.check_statement_assignment_with_op(lhs_expr, op, expr, s);
StmtEffect::None
}
Stmt::Block(ref statements) => {
let current_env = self.env.clone();
self.env = TypeEnvironment::create_child(current_env.clone());
Expand Down Expand Up @@ -392,6 +396,47 @@ impl TypeChecker {
};
}

fn check_statement_assignment_with_op(&mut self,
lhs_expr: &LhsExprNode,
op: &BinOp,
expr: &ExprNode,
stmt: &StmtNode) {
let checked_type = self.check_expr_as_value(expr);
match lhs_expr.data {
LhsExpr::Identifier(ref id) => {
let prev_type = match self.env.borrow_mut().get_type(id) {
Some(t) => t,
None => {
self.issues
.push((RuntimeError::ReferenceError(id.to_owned()).into(),
lhs_expr.pos));
Type::Any
}
};
let retval = match *op {
BinOp::Add => check_add_for_types(&prev_type, &checked_type),
ref op @ BinOp::Sub |
ref op @ BinOp::Mul |
ref op @ BinOp::Div |
ref op @ BinOp::Mod => {
check_binary_arithmetic_for_types(op.clone(), &prev_type, &checked_type)
}
_ => unreachable!(),
};
let new_type = match retval {
Ok(t) => t,
Err(issue) => {
self.issues.push((issue, stmt.pos));
Type::Any
}
};

// if id does not exist, then error was reported above
self.env.borrow_mut().set(id, new_type);
}
};
}

fn check_statement_if_then_else(&mut self,
statement: &StmtNode,
if_then_stmt: &IfThenStmt)
Expand Down
13 changes: 13 additions & 0 deletions tests/run-pass/compound-assign.bl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
var s = "Hello";
s += ", world!";
assert_eq(s, "Hello, world!");

var x = 5;
x += 5;
assert_eq(x, 10);
x -= 5;
assert_eq(x, 5);
x *= 2;
assert_eq(x, 10);
x /= 2;
assert_eq(x, 5);
2 changes: 2 additions & 0 deletions tests/typecheck-fail/compound-assign-binary-error.bl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
var x = true;
x += 1;
1 change: 1 addition & 0 deletions tests/typecheck-fail/compound-assign-binary-error.err
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[(RuntimeError(BinaryTypeError(Add, Bool, Number)), (14, 21))]
8 changes: 8 additions & 0 deletions tests/typecheck-pass/compound-assign.bl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var s = "Hello";
s += ", world!";

var x = 5;
x += 5;
x -= 5;
x *= 2;
x /= 2;

0 comments on commit 688935a

Please sign in to comment.