1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
//! Persisted node state (temporal operators)
//!
//! In Lustre, nodes may use different operators to use the language's temporal features, namely
//! `pre`, `->` and `fby`.
//! While `function` nodes behave mostly like traditional functions, actual `node`s need to persist
//! data from an invocation to the next in order to implement the above operators correctly.
//!
//! This module defines queries to obtain the state that a given node requires, that is, a list of
//! typed "slots" of memory that a Rustre runtime would need to allocate for each instance of the
//! aforementioned node.
//! Additionally, it provides a [check_node_function_state][check_node_function_state()] query to
//! emit diagnostics as to whether or not a given node is correctly defined as `function` or `node`,
//! if it has, respectively no, or some state.
//!
//! # Stateful expressions
//!
//! 4 kinds of Lustre expression require persisted memory between node invocations.
//!
//! * The `pre` unary operator: evaluates to the previous value it was applied to. It requires a
//! (nullable) "slot" of the same type as its operand.
//! * The `fby` binary operator: evaluates to the value of its first operand the first cycle, and
//! then to the previous value of its second operand for the remaining ones. It also requires a
//! (nullable) slot with the same type as its operands.
//! * The `->` binary operator evaluates to the value of its first operand the first cycle, and
//! then to the value of its second operand for the remaining ones. It only requires a boolean
//! value to be represented as it doesn't persist any Lustre data; it must just know if it is
//! in its first evaluation cycle.
//! * Node call sites: each call site corresponds to an _instanciation_ of a node, with its own
//! memory. They have to be recursively accounted for.
use crate::diagnostics::{Diagnostic, Level, Span};
use crate::id::{Id, IdRef};
use crate::static_args::StaticArgs;
use crate::types::ConstValue;
use rustre_parser::ast::expr_visitor::ExpressionWalker;
use rustre_parser::ast::{
ArrowExpressionNode, AstToken, CallByPosExpressionNode, ExpressionNode, FbyExpressionNode,
NodeNode, PreExpressionNode, WithExpressionNode,
};
use std::collections::HashSet;
use yeter::Database;
struct NodeStateWalker<'db, 'a> {
db: &'db Database,
static_args: &'a StaticArgs,
in_node: &'a Option<NodeNode>,
collected: HashSet<ExpressionNode>,
}
impl<'db, 'a> NodeStateWalker<'db, 'a> {
fn push(&mut self, node: ExpressionNode) {
let present = !self.collected.insert(node);
debug_assert!(
!present,
"attempted to insert expression twice in NodeStateWalker"
);
}
}
impl<'db, 'a> ExpressionWalker for NodeStateWalker<'db, 'a> {
fn walk_pre(&mut self, e: PreExpressionNode) {
self.push(ExpressionNode::PreExpressionNode(e));
}
fn walk_fby(&mut self, e: FbyExpressionNode) {
self.push(ExpressionNode::FbyExpressionNode(e));
}
fn walk_arrow(&mut self, e: ArrowExpressionNode) {
self.push(ExpressionNode::ArrowExpressionNode(e));
}
fn walk_with(&mut self, e: WithExpressionNode) -> bool {
if let Some(cond_node) = e.cond() {
let cond = crate::eval::eval_const_node(
self.db,
cond_node,
Some(self.static_args),
self.in_node.clone(),
);
match (cond.as_ref(), e.with_body(), e.else_body()) {
(Some(ConstValue::Bool(true)), Some(with), _) => self.walk_expr(with),
(Some(ConstValue::Bool(false)), _, Some(r#else)) => self.walk_expr(r#else),
_ => (),
};
}
false
}
fn walk_call_by_pos(&mut self, e: CallByPosExpressionNode) {
if let Some(node_name) = e
.node_ref()
.and_then(|n| n.id_node())
.and_then(|i| i.ident())
{
let sub_node = Option::clone(&crate::name_resolution::find_node(
self.db,
&None, // TODO there may actually be static args to consider
IdRef::new_implicit(<&Id>::from(&node_name)),
));
if matches!(sub_node, Some(sub_node) if *is_node_stateful(self.db, sub_node.clone(), self.static_args))
{
self.push(ExpressionNode::CallByPosExpressionNode(e));
}
}
}
}
/// **Query:** Returns a list of stateful expressions in a node
#[yeter::query]
pub fn stateful_expr_of_node<'a>(
db: &Database,
node: NodeNode,
static_args: &'a StaticArgs,
) -> Vec<ExpressionNode> {
let Some(body) = node.body_node() else {
return Default::default();
};
let mut walker = NodeStateWalker {
db,
static_args,
in_node: &Some(node),
collected: Default::default(),
};
body.all_equals_equation_node()
.flat_map(|e| e.expression_node())
.chain(
body.all_assert_equation_node()
.flat_map(|e| e.expression_node()),
)
.for_each(|e| {
walker.walk_expr(e);
});
walker.collected.into_iter().collect()
}
/// **Query:** Returns `true` if a node contains any stateful expressions (and is thus stateful
/// itself)
#[yeter::query]
pub fn is_node_stateful<'a>(db: &Database, node: NodeNode, static_args: &'a StaticArgs) -> bool {
!stateful_expr_of_node(db, node, static_args).is_empty()
}
/// **Query:** Checks the coherence between the use of the `function` or `node` keyword and the
/// presence or absence of temporal state
#[yeter::query]
pub fn check_node_function_state<'a>(db: &Database, node: NodeNode, static_args: &'a StaticArgs) {
let has_no_state = !*is_node_stateful(db, node.clone(), static_args);
if node.is_node() && has_no_state {
let span = Span::of_token(db, node.node().unwrap().syntax());
Diagnostic::new(
Level::Warning,
"node has no internal state, it should be declared as `function`",
)
.with_attachment(span, "hint: replace with `function`")
.emit(db);
}
if node.is_function() && !has_no_state {
let span = Span::of_token(db, node.function().unwrap().syntax());
Diagnostic::new(
Level::Error,
"function has internal state, it should be a node",
)
.with_attachment(span, "hint: replace with `node`")
.emit(db);
}
}