1mod common;
4mod fields;
5mod util;
6
7use sim_kernel::{Cx, Expr, Result, Symbol, Value};
8
9use crate::{
10 Budget, BudgetExhausted, Cell, Edge, EdgeId, Graph, Node, Port, PortMode, Scheduler,
11 SchedulerMode, model::TOPOLOGY_API,
12};
13
14use self::{common::*, fields::Fields, util::*};
15
16pub fn parse_graph(cx: &mut Cx, value: Value) -> Result<Graph> {
18 let expr = value.object().as_expr(cx)?;
19 parse_graph_expr(&expr)
20}
21
22pub fn graph_from_value(cx: &mut Cx, value: Value) -> Result<Graph> {
24 parse_graph(cx, value)
25}
26
27pub(crate) fn parse_graph_expr(expr: &Expr) -> Result<Graph> {
28 match data_expr(expr) {
29 Expr::Map(entries) => parse_graph_fields(Fields::from_map(entries, "graph")?),
30 Expr::List(items) | Expr::Vector(items) => parse_graph_list(items),
31 other => Err(parse_error(
32 "graph",
33 format!(
34 "expected graph map or keyword list, found {}",
35 expr_kind(other)
36 ),
37 )),
38 }
39}
40
41fn parse_graph_list(items: &[Expr]) -> Result<Graph> {
42 let body = match items.first().map(data_expr) {
43 Some(Expr::Symbol(symbol)) if symbol.as_qualified_str() == "topology/graph" => &items[1..],
44 _ => items,
45 };
46 parse_graph_fields(Fields::from_keywords(body, "graph")?)
47}
48
49fn parse_graph_fields(fields: Fields<'_>) -> Result<Graph> {
50 if let Some(kind) = fields.get("kind") {
51 expect_named(kind, "topology", "graph.kind")?;
52 }
53
54 let name = parse_symbol(fields.required("name", "graph.name")?, "graph.name")?;
55 let mut graph = Graph::new(name);
56
57 if let Some(version) = fields.get("version") {
58 graph.version = parse_string(version, "graph.version")?;
59 }
60 if let Some(api) = fields.get("api") {
61 let api = parse_string(api, "graph.api")?;
62 if api != TOPOLOGY_API {
63 return Err(parse_error(
64 "graph.api",
65 format!("expected {TOPOLOGY_API}, found {api}"),
66 ));
67 }
68 graph.api = api;
69 }
70 if let Some(input) = fields.get("input") {
71 graph.input = parse_optional_expr(input);
72 }
73 if let Some(output) = fields.get("output") {
74 graph.output = parse_optional_expr(output);
75 }
76 if let Some(nodes) = fields.get("nodes") {
77 graph.nodes = parse_nodes(nodes, "graph.nodes")?;
78 }
79 if let Some(edges) = fields.get("edges") {
80 graph.edges = parse_edges(edges, "graph.edges")?;
81 }
82 if let Some(cells) = fields.get("cells") {
83 graph.cells = parse_cells(cells, "graph.cells")?;
84 }
85 if let Some(scheduler) = fields.get("scheduler") {
86 graph.scheduler = parse_scheduler(scheduler, "graph.scheduler")?;
87 }
88 if let Some(budget) = fields.get("budget") {
89 graph.budget = parse_budget(budget, "graph.budget")?;
90 }
91 if let Some(capabilities) = fields.get("capabilities") {
92 graph.capabilities = parse_symbol_list(capabilities, "graph.capabilities")?;
93 }
94 if let Some(metadata) = fields.get("metadata") {
95 graph.metadata = parse_symbol_expr_map(metadata, "graph.metadata")?;
96 }
97 if let Some(tests) = fields.get("tests") {
98 graph.tests = parse_tests(tests, "graph.tests")?;
99 }
100
101 fields.reject_unknown(&[
102 "kind",
103 "name",
104 "version",
105 "api",
106 "input",
107 "output",
108 "nodes",
109 "edges",
110 "cells",
111 "scheduler",
112 "budget",
113 "capabilities",
114 "metadata",
115 "tests",
116 ])?;
117
118 Ok(graph)
119}
120
121fn parse_nodes(expr: &Expr, path: &str) -> Result<Vec<Node>> {
122 sequence_items(expr, path)?
123 .iter()
124 .enumerate()
125 .map(|(index, item)| parse_node(item, &format!("{path}[{index}]")))
126 .collect()
127}
128
129fn parse_node(expr: &Expr, path: &str) -> Result<Node> {
130 match data_expr(expr) {
131 Expr::Map(entries) => parse_node_fields(Fields::from_map(entries, path)?, path),
132 Expr::List(items) | Expr::Vector(items) => {
133 let Some((first, rest)) = items.split_first() else {
134 return Err(parse_error(path, "expected node id"));
135 };
136 let id = parse_symbol(first, &format!("{path}.id"))?;
137 let fields = Fields::from_keywords(rest, path)?;
138 parse_node_with_id(id, fields, path)
139 }
140 Expr::Symbol(symbol) => {
141 let id = symbol.clone();
142 Ok(Node::new(id.clone(), id))
143 }
144 other => Err(parse_error(
145 path,
146 format!(
147 "expected node map, list, or symbol, found {}",
148 expr_kind(other)
149 ),
150 )),
151 }
152}
153
154fn parse_node_fields(fields: Fields<'_>, path: &str) -> Result<Node> {
155 let id = parse_symbol(
156 fields.required("id", &format!("{path}.id"))?,
157 &format!("{path}.id"),
158 )?;
159 parse_node_with_id(id, fields, path)
160}
161
162fn parse_node_with_id(id: Symbol, fields: Fields<'_>, path: &str) -> Result<Node> {
163 let verb = match fields.get("verb") {
164 Some(value) => parse_symbol(value, &format!("{path}.verb"))?,
165 None => id.clone(),
166 };
167 let mut node = Node::new(id, verb);
168
169 if let Some(inputs) = fields.get("in") {
170 node.inputs = parse_ports(inputs, &format!("{path}.in"))?;
171 }
172 if let Some(outputs) = fields.get("out") {
173 node.outputs = parse_ports(outputs, &format!("{path}.out"))?;
174 }
175 if let Some(target) = fields.get("target") {
176 node.target = parse_optional_expr(target);
177 }
178 if let Some(role) = fields.get("role") {
179 node.role = parse_optional_symbol(role, &format!("{path}.role"))?;
180 }
181 if let Some(input) = fields.get("input") {
182 node.input = parse_optional_expr(input);
183 }
184 if let Some(output) = fields.get("output") {
185 node.output = parse_optional_expr(output);
186 }
187 if let Some(options) = fields.get("options") {
188 node.options = parse_symbol_expr_map(options, &format!("{path}.options"))?;
189 }
190 node.options.extend(fields.unknown_pairs(&[
191 "id", "verb", "in", "out", "target", "role", "input", "output", "options",
192 ]));
193
194 Ok(node)
195}
196
197fn parse_ports(expr: &Expr, path: &str) -> Result<Vec<Port>> {
198 sequence_items(expr, path)?
199 .iter()
200 .enumerate()
201 .map(|(index, item)| parse_port(item, &format!("{path}[{index}]")))
202 .collect()
203}
204
205fn parse_port(expr: &Expr, path: &str) -> Result<Port> {
206 match data_expr(expr) {
207 Expr::Map(entries) => parse_port_fields(Fields::from_map(entries, path)?, path),
208 Expr::List(items) | Expr::Vector(items) => {
209 let Some((first, rest)) = items.split_first() else {
210 return Err(parse_error(path, "expected port name"));
211 };
212 let name = parse_symbol(first, &format!("{path}.name"))?;
213 let fields = Fields::from_keywords(rest, path)?;
214 parse_port_with_name(name, fields, path)
215 }
216 Expr::Symbol(symbol) => Ok(Port::new(symbol.clone(), PortMode::Value, true)),
217 other => Err(parse_error(
218 path,
219 format!(
220 "expected port map, list, or symbol, found {}",
221 expr_kind(other)
222 ),
223 )),
224 }
225}
226
227fn parse_port_fields(fields: Fields<'_>, path: &str) -> Result<Port> {
228 let name = parse_symbol(
229 fields.required("name", &format!("{path}.name"))?,
230 &format!("{path}.name"),
231 )?;
232 parse_port_with_name(name, fields, path)
233}
234
235fn parse_port_with_name(name: Symbol, fields: Fields<'_>, path: &str) -> Result<Port> {
236 let mode = match fields.get("mode") {
237 Some(value) => parse_port_mode(value, &format!("{path}.mode"))?,
238 None => PortMode::Value,
239 };
240 let required = match fields.get("required") {
241 Some(value) => parse_bool(value, &format!("{path}.required"))?,
242 None => true,
243 };
244 let mut port = Port::new(name, mode, required);
245 if let Some(shape) = fields.get("shape") {
246 port.shape = parse_optional_expr(shape);
247 }
248 fields.reject_unknown(&["name", "shape", "mode", "required"])?;
249 Ok(port)
250}
251
252fn parse_edges(expr: &Expr, path: &str) -> Result<Vec<Edge>> {
253 sequence_items(expr, path)?
254 .iter()
255 .enumerate()
256 .map(|(index, item)| {
257 parse_edge(item, EdgeId::new(index as u32), &format!("{path}[{index}]"))
258 })
259 .collect()
260}
261
262fn parse_edge(expr: &Expr, fallback_id: EdgeId, path: &str) -> Result<Edge> {
263 match data_expr(expr) {
264 Expr::Map(entries) => {
265 parse_edge_fields(Fields::from_map(entries, path)?, fallback_id, path)
266 }
267 Expr::List(items) | Expr::Vector(items) => parse_edge_list(items, fallback_id, path),
268 other => Err(parse_error(
269 path,
270 format!("expected edge map or list, found {}", expr_kind(other)),
271 )),
272 }
273}
274
275fn parse_edge_list(items: &[Expr], fallback_id: EdgeId, path: &str) -> Result<Edge> {
276 if items.len() < 3 || !is_arrow(&items[1]) {
277 return Err(parse_error(path, "expected edge form: from -> to"));
278 }
279 let mut fields = Fields::from_keywords(&items[3..], path)?;
280 fields.push("from", &items[0]);
281 fields.push("to", &items[2]);
282 parse_edge_fields(fields, fallback_id, path)
283}
284
285fn parse_edge_fields(fields: Fields<'_>, fallback_id: EdgeId, path: &str) -> Result<Edge> {
286 let id = match fields.get("id") {
287 Some(value) => EdgeId::new(parse_u32(value, &format!("{path}.id"))?),
288 None => fallback_id,
289 };
290 let from = parse_port_ref(
291 fields.required("from", &format!("{path}.from"))?,
292 "out",
293 &format!("{path}.from"),
294 )?;
295 let to = parse_port_ref(
296 fields.required("to", &format!("{path}.to"))?,
297 "in",
298 &format!("{path}.to"),
299 )?;
300 let mut edge = Edge::new(id, from, to);
301
302 if let Some(when) = fields.get("when") {
303 edge.when = parse_optional_expr(when);
304 }
305 if let Some(transform) = fields.get("transform") {
306 edge.transform = parse_optional_expr(transform);
307 }
308 if let Some(as_name) = fields.get("as") {
309 edge.as_name = parse_optional_symbol(as_name, &format!("{path}.as"))?;
310 }
311 if let Some(priority) = fields.get("priority") {
312 edge.priority = parse_i64(priority, &format!("{path}.priority"))?;
313 }
314 if let Some(max_visits) = fields.get("max_visits") {
315 edge.max_visits = parse_optional_u32(max_visits, &format!("{path}.max_visits"))?;
316 }
317 if let Some(buffer) = fields.get("buffer") {
318 edge.buffer = parse_optional_expr(buffer);
319 }
320 if let Some(metadata) = fields.get("metadata") {
321 edge.metadata = parse_symbol_expr_map(metadata, &format!("{path}.metadata"))?;
322 }
323 edge.metadata.extend(fields.unknown_pairs(&[
324 "id",
325 "from",
326 "to",
327 "when",
328 "transform",
329 "as",
330 "priority",
331 "max_visits",
332 "buffer",
333 "metadata",
334 ]));
335
336 Ok(edge)
337}
338
339fn parse_cells(expr: &Expr, path: &str) -> Result<Vec<Cell>> {
340 sequence_items(expr, path)?
341 .iter()
342 .enumerate()
343 .map(|(index, item)| parse_cell(item, &format!("{path}[{index}]")))
344 .collect()
345}
346
347fn parse_cell(expr: &Expr, path: &str) -> Result<Cell> {
348 match data_expr(expr) {
349 Expr::Map(entries) => parse_cell_fields(Fields::from_map(entries, path)?, path),
350 Expr::List(items) | Expr::Vector(items) => {
351 let Some((first, rest)) = items.split_first() else {
352 return Err(parse_error(path, "expected cell name"));
353 };
354 let name = parse_symbol(first, &format!("{path}.name"))?;
355 parse_cell_with_name(name, Fields::from_keywords(rest, path)?, path)
356 }
357 other => Err(parse_error(
358 path,
359 format!("expected cell map or list, found {}", expr_kind(other)),
360 )),
361 }
362}
363
364fn parse_cell_fields(fields: Fields<'_>, path: &str) -> Result<Cell> {
365 let name = parse_symbol(
366 fields.required("name", &format!("{path}.name"))?,
367 &format!("{path}.name"),
368 )?;
369 parse_cell_with_name(name, fields, path)
370}
371
372fn parse_cell_with_name(name: Symbol, fields: Fields<'_>, path: &str) -> Result<Cell> {
373 let initial = fields
374 .required("initial", &format!("{path}.initial"))?
375 .clone();
376 let mut cell = Cell::new(name, initial);
377 if let Some(shape) = fields.get("shape") {
378 cell.shape = parse_optional_expr(shape);
379 }
380 if let Some(merge) = fields.get("merge") {
381 cell.merge = parse_optional_symbol(merge, &format!("{path}.merge"))?;
382 }
383 if let Some(private) = fields.get("private") {
384 cell.private = parse_bool(private, &format!("{path}.private"))?;
385 }
386 fields.reject_unknown(&["name", "shape", "initial", "merge", "private"])?;
387 Ok(cell)
388}
389
390fn parse_scheduler(expr: &Expr, path: &str) -> Result<Scheduler> {
391 let fields = parse_field_set(expr, path)?;
392 let mut scheduler = Scheduler::default();
393 if let Some(mode) = fields.get("mode") {
394 expect_named(mode, "sequential", &format!("{path}.mode"))?;
395 scheduler.mode = SchedulerMode::Sequential;
396 }
397 if let Some(seed) = fields.get("seed") {
398 scheduler.seed = parse_optional_u64(seed, &format!("{path}.seed"))?;
399 }
400 if let Some(max_concurrency) = fields.get("max_concurrency") {
401 scheduler.max_concurrency = parse_u32(max_concurrency, &format!("{path}.max_concurrency"))?;
402 }
403 if let Some(deterministic) = fields.get("deterministic") {
404 scheduler.deterministic = parse_bool(deterministic, &format!("{path}.deterministic"))?;
405 }
406 fields.reject_unknown(&["mode", "seed", "max_concurrency", "deterministic"])?;
407 Ok(scheduler)
408}
409
410fn parse_budget(expr: &Expr, path: &str) -> Result<Budget> {
411 let fields = parse_field_set(expr, path)?;
412 let mut budget = Budget::default();
413 if let Some(max_steps) = fields.get("max_steps") {
414 budget.max_steps = parse_u32(max_steps, &format!("{path}.max_steps"))?;
415 }
416 if let Some(max_node_visits) = fields.get("max_node_visits") {
417 budget.max_node_visits = parse_u32(max_node_visits, &format!("{path}.max_node_visits"))?;
418 }
419 if let Some(max_edge_visits) = fields.get("max_edge_visits") {
420 budget.max_edge_visits = parse_u32(max_edge_visits, &format!("{path}.max_edge_visits"))?;
421 }
422 if let Some(max_outputs) = fields.get("max_outputs") {
423 budget.max_outputs = parse_u32(max_outputs, &format!("{path}.max_outputs"))?;
424 }
425 if let Some(max_child_runs) = fields.get("max_child_runs") {
426 budget.max_child_runs = parse_u32(max_child_runs, &format!("{path}.max_child_runs"))?;
427 }
428 if let Some(deadline_ms) = fields.get("deadline_ms") {
429 budget.deadline_ms = parse_optional_u64(deadline_ms, &format!("{path}.deadline_ms"))?;
430 }
431 if let Some(on_exhausted) = fields.get("on_exhausted") {
432 budget.on_exhausted =
433 match symbolish_name(on_exhausted, &format!("{path}.on_exhausted"))?.as_str() {
434 "fail" => BudgetExhausted::Fail,
435 "partial" => BudgetExhausted::Partial,
436 other => {
437 return Err(parse_error(
438 format!("{path}.on_exhausted"),
439 format!("expected fail or partial, found {other}"),
440 ));
441 }
442 };
443 }
444 fields.reject_unknown(&[
445 "max_steps",
446 "max_node_visits",
447 "max_edge_visits",
448 "max_outputs",
449 "max_child_runs",
450 "deadline_ms",
451 "on_exhausted",
452 ])?;
453 Ok(budget)
454}