Skip to content

Commit 2014035

Browse files
authored
Merge pull request #40 from frame-lang/fix-interface-calls
Fix interface method calls
2 parents a7aa533 + 39f18dd commit 2014035

File tree

6 files changed

+282
-15
lines changed

6 files changed

+282
-15
lines changed

framec/src/frame_c/visitors/rust_visitor.rs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3039,10 +3039,9 @@ impl AstVisitor for RustVisitor {
30393039
) -> AstVisitorReturnType {
30403040
self.add_code(&format!(
30413041
"self.{}",
3042-
interface_method_call_expr_node.identifier.name.lexeme
3042+
self.format_value_name(&interface_method_call_expr_node.identifier.name.lexeme)
30433043
));
30443044
interface_method_call_expr_node.call_expr_list.accept(self);
3045-
// self.add_code(&format!(""));
30463045
// TODO: review this return as I think it is a nop.
30473046
AstVisitorReturnType::InterfaceMethodCallExpressionNode {}
30483047
}
@@ -3056,13 +3055,11 @@ impl AstVisitor for RustVisitor {
30563055
) -> AstVisitorReturnType {
30573056
output.push_str(&format!(
30583057
"self.{}",
3059-
interface_method_call_expr_node.identifier.name.lexeme
3058+
self.format_value_name(&interface_method_call_expr_node.identifier.name.lexeme)
30603059
));
30613060
interface_method_call_expr_node
30623061
.call_expr_list
30633062
.accept_to_string(self, output);
3064-
// self.add_code(&format!(""));
3065-
30663063
// TODO: review this return as I think it is a nop.
30673064
AstVisitorReturnType::InterfaceMethodCallExpressionNode {}
30683065
}
@@ -3761,11 +3758,48 @@ impl AstVisitor for RustVisitor {
37613758

37623759
//* --------------------------------------------------------------------- *//
37633760

3761+
// NOTE: Interface method calls must be treated specially since they may transition.
3762+
//
3763+
// The current approach is conservative, essentially assuming that an interface method call
3764+
// always transitions. This assumption imposes the following restrictions:
3765+
//
3766+
// * Interface method calls cannot occur in a chain (they must be a standalone call).
3767+
// * Interface method calls terminate the execution of their handler (like transitions).
3768+
//
3769+
// It would be possible to lift these restrictions and track the execution of a handler more
3770+
// precisely, but this would require embedding some logic in the generated code and would make
3771+
// handlers harder to reason about. The conservative approach has the advantage of both
3772+
// simplifying the implementation and reasoning about Frame programs.
37643773
fn visit_call_chain_literal_statement_node(
37653774
&mut self,
37663775
method_call_chain_literal_stmt_node: &CallChainLiteralStmtNode,
37673776
) -> AstVisitorReturnType {
37683777
self.newline();
3778+
3779+
// special case for interface method calls
3780+
let call_chain = &method_call_chain_literal_stmt_node
3781+
.call_chain_literal_expr_node
3782+
.call_chain;
3783+
if call_chain.len() == 1 {
3784+
if let CallChainLiteralNodeType::InterfaceMethodCallT {
3785+
interface_method_call_expr_node,
3786+
} = &call_chain[0]
3787+
{
3788+
self.this_branch_transitioned = true;
3789+
self.add_code(&format!(
3790+
"drop({});",
3791+
self.config.this_state_context_var_name
3792+
));
3793+
self.newline();
3794+
interface_method_call_expr_node.accept(self);
3795+
self.add_code(";");
3796+
self.newline();
3797+
self.add_code("return;");
3798+
return AstVisitorReturnType::CallChainLiteralStmtNode {};
3799+
}
3800+
}
3801+
3802+
// standard case
37693803
method_call_chain_literal_stmt_node
37703804
.call_chain_literal_expr_node
37713805
.accept(self);
@@ -3780,7 +3814,6 @@ impl AstVisitor for RustVisitor {
37803814
method_call_chain_expression_node: &CallChainLiteralExprNode,
37813815
) -> AstVisitorReturnType {
37823816
// TODO: maybe put this in an AST node
3783-
37843817
let mut separator = "";
37853818

37863819
for node in &method_call_chain_expression_node.call_chain {
@@ -3792,10 +3825,10 @@ impl AstVisitor for RustVisitor {
37923825
CallChainLiteralNodeType::CallT { call } => {
37933826
call.accept(self);
37943827
}
3795-
CallChainLiteralNodeType::InterfaceMethodCallT {
3796-
interface_method_call_expr_node,
3797-
} => {
3798-
interface_method_call_expr_node.accept(self);
3828+
CallChainLiteralNodeType::InterfaceMethodCallT { .. } => {
3829+
self.errors.push(String::from(
3830+
"Error: Interface method calls may not appear in call chains.",
3831+
));
37993832
}
38003833
CallChainLiteralNodeType::ActionCallT {
38013834
action_call_expr_node,
@@ -3832,10 +3865,10 @@ impl AstVisitor for RustVisitor {
38323865
CallChainLiteralNodeType::CallT { call } => {
38333866
call.accept_to_string(self, output);
38343867
}
3835-
CallChainLiteralNodeType::InterfaceMethodCallT {
3836-
interface_method_call_expr_node,
3837-
} => {
3838-
interface_method_call_expr_node.accept_to_string(self, output);
3868+
CallChainLiteralNodeType::InterfaceMethodCallT { .. } => {
3869+
self.errors.push(String::from(
3870+
"Error: Interface method calls may not appear in call chains.",
3871+
));
38393872
}
38403873
CallChainLiteralNodeType::ActionCallT {
38413874
action_call_expr_node,

framec_tests/src/handler_calls.frm

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#HandlerCalls
2+
-interface-
3+
NonRec
4+
SelfRec
5+
MutRec
6+
Call [event:String arg:i32]
7+
Foo [arg:i32]
8+
Bar [arg:i32]
9+
10+
-machine-
11+
$Init
12+
|NonRec| -> $NonRecursive ^
13+
|SelfRec| -> $SelfRecursive ^
14+
|MutRec| -> $MutuallyRecursive ^
15+
16+
$NonRecursive
17+
var counter:i32 = 0
18+
19+
|Foo| [arg:i32]
20+
log("Foo" arg)
21+
counter = counter + arg
22+
Bar(arg*2)
23+
--- the front-end should report the next line as a static error
24+
log("Unreachable" 0)
25+
^
26+
27+
|Bar| [arg:i32]
28+
log("Bar" arg)
29+
counter = counter + arg
30+
-> $Final(counter) ^
31+
32+
|Call| [event:String arg:i32]
33+
event ?~
34+
/Foo/ Foo(arg) :>
35+
/Bar/ Bar(arg)
36+
: Call("Foo" 1000)
37+
:: ^
38+
39+
$SelfRecursive
40+
var counter:i32 = 0
41+
42+
|Foo| [arg:i32]
43+
log("Foo" arg)
44+
counter = counter + arg
45+
counter < 100 ?
46+
Foo(arg*2)
47+
:
48+
-> $Final(counter)
49+
:: ^
50+
51+
|Bar| [arg:i32]
52+
log("Bar" arg)
53+
counter = counter + arg
54+
-> $Final(counter) ^
55+
56+
|Call| [event:String arg:i32]
57+
event ?~
58+
/Foo/ Foo(arg) :>
59+
/Bar/ Bar(arg)
60+
: :: ^
61+
62+
$MutuallyRecursive
63+
var counter:i32 = 0
64+
65+
|Foo| [arg:i32]
66+
log("Foo" arg)
67+
counter = counter + arg
68+
counter > 100 ?
69+
-> $Final(counter)
70+
:
71+
Bar(arg*2)
72+
:: ^
73+
74+
|Bar| [arg:i32]
75+
log("Bar" arg)
76+
counter = counter + arg
77+
arg ?#
78+
/4/ Foo(arg) :>
79+
/8/ Foo(arg*2)
80+
: Foo(arg*3)
81+
:: ^
82+
83+
|Call| [event:String arg:i32]
84+
event ?~
85+
/Foo/ Foo(arg) :>
86+
/Bar/ Bar(arg)
87+
: :: ^
88+
89+
$Final [counter:i32]
90+
|>|
91+
log("Final" counter)
92+
-> $Init ^
93+
94+
-actions-
95+
log [from:String val:i32]
96+
97+
-domain-
98+
var tape:Log = `vec![]`
99+
##

framec_tests/src/handler_calls.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Test directly invoking event handlers from within other event handlers.
2+
//! Since event handlers may transition, we conservatively treat such calls
3+
//! as terminating statements for the current handler.
4+
5+
type Log = Vec<String>;
6+
include!(concat!(env!("OUT_DIR"), "/", "handler_calls.rs"));
7+
8+
impl<'a> HandlerCalls<'a> {
9+
pub fn log(&mut self, from: String, val: i32) {
10+
self.tape.push(format!("{}({})", from, val));
11+
}
12+
}
13+
14+
#[cfg(test)]
15+
mod tests {
16+
use super::*;
17+
18+
#[test]
19+
/// Test that a handler call terminates the current handler.
20+
fn calls_terminate_handler() {
21+
let mut sm = HandlerCalls::new();
22+
sm.non_rec();
23+
sm.foo(10);
24+
assert!(!sm.tape.iter().any(|e| e == "Unreachable(0)"));
25+
}
26+
27+
#[test]
28+
/// Test non-recursive handler calls.
29+
fn non_recursive() {
30+
let mut sm = HandlerCalls::new();
31+
sm.non_rec();
32+
sm.foo(10);
33+
assert_eq!(sm.tape, vec!["Foo(10)", "Bar(20)", "Final(30)"]);
34+
}
35+
36+
#[test]
37+
/// Test self-recursive handler calls. Also tests calls in the then-branch
38+
/// of a conditional.
39+
fn self_recursive() {
40+
let mut sm = HandlerCalls::new();
41+
sm.self_rec();
42+
sm.foo(10);
43+
assert_eq!(
44+
sm.tape,
45+
vec!["Foo(10)", "Foo(20)", "Foo(40)", "Foo(80)", "Final(150)"]
46+
);
47+
}
48+
49+
#[test]
50+
/// Test self-recursive handler calls. Also tests calls in the else-branch
51+
/// of conditionals, and calls in integer matching constructs.
52+
fn mutually_recursive() {
53+
let mut sm = HandlerCalls::new();
54+
sm.mut_rec();
55+
sm.foo(2);
56+
assert_eq!(
57+
sm.tape,
58+
vec![
59+
"Foo(2)",
60+
"Bar(4)",
61+
"Foo(4)",
62+
"Bar(8)",
63+
"Foo(16)",
64+
"Bar(32)",
65+
"Foo(96)",
66+
"Final(162)"
67+
]
68+
);
69+
}
70+
71+
#[test]
72+
/// Test handler calls in string matching constructs.
73+
fn string_match_call() {
74+
let mut sm = HandlerCalls::new();
75+
76+
sm.non_rec();
77+
sm.call(String::from("Foo"), 5);
78+
assert_eq!(sm.tape, vec!["Foo(5)", "Bar(10)", "Final(15)"]);
79+
sm.tape.clear();
80+
81+
sm.non_rec();
82+
sm.call(String::from("Bar"), 20);
83+
assert_eq!(sm.tape, vec!["Bar(20)", "Final(20)"]);
84+
sm.tape.clear();
85+
86+
sm.non_rec();
87+
sm.call(String::from("Qux"), 37);
88+
assert_eq!(sm.tape, vec!["Foo(1000)", "Bar(2000)", "Final(3000)"]);
89+
}
90+
}

framec_tests/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod basic;
22
mod branch;
33
mod empty;
44
mod event_handler;
5+
mod handler_calls;
56
mod hierarchical;
67
mod hierarchical_guard;
78
mod r#match;

framec_tests/src/rust_naming.frm

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
#[follow_rust_naming="true"]
21
#RustNaming
32
-interface-
43
snake_event [snake_param:i32]
54
CamelEvent [CamelParam:i32]
65
event123 [param123:i32]
6+
call [event:String param:i32]
77

88
-machine-
99
$Init
@@ -16,6 +16,13 @@
1616
|event123| [param123:i32]
1717
-> $state123(param123) ^
1818

19+
|call| [event:String param:i32]
20+
event ?~
21+
/snake_event/ snake_event(param) :>
22+
/CamelEvent/ CamelEvent(param) :>
23+
/event123/ event123(param)
24+
: :: ^
25+
1926
$snake_state [snake_state_param:i32]
2027

2128
--- 1100
@@ -36,6 +43,13 @@
3643
action123(localVar123)
3744
-> $Final(localVar123) ^
3845

46+
|call| [event:String param:i32]
47+
event ?~
48+
/snake_event/ snake_event(param) :>
49+
/CamelEvent/ CamelEvent(param) :>
50+
/event123/ event123(param)
51+
: :: ^
52+
3953
$CamelState [CamelStateParam:i32]
4054

4155
--- 1200
@@ -56,6 +70,13 @@
5670
action123(localVar123)
5771
-> $Final(localVar123) ^
5872

73+
|call| [event:String param:i32]
74+
event ?~
75+
/snake_event/ snake_event(param) :>
76+
/CamelEvent/ CamelEvent(param) :>
77+
/event123/ event123(param)
78+
: :: ^
79+
5980
$state123 [stateParam123:i32]
6081

6182
--- 1300
@@ -76,6 +97,13 @@
7697
action123(localVar123)
7798
-> $Final(localVar123) ^
7899

100+
|call| [event:String param:i32]
101+
event ?~
102+
/snake_event/ snake_event(param) :>
103+
/CamelEvent/ CamelEvent(param) :>
104+
/event123/ event123(param)
105+
: :: ^
106+
79107
$Final [result:i32]
80108
|>|
81109
logFinal(result)

0 commit comments

Comments
 (0)