mod ast;
mod desugar;
mod error;
mod fmt;
mod layout;
mod lexer;
mod lint;
mod render;
mod resolve;
mod serve;
mod span;
mod syntax;
mod theme;
pub use error::{Diagnostic, Error, Level};
pub use fmt::format as format_source;
pub fn desugar_source(src: &str) -> Result<String, Error> {
let tokens = lexer::lex(src)?;
let file = syntax::parser::parse(&tokens)?;
Ok(fmt::print_file(&desugar::desugar(&file)))
}
pub use layout::{Rule, Severity, Violation};
pub use serve::serve;
pub use theme::extract_lini_vars;
#[derive(Clone, Debug, Default)]
pub struct Options {
pub bake_vars: bool,
pub format: OutputFormat,
pub theme_css: Option<String>,
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputFormat {
#[default]
Svg,
Html,
}
pub fn compile_str(src: &str) -> Result<String, Error> {
compile_str_with(src, &Options::default())
}
pub fn compile_str_with(src: &str, opts: &Options) -> Result<String, Error> {
let program = resolve_pipeline(src, opts)?;
let laid_out = layout::layout(&program)?;
Ok(finish_svg(&laid_out, opts))
}
pub fn compile_str_checked(src: &str, opts: &Options) -> Result<(String, Vec<Diagnostic>), Error> {
let program = resolve_pipeline(src, opts)?;
let laid_out = layout::layout(&program)?;
let diags = routing_diagnostics_of(layout::validate_routing(&laid_out));
Ok((finish_svg(&laid_out, opts), diags))
}
fn finish_svg(laid_out: &layout::LaidOut, opts: &Options) -> String {
let svg = render::render(laid_out, opts);
match opts.format {
OutputFormat::Svg => svg,
OutputFormat::Html => wrap_html(&svg),
}
}
pub fn check_parse(src: &str) -> Result<(), Error> {
let tokens = lexer::lex(src)?;
let _file = syntax::parser::parse(&tokens)?;
Ok(())
}
pub fn lint_str(src: &str) -> Result<Vec<Diagnostic>, Error> {
let tokens = lexer::lex(src)?;
let file = syntax::parser::parse(&tokens)?;
Ok(lint::lint(&file))
}
pub fn check(src: &str) -> Result<(), Error> {
check_with(src, &Options::default())
}
pub fn check_with(src: &str, opts: &Options) -> Result<(), Error> {
let _ = resolve_pipeline(src, opts)?;
Ok(())
}
pub fn validate_str(src: &str) -> Result<Vec<Violation>, Error> {
let program = resolve_pipeline(src, &Options::default())?;
let laid_out = layout::layout(&program)?;
Ok(layout::validate_routing(&laid_out))
}
fn routing_diagnostics_of(violations: Vec<Violation>) -> Vec<Diagnostic> {
violations
.into_iter()
.filter(|v| v.severity != Severity::Info)
.map(|v| {
Diagnostic::warn(
v.span,
format!("{} ({}): {}", v.rule.id(), v.wires.join(", "), v.detail),
)
})
.collect()
}
fn resolve_pipeline(src: &str, opts: &Options) -> Result<resolve::Program, Error> {
let tokens = lexer::lex(src)?;
let file = syntax::parser::parse(&tokens)?;
let theme = match &opts.theme_css {
Some(css) => theme::extract_lini_vars(css),
None => Vec::new(),
};
resolve::resolve_with_theme(&file, &theme)
}
fn wrap_html(svg: &str) -> String {
format!(
"<!doctype html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>lini</title>\n</head>\n<body>\n{}</body>\n</html>\n",
svg
)
}
#[doc(hidden)]
pub mod testing {
use crate::Options;
use crate::layout;
use crate::resolve::ResolvedValue;
pub use crate::layout::LaidOut;
pub fn node_rect(laid: &LaidOut, path: &str) -> Option<(f64, f64, f64, f64)> {
layout::node_rect(&laid.nodes, path)
}
pub fn route_sample(src: &str, clearance: f64) -> LaidOut {
layout::layout(&forced(src, clearance)).expect("layout")
}
pub fn route_sample_raw(src: &str, clearance: f64) -> LaidOut {
layout::layout_raw(&forced(src, clearance)).expect("layout")
}
fn forced(src: &str, clearance: f64) -> crate::resolve::Program {
let mut prog = super::resolve_pipeline(src, &Options::default()).expect("resolve");
for w in &mut prog.wires {
w.attrs
.insert("clearance", ResolvedValue::Number(clearance));
}
prog
}
pub fn declared_edges(src: &str) -> usize {
let prog = super::resolve_pipeline(src, &Options::default()).expect("resolve");
prog.wires
.iter()
.map(|w| w.endpoints.len().saturating_sub(1))
.sum()
}
pub fn laws(laid: &LaidOut) -> Vec<crate::Violation> {
layout::validate_routing(laid)
}
}