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 {
50 keyword: &'static str,
51 message: String,
52 },
53}
54
55pub fn compile<T: TataraDomain>(src: &str) -> Result<T, DomainError> {
73 let value = run(src)?.value;
74 compile_value(&value)
75}
76
77pub fn compile_value<T: TataraDomain>(value: &Value) -> Result<T, DomainError> {
83 let form = domain_form(T::KEYWORD, value)?;
84 T::compile_from_sexp(&form).map_err(|e| DomainError::Refused {
85 keyword: T::KEYWORD,
86 message: e.to_string(),
87 })
88}
89
90pub fn domain_form(keyword: &str, value: &Value) -> Result<Sexp, DomainError> {
100 let Value::Map(map) = value else {
101 return Err(DomainError::NotAMap {
102 found: kind_of(value),
103 });
104 };
105 let mut form = vec![Sexp::Atom(Atom::Symbol(keyword.to_string()))];
106 form.extend(keyword_args(map)?);
107 Ok(Sexp::List(form))
108}
109
110fn keyword_args(map: &tatara_lisp_eval::Map) -> Result<Vec<Sexp>, DomainError> {
112 let mut pairs: Vec<(String, &Value)> = map
113 .iter()
114 .map(|(k, v)| label_of(k).map(|label| (label, v)))
115 .collect::<Result<_, _>>()?;
116 pairs.sort_by(|a, b| a.0.cmp(&b.0));
117
118 let mut args = Vec::with_capacity(pairs.len() * 2);
119 for (label, v) in pairs {
120 let field = kebab(&label);
121 let arg = to_sexp(&field, v)?;
122 args.push(Sexp::Atom(Atom::Keyword(field)));
123 args.push(arg);
124 }
125 Ok(args)
126}
127
128fn label_of(key: &MapKey) -> Result<String, DomainError> {
131 match key {
132 MapKey::Keyword(k) | MapKey::Symbol(k) => Ok(k.to_string()),
133 MapKey::Str(_) => Err(DomainError::KeyNotALabel { found: "a string" }),
134 MapKey::Int(_) => Err(DomainError::KeyNotALabel {
135 found: "an integer",
136 }),
137 MapKey::Float(_) => Err(DomainError::KeyNotALabel { found: "a float" }),
138 MapKey::Bool(_) => Err(DomainError::KeyNotALabel { found: "a boolean" }),
139 MapKey::Nil => Err(DomainError::KeyNotALabel { found: "nil" }),
140 }
141}
142
143fn kebab(label: &str) -> String {
147 label.replace('_', "-")
148}
149
150fn to_sexp(field: &str, value: &Value) -> Result<Sexp, DomainError> {
152 Ok(match value {
153 Value::Nil => Sexp::Nil,
154 Value::Bool(b) => Sexp::Atom(Atom::Bool(*b)),
155 Value::Int(i) => Sexp::Atom(Atom::Int(*i)),
156 Value::Float(f) => Sexp::Atom(Atom::Float(*f)),
157 Value::Str(s) => Sexp::Atom(Atom::Str(s.to_string())),
158 Value::Symbol(s) => Sexp::Atom(Atom::Symbol(s.to_string())),
159 Value::Keyword(k) => Sexp::Atom(Atom::Keyword(kebab(k))),
160 Value::List(items) => Sexp::List(
161 items
162 .iter()
163 .map(|v| to_sexp(field, v))
164 .collect::<Result<_, _>>()?,
165 ),
166 Value::Map(map) => Sexp::List(keyword_args(map)?),
167 Value::Sexp(s, _) => s.clone(),
168 other => {
169 return Err(DomainError::Unrepresentable {
170 field: field.to_string(),
171 found: kind_of(other),
172 })
173 }
174 })
175}
176
177fn kind_of(value: &Value) -> &'static str {
178 match value {
179 Value::Nil => "nil",
180 Value::Bool(_) => "a boolean",
181 Value::Int(_) => "an integer",
182 Value::Float(_) => "a float",
183 Value::Str(_) => "a string",
184 Value::Symbol(_) => "a symbol",
185 Value::Keyword(_) => "a keyword",
186 Value::List(_) => "a list",
187 Value::Map(_) => "a map",
188 Value::Closure(_) | Value::NativeFn(_) => "a function",
189 Value::Promise(_) => "a promise",
190 Value::Error(_) => "an error object",
191 Value::Sexp(..) => "a quoted form",
192 _ => "a host value",
193 }
194}