1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use sqparse::{parse, tokenize, Flavor};
use std::path::{Path, PathBuf};
use std::time::Instant;

fn main() {
    let mut args = std::env::args();
    let exe = args.next().unwrap();

    let base_path = match args.next() {
        Some(arg) => PathBuf::from(arg),
        None => {
            eprintln!("Usage: {exe} [path]");
            eprintln!();
            eprintln!("Provide a path to a file to parse that file, or a path to a directory to");
            eprintln!("recursively parse all .nut and .gnut files in the directory");
            std::process::exit(1);
        }
    };

    let mut total_size_bytes = 0;
    let mut total_lex_secs = 0.;
    let mut total_parse_secs = 0.;

    visit(&base_path, &mut |path| {
        let extension = path.extension().and_then(|val| val.to_str());
        if !matches!(extension, Some("nut") | Some("gnut")) {
            return;
        }

        println!("{}", path.display());

        let file_text = match std::fs::read_to_string(path) {
            Ok(text) => text,
            Err(err) => {
                println!("  could not read: {err}");
                return;
            }
        };

        let lex_start = Instant::now();
        let tokens = match tokenize(&file_text, Flavor::SquirrelRespawn) {
            Ok(tokens) => tokens,
            Err(err) => {
                eprintln!("{}", err.display(&file_text));
                std::process::exit(1);
            }
        };
        let lex_secs = lex_start.elapsed().as_secs_f64();
        println!("  tokenize: {lex_secs}s");

        let parse_start = Instant::now();
        if let Err(err) = parse(&tokens) {
            eprintln!("{}", err.display(&file_text, &tokens));
            std::process::exit(1);
        }
        let parse_secs = parse_start.elapsed().as_secs_f64();
        println!("  parse: {parse_secs}s");

        total_size_bytes += file_text.bytes().len();
        total_lex_secs += lex_secs;
        total_parse_secs += parse_secs;
    });

    let total_mb = total_size_bytes as f64 / 1048576.;
    println!("Finished!");
    println!(
        "Tokenize: {:.4}s, {:.2} MB/s",
        total_lex_secs,
        total_mb / total_lex_secs
    );
    println!(
        "Parse: {:.4}s, {:.2} MB/s",
        total_parse_secs,
        total_mb / total_parse_secs
    );
}

fn visit<F: FnMut(&Path)>(path: &Path, cb: &mut F) {
    if path.is_file() {
        cb(path);
    } else if path.is_dir() {
        for entry in std::fs::read_dir(path).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();
            visit(&path, cb);
        }
    }
}