Skip to main content

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            && other.key.loc.kind >= key.loc.kind
37        {
38            dup_error(&key, &other.key, "data binding");
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            && let Some((_, paramsx)) = def.split_once('(')
92            && let Some((arguments, _)) = paramsx.split_once(')')
93        {
94            for param in arguments.split(',') {
95                params.push(param);
96            }
97        }
98        let mut body = None;
99        if let Some(rep) = block.get_field_value("replace_with") {
100            // TODO: restructure ValueParser to have a separate DatafunctionParser,
101            // so that we don't have to synthesize these brackets.
102            let open_bracket = Token::from_static_str("[", rep.loc);
103            let close_bracket = Token::from_static_str("]", rep.loc);
104            let to_parse = vec![&open_bracket, rep, &close_bracket];
105            let value = ValueParser::new(to_parse).parse();
106            if let LocaValue::Code(mut chain, _) = value {
107                body = Some(take(&mut chain));
108            } else {
109                let msg = "could not parse macro replacement";
110                err(ErrorKey::Datafunctions).msg(msg).loc(rep).push();
111            }
112        }
113        Self { key, block, params, body }
114    }
115
116    pub fn replace(&self, call: &Code) -> Option<CodeChain> {
117        if call.arguments.len() != self.params.len() {
118            let msg = "wrong number of arguments for macro";
119            err(ErrorKey::Datafunctions).msg(msg).loc(&call.name).push();
120            return None;
121        }
122        match self.replace_chain(self.body.as_ref()?, call)? {
123            CodeArg::Chain(chain) => Some(chain),
124            CodeArg::Literal(token) => {
125                let msg = "cannot substitute a literal here";
126                err(ErrorKey::Datafunctions).msg(msg).loc(token).push();
127                None
128            }
129        }
130    }
131
132    fn replace_chain(&self, body_chain: &CodeChain, call: &Code) -> Option<CodeArg> {
133        let mut result = Vec::new();
134        for body_code in &body_chain.codes {
135            if body_code.arguments.is_empty() {
136                // Check if body_code is a macro parameter.
137                // Note: self.params and call.arguments have already been checked to be the same length, so
138                // using the same index on both is ok.
139                if let Some(idx) = self.params.iter().position(|param| param == &body_code.name) {
140                    match &call.arguments[idx] {
141                        CodeArg::Literal(caller_token) => {
142                            // A literal can't be part of a chain, so accept it only if the "chain"
143                            // is only one part.
144                            if body_chain.codes.len() != 1 {
145                                let msg = "cannot substitute a literal here";
146                                err(ErrorKey::Datafunctions).msg(msg).loc(caller_token).push();
147                                return None;
148                            }
149                            return Some(call.arguments[idx].clone());
150                        }
151                        CodeArg::Chain(caller_chain) => {
152                            result.extend_from_slice(&caller_chain.codes);
153                        }
154                    }
155                } else {
156                    result.push(body_code.clone());
157                }
158            } else {
159                result.push(Code {
160                    name: body_code.name.clone(),
161                    arguments: body_code
162                        .arguments
163                        .iter()
164                        .map(|arg| self.replace_arg(arg, call))
165                        .collect::<Option<Vec<_>>>()?,
166                });
167            }
168        }
169        Some(CodeArg::Chain(CodeChain { codes: result.into_boxed_slice() }))
170    }
171
172    fn replace_arg(&self, body_arg: &CodeArg, call: &Code) -> Option<CodeArg> {
173        match body_arg {
174            CodeArg::Chain(body_chain) => self.replace_chain(body_chain, call),
175            CodeArg::Literal(_) => Some(body_arg.clone()),
176        }
177    }
178
179    fn validate(&self, data: &Everything) {
180        let mut vd = Validator::new(&self.block, data);
181        vd.req_field("replace_with");
182        vd.field_value("description");
183        vd.field_value("definition");
184        vd.field_value("replace_with");
185    }
186}