use std::any::Any;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
use crate::preprocessor::MacroCalledCallback;
use crate::token::Token;
use crate::StringInterner;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PerlvarEntry {
pub name: String,
pub prefix: char,
pub kind: PerlvarKind,
pub c_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PerlvarKind {
Var,
Init { init_expr: String },
Array { length: ArrayLength },
Const { init_expr: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArrayLength {
Literal(usize),
Symbolic(String),
}
#[derive(Debug, Default, Clone)]
pub struct PerlvarDict {
entries: BTreeMap<String, PerlvarEntry>,
}
impl PerlvarDict {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, entry: PerlvarEntry) {
self.entries.insert(entry.name.clone(), entry);
}
pub fn get(&self, name: &str) -> Option<&PerlvarEntry> {
self.entries.get(name)
}
pub fn iter(&self) -> impl Iterator<Item = &PerlvarEntry> {
self.entries.values()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
enum CollectorKind {
Var,
Init,
Array,
Const,
}
pub struct PerlvarCollector {
dict: Rc<RefCell<PerlvarDict>>,
kind: CollectorKind,
}
impl PerlvarCollector {
pub fn new_set() -> (
Rc<RefCell<PerlvarDict>>,
PerlvarCollector,
PerlvarCollector,
PerlvarCollector,
PerlvarCollector,
) {
let dict = Rc::new(RefCell::new(PerlvarDict::new()));
(
dict.clone(),
PerlvarCollector { dict: dict.clone(), kind: CollectorKind::Var },
PerlvarCollector { dict: dict.clone(), kind: CollectorKind::Init },
PerlvarCollector { dict: dict.clone(), kind: CollectorKind::Array },
PerlvarCollector { dict, kind: CollectorKind::Const },
)
}
}
impl MacroCalledCallback for PerlvarCollector {
fn on_macro_called(&mut self, args: Option<&[Vec<Token>]>, interner: &StringInterner) {
let Some(args) = args else { return };
if args.len() < 3 {
return;
}
let prefix = parse_prefix(&args[0], interner);
let name = arg_to_string(&args[1], interner);
let entry = match self.kind {
CollectorKind::Var => {
let c_type = arg_to_string(&args[2], interner);
PerlvarEntry {
name,
prefix,
kind: PerlvarKind::Var,
c_type,
}
}
CollectorKind::Init => {
if args.len() < 4 {
return;
}
let c_type = arg_to_string(&args[2], interner);
let init_expr = arg_to_string(&args[3], interner);
PerlvarEntry {
name,
prefix,
kind: PerlvarKind::Init { init_expr },
c_type,
}
}
CollectorKind::Array => {
if args.len() < 4 {
return;
}
let length = parse_array_length(&args[2], interner);
let c_type = arg_to_string(&args[3], interner);
PerlvarEntry {
name,
prefix,
kind: PerlvarKind::Array { length },
c_type,
}
}
CollectorKind::Const => {
if args.len() < 4 {
return;
}
let c_type = arg_to_string(&args[2], interner);
let init_expr = arg_to_string(&args[3], interner);
PerlvarEntry {
name,
prefix,
kind: PerlvarKind::Const { init_expr },
c_type,
}
}
};
self.dict.borrow_mut().insert(entry);
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
fn arg_to_string(tokens: &[Token], interner: &StringInterner) -> String {
let mut out = String::new();
let mut prev_was_word = false;
for t in tokens {
let s = t.kind.format(interner);
if s.is_empty() {
continue;
}
let starts_word = s
.chars()
.next()
.is_some_and(|c| c.is_alphanumeric() || c == '_');
if prev_was_word && starts_word {
out.push(' ');
}
out.push_str(&s);
prev_was_word = s
.chars()
.last()
.is_some_and(|c| c.is_alphanumeric() || c == '_');
}
out.trim().to_string()
}
fn parse_prefix(tokens: &[Token], interner: &StringInterner) -> char {
let s = arg_to_string(tokens, interner);
s.chars().next().unwrap_or('?')
}
fn parse_array_length(tokens: &[Token], interner: &StringInterner) -> ArrayLength {
let s = arg_to_string(tokens, interner);
if let Ok(n) = s.parse::<usize>() {
ArrayLength::Literal(n)
} else {
ArrayLength::Symbolic(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dict_iteration_is_sorted() {
let mut d = PerlvarDict::new();
for n in ["zebra", "alpha", "mango"] {
d.insert(PerlvarEntry {
name: n.to_string(),
prefix: 'I',
kind: PerlvarKind::Var,
c_type: "int".to_string(),
});
}
let names: Vec<&str> = d.iter().map(|e| e.name.as_str()).collect();
assert_eq!(names, vec!["alpha", "mango", "zebra"]);
}
#[test]
fn array_length_classification() {
assert_eq!(parse_array_length_str("4"), ArrayLength::Literal(4));
assert_eq!(
parse_array_length_str("SVt_LAST"),
ArrayLength::Symbolic("SVt_LAST".to_string())
);
}
fn parse_array_length_str(s: &str) -> ArrayLength {
if let Ok(n) = s.parse::<usize>() {
ArrayLength::Literal(n)
} else {
ArrayLength::Symbolic(s.to_string())
}
}
}