pub mod shapes;
pub mod signatures;
#[derive(Debug, Clone, Copy)]
pub struct BuiltinSignature {
pub name: &'static str,
pub params: &'static [Param],
pub returns: Ty,
pub type_params: &'static [&'static str],
pub has_rest: bool,
pub where_clauses: &'static [(&'static str, &'static str)],
}
#[derive(Debug, Clone, Copy)]
pub struct Param {
pub name: &'static str,
pub ty: Ty,
pub optional: bool,
}
impl Param {
pub const fn new(name: &'static str, ty: Ty) -> Self {
Self {
name,
ty,
optional: false,
}
}
pub const fn optional(name: &'static str, ty: Ty) -> Self {
Self {
name,
ty,
optional: true,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Ty {
Named(&'static str),
Generic(&'static str),
Any,
Optional(&'static Ty),
Apply(&'static str, &'static [Ty]),
Union(&'static [Ty]),
Fn(&'static [Ty], &'static Ty),
Shape(&'static [ShapeFieldDescriptor]),
SchemaOf(&'static str),
Never,
LitInt(i64),
LitString(&'static str),
}
#[derive(Debug, Clone, Copy)]
pub struct ShapeFieldDescriptor {
pub name: &'static str,
pub ty: Ty,
pub optional: bool,
}
impl ShapeFieldDescriptor {
pub const fn new(name: &'static str, ty: Ty) -> Self {
Self {
name,
ty,
optional: false,
}
}
pub const fn optional(name: &'static str, ty: Ty) -> Self {
Self {
name,
ty,
optional: true,
}
}
}
impl Ty {
pub const fn is_any(&self) -> bool {
matches!(self, Ty::Any)
}
}
impl core::fmt::Display for Ty {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Ty::Named(s) | Ty::Generic(s) => f.write_str(s),
Ty::Any => f.write_str("any"),
Ty::Never => f.write_str("never"),
Ty::Optional(inner) => write!(f, "{inner}?"),
Ty::Apply(name, args) => {
f.write_str(name)?;
f.write_str("<")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str(">")
}
Ty::Union(parts) => {
if let [inner, Ty::Named("nil")] = parts {
if !matches!(inner, Ty::Named("nil")) {
return write!(f, "{inner}?");
}
}
if let [Ty::Named("int"), Ty::Named("float")] = parts {
return f.write_str("number");
}
for (i, p) in parts.iter().enumerate() {
if i > 0 {
f.write_str(" | ")?;
}
write!(f, "{p}")?;
}
Ok(())
}
Ty::Fn(params, ret) => {
f.write_str("(")?;
for (i, p) in params.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{p}")?;
}
write!(f, ") -> {ret}")
}
Ty::Shape(fields) => {
f.write_str("{")?;
for (i, fld) in fields.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
let name = fld.name;
let ty = &fld.ty;
write!(f, "{name}: {ty}")?;
if fld.optional {
f.write_str("?")?;
}
}
f.write_str("}")
}
Ty::SchemaOf(t) => write!(f, "Schema<{t}>"),
Ty::LitInt(n) => write!(f, "{n}"),
Ty::LitString(s) => write!(f, "\"{s}\""),
}
}
}
impl BuiltinSignature {
pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
Self {
name,
params,
returns,
type_params: &[],
has_rest: false,
where_clauses: &[],
}
}
pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
Self {
name,
params,
returns,
type_params: &[],
has_rest: true,
where_clauses: &[],
}
}
pub const fn generic(
name: &'static str,
type_params: &'static [&'static str],
params: &'static [Param],
returns: Ty,
) -> Self {
Self {
name,
params,
returns,
type_params,
has_rest: false,
where_clauses: &[],
}
}
pub fn required_params(&self) -> usize {
self.params.iter().filter(|p| !p.optional).count()
}
pub fn is_type_param(&self, name: &str) -> bool {
self.type_params.contains(&name)
}
pub fn is_generic(&self) -> bool {
!self.type_params.is_empty()
}
pub fn type_param_names(&self) -> Vec<String> {
self.type_params.iter().map(|s| (*s).to_string()).collect()
}
pub fn where_clause_strings(&self) -> Vec<(String, String)> {
self.where_clauses
.iter()
.map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
.collect()
}
}
impl core::fmt::Display for BuiltinSignature {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if !self.type_params.is_empty() {
f.write_str("<")?;
for (i, tp) in self.type_params.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(tp)?;
}
if !self.where_clauses.is_empty() {
f.write_str(" where ")?;
for (i, (tp, iface)) in self.where_clauses.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{tp}: {iface}")?;
}
}
f.write_str("> ")?;
}
f.write_str(self.name)?;
f.write_str("(")?;
let last_idx = self.params.len().saturating_sub(1);
for (i, p) in self.params.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
if self.has_rest && i == last_idx {
f.write_str("...")?;
}
f.write_str(p.name)?;
if p.optional {
f.write_str("?")?;
}
let ty = &p.ty;
write!(f, ": {ty}")?;
}
let ret = &self.returns;
write!(f, ") -> {ret}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinMetadata {
pub name: &'static str,
pub return_types: &'static [&'static str],
}
pub const TY_ANY: Ty = Ty::Any;
pub const TY_BOOL: Ty = Ty::Named("bool");
pub const TY_BYTES: Ty = Ty::Named("bytes");
pub const TY_CLOSURE: Ty = Ty::Named("closure");
pub const TY_DECIMAL: Ty = Ty::Named("decimal");
pub const TY_DICT: Ty = Ty::Named("dict");
pub const TY_DURATION: Ty = Ty::Named("duration");
pub const TY_FLOAT: Ty = Ty::Named("float");
pub const TY_INT: Ty = Ty::Named("int");
pub const TY_LIST: Ty = Ty::Named("list");
pub const TY_NEVER: Ty = Ty::Never;
pub const TY_NIL: Ty = Ty::Named("nil");
pub const TY_STRING: Ty = Ty::Named("string");
pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
#[cfg(test)]
mod tests {
use super::*;
const APPLY_ARGS: &[Ty] = &[TY_DICT];
const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
ShapeFieldDescriptor::new("name", TY_STRING),
ShapeFieldDescriptor::optional("age", TY_INT),
];
#[test]
fn ty_display_atomic_and_compound() {
assert_eq!(format!("{TY_INT}"), "int");
assert_eq!(format!("{TY_ANY}"), "any");
assert_eq!(format!("{TY_NEVER}"), "never");
assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
let opt_int = Ty::Optional(&TY_INT);
assert_eq!(format!("{opt_int}"), "int?");
assert_eq!(format!("{TY_NUMBER}"), "number");
let list_dict = Ty::Apply("list", APPLY_ARGS);
assert_eq!(format!("{list_dict}"), "list<dict>");
let lit_int = Ty::LitInt(42);
assert_eq!(format!("{lit_int}"), "42");
let lit_str = Ty::LitString("pass");
assert_eq!(format!("{lit_str}"), "\"pass\"");
let schema_t = Ty::SchemaOf("T");
assert_eq!(format!("{schema_t}"), "Schema<T>");
let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
let shape = Ty::Shape(SHAPE_FIELDS);
assert_eq!(format!("{shape}"), "{name: string, age: int?}");
}
const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
const OPT_PARAMS: &[Param] = &[
Param::new("receipt", TY_DICT),
Param::optional("candidate", TY_ANY),
];
const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
#[test]
fn signature_display_basic() {
let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
}
#[test]
fn signature_display_with_optional_and_rest() {
let sig = BuiltinSignature {
name: "io_println",
params: REST_PARAMS,
returns: TY_NIL,
type_params: &[],
has_rest: true,
where_clauses: &[],
};
assert_eq!(
format!("{sig}"),
"io_println(prefix: string, ...args: any) -> nil"
);
let opt_sig =
BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
assert_eq!(
format!("{opt_sig}"),
"lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
);
}
#[test]
fn signature_display_with_generics_and_where() {
let sig = BuiltinSignature {
name: "schema_parse",
params: GENERIC_PARAMS,
returns: Ty::Generic("T"),
type_params: &["T"],
has_rest: false,
where_clauses: &[("T", "Decode")],
};
assert_eq!(
format!("{sig}"),
"<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
);
}
}