use std::collections::BTreeMap;
use crate::ExprId;
use crate::codegen::{internal_failure, wasm_u32};
use crate::compiler_error::CompilerFailure;
use crate::literal_units::literal_units;
#[derive(Default, Clone, Debug)]
pub struct StringPool {
pub strings: Vec<String>,
pub locations: BTreeMap<ExprId, usize>,
pub text_to_idx: BTreeMap<String, usize>,
}
impl StringPool {
pub(crate) fn intern_text(&mut self, text: &str) -> usize {
if let Some(&existing) = self.text_to_idx.get(text) {
return existing;
}
let idx = self.strings.len();
self.strings.push(text.to_string());
self.text_to_idx.insert(text.to_string(), idx);
idx
}
pub(crate) fn record_expr(&mut self, expr_id: ExprId, text: &str) {
let idx = self.intern_text(text);
self.locations.insert(expr_id, idx);
}
pub fn lookup_text(&self, text: &str) -> Option<usize> {
self.text_to_idx.get(text).copied()
}
pub fn is_empty(&self) -> bool {
self.strings.is_empty()
}
pub fn code_units(&self, idx: usize) -> Result<u32, CompilerFailure> {
wasm_u32(literal_units(self.string(idx)?).len())
}
pub fn utf16_le_bytes(&self, idx: usize) -> Result<Vec<u8>, CompilerFailure> {
Ok(literal_units(self.string(idx)?)
.into_iter()
.flat_map(u16::to_le_bytes)
.collect())
}
fn string(&self, idx: usize) -> Result<&str, CompilerFailure> {
self.strings
.get(idx)
.map(String::as_str)
.ok_or_else(|| internal_failure("a string literal pool index is out of range"))
}
}
#[cfg(test)]
mod tests {
use super::StringPool;
use crate::codegen::analysis::CodegenAnalysis;
use crate::{Asi, Token, TokenKind, capture, check, desugar, infer, parse};
fn pool(source: &str) -> StringPool {
let mut asi = Asi::new(source, crate::FileId(0));
let mut tokens: Vec<Token> = Vec::new();
loop {
let tok = asi.next_token();
let is_eof = matches!(tok.kind, TokenKind::Eof);
tokens.push(tok);
if is_eof {
break;
}
}
let _ = asi.into_diagnostics();
let (ast, _) = parse(source, tokens, crate::FileId(0));
let (prelude_defs, host_defs, _) =
crate::runtime::prelude::cached_runtime_package_declarations();
let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
packages.extend(prelude_defs.iter());
packages.extend(host_defs.iter());
let (mut ta, mut diags) = infer(source, "main", &ast, &packages);
diags.extend(check(&ta).unwrap());
ta = capture(ta).unwrap();
ta = desugar(ta, crate::FileId(0)).unwrap();
assert!(diags.is_empty(), "unexpected typecheck diags: {diags:?}");
CodegenAnalysis::collect(&ta, &[]).unwrap().string_pool
}
#[test]
fn empty_program_pool_is_empty() {
let p = pool("function main(): void { }");
assert!(p.is_empty());
assert!(p.strings.is_empty());
assert!(p.locations.is_empty());
}
#[test]
fn single_string_in_let_initializer() {
let p = pool(r#"let x: string = "hello"; function main(): void { }"#);
assert_eq!(p.strings, vec!["hello".to_string()]);
assert_eq!(p.locations.len(), 1);
assert_eq!(p.code_units(0).unwrap(), 5);
assert_eq!(p.utf16_le_bytes(0).unwrap(), b"h\0e\0l\0l\0o\0".to_vec());
assert!(p.code_units(1).is_err());
assert!(p.utf16_le_bytes(1).is_err());
}
#[test]
fn dedup_same_string_two_locations() {
let p =
pool(r#"let x: string = "hello"; let y: string = "hello"; function main(): void { }"#);
assert_eq!(p.strings, vec!["hello".to_string()]);
assert_eq!(p.locations.len(), 2);
for idx in p.locations.values() {
assert_eq!(*idx, 0);
}
}
#[test]
fn multiple_distinct_strings_keep_first_appearance_order() {
let p =
pool(r#"let x: string = "hello"; let y: string = "world"; function main(): void { }"#);
assert_eq!(p.strings, vec!["hello".to_string(), "world".to_string()]);
assert_eq!(p.locations.len(), 2);
}
#[test]
fn string_in_function_body_is_collected() {
let p = pool(r#"function main(): void { let s: string = "hi"; }"#);
assert_eq!(p.strings, vec!["hi".to_string()]);
assert_eq!(p.locations.len(), 1);
}
#[test]
fn string_in_concat_is_collected() {
let p = pool(r#"let z: string = "a" + "b"; function main(): void { }"#);
assert_eq!(p.strings, vec!["a".to_string(), "b".to_string()]);
assert_eq!(p.locations.len(), 2);
}
#[test]
fn string_inside_if_condition_branches_collected() {
let p = pool(
r#"
function main(): void {
const a: string = "a";
if (a === "b") {
let x: string = "then";
} else {
let y: string = "else";
}
}
"#,
);
let mut s = p.strings.clone();
s.sort();
assert_eq!(s, vec!["a", "b", "else", "then"]);
}
}