Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions framec/src/frame_c/visitors/rust_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3039,10 +3039,9 @@ impl AstVisitor for RustVisitor {
) -> AstVisitorReturnType {
self.add_code(&format!(
"self.{}",
interface_method_call_expr_node.identifier.name.lexeme
self.format_value_name(&interface_method_call_expr_node.identifier.name.lexeme)
));
interface_method_call_expr_node.call_expr_list.accept(self);
// self.add_code(&format!(""));
// TODO: review this return as I think it is a nop.
AstVisitorReturnType::InterfaceMethodCallExpressionNode {}
}
Expand All @@ -3056,13 +3055,11 @@ impl AstVisitor for RustVisitor {
) -> AstVisitorReturnType {
output.push_str(&format!(
"self.{}",
interface_method_call_expr_node.identifier.name.lexeme
self.format_value_name(&interface_method_call_expr_node.identifier.name.lexeme)
));
interface_method_call_expr_node
.call_expr_list
.accept_to_string(self, output);
// self.add_code(&format!(""));

// TODO: review this return as I think it is a nop.
AstVisitorReturnType::InterfaceMethodCallExpressionNode {}
}
Expand Down Expand Up @@ -3761,11 +3758,48 @@ impl AstVisitor for RustVisitor {

//* --------------------------------------------------------------------- *//

// NOTE: Interface method calls must be treated specially since they may transition.
//
// The current approach is conservative, essentially assuming that an interface method call
// always transitions. This assumption imposes the following restrictions:
//
// * Interface method calls cannot occur in a chain (they must be a standalone call).
// * Interface method calls terminate the execution of their handler (like transitions).
//
// It would be possible to lift these restrictions and track the execution of a handler more
// precisely, but this would require embedding some logic in the generated code and would make
// handlers harder to reason about. The conservative approach has the advantage of both
// simplifying the implementation and reasoning about Frame programs.
fn visit_call_chain_literal_statement_node(
&mut self,
method_call_chain_literal_stmt_node: &CallChainLiteralStmtNode,
) -> AstVisitorReturnType {
self.newline();

// special case for interface method calls
let call_chain = &method_call_chain_literal_stmt_node
.call_chain_literal_expr_node
.call_chain;
if call_chain.len() == 1 {
if let CallChainLiteralNodeType::InterfaceMethodCallT {
interface_method_call_expr_node,
} = &call_chain[0]
{
self.this_branch_transitioned = true;
self.add_code(&format!(
"drop({});",
self.config.this_state_context_var_name
));
self.newline();
interface_method_call_expr_node.accept(self);
self.add_code(";");
self.newline();
self.add_code("return;");
return AstVisitorReturnType::CallChainLiteralStmtNode {};
}
}

// standard case
method_call_chain_literal_stmt_node
.call_chain_literal_expr_node
.accept(self);
Expand All @@ -3780,7 +3814,6 @@ impl AstVisitor for RustVisitor {
method_call_chain_expression_node: &CallChainLiteralExprNode,
) -> AstVisitorReturnType {
// TODO: maybe put this in an AST node

let mut separator = "";

for node in &method_call_chain_expression_node.call_chain {
Expand All @@ -3792,10 +3825,10 @@ impl AstVisitor for RustVisitor {
CallChainLiteralNodeType::CallT { call } => {
call.accept(self);
}
CallChainLiteralNodeType::InterfaceMethodCallT {
interface_method_call_expr_node,
} => {
interface_method_call_expr_node.accept(self);
CallChainLiteralNodeType::InterfaceMethodCallT { .. } => {
self.errors.push(String::from(
"Error: Interface method calls may not appear in call chains.",
));
}
CallChainLiteralNodeType::ActionCallT {
action_call_expr_node,
Expand Down Expand Up @@ -3832,10 +3865,10 @@ impl AstVisitor for RustVisitor {
CallChainLiteralNodeType::CallT { call } => {
call.accept_to_string(self, output);
}
CallChainLiteralNodeType::InterfaceMethodCallT {
interface_method_call_expr_node,
} => {
interface_method_call_expr_node.accept_to_string(self, output);
CallChainLiteralNodeType::InterfaceMethodCallT { .. } => {
self.errors.push(String::from(
"Error: Interface method calls may not appear in call chains.",
));
}
CallChainLiteralNodeType::ActionCallT {
action_call_expr_node,
Expand Down
99 changes: 99 additions & 0 deletions framec_tests/src/handler_calls.frm
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#HandlerCalls
-interface-
NonRec
SelfRec
MutRec
Call [event:String arg:i32]
Foo [arg:i32]
Bar [arg:i32]

-machine-
$Init
|NonRec| -> $NonRecursive ^
|SelfRec| -> $SelfRecursive ^
|MutRec| -> $MutuallyRecursive ^

$NonRecursive
var counter:i32 = 0

|Foo| [arg:i32]
log("Foo" arg)
counter = counter + arg
Bar(arg*2)
--- the front-end should report the next line as a static error
log("Unreachable" 0)
^

|Bar| [arg:i32]
log("Bar" arg)
counter = counter + arg
-> $Final(counter) ^

|Call| [event:String arg:i32]
event ?~
/Foo/ Foo(arg) :>
/Bar/ Bar(arg)
: Call("Foo" 1000)
:: ^

$SelfRecursive
var counter:i32 = 0

|Foo| [arg:i32]
log("Foo" arg)
counter = counter + arg
counter < 100 ?
Foo(arg*2)
:
-> $Final(counter)
:: ^

|Bar| [arg:i32]
log("Bar" arg)
counter = counter + arg
-> $Final(counter) ^

|Call| [event:String arg:i32]
event ?~
/Foo/ Foo(arg) :>
/Bar/ Bar(arg)
: :: ^

$MutuallyRecursive
var counter:i32 = 0

|Foo| [arg:i32]
log("Foo" arg)
counter = counter + arg
counter > 100 ?
-> $Final(counter)
:
Bar(arg*2)
:: ^

|Bar| [arg:i32]
log("Bar" arg)
counter = counter + arg
arg ?#
/4/ Foo(arg) :>
/8/ Foo(arg*2)
: Foo(arg*3)
:: ^

|Call| [event:String arg:i32]
event ?~
/Foo/ Foo(arg) :>
/Bar/ Bar(arg)
: :: ^

$Final [counter:i32]
|>|
log("Final" counter)
-> $Init ^

-actions-
log [from:String val:i32]

-domain-
var tape:Log = `vec![]`
##
90 changes: 90 additions & 0 deletions framec_tests/src/handler_calls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! Test directly invoking event handlers from within other event handlers.
//! Since event handlers may transition, we conservatively treat such calls
//! as terminating statements for the current handler.

type Log = Vec<String>;
include!(concat!(env!("OUT_DIR"), "/", "handler_calls.rs"));

impl<'a> HandlerCalls<'a> {
pub fn log(&mut self, from: String, val: i32) {
self.tape.push(format!("{}({})", from, val));
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
/// Test that a handler call terminates the current handler.
fn calls_terminate_handler() {
let mut sm = HandlerCalls::new();
sm.non_rec();
sm.foo(10);
assert!(!sm.tape.iter().any(|e| e == "Unreachable(0)"));
}

#[test]
/// Test non-recursive handler calls.
fn non_recursive() {
let mut sm = HandlerCalls::new();
sm.non_rec();
sm.foo(10);
assert_eq!(sm.tape, vec!["Foo(10)", "Bar(20)", "Final(30)"]);
}

#[test]
/// Test self-recursive handler calls. Also tests calls in the then-branch
/// of a conditional.
fn self_recursive() {
let mut sm = HandlerCalls::new();
sm.self_rec();
sm.foo(10);
assert_eq!(
sm.tape,
vec!["Foo(10)", "Foo(20)", "Foo(40)", "Foo(80)", "Final(150)"]
);
}

#[test]
/// Test self-recursive handler calls. Also tests calls in the else-branch
/// of conditionals, and calls in integer matching constructs.
fn mutually_recursive() {
let mut sm = HandlerCalls::new();
sm.mut_rec();
sm.foo(2);
assert_eq!(
sm.tape,
vec![
"Foo(2)",
"Bar(4)",
"Foo(4)",
"Bar(8)",
"Foo(16)",
"Bar(32)",
"Foo(96)",
"Final(162)"
]
);
}

#[test]
/// Test handler calls in string matching constructs.
fn string_match_call() {
let mut sm = HandlerCalls::new();

sm.non_rec();
sm.call(String::from("Foo"), 5);
assert_eq!(sm.tape, vec!["Foo(5)", "Bar(10)", "Final(15)"]);
sm.tape.clear();

sm.non_rec();
sm.call(String::from("Bar"), 20);
assert_eq!(sm.tape, vec!["Bar(20)", "Final(20)"]);
sm.tape.clear();

sm.non_rec();
sm.call(String::from("Qux"), 37);
assert_eq!(sm.tape, vec!["Foo(1000)", "Bar(2000)", "Final(3000)"]);
}
}
1 change: 1 addition & 0 deletions framec_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod basic;
mod branch;
mod empty;
mod event_handler;
mod handler_calls;
mod hierarchical;
mod hierarchical_guard;
mod r#match;
Expand Down
30 changes: 29 additions & 1 deletion framec_tests/src/rust_naming.frm
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#[follow_rust_naming="true"]
#RustNaming
-interface-
snake_event [snake_param:i32]
CamelEvent [CamelParam:i32]
event123 [param123:i32]
call [event:String param:i32]

-machine-
$Init
Expand All @@ -16,6 +16,13 @@
|event123| [param123:i32]
-> $state123(param123) ^

|call| [event:String param:i32]
event ?~
/snake_event/ snake_event(param) :>
/CamelEvent/ CamelEvent(param) :>
/event123/ event123(param)
: :: ^

$snake_state [snake_state_param:i32]

--- 1100
Expand All @@ -36,6 +43,13 @@
action123(localVar123)
-> $Final(localVar123) ^

|call| [event:String param:i32]
event ?~
/snake_event/ snake_event(param) :>
/CamelEvent/ CamelEvent(param) :>
/event123/ event123(param)
: :: ^

$CamelState [CamelStateParam:i32]

--- 1200
Expand All @@ -56,6 +70,13 @@
action123(localVar123)
-> $Final(localVar123) ^

|call| [event:String param:i32]
event ?~
/snake_event/ snake_event(param) :>
/CamelEvent/ CamelEvent(param) :>
/event123/ event123(param)
: :: ^

$state123 [stateParam123:i32]

--- 1300
Expand All @@ -76,6 +97,13 @@
action123(localVar123)
-> $Final(localVar123) ^

|call| [event:String param:i32]
event ?~
/snake_event/ snake_event(param) :>
/CamelEvent/ CamelEvent(param) :>
/event123/ event123(param)
: :: ^

$Final [result:i32]
|>|
logFinal(result)
Expand Down
Loading