Skip to main content

sva_engine/instantiate/
mod.rs

1// Concern: holds one instance per argument tuple and resolves a name inside one | Non-concern: building the table (build.rs), writing a resolved walk out (resolved.rs) | IO: (path) -> a body, a Node
2
3mod build;
4mod edges;
5mod resolved;
6
7use std::collections::{BTreeMap, BTreeSet, HashMap};
8use std::sync::Arc;
9
10use sva_ast::{Address, Arg, BinOp, ByteSpan, Defined, Expr, Literal};
11
12use crate::error::EngineError;
13use crate::time::Grid;
14use sva_ast::is_builtin;
15
16use build::Journal;
17pub use build::{from_roots, instantiate};
18use edges::Edges;
19
20pub(crate) type ScopeId = u32;
21
22/// Scope 0 binds nothing, reserved for defaults: a default stands outside every invocation.
23pub(crate) const NO_PARAMS: ScopeId = 0;
24
25#[derive(Clone, Copy, Debug)]
26pub struct Thunk<'a> {
27    pub(crate) expr: &'a Expr,
28    pub(crate) scope: ScopeId,
29}
30
31/// An expression owned where it was written: a node's line and each child taken down to it.
32#[derive(Clone, Debug)]
33pub(crate) struct Written {
34    defined: Arc<Defined>,
35    path: Arc<[u32]>,
36}
37
38impl Written {
39    /// `line` 0 is the body, `k` the default line `k - 1`.
40    pub(crate) fn of(defined: &Arc<Defined>, line: u32) -> Written {
41        Written {
42            defined: Arc::clone(defined),
43            path: [line].into(),
44        }
45    }
46
47    pub(crate) fn expr(&self) -> &Expr {
48        let (line, path) = self.path.split_first().expect("a line");
49        let top = match *line {
50            0 => &self.defined.body,
51            k => &self.defined.defaults[k as usize - 1].1,
52        };
53        let mut at = top;
54        for k in path {
55            at = child(at, *k).expect("a path down this expression");
56        }
57        at
58    }
59}
60
61/// A child's number on a walk down, as [`child`] reads it.
62#[derive(Clone, Copy)]
63pub(crate) enum Child {
64    First,
65    Right,
66    Arg(u32),
67    Bind(u32),
68}
69
70impl Child {
71    pub(crate) fn index(self) -> u32 {
72        match self {
73            Child::First => 0,
74            Child::Right => 1,
75            Child::Arg(k) => k,
76            Child::Bind(k) => k + 1,
77        }
78    }
79}
80
81fn child(e: &Expr, k: u32) -> Option<&Expr> {
82    let k = k as usize;
83    match e {
84        Expr::Bin(_, l, r) => [l, r].get(k).map(|c| &***c),
85        Expr::Call { args, .. } => args.get(k).map(|(Arg::Pos(x) | Arg::Named(_, x))| x),
86        Expr::Ref { arg, binds, .. } => match k {
87            0 => Some(arg),
88            k => binds.get(k - 1).map(|(_, x)| x),
89        },
90        Expr::SelfRef { arg, .. } | Expr::Indexed { arg, .. } => (k == 0).then_some(&**arg),
91        Expr::Lit(_) | Expr::Var(_) => None,
92    }
93}
94
95#[derive(Clone, Debug)]
96pub(crate) struct Bound {
97    pub(crate) written: Written,
98    pub(crate) scope: ScopeId,
99}
100
101impl Bound {
102    pub(crate) fn thunk(&self) -> Thunk<'_> {
103        Thunk {
104            expr: self.written.expr(),
105            scope: self.scope,
106        }
107    }
108}
109
110/// Sorted by name: the tuple names the instance. Held while owned or read in.
111#[derive(Debug)]
112pub(crate) struct Scope {
113    pub(crate) vars: Vec<(String, Bound)>,
114    keys: Vec<u64>,
115    seen: u64,
116    chained: bool,
117    held: u32,
118    /// The child instance each invocation scanned in it names, by where it is written.
119    sites: HashMap<usize, String>,
120}
121
122/// One instance: what it reads, who reads it, where it was first invoked.
123#[derive(Debug)]
124pub(crate) struct Instance {
125    pub(crate) body: Bound,
126    pub(crate) file: String,
127    given: Vec<String>,
128    site: (String, Option<ByteSpan>),
129    held: u32,
130    scanned: bool,
131}
132
133/// A name's length and first seven bytes in one word, so a lookup costs integer compares.
134#[inline]
135pub(crate) fn packed(name: &str) -> u64 {
136    let bytes = name.as_bytes();
137    let mut key = (bytes.len() as u64) << 56;
138    for (at, byte) in bytes.iter().take(7).enumerate() {
139        key |= u64::from(*byte) << (at * 8);
140    }
141    key
142}
143
144#[inline]
145fn bit(key: u64) -> u64 {
146    1u64 << ((key ^ (key >> 32)) & 63)
147}
148
149impl Scope {
150    pub(crate) fn new(vars: Vec<(String, Bound)>, chained: bool) -> Scope {
151        let keys: Vec<u64> = vars.iter().map(|(k, _)| packed(k)).collect();
152        let seen = keys.iter().fold(0, |acc, k| acc | bit(*k));
153        Scope {
154            vars,
155            keys,
156            seen,
157            chained,
158            held: 0,
159            sites: HashMap::new(),
160        }
161    }
162
163    /// One bit says no before a key is read. Two names of a length share a key past the
164    /// seventh byte, so every match is offered the name.
165    #[inline]
166    pub(crate) fn get(&self, name: &str, key: u64) -> Option<&Bound> {
167        if self.seen & bit(key) == 0 {
168            return None;
169        }
170        self.keys
171            .iter()
172            .enumerate()
173            .find(|(at, k)| **k == key && (name.len() <= 7 || self.vars[*at].0 == name))
174            .map(|(at, _)| &self.vars[at].1)
175    }
176}
177
178/// `time` is DYNAMIC: `p(t - d)` moves every `t` under the parameter, however deep. `sp` is a
179/// step of `grid`.
180#[derive(Clone, Copy)]
181pub struct Cx<'a> {
182    pub(crate) scope: ScopeId,
183    pub(crate) time: Option<&'a Time<'a>>,
184    pub(crate) grid: Grid,
185}
186
187pub(crate) struct Time<'a> {
188    pub(crate) expr: &'a Expr,
189    pub(crate) cx: Cx<'a>,
190}
191
192enum Move<'a> {
193    Here(&'a Expr, Cx<'a>),
194    Shifted(&'a Expr, ScopeId, &'a Expr),
195}
196
197impl<'a> Cx<'a> {
198    pub(crate) fn under(self, scope: ScopeId) -> Cx<'a> {
199        Cx { scope, ..self }
200    }
201
202    pub(crate) fn on(self, grid: Grid) -> Cx<'a> {
203        Cx { grid, ..self }
204    }
205}
206
207/// Read from sva-ast so the two crates cannot resolve a path two ways.
208pub fn resolve_ref_path(referencing: &str, ref_path: &str) -> Result<String, EngineError> {
209    sva_ast::resolve_ref_path(referencing, ref_path)
210        .ok_or_else(|| EngineError::RefAboveRoot(referencing.to_string(), ref_path.to_string()))
211}
212
213/// `@kick.comb(delay=0.03)` is `@comb(t, x=@kick, delay=0.03)`: one name, so a file never
214/// states a parameter order.
215pub const SIGNAL_PARAM: &str = "x";
216
217/// Bounds BREADTH: a chain of functions each invoking the next several times multiplies.
218pub const MAX_INSTANCES: usize = 4096;
219
220/// A `Read` names the child INSTANCE, so this is reachable only past [`Instances::follow`].
221#[derive(Clone, Copy)]
222pub enum Node<'a> {
223    Lit(&'a Literal),
224    Name(&'a str),
225    Bin(BinOp, &'a Expr, &'a Expr),
226    Call {
227        name: &'a str,
228        args: &'a [Arg],
229        span: ByteSpan,
230    },
231    Read {
232        path: &'a str,
233        arg: &'a Expr,
234        address: Address,
235        span: ByteSpan,
236    },
237    Own {
238        arg: &'a Expr,
239        address: Address,
240        span: ByteSpan,
241    },
242    /// `x[i]`: `of`, what `x` is bound to, read at index `arg`.
243    Signal {
244        name: &'a str,
245        of: Thunk<'a>,
246        arg: &'a Expr,
247        span: ByteSpan,
248    },
249}
250
251/// One file's body beside what its parameters stand for: the body is shared, the scope is not.
252/// An instance lasts while a root or a reader holds it.
253#[derive(Debug)]
254pub struct Instances {
255    pub(crate) scopes: Vec<Option<Scope>>,
256    free: Vec<ScopeId>,
257    nodes: BTreeMap<String, Instance>,
258    edges: Edges,
259    pub(crate) own_terms: BTreeMap<String, String>,
260    files: BTreeMap<String, BTreeSet<String>>,
261    journal: Journal,
262    pub(crate) time: Expr,
263    /// The rate in use, whose step `sp` is.
264    pub(crate) rate: u32,
265}
266
267impl Instances {
268    pub fn new(rate: u32) -> Instances {
269        Instances {
270            scopes: vec![Some(Scope::new(Vec::new(), false))],
271            free: Vec::new(),
272            nodes: BTreeMap::new(),
273            edges: Edges::default(),
274            own_terms: BTreeMap::new(),
275            files: BTreeMap::new(),
276            journal: Journal::default(),
277            time: Expr::Var("t".to_string()),
278            rate,
279        }
280    }
281
282    pub(crate) fn rate(&self) -> u32 {
283        self.rate
284    }
285
286    pub(crate) fn grid(&self) -> Grid {
287        Grid::of(self.rate)
288    }
289
290    pub(crate) fn cx(&self, scope: ScopeId) -> Cx<'_> {
291        Cx {
292            scope,
293            time: None,
294            grid: self.grid(),
295        }
296    }
297
298    pub fn origin(&self, instance: &str) -> Option<&str> {
299        self.nodes.get(instance).map(|held| held.file.as_str())
300    }
301
302    pub fn deps(&self, path: &str) -> &[String] {
303        self.edges.of(path)
304    }
305
306    pub(crate) fn readers(&self, path: &str) -> impl Iterator<Item = &str> {
307        self.edges.readers(path)
308    }
309
310    /// `name` held as `held`, filed under its file: the one way an instance is added.
311    fn insert(&mut self, name: String, held: Instance) {
312        let files = self.files.entry(held.file.clone()).or_default();
313        files.insert(name.clone());
314        self.nodes.insert(name, held);
315    }
316
317    /// `name` let go, reading nothing and filed nowhere: the one way an instance goes.
318    fn remove(&mut self, name: &str) -> (Instance, Vec<String>) {
319        let held = self.nodes.remove(name).expect("an instance held");
320        let files = self
321            .files
322            .get_mut(&held.file)
323            .expect("its file's instances");
324        files.remove(name);
325        if files.is_empty() {
326            self.files.remove(&held.file);
327        }
328        (held, self.edges.cleared(name))
329    }
330
331    pub(crate) fn scope(&self, id: ScopeId) -> &Scope {
332        self.scopes[id as usize]
333            .as_ref()
334            .expect("a scope something holds")
335    }
336
337    pub fn paths(&self) -> impl Iterator<Item = &str> {
338        self.nodes.keys().map(String::as_str)
339    }
340
341    pub fn holds(&self, path: &str) -> bool {
342        self.nodes.contains_key(path)
343    }
344
345    /// A bare file name is the file read as a root, on its own terms; a call site's
346    /// instance carries the bindings that made it.
347    pub fn instance_of(&self, target: &str) -> Result<String, EngineError> {
348        if self.holds(target) {
349            return Ok(target.to_string());
350        }
351        if let Some(own) = self.own_terms.get(target) {
352            return Ok(own.clone());
353        }
354        sole(
355            target,
356            self.instances_of(target).map(|p| (p.clone(), p)).collect(),
357        )
358    }
359
360    pub fn instances_of<'a>(&'a self, file: &'a str) -> impl Iterator<Item = String> + 'a {
361        let held = self.files.get(file).into_iter().flatten();
362        held.map(String::clone)
363    }
364
365    pub fn at<'a>(&'a self, path: &str) -> Option<(&'a Expr, Cx<'a>)> {
366        let thunk = self.nodes.get(path)?.body.thunk();
367        Some((thunk.expr, self.cx(thunk.scope)))
368    }
369
370    pub fn bindings<'a>(&'a self, path: &str) -> Option<Vec<(&'a str, &'a Expr, Cx<'a>)>> {
371        let scope = self.nodes.get(path)?.body.scope;
372        Some(
373            self.vars(scope)
374                .map(|(name, value)| (name, value.expr, self.cx(value.scope)))
375                .collect(),
376        )
377    }
378
379    /// Each name `scope` binds, and what it stands for.
380    pub(crate) fn vars(&self, scope: ScopeId) -> impl Iterator<Item = (&str, Thunk<'_>)> {
381        let vars = self.scope(scope).vars.iter();
382        vars.map(|(name, bound)| (name.as_str(), bound.thunk()))
383    }
384
385    /// Every name any scope binds.
386    pub(crate) fn bound_names(&self) -> impl Iterator<Item = &str> {
387        let vars = self
388            .scopes
389            .iter()
390            .flatten()
391            .flat_map(|scope| scope.vars.iter());
392        vars.map(|(name, _)| name.as_str())
393    }
394}
395
396/// The one node a written name stands for: a file one argument tuple expanded is that
397/// instance, and a file several tuples share is none of them.
398pub(crate) fn sole<T>(target: &str, mut held: Vec<(String, T)>) -> Result<T, EngineError> {
399    match held.len() {
400        0 => Err(EngineError::UnknownNode(target.to_string())),
401        1 => Ok(held.remove(0).1),
402        _ => Err(EngineError::AmbiguousNode(
403            target.to_string(),
404            held.into_iter().map(|(name, _)| name).collect(),
405        )),
406    }
407}
408
409fn implicit_time(name: &str) -> bool {
410    sva_ast::FINITE_DIFFERENCE.contains(&name)
411        || sva_ast::MODAL.contains(&name)
412        || matches!(name, "noise" | "stft" | "istft")
413}
414
415impl Instances {
416    #[inline]
417    pub(crate) fn binds(&self, scope: ScopeId, name: &str) -> Option<Thunk<'_>> {
418        let bound = self.scope(scope).get(name, packed(name))?;
419        Some(bound.thunk())
420    }
421
422    /// A bound name continues in what it stands for; anything else is read where it stands.
423    #[inline]
424    pub fn follow<'a, R>(
425        &'a self,
426        e: &'a Expr,
427        cx: Cx<'a>,
428        go: impl FnOnce(&'a Expr, Cx<'_>) -> R,
429    ) -> Option<R> {
430        match self.step(e, cx)? {
431            Move::Here(e2, cx2) => Some(go(e2, cx2)),
432            Move::Shifted(e2, scope, when) => {
433                let moved = Time { expr: when, cx };
434                Some(go(
435                    e2,
436                    Cx {
437                        scope,
438                        time: Some(&moved),
439                        grid: cx.grid,
440                    },
441                ))
442            }
443        }
444    }
445
446    /// A walk of two at once needs both shifts in one frame, so it cannot take the closure.
447    #[inline(always)]
448    fn step<'a>(&'a self, e: &'a Expr, cx: Cx<'a>) -> Option<Move<'a>> {
449        match e {
450            Expr::Var(name) if name == "t" => cx.time.map(|t| Move::Here(t.expr, t.cx)),
451            Expr::Var(name) => self
452                .binds(cx.scope, name)
453                .map(|b| Move::Here(b.expr, cx.under(b.scope))),
454            Expr::Call { name, args, .. } => {
455                let bound = self.binds(cx.scope, name)?;
456                let [Arg::Pos(when)] = args.as_slice() else {
457                    return None;
458                };
459                Some(Move::Shifted(bound.expr, bound.scope, when))
460            }
461            _ => None,
462        }
463    }
464
465    #[inline]
466    pub fn node<'a>(&'a self, e: &'a Expr, cx: Cx<'_>) -> Node<'a> {
467        match e {
468            Expr::Lit(l) => Node::Lit(l),
469            Expr::Var(name) => Node::Name(name),
470            Expr::Bin(op, l, r) => Node::Bin(*op, l, r),
471            Expr::SelfRef { arg, address, span } => Node::Own {
472                arg,
473                address: *address,
474                span: *span,
475            },
476            Expr::Ref {
477                path,
478                arg,
479                address,
480                span,
481                ..
482            } => Node::Read {
483                path: self.site(e, cx.scope, path),
484                arg,
485                address: *address,
486                span: *span,
487            },
488            Expr::Indexed { name, arg, span } => match self.binds(cx.scope, name) {
489                Some(of) => Node::Signal {
490                    name,
491                    of,
492                    arg,
493                    span: *span,
494                },
495                None => Node::Name(name),
496            },
497            Expr::Call { name, args, span } if is_builtin(name) => Node::Call {
498                name,
499                args,
500                span: *span,
501            },
502            Expr::Call { name, span, .. } => Node::Read {
503                path: self.site(e, cx.scope, name),
504                arg: &self.time,
505                address: Address::Time,
506                span: *span,
507            },
508        }
509    }
510
511    fn site<'a>(&'a self, e: &Expr, scope: ScopeId, written: &'a str) -> &'a str {
512        match self
513            .scope(scope)
514            .sites
515            .get(&(std::ptr::from_ref(e) as usize))
516        {
517            Some(child) => child,
518            None => written,
519        }
520    }
521
522    /// A parameter's signal at the reader's own `t`: no shift the reader is under moves it.
523    pub(crate) fn signal<'a>(&self, of: Thunk<'a>, cx: Cx<'a>) -> Cx<'a> {
524        Cx {
525            scope: of.scope,
526            time: None,
527            grid: cx.grid,
528        }
529    }
530
531    pub(crate) fn is_now(&self, e: &Expr, cx: Cx) -> bool {
532        if let Some(r) = self.follow(e, cx, |e2, cx2| self.is_now(e2, cx2)) {
533            return r;
534        }
535        matches!(self.node(e, cx), Node::Name(n) if n == "t")
536    }
537
538    /// One hop down; `self(...)` is no edge.
539    fn direct_deps(&self, path: &str) -> Vec<String> {
540        let (e, cx) = self.at(path).expect("an instance");
541        let mut out = Vec::new();
542        self.direct_refs(e, cx, &mut out);
543        out.sort();
544        out.dedup();
545        out
546    }
547
548    fn direct_refs(&self, e: &Expr, cx: Cx, out: &mut Vec<String>) {
549        if self
550            .follow(e, cx, |e2, cx2| self.direct_refs(e2, cx2, out))
551            .is_some()
552        {
553            return;
554        }
555        match self.node(e, cx) {
556            Node::Lit(_) | Node::Name(_) => {}
557            Node::Bin(_, l, r) => {
558                self.direct_refs(l, cx, out);
559                self.direct_refs(r, cx, out);
560            }
561            Node::Call { args, .. } => {
562                for a in args {
563                    let (Arg::Pos(x) | Arg::Named(_, x)) = a;
564                    self.direct_refs(x, cx, out);
565                }
566            }
567            Node::Read { path, arg, .. } => {
568                out.push(path.to_string());
569                self.direct_refs(arg, cx, out);
570            }
571            Node::Own { arg, .. } => self.direct_refs(arg, cx, out),
572            Node::Signal { of, arg, .. } => {
573                self.direct_refs(of.expr, self.signal(of, cx), out);
574                self.direct_refs(arg, cx, out);
575            }
576        }
577    }
578
579    pub fn reads_self(&self, path: &str) -> bool {
580        self.at(path).is_some_and(|(e, cx)| self.holds_self(e, cx))
581    }
582
583    pub(crate) fn holds_self(&self, e: &Expr, cx: Cx) -> bool {
584        if let Some(r) = self.follow(e, cx, |e2, cx2| self.holds_self(e2, cx2)) {
585            return r;
586        }
587        match self.node(e, cx) {
588            Node::Lit(_) | Node::Name(_) => false,
589            Node::Own { .. } => true,
590            Node::Bin(_, l, r) => self.holds_self(l, cx) || self.holds_self(r, cx),
591            Node::Read { arg, .. } | Node::Signal { arg, .. } => self.holds_self(arg, cx),
592            Node::Call { args, .. } => args.iter().any(|a| {
593                let (Arg::Pos(x) | Arg::Named(_, x)) = a;
594                self.holds_self(x, cx)
595            }),
596        }
597    }
598
599    /// A value depending on WHICH sample is written rather than on the time handed to it.
600    pub(crate) fn position_dependent(&self, e: &Expr, cx: Cx) -> Option<String> {
601        if let Some(r) = self.follow(e, cx, |e2, cx2| self.position_dependent(e2, cx2)) {
602            return r;
603        }
604        match self.node(e, cx) {
605            Node::Lit(_) | Node::Name(_) => None,
606            Node::Own { .. } => Some("self".to_string()),
607            Node::Bin(_, l, r) => self
608                .position_dependent(l, cx)
609                .or_else(|| self.position_dependent(r, cx)),
610            Node::Read { arg, .. } | Node::Signal { arg, .. } => self.position_dependent(arg, cx),
611            Node::Call { name, args, .. } => {
612                if crate::vocabulary::shape(name).is_some() || name == "crop" || implicit_time(name)
613                {
614                    return Some(name.to_string());
615                }
616                args.iter().find_map(|a| {
617                    let (Arg::Pos(x) | Arg::Named(_, x)) = a;
618                    self.position_dependent(x, cx)
619                })
620            }
621        }
622    }
623
624    /// Two arguments are one tuple when they MEAN the same, walked both at once.
625    pub(crate) fn same<'x, 'y>(&self, a: &Expr, ax: Cx<'x>, b: &Expr, by: Cx<'y>) -> bool {
626        if let Some(moved) = self.step(a, ax) {
627            return match moved {
628                Move::Here(a2, ax2) => self.same(a2, ax2, b, by),
629                Move::Shifted(a2, scope, when) => {
630                    let moved = Time { expr: when, cx: ax };
631                    self.same(
632                        a2,
633                        Cx {
634                            scope,
635                            time: Some(&moved),
636                            grid: ax.grid,
637                        },
638                        b,
639                        by,
640                    )
641                }
642            };
643        }
644        if let Some(moved) = self.step(b, by) {
645            return match moved {
646                Move::Here(b2, by2) => self.same(a, ax, b2, by2),
647                Move::Shifted(b2, scope, when) => {
648                    let moved = Time { expr: when, cx: by };
649                    self.same(
650                        a,
651                        ax,
652                        b2,
653                        Cx {
654                            scope,
655                            time: Some(&moved),
656                            grid: by.grid,
657                        },
658                    )
659                }
660            };
661        }
662        match (self.node(a, ax), self.node(b, by)) {
663            (Node::Lit(x), Node::Lit(y)) => x == y,
664            (Node::Name(x), Node::Name(y)) => x == y,
665            (Node::Bin(o1, l1, r1), Node::Bin(o2, l2, r2)) => {
666                o1 == o2 && self.same(l1, ax, l2, by) && self.same(r1, ax, r2, by)
667            }
668            (
669                Node::Own {
670                    arg: x, address: i, ..
671                },
672                Node::Own {
673                    arg: y, address: j, ..
674                },
675            ) => i == j && self.same(x, ax, y, by),
676            (
677                Node::Read {
678                    path: p,
679                    arg: x,
680                    address: i,
681                    ..
682                },
683                Node::Read {
684                    path: q,
685                    arg: y,
686                    address: j,
687                    ..
688                },
689            ) => p == q && i == j && self.same(x, ax, y, by),
690            (Node::Signal { of: f, arg: x, .. }, Node::Signal { of: g, arg: y, .. }) => {
691                self.same(f.expr, self.signal(f, ax), g.expr, self.signal(g, by))
692                    && self.same(x, ax, y, by)
693            }
694            (
695                Node::Call {
696                    name: n1, args: a1, ..
697                },
698                Node::Call {
699                    name: n2, args: a2, ..
700                },
701            ) => {
702                n1 == n2
703                    && a1.len() == a2.len()
704                    && a1.iter().zip(a2).all(|(x, y)| match (x, y) {
705                        (Arg::Pos(x), Arg::Pos(y)) => self.same(x, ax, y, by),
706                        (Arg::Named(k1, x), Arg::Named(k2, y)) => {
707                            k1 == k2 && self.same(x, ax, y, by)
708                        }
709                        _ => false,
710                    })
711            }
712            _ => false,
713        }
714    }
715
716    pub(crate) fn same_binds(&self, scope: ScopeId, binds: &[(String, Bound)]) -> bool {
717        let held = &self.scope(scope).vars;
718        held.len() == binds.len()
719            && held.iter().zip(binds).all(|((k1, v1), (k2, v2))| {
720                let (v1, v2) = (v1.thunk(), v2.thunk());
721                k1 == k2 && self.same(v1.expr, self.cx(v1.scope), v2.expr, self.cx(v2.scope))
722            })
723    }
724}