blue_lang_runtime/
domain.rs1use tatara_lisp::{Atom, Sexp, TataraDomain};
26use tatara_lisp_eval::{MapKey, Value};
27
28use crate::pipeline::{run, RunError};
29
30#[derive(Debug, thiserror::Error)]
32pub enum DomainError {
33 #[error("the blue source did not run: {0}")]
35 Run(#[from] RunError),
36 #[error("a domain is authored as a map (`{{key: value}}`), but the value was {found}")]
38 NotAMap { found: &'static str },
39 #[error("domain keys are labels (`name:`), but a key was {found}")]
41 KeyNotALabel { found: &'static str },
42 #[error("the field `{field}` holds {found}, which has no form a domain can read")]
44 Unrepresentable { field: String, found: &'static str },
45 #[error("the domain `{keyword}` refused the form: {message}")]
49 Refused { keyword: &'static str, message: String },
50}
51
52pub fn compile<T: TataraDomain>(src: &str) -> Result<T, DomainError> {
70 let value = run(src)?.value;
71 compile_value(&value)
72}
73
74pub fn compile_value<T: TataraDomain>(value: &Value) -> Result<T, DomainError> {
80 let form = domain_form(T::KEYWORD, value)?;
81 T::compile_from_sexp(&form).map_err(|e| DomainError::Refused {
82 keyword: T::KEYWORD,
83 message: e.to_string(),
84 })
85}
86
87pub fn domain_form(keyword: &str, value: &Value) -> Result<Sexp, DomainError> {
97 let Value::Map(map) = value else {
98 return Err(DomainError::NotAMap { found: kind_of(value) });
99 };
100 let mut form = vec![Sexp::Atom(Atom::Symbol(keyword.to_string()))];
101 form.extend(keyword_args(map)?);
102 Ok(Sexp::List(form))
103}
104
105fn keyword_args(
107 map: &std::collections::HashMap<MapKey, Value>,
108) -> Result<Vec<Sexp>, DomainError> {
109 let mut pairs: Vec<(String, &Value)> = map
110 .iter()
111 .map(|(k, v)| label_of(k).map(|label| (label, v)))
112 .collect::<Result<_, _>>()?;
113 pairs.sort_by(|a, b| a.0.cmp(&b.0));
114
115 let mut args = Vec::with_capacity(pairs.len() * 2);
116 for (label, v) in pairs {
117 let field = kebab(&label);
118 let arg = to_sexp(&field, v)?;
119 args.push(Sexp::Atom(Atom::Keyword(field)));
120 args.push(arg);
121 }
122 Ok(args)
123}
124
125fn label_of(key: &MapKey) -> Result<String, DomainError> {
128 match key {
129 MapKey::Keyword(k) | MapKey::Symbol(k) => Ok(k.to_string()),
130 MapKey::Str(_) => Err(DomainError::KeyNotALabel { found: "a string" }),
131 MapKey::Int(_) => Err(DomainError::KeyNotALabel { found: "an integer" }),
132 MapKey::Float(_) => Err(DomainError::KeyNotALabel { found: "a float" }),
133 MapKey::Bool(_) => Err(DomainError::KeyNotALabel { found: "a boolean" }),
134 MapKey::Nil => Err(DomainError::KeyNotALabel { found: "nil" }),
135 }
136}
137
138fn kebab(label: &str) -> String {
142 label.replace('_', "-")
143}
144
145fn to_sexp(field: &str, value: &Value) -> Result<Sexp, DomainError> {
147 Ok(match value {
148 Value::Nil => Sexp::Nil,
149 Value::Bool(b) => Sexp::Atom(Atom::Bool(*b)),
150 Value::Int(i) => Sexp::Atom(Atom::Int(*i)),
151 Value::Float(f) => Sexp::Atom(Atom::Float(*f)),
152 Value::Str(s) => Sexp::Atom(Atom::Str(s.to_string())),
153 Value::Symbol(s) => Sexp::Atom(Atom::Symbol(s.to_string())),
154 Value::Keyword(k) => Sexp::Atom(Atom::Keyword(kebab(k))),
155 Value::List(items) => Sexp::List(
156 items
157 .iter()
158 .map(|v| to_sexp(field, v))
159 .collect::<Result<_, _>>()?,
160 ),
161 Value::Map(map) => Sexp::List(keyword_args(map)?),
162 Value::Sexp(s, _) => s.clone(),
163 other => {
164 return Err(DomainError::Unrepresentable {
165 field: field.to_string(),
166 found: kind_of(other),
167 })
168 }
169 })
170}
171
172fn kind_of(value: &Value) -> &'static str {
173 match value {
174 Value::Nil => "nil",
175 Value::Bool(_) => "a boolean",
176 Value::Int(_) => "an integer",
177 Value::Float(_) => "a float",
178 Value::Str(_) => "a string",
179 Value::Symbol(_) => "a symbol",
180 Value::Keyword(_) => "a keyword",
181 Value::List(_) => "a list",
182 Value::Map(_) => "a map",
183 Value::Closure(_) | Value::NativeFn(_) => "a function",
184 Value::Promise(_) => "a promise",
185 Value::Error(_) => "an error object",
186 Value::Sexp(..) => "a quoted form",
187 _ => "a host value",
188 }
189}