use wirespec_codec::ir::*;
pub enum ExprContext {
Parse,
Serialize,
CapsuleVariantParse {
variant_prefix: String,
header_field_names: Vec<String>,
},
CapsuleVariantSerialize {
variant_prefix: String,
header_field_names: Vec<String>,
},
}
use std::cell::RefCell;
thread_local! {
static CONST_PREFIX: RefCell<String> = const { RefCell::new(String::new()) };
}
pub fn set_const_prefix(prefix: &str) {
CONST_PREFIX.with(|p| *p.borrow_mut() = prefix.to_string());
}
impl ExprContext {
fn resolve_prefix<'a>(&'a self, field_name: &str) -> &'a str {
match self {
ExprContext::Parse => "out->",
ExprContext::Serialize => "val->",
ExprContext::CapsuleVariantParse {
variant_prefix,
header_field_names,
} => {
if header_field_names.iter().any(|n| n == field_name) {
"out->"
} else {
variant_prefix.as_str()
}
}
ExprContext::CapsuleVariantSerialize {
variant_prefix,
header_field_names,
} => {
if header_field_names.iter().any(|n| n == field_name) {
"val->"
} else {
variant_prefix.as_str()
}
}
}
}
}
pub fn expr_to_c(expr: &CodecExpr, ctx: &ExprContext) -> String {
match expr {
CodecExpr::ValueRef { reference } => match reference.kind {
ValueRefKind::Field | ValueRefKind::Derived => {
let name = extract_field_name(&reference.value_id);
let prefix = ctx.resolve_prefix(name);
format!("{prefix}{name}")
}
ValueRefKind::Const => {
let name_upper = crate::names::to_snake_case(&reference.value_id).to_uppercase();
CONST_PREFIX.with(|p| {
let prefix = p.borrow();
if prefix.is_empty() {
name_upper.clone()
} else {
format!("{}_{}", prefix.to_uppercase(), name_upper)
}
})
}
},
CodecExpr::Literal { value } => match value {
LiteralValue::Int(n) => {
if *n < 0 {
format!("({n})")
} else if *n > 0xFFFF_FFFF {
format!("{n}ULL")
} else if *n > 0xFFFF {
format!("{n}UL")
} else {
format!("{n}")
}
}
LiteralValue::Bool(b) => if *b { "true" } else { "false" }.into(),
LiteralValue::String(s) => {
let escaped = s
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("\"{escaped}\"")
}
LiteralValue::Null => "0".into(),
},
CodecExpr::Binary { op, left, right } => {
let l = expr_to_c(left, ctx);
let r = expr_to_c(right, ctx);
let c_op = match op.as_str() {
"and" => "&&",
"or" => "||",
o => o,
};
format!("({l} {c_op} {r})")
}
CodecExpr::Unary { op, operand } => {
let o = expr_to_c(operand, ctx);
format!("({op}{o})")
}
CodecExpr::Coalesce {
expr: e,
default: d,
} => {
let e_str = expr_to_c(e, ctx);
let d_str = expr_to_c(d, ctx);
if let CodecExpr::ValueRef { reference } = e.as_ref() {
let name = extract_field_name(&reference.value_id);
let prefix = ctx.resolve_prefix(name);
format!("({prefix}has_{name} ? {e_str} : {d_str})")
} else {
format!("({e_str} ? {e_str} : {d_str})")
}
}
CodecExpr::Subscript { base, index } => {
let b = expr_to_c(base, ctx);
let i = expr_to_c(index, ctx);
format!("{b}[{i}]")
}
CodecExpr::InState { .. }
| CodecExpr::StateConstructor { .. }
| CodecExpr::Fill { .. }
| CodecExpr::Slice { .. }
| CodecExpr::All { .. } => unreachable!("SM expression in non-SM context"),
}
}
pub fn extract_field_name(id: &str) -> &str {
let after_dot = if let Some(dot_pos) = id.rfind('.') {
&id[dot_pos + 1..]
} else {
id
};
if let Some(bracket) = after_dot.find('[') {
&after_dot[..bracket]
} else {
after_dot
}
}
use wirespec_sema::expr::{SemanticExpr, SemanticLiteral, TransitionPeerKind};
use wirespec_sema::ir::SemanticStateMachine;
use wirespec_sema::types::SemanticType;
fn is_bytes_expr(expr: &SemanticExpr, ctx: &SmExprContext) -> bool {
if let SemanticExpr::TransitionPeerRef { reference } = expr
&& let Some(field_name) = reference.path.first()
&& let Some(sm) = ctx.sm
{
match reference.peer {
TransitionPeerKind::Src | TransitionPeerKind::Dst => {
for state in &sm.states {
for f in &state.fields {
if &f.name == field_name {
return matches!(&f.ty, SemanticType::Bytes { .. });
}
}
}
}
TransitionPeerKind::EventParam => {
for event in &sm.events {
for p in &event.params {
if &p.name == field_name {
return matches!(&p.ty, SemanticType::Bytes { .. });
}
}
}
}
}
}
false
}
pub struct SmExprContext<'a> {
pub src_state_snake: &'a str,
pub dst_state_snake: &'a str,
pub event_snake: &'a str,
pub sm: Option<&'a SemanticStateMachine>,
pub prefix: &'a str,
}
pub fn sema_expr_to_c(expr: &SemanticExpr, ctx: &SmExprContext) -> String {
match expr {
SemanticExpr::TransitionPeerRef { reference } => {
let path = reference.path.join(".");
match reference.peer {
TransitionPeerKind::Src => {
format!("sm->{}.{path}", ctx.src_state_snake)
}
TransitionPeerKind::Dst => {
format!("dst.{}.{path}", ctx.dst_state_snake)
}
TransitionPeerKind::EventParam => {
format!("event->{}.{path}", ctx.event_snake)
}
}
}
SemanticExpr::ValueRef { reference } => {
match reference.kind {
wirespec_sema::expr::ValueRefKind::Const => reference.value_id.to_uppercase(),
_ => {
let name = extract_field_name(&reference.value_id);
format!("sm->{}.{name}", ctx.src_state_snake)
}
}
}
SemanticExpr::Literal { value } => match value {
SemanticLiteral::Int(n) => {
if *n < 0 {
format!("({n})")
} else if *n > 0xFFFF_FFFF {
format!("{n}ULL")
} else if *n > 0xFFFF {
format!("{n}UL")
} else {
format!("{n}")
}
}
SemanticLiteral::Bool(b) => if *b { "true" } else { "false" }.into(),
SemanticLiteral::String(s) => {
let escaped = s
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("\"{escaped}\"")
}
SemanticLiteral::Null => "0".into(),
},
SemanticExpr::Binary { op, left, right } => {
let l = sema_expr_to_c(left, ctx);
let r = sema_expr_to_c(right, ctx);
let is_bytes_cmp = (op == "==" || op == "!=") && is_bytes_expr(left, ctx);
if is_bytes_cmp {
if op == "==" {
format!("({l}.len == {r}.len && memcmp({l}.ptr, {r}.ptr, {l}.len) == 0)")
} else {
format!("({l}.len != {r}.len || memcmp({l}.ptr, {r}.ptr, {l}.len) != 0)")
}
} else {
let c_op = match op.as_str() {
"and" => "&&",
"or" => "||",
o => o,
};
format!("({l} {c_op} {r})")
}
}
SemanticExpr::Unary { op, operand } => {
let o = sema_expr_to_c(operand, ctx);
format!("({op}{o})")
}
SemanticExpr::Subscript { base, index } => {
let b = sema_expr_to_c(base, ctx);
let i = sema_expr_to_c(index, ctx);
format!("{b}[{i}]")
}
SemanticExpr::InState {
expr,
sm_name,
state_name,
..
} => {
let expr_c = sema_expr_to_c(expr, ctx);
let sm_snake = crate::names::to_snake_case(sm_name);
let state_snake = crate::names::to_snake_case(state_name);
let prefix_upper = ctx.prefix.to_uppercase();
let sm_upper = sm_snake.to_uppercase();
let state_upper = state_snake.to_uppercase();
format!("({expr_c}.tag == {prefix_upper}_{sm_upper}_{state_upper})")
}
SemanticExpr::StateConstructor {
sm_name,
state_name,
args,
..
} => {
let sm_snake = crate::names::to_snake_case(sm_name);
let state_snake = crate::names::to_snake_case(state_name);
let prefix_upper = ctx.prefix.to_uppercase();
let sm_upper = sm_snake.to_uppercase();
let state_upper = state_snake.to_uppercase();
let sm_type = format!("{prefix}_{sm_snake}_t", prefix = ctx.prefix);
let tag = format!("{prefix_upper}_{sm_upper}_{state_upper}");
if args.is_empty() {
format!("(({sm_type}){{ .tag = {tag} }})")
} else {
let field_names: Vec<String> = ctx
.sm
.and_then(|sm| sm.states.iter().find(|s| &s.name == state_name))
.map(|state| {
state
.fields
.iter()
.map(|f| crate::names::to_snake_case(&f.name))
.collect()
})
.unwrap_or_default();
let arg_strs: Vec<String> = args
.iter()
.enumerate()
.map(|(i, a)| {
let val = sema_expr_to_c(a, ctx);
if i < field_names.len() {
format!(".{} = {val}", field_names[i])
} else {
val
}
})
.collect();
let inits = arg_strs.join(", ");
format!("(({sm_type}){{ .tag = {tag}, .{state_snake} = {{ {inits} }} }})")
}
}
SemanticExpr::Fill { .. } => {
"/* fill: see action emission */".into()
}
SemanticExpr::Slice { base, start, end } => {
let b = sema_expr_to_c(base, ctx);
let s = sema_expr_to_c(start, ctx);
let e = sema_expr_to_c(end, ctx);
format!("/* slice: {b}[{s}..{e}] */")
}
SemanticExpr::All {
collection,
sm_name,
state_name,
..
} => {
let sm_snake = crate::names::to_snake_case(sm_name);
let state_snake = crate::names::to_snake_case(state_name);
let prefix_upper = ctx.prefix.to_uppercase();
let sm_upper = sm_snake.to_uppercase();
let state_upper = state_snake.to_uppercase();
let tag = format!("{prefix_upper}_{sm_upper}_{state_upper}");
match collection.as_ref() {
SemanticExpr::Slice { base, start, end } => {
let base_c = sema_expr_to_c(base, ctx);
let start_c = sema_expr_to_c(start, ctx);
let end_c = sema_expr_to_c(end, ctx);
format!(
"({{ bool _all_ok = true; \
for (size_t _aj = (size_t)({start_c}); _aj < (size_t)({end_c}); _aj++) {{ \
if ({base_c}[_aj].tag != {tag}) {{ _all_ok = false; break; }} \
}} _all_ok; }})"
)
}
_ => {
let coll_c = sema_expr_to_c(collection, ctx);
format!("({{ bool _all_ok = true; /* all check on {coll_c} */ _all_ok; }})")
}
}
}
SemanticExpr::Coalesce {
expr: e,
default: d,
} => {
let e_str = sema_expr_to_c(e, ctx);
let d_str = sema_expr_to_c(d, ctx);
format!("({e_str} ? {e_str} : {d_str})")
}
}
}