Skip to main content

sva_core/
lib.rs

1// Concern: the parse->tempo->render pipeline shared by both front ends | Non-concern: argv (sva-cli), JS bindings (sva-wasm) | IO: (a Source, a target) -> Rendered, a Stream or CliError
2
3mod answer;
4mod builtins;
5mod cli_error;
6pub mod encode;
7pub mod json;
8mod lint_code;
9mod outline;
10mod output;
11mod query;
12mod set;
13mod song;
14mod target;
15mod until;
16
17pub use answer::{
18    Played, Printed, Report, SAMPLE_LIMIT, answer_json, counters_json, engine_stats_json,
19    label_json, query_data, stats_json, stream_stats_json, value_json, work_json,
20};
21pub use builtins::{Builtins, Callable, Crossing, builtins, builtins_data};
22pub use cli_error::{CliError, LintViolation, lint_diagnostic, warning_diagnostic};
23pub use lint_code::LintCode;
24pub use outline::{events_data, outline_data, song_data};
25pub use output::{Diagnostic, Severity, diagnostics_json, error_envelope, success_envelope};
26pub use query::{
27    Asked, Call, Destination, RETIRED, asked, asked_all, call, calls, is_wav, retired, seconds,
28    wav_path,
29};
30pub use set::{overlaid, set};
31pub use song::{as_lane, sung};
32pub use sva_ast::RESERVED_VARIABLES;
33pub use target::{Edge, Target, target};
34pub use until::until;
35
36use std::cell::RefCell;
37use std::num::NonZeroUsize;
38use std::path::Path;
39
40use sva_ast::song::Song;
41use sva_ast::{Dir, Graph, Refusal, Source};
42use sva_engine::{
43    Ask, Change, Changed, DEFAULT_SAMPLE_RATE, EngineError, Kind, PSYCHOACOUSTIC_V1, Phase, Range,
44    RenderConfig, StreamConfig, render, render_called,
45};
46
47pub use sva_engine::{Abandon, Handle, Hear, Never, Out, Placed, Session, Stream, Until};
48
49pub use sva_engine::{Answer, Extent, Label, Output, Representation};
50pub use sva_engine::{
51    Backend, CacheStats, Counters, DEFAULT_CACHE_BYTES, DEFAULT_STORE_BYTES, EngineStats,
52    FETCH_READS, Nothing, Persisted, Store, Tier,
53};
54
55pub const ROOT: &str = "master";
56pub const PROBE: &str = "probe";
57
58pub struct Rendered {
59    pub config: RenderConfig,
60    /// What the song's files earn: each prints, and the render goes on.
61    pub warnings: Vec<sva_ast::song::Warning>,
62    pub expression: String,
63    pub target: String,
64    pub render: sva_engine::Render,
65    /// So a structural check needs no second parse, and an ad-hoc target is the same `probe`.
66    pub graph: Graph,
67}
68
69impl Rendered {
70    pub fn answer(&self, node: &str, representation: Representation) -> Result<Answer, CliError> {
71        let written = |e| CliError::Engine(as_written(e, &self.expression));
72        let id = self.render.node(node).map_err(written)?;
73        let mut answer = sva_engine::answer(&self.render, id, representation).map_err(written)?;
74        self.attribute(&mut answer)?;
75        Ok(answer)
76    }
77
78    /// `asked`'s reading, its bars in this render's tempo.
79    pub fn answered(&self, asked: &Asked) -> Result<Answer, CliError> {
80        let per_bar = self.graph.tempo().map(|tempo| tempo.seconds_per_bar());
81        let representation = asked.representation(self.config.rate, per_bar)?;
82        self.answer(
83            asked.node.as_deref().unwrap_or(&self.target),
84            representation,
85        )
86    }
87
88    /// An alias score reads beside how many instances the render holds.
89    fn attribute(&self, answer: &mut Answer) -> Result<(), CliError> {
90        let Output::Alias(alias) = &mut answer.value else {
91            return Ok(());
92        };
93        alias.instances = self.render.tys.paths().count();
94        Ok(())
95    }
96
97    pub fn label(&self) -> Option<&sva_engine::Label> {
98        self.render.labels.get(&self.render.root)
99    }
100}
101
102/// One render: `target` an expression over the composition `source` holds, its own ref
103/// read over an interval where it writes one.
104pub struct Job<'a> {
105    pub source: &'a dyn Source,
106    pub target: &'a str,
107    /// `None` ends the render where the interval or the target's support ends.
108    pub until: Option<&'a str>,
109    pub rate: Option<u32>,
110    pub bits: Option<i32>,
111    pub asked: &'a [Asked],
112    pub volatile: &'a [String],
113    pub out: Out,
114    pub abandon: &'a dyn Abandon,
115    pub hear: Option<&'a dyn Hear>,
116    /// The most threads it computes on; `None` takes every core the platform reports.
117    pub threads: Option<NonZeroUsize>,
118}
119
120impl<'a> Job<'a> {
121    pub fn over(source: &'a dyn Source, target: &'a str) -> Job<'a> {
122        Job {
123            source,
124            target,
125            until: None,
126            rate: None,
127            bits: None,
128            asked: &[],
129            volatile: &[],
130            out: Out::Kept,
131            abandon: &Never,
132            hear: None,
133            threads: None,
134        }
135    }
136}
137
138fn settle(job: &Job) -> Result<(Graph, RenderConfig, Song), CliError> {
139    let Target { expr, interval } = target(job.target)?;
140    let roots = roots_of(job.source, &expr)?;
141    let mut graph = settled(sva_ast::load_reaching(
142        job.source,
143        &roots.iter().map(String::as_str).collect::<Vec<_>>(),
144    ))?;
145    let parsed = sva_ast::parse_expr(&expr).map_err(|d| {
146        CliError::BadProbe(format!(
147            "`{}` does not parse as an expression: {}",
148            job.target, d.message
149        ))
150    })?;
151    define_probe(&mut graph, parsed)?;
152    let rate = job.rate.unwrap_or(DEFAULT_SAMPLE_RATE);
153    let per_bar = graph.tempo().map(|tempo| tempo.seconds_per_bar());
154    let range = match interval {
155        None => Range::default(),
156        Some((start, end)) => Range {
157            start: start.sample(rate, per_bar)?,
158            end: end.sample(rate, per_bar)?,
159        },
160    };
161    if let Range {
162        start: Some(start),
163        end: Some(end),
164    } = range
165        && end <= start
166    {
167        return Err(CliError::usage(
168            "render.empty_interval",
169            format!(
170                "`{}` reads an interval from sample {start} to {end}, which holds none",
171                job.target
172            ),
173        ));
174    }
175    let until = match job.until {
176        Some(text) => Some(until(text, rate, per_bar)?),
177        None => None,
178    };
179    let mut config = RenderConfig {
180        range,
181        until,
182        ..RenderConfig::at(rate)
183    };
184    config.profile.precision_bits = precision_bits(job.bits)?;
185    config.volatile = job.volatile.to_vec();
186    config.out = job.out;
187    if let Some(threads) = job.threads {
188        config.threads = threads;
189    }
190    config.asks = job
191        .asked
192        .iter()
193        .map(|asked| {
194            Ok(Ask {
195                node: asked.node.clone().unwrap_or_else(|| PROBE.to_string()),
196                representation: asked.representation(rate, per_bar)?,
197            })
198        })
199        .collect::<Result<_, CliError>>()?;
200    let song = sung(&mut graph, PROBE, rate)?;
201    Ok((graph, config, song))
202}
203
204/// An engine refusal as the song names it where a moving lane reaches it, else as written.
205fn refused(song: &Song, graph: &Graph, e: EngineError, target: &str) -> CliError {
206    match as_lane(song, graph, e) {
207        CliError::Engine(e) => CliError::Engine(as_written(e, target)),
208        other => other,
209    }
210}
211
212/// `trace` names one instance of a parameterized file as `<path>(<name>=<value>, ..)`.
213fn instance_call(text: &str) -> Option<(&str, &str)> {
214    let (path, rest) = text.split_once('(')?;
215    let binds = rest.strip_suffix(')')?.trim();
216    (!binds.is_empty() && all_named(binds)).then_some((path, binds))
217}
218
219/// Only commas and equals outside a bind's own parens count: a bind's value may be a call.
220fn all_named(binds: &str) -> bool {
221    let mut depth = 0i32;
222    let mut named = false;
223    for c in binds.chars() {
224        match c {
225            '(' => depth += 1,
226            ')' if depth == 0 => return false,
227            ')' => depth -= 1,
228            '=' if depth == 0 => named = true,
229            ',' if depth == 0 && !std::mem::take(&mut named) => return false,
230            _ => {}
231        }
232    }
233    named && depth == 0
234}
235
236/// The ref an instance name stands for, so `lint` and `trace` answer for the same node.
237fn instance_read(graph: &Graph, text: &str) -> Option<sva_ast::Expr> {
238    let (path, binds) = instance_call(text)?;
239    if !graph.defines(path) {
240        return None;
241    }
242    sva_ast::parse_expr(&format!("@{path}(t, {binds})")).ok()
243}
244
245pub fn execute(job: Job, tier: &Tier) -> Result<Rendered, CliError> {
246    let (graph, config, song) = settle(&job)?;
247    let render = render(&graph, PROBE, config, tier);
248    rendered(&job, (graph, song), render)
249}
250
251/// `job` over `tier`, typing anew only what `session` typed otherwise; only `persist` commits
252/// what it offers a disk beneath.
253pub async fn execute_over<B: Backend>(
254    job: Job<'_>,
255    tier: &Tier<B>,
256    session: &mut Session,
257) -> Result<Rendered, CliError> {
258    let call = tier.call(Kind::Render, job.target, Phase::Parsing);
259    let (graph, config, song) = settle(&job)?;
260    let hooks = (job.abandon, job.hear);
261    let render = render_called(session, &graph, PROBE, config, tier, hooks, call).await;
262    rendered(&job, (graph, song), render)
263}
264
265fn rendered(
266    job: &Job,
267    (graph, song): (Graph, Song),
268    render: Result<sva_engine::Render, EngineError>,
269) -> Result<Rendered, CliError> {
270    let render = render.map_err(|e| refused(&song, &graph, e, job.target))?;
271    Ok(Rendered {
272        warnings: song.warnings().to_vec(),
273        config: render.config.clone(),
274        expression: job.target.to_string(),
275        target: PROBE.to_string(),
276        render,
277        graph,
278    })
279}
280
281/// The render a job would run, and the warnings its song earns.
282pub fn plan(job: &Job) -> Result<(sva_engine::Render, Vec<sva_ast::song::Warning>), CliError> {
283    let (graph, config, song) = settle(job)?;
284    let planned = sva_engine::plan(&graph, PROBE, config);
285    let planned = planned.map_err(|e| refused(&song, &graph, e, job.target))?;
286    Ok((planned, song.warnings().to_vec()))
287}
288
289/// Every refusal typing the target reaches, and no render's; the warnings its song earns.
290pub fn types(job: &Job) -> Result<Vec<sva_ast::song::Warning>, CliError> {
291    let (graph, config, song) = settle(job)?;
292    sva_engine::types_at(&graph, PROBE, config.rate)
293        .map_err(|e| refused(&song, &graph, e, job.target))?;
294    Ok(song.warnings().to_vec())
295}
296
297/// `root`'s song derived and laid over `graph`, as `outline` reads it.
298pub fn song_of(source: &dyn Source, target: &str) -> Result<(Graph, Song), CliError> {
299    let job = Job::over(source, target);
300    let (graph, _, song) = settle(&job)?;
301    Ok((graph, song))
302}
303
304/// `channels`: what it plays, the target's own where `None`.
305pub async fn stream<B: Backend>(
306    job: &Job<'_>,
307    (block, channels): (usize, Option<usize>),
308    tier: &Tier<B>,
309) -> Result<Stream, CliError> {
310    let call = tier.call(Kind::Stream, job.target, Phase::Parsing);
311    let (graph, config, _) = settle(job)?;
312    let target = graph
313        .expr(PROBE)
314        .cloned()
315        .expect("the target was defined as the probe");
316    let config = StreamConfig {
317        block,
318        channels,
319        render: config,
320    };
321    Stream::open_called(&graph, &target, config, tier, call)
322        .await
323        .map_err(|e| CliError::Engine(as_written(e, job.target)))
324}
325
326/// `target`, an expression over `source` with no interval of its own, in place of what
327/// `stream` plays. Each edit here is `sva_engine::change`'s: the stream plays on meanwhile.
328pub async fn edit<B: Backend>(
329    stream: &RefCell<Stream>,
330    source: &dyn Source,
331    target: &str,
332    tier: &Tier<B>,
333) -> Result<(), CliError> {
334    let build = |s: &Stream| {
335        let (graph, expr) = streamed(s, source, target, None)?;
336        Ok(Change::Target(graph, expr))
337    };
338    changed(stream, build, Some(target), tier).await.map(|_| ())
339}
340
341/// `term` summed into the stream's `@notes`, its sample 0 placed `at`.
342pub async fn add<B: Backend>(
343    stream: &RefCell<Stream>,
344    source: &dyn Source,
345    (term, at): (&str, Placed),
346    tier: &Tier<B>,
347) -> Result<Handle, CliError> {
348    let build = |s: &Stream| {
349        let (graph, expr) = streamed(s, source, term, Some(at))?;
350        Ok(Change::Add(graph, expr, at))
351    };
352    match changed(stream, build, Some(term), tier).await? {
353        Changed::Added(handle) => Ok(handle),
354        _ => unreachable!("an add answers its handle"),
355    }
356}
357
358/// False where the stream no longer holds `handle`.
359pub async fn replace<B: Backend>(
360    stream: &RefCell<Stream>,
361    source: &dyn Source,
362    (handle, term, at): (Handle, &str, Placed),
363    tier: &Tier<B>,
364) -> Result<bool, CliError> {
365    let build = |s: &Stream| {
366        let (graph, expr) = streamed(s, source, term, Some(at))?;
367        Ok(Change::Replace(handle, graph, expr, at))
368    };
369    Ok(changed(stream, build, Some(term), tier).await? == Changed::Held(true))
370}
371
372/// False where the stream no longer holds `handle`.
373pub async fn remove<B: Backend>(
374    stream: &RefCell<Stream>,
375    handle: Handle,
376    tier: &Tier<B>,
377) -> Result<bool, CliError> {
378    let build = |_: &Stream| Ok(Change::Remove(handle));
379    Ok(changed(stream, build, None, tier).await? == Changed::Held(true))
380}
381
382async fn changed<B: Backend>(
383    stream: &RefCell<Stream>,
384    build: impl FnMut(&Stream) -> Result<Change, CliError>,
385    text: Option<&str>,
386    tier: &Tier<B>,
387) -> Result<Changed, CliError> {
388    let changed = sva_engine::change(stream, build, tier).await;
389    changed.map_err(|e| match (e, text) {
390        (CliError::Engine(e), Some(text)) => CliError::Engine(as_written(e, text)),
391        (e, _) => e,
392    })
393}
394
395pub async fn fetch<B: Backend>(stream: &RefCell<Stream>, tier: &Tier<B>) {
396    sva_engine::fetch(stream, tier).await;
397}
398
399/// `text` with no interval, and each node it reads that `stream` does not hold, or holds at an
400/// earlier edit of `source`, read and parsed off `source`: an edit is a version the stream plays.
401fn streamed(
402    stream: &Stream,
403    source: &dyn Source,
404    text: &str,
405    term: Option<Placed>,
406) -> Result<(Graph, sva_ast::Expr), CliError> {
407    let Target { expr, interval } = target(text)?;
408    if interval.is_some() {
409        return Err(CliError::usage(
410            "stream.interval",
411            format!("`{text}` reads an interval, and a stream keeps its own"),
412        ));
413    }
414    let held = stream.graph();
415    let roots = roots_of(source, &expr)?;
416    let roots: Vec<&str> = roots.iter().map(String::as_str).collect();
417    let mut delta = sva_ast::load_beside(source, &roots, held).map_err(CliError::Refusals)?;
418    delta
419        .lower_reserved(Some(held))
420        .map_err(CliError::Refusals)?;
421    let parsed = sva_ast::parse_expr(&expr).map_err(|d| {
422        CliError::BadProbe(format!(
423            "`{text}` does not parse as an expression: {}",
424            d.message
425        ))
426    })?;
427    let mut view = held.sourced();
428    for path in delta.paths() {
429        view.set(path, delta.held(path));
430    }
431    let rate = stream.config().render.rate;
432    let lowered = match term {
433        None => {
434            view.set(PROBE, None);
435            define_probe(&mut view, parsed)?;
436            sung(&mut view, PROBE, rate)?;
437            view.expr(PROBE)
438                .cloned()
439                .expect("the target was defined as the probe")
440        }
441        Some(placed) => termed(&mut view, parsed, placed, rate)?,
442    };
443    let mut out = delta;
444    let changed: Vec<String> = view
445        .paths()
446        .filter(|path| !held.holds_as(path, &view) || !held.read_as(path, &view))
447        .map(str::to_string)
448        .collect();
449    for path in changed {
450        out.set(&path, view.held(&path));
451    }
452    Ok((out, lowered))
453}
454
455/// A stream term lowered beside the stream's song: its instances read the song's lanes at
456/// their position, and a term reaching a move of its own refuses, since lanes derive from the
457/// song's files.
458fn termed(
459    view: &mut Graph,
460    term: sva_ast::Expr,
461    placed: Placed,
462    rate: u32,
463) -> Result<sva_ast::Expr, CliError> {
464    const ALONE: &str = "#term";
465    const BESIDE: &str = "#song";
466    let mut alone = view.clone();
467    alone
468        .define_settled(None, ALONE, term)
469        .map_err(CliError::Refusals)?;
470    let written = alone.expr(ALONE).cloned().expect("just defined");
471    let own = alone.song(ALONE).map_err(CliError::Refusals)?;
472    if let Some(lane) = own.lanes().first() {
473        let first = &lane.moves[0].reached;
474        return Err(CliError::Refusals(vec![Refusal::new(
475            format!("{}{}", first.score, sva_ast::score::EXTENSION),
476            sva_ast::ByteSpan::at(0),
477            sva_ast::DiagCode::BadLane,
478            format!(
479                "a stream term reaches this move on `{}`; a term adds sound and never moves, \
480                 since lanes derive from the song's files: move it in one",
481                lane.id.name()
482            ),
483        )]));
484    }
485    let target = view.source_expr(PROBE).expect("a stream holds its target");
486    let both = sva_ast::Expr::Bin(
487        sva_ast::BinOp::Add,
488        Box::new(target),
489        Box::new(written.clone()),
490    );
491    view.define_settled(None, BESIDE, both)
492        .map_err(CliError::Refusals)?;
493    sung(view, BESIDE, rate)?;
494    let Some(sva_ast::Expr::Bin(_, _, lowered)) = view.expr(BESIDE).cloned() else {
495        unreachable!("a sum lowers to a sum")
496    };
497    view.set(BESIDE, None);
498    if placed == Placed::Landing && *lowered != written {
499        return Err(CliError::Refusals(vec![Refusal::new(
500            ALONE,
501            sva_ast::ByteSpan::at(0),
502            sva_ast::DiagCode::BadLane,
503            "this term lands where the stream is when it arrives, so it has no song time, and a \
504             lane of the song moves what it plays; a lane reaches an instance at its song \
505             position, so add it at a written time"
506                .to_string(),
507        )]));
508    }
509    Ok(*lowered)
510}
511
512/// The precision a render writes every sample to: `bits`, else the profile's own.
513pub fn precision_bits(bits: Option<i32>) -> Result<i32, CliError> {
514    bits.map_or(Ok(PSYCHOACOUSTIC_V1.precision_bits), precision)
515}
516
517/// A double holds no bit past its own mantissa, and one bit writes only zero.
518fn precision(bits: i32) -> Result<i32, CliError> {
519    match (2..=52).contains(&bits) {
520        true => Ok(bits),
521        false => Err(CliError::usage(
522            "cli.bad_value",
523            format!("`--bits` takes a precision from 2 to 52 bits, not {bits}"),
524        )),
525    }
526}
527
528/// The engine knows a target only as the node it was defined as, so a refusal that names
529/// that node names the target as the caller wrote it instead.
530fn as_written(refused: EngineError, target: &str) -> EngineError {
531    match refused {
532        EngineError::Refused(mut d) => {
533            if d.location.node == PROBE {
534                d.location.node = target.to_string();
535            }
536            d.message = d
537                .message
538                .replace(&format!("`{PROBE}`"), &format!("`{target}`"));
539            EngineError::Refused(d)
540        }
541        EngineError::Binding { node, span, fault } if node == PROBE => EngineError::Binding {
542            node: target.to_string(),
543            span,
544            fault,
545        },
546        other => other,
547    }
548}
549
550pub fn probe(dir: &Path, expression: &str) -> Result<Rendered, CliError> {
551    execute(Job::over(&Dir::at(dir), expression), &Tier::default())
552}
553
554pub fn cwd() -> Result<std::path::PathBuf, CliError> {
555    std::env::current_dir()
556        .map_err(|e| CliError::Io(format!("could not read the current directory: {e}")))
557}
558
559pub fn prepared(source: &dyn Source) -> Result<Graph, CliError> {
560    settled(sva_ast::load(source))
561}
562
563/// Argv math: `trace` and `lint` each refuse a target no node answers for here, under one
564/// code and one message.
565pub fn define_probe_for(graph: &mut Graph, text: &str) -> Result<(), CliError> {
566    let expr = match instance_read(graph, text) {
567        Some(read) => read,
568        None if names_a_missing_node(text) => {
569            return Err(CliError::NotFound(format!(
570                "`{text}` is not a node this composition defines"
571            )));
572        }
573        None => sva_ast::parse_expr(text).map_err(|d| {
574            CliError::BadProbe(format!(
575                "`{text}` is not a node in this composition, and does not parse as an \
576                 expression: {}",
577                d.message
578            ))
579        })?,
580    };
581    define_probe(graph, expr)
582}
583
584/// Only a path, or instance syntax, is a node; the rest is math the parser and engine answer
585/// for. `t` and `A4` are spelled like paths and read as values.
586fn names_a_missing_node(text: &str) -> bool {
587    match instance_call(text) {
588        Some((path, _)) => sva_ast::names_a_node(path),
589        None => !text.contains('(') && sva_ast::names_a_node(text) && !reads_as_a_value(text),
590    }
591}
592
593fn reads_as_a_value(text: &str) -> bool {
594    match sva_ast::parse_expr(text) {
595        Ok(sva_ast::Expr::Lit(_)) => true,
596        Ok(sva_ast::Expr::Var(name)) => sva_ast::is_reserved(&name),
597        _ => false,
598    }
599}
600
601/// Argv math settled the way a file is; `concat` is no file-only dialect.
602fn define_probe(graph: &mut Graph, expr: sva_ast::Expr) -> Result<(), CliError> {
603    let defined = graph
604        .define_settled(None, PROBE, expr)
605        .map_err(CliError::Refusals)?;
606    if !defined {
607        return Err(CliError::BadProbe(format!(
608            "this composition already has a node named `{PROBE}`"
609        )));
610    }
611    Ok(())
612}
613
614/// Loaded with its reserved variables lowered; a song lowers its scores once a root is named.
615pub fn settled(loaded: Result<Graph, Vec<Refusal>>) -> Result<Graph, CliError> {
616    let mut graph = loaded.map_err(CliError::Refusals)?;
617    graph.lower_reserved(None).map_err(CliError::Refusals)?;
618    Ok(graph)
619}
620
621/// A node path the source answers for is a node; anything else is math, and its reads are
622/// roots. Only a node path is asked of the source: math is never looked up as a file.
623pub fn roots_of(source: &dyn Source, target: &str) -> Result<Vec<String>, CliError> {
624    let mut roots: Vec<String> = RESERVED_VARIABLES.map(sva_ast::variable).to_vec();
625    let listed = source.paths().map_err(CliError::Io)?.found;
626    let variables = format!("{}/", sva_ast::VARIABLES);
627    roots.extend(listed.into_iter().filter(|p| p.starts_with(&variables)));
628    roots.sort();
629    roots.dedup();
630    let held = |path: &str| -> Result<bool, CliError> {
631        let file = format!("{path}{}", sva_ast::score::EXTENSION);
632        let get = |at: &str| {
633            source
634                .get(at)
635                .map(|text| text.is_some())
636                .map_err(CliError::Io)
637        };
638        Ok(sva_ast::whole_ref_path(path) && (get(path)? || get(&file)?))
639    };
640    match target {
641        target if held(target)? => {
642            roots.push(target.to_string());
643        }
644        text => match instance_call(text).map(|(path, _)| path) {
645            Some(path) if held(path)? => {
646                roots.push(path.to_string());
647            }
648            _ => {
649                if let Ok(expr) = sva_ast::parse_expr(text) {
650                    roots.extend(sva_ast::reads_of(PROBE, &expr));
651                }
652            }
653        },
654    }
655    Ok(roots)
656}