Skip to main content

interscript_rs/
document.rs

1//! The document model — mirrors `Interscript::Node::Document` as
2//! produced by `Interscript::Isc::NodeAdapter`, plus the pieces the
3//! interpreter needs (resolved dependency documents, embedded tests).
4
5use crate::item::Item;
6use crate::stdlib::Funcall;
7use std::collections::BTreeMap;
8use std::rc::Rc;
9
10#[derive(Debug, Clone)]
11pub struct Sub {
12    pub from: Item,
13    pub to: SubTo,
14    pub before: Option<Item>,
15    pub not_before: Option<Item>,
16    pub after: Option<Item>,
17    pub not_after: Option<Item>,
18    /// Number of capture groups in regex order
19    /// (before, not_before, from, not_after, after).
20    pub group_count: usize,
21    /// Ruby `reverse_run:`: `Some(true)` skips the rule in the forward
22    /// direction and runs it in the reversed document (and vice versa).
23    /// The ISC grammar cannot express it; only `Document::reverse`
24    /// produces it.
25    pub reverse_run: Option<bool>,
26}
27
28#[derive(Debug, Clone)]
29pub enum SubTo {
30    /// `to upcase` — upcase the whole match.
31    Upcase,
32    /// `to downcase` — downcase the whole match.
33    Downcase,
34    /// A replacement item.
35    Item(Item),
36}
37
38impl Sub {
39    /// `Interscript::Node::Rule::Sub#max_length`.
40    pub fn max_length(&self) -> usize {
41        let mut len = self.from.max_length();
42        for c in [&self.before, &self.not_before, &self.after, &self.not_after] {
43            if let Some(item) = c {
44                len += item.max_length();
45            }
46        }
47        len
48    }
49}
50
51#[derive(Debug, Clone)]
52pub enum StageItem {
53    /// parallel { ... } — single simultaneous pass.
54    Parallel(Vec<Sub>),
55    /// sequence { ... } rules and bare rules — sequential gsub passes
56    /// (the Ruby adapter flattens sequence blocks into the stage).
57    Sequential(Vec<Sub>),
58    /// run stage.x / run map.<dep>.stage.x
59    Run {
60        dep: Option<String>,
61        stage: String,
62        reverse_run: Option<bool>,
63    },
64    /// A stdlib function application.
65    Funcall(Funcall),
66}
67
68#[derive(Debug, Clone)]
69pub struct Stage {
70    pub name: String,
71    pub items: Vec<StageItem>,
72    /// Ruby `reverse_run:` at stage level (Group#reverse toggles it).
73    pub reverse_run: Option<bool>,
74}
75
76#[derive(Debug, Clone)]
77pub struct AliasDef {
78    pub name: String,
79    pub value: Item,
80}
81
82#[derive(Debug, Clone)]
83pub struct Dependency {
84    /// The located map name (`dependency "x"`).
85    pub target: String,
86    /// The `as` alias name; None = library import.
87    pub alias: Option<String>,
88}
89
90#[derive(Debug, Clone, Default)]
91pub struct Tests {
92    pub data: Vec<(String, String)>,
93}
94
95/// A parsed, dependency-resolved map document.
96pub struct Document {
97    /// The map id (file stem; how `Interscript.transliterate` addresses it).
98    pub name: String,
99    pub system_code: String,
100    pub aliases: BTreeMap<String, Item>,
101    pub stages: BTreeMap<String, Stage>,
102    /// Aliased dependencies (stage-run targets), keyed by alias.
103    pub dep_aliases: BTreeMap<String, Rc<Document>>,
104    /// Library imports (unaliased dependencies), loaded documents.
105    pub imports: Vec<Rc<Document>>,
106    pub tests: Tests,
107    /// Raw metadata key/value text pairs (inert for transliteration).
108    pub metadata: Vec<(String, String)>,
109}
110
111impl Document {
112    /// `Interscript::Node::Document#imported_aliases`: each import
113    /// dependency's own aliases merged UNDER the doc's own aliases.
114    pub fn imported_aliases(&self) -> BTreeMap<String, Item> {
115        let mut merged: BTreeMap<String, Item> = BTreeMap::new();
116        for dep in &self.imports {
117            for (k, v) in &dep.aliases {
118                merged.entry(k.clone()).or_insert_with(|| v.clone());
119            }
120        }
121        for (k, v) in &self.aliases {
122            merged.insert(k.clone(), v.clone());
123        }
124        merged
125    }
126
127    /// `imported_stages`: import deps' stages merged under own stages.
128    pub fn imported_stages(&self) -> BTreeMap<String, Stage> {
129        let mut merged: BTreeMap<String, Stage> = BTreeMap::new();
130        for dep in &self.imports {
131            for (k, v) in &dep.stages {
132                merged.entry(k.clone()).or_insert_with(|| v.clone());
133            }
134        }
135        for (k, v) in &self.stages {
136            merged.insert(k.clone(), v.clone());
137        }
138        merged
139    }
140
141    /// Look up an alias through the import chain (word context).
142    pub fn lookup_alias(&self, name: &str) -> Option<Item> {
143        self.imported_aliases().get(name).cloned()
144    }
145}