tiger_lib/data/
scripted_lists.rs1use std::path::PathBuf;
2use std::sync::RwLock;
3
4use crate::block::Block;
5use crate::context::ScopeContext;
6use crate::everything::Everything;
7use crate::fileset::{FileEntry, FileHandler};
8use crate::helpers::{TigerHashMap, dup_error};
9use crate::parse::ParserMemory;
10use crate::pdxfile::PdxFile;
11use crate::report::{ErrorKey, err};
12use crate::scopes::{Scopes, scope_iterator};
13use crate::token::{Loc, Token};
14use crate::tooltipped::Tooltipped;
15use crate::trigger::validate_trigger;
16use crate::validator::Validator;
17use crate::variables::Variables;
18
19#[derive(Debug, Default)]
20pub struct ScriptedLists {
21 lists: TigerHashMap<&'static str, List>,
22}
23
24impl ScriptedLists {
25 fn load_item(&mut self, key: Token, block: Block) {
26 if let Some(other) = self.lists.get(key.as_str()) {
27 if other.key.loc.kind >= key.loc.kind {
28 dup_error(&key, &other.key, "scripted list");
29 }
30 }
31 self.lists.insert(key.as_str(), List::new(key, block));
32 }
33
34 pub fn scan_variables(&self, registry: &mut Variables) {
35 for item in self.lists.values() {
36 registry.scan(&item.block);
37 }
38 }
39
40 pub fn exists(&self, key: &str) -> bool {
41 self.lists.contains_key(key)
42 }
43
44 pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
45 self.lists.values().map(|item| &item.key)
46 }
47
48 pub fn validate_call(&self, key: &Token, data: &Everything, sc: &mut ScopeContext) {
49 if let Some(item) = self.lists.get(key.as_str()) {
50 item.validate_call(key, data, sc);
51 }
52 }
53
54 pub fn base(&self, item: &Token) -> Option<&Token> {
55 self.lists.get(item.as_str()).and_then(|item| item.block.get_field_value("base"))
56 }
57
58 pub fn validate(&self, data: &Everything) {
59 for item in self.lists.values() {
60 item.validate(data);
61 }
62 }
63}
64
65impl FileHandler<Block> for ScriptedLists {
66 fn subpath(&self) -> PathBuf {
67 PathBuf::from("common/scripted_lists")
68 }
69
70 fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
71 if !entry.filename().to_string_lossy().ends_with(".txt") {
72 return None;
73 }
74
75 PdxFile::read(entry, parser)
76 }
77
78 fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
79 for (key, block) in block.drain_definitions_warn() {
80 self.load_item(key, block);
81 }
82 }
83}
84
85#[derive(Debug)]
86pub struct List {
87 pub key: Token,
88 block: Block,
89 cache: RwLock<TigerHashMap<Loc, ScopeContext>>,
90}
91
92impl List {
93 pub fn new(key: Token, block: Block) -> Self {
94 Self { key, block, cache: RwLock::new(TigerHashMap::default()) }
95 }
96
97 fn cached_compat(&self, key: &Token, sc: &mut ScopeContext, data: &Everything) -> bool {
98 if let Some(our_sc) = self.cache.read().unwrap().get(&key.loc) {
99 sc.expect_compatibility(our_sc, key, data);
100 true
101 } else {
102 false
103 }
104 }
105
106 pub fn validate(&self, data: &Everything) {
107 let mut vd = Validator::new(&self.block, data);
108
109 vd.req_field("base");
110 vd.req_field("conditions");
111
112 if let Some(token) = vd.field_value("base") {
113 let mut sc = ScopeContext::new(Scopes::all(), token);
114 sc.set_strict_scopes(false);
115 if let Some((_, outscope)) = scope_iterator(token, data, &mut sc) {
116 let mut sc = ScopeContext::new_unrooted(outscope, token);
117 sc.set_strict_scopes(false);
118 vd.field_trigger("conditions", Tooltipped::No, &mut sc);
119 } else {
120 err(ErrorKey::MissingItem).msg("no such base list").loc(token).push();
121 }
122 }
123 }
124
125 fn validate_conditions(block: &Block, data: &Everything, sc: &mut ScopeContext) {
126 if let Some(token) = block.get_field_value("base") {
127 if let Some((_, outscope)) = scope_iterator(token, data, sc) {
128 if let Some(block) = block.get_field_block("conditions") {
129 sc.open_scope(outscope, token.clone());
130 validate_trigger(block, data, sc, Tooltipped::No);
131 sc.close();
132 }
133 }
134 }
135 }
136
137 pub fn validate_call(&self, key: &Token, data: &Everything, sc: &mut ScopeContext) {
138 if !self.cached_compat(key, sc, data) {
139 let mut our_sc = ScopeContext::new_unrooted(Scopes::all(), &self.key);
140 our_sc.set_strict_scopes(false);
141 self.cache.write().unwrap().insert(key.loc, our_sc.clone());
142 Self::validate_conditions(&self.block, data, &mut our_sc);
143 sc.expect_compatibility(&our_sc, key, data);
144 self.cache.write().unwrap().insert(key.loc, our_sc);
145 }
146 }
147}