tiger_lib/data/
data_binding.rs

1use std::mem::take;
2use std::path::PathBuf;
3
4use crate::block::Block;
5use crate::data::localization::LocaValue;
6use crate::datatype::{Code, CodeArg, CodeChain};
7use crate::everything::Everything;
8use crate::fileset::{FileEntry, FileHandler};
9use crate::helpers::{TigerHashMap, dup_error};
10use crate::parse::ParserMemory;
11use crate::parse::localization::ValueParser;
12use crate::pdxfile::PdxFile;
13use crate::report::{ErrorKey, err, warn};
14use crate::token::Token;
15use crate::validator::Validator;
16
17#[derive(Clone, Debug, Default)]
18pub struct DataBindings {
19    bindings: TigerHashMap<&'static str, DataBinding>,
20}
21
22impl DataBindings {
23    fn load_macro(&mut self, block: Block) {
24        let key;
25        if let Some(def) = block.get_field_value("definition") {
26            if let Some((splitdef, _)) = def.split_once('(') {
27                key = splitdef;
28            } else {
29                key = def.clone();
30            }
31        } else {
32            warn(ErrorKey::ParseError).msg("missing field `definition`").loc(block).push();
33            return;
34        }
35        if let Some(other) = self.bindings.get(key.as_str()) {
36            if other.key.loc.kind >= key.loc.kind {
37                dup_error(&key, &other.key, "data binding");
38            }
39        }
40        self.bindings.insert(key.as_str(), DataBinding::new(key, block));
41    }
42
43    pub fn get(&self, key: &str) -> Option<&DataBinding> {
44        self.bindings.get(key)
45    }
46
47    pub fn validate(&self, data: &Everything) {
48        for item in self.bindings.values() {
49            item.validate(data);
50        }
51    }
52}
53
54impl FileHandler<Block> for DataBindings {
55    fn subpath(&self) -> PathBuf {
56        PathBuf::from("data_binding")
57    }
58
59    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
60        if !entry.filename().to_string_lossy().ends_with(".txt") {
61            return None;
62        }
63
64        PdxFile::read(entry, parser)
65    }
66
67    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
68        for (key, block) in block.drain_definitions_warn() {
69            if key.is("macro") {
70                self.load_macro(block);
71            } else {
72                let msg = format!("unexpected key {key} in data_binding");
73                warn(ErrorKey::ParseError).msg(msg).loc(key).push();
74            }
75        }
76    }
77}
78
79#[derive(Clone, Debug)]
80pub struct DataBinding {
81    key: Token,
82    block: Block,
83    params: Vec<Token>,
84    body: Option<CodeChain>,
85}
86
87impl DataBinding {
88    fn new(key: Token, block: Block) -> Self {
89        let mut params = Vec::new();
90        if let Some(def) = block.get_field_value("definition") {
91            if let Some((_, paramsx)) = def.split_once('(') {
92                if let Some((arguments, _)) = paramsx.split_once(')') {
93                    for param in arguments.split(',') {
94                        params.push(param);
95                    }
96                }
97            }
98        }
99        let mut body = None;
100        if let Some(rep) = block.get_field_value("replace_with") {
101            // TODO: restructure ValueParser to have a separate DatafunctionParser,
102            // so that we don't have to synthesize these brackets.
103            let open_bracket = Token::from_static_str("[", rep.loc);
104            let close_bracket = Token::from_static_str("]", rep.loc);
105            let to_parse = vec![&open_bracket, rep, &close_bracket];
106            let value = ValueParser::new(to_parse).parse();
107            if let LocaValue::Code(mut chain, _) = value {
108                body = Some(take(&mut chain));
109            } else {
110                let msg = "could not parse macro replacement";
111                err(ErrorKey::Datafunctions).msg(msg).loc(rep).push();
112            }
113        }
114        Self { key, block, params, body }
115    }
116
117    pub fn replace(&self, call: &Code) -> Option<CodeChain> {
118        if call.arguments.len() != self.params.len() {
119            let msg = "wrong number of arguments for macro";
120            err(ErrorKey::Datafunctions).msg(msg).loc(&call.name).push();
121            return None;
122        }
123        match self.replace_chain(self.body.as_ref()?, call)? {
124            CodeArg::Chain(chain) => Some(chain),
125            CodeArg::Literal(token) => {
126                let msg = "cannot substitute a literal here";
127                err(ErrorKey::Datafunctions).msg(msg).loc(token).push();
128                None
129            }
130        }
131    }
132
133    fn replace_chain(&self, body_chain: &CodeChain, call: &Code) -> Option<CodeArg> {
134        let mut result = Vec::new();
135        for body_code in &body_chain.codes {
136            if body_code.arguments.is_empty() {
137                // Check if body_code is a macro parameter.
138                // Note: self.params and call.arguments have already been checked to be the same length, so
139                // using the same index on both is ok.
140                if let Some(idx) = self.params.iter().position(|param| param == &body_code.name) {
141                    match &call.arguments[idx] {
142                        CodeArg::Literal(caller_token) => {
143                            // A literal can't be part of a chain, so accept it only if the "chain"
144                            // is only one part.
145                            if body_chain.codes.len() != 1 {
146                                let msg = "cannot substitute a literal here";
147                                err(ErrorKey::Datafunctions).msg(msg).loc(caller_token).push();
148                                return None;
149                            }
150                            return Some(call.arguments[idx].clone());
151                        }
152                        CodeArg::Chain(caller_chain) => {
153                            result.extend_from_slice(&caller_chain.codes);
154                        }
155                    }
156                } else {
157                    result.push(body_code.clone());
158                }
159            } else {
160                result.push(Code {
161                    name: body_code.name.clone(),
162                    arguments: body_code
163                        .arguments
164                        .iter()
165                        .map(|arg| self.replace_arg(arg, call))
166                        .collect::<Option<Vec<_>>>()?,
167                });
168            }
169        }
170        Some(CodeArg::Chain(CodeChain { codes: result.into_boxed_slice() }))
171    }
172
173    fn replace_arg(&self, body_arg: &CodeArg, call: &Code) -> Option<CodeArg> {
174        match body_arg {
175            CodeArg::Chain(body_chain) => self.replace_chain(body_chain, call),
176            CodeArg::Literal(_) => Some(body_arg.clone()),
177        }
178    }
179
180    fn validate(&self, data: &Everything) {
181        let mut vd = Validator::new(&self.block, data);
182        vd.req_field("replace_with");
183        vd.field_value("description");
184        vd.field_value("definition");
185        vd.field_value("replace_with");
186    }
187}