Skip to content

Commit

Permalink
Implement support for break statement
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Nov 18, 2020
1 parent 05b6f3d commit c9e7352
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 12 deletions.
13 changes: 12 additions & 1 deletion compiler/src/yul/mappers/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ fn func_stmt(
fe::FuncStmt::Assert { .. } => assert(context, stmt),
fe::FuncStmt::Expr { .. } => expr(context, stmt),
fe::FuncStmt::Pass => unimplemented!(),
fe::FuncStmt::Break => unimplemented!(),
fe::FuncStmt::Break => break_statement(context, stmt),
fe::FuncStmt::Continue => unimplemented!(),
fe::FuncStmt::Revert => revert(stmt),
}
Expand Down Expand Up @@ -199,6 +199,17 @@ fn assert(context: &Context, stmt: &Spanned<fe::FuncStmt>) -> Result<yul::Statem
unreachable!()
}

fn break_statement(
_context: &Context,
stmt: &Spanned<fe::FuncStmt>,
) -> Result<yul::Statement, CompileError> {
if let fe::FuncStmt::Break {} = &stmt.node {
return Ok(statement! { break });
}

unreachable!()
}

fn func_return(
context: &Context,
stmt: &Spanned<fe::FuncStmt>,
Expand Down
5 changes: 5 additions & 0 deletions compiler/tests/compile_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ use std::fs;
#[rstest(
fixture_file,
error,
case("break_without_loop.fe", "[Str(\"semantic error: BreakWithoutLoop\")]"),
case(
"break_without_loop_2.fe",
"[Str(\"semantic error: BreakWithoutLoop\")]"
),
case(
"not_in_scope.fe",
"[Str(\"semantic error: UndefinedValue { value: \\\"y\\\" }\")]"
Expand Down
2 changes: 2 additions & 0 deletions compiler/tests/evm_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ fn test_assert() {

#[rstest(fixture_file, input, expected,
case("while_loop.fe", vec![], Some(u256_token(3))),
case("while_loop_with_break.fe", vec![], Some(u256_token(1))),
case("while_loop_with_break_2.fe", vec![], Some(u256_token(1))),
case("if_statement.fe", vec![6], Some(u256_token(1))),
case("if_statement.fe", vec![4], Some(u256_token(0))),
case("if_statement_2.fe", vec![6], Some(u256_token(1))),
Expand Down
4 changes: 4 additions & 0 deletions compiler/tests/fixtures/compile_errors/break_without_loop.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
contract Foo:

pub def bar():
break
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
contract Foo:

pub def bar():
if true:
break
7 changes: 7 additions & 0 deletions compiler/tests/fixtures/while_loop_with_break.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
contract Foo:
pub def bar() -> u256:
val: u256 = 0
while val < 2:
val = val + 1
break
return val
8 changes: 8 additions & 0 deletions compiler/tests/fixtures/while_loop_with_break_2.fe
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
contract Foo:
pub def bar() -> u256:
val: u256 = 0
while val < 2:
val = val + 1
if val == 1:
break
return val
1 change: 1 addition & 0 deletions semantics/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// Errors for things that may arise in a valid Fe AST.
#[derive(Debug, PartialEq)]
pub enum SemanticError {
BreakWithoutLoop,
MissingReturn,
NotAnExpression,
NotSubscriptable,
Expand Down
118 changes: 111 additions & 7 deletions semantics/src/namespace/scopes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub struct BlockScope {
pub span: Span,
pub parent: BlockScopeParent,
pub defs: HashMap<String, BlockDef>,
pub typ: BlockScopeType,
}

#[allow(dead_code)]
Expand All @@ -72,6 +73,13 @@ pub enum BlockScopeParent {
Block(Shared<BlockScope>),
}

#[derive(Clone, Debug, PartialEq)]
pub enum BlockScopeType {
Function,
IfElse,
Loop,
}

impl Scope {
pub fn module_scope(&self) -> Shared<ModuleScope> {
match self {
Expand Down Expand Up @@ -144,18 +152,27 @@ impl ContractScope {

impl BlockScope {
pub fn from_contract_scope(span: Span, parent: Shared<ContractScope>) -> Shared<Self> {
BlockScope::new(span, BlockScopeParent::Contract(parent))
BlockScope::new(
span,
BlockScopeType::Function,
BlockScopeParent::Contract(parent),
)
}

pub fn from_block_scope(span: Span, parent: Shared<BlockScope>) -> Shared<Self> {
BlockScope::new(span, BlockScopeParent::Block(parent))
pub fn from_block_scope(
span: Span,
typ: BlockScopeType,
parent: Shared<BlockScope>,
) -> Shared<Self> {
BlockScope::new(span, typ, BlockScopeParent::Block(parent))
}

pub fn new(span: Span, parent: BlockScopeParent) -> Shared<Self> {
pub fn new(span: Span, typ: BlockScopeType, parent: BlockScopeParent) -> Shared<Self> {
Rc::new(RefCell::new(BlockScope {
span,
parent,
defs: HashMap::new(),
typ,
}))
}

Expand Down Expand Up @@ -216,13 +233,26 @@ impl BlockScope {
pub fn add_base(&mut self, name: String, base: Base) {
self.defs.insert(name, BlockDef::Base(base));
}

/// Return true if the scope or any of its parents is of the given type
pub fn inherits_type(&self, typ: BlockScopeType) -> bool {
if self.typ != typ {
if let BlockScopeParent::Block(scope) = &self.parent {
return scope.borrow().inherits_type(typ);
} else {
return false;
}
}
true
}
}

#[cfg(test)]
mod tests {
use crate::namespace::scopes::{
BlockDef,
BlockScope,
BlockScopeType,
ContractScope,
ModuleScope,
};
Expand All @@ -245,7 +275,11 @@ mod tests {
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
let block_scope_2 = BlockScope::from_block_scope(
Span::new(0, 0),
BlockScopeType::IfElse,
block_scope_1.clone(),
);
assert_eq!(block_scope_1, block_scope_2.borrow().function_scope());
assert_eq!(contract_scope, block_scope_2.borrow().contract_scope());
}
Expand All @@ -271,7 +305,11 @@ mod tests {
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
let block_scope_2 = BlockScope::from_block_scope(
Span::new(0, 0),
BlockScopeType::IfElse,
block_scope_1.clone(),
);
block_scope_1
.borrow_mut()
.add_base("some_thing".to_string(), Base::Bool);
Expand All @@ -287,10 +325,76 @@ mod tests {
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
let block_scope_2 = BlockScope::from_block_scope(Span::new(0, 0), block_scope_1.clone());
let block_scope_2 = BlockScope::from_block_scope(
Span::new(0, 0),
BlockScopeType::IfElse,
block_scope_1.clone(),
);
block_scope_2
.borrow_mut()
.add_base("some_thing".to_string(), Base::Bool);
assert_eq!(None, block_scope_1.borrow().def("some_thing".to_string()));
}

#[test]
fn test_inherits_type() {
let module_scope = ModuleScope::new();
let contract_scope = ContractScope::new(module_scope);
let block_scope_1 =
BlockScope::from_contract_scope(Span::new(0, 0), contract_scope.clone());
assert_eq!(
true,
block_scope_1
.borrow()
.inherits_type(BlockScopeType::Function)
);
assert_eq!(
false,
block_scope_1.borrow().inherits_type(BlockScopeType::IfElse)
);
assert_eq!(
false,
block_scope_1.borrow().inherits_type(BlockScopeType::Loop)
);

let block_scope_2 = BlockScope::from_block_scope(
Span::new(0, 0),
BlockScopeType::IfElse,
block_scope_1.clone(),
);
assert_eq!(
true,
block_scope_2
.borrow()
.inherits_type(BlockScopeType::Function)
);
assert_eq!(
true,
block_scope_2.borrow().inherits_type(BlockScopeType::IfElse)
);
assert_eq!(
false,
block_scope_2.borrow().inherits_type(BlockScopeType::Loop)
);

let block_scope_3 = BlockScope::from_block_scope(
Span::new(0, 0),
BlockScopeType::Loop,
block_scope_2.clone(),
);
assert_eq!(
true,
block_scope_3
.borrow()
.inherits_type(BlockScopeType::Function)
);
assert_eq!(
true,
block_scope_3.borrow().inherits_type(BlockScopeType::IfElse)
);
assert_eq!(
true,
block_scope_3.borrow().inherits_type(BlockScopeType::Loop)
);
}
}
27 changes: 23 additions & 4 deletions semantics/src/traversal/functions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::errors::SemanticError;
use crate::namespace::scopes::{
BlockScope,
BlockScopeType,
ContractDef,
ContractScope,
Scope,
Expand Down Expand Up @@ -163,7 +164,7 @@ fn func_stmt(
fe::FuncStmt::Assert { .. } => assert(scope, context, stmt),
fe::FuncStmt::Expr { .. } => expr(scope, context, stmt),
fe::FuncStmt::Pass => unimplemented!(),
fe::FuncStmt::Break => unimplemented!(),
fe::FuncStmt::Break => break_statement(scope, context, stmt),
fe::FuncStmt::Continue => unimplemented!(),
fe::FuncStmt::Revert => Ok(()),
}
Expand All @@ -182,6 +183,21 @@ fn verify_is_boolean(
Err(SemanticError::TypeError)
}

fn break_statement(
scope: Shared<BlockScope>,
_context: Shared<Context>,
stmt: &Spanned<fe::FuncStmt>,
) -> Result<(), SemanticError> {
if let fe::FuncStmt::Break {} = &stmt.node {
if scope.borrow().inherits_type(BlockScopeType::Loop) {
return Ok(());
} else {
return Err(SemanticError::BreakWithoutLoop);
}
}
unreachable!()
}

fn if_statement(
scope: Shared<BlockScope>,
context: Shared<Context>,
Expand All @@ -193,9 +209,11 @@ fn if_statement(
body,
or_else,
} => {
let body_scope = BlockScope::from_block_scope(stmt.span, scope.clone());
let body_scope =
BlockScope::from_block_scope(stmt.span, BlockScopeType::IfElse, scope.clone());
traverse_statements(body_scope, context.clone(), body)?;
let or_else_scope = BlockScope::from_block_scope(stmt.span, scope.clone());
let or_else_scope =
BlockScope::from_block_scope(stmt.span, BlockScopeType::IfElse, scope.clone());
traverse_statements(or_else_scope, context.clone(), or_else)?;
verify_is_boolean(scope, context, test)
}
Expand All @@ -217,7 +235,8 @@ fn while_loop(
if !or_else.is_empty() {
unimplemented!();
}
let body_scope = BlockScope::from_block_scope(stmt.span, scope.clone());
let body_scope =
BlockScope::from_block_scope(stmt.span, BlockScopeType::Loop, scope.clone());
traverse_statements(body_scope, context.clone(), body)?;
verify_is_boolean(scope, context, test)
}
Expand Down

0 comments on commit c9e7352

Please sign in to comment.