| 1 | // Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | // vtest build: macos |
| 5 | module builder |
| 6 | |
| 7 | import os |
| 8 | import v2.ast |
| 9 | import v2.parser |
| 10 | import v2.pref |
| 11 | import v2.token |
| 12 | |
| 13 | fn parse_source_for_flat(src string) []ast.File { |
| 14 | tmp := '/tmp/v2_flat_roundtrip_${os.getpid()}.v' |
| 15 | os.write_file(tmp, src) or { panic('write_file: ${err}') } |
| 16 | defer { |
| 17 | os.rm(tmp) or {} |
| 18 | } |
| 19 | p := &pref.Preferences{} |
| 20 | mut fs := token.FileSet.new() |
| 21 | mut par := parser.Parser.new(p) |
| 22 | return par.parse_files([tmp], mut fs) |
| 23 | } |
| 24 | |
| 25 | fn assert_roundtrip(src string) { |
| 26 | files := parse_source_for_flat(src) |
| 27 | flat_a := ast.flatten_files(files) |
| 28 | rt_files := flat_a.to_files() |
| 29 | flat_b := ast.flatten_files(rt_files) |
| 30 | sig_a := flat_a.signature() |
| 31 | sig_b := flat_b.signature() |
| 32 | if sig_a != sig_b { |
| 33 | mut diff_at := 0 |
| 34 | for diff_at < sig_a.len && diff_at < sig_b.len && sig_a[diff_at] == sig_b[diff_at] { |
| 35 | diff_at++ |
| 36 | } |
| 37 | from := if diff_at > 80 { diff_at - 80 } else { 0 } |
| 38 | end_a := if diff_at + 200 < sig_a.len { diff_at + 200 } else { sig_a.len } |
| 39 | end_b := if diff_at + 200 < sig_b.len { diff_at + 200 } else { sig_b.len } |
| 40 | eprintln('signature mismatch at offset ${diff_at}') |
| 41 | eprintln('A: ${sig_a[from..end_a]}') |
| 42 | eprintln('B: ${sig_b[from..end_b]}') |
| 43 | } |
| 44 | assert sig_a == sig_b, 'round-trip signature mismatch for source:\n${src}' |
| 45 | } |
| 46 | |
| 47 | fn test_roundtrip_module_only() { |
| 48 | assert_roundtrip('module foo\n') |
| 49 | } |
| 50 | |
| 51 | fn test_roundtrip_simple_fn() { |
| 52 | src := 'module main |
| 53 | |
| 54 | fn add(a int, b int) int { |
| 55 | return a + b |
| 56 | } |
| 57 | ' |
| 58 | assert_roundtrip(src) |
| 59 | } |
| 60 | |
| 61 | fn test_roundtrip_struct_and_const() { |
| 62 | src := "module main |
| 63 | |
| 64 | pub const greeting = 'hello' |
| 65 | |
| 66 | pub struct Point { |
| 67 | pub mut: |
| 68 | x int |
| 69 | y int |
| 70 | } |
| 71 | |
| 72 | fn (p Point) sum() int { |
| 73 | return p.x + p.y |
| 74 | } |
| 75 | " |
| 76 | assert_roundtrip(src) |
| 77 | } |
| 78 | |
| 79 | fn test_roundtrip_control_flow() { |
| 80 | src := 'module main |
| 81 | |
| 82 | fn run(n int) int { |
| 83 | mut total := 0 |
| 84 | for i := 0; i < n; i++ { |
| 85 | if i % 2 == 0 { |
| 86 | total += i |
| 87 | } else { |
| 88 | total -= i |
| 89 | } |
| 90 | } |
| 91 | return total |
| 92 | } |
| 93 | ' |
| 94 | assert_roundtrip(src) |
| 95 | } |
| 96 | |
| 97 | fn test_roundtrip_string_inter() { |
| 98 | src := "module main |
| 99 | |
| 100 | fn show(name string, age int) string { |
| 101 | return 'name=\${name} age=\${age:04d}' |
| 102 | } |
| 103 | " |
| 104 | assert_roundtrip(src) |
| 105 | } |
| 106 | |
| 107 | fn test_roundtrip_match_and_array() { |
| 108 | src := 'module main |
| 109 | |
| 110 | fn classify(x int) string { |
| 111 | return match x { |
| 112 | 0 { "zero" } |
| 113 | 1, 2, 3 { "small" } |
| 114 | else { "big" } |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | fn sum_array() int { |
| 119 | nums := [1, 2, 3, 4, 5] |
| 120 | mut total := 0 |
| 121 | for n in nums { |
| 122 | total += n |
| 123 | } |
| 124 | return total |
| 125 | } |
| 126 | ' |
| 127 | assert_roundtrip(src) |
| 128 | } |
| 129 | |
| 130 | fn test_roundtrip_map_and_or() { |
| 131 | src := "module main |
| 132 | |
| 133 | fn lookup() int { |
| 134 | m := {'a': 1, 'b': 2} |
| 135 | v := m['a'] or { -1 } |
| 136 | return v |
| 137 | } |
| 138 | " |
| 139 | assert_roundtrip(src) |
| 140 | } |
| 141 | |
| 142 | fn test_roundtrip_enum_and_attrs() { |
| 143 | src := 'module main |
| 144 | |
| 145 | @[flag] |
| 146 | pub enum Color { |
| 147 | red |
| 148 | green |
| 149 | blue |
| 150 | } |
| 151 | ' |
| 152 | assert_roundtrip(src) |
| 153 | } |
| 154 | |
| 155 | fn test_roundtrip_interface() { |
| 156 | src := 'module main |
| 157 | |
| 158 | pub interface Animal { |
| 159 | name() string |
| 160 | mut: |
| 161 | age int |
| 162 | } |
| 163 | ' |
| 164 | assert_roundtrip(src) |
| 165 | } |
| 166 | |
| 167 | fn test_roundtrip_generic_fn() { |
| 168 | src := 'module main |
| 169 | |
| 170 | fn max[T](a T, b T) T { |
| 171 | if a > b { return a } |
| 172 | return b |
| 173 | } |
| 174 | ' |
| 175 | assert_roundtrip(src) |
| 176 | } |
| 177 | |
| 178 | fn test_roundtrip_real_source_file() { |
| 179 | // Round-trip a real V2 source file end-to-end. This exercises the full |
| 180 | // surface of parser-produced AST variants. |
| 181 | path := os.real_path('vlib/v2/ast/ast.v') |
| 182 | if !os.exists(path) { |
| 183 | return |
| 184 | } |
| 185 | src := os.read_file(path) or { return } |
| 186 | files := parse_source_for_flat(src) |
| 187 | flat_a := ast.flatten_files(files) |
| 188 | rt_files := flat_a.to_files() |
| 189 | flat_b := ast.flatten_files(rt_files) |
| 190 | assert flat_a.signature() == flat_b.signature(), 'round-trip mismatch for ${path}' |
| 191 | } |
| 192 | |