use std::collections::HashSet;
use std::path::Path;
use crate::deck::{self, Card, Deck, IncludeLoader};
use crate::diag::Diagnostic;
use crate::elem::{ElemCtx, parse_element};
use crate::ir::{Model, Netlist};
use crate::param::ParamEnv;
use crate::scan::Scanner;
use crate::span::SourceMap;
use crate::subckt::{Scope, SubcktDef, expand};
const MODEL_TYPES: &[&str] = &[
"r", "c", "sw", "csw", "urc", "ltra", "d", "npn", "pnp", "njf", "pjf", "nmos", "pmos", "nmf",
"pmf",
];
#[derive(Debug)]
pub struct ParseOutcome {
pub netlist: Netlist,
pub diags: Vec<Diagnostic>,
pub sources: SourceMap,
}
impl ParseOutcome {
pub fn is_clean(&self) -> bool {
self.diags
.iter()
.all(|d| d.severity != crate::diag::Severity::Error)
}
}
pub fn parse_file(path: &Path) -> Result<ParseOutcome, crate::Error> {
let deck = deck::read_file(path)?;
Ok(parse_deck(deck))
}
pub fn parse_with_loader(
name: &Path,
text: String,
loader: &mut dyn IncludeLoader,
) -> ParseOutcome {
parse_deck(deck::read_with_loader(name, text, loader))
}
pub fn parse_str(name: &str, text: &str) -> ParseOutcome {
parse_with_loader(Path::new(name), text.to_string(), &mut deck::FsLoader)
}
fn first_keyword(card: &Card) -> String {
Scanner::new(card)
.next_tok()
.map(|t| t.lc())
.unwrap_or_default()
}
fn parse_deck(deck: Deck) -> ParseOutcome {
let Deck {
title,
cards,
sources,
diags: mut all_diags,
} = deck;
let mut nl = Netlist {
title,
..Netlist::default()
};
let mut model_names: HashSet<String> = HashSet::new();
let mut env = ParamEnv::default();
let mut in_control = false;
for card in &cards {
let kw = first_keyword(card);
if in_control {
in_control = kw != ".endc";
continue;
}
match kw.as_str() {
".control" => in_control = true,
".model" => {
let mut sc = Scanner::new(card);
sc.next_tok();
if let Some(name) = sc.next_tok() {
model_names.insert(name.lc());
}
}
".param" => collect_params(card, &mut env, &mut all_diags),
_ => {}
}
}
let mut top = Scope::default();
let mut stack: Vec<SubcktDef> = Vec::new();
let mut in_control = false;
let mut saw_end = false;
let mut after_end = 0usize;
for card in &cards {
let kw = first_keyword(card);
if saw_end {
after_end += 1;
continue;
}
if in_control {
if kw == ".endc" {
in_control = false;
} else {
nl.control.push(card.text.clone());
}
continue;
}
let span = card.span();
match kw.as_str() {
".control" => in_control = true,
".endc" => {
all_diags.push(Diagnostic::error(".endc without .control", span));
}
".end" => saw_end = true,
".subckt" => {
let mut sc = Scanner::new(card);
sc.next_tok();
let Some(name) = sc.next_tok() else {
all_diags.push(Diagnostic::error(".subckt needs a name", span));
continue;
};
let name = name.lc();
let mut ports = Vec::new();
while let Some(t) = sc.next_tok() {
ports.push(t.lc());
}
stack.push(SubcktDef {
name,
ports,
scope: Scope::default(),
span,
});
}
".ends" => {
let mut sc = Scanner::new(card);
sc.next_tok();
let named = sc.next_tok().map(|t| t.lc());
match stack.pop() {
None => {
all_diags.push(Diagnostic::error(".ends without .subckt", span));
}
Some(def) => {
if let Some(n) = named
&& n != def.name
{
all_diags.push(Diagnostic::warning(
format!(".ends {n} closes .subckt {}", def.name),
span,
));
}
let parent = match stack.last_mut() {
Some(outer) => &mut outer.scope,
None => &mut top,
};
parent.subckts.push(def);
}
}
}
".model" => {
let scope = match stack.last_mut() {
Some(def) => &mut def.scope,
None => &mut top,
};
if let Some(model) = parse_model(card, &mut all_diags, &env) {
scope.models.push(model);
}
}
".param" => {} ".include" => {
all_diags.push(Diagnostic::error(
".include is only recognized at column 1",
span,
));
}
k if k.starts_with('.') => {
if !stack.is_empty() {
all_diags.push(Diagnostic::error(
format!("{k} is not allowed inside .subckt"),
span,
));
} else if !crate::dot::parse_dot(k, card, &mut all_diags, &env, &mut nl) {
all_diags.push(Diagnostic::error(
format!("unknown control card `{k}`"),
span,
));
}
}
"" => {}
_ => {
let scope = match stack.last_mut() {
Some(def) => &mut def.scope,
None => &mut top,
};
if let Some(el) = parse_element(card, &mut all_diags, &model_names, &env) {
scope.elements.push(el);
}
}
}
}
for def in &stack {
all_diags.push(Diagnostic::error(
format!(".subckt {} never closed by .ends", def.name),
def.span,
));
}
if !saw_end {
all_diags.push(Diagnostic::global_warning("no .end card"));
}
if after_end > 0 {
all_diags.push(Diagnostic::global_warning(format!(
"{after_end} card(s) after .end ignored"
)));
}
let (elements, models) = expand(&top, &mut all_diags);
nl.elements = elements;
nl.models = models;
ParseOutcome {
netlist: nl,
diags: all_diags,
sources,
}
}
fn collect_params(card: &Card, env: &mut ParamEnv, diags: &mut Vec<Diagnostic>) {
let mut sc = Scanner::new(card);
sc.next_tok(); while let Some(name_tok) = sc.next_tok() {
let name = name_tok.lc();
let nspan = sc.span(&name_tok);
if crate::scan::evaluate(&name).is_some() {
diags.push(Diagnostic::error(
format!("`{name}` cannot be a parameter name"),
nspan,
));
return;
}
let value = if let Some((v, _)) = sc.try_value() {
Some(v)
} else if let Some(vtok) = sc.next_tok() {
let text = vtok.lc();
let vspan = sc.span(&vtok);
let result = if text.starts_with('{') {
env.eval_brace(&text)
} else {
env.eval_expr(&text)
};
match result {
Ok(v) => Some(v),
Err(e) => {
diags.push(Diagnostic::error(e, vspan));
None
}
}
} else {
diags.push(Diagnostic::error(
format!("parameter `{name}` has no value"),
nspan,
));
None
};
if let Some(v) = value {
env.define(&name, v);
}
}
}
fn parse_model(card: &Card, diags: &mut Vec<Diagnostic>, env: &ParamEnv) -> Option<Model> {
let span = card.span();
let mut ctx = ElemCtx {
sc: Scanner::new(card),
card,
diags,
env,
};
ctx.sc.next_tok(); let (name, _) = ctx.tok_req("model name")?;
let (mtype, tspan) = ctx.tok_req("model type")?;
if !MODEL_TYPES.contains(&mtype.as_str()) {
ctx.diags.push(Diagnostic::error(
format!("unknown model type `{mtype}`"),
tspan,
));
return None;
}
let params = ctx.keyword_params();
Some(Model {
name,
mtype,
params,
span,
})
}