1use 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
47pub const INSTANCE_SEPARATOR: char = '/';
50
51pub const OUTSIDE: &str = "..";
54
55pub const TARGET_SEPARATOR: char = ':';
59
60pub 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
86pub fn split_instance_path(path: &str) -> (&str, &str) {
90 path.rsplit_once(INSTANCE_SEPARATOR).unwrap_or(("", path))
91}
92
93#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
103pub struct Located {
104 path: String,
105 name: String,
106}
107
108impl Located {
109 pub fn new(name: impl Into<String>) -> Self {
111 Self::in_instance("", name)
112 }
113
114 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 pub fn path(&self) -> &str {
126 &self.path
127 }
128
129 pub fn name(&self) -> &str {
131 &self.name
132 }
133
134 pub fn local(&self) -> Self {
136 Self::new(self.name.clone())
137 }
138
139 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 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 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 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
228pub 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
238pub 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
248pub 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
257fn 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#[derive(Clone, Debug, PartialEq)]
276pub struct InInstance<E> {
277 pub element: E,
278 pub instance: String,
280}
281
282pub(crate) const PLACED: &str = "placed:";
284
285pub fn placed(instance: &str) -> TargetReference<PlacedPart> {
287 TargetReference::new(format!("{PLACED}{instance}"))
288}
289
290#[derive(Clone)]
293pub struct PlacedPart {
294 pub part: Rc<Part>,
295 pub location: Location,
296}
297
298impl 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#[derive(Clone, Debug, Default, PartialEq)]
312pub struct PlacedArgs {
313 pub file: String,
314 pub inputs: Settings,
315 pub then: Vec<ProgramStep>,
316}
317
318pub 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
335pub 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
354pub fn build_derived(reader: &Reader, args: &PlacedArgs) -> GeopResult<PlacedPart> {
359 build(reader, args, None)
360}
361
362fn 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 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 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 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#[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
498impl 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 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 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#[derive(Default)]
531pub(crate) struct BuildCache(HashMap<u64, Vec<(BuildKey, Rc<Part>)>>);
532
533impl BuildCache {
534 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 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 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 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 #[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 #[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 #[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 #[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 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 #[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}