celox-macros 0.10.0

Celox HDL Simulator
Documentation
#![allow(clippy::disallowed_methods)] // proc-macro build context is an application boundary

mod generator;

use proc_macro::TokenStream;
use std::env;
use std::fs;
use std::path::PathBuf;
use syn::LitStr;
use veryl_analyzer::ir::Ir;
use veryl_analyzer::{Analyzer, Context};
use veryl_metadata::Metadata;
use veryl_parser::Parser;

#[proc_macro]
pub fn veryl_test(input: TokenStream) -> TokenStream {
    let project_path_lit = match syn::parse::<LitStr>(input.clone()) {
        Ok(lit) => lit,
        Err(err) => return err.to_compile_error().into(),
    };
    let project_path_str = project_path_lit.value();
    let span = project_path_lit.span();

    macro_rules! exit_with_error {
        ($msg:expr) => {{
            let err_msg = $msg;
            return quote::quote_spanned! { span => compile_error!(#err_msg); }.into();
        }};
    }

    // 1. Find Veryl.toml
    let manifest_dir = match env::var("CARGO_MANIFEST_DIR") {
        Ok(dir) => dir,
        Err(_) => exit_with_error!("CARGO_MANIFEST_DIR is not set"),
    };
    let base_dir = PathBuf::from(manifest_dir);
    let target_dir = base_dir.join(project_path_str);

    let metadata_path = match Metadata::search_from(&target_dir) {
        Ok(path) => path,
        Err(_) => exit_with_error!(&format!("Failed to find Veryl.toml from {:?}", target_dir)),
    };

    let mut metadata = match Metadata::load(&metadata_path) {
        Ok(meta) => meta,
        Err(e) => exit_with_error!(&format!("Failed to load Veryl.toml: {}", e)),
    };

    // 2. Run Analysis
    veryl_analyzer::symbol_table::clear();
    veryl_analyzer::attribute_table::clear();
    let mut ir = Ir::default();
    let mut context = Context::default();
    let analyzer = Analyzer::new(&metadata);
    analyzer.clear();

    let paths = match metadata.paths::<PathBuf>(&[], false, true) {
        Ok(p) => p
            .into_iter()
            .filter(|path| !path.example)
            .collect::<Vec<_>>(),
        Err(e) => exit_with_error!(&format!("Failed to gather paths: {}", e)),
    };

    // Analyze every source file so pass1 registers all declarations before pass2.
    let mut parsed_files = Vec::new();
    for path_set in paths {
        let code = match fs::read_to_string(&path_set.src) {
            Ok(c) => c,
            Err(e) => {
                exit_with_error!(&format!("Failed to read {}: {}", path_set.src.display(), e))
            }
        };
        parsed_files.push((path_set, code));
    }

    let mut parsers = Vec::new();
    for (path_set, code) in &parsed_files {
        let parser = match Parser::parse(code, &path_set.src) {
            Ok(p) => p,
            Err(_) => exit_with_error!(&format!("Failed to parse {}", path_set.src.display())),
        };
        let errors = analyzer.analyze_pass1(&path_set.prj, &parser.veryl);
        if !errors.is_empty() {
            let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
            exit_with_error!(&format!("Analysis pass1 errors: {}", msgs.join("; ")));
        }
        parsers.push(parser);
    }
    let errors = Analyzer::analyze_post_pass1();
    if !errors.is_empty() {
        let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
        exit_with_error!(&format!("Analysis post-pass1 errors: {}", msgs.join("; ")));
    }

    for (i, (_path_set, _code)) in parsed_files.iter().enumerate() {
        let parser = &parsers[i];
        let errors = analyzer.analyze_pass2(&parser.veryl, &mut context, Some(&mut ir));
        if !errors.is_empty() {
            let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
            exit_with_error!(&format!("Analysis pass2 errors: {}", msgs.join("; ")));
        }
    }
    let errors = Analyzer::analyze_post_pass2(&ir);
    if !errors.is_empty() {
        let msgs: Vec<String> = errors.iter().map(|e| format!("{e}")).collect();
        exit_with_error!(&format!("Analysis post-pass2 errors: {}", msgs.join("; ")));
    }

    // 3. Generate TokenStream
    let expanded = generator::generate_project(&ir);

    TokenStream::from(expanded)
}