use std::fmt;
use serde::{Deserialize, Serialize};
use super::ProgramError;
pub const FORMAT_VERSION: u32 = 1;
pub const MAX_NESTING: usize = 100;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Program {
pub fugue_program: u32,
pub body: Vec<Stmt>,
pub ret: Expr,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "stmt", rename_all = "snake_case", deny_unknown_fields)]
pub enum Stmt {
Sample {
var: String,
addr: Addr,
dist: DistCall,
},
Let {
var: String,
value: Expr,
},
Assign {
var: String,
value: Expr,
},
Observe {
addr: Addr,
dist: DistCall,
value: Expr,
},
Factor {
logw: Expr,
},
For {
var: String,
start: Expr,
end: Expr,
body: Vec<Stmt>,
},
If {
cond: Expr,
#[serde(rename = "then")]
then_branch: Vec<Stmt>,
#[serde(rename = "else", default, skip_serializing_if = "Vec::is_empty")]
else_branch: Vec<Stmt>,
},
Break {},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Addr {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<Expr>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DistCall {
pub name: String,
#[serde(default)]
pub args: Vec<Expr>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case", deny_unknown_fields)]
pub enum Expr {
F64 {
value: f64,
},
Int {
value: i64,
},
Bool {
value: bool,
},
Var {
name: String,
},
Array {
items: Vec<Expr>,
},
Index {
array: Box<Expr>,
index: Box<Expr>,
},
Len {
array: Box<Expr>,
},
Neg {
arg: Box<Expr>,
},
Not {
arg: Box<Expr>,
},
Add {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Sub {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Mul {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Div {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Eq {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Ne {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Lt {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Le {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Gt {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Ge {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
And {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Or {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Call {
func: String,
#[serde(default)]
args: Vec<Expr>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
}
impl BinOp {
pub fn symbol(self) -> &'static str {
match self {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Eq => "==",
BinOp::Ne => "!=",
BinOp::Lt => "<",
BinOp::Le => "<=",
BinOp::Gt => ">",
BinOp::Ge => ">=",
BinOp::And => "&&",
BinOp::Or => "||",
}
}
pub(crate) fn precedence(self) -> u8 {
match self {
BinOp::Or => 1,
BinOp::And => 2,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => 3,
BinOp::Add | BinOp::Sub => 4,
BinOp::Mul | BinOp::Div => 5,
}
}
}
impl Expr {
pub fn var(name: impl Into<String>) -> Expr {
Expr::Var { name: name.into() }
}
pub fn binary(op: BinOp, lhs: Expr, rhs: Expr) -> Expr {
let (lhs, rhs) = (Box::new(lhs), Box::new(rhs));
match op {
BinOp::Add => Expr::Add { lhs, rhs },
BinOp::Sub => Expr::Sub { lhs, rhs },
BinOp::Mul => Expr::Mul { lhs, rhs },
BinOp::Div => Expr::Div { lhs, rhs },
BinOp::Eq => Expr::Eq { lhs, rhs },
BinOp::Ne => Expr::Ne { lhs, rhs },
BinOp::Lt => Expr::Lt { lhs, rhs },
BinOp::Le => Expr::Le { lhs, rhs },
BinOp::Gt => Expr::Gt { lhs, rhs },
BinOp::Ge => Expr::Ge { lhs, rhs },
BinOp::And => Expr::And { lhs, rhs },
BinOp::Or => Expr::Or { lhs, rhs },
}
}
pub fn as_binary(&self) -> Option<(BinOp, &Expr, &Expr)> {
let (op, lhs, rhs) = match self {
Expr::Add { lhs, rhs } => (BinOp::Add, lhs, rhs),
Expr::Sub { lhs, rhs } => (BinOp::Sub, lhs, rhs),
Expr::Mul { lhs, rhs } => (BinOp::Mul, lhs, rhs),
Expr::Div { lhs, rhs } => (BinOp::Div, lhs, rhs),
Expr::Eq { lhs, rhs } => (BinOp::Eq, lhs, rhs),
Expr::Ne { lhs, rhs } => (BinOp::Ne, lhs, rhs),
Expr::Lt { lhs, rhs } => (BinOp::Lt, lhs, rhs),
Expr::Le { lhs, rhs } => (BinOp::Le, lhs, rhs),
Expr::Gt { lhs, rhs } => (BinOp::Gt, lhs, rhs),
Expr::Ge { lhs, rhs } => (BinOp::Ge, lhs, rhs),
Expr::And { lhs, rhs } => (BinOp::And, lhs, rhs),
Expr::Or { lhs, rhs } => (BinOp::Or, lhs, rhs),
_ => return None,
};
Some((op, lhs, rhs))
}
}
impl Program {
pub fn new(body: Vec<Stmt>, ret: Expr) -> Program {
Program {
fugue_program: FORMAT_VERSION,
body,
ret,
}
}
pub fn from_json(json: &str) -> Result<Program, ProgramError> {
#[derive(Deserialize)]
struct VersionProbe {
fugue_program: Option<serde_json::Value>,
}
let probe: VersionProbe =
serde_json::from_str(json).map_err(|e| ProgramError::Json(e.to_string()))?;
let version = match probe.fugue_program {
None => {
return Err(ProgramError::Json(
"missing field `fugue_program` (the program format version)".to_string(),
))
}
Some(v) => v.as_u64().ok_or_else(|| {
ProgramError::Json(format!(
"`fugue_program` must be a non-negative integer format version, found {v}"
))
})?,
};
if version != u64::from(FORMAT_VERSION) {
return Err(ProgramError::UnsupportedVersion {
found: version,
supported: FORMAT_VERSION,
});
}
let program: Program =
serde_json::from_str(json).map_err(|e| ProgramError::Json(e.to_string()))?;
program.validate()?;
Ok(program)
}
pub fn to_json(&self) -> Result<String, ProgramError> {
self.validate()?;
serde_json::to_string(self).map_err(|e| ProgramError::Json(e.to_string()))
}
pub fn to_json_pretty(&self) -> Result<String, ProgramError> {
self.validate()?;
serde_json::to_string_pretty(self).map_err(|e| ProgramError::Json(e.to_string()))
}
pub(crate) fn validate(&self) -> Result<(), ProgramError> {
if self.fugue_program != FORMAT_VERSION {
return Err(ProgramError::UnsupportedVersion {
found: u64::from(self.fugue_program),
supported: FORMAT_VERSION,
});
}
let depth = self.nesting_depth();
if depth > MAX_NESTING {
return Err(ProgramError::Check(format!(
"program nests {depth} levels deep; the format allows at most {MAX_NESTING}"
)));
}
let mut stmts: Vec<&Stmt> = self.body.iter().collect();
let mut exprs: Vec<&Expr> = vec![&self.ret];
let ident = |kind: &str, name: &str| -> Result<(), ProgramError> {
if is_ident(name) {
Ok(())
} else {
Err(ProgramError::Check(format!(
"{kind} name `{name}` is not an identifier (or is a reserved word)"
)))
}
};
while let Some(s) = stmts.pop() {
match s {
Stmt::Sample { var, addr, dist } => {
ident("variable", var)?;
ident("distribution", &dist.name)?;
exprs.extend(addr.index.iter());
exprs.extend(dist.args.iter());
}
Stmt::Let { var, value } | Stmt::Assign { var, value } => {
ident("variable", var)?;
exprs.push(value);
}
Stmt::Observe { addr, dist, value } => {
ident("distribution", &dist.name)?;
exprs.extend(addr.index.iter());
exprs.extend(dist.args.iter());
exprs.push(value);
}
Stmt::Factor { logw } => exprs.push(logw),
Stmt::For {
var,
start,
end,
body,
} => {
ident("variable", var)?;
exprs.push(start);
exprs.push(end);
stmts.extend(body.iter());
}
Stmt::If {
cond,
then_branch,
else_branch,
} => {
exprs.push(cond);
stmts.extend(then_branch.iter());
stmts.extend(else_branch.iter());
}
Stmt::Break {} => {}
}
}
while let Some(e) = exprs.pop() {
match e {
Expr::F64 { value } if !value.is_finite() => {
return Err(ProgramError::Check(format!(
"number literal {value} is not finite (JSON cannot represent it)"
)))
}
Expr::F64 { .. } | Expr::Int { .. } | Expr::Bool { .. } => {}
Expr::Var { name } => ident("variable", name)?,
Expr::Array { items } => exprs.extend(items.iter()),
Expr::Index { array, index } => {
exprs.push(array);
exprs.push(index);
}
Expr::Len { array: arg } | Expr::Neg { arg } | Expr::Not { arg } => exprs.push(arg),
Expr::Call { func, args } => {
ident("function", func)?;
exprs.extend(args.iter());
}
other => {
let (_, lhs, rhs) = other.as_binary().expect("remaining variants are binary");
exprs.push(lhs);
exprs.push(rhs);
}
}
}
Ok(())
}
pub(crate) fn nesting_depth(&self) -> usize {
let mut max = 2; let mut stmts: Vec<(&Stmt, usize)> = self.body.iter().map(|s| (s, 3)).collect();
let mut exprs: Vec<(&Expr, usize)> = vec![(&self.ret, 2)];
while let Some((s, d)) = stmts.pop() {
max = max.max(d);
match s {
Stmt::Sample { addr, dist, .. } | Stmt::Observe { addr, dist, .. } => {
max = max.max(d + 2);
if let Some(ix) = &addr.index {
exprs.push((ix, d + 2));
}
exprs.extend(dist.args.iter().map(|a| (a, d + 3)));
if let Stmt::Observe { value, .. } = s {
exprs.push((value, d + 1));
}
}
Stmt::Let { value, .. } | Stmt::Assign { value, .. } => exprs.push((value, d + 1)),
Stmt::Factor { logw } => exprs.push((logw, d + 1)),
Stmt::For {
start, end, body, ..
} => {
exprs.push((start, d + 1));
exprs.push((end, d + 1));
max = max.max(d + 1); stmts.extend(body.iter().map(|s| (s, d + 2)));
}
Stmt::If {
cond,
then_branch,
else_branch,
} => {
exprs.push((cond, d + 1));
max = max.max(d + 1); stmts.extend(then_branch.iter().map(|s| (s, d + 2)));
stmts.extend(else_branch.iter().map(|s| (s, d + 2)));
}
Stmt::Break {} => {}
}
}
while let Some((e, d)) = exprs.pop() {
max = max.max(d);
match e {
Expr::F64 { .. } | Expr::Int { .. } | Expr::Bool { .. } | Expr::Var { .. } => {}
Expr::Array { items: args } | Expr::Call { args, .. } => {
max = max.max(d + 1); exprs.extend(args.iter().map(|a| (a, d + 2)));
}
Expr::Index { array, index } => {
exprs.push((array, d + 1));
exprs.push((index, d + 1));
}
Expr::Len { array: arg } | Expr::Neg { arg } | Expr::Not { arg } => {
exprs.push((arg, d + 1))
}
other => {
let (_, lhs, rhs) = other.as_binary().expect("remaining variants are binary");
exprs.push((lhs, d + 1));
exprs.push((rhs, d + 1));
}
}
}
max
}
}
pub(crate) const RESERVED: &[&str] = &[
"let", "mut", "observe", "factor", "for", "in", "if", "else", "break", "pure", "true", "false",
];
pub(crate) fn is_ident(s: &str) -> bool {
let mut chars = s.chars();
matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !RESERVED.contains(&s)
}
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for s in &self.body {
write_stmt(f, s, 0)?;
}
write!(f, "pure({})", self.ret)
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_expr(f, self)
}
}
fn indent(f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result {
for _ in 0..depth {
f.write_str(" ")?;
}
Ok(())
}
fn write_block(f: &mut fmt::Formatter<'_>, stmts: &[Stmt], depth: usize) -> fmt::Result {
f.write_str("{\n")?;
for s in stmts {
write_stmt(f, s, depth + 1)?;
}
indent(f, depth)?;
f.write_str("}")
}
fn write_stmt(f: &mut fmt::Formatter<'_>, s: &Stmt, depth: usize) -> fmt::Result {
indent(f, depth)?;
match s {
Stmt::Sample { var, addr, dist } => {
writeln!(f, "let {var} <- sample({addr}, {dist});")
}
Stmt::Let { var, value } => writeln!(f, "let {var} = {value};"),
Stmt::Assign { var, value } => writeln!(f, "{var} = {value};"),
Stmt::Observe { addr, dist, value } => {
writeln!(f, "observe({addr}, {dist}, {value});")
}
Stmt::Factor { logw } => writeln!(f, "factor({logw});"),
Stmt::For {
var,
start,
end,
body,
} => {
write!(f, "for {var} in {start}..{end} ")?;
write_block(f, body, depth)?;
f.write_str("\n")
}
Stmt::If { .. } => {
write_if(f, s, depth)?;
f.write_str("\n")
}
Stmt::Break {} => f.write_str("break;\n"),
}
}
fn write_if(f: &mut fmt::Formatter<'_>, s: &Stmt, depth: usize) -> fmt::Result {
let Stmt::If {
cond,
then_branch,
else_branch,
} = s
else {
unreachable!("write_if is only called on `If`")
};
write!(f, "if {cond} ")?;
write_block(f, then_branch, depth)?;
match else_branch.as_slice() {
[] => Ok(()),
[nested @ Stmt::If { .. }] => {
f.write_str(" else ")?;
write_if(f, nested, depth)
}
stmts => {
f.write_str(" else ")?;
write_block(f, stmts, depth)
}
}
}
impl fmt::Display for Addr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("addr!(")?;
write_str_literal(f, &self.name)?;
if let Some(ix) = &self.index {
write!(f, ", {ix}")?;
}
f.write_str(")")
}
}
impl fmt::Display for DistCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.name)?;
write_list(f, &self.args)?;
f.write_str(")")
}
}
fn write_list(f: &mut fmt::Formatter<'_>, items: &[Expr]) -> fmt::Result {
for (i, a) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write_expr(f, a)?;
}
Ok(())
}
fn write_str_literal(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
f.write_str("\"")?;
for c in s.chars() {
match c {
'\\' => f.write_str("\\\\")?,
'"' => f.write_str("\\\"")?,
'\n' => f.write_str("\\n")?,
'\r' => f.write_str("\\r")?,
'\t' => f.write_str("\\t")?,
'\0' => f.write_str("\\0")?,
c => write!(f, "{c}")?,
}
}
f.write_str("\"")
}
fn expr_prec(e: &Expr) -> u8 {
match e {
Expr::F64 { value } if value.is_sign_negative() => 6,
Expr::Int { value } if *value < 0 => 6,
Expr::Neg { .. } | Expr::Not { .. } => 6,
other => other.as_binary().map_or(7, |(op, _, _)| op.precedence()),
}
}
fn write_expr(f: &mut fmt::Formatter<'_>, e: &Expr) -> fmt::Result {
match e {
Expr::F64 { value } => write!(f, "{value:?}"),
Expr::Int { value } => write!(f, "{value}"),
Expr::Bool { value } => write!(f, "{value}"),
Expr::Var { name } => f.write_str(name),
Expr::Array { items } => {
f.write_str("[")?;
write_list(f, items)?;
f.write_str("]")
}
Expr::Index { array, index } => {
write_operand(f, array, 7)?;
write!(f, "[{index}]")
}
Expr::Len { array } => {
write_operand(f, array, 7)?;
f.write_str(".len()")
}
Expr::Neg { arg } => {
if matches!(**arg, Expr::F64 { .. } | Expr::Int { .. }) {
write!(f, "-({arg})")
} else {
f.write_str("-")?;
write_operand(f, arg, 6)
}
}
Expr::Not { arg } => {
f.write_str("!")?;
write_operand(f, arg, 6)
}
Expr::Call { func, args } => {
write!(f, "{func}(")?;
write_list(f, args)?;
f.write_str(")")
}
other => {
let (op, lhs, rhs) = other.as_binary().expect("remaining variants are binary");
let p = op.precedence();
let lhs_min = if p == 3 { p + 1 } else { p };
write_operand(f, lhs, lhs_min)?;
write!(f, " {} ", op.symbol())?;
write_operand(f, rhs, p + 1)
}
}
}
fn write_operand(f: &mut fmt::Formatter<'_>, e: &Expr, min: u8) -> fmt::Result {
if expr_prec(e) >= min {
write_expr(f, e)
} else {
write!(f, "({e})")
}
}