wxml-parser-rs 0.0.2

Core WXML parser implemented in Rust
Documentation
use serde_json::{json, Value};
use std::env;
use std::fs;
use std::time::Instant;
use wxml_parser_rs::{parse_for_eslint_json, parse_for_eslint_json_string, parse_json, parse_json_string};

fn summarize_parse(result: &Value) -> Value {
  json!({
    "type": result.get("type").and_then(Value::as_str).unwrap_or(""),
    "bodyLength": result.get("body").and_then(Value::as_array).map_or(0, |v| v.len()),
    "errorsLength": result.get("errors").and_then(Value::as_array).map_or(0, |v| v.len()),
  })
}

fn summarize_parse_for_eslint(result: &Value) -> Value {
  let ast = result.get("ast").unwrap_or(result);
  let visitor_program = result
    .get("visitorKeys")
    .and_then(|v| v.get("Program"))
    .and_then(Value::as_array)
    .map_or(0, |v| v.len());

  json!({
    "type": ast.get("type").and_then(Value::as_str).unwrap_or(""),
    "bodyLength": ast.get("body").and_then(Value::as_array).map_or(0, |v| v.len()),
    "errorsLength": ast.get("errors").and_then(Value::as_array).map_or(0, |v| v.len()),
    "visitorProgramLength": visitor_program,
  })
}

fn parse_args() -> Result<(String, String, usize, usize, usize), String> {
  let mut args = env::args().skip(1);
  let mode = args.next().ok_or("missing mode")?;
  let fixture_path = args.next().ok_or("missing fixture path")?;
  let warmup = args
    .next()
    .ok_or("missing warmup")?
    .parse::<usize>()
    .map_err(|_| "invalid warmup")?;
  let rounds = args
    .next()
    .ok_or("missing rounds")?
    .parse::<usize>()
    .map_err(|_| "invalid rounds")?;
  let iterations = args
    .next()
    .ok_or("missing iterations")?
    .parse::<usize>()
    .map_err(|_| "invalid iterations")?;
  Ok((mode, fixture_path, warmup, rounds, iterations))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
  let (mode, fixture_path, warmup, rounds, iterations) = parse_args().map_err(|msg| {
    std::io::Error::new(std::io::ErrorKind::InvalidInput, msg)
  })?;

  let code = fs::read_to_string(&fixture_path)?;

  let summarize: fn(&Value) -> Value = match mode.as_str() {
    "parse" => summarize_parse,
    "parseForESLint" => summarize_parse_for_eslint,
    _ => {
      return Err(std::io::Error::new(
        std::io::ErrorKind::InvalidInput,
        format!("unsupported mode: {}", mode),
      )
      .into())
    }
  };

  let runner: fn(&str) -> Value = match mode.as_str() {
    "parse" => parse_json,
    "parseForESLint" => parse_for_eslint_json,
    _ => unreachable!(),
  };

  let runner_string: fn(&str) -> String = match mode.as_str() {
    "parse" => parse_json_string,
    "parseForESLint" => parse_for_eslint_json_string,
    _ => unreachable!(),
  };

  let baseline = runner(&code);
  let summary = summarize(&baseline);

  // Benchmark the string-return path (no serde_json::Value construction)
  for _ in 0..warmup {
    for _ in 0..iterations {
      let result = runner_string(&code);
      std::hint::black_box(result);
    }
  }

  let mut round_ns = Vec::with_capacity(rounds);
  for _ in 0..rounds {
    let start = Instant::now();
    for _ in 0..iterations {
      let result = runner_string(&code);
      std::hint::black_box(result);
    }
    round_ns.push(start.elapsed().as_nanos() as f64);
  }

  let total_ns: f64 = round_ns.iter().sum();
  let avg_ns_per_round = total_ns / rounds as f64;
  let avg_ns_per_op = avg_ns_per_round / iterations as f64;

  let mut sorted = round_ns.clone();
  sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
  let median_ns_per_round = if rounds % 2 == 0 {
    (sorted[rounds / 2 - 1] + sorted[rounds / 2]) / 2.0
  } else {
    sorted[rounds / 2]
  };
  let median_ns_per_op = median_ns_per_round / iterations as f64;
  let ops_per_sec = 1_000_000_000.0 / avg_ns_per_op;

  let output = json!({
    "implementation": "rust-core",
    "mode": mode,
    "fixturePath": fixture_path,
    "summary": summary,
    "stats": {
      "warmup": warmup,
      "rounds": rounds,
      "iterations": iterations,
      "avgMsPerOp": avg_ns_per_op / 1_000_000.0,
      "medianMsPerOp": median_ns_per_op / 1_000_000.0,
      "opsPerSec": ops_per_sec,
    }
  });

  println!("{}", serde_json::to_string(&output)?);
  Ok(())
}