| 1 | module main |
| 2 | |
| 3 | import compress.lz |
| 4 | import os |
| 5 | |
| 6 | const default_data_size = 512 * 1024 |
| 7 | |
| 8 | fn main() { |
| 9 | mut data_size := default_data_size |
| 10 | if os.args.len > 1 { |
| 11 | parsed := int(os.args[1].i32()) |
| 12 | if parsed > 0 { |
| 13 | data_size = parsed |
| 14 | } |
| 15 | } |
| 16 | data := `z`.repeat(data_size).bytes() |
| 17 | |
| 18 | println('LZ interop input: ${data.len} bytes') |
| 19 | |
| 20 | for format in [lz.Format.lz77, .lz78, .lzw, .lz4, .lzss, .lzma, .lzma2, .lzjb] { |
| 21 | validate_v_roundtrip(data, format) or { |
| 22 | eprintln('V validation failed for ${format}: ${err.msg()}') |
| 23 | exit(1) |
| 24 | } |
| 25 | println('V roundtrip (${format}): OK') |
| 26 | } |
| 27 | |
| 28 | tmp_dir := os.join_path(os.temp_dir(), 'v_lz_interop') |
| 29 | os.mkdir_all(tmp_dir) or { |
| 30 | eprintln('Could not create temp directory ${tmp_dir}: ${err.msg()}') |
| 31 | exit(1) |
| 32 | } |
| 33 | defer { |
| 34 | os.rmdir_all(tmp_dir) or { |
| 35 | eprintln('Could not remove temp directory ${tmp_dir}: ${err.msg()}') |
| 36 | } |
| 37 | } |
| 38 | input_path := os.join_path(tmp_dir, 'input.bin') |
| 39 | os.write_file_array(input_path, data) or { |
| 40 | eprintln('Could not write input file ${input_path}: ${err.msg()}') |
| 41 | exit(1) |
| 42 | } |
| 43 | mut c_bin := '' |
| 44 | if bin := compile_c_runner() { |
| 45 | c_bin = bin |
| 46 | } else { |
| 47 | eprintln('Skipping C benchmark: ${err.msg()}') |
| 48 | } |
| 49 | python_ok := has_python3() |
| 50 | if !python_ok { |
| 51 | eprintln('Skipping Python benchmark: python3 is not available') |
| 52 | } |
| 53 | |
| 54 | if c_bin.len > 0 { |
| 55 | cross_validate_v_c(c_bin, data, input_path, tmp_dir) or { |
| 56 | eprintln('Cross-validation V<->C failed: ${err.msg()}') |
| 57 | exit(1) |
| 58 | } |
| 59 | println('Cross-validation: V<->C compress/decompress OK') |
| 60 | } else { |
| 61 | println('Cross-validation: skipped V<->C (requires C compiler)') |
| 62 | } |
| 63 | |
| 64 | if python_ok { |
| 65 | cross_validate_v_python(data, input_path, tmp_dir) or { |
| 66 | eprintln('Cross-validation V<->Python failed: ${err.msg()}') |
| 67 | exit(1) |
| 68 | } |
| 69 | println('Cross-validation: V<->Python compress/decompress OK') |
| 70 | } else { |
| 71 | println('Cross-validation: skipped V<->Python (requires python3)') |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | fn validate_v_roundtrip(data []u8, format lz.Format) ! { |
| 76 | encoded := lz.compress(data, format)! |
| 77 | decoded := lz.decompress(encoded, format)! |
| 78 | if decoded != data { |
| 79 | return error('roundtrip mismatch for ${format}') |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | fn compile_c_runner() !string { |
| 84 | cc := choose_cc() |
| 85 | if cc.len == 0 { |
| 86 | return error('no C compiler found (tried cc, gcc, and clang)') |
| 87 | } |
| 88 | bin_path := os.join_path(os.temp_dir(), 'lz77_ref_bench') |
| 89 | c_src := os.join_path(@DIR, 'lz77_ref.c') |
| 90 | compile_cmd := '${cc} -O3 -std=c99 ${os.quoted_path(c_src)} -o ${os.quoted_path(bin_path)}' |
| 91 | compile_res := os.execute(compile_cmd) |
| 92 | if compile_res.exit_code != 0 { |
| 93 | return error('C compile failed: ${compile_res.output.trim_space()}') |
| 94 | } |
| 95 | return bin_path |
| 96 | } |
| 97 | |
| 98 | fn choose_cc() string { |
| 99 | for cc in ['cc', 'gcc', 'clang'] { |
| 100 | if os.execute('${cc} --version').exit_code == 0 { |
| 101 | return cc |
| 102 | } |
| 103 | } |
| 104 | return '' |
| 105 | } |
| 106 | |
| 107 | fn has_python3() bool { |
| 108 | return os.execute('python3 --version').exit_code == 0 |
| 109 | } |
| 110 | |
| 111 | fn cross_validate_v_c(c_bin string, original []u8, input_path string, tmp_dir string) ! { |
| 112 | v_encoded := os.join_path(tmp_dir, 'v_encoded.bin') |
| 113 | c_decoded := os.join_path(tmp_dir, 'c_decoded.bin') |
| 114 | c_encoded := os.join_path(tmp_dir, 'c_encoded.bin') |
| 115 | |
| 116 | v_stream := lz.compress_lz77(original)! |
| 117 | os.write_file_array(v_encoded, v_stream)! |
| 118 | |
| 119 | mut res := |
| 120 | os.execute('${os.quoted_path(c_bin)} decompress ${os.quoted_path(v_encoded)} ${os.quoted_path(c_decoded)}') |
| 121 | if res.exit_code != 0 { |
| 122 | return error('C decompress(V output) failed: ${res.output.trim_space()}') |
| 123 | } |
| 124 | validate_equal_files(input_path, c_decoded, 'V->C')! |
| 125 | |
| 126 | res = |
| 127 | os.execute('${os.quoted_path(c_bin)} compress ${os.quoted_path(input_path)} ${os.quoted_path(c_encoded)}') |
| 128 | if res.exit_code != 0 { |
| 129 | return error('C compress failed: ${res.output.trim_space()}') |
| 130 | } |
| 131 | c_encoded_data := os.read_bytes(c_encoded)! |
| 132 | v_decoded := lz.decompress_lz77(c_encoded_data)! |
| 133 | if v_decoded != original { |
| 134 | return error('C->V output mismatch') |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | fn cross_validate_v_python(original []u8, input_path string, tmp_dir string) ! { |
| 139 | v_encoded := os.join_path(tmp_dir, 'v_encoded_for_py.bin') |
| 140 | py_decoded := os.join_path(tmp_dir, 'py_decoded.bin') |
| 141 | py_encoded := os.join_path(tmp_dir, 'py_encoded.bin') |
| 142 | py_script := os.join_path(@DIR, 'lz77_ref.py') |
| 143 | |
| 144 | v_stream := lz.compress_lz77(original)! |
| 145 | os.write_file_array(v_encoded, v_stream)! |
| 146 | |
| 147 | mut res := |
| 148 | os.execute('python3 ${os.quoted_path(py_script)} decompress ${os.quoted_path(v_encoded)} ${os.quoted_path(py_decoded)}') |
| 149 | if res.exit_code != 0 { |
| 150 | return error('Python decompress(V output) failed: ${res.output.trim_space()}') |
| 151 | } |
| 152 | validate_equal_files(input_path, py_decoded, 'V->Python')! |
| 153 | |
| 154 | res = |
| 155 | os.execute('python3 ${os.quoted_path(py_script)} compress ${os.quoted_path(input_path)} ${os.quoted_path(py_encoded)}') |
| 156 | if res.exit_code != 0 { |
| 157 | return error('Python compress failed: ${res.output.trim_space()}') |
| 158 | } |
| 159 | py_encoded_data := os.read_bytes(py_encoded)! |
| 160 | v_decoded := lz.decompress_lz77(py_encoded_data)! |
| 161 | if v_decoded != original { |
| 162 | return error('Python->V output mismatch') |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | fn validate_equal_files(expected_path string, actual_path string, tag string) ! { |
| 167 | expected := os.read_bytes(expected_path)! |
| 168 | actual := os.read_bytes(actual_path)! |
| 169 | if expected != actual { |
| 170 | return error('${tag} output mismatch') |
| 171 | } |
| 172 | } |
| 173 | |