use crate::{Ident, Span, TypeExpr};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub mod_decls: Vec<ModDecl>,
pub use_decls: Vec<UseDecl>,
pub records: Vec<RecordDecl>,
pub enums: Vec<EnumDecl>,
pub consts: Vec<ConstDecl>,
pub tools: Vec<ToolDecl>,
pub protocols: Vec<ProtocolDecl>,
pub effect_handlers: Vec<EffectHandlerDecl>,
pub agents: Vec<AgentDecl>,
pub supervisors: Vec<SupervisorDecl>,
pub functions: Vec<FnDecl>,
pub extern_fns: Vec<ExternFnDecl>,
pub tests: Vec<TestDecl>,
pub run_agent: Option<Ident>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ModDecl {
pub is_pub: bool,
pub name: Ident,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UseDecl {
pub is_pub: bool,
pub path: Vec<Ident>,
pub kind: UseKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UseKind {
Simple(Option<Ident>),
Glob,
Group(Vec<(Ident, Option<Ident>)>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordDecl {
pub is_pub: bool,
pub name: Ident,
pub type_params: Vec<Ident>,
pub fields: Vec<RecordField>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordField {
pub name: Ident,
pub ty: TypeExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumVariant {
pub name: Ident,
pub payload: Option<TypeExpr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumDecl {
pub is_pub: bool,
pub name: Ident,
pub type_params: Vec<Ident>,
pub variants: Vec<EnumVariant>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConstDecl {
pub is_pub: bool,
pub name: Ident,
pub ty: TypeExpr,
pub value: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolDecl {
pub is_pub: bool,
pub name: Ident,
pub functions: Vec<ToolFnDecl>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolFnDecl {
pub name: Ident,
pub params: Vec<Param>,
pub return_ty: TypeExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AgentDecl {
pub is_pub: bool,
pub name: Ident,
pub receives: Option<TypeExpr>,
pub follows: Vec<ProtocolRole>,
pub tool_uses: Vec<Ident>,
pub beliefs: Vec<BeliefDecl>,
pub handlers: Vec<HandlerDecl>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BeliefDecl {
pub is_persistent: bool,
pub name: Ident,
pub ty: TypeExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandlerDecl {
pub event: EventKind,
pub body: Block,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventKind {
Waking,
Start,
Message {
param_name: Ident,
param_ty: TypeExpr,
},
Pause,
Resume,
Stop,
Resting,
Error {
param_name: Ident,
},
}
impl fmt::Display for EventKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EventKind::Waking => write!(f, "waking"),
EventKind::Start => write!(f, "start"),
EventKind::Message {
param_name,
param_ty,
} => {
write!(f, "message({param_name}: {param_ty})")
}
EventKind::Pause => write!(f, "pause"),
EventKind::Resume => write!(f, "resume"),
EventKind::Stop => write!(f, "stop"),
EventKind::Resting => write!(f, "resting"),
EventKind::Error { param_name } => {
write!(f, "error({param_name})")
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FnDecl {
pub is_pub: bool,
pub name: Ident,
pub type_params: Vec<Ident>,
pub params: Vec<Param>,
pub return_ty: TypeExpr,
pub is_fallible: bool,
pub body: Block,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExternFnDecl {
pub name: Ident,
pub params: Vec<Param>,
pub return_ty: TypeExpr,
pub is_fallible: bool,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub name: Ident,
pub ty: TypeExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ClosureParam {
pub name: Ident,
pub ty: Option<TypeExpr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TestDecl {
pub name: String,
pub is_serial: bool,
pub body: Block,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SupervisorDecl {
pub is_pub: bool,
pub name: Ident,
pub strategy: SupervisionStrategy,
pub children: Vec<ChildSpec>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SupervisionStrategy {
OneForOne,
OneForAll,
RestForOne,
}
impl fmt::Display for SupervisionStrategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SupervisionStrategy::OneForOne => write!(f, "OneForOne"),
SupervisionStrategy::OneForAll => write!(f, "OneForAll"),
SupervisionStrategy::RestForOne => write!(f, "RestForOne"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum RestartPolicy {
#[default]
Permanent,
Transient,
Temporary,
}
impl fmt::Display for RestartPolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RestartPolicy::Permanent => write!(f, "Permanent"),
RestartPolicy::Transient => write!(f, "Transient"),
RestartPolicy::Temporary => write!(f, "Temporary"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChildSpec {
pub agent_name: Ident,
pub restart: RestartPolicy,
pub beliefs: Vec<FieldInit>,
pub handler_assignments: Vec<HandlerAssignment>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProtocolDecl {
pub is_pub: bool,
pub name: Ident,
pub steps: Vec<ProtocolStep>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProtocolStep {
pub sender: Ident,
pub receiver: Ident,
pub message_type: TypeExpr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProtocolRole {
pub protocol: Ident,
pub role: Ident,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EffectHandlerDecl {
pub is_pub: bool,
pub name: Ident,
pub effect: Ident,
pub config: Vec<HandlerConfig>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandlerConfig {
pub key: Ident,
pub value: Literal,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandlerAssignment {
pub effect: Ident,
pub handler: Ident,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Block {
pub stmts: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Let {
name: Ident,
ty: Option<TypeExpr>,
value: Expr,
span: Span,
},
Assign {
name: Ident,
value: Expr,
span: Span,
},
Return {
value: Option<Expr>,
span: Span,
},
If {
condition: Expr,
then_block: Block,
else_block: Option<ElseBranch>,
span: Span,
},
For {
pattern: Pattern,
iter: Expr,
body: Block,
span: Span,
},
While {
condition: Expr,
body: Block,
span: Span,
},
Loop {
body: Block,
span: Span,
},
Break {
span: Span,
},
SpanBlock {
name: Expr,
body: Block,
span: Span,
},
Checkpoint {
span: Span,
},
Expr {
expr: Expr,
span: Span,
},
LetTuple {
names: Vec<Ident>,
ty: Option<TypeExpr>,
value: Expr,
span: Span,
},
MockDivine {
value: MockValue,
span: Span,
},
MockTool {
tool_name: Ident,
fn_name: Ident,
value: MockValue,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum MockValue {
Value(Expr),
Fail(Expr),
}
impl Stmt {
#[must_use]
pub fn span(&self) -> &Span {
match self {
Stmt::Let { span, .. }
| Stmt::Assign { span, .. }
| Stmt::Return { span, .. }
| Stmt::If { span, .. }
| Stmt::For { span, .. }
| Stmt::While { span, .. }
| Stmt::Loop { span, .. }
| Stmt::Break { span, .. }
| Stmt::SpanBlock { span, .. }
| Stmt::Checkpoint { span, .. }
| Stmt::Expr { span, .. }
| Stmt::LetTuple { span, .. }
| Stmt::MockDivine { span, .. }
| Stmt::MockTool { span, .. } => span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ElseBranch {
Block(Block),
ElseIf(Box<Stmt>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Divine {
template: StringTemplate,
result_ty: Option<TypeExpr>,
span: Span,
},
Summon {
agent: Ident,
fields: Vec<FieldInit>,
span: Span,
},
Await {
handle: Box<Expr>,
timeout: Option<Box<Expr>>,
span: Span,
},
Send {
handle: Box<Expr>,
message: Box<Expr>,
span: Span,
},
Yield {
value: Box<Expr>,
span: Span,
},
Call {
name: Ident,
type_args: Vec<TypeExpr>,
args: Vec<Expr>,
span: Span,
},
Apply {
callee: Box<Expr>,
args: Vec<Expr>,
span: Span,
},
SelfMethodCall {
method: Ident,
args: Vec<Expr>,
span: Span,
},
SelfField {
field: Ident,
span: Span,
},
Binary {
op: BinOp,
left: Box<Expr>,
right: Box<Expr>,
span: Span,
},
Unary {
op: UnaryOp,
operand: Box<Expr>,
span: Span,
},
List {
elements: Vec<Expr>,
span: Span,
},
Literal {
value: Literal,
span: Span,
},
Var {
name: Ident,
span: Span,
},
Paren {
inner: Box<Expr>,
span: Span,
},
StringInterp {
template: StringTemplate,
span: Span,
},
Match {
scrutinee: Box<Expr>,
arms: Vec<MatchArm>,
span: Span,
},
RecordConstruct {
name: Ident,
type_args: Vec<TypeExpr>,
fields: Vec<FieldInit>,
span: Span,
},
FieldAccess {
object: Box<Expr>,
field: Ident,
span: Span,
},
Receive {
span: Span,
},
Try {
expr: Box<Expr>,
span: Span,
},
Catch {
expr: Box<Expr>,
error_bind: Option<Ident>,
recovery: Box<Expr>,
span: Span,
},
Fail {
error: Box<Expr>,
span: Span,
},
Retry {
count: Box<Expr>,
delay: Option<Box<Expr>>,
on_errors: Option<Vec<Expr>>,
body: Box<Expr>,
span: Span,
},
Trace {
message: Box<Expr>,
span: Span,
},
Closure {
params: Vec<ClosureParam>,
body: Box<Expr>,
span: Span,
},
Tuple {
elements: Vec<Expr>,
span: Span,
},
TupleIndex {
tuple: Box<Expr>,
index: usize,
span: Span,
},
Map {
entries: Vec<MapEntry>,
span: Span,
},
VariantConstruct {
enum_name: Ident,
type_args: Vec<TypeExpr>,
variant: Ident,
payload: Option<Box<Expr>>,
span: Span,
},
ToolCall {
tool: Ident,
function: Ident,
args: Vec<Expr>,
span: Span,
},
Reply {
message: Box<Expr>,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct MapEntry {
pub key: Expr,
pub value: Expr,
pub span: Span,
}
impl Expr {
#[must_use]
pub fn span(&self) -> &Span {
match self {
Expr::Divine { span, .. }
| Expr::Summon { span, .. }
| Expr::Await { span, .. }
| Expr::Send { span, .. }
| Expr::Yield { span, .. }
| Expr::Call { span, .. }
| Expr::Apply { span, .. }
| Expr::SelfMethodCall { span, .. }
| Expr::SelfField { span, .. }
| Expr::Binary { span, .. }
| Expr::Unary { span, .. }
| Expr::List { span, .. }
| Expr::Literal { span, .. }
| Expr::Var { span, .. }
| Expr::Paren { span, .. }
| Expr::StringInterp { span, .. }
| Expr::Match { span, .. }
| Expr::RecordConstruct { span, .. }
| Expr::FieldAccess { span, .. }
| Expr::Receive { span, .. }
| Expr::Try { span, .. }
| Expr::Catch { span, .. }
| Expr::Fail { span, .. }
| Expr::Retry { span, .. }
| Expr::Trace { span, .. }
| Expr::Closure { span, .. }
| Expr::Tuple { span, .. }
| Expr::TupleIndex { span, .. }
| Expr::Map { span, .. }
| Expr::VariantConstruct { span, .. }
| Expr::ToolCall { span, .. }
| Expr::Reply { span, .. } => span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldInit {
pub name: Ident,
pub value: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MatchArm {
pub pattern: Pattern,
pub body: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Pattern {
Wildcard {
span: Span,
},
Variant {
enum_name: Option<Ident>,
variant: Ident,
payload: Option<Box<Pattern>>,
span: Span,
},
Literal {
value: Literal,
span: Span,
},
Binding {
name: Ident,
span: Span,
},
Tuple {
elements: Vec<Pattern>,
span: Span,
},
}
impl Pattern {
#[must_use]
pub fn span(&self) -> &Span {
match self {
Pattern::Wildcard { span }
| Pattern::Variant { span, .. }
| Pattern::Literal { span, .. }
| Pattern::Binding { span, .. }
| Pattern::Tuple { span, .. } => span,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Rem,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
And,
Or,
Concat,
}
impl BinOp {
#[must_use]
pub fn precedence(self) -> u8 {
match self {
BinOp::Or => 1,
BinOp::And => 2,
BinOp::Eq | BinOp::Ne => 3,
BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => 4,
BinOp::Concat => 5,
BinOp::Add | BinOp::Sub => 6,
BinOp::Mul | BinOp::Div | BinOp::Rem => 7,
}
}
#[must_use]
pub fn is_left_assoc(self) -> bool {
true
}
}
impl fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BinOp::Add => write!(f, "+"),
BinOp::Sub => write!(f, "-"),
BinOp::Mul => write!(f, "*"),
BinOp::Div => write!(f, "/"),
BinOp::Rem => write!(f, "%"),
BinOp::Eq => write!(f, "=="),
BinOp::Ne => write!(f, "!="),
BinOp::Lt => write!(f, "<"),
BinOp::Gt => write!(f, ">"),
BinOp::Le => write!(f, "<="),
BinOp::Ge => write!(f, ">="),
BinOp::And => write!(f, "&&"),
BinOp::Or => write!(f, "||"),
BinOp::Concat => write!(f, "++"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnaryOp {
Neg,
Not,
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnaryOp::Neg => write!(f, "-"),
UnaryOp::Not => write!(f, "!"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Int(i64),
Float(f64),
Bool(bool),
String(String),
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Literal::Int(n) => write!(f, "{n}"),
Literal::Float(n) => write!(f, "{n}"),
Literal::Bool(b) => write!(f, "{b}"),
Literal::String(s) => write!(f, "\"{s}\""),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StringTemplate {
pub parts: Vec<StringPart>,
pub span: Span,
}
impl StringTemplate {
#[must_use]
pub fn literal(s: String, span: Span) -> Self {
Self {
parts: vec![StringPart::Literal(s)],
span,
}
}
#[must_use]
pub fn has_interpolations(&self) -> bool {
self.parts
.iter()
.any(|p| matches!(p, StringPart::Interpolation(_)))
}
pub fn interpolations(&self) -> impl Iterator<Item = &Expr> {
self.parts.iter().filter_map(|p| match p {
StringPart::Interpolation(expr) => Some(expr.as_ref()),
StringPart::Literal(_) => None,
})
}
}
impl fmt::Display for StringTemplate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"")?;
for part in &self.parts {
match part {
StringPart::Literal(s) => write!(f, "{s}")?,
StringPart::Interpolation(_) => write!(f, "{{...}}")?,
}
}
write!(f, "\"")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StringPart {
Literal(String),
Interpolation(Box<Expr>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binop_precedence() {
assert!(BinOp::Mul.precedence() > BinOp::Add.precedence());
assert!(BinOp::Add.precedence() > BinOp::Lt.precedence());
assert!(BinOp::Lt.precedence() > BinOp::And.precedence());
assert!(BinOp::And.precedence() > BinOp::Or.precedence());
}
#[test]
fn binop_display() {
assert_eq!(format!("{}", BinOp::Add), "+");
assert_eq!(format!("{}", BinOp::Eq), "==");
assert_eq!(format!("{}", BinOp::Concat), "++");
assert_eq!(format!("{}", BinOp::And), "&&");
}
#[test]
fn unaryop_display() {
assert_eq!(format!("{}", UnaryOp::Neg), "-");
assert_eq!(format!("{}", UnaryOp::Not), "!");
}
#[test]
fn literal_display() {
assert_eq!(format!("{}", Literal::Int(42)), "42");
assert_eq!(format!("{}", Literal::Float(3.14)), "3.14");
assert_eq!(format!("{}", Literal::Bool(true)), "true");
assert_eq!(format!("{}", Literal::String("hello".into())), "\"hello\"");
}
#[test]
fn event_kind_display() {
assert_eq!(format!("{}", EventKind::Start), "start");
assert_eq!(format!("{}", EventKind::Stop), "stop");
let msg = EventKind::Message {
param_name: Ident::dummy("msg"),
param_ty: TypeExpr::String,
};
assert_eq!(format!("{msg}"), "message(msg: String)");
}
#[test]
fn string_template_literal() {
let template = StringTemplate::literal("hello".into(), Span::dummy());
assert!(!template.has_interpolations());
assert_eq!(format!("{template}"), "\"hello\"");
}
#[test]
fn string_template_with_interpolation() {
let template = StringTemplate {
parts: vec![
StringPart::Literal("Hello, ".into()),
StringPart::Interpolation(Box::new(Expr::Var {
name: Ident::dummy("name"),
span: Span::dummy(),
})),
StringPart::Literal("!".into()),
],
span: Span::dummy(),
};
assert!(template.has_interpolations());
assert_eq!(format!("{template}"), "\"Hello, {...}!\"");
let interps: Vec<_> = template.interpolations().collect();
assert_eq!(interps.len(), 1);
if let Expr::Var { name, .. } = interps[0] {
assert_eq!(name.name, "name");
} else {
panic!("Expected Var expression");
}
}
#[test]
fn expr_span() {
let span = Span::dummy();
let expr = Expr::Literal {
value: Literal::Int(42),
span: span.clone(),
};
assert_eq!(expr.span(), &span);
}
#[test]
fn stmt_span() {
let span = Span::dummy();
let stmt = Stmt::Return {
value: None,
span: span.clone(),
};
assert_eq!(stmt.span(), &span);
}
}