1use sva_ast::{Token, TokenKind, tokenize};
4
5use crate::cli_error::CliError;
6use crate::query::{Asked, asked, call};
7
8pub const SUBTRACTS: [&str; 7] = [
10 "envelope", "loudness", "bands", "crest", "spectrum", "onsets", "pitch",
11];
12
13pub const REDUCERS: [&str; 5] = ["norm", "sum", "dot", "max", "abs"];
14
15pub fn takes(reducer: &str) -> usize {
17 match reducer {
18 "dot" => 2,
19 _ => 1,
20 }
21}
22
23pub const REPR: &str = "repr";
25
26pub const OPERATORS: [(&str, Op); 4] = [
28 ("add", Op::Add),
29 ("sub", Op::Sub),
30 ("mul", Op::Mul),
31 ("div", Op::Div),
32];
33
34pub const LOAD_JSON: &str = "load_json";
36
37#[derive(Clone, Debug, PartialEq)]
38pub struct Leaf {
39 pub target: String,
41 pub recording: Option<std::sync::Arc<sva_ast::Recording>>,
43 pub asked: Asked,
44 pub written: String,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum Step {
50 Key(String),
51 Index(usize),
52}
53
54#[derive(Clone, Debug, PartialEq)]
57pub enum From {
58 File {
59 file: String,
60 path: Vec<Step>,
61 path_text: String,
62 },
63 Value(serde_json::Value),
64}
65
66#[derive(Clone, Debug, PartialEq)]
68pub struct Load {
69 pub from: From,
70 pub written: String,
71}
72
73#[derive(Clone, Debug, PartialEq)]
76pub enum Held {
77 Number(f64),
78 Vector(Vec<f64>),
79 Reading(sva_engine::Output),
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum Op {
84 Add,
85 Sub,
86 Mul,
87 Div,
88}
89
90#[derive(Clone, Debug, PartialEq)]
91pub enum Node {
92 Leaf(usize),
93 Loaded(usize),
94 Num(f64),
95 Vector(Vec<f64>),
96 Neg(Box<Node>),
97 Bin(Op, Box<Node>, Box<Node>),
98 Reduce(&'static str, Vec<Node>),
99}
100
101#[derive(Clone, Debug, PartialEq)]
102pub struct Term {
103 pub text: String,
104 pub sign: f64,
105 pub node: Node,
106}
107
108#[derive(Clone, Debug, PartialEq)]
109pub struct Cost {
110 pub text: String,
111 pub leaves: Vec<Leaf>,
112 pub loads: Vec<Load>,
113 pub terms: Vec<Term>,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq)]
118enum Kind {
119 Reading(usize),
120 Object(usize),
122 Unknown,
124 Vector,
125 Scalar,
126}
127
128fn refused(code: &'static str, message: String) -> CliError {
129 CliError::usage(code, message)
130}
131
132#[derive(Clone, Copy)]
134enum Whence<'a> {
135 Argv,
136 File(&'a str),
138}
139
140impl Whence<'_> {
141 fn refused(self, text: &str, code: &'static str, byte: usize, what: String) -> CliError {
142 match self {
143 Whence::Argv => refused(code, format!("{what}, at byte {byte} of the cost")),
144 Whence::File(file) => {
145 let before = &text[..byte.min(text.len())];
146 let line = before.matches('\n').count() + 1;
147 let column = byte - before.rfind('\n').map_or(0, |at| at + 1) + 1;
148 CliError::Located {
149 code,
150 message: format!("{what}, at line {line}, byte {column} of `{file}`"),
151 file: file.to_string(),
152 byte,
153 line,
154 column,
155 }
156 }
157 }
158 }
159}
160
161pub fn cost(text: &str) -> Result<Cost, CliError> {
162 parsed(text, Whence::Argv)
163}
164
165pub fn cost_file(file: &str, content: &str) -> Result<Cost, CliError> {
168 let blanked: Vec<String> = content
169 .split('\n')
170 .map(|line| {
171 let code = sva_ast::strip_line_comment(line);
172 format!("{code}{}", " ".repeat(line.len() - code.len()))
173 })
174 .collect();
175 let text = blanked.join("\n");
176 if text.trim().is_empty() {
177 return Err(refused(
178 "solve.bad_cost",
179 format!("`{file}` holds no cost, only comments; write one under its annotation line"),
180 ));
181 }
182 let mut cost = parsed(&text, Whence::File(file))?;
183 let spaced = |t: &str| t.split_whitespace().collect::<Vec<_>>().join(" ");
184 cost.text = spaced(&cost.text);
185 for term in &mut cost.terms {
186 term.text = spaced(&term.text);
187 }
188 Ok(cost)
189}
190
191fn parsed(text: &str, whence: Whence) -> Result<Cost, CliError> {
192 let tokens = tokenize(text).map_err(|d| {
193 whence.refused(
194 text,
195 "solve.bad_cost",
196 d.span.start,
197 format!("the cost does not read: {}", d.message),
198 )
199 })?;
200 let mut parser = Parser {
201 whence,
202 text,
203 tokens: &tokens,
204 at: 0,
205 leaves: Vec::new(),
206 loads: Vec::new(),
207 terms: Vec::new(),
208 };
209 let (node, ..) = parser.sum(true)?;
210 if let Some(extra) = tokens.get(parser.at) {
211 return Err(parser.unexpected(extra, "the cost ends here"));
212 }
213 let Parser {
214 leaves,
215 loads,
216 terms,
217 ..
218 } = parser;
219 assembled(text.trim().to_string(), node, terms, leaves, loads)
220}
221
222fn assembled(
225 text: String,
226 node: Node,
227 terms: Vec<Term>,
228 leaves: Vec<Leaf>,
229 loads: Vec<Load>,
230) -> Result<Cost, CliError> {
231 let mut cost = Cost {
232 text: text.clone(),
233 leaves,
234 loads,
235 terms,
236 };
237 let typing = Typing {
238 leaves: &cost.leaves,
239 loads: &cost.loads,
240 held: None,
241 };
242 let split = cost.terms.iter().all(|t| {
243 typing
244 .kind(&t.node)
245 .is_ok_and(|k| matches!(k, Kind::Scalar | Kind::Unknown))
246 });
247 if !split || cost.terms.is_empty() {
248 cost.terms = vec![Term {
249 text,
250 sign: 1.0,
251 node,
252 }];
253 }
254 cost.typed(None)?;
255 Ok(cost)
256}
257
258impl Cost {
259 pub fn typed(&self, held: Option<&[Held]>) -> Result<(), CliError> {
262 let typing = Typing {
263 leaves: &self.leaves,
264 loads: &self.loads,
265 held,
266 };
267 for term in &self.terms {
268 typing.term(term)?;
269 }
270 Ok(())
271 }
272
273 pub fn paired(&self) -> Vec<Option<usize>> {
275 let mut out = vec![None; self.loads.len()];
276 for term in &self.terms {
277 pairs(&term.node, &mut out);
278 }
279 out
280 }
281}
282
283fn pairs(node: &Node, out: &mut [Option<usize>]) {
284 match node {
285 Node::Bin(Op::Sub, a, b) => match (&**a, &**b) {
286 (Node::Leaf(x), Node::Loaded(j)) | (Node::Loaded(j), Node::Leaf(x)) => {
287 out[*j] = Some(*x);
288 }
289 _ => {
290 pairs(a, out);
291 pairs(b, out);
292 }
293 },
294 Node::Bin(_, a, b) => {
295 pairs(a, out);
296 pairs(b, out);
297 }
298 Node::Neg(x) => pairs(x, out),
299 Node::Reduce(_, args) => args.iter().for_each(|a| pairs(a, out)),
300 Node::Leaf(_) | Node::Loaded(_) | Node::Num(_) | Node::Vector(_) => {}
301 }
302}
303
304struct Typing<'c> {
305 leaves: &'c [Leaf],
306 loads: &'c [Load],
307 held: Option<&'c [Held]>,
308}
309
310impl Typing<'_> {
311 fn term(&self, term: &Term) -> Result<(), CliError> {
312 match self.kind(&term.node)? {
313 Kind::Scalar | Kind::Unknown => {}
314 Kind::Vector => {
315 return Err(refused(
316 "solve.vector_cost",
317 format!(
318 "`{}` is a vector, and a cost is one number; reduce it, as \
319 `norm(...)`, `sum(abs(...))`, `max(abs(...))` or `dot([w1, w2], ...)`",
320 term.text
321 ),
322 ));
323 }
324 Kind::Reading(leaf) => return Err(unpaired(&self.leaves[leaf])),
325 Kind::Object(load) => return Err(unpaired_load(&self.loads[load])),
326 }
327 Ok(())
328 }
329
330 fn kind(&self, node: &Node) -> Result<Kind, CliError> {
331 let leaves = self.leaves;
332 let loaded = |k: Kind| match k {
333 Kind::Reading(at) => Err(unpaired(&leaves[at])),
334 Kind::Object(at) => Err(unpaired_load(&self.loads[at])),
335 other => Ok(other),
336 };
337 Ok(match node {
338 Node::Leaf(at) => Kind::Reading(*at),
339 Node::Loaded(at) => match self.held.map(|held| &held[*at]) {
340 None => Kind::Unknown,
341 Some(Held::Number(_)) => Kind::Scalar,
342 Some(Held::Vector(_)) => Kind::Vector,
343 Some(Held::Reading(_)) => Kind::Object(*at),
344 },
345 Node::Num(_) => Kind::Scalar,
346 Node::Vector(_) => Kind::Vector,
347 Node::Neg(x) => loaded(self.kind(x)?)?,
348 Node::Bin(op, a, b) => match (self.kind(a)?, self.kind(b)?) {
349 (Kind::Reading(x), Kind::Reading(y)) if *op == Op::Sub => {
350 let (l, r) = (&leaves[x], &leaves[y]);
351 if l.asked != r.asked {
352 return Err(refused(
353 "solve.mismatched_readings",
354 format!(
355 "`{}` and `{}` are not one reading with one set of arguments, \
356 so they do not subtract; read both sides alike, as `repr.{}(@a) \
357 - repr.{}(sample(\"../ref.wav\"))`",
358 l.written, r.written, l.asked.name, l.asked.name
359 ),
360 ));
361 }
362 Kind::Vector
363 }
364 (Kind::Reading(_), Kind::Object(_) | Kind::Unknown)
365 | (Kind::Object(_) | Kind::Unknown, Kind::Reading(_))
366 if *op == Op::Sub =>
367 {
368 Kind::Vector
369 }
370 (Kind::Reading(at), _) | (_, Kind::Reading(at)) => {
371 return Err(unpaired(&leaves[at]));
372 }
373 (Kind::Object(at), _) | (_, Kind::Object(at)) => {
374 return Err(unpaired_load(&self.loads[at]));
375 }
376 (Kind::Unknown, _) | (_, Kind::Unknown) => Kind::Unknown,
377 (Kind::Scalar, Kind::Scalar) => Kind::Scalar,
378 (Kind::Vector, Kind::Vector) if matches!(op, Op::Add | Op::Sub) => Kind::Vector,
379 (Kind::Vector, Kind::Scalar) | (Kind::Scalar, Kind::Vector)
380 if matches!(op, Op::Mul) =>
381 {
382 Kind::Vector
383 }
384 (Kind::Vector, Kind::Scalar) if *op == Op::Div => Kind::Vector,
385 _ => {
386 return Err(refused(
387 "solve.bad_cost",
388 "a vector adds or subtracts only a vector, and scales only by a number; \
389 reduce it first, as `norm(...)`"
390 .to_string(),
391 ));
392 }
393 },
394 Node::Reduce(name, args) => {
395 let kinds = args
396 .iter()
397 .map(|a| self.kind(a).and_then(loaded))
398 .collect::<Result<Vec<_>, _>>()?;
399 let vector = |k: &Kind| matches!(k, Kind::Vector | Kind::Unknown);
400 match (*name, kinds.as_slice()) {
401 ("abs", [one]) => *one,
402 ("dot", [w, v]) if vector(w) && vector(v) => Kind::Scalar,
403 ("norm" | "sum" | "max", [v]) if vector(v) => Kind::Scalar,
404 _ => {
405 return Err(refused(
406 "solve.bad_cost",
407 format!(
408 "`{name}` takes {}",
409 match *name {
410 "dot" => "two vectors, `dot([1, 0.5], v)`",
411 "abs" => "one number or vector",
412 _ => "one vector, as a subtraction of two readings",
413 }
414 ),
415 ));
416 }
417 }
418 }
419 })
420 }
421}
422
423fn unpaired(leaf: &Leaf) -> CliError {
424 refused(
425 "solve.unpaired_reading",
426 format!(
427 "`{}` is a reading, which a cost subtracts from another of its kind before reducing, \
428 as `norm({} - repr.{}(sample(\"../ref.wav\")))`",
429 leaf.written, leaf.written, leaf.asked.name
430 ),
431 )
432}
433
434fn own_arguments(call_text: &str, written: &str) -> Result<Asked, CliError> {
436 let asked = asked(&call(call_text)?)?;
437 if !SUBTRACTS.contains(&asked.name.as_str()) {
438 return Err(refused(
439 "solve.unsupported_reading",
440 format!(
441 "`{}` is no reading a cost subtracts; it subtracts {}",
442 asked.name,
443 SUBTRACTS.join(", ")
444 ),
445 ));
446 }
447 if asked.node.is_some() || asked.dest.is_some() || asked.brief || asked.skim {
448 return Err(refused(
449 "solve.bad_cost",
450 format!("`{written}` takes only its reading's own arguments"),
451 ));
452 }
453 Ok(asked)
454}
455
456fn unpaired_load(load: &Load) -> CliError {
457 refused(
458 "solve.unpaired_load",
459 format!(
460 "`{}` holds a reading, which a cost subtracts from a `repr.` leaf of that reading \
461 before reducing, as `norm(repr.bands(@a) - {})`; a number or a list of numbers \
462 loads as a weight",
463 load.written, load.written
464 ),
465 )
466}
467
468struct Parser<'a> {
469 whence: Whence<'a>,
470 text: &'a str,
471 tokens: &'a [Token],
472 at: usize,
473 leaves: Vec<Leaf>,
474 loads: Vec<Load>,
475 terms: Vec<Term>,
476}
477
478type Spanned = (Node, usize, usize);
479
480impl Parser<'_> {
481 fn peek(&self) -> Option<&TokenKind> {
482 self.tokens.get(self.at).map(|t| &t.kind)
483 }
484
485 fn end(&self) -> usize {
486 self.at
487 .checked_sub(1)
488 .map_or(0, |at| self.tokens[at].span.end)
489 }
490
491 fn unexpected(&self, token: &Token, wanted: &str) -> CliError {
492 self.whence.refused(
493 self.text,
494 "solve.bad_cost",
495 token.span.start,
496 format!(
497 "`{}`: {wanted}; a cost is `repr.<reading>(<target>, <args>)` and \
498 `load_json(\"<file>\", \"<jq path>\")` leaves, subtracted in pairs, reduced \
499 by {} and weighted by numbers",
500 &self.text[token.span.start..token.span.end],
501 REDUCERS.join(", ")
502 ),
503 )
504 }
505
506 fn ended(&self, wanted: &str) -> CliError {
507 self.whence.refused(
508 self.text,
509 "solve.bad_cost",
510 self.text.trim_end().len(),
511 format!("the cost ends where {wanted}"),
512 )
513 }
514
515 fn expect(&mut self, kind: &TokenKind, wanted: &str) -> Result<(), CliError> {
516 match self.tokens.get(self.at) {
517 Some(t) if &t.kind == kind => {
518 self.at += 1;
519 Ok(())
520 }
521 Some(t) => Err(self.unexpected(t, wanted)),
522 None => Err(self.ended(wanted)),
523 }
524 }
525
526 fn sum(&mut self, top: bool) -> Result<Spanned, CliError> {
528 let first = self.product()?;
529 let start = first.1;
530 let mut parts = vec![(1.0, first)];
531 while let Some(sign) = self.peek().and_then(|k| match k {
532 TokenKind::Plus => Some(1.0),
533 TokenKind::Minus => Some(-1.0),
534 _ => None,
535 }) {
536 self.at += 1;
537 parts.push((sign, self.product()?));
538 }
539 let end = parts.last().map_or(start, |(_, (_, _, e))| *e);
540 if top && parts.len() > 1 {
541 self.terms = parts
542 .iter()
543 .map(|(sign, (node, s, e))| Term {
544 text: self.text[*s..*e].trim().to_string(),
545 sign: *sign,
546 node: node.clone(),
547 })
548 .collect();
549 }
550 let mut parts = parts.into_iter();
551 let (_, (mut node, ..)) = parts.next().expect("a first operand");
552 for (sign, (part, ..)) in parts {
553 let op = if sign > 0.0 { Op::Add } else { Op::Sub };
554 node = Node::Bin(op, Box::new(node), Box::new(part));
555 }
556 Ok((node, start, end))
557 }
558
559 fn product(&mut self) -> Result<Spanned, CliError> {
560 let (mut node, start, mut end) = self.unary()?;
561 while let Some(op) = self.peek().and_then(|k| match k {
562 TokenKind::Star => Some(Op::Mul),
563 TokenKind::Slash => Some(Op::Div),
564 _ => None,
565 }) {
566 self.at += 1;
567 let (rhs, _, e) = self.unary()?;
568 end = e;
569 node = Node::Bin(op, Box::new(node), Box::new(rhs));
570 }
571 Ok((node, start, end))
572 }
573
574 fn unary(&mut self) -> Result<Spanned, CliError> {
575 let start = self.tokens.get(self.at).map_or(0, |t| t.span.start);
576 if self.peek() == Some(&TokenKind::Minus) {
577 self.at += 1;
578 let (x, _, end) = self.unary()?;
579 return Ok((Node::Neg(Box::new(x)), start, end));
580 }
581 let node = self.primary()?;
582 Ok((node, start, self.end()))
583 }
584
585 fn primary(&mut self) -> Result<Node, CliError> {
586 let Some(token) = self.tokens.get(self.at) else {
587 return Err(self.ended("a value was expected"));
588 };
589 self.at += 1;
590 match &token.kind {
591 TokenKind::Num(n) => Ok(Node::Num(*n)),
592 TokenKind::LParen => {
593 let (inner, ..) = self.sum(false)?;
594 self.expect(&TokenKind::RParen, "a `(` closes here")?;
595 Ok(inner)
596 }
597 TokenKind::LBracket => self.vector(),
598 TokenKind::Ident(name) if name == REPR => self.leaf(token.span.start),
599 TokenKind::Ident(name) if name == LOAD_JSON => self.load(token.span.start),
600 TokenKind::Ident(name) => match REDUCERS.iter().find(|r| **r == name) {
601 Some(reducer) => {
602 self.expect(&TokenKind::LParen, "a reducer is called, as `norm(...)`")?;
603 let mut args = vec![self.sum(false)?.0];
604 while self.peek() == Some(&TokenKind::Comma) {
605 self.at += 1;
606 args.push(self.sum(false)?.0);
607 }
608 self.expect(&TokenKind::RParen, "its `)` closes here")?;
609 Ok(Node::Reduce(reducer, args))
610 }
611 None => Err(self.unexpected(token, "no such reducer or leaf")),
612 },
613 _ => Err(self.unexpected(token, "a value was expected")),
614 }
615 }
616
617 fn vector(&mut self) -> Result<Node, CliError> {
618 let mut out = Vec::new();
619 loop {
620 let sign = match self.peek() {
621 Some(TokenKind::Minus) => {
622 self.at += 1;
623 -1.0
624 }
625 _ => 1.0,
626 };
627 match self.tokens.get(self.at) {
628 Some(Token {
629 kind: TokenKind::Num(n),
630 ..
631 }) => out.push(sign * n),
632 Some(t) => return Err(self.unexpected(t, "a weight vector holds numbers")),
633 None => return Err(self.ended("a weight vector's `]` was expected")),
634 }
635 self.at += 1;
636 match self.peek() {
637 Some(TokenKind::Comma) => self.at += 1,
638 Some(TokenKind::RBracket) => {
639 self.at += 1;
640 return Ok(Node::Vector(out));
641 }
642 _ => return Err(self.ended("a weight vector's `,` or `]` was expected")),
643 }
644 }
645 }
646
647 fn path_text(&mut self, wanted: &str) -> Result<(String, usize), CliError> {
648 match self.tokens.get(self.at) {
649 Some(Token {
650 kind: TokenKind::Str(text),
651 span,
652 }) => {
653 self.at += 1;
654 Ok((text.clone(), span.start))
655 }
656 Some(t) => Err(self.unexpected(t, wanted)),
657 None => Err(self.ended(wanted)),
658 }
659 }
660
661 fn load(&mut self, start: usize) -> Result<Node, CliError> {
663 let wanted = "`load_json` takes a file and a jq path, as `load_json(\"crop.json\", \
664 \".readings['bands(prune=1)']\")`";
665 self.expect(&TokenKind::LParen, wanted)?;
666 let (file, _) = self.path_text(wanted)?;
667 self.expect(&TokenKind::Comma, wanted)?;
668 let (path_text, at) = self.path_text(wanted)?;
669 self.expect(&TokenKind::RParen, wanted)?;
670 let written = self.text[start..self.end()].to_string();
671 let path = jq_path(&path_text).map_err(|why| {
672 self.whence.refused(
673 self.text,
674 "solve.bad_path",
675 at,
676 format!(
677 "`{path_text}` is no jq path: {why}; a path is only `.name` steps, `[3]` \
678 indices and `['any key']` for a key that is not a plain name, as \
679 `.readings['bands(prune=1)']`, and none of jq's filters"
680 ),
681 )
682 })?;
683 self.loads.push(Load {
684 from: From::File {
685 file,
686 path,
687 path_text,
688 },
689 written,
690 });
691 Ok(Node::Loaded(self.loads.len() - 1))
692 }
693
694 fn leaf(&mut self, start: usize) -> Result<Node, CliError> {
697 self.expect(&TokenKind::Dot, "`repr` is followed by `.<reading>`")?;
698 let name = match self.tokens.get(self.at).map(|t| &t.kind) {
699 Some(TokenKind::Ident(name)) => name.clone(),
700 _ => return Err(self.ended("a reading's name follows `repr.`")),
701 };
702 self.at += 1;
703 if !SUBTRACTS.contains(&name.as_str()) {
704 return Err(refused(
705 "solve.unsupported_reading",
706 format!(
707 "`repr.{name}` is no reading a cost subtracts; it subtracts {}",
708 SUBTRACTS.join(", ")
709 ),
710 ));
711 }
712 let open = self.at;
713 self.expect(&TokenKind::LParen, "a reading is called on its target")?;
714 let mut depth = 1;
715 let mut pieces = Vec::new();
716 let mut from = self.tokens[open].span.end;
717 while depth > 0 {
718 let Some(token) = self.tokens.get(self.at) else {
719 return Err(self.ended(&format!("`repr.{name}(`'s `)` was expected")));
720 };
721 match token.kind {
722 TokenKind::LParen | TokenKind::LBracket => depth += 1,
723 TokenKind::RParen | TokenKind::RBracket => depth -= 1,
724 TokenKind::Comma if depth == 1 => {
725 pieces.push(self.text[from..token.span.start].trim().to_string());
726 from = token.span.end;
727 }
728 _ => {}
729 }
730 if depth == 0 {
731 pieces.push(self.text[from..token.span.start].trim().to_string());
732 }
733 self.at += 1;
734 }
735 let written = self.text[start..self.end()].to_string();
736 let mut pieces = pieces.into_iter();
737 let target = pieces.next().filter(|t| !t.is_empty()).ok_or_else(|| {
738 refused(
739 "solve.bad_cost",
740 format!("`{written}` reads no target; write one, as `repr.{name}(@master)`"),
741 )
742 })?;
743 let args: Vec<String> = pieces.collect();
744 let asked = own_arguments(&format!("{name}({})", args.join(", ")), &written)?;
745 self.leaves.push(Leaf {
746 target,
747 recording: None,
748 asked,
749 written,
750 });
751 Ok(Node::Leaf(self.leaves.len() - 1))
752 }
753}
754
755pub fn jq_path(text: &str) -> Result<Vec<Step>, String> {
757 let mut steps = Vec::new();
758 let mut rest = text.trim();
759 if rest == "." {
760 return Ok(steps);
761 }
762 if !rest.starts_with(['.', '[']) {
763 return Err("it does not start with `.` or `[`".to_string());
764 }
765 while !rest.is_empty() {
766 if let Some(after) = rest.strip_prefix('.') {
767 if after.starts_with('[') {
768 rest = after;
769 continue;
770 }
771 let end = after
772 .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
773 .unwrap_or(after.len());
774 let name = &after[..end];
775 if name.is_empty() || name.starts_with(|c: char| c.is_ascii_digit()) {
776 return Err(format!(
777 "`.{}` names no key; write one that is not a plain name in brackets, \
778 `['{}']`",
779 after.split(['.', '[']).next().unwrap_or(after),
780 after.split(['.', '[']).next().unwrap_or(after)
781 ));
782 }
783 steps.push(Step::Key(name.to_string()));
784 rest = &after[end..];
785 } else if let Some(after) = rest.strip_prefix("['") {
786 let Some(close) = after.find("']") else {
787 return Err(format!("`['{after}` is never closed by `']`"));
788 };
789 steps.push(Step::Key(after[..close].to_string()));
790 rest = &after[close + 2..];
791 } else if let Some(after) = rest.strip_prefix('[') {
792 let Some(close) = after.find(']') else {
793 return Err(format!("`[{after}` is never closed by `]`"));
794 };
795 let index = after[..close].trim().parse::<usize>().map_err(|_| {
796 format!(
797 "`[{}]` is no index from 0; a key goes in single quotes, `['{}']`",
798 &after[..close],
799 &after[..close]
800 )
801 })?;
802 steps.push(Step::Index(index));
803 rest = &after[close + 1..];
804 } else {
805 return Err(format!("`{rest}` is no `.name`, `[3]` or `['key']` step"));
806 }
807 }
808 Ok(steps)
809}
810
811#[derive(Clone, Debug, PartialEq)]
813pub enum Target {
814 Text(String),
815 Samples { rate: u32, planes: Vec<Vec<f32>> },
816}
817
818#[derive(Clone, Debug, PartialEq)]
821pub enum Tree {
822 Num(f64),
823 Weights(Vec<f64>),
824 Repr {
826 call: String,
827 target: Target,
828 },
829 Value(serde_json::Value),
830 Neg(Box<Tree>),
831 Bin(Op, Box<Tree>, Box<Tree>),
832 Reduce(&'static str, Vec<Tree>),
834}
835
836fn printed(node: &Node, leaves: &[Leaf], loads: &[Load]) -> String {
838 let whole = |n: &Node| printed(n, leaves, loads);
839 let each = |n: &Node| match n {
840 Node::Bin(..) => format!("({})", whole(n)),
841 _ => whole(n),
842 };
843 match node {
844 Node::Leaf(at) => leaves[*at].written.clone(),
845 Node::Loaded(at) => loads[*at].written.clone(),
846 Node::Num(n) => format!("{n}"),
847 Node::Vector(v) => {
848 let v: Vec<String> = v.iter().map(|x| format!("{x}")).collect();
849 format!("[{}]", v.join(", "))
850 }
851 Node::Neg(x) => format!("-({})", each(x)),
852 Node::Bin(op, a, b) => {
853 let op = match op {
854 Op::Add => "+",
855 Op::Sub => "-",
856 Op::Mul => "*",
857 Op::Div => "/",
858 };
859 format!("{} {op} {}", each(a), each(b))
860 }
861 Node::Reduce(name, args) => {
862 let args: Vec<String> = args.iter().map(whole).collect();
863 format!("{name}({})", args.join(", "))
864 }
865 }
866}
867
868struct Building {
869 leaves: Vec<Leaf>,
870 loads: Vec<Load>,
871}
872
873impl Building {
874 fn node(&mut self, tree: Tree) -> Result<Node, CliError> {
875 Ok(match tree {
876 Tree::Num(n) => Node::Num(n),
877 Tree::Weights(v) => Node::Vector(v),
878 Tree::Neg(x) => Node::Neg(Box::new(self.node(*x)?)),
879 Tree::Bin(op, a, b) => {
880 Node::Bin(op, Box::new(self.node(*a)?), Box::new(self.node(*b)?))
881 }
882 Tree::Reduce(reducer, args) => {
883 let args = args
884 .into_iter()
885 .map(|a| self.node(a))
886 .collect::<Result<Vec<_>, _>>()?;
887 Node::Reduce(reducer, args)
888 }
889 Tree::Value(value) => {
890 let shape = crate::decode::shape(&value);
891 self.loads.push(Load {
892 written: format!("value {} ({shape})", self.loads.len()),
893 from: From::Value(value),
894 });
895 Node::Loaded(self.loads.len() - 1)
896 }
897 Tree::Repr { call, target } => {
898 let (target, recording) = match target {
899 Target::Text(text) if text.trim().is_empty() => {
900 return Err(refused(
901 "solve.bad_cost",
902 format!("`{call}` reads no target; give it one, as `@master`"),
903 ));
904 }
905 Target::Text(text) => (text, None),
906 Target::Samples { rate, planes } => {
907 let key = format!("#recording-{}", self.leaves.len());
908 let recording = sva_ast::Recording {
909 path: key.clone(),
910 rate,
911 planes: std::sync::Arc::new(planes),
912 };
913 (
914 format!("sample(\"{key}\")"),
915 Some(std::sync::Arc::new(recording)),
916 )
917 }
918 };
919 let shown = match &recording {
920 Some(r) => format!("<recording at {} Hz>", r.rate),
921 None => target.clone(),
922 };
923 let probe = asked(&self::call(&call)?)?;
924 let args = probe
925 .written
926 .strip_prefix(probe.name.as_str())
927 .unwrap_or("");
928 let args = args.trim_start_matches('(').trim_end_matches(')');
929 let written = match args.is_empty() {
930 true => format!("repr.{}({shown})", probe.name),
931 false => format!("repr.{}({shown}, {args})", probe.name),
932 };
933 let asked = own_arguments(&call, &written)?;
934 self.leaves.push(Leaf {
935 target,
936 recording,
937 asked,
938 written,
939 });
940 Node::Leaf(self.leaves.len() - 1)
941 }
942 })
943 }
944}
945
946fn split(node: Node, out: &mut Vec<(f64, Node)>) {
948 match node {
949 Node::Bin(Op::Add, a, b) => {
950 split(*a, out);
951 out.push((1.0, *b));
952 }
953 Node::Bin(Op::Sub, a, b) => {
954 split(*a, out);
955 out.push((-1.0, *b));
956 }
957 other => out.push((1.0, other)),
958 }
959}
960
961pub fn built(tree: Tree) -> Result<Cost, CliError> {
963 let mut building = Building {
964 leaves: Vec::new(),
965 loads: Vec::new(),
966 };
967 let node = building.node(tree)?;
968 let Building { leaves, loads } = building;
969 let text = printed(&node, &leaves, &loads);
970 let mut parts = Vec::new();
971 split(node.clone(), &mut parts);
972 let terms = match parts.len() {
973 1 => Vec::new(),
974 _ => parts
975 .into_iter()
976 .map(|(sign, node)| Term {
977 text: printed(&node, &leaves, &loads),
978 sign,
979 node,
980 })
981 .collect(),
982 };
983 assembled(text, node, terms, leaves, loads)
984}