// -*- coding: utf-8 -*- // ------------------------------------------------------------------------------------------------ // Copyright © 2024, stack-graphs authors. // Licensed under either of Apache License, Version 2.0, or MIT license, at your option. // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ use std::path::Path; use anyhow::{bail, Result}; use regex::Regex; const TSG_SOURCE: &str = "src/stack-graphs.tsg"; const DIALECTS: [&str; 2] = ["typescript", "tsx"]; /// preprocess the input file, removing lines that are not for the selected dialect fn preprocess( input: impl std::io::Read, mut output: impl std::io::Write, dialect: &str, ) -> Result<()> { // Matches: ; #dialect typescript let directive_start = Regex::new(r";\s*#dialect\s+(\w+)").unwrap(); // Matches: ; #end let directive_end = Regex::new(r";\s*#end").unwrap(); let no_code = Regex::new(r"^[\s;]+").unwrap(); let input = std::io::read_to_string(input)?; // If the filter is None or Some(true), the lines are written to the output let mut filter: Option = None; for (mut line_no, line) in input.lines().enumerate() { // Line numbers are one based line_no += 1; if let Some(captures) = directive_start.captures(line) { if !no_code.is_match(line) { bail!("Line {line_no}: unexpected code before directive"); } if filter.is_some() { bail!("Line {line_no}: dialect directive cannot be nested"); } let directive = captures.get(1).unwrap().as_str(); if !DIALECTS.contains(&directive) { bail!("Line {line_no}: unknown dialect: {directive}"); } filter = Some(dialect == directive); } else if directive_end.is_match(line) { if !no_code.is_match(line) { bail!("Line {line_no}: unexpected code before directive end"); } if filter.is_none() { bail!("Line {line_no}: unmatched directive end"); } filter = None; } else if filter.unwrap_or(true) { output.write_all(line.as_bytes())?; } // a new line is always written so that removed lines are padded to preserve line numbers output.write(b"\n")?; } if filter.is_some() { bail!("Unmatched directive end at the end of the file"); } Ok(()) } fn main() { let out_dir = std::env::var_os("OUT_DIR").expect("OUT_DIR is not set"); for dialect in DIALECTS { let input = std::fs::File::open(TSG_SOURCE).expect("Failed to open stack-graphs.tsg"); let out_filename = Path::new(&out_dir).join(format!("stack-graphs-{dialect}.tsg")); let output = std::fs::File::create(out_filename).expect("Failed to create output file"); preprocess(input, output, dialect).expect("Failed to preprocess stack-graphs.tsg"); } println!("cargo:rerun-if-changed={TSG_SOURCE}"); println!("cargo:rerun-if-changed=build.rs"); }