use serde::{Deserialize, Serialize};
pub mod source_map;
pub use source_map::SourceMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const UNKNOWN: Span = Span { start: 0, end: 0 };
pub fn merge(self, other: Span) -> Span {
Span {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Spanned<T> {
pub node: T,
pub span: Span,
}
#[allow(dead_code)] impl<T> Spanned<T> {
pub fn new(node: T, span: Span) -> Self {
Spanned { node, span }
}
pub fn unknown(node: T) -> Self {
Spanned {
node,
span: Span::UNKNOWN,
}
}
}
impl<T> std::ops::Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &T {
&self.node
}
}
impl<T: Serialize> Serialize for Spanned<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.node.serialize(serializer)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Spanned<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
T::deserialize(deserializer).map(|node| Spanned {
node,
span: Span::UNKNOWN,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Type {
Number, Text, Bool, Any, Optional(Box<Type>), List(Box<Type>), Map(Box<Type>, Box<Type>), Result(Box<Type>, Box<Type>), Sum(Vec<String>), Fn(Vec<Type>, Box<Type>), Named(String), }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Param {
pub name: String,
pub ty: Type,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Decl {
Function {
name: String,
params: Vec<Param>,
return_type: Type,
body: Vec<Spanned<Stmt>>,
#[serde(skip)]
span: Span,
},
TypeDef {
name: String,
fields: Vec<Param>,
#[serde(skip)]
span: Span,
},
Tool {
name: String,
description: String,
params: Vec<Param>,
return_type: Type,
timeout: Option<f64>,
retry: Option<f64>,
#[serde(skip)]
span: Span,
},
Alias {
name: String,
target: Type,
#[serde(skip)]
span: Span,
},
Use {
path: String,
only: Option<Vec<String>>,
#[serde(skip)]
span: Span,
},
Error {
#[serde(skip)]
span: Span,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Stmt {
Let { name: String, value: Expr },
Guard {
condition: Expr,
negated: bool,
body: Vec<Spanned<Stmt>>,
else_body: Option<Vec<Spanned<Stmt>>>,
#[serde(default)]
braceless: bool,
},
Match {
subject: Option<Expr>,
arms: Vec<MatchArm>,
},
ForEach {
binding: String,
collection: Expr,
body: Vec<Spanned<Stmt>>,
},
ForRange {
binding: String,
start: Expr,
end: Expr,
body: Vec<Spanned<Stmt>>,
},
While {
condition: Expr,
body: Vec<Spanned<Stmt>>,
},
Return(Expr),
Break(Option<Expr>),
Continue,
Destructure { bindings: Vec<String>, value: Expr },
Expr(Expr),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MatchArm {
pub pattern: Pattern,
pub body: Vec<Spanned<Stmt>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Pattern {
Err(String),
Ok(String),
Literal(Literal),
Wildcard,
TypeIs { ty: Type, binding: String },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expr {
Literal(Literal),
Ref(String),
Field {
object: Box<Expr>,
field: String,
safe: bool,
},
Index {
object: Box<Expr>,
index: usize,
safe: bool,
},
Call {
function: String,
args: Vec<Expr>,
#[serde(default)]
unwrap: bool,
},
BinOp {
op: BinOp,
left: Box<Expr>,
right: Box<Expr>,
},
UnaryOp {
op: UnaryOp,
operand: Box<Expr>,
},
Ok(Box<Expr>),
Err(Box<Expr>),
List(Vec<Expr>),
Record {
type_name: String,
fields: Vec<(String, Expr)>,
},
Match {
subject: Option<Box<Expr>>,
arms: Vec<MatchArm>,
},
NilCoalesce {
value: Box<Expr>,
default: Box<Expr>,
},
With {
object: Box<Expr>,
updates: Vec<(String, Expr)>,
},
Ternary {
condition: Box<Expr>,
then_expr: Box<Expr>,
else_expr: Box<Expr>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Literal {
Number(f64),
Text(String),
Bool(bool),
Nil,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BinOp {
Add,
Subtract,
Multiply,
Divide,
Equals,
NotEquals,
GreaterThan,
LessThan,
GreaterOrEqual,
LessOrEqual,
And,
Or,
Append,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum UnaryOp {
Not,
Negate,
}
fn serialize_decls<S: serde::Serializer>(decls: &[Decl], s: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeSeq;
let mut seq = s.serialize_seq(None)?;
for d in decls
.iter()
.filter(|d| !matches!(d, Decl::Error { .. } | Decl::Use { .. }))
{
seq.serialize_element(d)?;
}
seq.end()
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Program {
#[serde(serialize_with = "serialize_decls")]
pub declarations: Vec<Decl>,
#[serde(skip)]
pub source: Option<String>,
}
const BUILTIN_ALIASES: &[(&str, &str)] = &[
("floor", "flr"),
("ceil", "cel"),
("round", "rou"),
("random", "rnd"),
("string", "str"),
("number", "num"),
("length", "len"),
("head", "hd"),
("tail", "tl"),
("reverse", "rev"),
("sort", "srt"),
("slice", "slc"),
("unique", "unq"),
("filter", "flt"),
("fold", "fld"),
("flatten", "flat"),
("concat", "cat"),
("contains", "has"),
("group", "grp"),
("average", "avg"),
("print", "prnt"),
("trim", "trm"),
("split", "spl"),
("format", "fmt"),
("regex", "rgx"),
("regex_all", "rgxall"),
("regex_sub", "rgxsub"),
("read", "rd"),
("readlines", "rdl"),
("readbuf", "rdb"),
("write", "wr"),
("writelines", "wrl"),
];
pub fn resolve_alias(name: &str) -> Option<&'static str> {
BUILTIN_ALIASES
.iter()
.find(|(long, _)| *long == name)
.map(|(_, short)| *short)
}
pub fn all_builtin_aliases() -> impl Iterator<Item = (&'static str, &'static str)> {
BUILTIN_ALIASES.iter().copied()
}
pub fn resolve_aliases(program: &mut Program) {
for decl in &mut program.declarations {
if let Decl::Function { body, .. } = decl {
for stmt in body {
resolve_aliases_stmt(&mut stmt.node);
}
}
}
}
fn resolve_aliases_stmt(stmt: &mut Stmt) {
match stmt {
Stmt::Expr(expr) | Stmt::Let { value: expr, .. } => resolve_aliases_expr(expr),
Stmt::Guard {
condition,
body,
else_body,
..
} => {
resolve_aliases_expr(condition);
for s in body {
resolve_aliases_stmt(&mut s.node);
}
if let Some(eb) = else_body {
for s in eb {
resolve_aliases_stmt(&mut s.node);
}
}
}
Stmt::Match { subject, arms } => {
if let Some(expr) = subject {
resolve_aliases_expr(expr);
}
for arm in arms {
for s in &mut arm.body {
resolve_aliases_stmt(&mut s.node);
}
}
}
Stmt::ForEach {
collection, body, ..
} => {
resolve_aliases_expr(collection);
for s in body {
resolve_aliases_stmt(&mut s.node);
}
}
Stmt::ForRange {
start, end, body, ..
} => {
resolve_aliases_expr(start);
resolve_aliases_expr(end);
for s in body {
resolve_aliases_stmt(&mut s.node);
}
}
Stmt::While { condition, body } => {
resolve_aliases_expr(condition);
for s in body {
resolve_aliases_stmt(&mut s.node);
}
}
Stmt::Return(expr) => resolve_aliases_expr(expr),
Stmt::Destructure { value, .. } => resolve_aliases_expr(value),
Stmt::Break(Some(expr)) => resolve_aliases_expr(expr),
Stmt::Break(None) | Stmt::Continue => {}
}
}
fn resolve_aliases_expr(expr: &mut Expr) {
match expr {
Expr::Call { function, args, .. } => {
if let Some(canonical) = resolve_alias(function) {
*function = canonical.to_string();
}
for arg in args {
resolve_aliases_expr(arg);
}
}
Expr::BinOp { left, right, .. } => {
resolve_aliases_expr(left);
resolve_aliases_expr(right);
}
Expr::UnaryOp { operand, .. } => resolve_aliases_expr(operand),
Expr::Ok(inner) | Expr::Err(inner) => resolve_aliases_expr(inner),
Expr::NilCoalesce { value, default } => {
resolve_aliases_expr(value);
resolve_aliases_expr(default);
}
Expr::List(items) => {
for item in items {
resolve_aliases_expr(item);
}
}
Expr::Record { fields, .. } => {
for (_, val) in fields {
resolve_aliases_expr(val);
}
}
Expr::Match { subject, arms } => {
if let Some(s) = subject {
resolve_aliases_expr(s);
}
for arm in arms {
for s in &mut arm.body {
resolve_aliases_stmt(&mut s.node);
}
}
}
Expr::With { object, updates } => {
resolve_aliases_expr(object);
for (_, val) in updates {
resolve_aliases_expr(val);
}
}
Expr::Ternary {
condition,
then_expr,
else_expr,
} => {
resolve_aliases_expr(condition);
resolve_aliases_expr(then_expr);
resolve_aliases_expr(else_expr);
}
Expr::Literal(_) | Expr::Ref(_) | Expr::Field { .. } | Expr::Index { .. } => {}
}
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
mod tests {
use super::*;
#[test]
fn span_unknown_is_zero() {
assert_eq!(Span::UNKNOWN, Span { start: 0, end: 0 });
}
#[test]
fn span_merge_takes_extremes() {
let a = Span { start: 5, end: 10 };
let b = Span { start: 2, end: 15 };
let merged = a.merge(b);
assert_eq!(merged, Span { start: 2, end: 15 });
}
#[test]
fn span_merge_same() {
let a = Span { start: 3, end: 7 };
assert_eq!(a.merge(a), a);
}
#[test]
fn span_merge_non_overlapping() {
let a = Span { start: 0, end: 5 };
let b = Span { start: 10, end: 20 };
assert_eq!(a.merge(b), Span { start: 0, end: 20 });
}
#[test]
fn span_default_is_zero() {
let s = Span::default();
assert_eq!(s, Span { start: 0, end: 0 });
}
#[test]
fn spanned_deref() {
let s = Spanned::new(42, Span { start: 0, end: 2 });
assert_eq!(*s, 42);
}
#[test]
fn spanned_unknown() {
let s = Spanned::unknown("hello");
assert_eq!(s.span, Span::UNKNOWN);
assert_eq!(*s, "hello");
}
#[test]
fn spanned_serialize_transparent() {
let s = Spanned::new(42i32, Span { start: 5, end: 10 });
let json = serde_json::to_string(&s).unwrap();
assert_eq!(json, "42");
}
#[test]
fn spanned_deserialize_transparent() {
let s: Spanned<i32> = serde_json::from_str("42").unwrap();
assert_eq!(s.node, 42);
assert_eq!(s.span, Span::UNKNOWN);
}
#[test]
fn spanned_serialize_complex() {
let expr = Spanned::new(
Expr::Literal(Literal::Number(3.14)),
Span { start: 0, end: 4 },
);
let json = serde_json::to_string(&expr).unwrap();
assert!(json.contains("Number"));
assert!(!json.contains("span"));
}
#[test]
fn decl_span_not_serialized() {
let decl = Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Literal(
Literal::Number(1.0),
)))],
span: Span { start: 0, end: 10 },
};
let json = serde_json::to_string(&decl).unwrap();
assert!(!json.contains("span"));
}
#[test]
fn program_source_not_serialized() {
let prog = Program {
declarations: vec![],
source: Some("f x:n>n;x".to_string()),
};
let json = serde_json::to_string(&prog).unwrap();
assert!(!json.contains("source"));
assert!(!json.contains("f x:n>n;x"));
}
#[test]
fn resolve_aliases_while_stmt() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::While {
condition: Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
},
body: vec![Spanned::unknown(Stmt::Expr(Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("y".to_string())],
unwrap: false,
}))],
})],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::While {
condition,
body: wbody,
} = &body[0].node
else {
panic!("expected While")
};
let Expr::Call { function, .. } = condition else {
panic!("expected call")
};
assert_eq!(function, "len");
let Stmt::Expr(Expr::Call { function: f2, .. }) = &wbody[0].node else {
panic!("expected call")
};
assert_eq!(f2, "len");
}
#[test]
fn resolve_aliases_return_stmt() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Return(Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
}))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Return(Expr::Call { function, .. }) = &body[0].node else {
panic!("expected Return(Call)")
};
assert_eq!(function, "len");
}
#[test]
fn resolve_aliases_destructure_stmt() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Destructure {
bindings: vec!["a".to_string(), "b".to_string()],
value: Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
},
})],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Destructure {
value: Expr::Call { function, .. },
..
} = &body[0].node
else {
panic!("expected Destructure")
};
assert_eq!(function, "len");
}
#[test]
fn resolve_aliases_break_with_value() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Break(Some(Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
})))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Break(Some(Expr::Call { function, .. })) = &body[0].node else {
panic!("expected Break(Some(Call))")
};
assert_eq!(function, "len");
}
#[test]
fn resolve_aliases_break_none_and_continue() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![
Spanned::unknown(Stmt::Break(None)),
Spanned::unknown(Stmt::Continue),
],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
assert!(matches!(&prog.declarations[0], Decl::Function { body, .. } if body.len() == 2));
}
#[test]
fn resolve_aliases_nil_coalesce_expr() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::NilCoalesce {
value: Box::new(Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
}),
default: Box::new(Expr::Call {
function: "reverse".to_string(),
args: vec![Expr::Ref("y".to_string())],
unwrap: false,
}),
}))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Expr(Expr::NilCoalesce { value, default }) = &body[0].node else {
panic!("expected NilCoalesce")
};
let Expr::Call { function, .. } = value.as_ref() else {
panic!("expected call")
};
assert_eq!(function, "len");
let Expr::Call { function: f2, .. } = default.as_ref() else {
panic!("expected call")
};
assert_eq!(f2, "rev");
}
#[test]
fn resolve_aliases_record_expr() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Record {
type_name: "point".to_string(),
fields: vec![(
"x".to_string(),
Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("a".to_string())],
unwrap: false,
},
)],
}))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Expr(Expr::Record { fields, .. }) = &body[0].node else {
panic!("expected Record")
};
let Expr::Call { function, .. } = &fields[0].1 else {
panic!("expected call")
};
assert_eq!(function, "len");
}
#[test]
fn resolve_aliases_match_expr() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Match {
subject: Some(Box::new(Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
})),
arms: vec![MatchArm {
pattern: Pattern::Wildcard,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Call {
function: "reverse".to_string(),
args: vec![Expr::Ref("y".to_string())],
unwrap: false,
}))],
}],
}))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Expr(Expr::Match { subject, arms }) = &body[0].node else {
panic!("expected Match")
};
let Some(s) = subject else {
panic!("expected subject")
};
let Expr::Call { function, .. } = s.as_ref() else {
panic!("expected call")
};
assert_eq!(function, "len");
let Stmt::Expr(Expr::Call { function: f2, .. }) = &arms[0].body[0].node else {
panic!("expected call")
};
assert_eq!(f2, "rev");
}
#[test]
fn resolve_aliases_with_expr() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::With {
object: Box::new(Expr::Call {
function: "length".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
}),
updates: vec![(
"a".to_string(),
Expr::Call {
function: "reverse".to_string(),
args: vec![Expr::Ref("y".to_string())],
unwrap: false,
},
)],
}))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
let Stmt::Expr(Expr::With { object, updates }) = &body[0].node else {
panic!("expected With")
};
let Expr::Call { function, .. } = object.as_ref() else {
panic!("expected call")
};
assert_eq!(function, "len");
let Expr::Call { function: f2, .. } = &updates[0].1 else {
panic!("expected call")
};
assert_eq!(f2, "rev");
}
#[test]
fn program_json_round_trip() {
let prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![Param {
name: "x".to_string(),
ty: Type::Number,
}],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Ref("x".to_string())))],
span: Span { start: 0, end: 13 },
}],
source: Some("f x:n>n;x".to_string()),
};
let json = serde_json::to_string_pretty(&prog).unwrap();
let deserialized: Program = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.declarations.len(), 1);
assert!(deserialized.source.is_none());
}
#[test]
fn resolve_aliases_stmt_match_no_subject() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Match {
subject: None,
arms: vec![MatchArm {
pattern: Pattern::Wildcard,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Call {
function: "len".to_string(),
args: vec![Expr::Ref("x".to_string())],
unwrap: false,
}))],
}],
})],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
assert!(
matches!(&body[0].node, Stmt::Match { subject: None, arms } if arms.len() == 1),
"expected Match{{None}} after resolve_aliases"
);
}
#[test]
fn resolve_aliases_expr_match_no_subject() {
let mut prog = Program {
declarations: vec![Decl::Function {
name: "f".to_string(),
params: vec![],
return_type: Type::Number,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Match {
subject: None,
arms: vec![MatchArm {
pattern: Pattern::Wildcard,
body: vec![Spanned::unknown(Stmt::Expr(Expr::Call {
function: "len".to_string(),
args: vec![Expr::Ref("y".to_string())],
unwrap: false,
}))],
}],
}))],
span: Span::UNKNOWN,
}],
source: None,
};
resolve_aliases(&mut prog);
let Decl::Function { body, .. } = &prog.declarations[0] else {
panic!()
};
assert!(
matches!(&body[0].node, Stmt::Expr(Expr::Match { subject: None, arms }) if arms.len() == 1),
"expected Expr::Match{{None}} after resolve_aliases"
);
}
}