use std::collections::HashMap;
use sv_lang::kinds::{SyntaxKind, TokenKind, TriviaKind};
use crate::preproc::{self, Node, Tok};
pub type Part = (String, Option<(u32, u32)>);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FsmDef {
pub name: String,
pub parts: Vec<Part>,
pub states: Vec<String>,
}
enum Pragma {
StateVector {
parts: Vec<Part>,
name: Option<String>,
en: Option<String>,
},
Enum(String),
}
fn comment_body(text: &str) -> &str {
if let Some(b) = text.strip_prefix("/*") {
b.strip_suffix("*/").unwrap_or(b)
} else {
text.strip_prefix("//").unwrap_or(text)
}
}
fn parse_part(s: &str) -> Option<Part> {
let s = s.trim();
let Some((n, rest)) = s.split_once('[') else {
return (!s.is_empty()).then(|| (s.to_string(), None));
};
let (a, b) = rest.strip_suffix(']')?.split_once(':')?;
Some((
n.trim().to_string(),
Some((a.trim().parse().ok()?, b.trim().parse().ok()?)),
))
}
fn parse_pragma(text: &str) -> Option<Pragma> {
let body = comment_body(text).trim();
let rest = body.strip_prefix("tool")?;
if !rest.starts_with(char::is_whitespace) {
return None;
}
let rest = rest.trim_start();
if let Some(r) = rest.strip_prefix("enum") {
let name = r.split_whitespace().next()?;
return Some(Pragma::Enum(name.to_string()));
}
let r = rest.strip_prefix("state_vector")?;
if !r.starts_with(char::is_whitespace) {
return None;
}
let r = r.trim_start();
let (parts, tail) = if let Some(c) = r.strip_prefix('{') {
let (list, tail) = c.split_once('}')?;
let parts = list
.split(',')
.map(parse_part)
.collect::<Option<Vec<_>>>()?;
(parts, tail)
} else {
let end = r.find(char::is_whitespace).unwrap_or(r.len());
let (mut head, mut tail) = r.split_at(end);
let t = tail.trim_start();
let owned;
if t.starts_with('[') && !head.contains('[') {
let close = t.find(']')? + 1;
owned = format!("{head}{}", &t[..close]);
head = &owned;
tail = &t[close..];
}
(vec![parse_part(head)?], tail)
};
let mut words = tail.split_whitespace();
let mut name = None;
let mut en = None;
while let Some(w) = words.next() {
if w == "enum" {
en = words.next().map(str::to_string);
} else if name.is_none() {
name = Some(w.to_string());
}
}
Some(Pragma::StateVector { parts, name, en })
}
pub fn extract(src: &str, path: &str, include_dirs: &[String]) -> HashMap<String, Vec<FsmDef>> {
let mut out = HashMap::new();
if !src.contains("state_vector") {
return out;
}
if let Some(root) = preproc::parse(src, path, include_dirs) {
walk(&root, &mut out);
}
out
}
fn walk(n: &Node, out: &mut HashMap<String, Vec<FsmDef>>) {
if n.kind == SyntaxKind::ModuleDeclaration {
let toks = n.tokens();
let name = toks
.iter()
.skip_while(|t| t.kind != TokenKind::ModuleKeyword)
.find(|t| t.kind == TokenKind::Identifier)
.map(|t| t.text.clone());
if let Some(name) = name {
let defs = module_fsms(&toks);
if !defs.is_empty() {
out.entry(name).or_default().extend(defs);
}
}
return;
}
for m in &n.members {
walk_elem(m, out);
}
}
fn walk_elem(e: &preproc::Elem, out: &mut HashMap<String, Vec<FsmDef>>) {
match e {
preproc::Elem::Node(n) => walk(n, out),
preproc::Elem::List(v) => v.iter().for_each(|e| walk_elem(e, out)),
_ => {}
}
}
fn declared_names(toks: &[&Tok], i: usize) -> Vec<String> {
let mut names = Vec::new();
let mut depth = 0i32;
let mut want = true;
for t in &toks[i..] {
match t.kind {
TokenKind::Semicolon if depth == 0 => break,
TokenKind::OpenParenthesis | TokenKind::OpenBracket | TokenKind::OpenBrace => {
depth += 1
}
TokenKind::CloseParenthesis | TokenKind::CloseBracket | TokenKind::CloseBrace => {
depth -= 1;
if depth < 0 {
break;
}
}
TokenKind::Comma if depth == 0 => want = true,
TokenKind::Identifier if depth == 0 && want => {
names.push(t.text.clone());
want = false;
}
_ => {}
}
}
names
}
fn module_fsms(toks: &[&Tok]) -> Vec<FsmDef> {
let mut vectors: Vec<(Vec<Part>, Option<String>, Option<String>)> = Vec::new();
let mut sig_enum: HashMap<String, String> = HashMap::new();
let mut enum_states: HashMap<String, Vec<String>> = HashMap::new();
let mut in_param = false;
for (i, t) in toks.iter().enumerate() {
if matches!(
t.kind,
TokenKind::ParameterKeyword | TokenKind::LocalParamKeyword
) {
in_param = true;
}
for (k, text) in &t.trivia {
if !matches!(k, TriviaKind::BlockComment | TriviaKind::LineComment) {
continue;
}
match parse_pragma(text) {
Some(Pragma::StateVector { parts, name, en }) => vectors.push((parts, name, en)),
Some(Pragma::Enum(e)) if in_param => {
enum_states
.entry(e)
.or_default()
.extend(declared_names(toks, i));
}
Some(Pragma::Enum(e)) => {
for n in declared_names(toks, i) {
sig_enum.entry(n).or_insert_with(|| e.clone());
}
}
None => {}
}
}
if t.kind == TokenKind::Semicolon {
in_param = false;
}
}
vectors
.into_iter()
.map(|(parts, name, en)| {
let en = en.or_else(|| sig_enum.get(&parts[0].0).cloned());
let name = name.unwrap_or_else(|| parts[0].0.clone());
let states = en
.and_then(|e| enum_states.get(&e).cloned())
.unwrap_or_default();
FsmDef {
name,
parts,
states,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pragma_forms() {
let Some(Pragma::StateVector { parts, name, en }) =
parse_pragma("/* tool state_vector cs[3:0] F enum E2 */")
else {
panic!()
};
assert_eq!(parts, vec![("cs".to_string(), Some((3, 0)))]);
assert_eq!((name.as_deref(), en.as_deref()), (Some("F"), Some("E2")));
let Some(Pragma::StateVector { parts, .. }) =
parse_pragma("// tool state_vector {a, b} CAT enum E")
else {
panic!()
};
assert_eq!(parts.len(), 2);
assert!(parse_pragma("// tool state_vectors x").is_none());
assert!(matches!(parse_pragma("// tool enum E"), Some(Pragma::Enum(e)) if e == "E"));
}
}