From f9dcf3f4e2cfd24df323bba1741819bd9de2bd04 Mon Sep 17 00:00:00 2001 From: Byte Northbridge Date: Fri, 8 Aug 2025 14:15:09 -0500 Subject: [PATCH 1/3] fix: criterion benchmark paths --- ck3-tiger/benches/criterion.rs | 13 ++++++++++++- vic3-tiger/benches/criterion.rs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/ck3-tiger/benches/criterion.rs b/ck3-tiger/benches/criterion.rs index c3934cc58..229a59338 100644 --- a/ck3-tiger/benches/criterion.rs +++ b/ck3-tiger/benches/criterion.rs @@ -22,12 +22,23 @@ struct Config { sample_size: Option, } +fn workspace_path(s: &str) -> PathBuf { + let p = PathBuf::from(s); + if p.is_relative() { + PathBuf::from("..").join(p) + } + else { + p + } +} + fn bench_multiple(c: &mut Criterion) { let content = fs::read_to_string(CONFIG_PATH).unwrap(); let config: Config = toml::from_str(&content).unwrap(); - let mut modfile_paths = config.modfile_paths.iter().map(PathBuf::from).collect::>(); + let mut modfile_paths = config.modfile_paths.iter().map(|p| workspace_path(p)).collect::>(); if let Some(modfile_dir) = config.modfile_dir { + let modfile_dir = workspace_path(&modfile_dir); let iter = fs::read_dir(modfile_dir).unwrap().filter_map(|entry| entry.ok()).filter_map(|entry| { entry.file_name().to_string_lossy().ends_with(".mod").then(|| entry.path()) diff --git a/vic3-tiger/benches/criterion.rs b/vic3-tiger/benches/criterion.rs index 3093e8869..0a972560f 100644 --- a/vic3-tiger/benches/criterion.rs +++ b/vic3-tiger/benches/criterion.rs @@ -23,13 +23,24 @@ struct Config { sample_size: Option, } +fn workspace_path(s: &str) -> PathBuf { + let p = PathBuf::from(s); + if p.is_relative() { + PathBuf::from("..").join(p) + } + else { + p + } +} + fn bench_multiple(c: &mut Criterion) { Game::set(Game::Vic3).unwrap(); let content = fs::read_to_string(CONFIG_PATH).unwrap(); let config: Config = toml::from_str(&content).unwrap(); - let mut mod_paths = config.mod_paths.iter().map(PathBuf::from).collect::>(); + let mut mod_paths = config.mod_paths.iter().map(|p| workspace_path(p)).collect::>(); if let Some(mod_dir) = config.mod_dir { + let mod_dir = workspace_path(&mod_dir); let iter = fs::read_dir(mod_dir).unwrap().filter_map(|entry| entry.ok()).filter_map(|entry| { entry.path().join(".metadata/metadata.json").is_file().then(|| entry.path()) From 314677b824fe0f5483ad4f32888c7aa3329fdf69 Mon Sep 17 00:00:00 2001 From: Byte Northbridge Date: Mon, 11 Aug 2025 14:37:47 -0500 Subject: [PATCH 2/3] fix: Expanded report pointers have stable order --- ck3-tiger/benches/criterion.rs | 6 +- ck3-tiger/src/bin/scan-mod-ck3-tiger.rs | 7 +- src/block.rs | 10 +- src/ck3/data/characters.rs | 8 +- src/ck3/data/doctrines.rs | 4 +- src/ck3/data/gameconcepts.rs | 2 +- src/ck3/data/interaction_cats.rs | 2 +- src/ck3/data/maa.rs | 2 +- src/ck3/data/prov_history.rs | 2 +- src/ck3/data/prov_terrain.rs | 10 +- src/ck3/data/provinces.rs | 4 +- src/ck3/data/religions.rs | 2 +- src/ck3/data/title_history.rs | 2 +- src/ck3/data/titles.rs | 2 +- src/ck3/data/traits.rs | 2 +- src/config_load.rs | 10 +- src/data/assets.rs | 2 +- src/data/coa.rs | 4 +- src/data/data_binding.rs | 2 +- src/data/defines.rs | 2 +- src/data/events.rs | 10 +- src/data/gui.rs | 10 +- src/data/localization.rs | 2 +- src/data/music.rs | 2 +- src/data/script_values.rs | 6 +- src/data/scripted_effects.rs | 2 +- src/data/scripted_lists.rs | 6 +- src/data/scripted_modifiers.rs | 2 +- src/data/scripted_triggers.rs | 4 +- src/db.rs | 4 +- src/everything.rs | 4 +- src/hoi4/data/gfx.rs | 4 +- src/hoi4/data/music.rs | 2 +- src/imperator/data/provinces.rs | 4 +- src/lib.rs | 4 +- src/macros.rs | 18 +-- src/parse/cob.rs | 8 +- src/parse/csv.rs | 16 +-- src/parse/json.rs | 32 +++--- src/parse/localization.rs | 38 +++---- src/parse/pdxfile.rs | 22 ++-- src/parse/pdxfile/lexer.rs | 31 +++--- src/parse/pdxfile/parser.lalrpop | 6 +- src/report/builder.rs | 32 ++++-- src/report/error_loc.rs | 46 ++++---- src/report/errors.rs | 50 ++++----- src/report/filter.rs | 2 +- src/report/mod.rs | 4 +- src/report/report_struct.rs | 31 ++++-- src/report/writer.rs | 3 +- src/token.rs | 141 ++++++++++++++++-------- src/trigger.rs | 28 ++--- vic3-tiger/benches/criterion.rs | 3 +- 53 files changed, 374 insertions(+), 288 deletions(-) diff --git a/ck3-tiger/benches/criterion.rs b/ck3-tiger/benches/criterion.rs index 229a59338..48d40d469 100644 --- a/ck3-tiger/benches/criterion.rs +++ b/ck3-tiger/benches/criterion.rs @@ -26,8 +26,7 @@ fn workspace_path(s: &str) -> PathBuf { let p = PathBuf::from(s); if p.is_relative() { PathBuf::from("..").join(p) - } - else { + } else { p } } @@ -35,7 +34,8 @@ fn workspace_path(s: &str) -> PathBuf { fn bench_multiple(c: &mut Criterion) { let content = fs::read_to_string(CONFIG_PATH).unwrap(); let config: Config = toml::from_str(&content).unwrap(); - let mut modfile_paths = config.modfile_paths.iter().map(|p| workspace_path(p)).collect::>(); + let mut modfile_paths = + config.modfile_paths.iter().map(|p| workspace_path(p)).collect::>(); if let Some(modfile_dir) = config.modfile_dir { let modfile_dir = workspace_path(&modfile_dir); diff --git a/ck3-tiger/src/bin/scan-mod-ck3-tiger.rs b/ck3-tiger/src/bin/scan-mod-ck3-tiger.rs index 1dc19d087..81d70e6ca 100644 --- a/ck3-tiger/src/bin/scan-mod-ck3-tiger.rs +++ b/ck3-tiger/src/bin/scan-mod-ck3-tiger.rs @@ -50,12 +50,13 @@ fn main() -> Result<()> { let mut grand_json = json!({}); for itype in Item::iter() { let mut vec = Vec::new(); - for token in everything.iter_keys(itype).filter(|token| token.loc.kind == FileKind::Mod) { + for token in everything.iter_keys(itype).filter(|token| token.loc.ptr.kind == FileKind::Mod) + { let json = json!({ "key": token.to_string(), "file": token.loc.pathname(), - "line": if token.loc.line == 0 { None } else { Some(token.loc.line) }, - "column": if token.loc.column == 0 { None } else { Some(token.loc.column) }, + "line": if token.loc.ptr.line == 0 { None } else { Some(token.loc.ptr.line) }, + "column": if token.loc.ptr.column == 0 { None } else { Some(token.loc.ptr.column) }, }); vec.push(json); } diff --git a/src/block.rs b/src/block.rs index 9ed29e5d0..f307f1bcc 100644 --- a/src/block.rs +++ b/src/block.rs @@ -3,7 +3,7 @@ use crate::date::Date; use crate::macros::MACRO_MAP; use crate::parse::pdxfile::{parse_pdx_macro, MacroComponent, MacroComponentKind, PdxfileMemory}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; mod blockitem; mod bv; @@ -39,7 +39,7 @@ pub struct Block { /// It is in a `Box` to save space in blocks that don't have a tag, which is most of them. pub tag: Option>, /// The location of the start of the block. Used mostly for error reporting. - pub loc: Loc, + pub loc: LocStack, /// If the block is a top-level block and contains macro substitutions, this field will /// hold the original source for re-parsing. /// The source has already been split into a vec that alternates content with macro parameters. @@ -50,7 +50,7 @@ pub struct Block { impl Block { /// Open a new `Block` at the given location. - pub fn new(loc: Loc) -> Self { + pub fn new(loc: LocStack) -> Self { Block { v: Vec::new(), tag: None, loc, source: None } } @@ -463,7 +463,7 @@ impl Block { pub fn expand_macro( &self, args: &[(&str, Token)], - loc: Loc, + loc: LocStack, global: &PdxfileMemory, ) -> Option { let link_index = MACRO_MAP.get_or_insert_loc(loc); @@ -484,7 +484,7 @@ impl Block { let mut val = val.clone(); let orig_loc = val.loc; val.loc = token.loc; - val.loc.column -= 1; // point at the $, it looks better + val.loc.ptr.column -= 1; // point at the $, it looks better val.loc.link_idx = Some(MACRO_MAP.get_or_insert_loc(orig_loc)); content.push(val); break; diff --git a/src/ck3/data/characters.rs b/src/ck3/data/characters.rs index 8c80ae785..958edaf28 100644 --- a/src/ck3/data/characters.rs +++ b/src/ck3/data/characters.rs @@ -488,7 +488,7 @@ impl Character { if birth.is_none() && event != Birth { let msg = format!("{character} was not born yet on {date}"); let mut loc = token.loc; - loc.column = 0; + loc.ptr.column = 0; warn(ErrorKey::History).msg(msg).loc(loc).push(); } @@ -498,7 +498,7 @@ impl Character { "{character} was not alive on {date}, had already died on {death_date}" ); let mut loc = token.loc; - loc.column = 0; + loc.ptr.column = 0; warn(ErrorKey::History) .msg(msg) .loc(loc) @@ -510,7 +510,7 @@ impl Character { match event { Birth => { let mut loc = token.loc; - loc.column = 0; + loc.ptr.column = 0; if let Some((birth_date, birth_loc)) = birth { let msg = format!( @@ -551,7 +551,7 @@ impl Character { } Death => { let mut loc = token.loc; - loc.column = 0; + loc.ptr.column = 0; death = Some((date, loc)); } Posthumous => { diff --git a/src/ck3/data/doctrines.rs b/src/ck3/data/doctrines.rs index 4bc5b4678..106399d9f 100644 --- a/src/ck3/data/doctrines.rs +++ b/src/ck3/data/doctrines.rs @@ -30,7 +30,7 @@ pub struct Doctrines { impl Doctrines { fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.categories.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "doctrine category"); } } @@ -128,7 +128,7 @@ impl FileHandler for Doctrines { } if let Some(other) = self.doctrines.get(doctrine.as_str()) { - if other.key.loc.kind >= doctrine.loc.kind { + if other.key.loc.ptr.kind >= doctrine.loc.ptr.kind { dup_error(doctrine, &other.key, "doctrine"); } } diff --git a/src/ck3/data/gameconcepts.rs b/src/ck3/data/gameconcepts.rs index 279174b0e..b6ed37741 100644 --- a/src/ck3/data/gameconcepts.rs +++ b/src/ck3/data/gameconcepts.rs @@ -20,7 +20,7 @@ pub struct GameConcepts { impl GameConcepts { pub fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.concepts.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "game concept"); } } diff --git a/src/ck3/data/interaction_cats.rs b/src/ck3/data/interaction_cats.rs index bb67f053f..ef71488d4 100644 --- a/src/ck3/data/interaction_cats.rs +++ b/src/ck3/data/interaction_cats.rs @@ -21,7 +21,7 @@ pub struct CharacterInteractionCategories { impl CharacterInteractionCategories { pub fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.categories.get(key.as_str()) { - if other.key.loc.kind == key.loc.kind { + if other.key.loc.ptr.kind == key.loc.ptr.kind { dup_error(&key, &other.key, "interaction category"); } } diff --git a/src/ck3/data/maa.rs b/src/ck3/data/maa.rs index bbfb3eb92..380b7ca02 100644 --- a/src/ck3/data/maa.rs +++ b/src/ck3/data/maa.rs @@ -27,7 +27,7 @@ pub struct MenAtArmsTypes { impl MenAtArmsTypes { pub fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.menatarmstypes.get(key.as_str()) { - if other.key.loc.kind == key.loc.kind { + if other.key.loc.ptr.kind == key.loc.ptr.kind { dup_error(&key, &other.key, "men-at-arms type"); } } diff --git a/src/ck3/data/prov_history.rs b/src/ck3/data/prov_history.rs index 93096866e..72afa77ea 100644 --- a/src/ck3/data/prov_history.rs +++ b/src/ck3/data/prov_history.rs @@ -26,7 +26,7 @@ impl ProvinceHistories { fn load_item(&mut self, id: ProvId, key: Token, mut block: Block) { if let Some(province) = self.provinces.get_mut(&id) { // Multiple entries are valid but could easily be a mistake. - if province.key.loc.kind >= key.loc.kind { + if province.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &province.key, "province"); } province.block.append(&mut block); diff --git a/src/ck3/data/prov_terrain.rs b/src/ck3/data/prov_terrain.rs index c808eb0ff..9c9a57f75 100644 --- a/src/ck3/data/prov_terrain.rs +++ b/src/ck3/data/prov_terrain.rs @@ -8,7 +8,7 @@ use crate::item::Item; use crate::parse::ParserMemory; use crate::pdxfile::PdxFile; use crate::report::{warn, ErrorKey, Severity}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::validator::Validator; use super::provinces::ProvId; @@ -18,14 +18,14 @@ const DEFAULT_TERRAINS: &[&str] = &["default_land", "default_sea", "default_coas #[derive(Clone, Debug, Default)] pub struct ProvinceTerrains { provinces: TigerHashMap, - file_loc: Option, + file_loc: Option, defaults: [Option; DEFAULT_TERRAINS.len()], } impl ProvinceTerrains { fn load_item(&mut self, id: ProvId, key: Token, value: Token) { if let Some(province) = self.provinces.get_mut(&id) { - if province.key.loc.kind >= key.loc.kind { + if province.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &province.key, "province"); } *province = ProvinceTerrain::new(key, value); @@ -79,7 +79,7 @@ impl FileHandler for ProvinceTerrains { self.load_item(id, key, value); } else if let Some(index) = DEFAULT_TERRAINS.iter().position(|&x| x == key.as_str()) { if let Some(default) = &self.defaults[index] { - if default.loc.kind >= key.loc.kind { + if default.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, default, "default terrain"); } } else { @@ -119,7 +119,7 @@ impl ProvinceProperties { fn load_item(&mut self, id: ProvId, key: Token, mut block: Block) { if let Some(province) = self.provinces.get_mut(&id) { // Multiple entries are valid but could easily be a mistake. - if province.key.loc.kind >= key.loc.kind { + if province.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &province.key, "province"); } province.block.append(&mut block); diff --git a/src/ck3/data/provinces.rs b/src/ck3/data/provinces.rs index 1bb3ce8f2..242d13551 100644 --- a/src/ck3/data/provinces.rs +++ b/src/ck3/data/provinces.rs @@ -17,7 +17,7 @@ use crate::parse::csv::{parse_csv, read_csv}; use crate::parse::ParserMemory; use crate::pdxfile::{PdxEncoding, PdxFile}; use crate::report::{err, fatal, report, untidy, warn, ErrorKey, Severity}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::validator::Validator; pub type ProvId = u32; @@ -383,7 +383,7 @@ pub struct Coords { #[allow(dead_code)] // TODO #[derive(Clone, Debug)] pub struct Adjacency { - line: Loc, + line: LocStack, from: ProvId, to: ProvId, /// TODO: check type is sea or `river_large` diff --git a/src/ck3/data/religions.rs b/src/ck3/data/religions.rs index a3b1e47a2..f7481ba25 100644 --- a/src/ck3/data/religions.rs +++ b/src/ck3/data/religions.rs @@ -287,7 +287,7 @@ impl DbKind for Faith { _data: &Everything, ) -> bool { if property == "is_modded" { - return key.loc.kind == FileKind::Mod; + return key.loc.ptr.kind == FileKind::Mod; } false } diff --git a/src/ck3/data/title_history.rs b/src/ck3/data/title_history.rs index 75ecbef43..00c6deb1e 100644 --- a/src/ck3/data/title_history.rs +++ b/src/ck3/data/title_history.rs @@ -25,7 +25,7 @@ impl TitleHistories { pub fn load_item(&mut self, key: Token, mut block: Block) { if let Some(other) = self.histories.get_mut(key.as_str()) { // Multiple entries are valid but could easily be a mistake. - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { warn(ErrorKey::DuplicateItem) .msg("title has two definition blocks, they will be added together") .loc(&other.key) diff --git a/src/ck3/data/titles.rs b/src/ck3/data/titles.rs index ca6297a78..361e01281 100644 --- a/src/ck3/data/titles.rs +++ b/src/ck3/data/titles.rs @@ -82,7 +82,7 @@ impl Titles { is_county_capital: bool, ) { if let Some(other) = self.titles.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "title"); } } diff --git a/src/ck3/data/traits.rs b/src/ck3/data/traits.rs index b6661f91a..ebfeeae95 100644 --- a/src/ck3/data/traits.rs +++ b/src/ck3/data/traits.rs @@ -38,7 +38,7 @@ pub struct Traits { impl Traits { fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.traits.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "trait"); } } diff --git a/src/config_load.rs b/src/config_load.rs index 36edfded5..755691c35 100644 --- a/src/config_load.rs +++ b/src/config_load.rs @@ -11,16 +11,16 @@ use crate::block::{Block, BlockItem, Comparator, Eq::*, Field, BV}; use crate::helpers::stringify_list; use crate::report::{ err, set_predicate, set_show_loaded_mods, set_show_vanilla, Confidence, ErrorKey, ErrorLoc, - FilterRule, PointedMessage, Severity, + FilterRule, PointedMessageStack, Severity, }; /// Checks for legacy ignore blocks (that no longer work) and report an error if they are present. pub fn check_for_legacy_ignore(config: &Block) { // First, report errors if legacy ignore blocks are detected: - let pointers: Vec = config + let pointers: Vec = config .get_keys("ignore") .into_iter() - .map(|key| PointedMessage::new(key.into_loc())) + .map(|key| PointedMessageStack::new(key.into_loc())) .collect(); if !pointers.is_empty() { err(ErrorKey::Config) @@ -406,10 +406,10 @@ pub fn assert_one_key(assert_key: &str, block: &Block) { let pointers = keys .iter() .enumerate() - .map(|(index, key)| PointedMessage { + .map(|(index, key)| PointedMessageStack { loc: key.into_loc(), length: 1, - msg: Some((if index == 0 { "It occurs here" } else { "and here" }).to_owned()), + msg: Some((if index == 0 { "It occurs here" } else { "and here" }).into()), }) .collect(); err(ErrorKey::Config) diff --git a/src/data/assets.rs b/src/data/assets.rs index 970b2df53..a6341d80d 100644 --- a/src/data/assets.rs +++ b/src/data/assets.rs @@ -31,7 +31,7 @@ impl Assets { pub fn load_item(&mut self, key: &Token, block: &Block) { if let Some(name) = block.get_field_value("name") { if let Some(other) = self.assets.get(name.as_str()) { - if other.key.loc.kind >= name.loc.kind { + if other.key.loc.ptr.kind >= name.loc.ptr.kind { dup_error(name, &other.key, "asset"); } } diff --git a/src/data/coa.rs b/src/data/coa.rs index a6920110b..cf5b15113 100644 --- a/src/data/coa.rs +++ b/src/data/coa.rs @@ -31,7 +31,7 @@ impl Coas { if let Some(block) = bv.expect_block() { for (key, block) in block.iter_definitions_warn() { if let Some(other) = self.templates.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if let BV::Block(otherblock) = &other.bv { if otherblock.equivalent(block) { exact_dup_advice(key, &other.key, "coa template"); @@ -49,7 +49,7 @@ impl Coas { } } else { if let Some(other) = self.coas.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if other.bv.equivalent(bv) { exact_dup_advice(key, &other.key, "coat of arms"); } else { diff --git a/src/data/data_binding.rs b/src/data/data_binding.rs index 1a5804d8d..ed7580490 100644 --- a/src/data/data_binding.rs +++ b/src/data/data_binding.rs @@ -33,7 +33,7 @@ impl DataBindings { return; } if let Some(other) = self.bindings.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "data binding"); } } diff --git a/src/data/defines.rs b/src/data/defines.rs index 7118f8591..36864c430 100644 --- a/src/data/defines.rs +++ b/src/data/defines.rs @@ -23,7 +23,7 @@ impl Defines { pub fn load_item(&mut self, group: Token, name: Token, bv: &BV) { let key = format!("{}|{}", &group, &name); if let Some(other) = self.defines.get(&key) { - if other.name.loc.kind >= name.loc.kind && !bv.equivalent(&other.bv) { + if other.name.loc.ptr.kind >= name.loc.ptr.kind && !bv.equivalent(&other.bv) { dup_error(&name, &other.name, "define"); } } diff --git a/src/data/events.rs b/src/data/events.rs index 4a41454d9..028425d7f 100644 --- a/src/data/events.rs +++ b/src/data/events.rs @@ -39,7 +39,7 @@ impl Events { if Game::is_vic3() { // Earlier events override later ones in vic3. // The game will complain but it does work, so don't warn unless warranted. - if other.key.loc.kind <= key.loc.kind { + if other.key.loc.ptr.kind <= key.loc.ptr.kind { dup_error(&other.key, &key, "event"); } return; @@ -58,7 +58,7 @@ impl Events { } fn load_scripted_trigger(&mut self, key: Token, block: Block) { - let index = (key.loc.idx, key.as_str()); + let index = (key.loc.ptr.idx, key.as_str()); if let Some(other) = self.triggers.get(&index) { dup_error(&key, &other.key, "scripted trigger"); } @@ -66,7 +66,7 @@ impl Events { } fn load_scripted_effect(&mut self, key: Token, block: Block) { - let index = (key.loc.idx, key.as_str()); + let index = (key.loc.ptr.idx, key.as_str()); if let Some(other) = self.effects.get(&index) { dup_error(&key, &other.key, "scripted effect"); } @@ -87,13 +87,13 @@ impl Events { #[cfg(feature = "ck3")] pub fn get_trigger(&self, key: &Token) -> Option<&Trigger> { - let index = (key.loc.idx, key.as_str()); + let index = (key.loc.ptr.idx, key.as_str()); self.triggers.get(&index) } #[cfg(feature = "ck3")] pub fn get_effect(&self, key: &Token) -> Option<&Effect> { - let index = (key.loc.idx, key.as_str()); + let index = (key.loc.ptr.idx, key.as_str()); self.effects.get(&index) } diff --git a/src/data/gui.rs b/src/data/gui.rs index 02d4c4a2b..952880f31 100644 --- a/src/data/gui.rs +++ b/src/data/gui.rs @@ -139,7 +139,7 @@ impl Gui { // With gui types the earlier one takes precedence if let Some(other) = self.types.get(&key_lc) { - if other.key.loc.kind <= key.loc.kind { + if other.key.loc.ptr.kind <= key.loc.ptr.kind { dup_error(&other.key, &key, "gui type"); } return; @@ -150,7 +150,7 @@ impl Gui { pub fn load_template(&mut self, key: Token, block: Block) { // With gui templates the earlier one takes precedence if let Some(other) = self.templates.get(key.as_str()) { - if other.key.loc.kind <= key.loc.kind { + if other.key.loc.ptr.kind <= key.loc.ptr.kind { dup_error(&other.key, &key, "gui template"); } return; @@ -160,7 +160,7 @@ impl Gui { pub fn load_layer(&mut self, key: Token, block: Block) { if let Some(other) = self.layers.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "gui layer"); } } @@ -180,7 +180,7 @@ impl Gui { if let Some(cbm) = color_blind_mode { let index = (cbm.as_str(), key.as_str()); if let Some(other) = self.textformats_colorblind.get(&index) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { let id = format!("textformat for {cbm}"); dup_error(&key, &other.key, &id); } @@ -188,7 +188,7 @@ impl Gui { self.textformats_colorblind.insert(index, TextFormat::new(key, block, Some(cbm))); } else { if let Some(other) = self.textformats.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "textformat"); } } diff --git a/src/data/localization.rs b/src/data/localization.rs index 1e5f8c18e..c77b7a7eb 100644 --- a/src/data/localization.rs +++ b/src/data/localization.rs @@ -807,7 +807,7 @@ impl FileHandler<(Language, Vec)> for Localization { // because in loca the older definition overrides the later one. if is_replace_path(entry.path()) { occupied_entry.insert(loca); - } else if other.key.loc.kind == entry.kind() && other.orig != loca.orig { + } else if other.key.loc.ptr.kind == entry.kind() && other.orig != loca.orig { dup_error(&other.key, &loca.key, "localization"); } } diff --git a/src/data/music.rs b/src/data/music.rs index b67c9a289..b44a5ade5 100644 --- a/src/data/music.rs +++ b/src/data/music.rs @@ -26,7 +26,7 @@ pub struct Musics { impl Musics { pub fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.musics.get(key.as_str()) { - if other.key.loc.kind == key.loc.kind { + if other.key.loc.ptr.kind == key.loc.ptr.kind { dup_error(&key, &other.key, "music"); } } diff --git a/src/data/script_values.rs b/src/data/script_values.rs index 0972809db..f464e6592 100644 --- a/src/data/script_values.rs +++ b/src/data/script_values.rs @@ -11,7 +11,7 @@ use crate::pdxfile::PdxFile; use crate::report::{err, warn, ErrorKey}; use crate::scopes::Scopes; use crate::script_value::{validate_non_dynamic_script_value, validate_script_value}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::variables::Variables; #[derive(Debug, Default)] @@ -23,7 +23,7 @@ pub struct ScriptValues { impl ScriptValues { fn load_item(&mut self, key: &Token, bv: &BV) { if let Some(other) = self.script_values.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if other.bv.equivalent(bv) { exact_dup_error(key, &other.key, "script value"); } else { @@ -121,7 +121,7 @@ impl FileHandler for ScriptValues { pub struct ScriptValue { key: Token, bv: BV, - cache: RwLock>, + cache: RwLock>, scope_override: Option, } diff --git a/src/data/scripted_effects.rs b/src/data/scripted_effects.rs index 2cedd1ebd..3b40e13df 100644 --- a/src/data/scripted_effects.rs +++ b/src/data/scripted_effects.rs @@ -27,7 +27,7 @@ pub struct Effects { impl Effects { fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.effects.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if other.block.equivalent(&block) { exact_dup_error(&key, &other.key, "scripted effect"); } else { diff --git a/src/data/scripted_lists.rs b/src/data/scripted_lists.rs index 068186bae..61a1ded4c 100644 --- a/src/data/scripted_lists.rs +++ b/src/data/scripted_lists.rs @@ -10,7 +10,7 @@ use crate::parse::ParserMemory; use crate::pdxfile::PdxFile; use crate::report::{err, ErrorKey}; use crate::scopes::{scope_iterator, Scopes}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::tooltipped::Tooltipped; use crate::trigger::validate_trigger; use crate::validator::Validator; @@ -24,7 +24,7 @@ pub struct ScriptedLists { impl ScriptedLists { fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.lists.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "scripted list"); } } @@ -86,7 +86,7 @@ impl FileHandler for ScriptedLists { pub struct List { pub key: Token, block: Block, - cache: RwLock>, + cache: RwLock>, } impl List { diff --git a/src/data/scripted_modifiers.rs b/src/data/scripted_modifiers.rs index 0e432d907..dbd392664 100644 --- a/src/data/scripted_modifiers.rs +++ b/src/data/scripted_modifiers.rs @@ -24,7 +24,7 @@ pub struct ScriptedModifiers { impl ScriptedModifiers { fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.scripted_modifiers.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { dup_error(&key, &other.key, "scripted modifier"); } } diff --git a/src/data/scripted_triggers.rs b/src/data/scripted_triggers.rs index e2fdf8905..d1cb35e9f 100644 --- a/src/data/scripted_triggers.rs +++ b/src/data/scripted_triggers.rs @@ -29,7 +29,7 @@ pub struct Triggers { impl Triggers { fn load_item(&mut self, key: Token, block: Block) { if let Some(other) = self.triggers.get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if other.block.equivalent(&block) { exact_dup_error(&key, &other.key, "scripted trigger"); } else { @@ -260,6 +260,6 @@ const BUILTIN_OVERRIDE_TRIGGERS: &[&str] = &[ /// but only if the key is from vanilla. If it's from the mod, they may have implemented the /// trigger differently. fn builtin_scope_overrides(key: &Token) -> Option { - (key.loc.kind.counts_as_vanilla() && BUILTIN_OVERRIDE_TRIGGERS.contains(&key.as_str())) + (key.loc.ptr.kind.counts_as_vanilla() && BUILTIN_OVERRIDE_TRIGGERS.contains(&key.as_str())) .then_some(Scopes::all()) } diff --git a/src/db.rs b/src/db.rs index a6d4f2c6b..44cb1a3fc 100644 --- a/src/db.rs +++ b/src/db.rs @@ -51,7 +51,7 @@ impl Default for Db { impl Db { pub fn add(&mut self, item: Item, key: Token, block: Block, kind: Box) { if let Some(other) = self.database[item as usize].get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if other.block.equivalent(&block) { exact_dup_error(&key, &other.key, &item.to_string()); } else { @@ -72,7 +72,7 @@ impl Db { kind: Box, ) { if let Some(other) = self.database[item as usize].get(key.as_str()) { - if other.key.loc.kind >= key.loc.kind { + if other.key.loc.ptr.kind >= key.loc.ptr.kind { if other.block.equivalent(&block) { exact_dup_advice(&key, &other.key, &item.to_string()); } else { diff --git a/src/everything.rs b/src/everything.rs index 2b9bf46ac..aac02c425 100644 --- a/src/everything.rs +++ b/src/everything.rs @@ -80,7 +80,7 @@ use crate::pdxfile::PdxFile; use crate::report::err; use crate::report::{report, set_output_style, ErrorKey, OutputStyle, Severity}; use crate::rivers::Rivers; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::variables::Variables; #[cfg(feature = "vic3")] use crate::vic3::data::{ @@ -263,7 +263,7 @@ impl Everything { Self::read_config(config_file_name, &config_file) .ok_or(FilesError::ConfigUnreadable { path: config_file })? } else { - Block::new(Loc::for_file(config_file.clone(), FileKind::Mod, config_file.clone())) + Block::new(LocStack::for_file(config_file.clone(), FileKind::Mod, config_file.clone())) }; fileset.config(config.clone(), workshop_dir, paradox_dir)?; diff --git a/src/hoi4/data/gfx.rs b/src/hoi4/data/gfx.rs index e1fbe018d..a75b3fbde 100644 --- a/src/hoi4/data/gfx.rs +++ b/src/hoi4/data/gfx.rs @@ -25,7 +25,7 @@ impl Gfx { pub fn load_sprite(&mut self, key: Token, block: Block) { if let Some(name) = block.get_field_value("name") { if let Some(other) = self.sprites.get(name.as_str()) { - if other.key.loc.kind >= name.loc.kind { + if other.key.loc.ptr.kind >= name.loc.ptr.kind { if other.block.equivalent(&block) { exact_dup_advice(name, &other.key, "sprite"); } else { @@ -40,7 +40,7 @@ impl Gfx { pub fn load_mesh(&mut self, key: Token, block: Block) { if let Some(name) = block.get_field_value("name") { if let Some(other) = self.meshes.get(name.as_str()) { - if other.key.loc.kind >= name.loc.kind { + if other.key.loc.ptr.kind >= name.loc.ptr.kind { dup_error(name, &other.key, "pdxmesh"); } } diff --git a/src/hoi4/data/music.rs b/src/hoi4/data/music.rs index fa1c1478d..4db85d492 100644 --- a/src/hoi4/data/music.rs +++ b/src/hoi4/data/music.rs @@ -23,7 +23,7 @@ pub struct Hoi4Musics { impl Hoi4Musics { pub fn load_item(&mut self, key: Token, block: Block, station: Option) { if let Some(other) = self.musics.get(key.as_str()) { - if other.key.loc.kind == key.loc.kind { + if other.key.loc.ptr.kind == key.loc.ptr.kind { dup_error(&key, &other.key, "music"); } } diff --git a/src/imperator/data/provinces.rs b/src/imperator/data/provinces.rs index 35c0e0b23..3d3149025 100644 --- a/src/imperator/data/provinces.rs +++ b/src/imperator/data/provinces.rs @@ -13,7 +13,7 @@ use crate::parse::csv::{parse_csv, read_csv}; use crate::parse::ParserMemory; use crate::pdxfile::PdxFile; use crate::report::{err, fatal, report, untidy, warn, ErrorKey, Severity}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; pub type ProvId = u32; @@ -319,7 +319,7 @@ pub struct Coords { #[allow(dead_code)] // TODO #[derive(Clone, Debug)] pub struct Adjacency { - line: Loc, + line: LocStack, from: ProvId, to: ProvId, /// TODO: check type is sea or `river_large` diff --git a/src/lib.rs b/src/lib.rs index 9d6269453..1be91dfe1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,9 +31,9 @@ pub use crate::modfile::ModFile; pub use crate::report::{ add_loaded_mod_root, disable_ansi_colors, emit_reports, log, set_output_style, set_show_loaded_mods, set_show_vanilla, suppress_from_json, take_reports, Confidence, - LogReportMetadata, LogReportPointers, PointedMessage, Severity, + LogReportMetadata, LogReportPointers, LogReportStackPointers, PointedMessageStack, Severity, }; -pub use crate::token::{Loc, Token}; +pub use crate::token::{LocStack, Token}; #[cfg(feature = "internal_benches")] mod benches; diff --git a/src/macros.rs b/src/macros.rs index 41c8dd2af..80c93dc96 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,17 +1,17 @@ -//! [`MacroCache`] to cache macro expansions, and [`MacroMap`] to track [`Loc`] use across macro expansions. +//! [`MacroCache`] to cache macro expansions, and [`MacroMap`] to track [`LocStack`] use across macro expansions. use std::hash::Hash; use std::num::NonZeroU32; use std::sync::{LazyLock, RwLock}; use crate::helpers::{BiTigerHashMap, TigerHashMap}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::tooltipped::Tooltipped; #[derive(Clone, Debug, Eq, PartialEq, Hash)] struct MacroKey { /// the loc of the call site - loc: Loc, + loc: LocStack, /// lexically sorted macro arguments args: Vec<(&'static str, &'static str)>, tooltipped: Tooltipped, @@ -21,7 +21,7 @@ struct MacroKey { impl MacroKey { pub fn new( - mut loc: Loc, + mut loc: LocStack, args: &[(&'static str, Token)], tooltipped: Tooltipped, negated: bool, @@ -89,7 +89,7 @@ pub struct MacroMap(RwLock); /// to the block containing the macros. pub struct MacroMapInner { counter: NonZeroU32, - bi_map: BiTigerHashMap, + bi_map: BiTigerHashMap, } impl Default for MacroMapInner { @@ -100,16 +100,16 @@ impl Default for MacroMapInner { impl MacroMap { /// Get the loc associated with the index - pub fn get_loc(&self, index: MacroMapIndex) -> Option { + pub fn get_loc(&self, index: MacroMapIndex) -> Option { self.0.read().unwrap().bi_map.get_by_left(&index.0).copied() } /// Get the index associated with the loc - pub fn get_index(&self, loc: Loc) -> Option { + pub fn get_index(&self, loc: LocStack) -> Option { self.0.read().unwrap().bi_map.get_by_right(&loc).copied().map(MacroMapIndex) } /// Insert a loc that is not expected to be in the map yet, and return its index. - pub fn insert_or_get_loc(&self, loc: Loc) -> MacroMapIndex { + pub fn insert_or_get_loc(&self, loc: LocStack) -> MacroMapIndex { let mut guard = self.0.write().unwrap(); let counter = guard.counter; if guard.bi_map.insert_no_overwrite(counter, loc).is_err() { @@ -122,7 +122,7 @@ impl MacroMap { } /// Get the index of a loc, inserting it if it was not yet stored - pub fn get_or_insert_loc(&self, loc: Loc) -> MacroMapIndex { + pub fn get_or_insert_loc(&self, loc: LocStack) -> MacroMapIndex { // First try with just a read lock, which allows for more parallelism than using a write lock. if let Some(index) = self.get_index(loc) { index diff --git a/src/parse/cob.rs b/src/parse/cob.rs index 20331f9b6..2421b0407 100644 --- a/src/parse/cob.rs +++ b/src/parse/cob.rs @@ -1,13 +1,13 @@ use std::mem::take; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; /// Copy on boundary type used for when a token may cross multiple parts of the input. #[derive(Clone, Debug)] pub(crate) enum Cob { Uninit, - Borrowed(&'static str, usize, usize, Loc), - Owned(String, Loc), + Borrowed(&'static str, usize, usize, LocStack), + Owned(String, LocStack), } impl Default for Cob { @@ -22,7 +22,7 @@ impl Cob { } #[inline] - pub(crate) fn set(&mut self, str: &'static str, index: usize, loc: Loc) { + pub(crate) fn set(&mut self, str: &'static str, index: usize, loc: LocStack) { *self = Self::Borrowed(str, index, index, loc); } diff --git a/src/parse/csv.rs b/src/parse/csv.rs index 678a1fe48..f04a1eabb 100644 --- a/src/parse/csv.rs +++ b/src/parse/csv.rs @@ -8,11 +8,11 @@ use encoding_rs::WINDOWS_1252; use crate::fileset::FileEntry; use crate::report::ErrorLoc; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; #[derive(Clone, Debug)] struct CsvParser<'a> { - loc: Loc, + loc: LocStack, offset: usize, content: &'a str, header_lines: usize, @@ -20,9 +20,9 @@ struct CsvParser<'a> { } impl<'a> CsvParser<'a> { - fn new(mut loc: Loc, header_lines: usize, content: &'a str) -> Self { - loc.line = 1; - loc.column = 1; + fn new(mut loc: LocStack, header_lines: usize, content: &'a str) -> Self { + loc.ptr.line = 1; + loc.ptr.column = 1; let chars = content.chars().peekable(); Self { loc, offset: 0, content, header_lines, chars } } @@ -32,10 +32,10 @@ impl<'a> CsvParser<'a> { if let Some(c) = self.chars.next() { self.offset += c.len_utf8(); if c == '\n' { - self.loc.line += 1; - self.loc.column = 1; + self.loc.ptr.line += 1; + self.loc.ptr.column = 1; } else { - self.loc.column += 1; + self.loc.ptr.column += 1; } } } diff --git a/src/parse/json.rs b/src/parse/json.rs index 22376063f..66a99fab5 100644 --- a/src/parse/json.rs +++ b/src/parse/json.rs @@ -10,7 +10,7 @@ use crate::block::Eq::Single; use crate::block::{Block, Comparator, BV}; use crate::fileset::FileEntry; use crate::report::{err, warn, ErrorKey}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; #[derive(Copy, Clone, Debug)] enum State { @@ -35,33 +35,33 @@ struct Parser { } impl Parser { - fn unknown_char(c: char, loc: Loc) { + fn unknown_char(c: char, loc: LocStack) { let msg = format!("Unrecognized character {c}"); err(ErrorKey::ParseError).msg(msg).loc(loc).push(); } - fn colon(&mut self, loc: Loc) { + fn colon(&mut self, loc: LocStack) { if !self.current.expect_colon { err(ErrorKey::ParseError).msg("unexpected `:`").loc(loc).push(); } self.current.expect_colon = false; } - fn check_colon(&mut self, loc: Loc) { + fn check_colon(&mut self, loc: LocStack) { if self.current.expect_colon { err(ErrorKey::ParseError).msg("expected `:`").loc(loc).push(); self.current.expect_comma = false; } } - fn comma(&mut self, loc: Loc) { + fn comma(&mut self, loc: LocStack) { if !self.current.expect_comma { err(ErrorKey::ParseError).msg("unexpected `,`").loc(loc).push(); } self.current.expect_comma = false; } - fn check_comma(&mut self, loc: Loc) { + fn check_comma(&mut self, loc: LocStack) { if self.current.expect_comma { err(ErrorKey::ParseError).msg("expected `,`").loc(loc).push(); self.current.expect_comma = false; @@ -100,7 +100,7 @@ impl Parser { } } - fn open_bracket(&mut self, loc: Loc, bracket: char) { + fn open_bracket(&mut self, loc: LocStack, bracket: char) { self.check_colon(loc); self.check_comma(loc); if self.current.opening_bracket == '{' && self.current.key.is_none() { @@ -118,7 +118,7 @@ impl Parser { self.stack.push(new_level); } - fn close_bracket(&mut self, loc: Loc, bracket: char) { + fn close_bracket(&mut self, loc: LocStack, bracket: char) { self.end_assign(); if let Some(mut prev_level) = self.stack.pop() { swap(&mut self.current, &mut prev_level); @@ -132,7 +132,7 @@ impl Parser { .push(); } self.block_value(prev_level.block); - if loc.column == 1 && !self.stack.is_empty() { + if loc.ptr.column == 1 && !self.stack.is_empty() { let msg = "possible bracket error"; let info = "This closing bracket is at the start of a line but does not end a top-level item."; warn(ErrorKey::BracePlacement).msg(msg).info(info).loc(loc).push(); @@ -154,7 +154,7 @@ impl Parser { } } -fn parse(blockloc: Loc, content: &str) -> Block { +fn parse(blockloc: LocStack, content: &str) -> Block { let mut parser = Parser { current: ParseLevel { block: Block::new(blockloc), @@ -280,10 +280,10 @@ fn parse(blockloc: Loc, content: &str) -> Block { } if c == '\n' { - loc.line += 1; - loc.column = 1; + loc.ptr.line += 1; + loc.ptr.column = 1; } else { - loc.column += 1; + loc.ptr.column += 1; } } @@ -316,9 +316,9 @@ fn parse(blockloc: Loc, content: &str) -> Block { #[allow(clippy::module_name_repetitions)] pub fn parse_json(entry: &FileEntry, content: &str) -> Block { - let mut loc = Loc::from(entry); - loc.line = 1; - loc.column = 1; + let mut loc = LocStack::from(entry); + loc.ptr.line = 1; + loc.ptr.column = 1; parse(loc, content) } diff --git a/src/parse/localization.rs b/src/parse/localization.rs index 4abe22f82..62f467bcb 100644 --- a/src/parse/localization.rs +++ b/src/parse/localization.rs @@ -10,7 +10,7 @@ use crate::parse::cob::Cob; use crate::parse::ignore::{parse_comment, IgnoreFilter, IgnoreSize}; use crate::report::register_ignore_filter; use crate::report::{untidy, warn, ErrorKey}; -use crate::token::{leak, Loc, Token}; +use crate::token::{leak, LocStack, Token}; fn is_key_char(c: char) -> bool { c.is_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '\'' @@ -24,7 +24,7 @@ fn is_code_char(c: char) -> bool { #[derive(Clone, Debug)] struct LocaParser { - loc: Loc, + loc: LocStack, offset: usize, content: &'static str, chars: Peekable>, @@ -41,23 +41,23 @@ impl LocaParser { let mut chars = content.chars().peekable(); let mut offset = 0; - let mut loc = Loc::from(entry); - // loc.line == 0 making this a whole file report - loc.column = 1; // From our perspective the BOM is a character and needs to be included in column offset + let mut loc = LocStack::from(entry); + // loc.ptr.line == 0 making this a whole file report + loc.ptr.column = 1; // From our perspective the BOM is a character and needs to be included in column offset if chars.peek() == Some(&'\u{feff}') { offset += '\u{feff}'.len_utf8(); - loc.column += 1; + loc.ptr.column += 1; chars.next(); if chars.peek() == Some(&'\u{feff}') { // Second BOM is file content, not header, and should be reported with line number - loc.line = 1; + loc.ptr.line = 1; let msg = "double BOM in localization file"; let info = "This will make the game engine skip the whole file."; warn(ErrorKey::Encoding).strong().msg(msg).info(info).loc(loc).push(); offset += '\u{feff}'.len_utf8(); - loc.column += 1; + loc.ptr.column += 1; chars.next(); } } else { @@ -65,7 +65,7 @@ impl LocaParser { } // From here on we are reporting on file content - loc.line = 1; + loc.ptr.line = 1; LocaParser { loc, @@ -86,10 +86,10 @@ impl LocaParser { if let Some(c) = self.chars.next() { self.offset += c.len_utf8(); if c == '\n' { - self.loc.line += 1; - self.loc.column = 1; + self.loc.ptr.line += 1; + self.loc.ptr.column = 1; } else { - self.loc.column += 1; + self.loc.ptr.column += 1; } } } @@ -279,12 +279,12 @@ impl LocaParser { register_ignore_filter(self.loc.pathname(), .., spec.filter); } IgnoreSize::Begin => { - self.active_range_ignores.push((self.loc.line + 1, spec.filter)); + self.active_range_ignores.push((self.loc.ptr.line + 1, spec.filter)); } IgnoreSize::End => { if let Some((start_line, filter)) = self.active_range_ignores.pop() { let path = self.loc.pathname(); - register_ignore_filter(path, start_line..self.loc.line, filter); + register_ignore_filter(path, start_line..self.loc.ptr.line, filter); } } } @@ -311,7 +311,7 @@ impl LocaParser { self.chars.peek()?; for filter in self.pending_line_ignores.drain(..) { let path = self.loc.pathname(); - let line = self.loc.line; + let line = self.loc.ptr.line; register_ignore_filter(path, line..=line, filter); } @@ -413,7 +413,7 @@ impl LocaParser { } pub struct ValueParser<'a> { - loc: Loc, + loc: LocStack, offset: usize, content: Vec<&'a Token>, content_iters: Vec>>, @@ -460,7 +460,7 @@ impl<'a> ValueParser<'a> { fn next_char(&mut self) { if let Some(c) = self.content_iters[self.content_idx].next() { self.offset += c.len_utf8(); - self.loc.column += 1; + self.loc.ptr.column += 1; } else if self.maybe_advance_idx() { self.next_char(); } @@ -650,7 +650,7 @@ impl<'a> ValueParser<'a> { } } - fn handle_tooltip(&mut self, value: &str, loc: Loc) { + fn handle_tooltip(&mut self, value: &str, loc: LocStack) { if value.contains(',') { // If the value contains commas, then it's #tooltip:tag,key or #tooltip:tag,key,value // Separate out the tooltip. @@ -685,7 +685,7 @@ impl<'a> ValueParser<'a> { // #tooltip:[Party.GetTooltipTag]|[InterestGroup.GetTooltipTag],INTEREST_GROUP_AFFILIATION_BREAKDOWN enum State { InKey(String), - InValue(String, String, Loc, usize), + InValue(String, String, LocStack, usize), } let mut state = State::InKey(String::new()); while let Some(c) = self.peek() { diff --git a/src/parse/pdxfile.rs b/src/parse/pdxfile.rs index 734ebe30d..52fbd57cf 100644 --- a/src/parse/pdxfile.rs +++ b/src/parse/pdxfile.rs @@ -17,7 +17,7 @@ use crate::parse::pdxfile::memory::CombinedMemory; pub use crate::parse::pdxfile::memory::PdxfileMemory; use crate::parse::ParserMemory; use crate::report::{err, store_source_file, ErrorKey}; -use crate::token::{leak, Loc, Token}; +use crate::token::{leak, LocStack, Token}; mod lexer; pub mod memory; @@ -48,10 +48,10 @@ pub fn parse_pdx_macro(inputs: &[Token], global: &PdxfileMemory, local: &Pdxfile /// Parse a whole file into a `Block`. fn parse_pdx(entry: &FileEntry, content: &'static str, memory: &ParserMemory) -> Block { - let file_loc = Loc::from(entry); + let file_loc = LocStack::from(entry); let mut loc = file_loc; - loc.line = 1; - loc.column = 1; + loc.ptr.line = 1; + loc.ptr.column = 1; let inputs = [Token::from_static_str(content, loc)]; let mut combined = CombinedMemory::new(&memory.pdxfile); match FILE_PARSER.parse(&inputs, &mut combined, Lexer::new(&inputs)) { @@ -89,9 +89,9 @@ pub fn parse_reader_export( let content = leak(content); store_source_file(entry.fullpath().to_path_buf(), &content[offset..]); let content = &content[offset..]; - let mut loc = Loc::from(entry); - loc.line = 1; - loc.column = 1; + let mut loc = LocStack::from(entry); + loc.ptr.line = 1; + loc.ptr.column = 1; let inputs = [Token::from_static_str(content, loc)]; let mut combined = CombinedMemory::new(global); match FILE_PARSER.parse(&inputs, &mut combined, Lexer::new(&inputs)) { @@ -155,7 +155,7 @@ fn split_macros(token: &Token) -> Vec { }); // Do this before pushing `param` to the vec, because it uses `param`. index_loc = (end, param.loc); - index_loc.1.column += 1 + param.as_str().chars().count() as u32; + index_loc.1.ptr.column += 1 + param.as_str().chars().count() as u32; vec.push(MacroComponent { kind: MacroComponentKind::Macro, token: param }); } } @@ -199,14 +199,14 @@ fn warn_macros(token: &Token, has_macro_params: bool) { } } -fn report_error(error: ParseError, mut file_loc: Loc) { +fn report_error(error: ParseError, mut file_loc: LocStack) { match error { ParseError::InvalidToken { location: _ } // we don't pass `LexError`s | ParseError::User { error: _ } => unreachable!(), ParseError::UnrecognizedEof { location: _, expected: _ } => { let msg = "unexpected end of file"; - file_loc.line = 0; - file_loc.column = 0; + file_loc.ptr.line = 0; + file_loc.ptr.column = 0; err(ErrorKey::ParseError).msg(msg).loc(file_loc).push(); } ParseError::UnrecognizedToken { token: (_, lexeme, _), expected: _ } diff --git a/src/parse/pdxfile/lexer.rs b/src/parse/pdxfile/lexer.rs index b886b03b8..15ed2ed93 100644 --- a/src/parse/pdxfile/lexer.rs +++ b/src/parse/pdxfile/lexer.rs @@ -8,7 +8,7 @@ use crate::game::Game; use crate::parse::ignore::{parse_comment, IgnoreFilter, IgnoreSize}; use crate::parse::pdxfile::{CharExt, Cob}; use crate::report::{err, register_ignore_filter, untidy, warn, ErrorKey}; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; /// ^Z is by convention an end-of-text marker, and the game engine treats it as such. const CONTROL_Z: char = '\u{001A}'; @@ -76,8 +76,8 @@ impl Lexeme { } } - /// Return the [`Loc`] of this lexeme. - pub fn get_loc(&self) -> Loc { + /// Return the [`LocStack`] of this lexeme. + pub fn get_loc(&self) -> LocStack { match self { Lexeme::General(token) | Lexeme::Comparator(_, token) @@ -136,7 +136,7 @@ pub struct Lexer<'input> { /// The current index into the `inputs` array. inputs_index: usize, /// Tracking file, line, and column of the current char. - loc: Loc, + loc: LocStack, /// Iterator over the current `inputs` token. iter: Peekable>, /// How many nested braces are around the current char. @@ -197,10 +197,10 @@ impl<'input> Lexer<'input> { if self.peek().is_some() { let (_, c) = self.iter.next().unwrap(); if c == '\n' { - self.loc.line += 1; - self.loc.column = 1; + self.loc.ptr.line += 1; + self.loc.ptr.column = 1; } else { - self.loc.column += 1; + self.loc.ptr.column += 1; } } } @@ -233,7 +233,7 @@ impl<'input> Lexer<'input> { /// Apply the pending line-ignores to the current line. fn apply_line_ignores(&mut self) { - let line = self.loc.line; + let line = self.loc.ptr.line; let path = self.loc.pathname(); for filter in self.pending_line_ignores.drain(..) { register_ignore_filter(path, line..=line, filter); @@ -243,7 +243,7 @@ impl<'input> Lexer<'input> { /// Apply the pending block-ignores to the current open brace. fn apply_block_ignores(&mut self) { for filter in self.pending_block_ignores.drain(..) { - self.active_block_ignores.push((self.brace_depth, self.loc.line, filter)); + self.active_block_ignores.push((self.brace_depth, self.loc.ptr.line, filter)); } } @@ -252,7 +252,7 @@ impl<'input> Lexer<'input> { let path = self.loc.pathname(); while let Some((depth, line, filter)) = self.active_block_ignores.last() { if self.brace_depth == *depth { - register_ignore_filter(path, *line..=self.loc.line, filter.clone()); + register_ignore_filter(path, *line..=self.loc.ptr.line, filter.clone()); self.active_block_ignores.pop(); } else { break; @@ -544,13 +544,18 @@ impl Iterator for Lexer<'_> { register_ignore_filter(path, .., spec.filter); } IgnoreSize::Begin => { - self.active_range_ignores.push((self.loc.line + 1, spec.filter)); + self.active_range_ignores + .push((self.loc.ptr.line + 1, spec.filter)); } IgnoreSize::End => { if let Some((start_line, filter)) = self.active_range_ignores.pop() { let path = self.loc.pathname(); - register_ignore_filter(path, start_line..self.loc.line, filter); + register_ignore_filter( + path, + start_line..self.loc.ptr.line, + filter, + ); } } } @@ -603,7 +608,7 @@ impl Iterator for Lexer<'_> { if self.brace_depth > 0 { self.brace_depth -= 1; } - if self.loc.column == 1 && self.brace_depth > 0 { + if self.loc.ptr.column == 1 && self.brace_depth > 0 { let msg = "possible brace error"; let info = "This closing brace is at the start of the line but does not close a top-level block."; warn(ErrorKey::BracePlacement) diff --git a/src/parse/pdxfile/parser.lalrpop b/src/parse/pdxfile/parser.lalrpop index 0bbe948f4..35246aec1 100644 --- a/src/parse/pdxfile/parser.lalrpop +++ b/src/parse/pdxfile/parser.lalrpop @@ -105,7 +105,7 @@ FileItem: Option = { // Handle a block with macro parameters let s = &inputs[0].as_str()[start + 1..end - 1]; let mut loc = block.0.loc; - loc.column += 1; + loc.ptr.column += 1; let token = Token::from_static_str(s, loc); block.0.source = Some(Box::new((split_macros(&token), memory.get_local()))); } @@ -122,7 +122,7 @@ FileItem: Option = { // Handle a block with macro parameters let s = &inputs[0].as_str()[start + 1..end - 1]; let mut loc = block.0.loc; - loc.column += 1; + loc.ptr.column += 1; let token = Token::from_static_str(s, loc); block.0.source = Some(Box::new((split_macros(&token), memory.get_local()))); } @@ -249,7 +249,7 @@ MacroDefinition: () = { } else { let s = &inputs[0].as_str()[start + 1..end - 1]; let mut loc = block.0.loc; - loc.column += 1; + loc.ptr.column += 1; let token = Token::from_static_str(s, loc); block.0.source = Some(Box::new((split_macros(&token), memory.get_local()))); } diff --git a/src/report/builder.rs b/src/report/builder.rs index d767fe2c2..2f600e8f2 100644 --- a/src/report/builder.rs +++ b/src/report/builder.rs @@ -5,8 +5,8 @@ //! without pointers, which would lead to panics. use crate::report::{ - log, Confidence, ErrorKey, ErrorLoc, LogReportMetadata, LogReportPointers, LogReportStyle, - PointedMessage, Severity, + log, Confidence, ErrorKey, ErrorLoc, LogReportMetadata, LogReportStackPointers, LogReportStyle, + PointedMessageStack, Severity, }; // ================================================================================================= @@ -113,7 +113,7 @@ impl ReportBuilderStage2 { msg: self.msg, info: self.info, wiki: self.wiki, - pointers: vec![PointedMessage { loc: eloc.into_loc(), length, msg: None }], + pointers: vec![PointedMessageStack { loc: eloc.into_loc(), length, msg: None }], } } @@ -124,11 +124,15 @@ impl ReportBuilderStage2 { msg: self.msg, info: self.info, wiki: self.wiki, - pointers: vec![PointedMessage { loc: eloc.into_loc(), length, msg: Some(msg.into()) }], + pointers: vec![PointedMessageStack { + loc: eloc.into_loc(), + length, + msg: Some(msg.into()), + }], } } - pub fn pointers(self, pointers: LogReportPointers) -> ReportBuilderFull { + pub fn pointers(self, pointers: LogReportStackPointers) -> ReportBuilderFull { ReportBuilderFull { stage1: self.stage1, msg: self.msg, @@ -144,7 +148,7 @@ impl ReportBuilderStage2 { msg: self.msg, info: self.info, wiki: self.wiki, - pointers: vec![PointedMessage { loc: eloc.into_loc(), length: 0, msg: None }], + pointers: vec![PointedMessageStack { loc: eloc.into_loc(), length: 0, msg: None }], } } } @@ -156,19 +160,23 @@ pub struct ReportBuilderFull { msg: String, info: Option, wiki: Option, - pointers: LogReportPointers, + pointers: LogReportStackPointers, } impl ReportBuilderFull { pub fn loc_msg>(mut self, eloc: E, msg: S) -> Self { let length = eloc.loc_length(); - self.pointers.push(PointedMessage { loc: eloc.into_loc(), length, msg: Some(msg.into()) }); + self.pointers.push(PointedMessageStack { + loc: eloc.into_loc(), + length, + msg: Some(msg.into()), + }); self } pub fn opt_loc_msg>(mut self, eloc: Option, msg: S) -> Self { if let Some(eloc) = eloc { let length = eloc.loc_length(); - self.pointers.push(PointedMessage { + self.pointers.push(PointedMessageStack { loc: eloc.into_loc(), length, msg: Some(msg.into()), @@ -177,7 +185,7 @@ impl ReportBuilderFull { self } /// Build the report and return it. - pub fn build(self) -> (LogReportMetadata, LogReportPointers) { + pub fn build(self) -> (LogReportMetadata, LogReportStackPointers) { ( LogReportMetadata { key: self.stage1.0, @@ -204,12 +212,12 @@ pub struct ReportBuilderAbbreviated { msg: String, info: Option, wiki: Option, - pointers: LogReportPointers, + pointers: LogReportStackPointers, } impl ReportBuilderAbbreviated { /// Build the report and return it. - pub fn build(self) -> (LogReportMetadata, LogReportPointers) { + pub fn build(self) -> (LogReportMetadata, LogReportStackPointers) { ( LogReportMetadata { key: self.stage1.0, diff --git a/src/report/error_loc.rs b/src/report/error_loc.rs index 63d2729b6..d81eaca43 100644 --- a/src/report/error_loc.rs +++ b/src/report/error_loc.rs @@ -1,6 +1,6 @@ use crate::block::{Block, BlockItem, Field, BV}; use crate::fileset::FileEntry; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::trigger::Part; use crate::validator::ValueValidator; @@ -9,7 +9,7 @@ pub trait ErrorLoc { fn loc_length(&self) -> usize { 1 } - fn into_loc(self) -> Loc; + fn into_loc(self) -> LocStack; } impl ErrorLoc for ValueValidator<'_> { @@ -17,7 +17,7 @@ impl ErrorLoc for ValueValidator<'_> { self.value().loc_length() } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.value().into_loc() } } @@ -27,7 +27,7 @@ impl ErrorLoc for &ValueValidator<'_> { self.value().loc_length() } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.value().into_loc() } } @@ -37,7 +37,7 @@ impl ErrorLoc for &mut ValueValidator<'_> { self.value().loc_length() } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.value().into_loc() } } @@ -51,7 +51,7 @@ impl ErrorLoc for BlockItem { } } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { match self { BlockItem::Value(token) => token.into_loc(), BlockItem::Block(block) => block.into_loc(), @@ -69,7 +69,7 @@ impl ErrorLoc for &BlockItem { } } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { match self { BlockItem::Value(token) => token.into_loc(), BlockItem::Block(block) => block.into_loc(), @@ -79,13 +79,13 @@ impl ErrorLoc for &BlockItem { } impl ErrorLoc for Field { - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.into_key().into_loc() } } impl ErrorLoc for &Field { - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.key().into_loc() } } @@ -98,7 +98,7 @@ impl ErrorLoc for BV { } } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { match self { BV::Value(token) => token.into_loc(), BV::Block(block) => block.into_loc(), @@ -114,7 +114,7 @@ impl ErrorLoc for &BV { } } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { match self { BV::Value(t) => t.into_loc(), BV::Block(s) => s.into_loc(), @@ -123,19 +123,19 @@ impl ErrorLoc for &BV { } impl ErrorLoc for FileEntry { - fn into_loc(self) -> Loc { - Loc::from(&self) + fn into_loc(self) -> LocStack { + LocStack::from(&self) } } impl ErrorLoc for &FileEntry { - fn into_loc(self) -> Loc { - Loc::from(self) + fn into_loc(self) -> LocStack { + LocStack::from(self) } } -impl ErrorLoc for Loc { - fn into_loc(self) -> Loc { +impl ErrorLoc for LocStack { + fn into_loc(self) -> LocStack { self } } @@ -145,7 +145,7 @@ impl ErrorLoc for Token { self.as_str().chars().count().max(1) } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.loc } } @@ -155,25 +155,25 @@ impl ErrorLoc for &Token { self.as_str().chars().count().max(1) } - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.loc } } impl ErrorLoc for Block { - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.loc } } impl ErrorLoc for &Block { - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { self.loc } } impl ErrorLoc for Part { - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { match self { Part::Token(t) | Part::TokenArgument(t, _) => t.loc, } @@ -188,7 +188,7 @@ impl ErrorLoc for Part { } impl ErrorLoc for &Part { - fn into_loc(self) -> Loc { + fn into_loc(self) -> LocStack { match self { Part::Token(t) | Part::TokenArgument(t, _) => t.loc, } diff --git a/src/report/errors.rs b/src/report/errors.rs index fbd705b48..fb59fc666 100644 --- a/src/report/errors.rs +++ b/src/report/errors.rs @@ -13,6 +13,7 @@ use std::sync::{LazyLock, Mutex, MutexGuard}; use encoding_rs::{UTF_8, WINDOWS_1252}; +use super::report_struct::{LogReportPointers, PointedMessage}; use crate::helpers::{TigerHashMap, TigerHashSet}; use crate::macros::MACRO_MAP; use crate::parse::ignore::IgnoreFilter; @@ -22,8 +23,8 @@ use crate::report::suppress::{Suppression, SuppressionKey}; use crate::report::writer::{log_report, log_summary}; use crate::report::writer_json::log_report_json; use crate::report::{ - ErrorKey, FilterRule, LogReport, LogReportMetadata, LogReportPointers, LogReportStyle, - OutputStyle, PointedMessage, + ErrorKey, FilterRule, LogReport, LogReportMetadata, LogReportStyle, OutputStyle, + PointedMessageStack, }; use crate::set; use crate::token::{leak, Loc}; @@ -316,37 +317,36 @@ pub fn add_loaded_dlc_root(label: String) { } /// Store an error report to be emitted when [`emit_reports`] is called. -pub fn log((report, mut pointers): LogReport) { - let mut vec = Vec::new(); - pointers.drain(..).for_each(|pointer| { - let index = vec.len(); - recursive_pointed_msg_expansion(&mut vec, &pointer); - vec.insert(index, pointer); - }); - pointers.extend(vec); +pub fn log((report, pointers): LogReport) { + let pointers = pointed_msg_expansion(pointers); Errors::get_mut().push_report(report, pointers); } -/// Expand `PointedMessage` recursively. -/// That is; for the given `PointedMessage`, follow its location's link until such link is no -/// longer available, adding a newly created `PointedMessage` to the given `Vec` for each linked +/// Expand `Vec` to `Vec`. +/// That is; for each `PointedMessageStack`, follow its location's link until such link is no +/// longer available, adding a newly created `PointedMessage` to the returned `Vec` for each linked /// location. -fn recursive_pointed_msg_expansion(vec: &mut LogReportPointers, pointer: &PointedMessage) { - if let Some(link) = pointer.loc.link_idx { - let from_here = PointedMessage { - loc: MACRO_MAP.get_loc(link).unwrap(), - length: 1, - msg: Some("from here".to_owned()), - }; - let index = vec.len(); - recursive_pointed_msg_expansion(vec, &from_here); - vec.insert(index, from_here); - } +fn pointed_msg_expansion(pointers: Vec) -> Vec { + pointers + .into_iter() + .flat_map(|p| { + let mut next_loc = Some(p.loc); + std::iter::from_fn(move || match next_loc { + Some(stack) => { + let next = + PointedMessage { loc: stack.ptr, length: 1, msg: Some("from here".into()) }; + next_loc = stack.link_idx.and_then(|idx| MACRO_MAP.get_loc(idx)); + Some(next) + } + None => None, + }) + }) + .collect() } /// Tests whether the report might be printed. If false, the report will definitely not be printed. pub fn will_maybe_log(eloc: E, key: ErrorKey) -> bool { - Errors::get().filter.should_maybe_print(key, eloc.into_loc()) + Errors::get().filter.should_maybe_print(key, eloc.into_loc().ptr) } /// Print all the stored reports to the error output. diff --git a/src/report/filter.rs b/src/report/filter.rs index 03a8ed83a..030dea495 100644 --- a/src/report/filter.rs +++ b/src/report/filter.rs @@ -143,7 +143,7 @@ impl FilterRule { .loc_msg( { let mut loc = token.into_loc(); - loc.column += u32::try_from(e.pos).unwrap_or(0); + loc.ptr.column += u32::try_from(e.pos).unwrap_or(0); loc }, e.msg, diff --git a/src/report/mod.rs b/src/report/mod.rs index 058395ffc..78f161c67 100644 --- a/src/report/mod.rs +++ b/src/report/mod.rs @@ -7,8 +7,8 @@ pub use errors::*; pub(crate) use filter::FilterRule; pub(crate) use output_style::OutputStyle; pub use report_struct::{ - Confidence, LogReport, LogReportMetadata, LogReportPointers, LogReportStyle, PointedMessage, - Severity, + Confidence, LogReport, LogReportMetadata, LogReportPointers, LogReportStackPointers, + LogReportStyle, PointedMessageStack, Severity, }; pub use suppress::suppress_from_json; diff --git a/src/report/report_struct.rs b/src/report/report_struct.rs index de8cad0be..63f025882 100644 --- a/src/report/report_struct.rs +++ b/src/report/report_struct.rs @@ -2,9 +2,9 @@ use serde::Serialize; use strum_macros::{Display, EnumCount, EnumIter, EnumString, IntoStaticStr}; use crate::report::ErrorKey; -use crate::token::Loc; +use crate::token::{Loc, LocStack}; -pub type LogReport = (LogReportMetadata, LogReportPointers); +pub type LogReport = (LogReportMetadata, LogReportStackPointers); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum LogReportStyle { @@ -32,10 +32,10 @@ pub struct LogReportMetadata { } #[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct PointedMessage { +pub struct PointedMessageStack { /// Which file and where in the file the error occurs. /// Might point to a whole file, rather than a specific location in the file. - pub loc: Loc, + pub loc: LocStack, /// The length of the offending phrase in characters. /// Set this to 1 if the length cannot be determined. /// This will determine the number of carets that are printed at the given location. @@ -47,13 +47,30 @@ pub struct PointedMessage { pub msg: Option, } -impl PointedMessage { - pub fn new(loc: Loc) -> Self { - Self { loc, msg: None, length: 0 } +impl PointedMessageStack { + pub fn new(loc: LocStack) -> Self { + PointedMessageStack { loc, msg: None, length: 0 } } } +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct PointedMessage { + /// Which file and where in the file the error occurs. + /// Might point to a whole file, rather than a specific location in the file. + pub loc: Loc, + /// The length of the offending phrase in characters. + /// Set this to 1 if the length cannot be determined. + /// This will determine the number of carets that are printed at the given location. + /// e.g.: ^^^^^^^^^ + /// A length of 0 will hide the carets + /// TODO: If we end up adding length to Loc, this field can be deleted. + pub length: usize, + /// A short message that will be printed at the caret location. + pub msg: Option, +} + /// Should contain one or more elements. +pub type LogReportStackPointers = Vec; pub type LogReportPointers = Vec; pub fn pointer_indentation(pointers: &LogReportPointers) -> usize { diff --git a/src/report/writer.rs b/src/report/writer.rs index 8af422df2..a219ff55a 100644 --- a/src/report/writer.rs +++ b/src/report/writer.rs @@ -6,12 +6,13 @@ use itertools::Itertools; use strum::{EnumCount as _, IntoEnumIterator}; use unicode_width::UnicodeWidthChar; +use super::report_struct::{LogReportPointers, PointedMessage}; use crate::fileset::FileKind; use crate::game::Game; use crate::report::errors::Errors; use crate::report::output_style::Styled; use crate::report::report_struct::pointer_indentation; -use crate::report::{LogReportMetadata, LogReportPointers, OutputStyle, PointedMessage, Severity}; +use crate::report::{LogReportMetadata, OutputStyle, Severity}; /// Source lines printed in the output have leading tab characters replaced by this number of spaces. const SPACES_PER_TAB: usize = 4; diff --git a/src/token.rs b/src/token.rs index 55abbecde..786f5f17c 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,7 +1,8 @@ -//! Contains the core [`Token`] and [`Loc`] types, which represent pieces of game script and where +//! Contains the core [`Token`] and [`LocStack`] types, which represent pieces of game script and where //! in the game files they came from. use std::borrow::{Borrow, Cow}; +use std::cmp::Ordering; use std::ffi::OsStr; use std::fmt::{Debug, Display, Error, Formatter}; use std::hash::Hash; @@ -14,29 +15,20 @@ use bumpalo::Bump; use crate::date::Date; use crate::fileset::{FileEntry, FileKind}; -use crate::macros::MacroMapIndex; +use crate::macros::{MacroMapIndex, MACRO_MAP}; use crate::pathtable::{PathTable, PathTableIndex}; use crate::report::{err, untidy, ErrorKey}; -#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct Loc { pub(crate) idx: PathTableIndex, pub kind: FileKind, /// line 0 means the loc applies to the file as a whole. pub line: u32, pub column: u32, - /// Used in macro expansions to point to the macro invocation - /// in the macro table - pub link_idx: Option, } impl Loc { - #[must_use] - pub(crate) fn for_file(pathname: PathBuf, kind: FileKind, fullpath: PathBuf) -> Self { - let idx = PathTable::store(pathname, fullpath); - Loc { idx, kind, line: 0, column: 0, link_idx: None } - } - pub fn filename(self) -> Cow<'static, str> { PathTable::lookup_path(self.idx) .file_name() @@ -58,25 +50,18 @@ impl Loc { } } -impl From<&FileEntry> for Loc { - fn from(entry: &FileEntry) -> Self { - if let Some(idx) = entry.path_idx() { - Loc { idx, kind: entry.kind(), line: 0, column: 0, link_idx: None } - } else { - Self::for_file(entry.path().to_path_buf(), entry.kind(), entry.fullpath().to_path_buf()) - } - } -} - -impl From<&mut FileEntry> for Loc { - fn from(entry: &mut FileEntry) -> Self { - (&*entry).into() +impl PartialOrd for Loc { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } -impl From for Loc { - fn from(entry: FileEntry) -> Self { - (&entry).into() +impl Ord for Loc { + fn cmp(&self, other: &Self) -> Ordering { + self.idx + .cmp(&other.idx) + .then(self.line.cmp(&other.line)) + .then(self.column.cmp(&other.column)) } } @@ -90,11 +75,81 @@ impl Debug for Loc { .field("kind", &self.kind) .field("line", &self.line) .field("column", &self.column) - .field("linkindex", &self.link_idx) .finish() } } +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub struct LocStack { + pub ptr: Loc, + /// Used in macro expansions to point to the macro invocation + /// in the macro table + pub link_idx: Option, +} + +impl LocStack { + #[must_use] + pub(crate) fn for_file(pathname: PathBuf, kind: FileKind, fullpath: PathBuf) -> Self { + let idx = PathTable::store(pathname, fullpath); + LocStack { ptr: Loc { idx, kind, line: 0, column: 0 }, link_idx: None } + } + + pub fn filename(self) -> Cow<'static, str> { + self.ptr.filename() + } + + pub fn pathname(self) -> &'static Path { + self.ptr.pathname() + } + + pub fn fullpath(self) -> &'static Path { + self.ptr.fullpath() + } + + #[inline] + pub fn same_file(self, other: LocStack) -> bool { + self.ptr.same_file(other.ptr) + } +} + +impl PartialOrd for LocStack { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for LocStack { + fn cmp(&self, other: &Self) -> Ordering { + self.ptr.cmp(&other.ptr).then( + self.link_idx + .map(|link| MACRO_MAP.get_loc(link)) + .cmp(&other.link_idx.map(|link| MACRO_MAP.get_loc(link))), + ) + } +} + +impl From<&FileEntry> for LocStack { + fn from(entry: &FileEntry) -> Self { + if let Some(idx) = entry.path_idx() { + LocStack { ptr: Loc { idx, kind: entry.kind(), line: 0, column: 0 }, link_idx: None } + } else { + Self::for_file(entry.path().to_path_buf(), entry.kind(), entry.fullpath().to_path_buf()) + } + } +} + +impl From<&mut FileEntry> for LocStack { + fn from(entry: &mut FileEntry) -> Self { + (&*entry).into() + } +} + +impl From for LocStack { + fn from(entry: FileEntry) -> Self { + (&entry).into() + } +} + /// Leak the string, including any excess capacity. /// /// It should only be used for large strings, rather than for small, individuals strings, @@ -129,23 +184,23 @@ pub(crate) fn bump(s: &str) -> &'static str { #[derive(Clone, Debug)] pub struct Token { s: &'static str, - pub loc: Loc, + pub loc: LocStack, } impl Token { #[must_use] - pub fn new(s: &str, loc: Loc) -> Self { + pub fn new(s: &str, loc: LocStack) -> Self { Token { s: bump(s), loc } } #[must_use] - pub fn from_static_str(s: &'static str, loc: Loc) -> Self { + pub fn from_static_str(s: &'static str, loc: LocStack) -> Self { Token { s, loc } } /// Create a `Token` from a substring of the given `Token`. #[must_use] - pub fn subtoken(&self, range: R, loc: Loc) -> Token + pub fn subtoken(&self, range: R, loc: LocStack) -> Token where R: RangeBounds + SliceIndex, { @@ -155,7 +210,7 @@ impl Token { /// Create a `Token` from a subtring of the given `Token`, /// stripping any whitespace from the created token. #[must_use] - pub fn subtoken_stripped(&self, mut range: Range, mut loc: Loc) -> Token { + pub fn subtoken_stripped(&self, mut range: Range, mut loc: LocStack) -> Token { let mut start = match range.start_bound() { Bound::Included(&i) => i, Bound::Excluded(&i) => i + 1, @@ -172,7 +227,7 @@ impl Token { range = start..end; break; } - loc.column += 1; + loc.ptr.column += 1; } for (i, c) in self.s[range.clone()].char_indices().rev() { if !c.is_whitespace() { @@ -216,8 +271,8 @@ impl Token { if c == ch { vec.push(self.subtoken(pos..i, loc)); pos = i + 1; - loc.column = self.loc.column + cols + 1; - loc.line = self.loc.line + lines; + loc.ptr.column = self.loc.ptr.column + cols + 1; + loc.ptr.line = self.loc.ptr.line + lines; } if c == '\n' { lines += 1; @@ -237,7 +292,7 @@ impl Token { #[allow(clippy::cast_possible_truncation)] self.s.strip_prefix(pfx).map(|sfx| { let mut loc = self.loc; - loc.column += pfx.chars().count() as u32; + loc.ptr.column += pfx.chars().count() as u32; Token::from_static_str(sfx, loc) }) } @@ -255,7 +310,7 @@ impl Token { if c == ch { let token1 = self.subtoken(..i, self.loc); let mut loc = self.loc; - loc.column += cols + 1; + loc.ptr.column += cols + 1; let token2 = self.subtoken(i + 1.., loc); return Some((token1, token2)); } @@ -278,7 +333,7 @@ impl Token { let chlen = ch.len_utf8(); let token1 = self.subtoken(..i + chlen, self.loc); let mut loc = self.loc; - loc.column += cols + chlen as u32; + loc.ptr.column += cols + chlen as u32; let token2 = self.subtoken(i + chlen.., loc); return Some((token1, token2)); } @@ -316,7 +371,7 @@ impl Token { } if let Some((cols, i)) = real_start { let mut loc = self.loc; - loc.column += cols; + loc.ptr.column += cols; self.subtoken(i..real_end, loc) } else { // all spaces @@ -465,8 +520,8 @@ impl Borrow for &Token { } } -impl From for Token { - fn from(loc: Loc) -> Self { +impl From for Token { + fn from(loc: LocStack) -> Self { Token { s: "", loc } } } diff --git a/src/trigger.rs b/src/trigger.rs index 309ad646e..3bb25f045 100644 --- a/src/trigger.rs +++ b/src/trigger.rs @@ -30,7 +30,7 @@ use crate::scopes::{ }; #[cfg(feature = "jomini")] use crate::script_value::validate_script_value; -use crate::token::{Loc, Token}; +use crate::token::{LocStack, Token}; use crate::tooltipped::Tooltipped; use crate::validate::{ precheck_iterator_fields, validate_identifier, validate_ifelse_sequence, @@ -1389,7 +1389,7 @@ impl std::fmt::Display for Part { } impl Part { - fn loc(&self) -> Loc { + fn loc(&self) -> LocStack { match self { Part::Token(t) | Part::TokenArgument(t, _) => t.loc, } @@ -1417,12 +1417,12 @@ pub fn partition(token: &Token) -> Vec { if part_idx == idx { // Empty part; err but skip it since it's likely a typo let mut loc = token.loc; - loc.column += col; + loc.ptr.column += col; err(ErrorKey::Validation).msg("empty part").loc(loc).push(); } else if !has_part_argument { // The just completed part has no argument let mut part_loc = token.loc; - part_loc.column += part_col; + part_loc.ptr.column += part_col; #[allow(unused_mut)] let mut part_token = token.subtoken(part_idx..idx, part_loc); #[cfg(feature = "imperator")] @@ -1454,7 +1454,7 @@ pub fn partition(token: &Token) -> Vec { if paren_depth == 0 { // Missing opening parenthesis `(` let mut loc = token.loc; - loc.column += col; + loc.ptr.column += col; err(ErrorKey::Validation) .msg("closing without opening parenthesis `(`") .loc(loc) @@ -1462,11 +1462,11 @@ pub fn partition(token: &Token) -> Vec { } else if paren_depth == 1 { // Argument between parentheses let mut func_loc = token.loc; - func_loc.column += part_col; + func_loc.ptr.column += part_col; let func_token = token.subtoken(part_idx..first_paren_idx, func_loc); let mut arg_loc = token.loc; - arg_loc.column += first_paren_col + 1; + arg_loc.ptr.column += first_paren_col + 1; let arg_token = token.subtoken_stripped(first_paren_idx + 1..idx, arg_loc); parts.push(Part::TokenArgument(func_token, arg_token)); @@ -1475,7 +1475,7 @@ pub fn partition(token: &Token) -> Vec { } else if paren_depth == 2 { // Cannot have nested parentheses let mut loc = token.loc; - loc.column += second_paren_col; + loc.ptr.column += second_paren_col; let nested_paren_token = token.subtoken(second_paren_idx..=idx, loc); err(ErrorKey::Validation) .msg("cannot have nested parentheses") @@ -1488,7 +1488,7 @@ pub fn partition(token: &Token) -> Vec { // an argument can only be the last part or followed by dot `.` AND hasn't erred from it yet if has_part_argument && !has_part_argument_erred { let mut loc = token.loc; - loc.column += col; + loc.ptr.column += col; err(ErrorKey::Validation) .msg("argument can only be the last part or followed by dot `.`") .loc(loc) @@ -1502,7 +1502,7 @@ pub fn partition(token: &Token) -> Vec { if paren_depth > 0 { // Missing closing parenthesis `)` let mut loc = token.loc; - loc.column += first_paren_col; + loc.ptr.column += first_paren_col; let broken_token = token.subtoken(first_paren_idx.., loc); err(ErrorKey::Validation) .msg("opening without closing parenthesis `)`") @@ -1513,12 +1513,12 @@ pub fn partition(token: &Token) -> Vec { if part_idx == token.as_str().len() { // Trailing `.` let mut loc = token.loc; - loc.column += part_col; + loc.ptr.column += part_col; err(ErrorKey::Validation).msg("trailing dot `.`").loc(loc).push(); } else if !has_part_argument { // final part (without argument) let mut part_loc = token.loc; - part_loc.column += part_col; + part_loc.ptr.column += part_col; // SAFETY: part_idx < token.as_str.len() #[allow(unused_mut)] let mut part_token = token.subtoken(part_idx.., part_loc); @@ -1666,7 +1666,7 @@ pub fn validate_argument( // Imperator does not use `()` let msg = "imperator does not support the `()` syntax"; let mut opening_paren_loc = arg.loc; - opening_paren_loc.column -= 1; + opening_paren_loc.ptr.column -= 1; err(ErrorKey::WrongGame).msg(msg).loc(opening_paren_loc).push(); return; } @@ -1675,7 +1675,7 @@ pub fn validate_argument( if Game::is_hoi4() { let msg = "hoi4 does not support the `()` syntax"; let mut opening_paren_loc = arg.loc; - opening_paren_loc.column -= 1; + opening_paren_loc.ptr.column -= 1; err(ErrorKey::WrongGame).msg(msg).loc(opening_paren_loc).push(); return; } diff --git a/vic3-tiger/benches/criterion.rs b/vic3-tiger/benches/criterion.rs index 0a972560f..5ed9e271d 100644 --- a/vic3-tiger/benches/criterion.rs +++ b/vic3-tiger/benches/criterion.rs @@ -27,8 +27,7 @@ fn workspace_path(s: &str) -> PathBuf { let p = PathBuf::from(s); if p.is_relative() { PathBuf::from("..").join(p) - } - else { + } else { p } } From f2fed2253d7a23c4e4a781f15a28388c5a5b66a1 Mon Sep 17 00:00:00 2001 From: Byte Northbridge Date: Mon, 11 Aug 2025 17:13:46 -0500 Subject: [PATCH 3/3] fix: item db duplicate handling is stable over insertion order --- src/db.rs | 66 +++++++++++++++++++++++++++++++++++--------------- src/helpers.rs | 2 ++ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/db.rs b/src/db.rs index 44cb1a3fc..85f24f00c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -13,7 +13,9 @@ use strum::EnumCount; use crate::block::Block; use crate::context::ScopeContext; use crate::everything::Everything; -use crate::helpers::{dup_error, exact_dup_advice, exact_dup_error, TigerHashMap, TigerHashSet}; +use crate::helpers::{ + dup_error, exact_dup_advice, exact_dup_error, DupReporter, TigerHashMap, TigerHashSet, +}; use crate::item::Item; use crate::lowercase::Lowercase; use crate::token::Token; @@ -50,17 +52,7 @@ impl Default for Db { impl Db { pub fn add(&mut self, item: Item, key: Token, block: Block, kind: Box) { - if let Some(other) = self.database[item as usize].get(key.as_str()) { - if other.key.loc.ptr.kind >= key.loc.ptr.kind { - if other.block.equivalent(&block) { - exact_dup_error(&key, &other.key, &item.to_string()); - } else { - dup_error(&key, &other.key, &item.to_string()); - } - } - } - self.items_lc[item as usize].insert(Lowercase::new(key.as_str()), key.as_str()); - self.database[item as usize].insert(key.as_str(), DbEntry { key, block, kind }); + self.add_with_reporter(item, key, block, kind, exact_dup_error); } #[allow(dead_code)] @@ -71,17 +63,51 @@ impl Db { block: Block, kind: Box, ) { - if let Some(other) = self.database[item as usize].get(key.as_str()) { - if other.key.loc.ptr.kind >= key.loc.ptr.kind { - if other.block.equivalent(&block) { - exact_dup_advice(&key, &other.key, &item.to_string()); - } else { - dup_error(&key, &other.key, &item.to_string()); + self.add_with_reporter(item, key, block, kind, exact_dup_advice); + } + + fn add_with_reporter( + &mut self, + item: Item, + key: Token, + block: Block, + kind: Box, + exact_dup_reporter: DupReporter, + ) { + use std::collections::hash_map::Entry; + + match self.database[item as usize].entry(key.as_str()) { + Entry::Occupied(mut occupied_entry) => { + let new = DbEntry { key, block, kind }; + let existing = occupied_entry.get(); + + let (keep, overwriten) = + // Just compare the top loc, not the whole stack + if new.key.loc.ptr > existing.key.loc.ptr { + (&new, existing) + } else { + (existing, &new) + }; + + if overwriten.key.loc.ptr.kind >= keep.key.loc.ptr.kind { + if overwriten.block.equivalent(&keep.block) { + exact_dup_reporter(&keep.key, &overwriten.key, &item.to_string()); + } else { + dup_error(&keep.key, &overwriten.key, &item.to_string()); + } } + // Update the db if the new entry is the one we're keeping + if &raw const new == &raw const *keep { + self.items_lc[item as usize] + .insert(Lowercase::new(new.key.as_str()), new.key.as_str()); + occupied_entry.insert(new); + } + } + Entry::Vacant(vacant_entry) => { + self.items_lc[item as usize].insert(Lowercase::new(key.as_str()), key.as_str()); + vacant_entry.insert(DbEntry { key, block, kind }); } } - self.items_lc[item as usize].insert(Lowercase::new(key.as_str()), key.as_str()); - self.database[item as usize].insert(key.as_str(), DbEntry { key, block, kind }); } #[cfg(feature = "hoi4")] diff --git a/src/helpers.rs b/src/helpers.rs index 89baecd4f..96f0181d6 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -22,6 +22,8 @@ macro_rules! set { }; } +pub type DupReporter = fn(&Token, &Token, &str) -> (); + /// Warns about a redefinition of a database item pub fn dup_error(key: &Token, other: &Token, id: &str) { warn(ErrorKey::DuplicateItem)