1use sim_kernel::{Cx, Error, Expr, Result, ShapeId, Symbol};
2use sim_shape::{check_shape_on_expr, parse_shape_expr};
3use sim_value::{
4 access::{entry_field, map_entries},
5 build::entry,
6};
7
8use crate::{ContractCard, ContractGap};
9
10impl ContractCard {
11 pub fn as_expr(&self) -> Expr {
13 Expr::Map(vec![
14 entry("kind", Expr::Symbol(contract_card_symbol())),
15 entry("lib", Expr::Symbol(self.lib.clone())),
16 entry("export-kind", Expr::Symbol(self.export_kind.clone())),
17 entry("symbol", Expr::Symbol(self.symbol.clone())),
18 entry("args-shape", option_expr(self.args_shape.clone())),
19 entry("result-shape", option_expr(self.result_shape.clone())),
20 entry(
21 "capabilities",
22 Expr::List(
23 self.capability_symbols
24 .iter()
25 .cloned()
26 .map(Expr::Symbol)
27 .collect(),
28 ),
29 ),
30 entry("card-requires", option_expr(self.card_requires.clone())),
31 entry("summary", Expr::String(self.summary.clone())),
32 entry("example", option_expr(self.example.clone())),
33 entry(
34 "partial",
35 Expr::List(
36 self.partial
37 .iter()
38 .map(|gap| Expr::Symbol(gap.as_symbol()))
39 .collect(),
40 ),
41 ),
42 ])
43 }
44}
45
46impl ContractGap {
47 pub fn as_symbol(&self) -> Symbol {
49 match self {
50 Self::MissingCallableShape => {
51 Symbol::qualified("contract-gap", "missing-callable-shape")
52 }
53 Self::MissingCard => Symbol::qualified("contract-gap", "missing-card"),
54 Self::MissingExample => Symbol::qualified("contract-gap", "missing-example"),
55 Self::SynthesizedExample => Symbol::qualified("contract-gap", "synthesized-example"),
56 }
57 }
58
59 fn from_symbol(symbol: &Symbol) -> Result<Self> {
60 let namespace = symbol.namespace.as_deref();
61 let name = symbol.name.as_ref();
62 match (namespace, name) {
63 (Some("contract-gap"), "missing-callable-shape") => Ok(Self::MissingCallableShape),
64 (Some("contract-gap"), "missing-card") => Ok(Self::MissingCard),
65 (Some("contract-gap"), "missing-example") => Ok(Self::MissingExample),
66 (Some("contract-gap"), "synthesized-example") => Ok(Self::SynthesizedExample),
67 _ => Err(Error::Eval(format!("unknown contract gap {symbol}"))),
68 }
69 }
70}
71
72pub fn contract_card_shape() -> Expr {
77 Expr::List(vec![
78 Expr::Symbol(Symbol::qualified("shape", "table-open")),
79 Expr::List(vec![
80 shape_field("kind", symbol_shape()),
81 shape_field("lib", symbol_shape()),
82 shape_field("export-kind", symbol_shape()),
83 shape_field("symbol", symbol_shape()),
84 shape_field("args-shape", any_shape()),
85 shape_field("result-shape", any_shape()),
86 shape_field(
87 "capabilities",
88 Expr::List(vec![
89 Expr::Symbol(Symbol::qualified("shape", "repeat")),
90 symbol_shape(),
91 ]),
92 ),
93 shape_field("card-requires", any_shape()),
94 shape_field("summary", string_shape()),
95 shape_field("example", any_shape()),
96 shape_field(
97 "partial",
98 Expr::List(vec![
99 Expr::Symbol(Symbol::qualified("shape", "repeat")),
100 symbol_shape(),
101 ]),
102 ),
103 ]),
104 ])
105}
106
107pub fn contract_card_from_expr(cx: &mut Cx, e: &Expr) -> Result<ContractCard> {
109 let shape = parse_shape_expr(&contract_card_shape())?;
110 let checked = check_shape_on_expr(shape.as_ref(), cx, e)?;
111 if !checked.accepted {
112 return Err(Error::WrongShape {
113 expected: ShapeId(0),
114 diagnostics: checked.diagnostics,
115 });
116 }
117
118 let entries = map_entries(e, "contract card")?;
119 let kind = required_symbol(entries, "kind")?;
120 if kind != contract_card_symbol() {
121 return Err(Error::Eval(format!(
122 "contract card kind must be {}, found {kind}",
123 contract_card_symbol()
124 )));
125 }
126
127 Ok(ContractCard {
128 lib: required_symbol(entries, "lib")?,
129 export_kind: required_symbol(entries, "export-kind")?,
130 symbol: required_symbol(entries, "symbol")?,
131 args_shape: optional_expr(entries, "args-shape")?,
132 result_shape: optional_expr(entries, "result-shape")?,
133 capability_symbols: required_symbol_list(entries, "capabilities")?,
134 card_requires: optional_expr(entries, "card-requires")?,
135 summary: required_string(entries, "summary")?,
136 example: optional_expr(entries, "example")?,
137 partial: required_symbol_list(entries, "partial")?
138 .iter()
139 .map(ContractGap::from_symbol)
140 .collect::<Result<Vec<_>>>()?,
141 })
142}
143
144fn option_expr(expr: Option<Expr>) -> Expr {
145 expr.unwrap_or(Expr::Nil)
146}
147
148fn optional_expr(entries: &[(Expr, Expr)], field: &str) -> Result<Option<Expr>> {
149 Ok(match required_expr(entries, field)? {
150 Expr::Nil => None,
151 expr => Some(expr.clone()),
152 })
153}
154
155fn required_expr<'a>(entries: &'a [(Expr, Expr)], field: &str) -> Result<&'a Expr> {
156 entry_field(entries, field)
157 .ok_or_else(|| Error::Eval(format!("contract card is missing field {field}")))
158}
159
160fn required_symbol(entries: &[(Expr, Expr)], field: &str) -> Result<Symbol> {
161 match required_expr(entries, field)? {
162 Expr::Symbol(symbol) => Ok(symbol.clone()),
163 _ => Err(Error::Eval(format!(
164 "contract card field {field} must be a symbol"
165 ))),
166 }
167}
168
169fn required_string(entries: &[(Expr, Expr)], field: &str) -> Result<String> {
170 match required_expr(entries, field)? {
171 Expr::String(text) => Ok(text.clone()),
172 _ => Err(Error::Eval(format!(
173 "contract card field {field} must be a string"
174 ))),
175 }
176}
177
178fn required_symbol_list(entries: &[(Expr, Expr)], field: &str) -> Result<Vec<Symbol>> {
179 match required_expr(entries, field)? {
180 Expr::List(items) => items
181 .iter()
182 .map(|item| match item {
183 Expr::Symbol(symbol) => Ok(symbol.clone()),
184 _ => Err(Error::Eval(format!(
185 "contract card field {field} must contain only symbols"
186 ))),
187 })
188 .collect(),
189 _ => Err(Error::Eval(format!(
190 "contract card field {field} must be a list"
191 ))),
192 }
193}
194
195fn shape_field(field: &str, shape: Expr) -> Expr {
196 Expr::List(vec![Expr::Symbol(Symbol::new(field)), shape])
197}
198
199fn any_shape() -> Expr {
200 Expr::Symbol(Symbol::new("Any"))
201}
202
203fn string_shape() -> Expr {
204 Expr::Symbol(Symbol::new("String"))
205}
206
207fn symbol_shape() -> Expr {
208 Expr::Symbol(Symbol::new("Symbol"))
209}
210
211fn contract_card_symbol() -> Symbol {
212 Symbol::qualified("forge", "contract-card")
213}