Skip to main content

geop_core_program/
placed.rs

1//! Placed parts: the part another program builds, placed in a part as a
2//! node of its own ([`placed`]), whose value is the built part
3//! ([`PlacedPart`]), made by [`build_placed`].
4//!
5//! A placed part is built from exactly its program, its inputs and the
6//! slice of the placing part's state under its name — the state
7//! `/bolt:b.pose` of the part is the state `b.pose` of the part placed as
8//! `bolt` ([`key_behind`]). Equal builds are shared through the runner's build cache: a
9//! thousand screws of one size are one build. Where the part goes is no
10//! concern of the build: the pose that places it is the placing part's
11//! state, read by whatever draws or mates it.
12//!
13//! An element of a placed part is named by the instance's path before its
14//! target's name — `/bolt:model`, or `/asm/bolt:model` for a part placed in
15//! a placed part, and `/:model` for one of the part itself ([`Located`]) —
16//! and read through the placing
17//! part's reader ([`crate::reader::Reader::retrieve_placed`],
18//! [`crate::reader::Reader::placed_part`]), which records it as read inside
19//! the placed part: a node reading the second link of an arm is made again
20//! when that link changes, not when anything in the arm does.
21//!
22//! A part placed reads the part placing it in context, while it is built
23//! there, through the path [`OUTSIDE`]: `/../bracket:model` is the model
24//! of the part placed as `bracket` beside it — placed before it, so that no
25//! two parts read each other — and `/..:model` the placing part's own (see
26//! [`crate::reader::Reader::outside`]). What it read so is read by the
27//! placing part for it, so a change there builds it again, and a build of
28//! it is shared only where what it read there reads the same.
29
30use std::{
31    collections::HashMap,
32    hash::{Hash, Hasher},
33    rc::Rc,
34};
35
36use geop_core_math::geop_error::{GeopError, GeopResult};
37
38use crate::{
39    part::{Part, StepContext},
40    program::{Program, ProgramStep},
41    reader::Reader,
42    target::{Read, Target, TargetReference},
43    values::{Settings, StateValue, StateValues},
44    workspace::Location,
45};
46
47/// What separates the names of a path of instances, and begins a located
48/// name: `/asm/bolt:model`.
49pub const INSTANCE_SEPARATOR: char = '/';
50
51/// The name of the part a part is placed in, in a path of instances: the
52/// part placing it, read in context — `/../bracket:model`.
53pub const OUTSIDE: &str = "..";
54
55/// What separates a located name's instance path from the name in that
56/// instance: `/asm/bolt:model`. A name is split at the first one after the
57/// path, so the name itself may hold more: `/asm:placed:bolt`.
58pub const TARGET_SEPARATOR: char = ':';
59
60/// The path of the instance `name` of the instance at `outer`: `asm/bolt`
61/// — `name` itself, for none. A path is held without the leading
62/// [`INSTANCE_SEPARATOR`] its located names are spelled with. A `name`
63/// through the part `outer` is placed in ([`OUTSIDE`]) goes up out of it:
64/// `../bracket` of `asm/p` is `asm/bracket`.
65pub fn instance_path(outer: &str, name: &str) -> String {
66    let (mut outer, mut name) = (outer, name);
67    while !outer.is_empty() {
68        let up = match name.strip_prefix(OUTSIDE) {
69            Some("") => "",
70            Some(rest) => match rest.strip_prefix(INSTANCE_SEPARATOR) {
71                Some(rest) => rest,
72                None => break,
73            },
74            None => break,
75        };
76        outer = split_instance_path(outer).0;
77        name = up;
78    }
79    match (outer.is_empty(), name.is_empty()) {
80        (true, _) => name.to_string(),
81        (false, true) => outer.to_string(),
82        (false, false) => format!("{outer}{INSTANCE_SEPARATOR}{name}"),
83    }
84}
85
86/// The path of the instance the instance at `path` is placed in, and its
87/// name there: `("asm", "bolt")` for `asm/bolt`, an empty path for an
88/// instance of the part itself — [`instance_path`] undone.
89pub fn split_instance_path(path: &str) -> (&str, &str) {
90    path.rsplit_once(INSTANCE_SEPARATOR).unwrap_or(("", path))
91}
92
93/// A name behind the path of the instance it is in: a target of a part
94/// placed in the part — however deep — or of the part itself, its path
95/// empty. Spelled `/asm/bolt:model`, and `/:model` for the part itself
96/// ([`Located::parse`], its `Display`), and saved and sent to a front end
97/// so.
98///
99/// The tables of one part — its state, its inputs — name what is its own
100/// bare, `b.pose`, and what is of a part placed in it located,
101/// `/bolt:b.pose` ([`Located::key`], [`key_behind`]).
102#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
103pub struct Located {
104    path: String,
105    name: String,
106}
107
108impl Located {
109    /// The name `name` of the part itself.
110    pub fn new(name: impl Into<String>) -> Self {
111        Self::in_instance("", name)
112    }
113
114    /// The name `name` of the instance at `path`: `asm/bolt`, empty for the
115    /// part itself.
116    pub fn in_instance(path: impl Into<String>, name: impl Into<String>) -> Self {
117        Self {
118            path: path.into(),
119            name: name.into(),
120        }
121    }
122
123    /// The path of the instance it is in, without the leading separator:
124    /// `asm/bolt`, empty for the part itself.
125    pub fn path(&self) -> &str {
126        &self.path
127    }
128
129    /// The name in that instance.
130    pub fn name(&self) -> &str {
131        &self.name
132    }
133
134    /// The same name as its instance's own part names it.
135    pub fn local(&self) -> Self {
136        Self::new(self.name.clone())
137    }
138
139    /// The same name of the instance `instance` of the part — of the
140    /// instance at that path, if it is already of one: what a name of a
141    /// placed part is, read from the part it is placed in.
142    pub fn behind(&self, instance: &str) -> Self {
143        let path = match self.path.is_empty() {
144            true => instance.to_string(),
145            false => instance_path(instance, &self.path),
146        };
147        Self::in_instance(path, self.name.clone())
148    }
149
150    /// The name `text` spells: `/asm/bolt:model`, or `/:model` for the
151    /// part itself. Anything else is an error, a bare `model` too: a name
152    /// that does not say where it is would be read in the wrong part.
153    pub fn parse(text: &str) -> GeopResult<Self> {
154        let error = |why: &str| {
155            GeopError::new(format!(
156                "{text:?} names nothing: {why}; a name is spelled \
157                 `/asm/bolt:model`, or `/:model` for the part itself"
158            ))
159        };
160        let rest = (text.strip_prefix(INSTANCE_SEPARATOR))
161            .ok_or_else(|| error("it does not begin with `/`"))?;
162        let (path, name) = (rest.split_once(TARGET_SEPARATOR))
163            .ok_or_else(|| error("it has no `:` before the name"))?;
164        if name.is_empty() {
165            return Err(error("the name is empty"));
166        }
167        if !path.is_empty() && path.split(INSTANCE_SEPARATOR).any(str::is_empty) {
168            return Err(error("an instance of its path is empty"));
169        }
170        Ok(Self::in_instance(path, name))
171    }
172
173    /// It as a key of a part's own table: its bare name for one of the
174    /// part itself, else as it is spelled.
175    pub fn key(&self) -> String {
176        match self.path.is_empty() {
177            true => self.name.clone(),
178            false => self.to_string(),
179        }
180    }
181
182    /// The name a key of a part's own table names ([`Located::key`]).
183    pub fn of_key(key: &str) -> GeopResult<Self> {
184        match key.starts_with(INSTANCE_SEPARATOR) {
185            true => Self::parse(key),
186            false => Ok(Self::new(key)),
187        }
188    }
189}
190
191impl std::fmt::Display for Located {
192    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
193        let (path, name) = (&self.path, &self.name);
194        write!(f, "{INSTANCE_SEPARATOR}{path}{TARGET_SEPARATOR}{name}")
195    }
196}
197
198impl std::fmt::Debug for Located {
199    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
200        write!(f, "{:?}", self.to_string())
201    }
202}
203
204impl std::str::FromStr for Located {
205    type Err = GeopError;
206
207    fn from_str(text: &str) -> GeopResult<Self> {
208        Self::parse(text)
209    }
210}
211
212impl serde::Serialize for Located {
213    fn serialize<Scalar: serde::Serializer>(
214        &self,
215        serializer: Scalar,
216    ) -> Result<Scalar::Ok, Scalar::Error> {
217        serializer.collect_str(self)
218    }
219}
220
221impl<'de> serde::Deserialize<'de> for Located {
222    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
223        let text = String::deserialize(deserializer)?;
224        Self::parse(&text).map_err(serde::de::Error::custom)
225    }
226}
227
228/// The key `key` of the part placed as `instance` — its own, `b.pose`, or
229/// one of a part placed in it, `/bolt:b.pose` — as a key of the part it is
230/// placed in: `/asm:b.pose`, `/asm/bolt:b.pose` (see [`Located::key`]).
231pub fn key_behind(instance: &str, key: &str) -> String {
232    match key.starts_with(INSTANCE_SEPARATOR) {
233        true => format!("{INSTANCE_SEPARATOR}{instance}{key}"),
234        false => format!("{INSTANCE_SEPARATOR}{instance}{TARGET_SEPARATOR}{key}"),
235    }
236}
237
238/// The key of the part placed as `instance` that `key`, a key of the part
239/// it is placed in, is, if it is one of that part: [`key_behind`] undone.
240pub fn key_inside<'k>(instance: &str, key: &'k str) -> Option<&'k str> {
241    let rest = (key.strip_prefix(INSTANCE_SEPARATOR))?.strip_prefix(instance)?;
242    match rest.strip_prefix(TARGET_SEPARATOR) {
243        Some(own) => Some(own),
244        None => rest.starts_with(INSTANCE_SEPARATOR).then_some(rest),
245    }
246}
247
248/// The outermost instance a key of a part's own table is of, and the key
249/// as that instance's part names it: none for a key of the part's own.
250pub fn outermost_instance(key: &str) -> Option<(&str, &str)> {
251    let rest = key.strip_prefix(INSTANCE_SEPARATOR)?;
252    let end = rest.find([INSTANCE_SEPARATOR, TARGET_SEPARATOR])?;
253    let instance = rest.get(..end)?;
254    Some((instance, key_inside(instance, key)?))
255}
256
257/// Checks that `name` can name an instance: not empty, neither a path nor
258/// a located name, and not [`OUTSIDE`].
259fn check_name(name: &str) -> GeopResult<()> {
260    match name.is_empty()
261        || name == OUTSIDE
262        || name.contains([INSTANCE_SEPARATOR, TARGET_SEPARATOR])
263    {
264        true => Err(GeopError::new(format!(
265            "{name:?} cannot name an instance: it is empty, {OUTSIDE:?}, or contains \
266             {INSTANCE_SEPARATOR:?} or {TARGET_SEPARATOR:?}"
267        ))),
268        false => Ok(()),
269    }
270}
271
272/// An element of a part or of a part placed in it, as it is in its own
273/// part ([`Reader::retrieve_placed`]): where that part is placed, the
274/// step that placed it says.
275#[derive(Clone, Debug, PartialEq)]
276pub struct InInstance<E> {
277    pub element: E,
278    /// The path of the instance it is of: empty for the part itself.
279    pub instance: String,
280}
281
282/// What the target of a placed part is named, before the instance's name.
283pub(crate) const PLACED: &str = "placed:";
284
285/// The target of the part placed as `instance`.
286pub fn placed(instance: &str) -> TargetReference<PlacedPart> {
287    TargetReference::new(format!("{PLACED}{instance}"))
288}
289
290/// A placed part, as built: shared by every instance built alike — and
291/// where its program is, which an editor edits it in.
292#[derive(Clone)]
293pub struct PlacedPart {
294    pub part: Rc<Part>,
295    pub location: Location,
296}
297
298/// The same build, or builds holding the same.
299impl PartialEq for PlacedPart {
300    fn eq(&self, other: &Self) -> bool {
301        Rc::ptr_eq(&self.part, &other.part) || self.part.same_as(&other.part)
302    }
303}
304
305impl Target for PlacedPart {}
306
307/// What a part is placed from: the program file, as the placing program
308/// names it, what its parameters are set to, and the steps run after its
309/// own — none, for the part as its file builds it: a part derived from it,
310/// mirrored, say, otherwise.
311#[derive(Clone, Debug, Default, PartialEq)]
312pub struct PlacedArgs {
313    pub file: String,
314    pub inputs: Settings,
315    pub then: Vec<ProgramStep>,
316}
317
318/// Places the part the program in `args.file` builds as `instance`, its
319/// parameters set to `args.inputs` ([`build_placed`]). Fails if a step of
320/// the part placed fails — after placing it as far as it builds.
321pub fn place(ctx: &mut StepContext, instance: &str, args: &PlacedArgs) -> GeopResult<()> {
322    let ctx_error = |e: GeopError| e.with_context(format!("placing `{instance}`"));
323    ctx.produce(&placed(instance), args, build_placed)
324        .map_err(ctx_error)?;
325    let reader = ctx.reader();
326    let part = reader.part().placed_at(instance, reader.position(), None)?;
327    match part.failures().first() {
328        Some((step, e)) => Err(ctx_error(
329            (e.clone()).with_context(format!("step `{step}` of {:?}", args.file)),
330        )),
331        None => Ok(()),
332    }
333}
334
335/// The make of a placed part (see [`place`]): the program in the file
336/// `args.file` names, run with `args.inputs` and the state the placing part
337/// gives under the instance's name, the program's own where it gives none.
338/// Taken from the runner's build cache where an equal build is there; else
339/// built from the part the instance had before, if it had one, so that
340/// only what reads what changed is made again — placed in the part
341/// `reader` reads, which it reads in context. Records the file and every
342/// state and file the part placed read, and what it read in context.
343pub fn build_placed(reader: &Reader, args: &PlacedArgs) -> GeopResult<PlacedPart> {
344    let own = reader.own().unwrap_or_default();
345    let instance = own.strip_prefix(PLACED).ok_or_else(|| {
346        GeopError::new(format!(
347            "target `{own}` is no placed part's: name it by `placed`"
348        ))
349    })?;
350    check_name(instance)?;
351    build(reader, args, Some(instance))
352}
353
354/// The part the program `args` places builds on its own — with its own
355/// state, placed nowhere: what a step deriving a part from another reads
356/// the bodies of. Shared with every build alike, a placed one's too.
357/// Records the file and every file the part read.
358pub fn build_derived(reader: &Reader, args: &PlacedArgs) -> GeopResult<PlacedPart> {
359    build(reader, args, None)
360}
361
362/// The part the program `args` places builds — placed as `instance` of the
363/// part `reader` reads, with the slice of its state under the instance's
364/// name and reading it in context, or on its own (see [`build_placed`],
365/// [`build_derived`]).
366fn build(reader: &Reader, args: &PlacedArgs, instance: Option<&str>) -> GeopResult<PlacedPart> {
367    let runner = reader.runner()?;
368    let (loaded, location) = reader.program(&args.file)?;
369    let program = match args.then.is_empty() {
370        true => loaded,
371        false => Rc::new(Program {
372            steps: (loaded.steps.iter()).chain(&args.then).cloned().collect(),
373            ..(*loaded).clone()
374        }),
375    };
376    let given = &reader.part().state;
377    // The values the part gives under the instance's name, each with its
378    // name in the placed part: `/bolt:b.pose` is its `b.pose`.
379    let slice: Vec<(&String, &str, &StateValue)> = match instance {
380        Some(instance) => {
381            let prefix = format!("{INSTANCE_SEPARATOR}{instance}");
382            (given.range(prefix.clone()..))
383                .take_while(|(name, _)| name.starts_with(&prefix))
384                .filter_map(|(name, value)| Some((name, key_inside(instance, name)?, value)))
385                .collect()
386        }
387        None => Vec::new(),
388    };
389    let mut state = program.state.clone();
390    state.extend((slice.iter()).map(|(_, inside, value)| (inside.to_string(), (*value).clone())));
391    let key = BuildKey {
392        revision: runner.revision(&location.file),
393        path: location.file.clone(),
394        program: program.clone(),
395        inputs: args.inputs.clone(),
396        state,
397    };
398    let holds = |read: &Read| {
399        (reader.part()).holds(
400            read,
401            reader.position(),
402            reader.own(),
403            Some(runner),
404            reader.outside_reader(),
405        )
406    };
407    let part = match runner.cached(&key, holds) {
408        Some(part) => part,
409        None => {
410            let last = (reader.last())
411                .and_then(|node| node.value.as_ref().ok())
412                .and_then(|value| value.downcast_ref::<PlacedPart>());
413            let mut part = last.map(|last| (*last.part).clone()).unwrap_or_default();
414            let nested = runner.nested(location.clone());
415            nested.build(
416                &mut part,
417                &program.steps,
418                &key.inputs,
419                &key.state,
420                None,
421                instance.map(|_| Rc::new(reader.clone())),
422            );
423            let part = Rc::new(part);
424            runner.cache(key, part.clone());
425            part
426        }
427    };
428    // The slice it is given, and what it read besides: a value of the slice
429    // that changes or goes, or one it read that comes, builds it again, so
430    // its state is always the one it is placed with.
431    for (name, _, value) in &slice {
432        reader.record_raw(Read::State((*name).clone(), Some((*value).clone())));
433    }
434    for name in instance
435        .iter()
436        .flat_map(|instance| (part.states_read.iter()).map(move |name| key_behind(instance, name)))
437    {
438        let value = given.get(&name).cloned();
439        reader.record_raw(Read::State(name, value));
440    }
441    for (path, revision) in &part.files_read {
442        reader.record_raw(Read::File(path.clone(), *revision));
443    }
444    for read in &part.outside_read {
445        reader.record_raw(read.clone());
446    }
447    Ok(PlacedPart { part, location })
448}
449
450impl Reader<'_> {
451    /// What `make` makes of the part this reader reads, whole — a part
452    /// placed, as [`Reader::placed_part`] reads it — made once for every
453    /// reader of the part, with what `make` read recorded for each: every
454    /// screw of a size, one build, is drawn once. `key` names what `make`
455    /// makes: two makes of one key make the same of the same part. A
456    /// reader of a part before one of its steps, or of a target being
457    /// made, makes it each time.
458    pub fn shared<T: 'static>(
459        &self,
460        key: &'static str,
461        make: impl FnOnce(&Reader) -> GeopResult<T>,
462    ) -> GeopResult<Rc<T>> {
463        let part = self.part();
464        let whole = self.position() == part.ran() && self.own().is_none();
465        if whole
466            && let Some((made, reads)) = part.shared.get(key)
467            && let Ok(made) = made.downcast::<T>()
468        {
469            for read in reads.0 {
470                self.record_raw(read);
471            }
472            return Ok(made);
473        }
474        let (made, reads) = self.recorded(make);
475        for read in &reads.0 {
476            self.record_raw(read.clone());
477        }
478        let made = Rc::new(made?);
479        if whole {
480            part.shared.insert(key, (made.clone(), reads));
481        }
482        Ok(made)
483    }
484}
485
486/// What a placed part is built from, exactly: the program — its file, that
487/// file's revision, and the program itself, the same `Rc` while the file
488/// does not change — its inputs and its state.
489#[derive(Clone, Debug)]
490pub(crate) struct BuildKey {
491    path: String,
492    revision: u64,
493    program: Rc<Program>,
494    inputs: Settings,
495    state: StateValues,
496}
497
498/// The same file in the same revision, the same program — the very one, or
499/// one equal to it — and equal inputs and state.
500impl PartialEq for BuildKey {
501    fn eq(&self, other: &Self) -> bool {
502        self.path == other.path
503            && self.revision == other.revision
504            && (Rc::ptr_eq(&self.program, &other.program) || self.program == other.program)
505            && self.inputs == other.inputs
506            && self.state == other.state
507    }
508}
509
510impl BuildKey {
511    /// A hash of it: the same for equal keys.
512    fn hash(&self) -> u64 {
513        let mut hasher = std::hash::DefaultHasher::new();
514        self.path.hash(&mut hasher);
515        self.revision.hash(&mut hasher);
516        // Numbers hash by their text: equal values give equal text.
517        serde_json::to_string(&self.inputs)
518            .unwrap_or_default()
519            .hash(&mut hasher);
520        serde_json::to_string(&self.state)
521            .unwrap_or_default()
522            .hash(&mut hasher);
523        hasher.finish()
524    }
525}
526
527/// The parts placed so far, by what they were built from: what equal
528/// builds share. Swept at the end of every run of a part that places
529/// none of its own, of what no part holds any more.
530#[derive(Default)]
531pub(crate) struct BuildCache(HashMap<u64, Vec<(BuildKey, Rc<Part>)>>);
532
533impl BuildCache {
534    /// The part built from `key`, if one is, every file it read is as it
535    /// read it — `revision` saying how each is now — and every read of the
536    /// part it is placed in `holds`.
537    pub(crate) fn get(
538        &self,
539        key: &BuildKey,
540        revision: impl Fn(&str) -> u64,
541        holds: impl Fn(&Read) -> bool,
542    ) -> Option<Rc<Part>> {
543        let builds = self.0.get(&key.hash())?;
544        (builds.iter())
545            .find(|(built, part)| {
546                built == key
547                    && (part.files_read.iter()).all(|(path, read)| revision(path) == *read)
548                    && part.outside_read.iter().all(&holds)
549            })
550            .map(|(_, part)| part.clone())
551    }
552
553    /// Keeps `part`, built from `key`, in place of a build of the same key
554    /// that read the same of the part it is placed in: builds of one part
555    /// placed in two contexts are two.
556    pub(crate) fn insert(&mut self, key: BuildKey, part: Rc<Part>) {
557        let builds = self.0.entry(key.hash()).or_default();
558        builds.retain(|(built, kept)| *built != key || kept.outside_read != part.outside_read);
559        builds.push((key, part));
560    }
561
562    /// Drops the builds no part holds: only the cache does. A build held
563    /// only by a build dropped goes too.
564    pub(crate) fn sweep(&mut self) {
565        loop {
566            let mut dropped = false;
567            self.0.retain(|_, builds| {
568                let before = builds.len();
569                builds.retain(|(_, part)| Rc::strong_count(part) > 1);
570                dropped |= builds.len() < before;
571                !builds.is_empty()
572            });
573            if !dropped {
574                break;
575            }
576        }
577    }
578
579    /// How many builds it holds.
580    pub(crate) fn len(&self) -> usize {
581        self.0.values().map(Vec::len).sum()
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    /// A located name reads back as it is written: of the part itself, and
590    /// behind a path of instances.
591    #[test]
592    fn located_names_read_back_as_written() {
593        for text in ["/:model", "/a/b:model", "/bolt:b.pose"] {
594            let located = Located::parse(text).unwrap();
595            assert_eq!(located.to_string(), text);
596            let read: Located = serde_json::from_value(serde_json::json!(text)).unwrap();
597            assert_eq!(read, located);
598            assert_eq!(
599                serde_json::to_value(&located).unwrap(),
600                serde_json::json!(text)
601            );
602        }
603        let own = Located::parse("/:model").unwrap();
604        assert_eq!((own.path(), own.name()), ("", "model"));
605        let deep = Located::parse("/a/b:model").unwrap();
606        assert_eq!((deep.path(), deep.name()), ("a/b", "model"));
607        assert_eq!(deep, own.behind("b").behind("a"));
608        assert_eq!(deep.local(), own);
609    }
610
611    /// A name is split at the first `:` after the path: what follows is the
612    /// name, `:` and all.
613    #[test]
614    fn a_located_name_may_hold_a_colon() {
615        let located = Located::parse("/a:b:c").unwrap();
616        assert_eq!((located.path(), located.name()), ("a", "b:c"));
617        assert_eq!(located.to_string(), "/a:b:c");
618        let own = Located::parse("/:placed:bolt").unwrap();
619        assert_eq!((own.path(), own.name()), ("", "placed:bolt"));
620    }
621
622    /// What does not say where it is names nothing.
623    #[test]
624    fn a_name_that_does_not_say_where_it_is_is_refused() {
625        for text in [
626            "model",
627            "a/b:model",
628            "/model",
629            "/a:",
630            "/a//b:model",
631            "/a/:model",
632            "",
633        ] {
634            assert!(Located::parse(text).is_err(), "{text:?}");
635            assert!(serde_json::from_value::<Located>(serde_json::json!(text)).is_err());
636        }
637    }
638
639    /// A key of a part's own table is bare for its own, located for a
640    /// placed part's, and moves behind and back out of an instance.
641    #[test]
642    fn keys_move_behind_an_instance_and_back() {
643        assert_eq!(Located::new("b.pose").key(), "b.pose");
644        assert_eq!(Located::of_key("b.pose").unwrap(), Located::new("b.pose"));
645        assert_eq!(key_behind("bolt", "b.pose"), "/bolt:b.pose");
646        assert_eq!(key_behind("asm", "/bolt:b.pose"), "/asm/bolt:b.pose");
647        assert_eq!(key_inside("asm", "/asm/bolt:b.pose"), Some("/bolt:b.pose"));
648        assert_eq!(key_inside("bolt", "/bolt:b.pose"), Some("b.pose"));
649        assert_eq!(key_inside("bolt", "/bolt2:b.pose"), None);
650        assert_eq!(key_inside("bolt", "b.pose"), None);
651        // Up out of a part, through the part it is placed in.
652        assert_eq!(instance_path("asm/p", "../bracket"), "asm/bracket");
653        assert_eq!(instance_path("p", "../bracket"), "bracket");
654        assert_eq!(instance_path("p", ".."), "");
655        assert_eq!(instance_path("", "../bracket"), "../bracket");
656        assert_eq!(instance_path("a/p", "../../x"), "x");
657        assert_eq!(
658            Located::parse("/../bracket:model").unwrap().behind("p"),
659            Located::new("model").behind("bracket")
660        );
661        assert_eq!(
662            outermost_instance("/asm/bolt:b.pose"),
663            Some(("asm", "/bolt:b.pose"))
664        );
665        assert_eq!(outermost_instance("/bolt:x:y"), Some(("bolt", "x:y")));
666        assert_eq!(outermost_instance("b.pose"), None);
667        let located = Located::of_key("/asm/bolt:b.pose").unwrap();
668        assert_eq!(located.key(), "/asm/bolt:b.pose");
669        assert_eq!((located.path(), located.name()), ("asm/bolt", "b.pose"));
670    }
671
672    /// An instance is named by one name: no path, nothing located.
673    #[test]
674    fn an_instance_name_holds_no_separator() {
675        assert!(check_name("bolt").is_ok());
676        for name in ["", "a/b", "a:b", ".."] {
677            assert!(check_name(name).is_err(), "{name:?}");
678        }
679    }
680}