github.com/blynn/nex@v0.0.0-20210330102341-1a3320dab988/test/nex_test.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "testing" 12 ) 13 14 var nexBin string 15 16 func init() { 17 var err error 18 try := []string{ 19 filepath.Join(os.Getenv("GOPATH"), "bin", "nex"), 20 filepath.Join("..", "nex"), 21 "nex", // look in all of PATH 22 } 23 for _, path := range try { 24 if path, err = exec.LookPath(path); err != nil { 25 continue 26 } 27 if path, err = filepath.Abs(path); err != nil { 28 panic(fmt.Sprintf("cannot get absolute path to nex binary: %s", err)) 29 } 30 nexBin = path 31 return 32 } 33 panic("cannot find nex binary") 34 } 35 36 func dieErr(t *testing.T, err error, s string) { 37 if err != nil { 38 t.Fatalf("%s: %s", s, err) 39 } 40 } 41 42 // Test the reverse-Polish notation calculator rp.{nex,y}. 43 func TestNexPlusYacc(t *testing.T) { 44 tmpdir, err := ioutil.TempDir("", "nex") 45 dieErr(t, err, "TempDir") 46 defer func() { 47 dieErr(t, os.RemoveAll(tmpdir), "RemoveAll") 48 }() 49 run := func(s string) { 50 v := strings.Split(s, " ") 51 err := exec.Command(v[0], v[1:]...).Run() 52 dieErr(t, err, s) 53 } 54 dieErr(t, copyToDir(tmpdir, "rp.nex"), "copy rp.nex") 55 dieErr(t, copyToDir(tmpdir, "rp.y"), "copy rp.y") 56 wd, err := os.Getwd() 57 dieErr(t, err, "Getwd") 58 dieErr(t, os.Chdir(tmpdir), "Chdir") 59 defer func() { 60 dieErr(t, os.Chdir(wd), "Chdir") 61 }() 62 run(nexBin + " rp.nex") 63 run("go tool yacc rp.y") 64 run("go build y.go rp.nn.go") 65 cmd := exec.Command("./y") 66 cmd.Stdin = strings.NewReader( 67 `1 2 3 4 + * - 68 9 8 * 7 * 3 2 * 1 * / n 69 `) 70 want := "-13\n-84\n" 71 got, err := cmd.CombinedOutput() 72 dieErr(t, err, "CombinedOutput") 73 if want != string(got) { 74 t.Fatalf("want %q, got %q", want, string(got)) 75 } 76 } 77 78 func TestNexPrograms(t *testing.T) { 79 tmpdir, err := ioutil.TempDir("", "nex") 80 dieErr(t, err, "TempDir") 81 defer func() { 82 dieErr(t, os.RemoveAll(tmpdir), "RemoveAll") 83 }() 84 85 for _, x := range []struct { 86 prog, in, out string 87 }{ 88 {"lc.nex", "no newline", "0 10\n"}, 89 {"lc.nex", "one two three\nfour five six\n", "2 28\n"}, 90 91 {"toy.nex", "if\t\n6 * 9 == 42 then {one-line comment } else 1.23. end", 92 `A keyword: if 93 An integer: 6 94 An operator: * 95 An integer: 9 96 Unrecognized character: = 97 Unrecognized character: = 98 An integer: 42 99 A keyword: then 100 An identifier: else 101 A float: 1.23 102 Unrecognized character: . 103 A keyword: end 104 `}, 105 106 {"wc.nex", "no newline", "0 0 0\n"}, 107 {"wc.nex", "\n", "1 0 1\n"}, 108 {"wc.nex", "1\na b\nA B C\n", "3 6 12\n"}, 109 {"wc.nex", "one two three\nfour five six\n", "2 6 28\n"}, 110 111 {"rob.nex", 112 `1 robot 113 2 robo 114 3 rob 115 4 rob robot 116 5 robot rob 117 6 roboot 118 `, "2 robo\n3 rob\n6 roboot\n"}, 119 120 {"peter.nex", 121 ` ####### 122 ######### 123 #### ##### 124 #### #### # 125 #### ##### 126 #### ### 127 ######## ##### 128 #### ######### 129 #### # # #### 130 ## # ### ## 131 ### # ### 132 ### ## 133 ## # 134 # #### 135 # # 136 ## # ## 137 `, 138 `rect 5 6 1 2 139 rect 6 7 1 2 140 rect 7 8 1 2 141 rect 8 9 1 2 142 rect 9 10 1 2 143 rect 10 11 1 2 144 rect 11 12 1 2 145 rect 4 5 2 3 146 rect 5 6 2 3 147 rect 6 7 2 3 148 rect 7 8 2 3 149 rect 8 9 2 3 150 rect 9 10 2 3 151 rect 10 11 2 3 152 rect 11 12 2 3 153 rect 12 13 2 3 154 rect 3 4 3 4 155 rect 4 5 3 4 156 rect 5 6 3 4 157 rect 6 7 3 4 158 rect 9 10 3 4 159 rect 10 11 3 4 160 rect 11 12 3 4 161 rect 12 13 3 4 162 rect 13 14 3 4 163 rect 2 3 4 5 164 rect 3 4 4 5 165 rect 4 5 4 5 166 rect 5 6 4 5 167 rect 10 11 4 5 168 rect 11 12 4 5 169 rect 12 13 4 5 170 rect 13 14 4 5 171 rect 17 18 4 5 172 rect 2 3 5 6 173 rect 3 4 5 6 174 rect 4 5 5 6 175 rect 5 6 5 6 176 rect 12 13 5 6 177 rect 13 14 5 6 178 rect 14 15 5 6 179 rect 15 16 5 6 180 rect 16 17 5 6 181 rect 1 2 6 7 182 rect 2 3 6 7 183 rect 3 4 6 7 184 rect 4 5 6 7 185 rect 13 14 6 7 186 rect 14 15 6 7 187 rect 15 16 6 7 188 rect 1 2 7 8 189 rect 2 3 7 8 190 rect 3 4 7 8 191 rect 4 5 7 8 192 rect 5 6 7 8 193 rect 6 7 7 8 194 rect 7 8 7 8 195 rect 8 9 7 8 196 rect 12 13 7 8 197 rect 13 14 7 8 198 rect 14 15 7 8 199 rect 15 16 7 8 200 rect 16 17 7 8 201 rect 1 2 8 9 202 rect 2 3 8 9 203 rect 3 4 8 9 204 rect 4 5 8 9 205 rect 6 7 8 9 206 rect 7 8 8 9 207 rect 8 9 8 9 208 rect 9 10 8 9 209 rect 10 11 8 9 210 rect 11 12 8 9 211 rect 12 13 8 9 212 rect 13 14 8 9 213 rect 14 15 8 9 214 rect 1 2 9 10 215 rect 2 3 9 10 216 rect 3 4 9 10 217 rect 4 5 9 10 218 rect 6 7 9 10 219 rect 9 10 9 10 220 rect 11 12 9 10 221 rect 12 13 9 10 222 rect 13 14 9 10 223 rect 14 15 9 10 224 rect 1 2 10 11 225 rect 2 3 10 11 226 rect 4 5 10 11 227 rect 7 8 10 11 228 rect 8 9 10 11 229 rect 9 10 10 11 230 rect 13 14 10 11 231 rect 14 15 10 11 232 rect 1 2 11 12 233 rect 2 3 11 12 234 rect 3 4 11 12 235 rect 8 9 11 12 236 rect 11 12 11 12 237 rect 12 13 11 12 238 rect 13 14 11 12 239 rect 1 2 12 13 240 rect 2 3 12 13 241 rect 3 4 12 13 242 rect 8 9 12 13 243 rect 9 10 12 13 244 rect 2 3 13 14 245 rect 3 4 13 14 246 rect 7 8 13 14 247 rect 3 4 14 15 248 rect 7 8 14 15 249 rect 8 9 14 15 250 rect 9 10 14 15 251 rect 10 11 14 15 252 rect 3 4 15 16 253 rect 5 6 15 16 254 rect 1 2 16 17 255 rect 2 3 16 17 256 rect 6 7 16 17 257 rect 10 11 16 17 258 rect 11 12 16 17 259 `}, 260 {"peter2.nex", "###\n#\n####\n", "rect 1 4 1 2\nrect 1 2 2 3\nrect 1 5 3 4\n"}, 261 {"u.nex", "١ + ٢ + ... + ١٨ = 一百五十三", "1 + 2 + ... + 18 = 153"}, 262 {"bug50.nex", "# comment 1\nhello42:\n# comment 2\n\na\nblah:42x\n", "COMMENT: # comment 1\nTEXT: hello42\nERROR: :\nCOMMENT: # comment 2\nTEXT: a\nTEXT: blah:42x\n"}, 263 } { 264 cmd := exec.Command(nexBin, "-r", "-s", x.prog) 265 cmd.Stdin = strings.NewReader(x.in) 266 got, err := cmd.CombinedOutput() 267 dieErr(t, err, x.prog+" "+string(got)) 268 if string(got) != x.out { 269 t.Fatalf("program: %s\nwant %q, got %q", x.prog, x.out, string(got)) 270 } 271 } 272 } 273 274 // To save time, we combine several test cases into a single nex program. 275 func TestGiantProgram(t *testing.T) { 276 tmpdir, err := ioutil.TempDir("", "nex") 277 dieErr(t, err, "TempDir") 278 wd, err := os.Getwd() 279 dieErr(t, err, "Getwd") 280 dieErr(t, os.Chdir(tmpdir), "Chdir") 281 defer func() { 282 dieErr(t, os.RemoveAll(tmpdir), "RemoveAll") 283 }() 284 defer func() { 285 dieErr(t, os.Chdir(wd), "Chdir") 286 }() 287 s := "package main\n" 288 body := "" 289 for i, x := range []struct { 290 prog, in, out string 291 }{ 292 // Test parentheses and $. 293 {` 294 /[a-z]*/ < { *lval += "[" } 295 /a(($*|$$)($($)$$$))$($$$)*/ { *lval += "0" } 296 /(e$|f$)/ { *lval += "1" } 297 /(qux)*/ { *lval += "2" } 298 /$/ { *lval += "." } 299 > { *lval += "]" } 300 `, "a b c d e f g aaab aaaa eeeg fffe quxqux quxq quxe", 301 "[0][.][.][.][1][1][.][.][0][.][1][2][2][21]"}, 302 // Exercise ^ and rule precedence. 303 {` 304 /[a-z]*/ < { *lval += "[" } 305 /((^*|^^)(^(^)^^^))^(^^^)*bar/ { *lval += "0" } 306 /(^foo)*/ { *lval += "1" } 307 /^fooo$/ { *lval += "2" } 308 /^f(oo)*/ { *lval += "3" } 309 /^foo*/ { *lval += "4" } 310 /^/ { *lval += "." } 311 > { *lval += "]" } 312 `, "foo bar foooo fooo fooooo fooof baz foofoo", 313 "[1][0][3][2][4][4][.][1]"}, 314 // Anchored empty matches. 315 {` 316 /^/ { *lval += "BEGIN" } 317 /$/ { *lval += "END" } 318 `, "", "BEGIN"}, 319 320 {` 321 /$/ { *lval += "END" } 322 /^/ { *lval += "BEGIN" } 323 `, "", "END"}, 324 325 {` 326 /^$/ { *lval += "BOTH" } 327 /^/ { *lval += "BEGIN" } 328 /$/ { *lval += "END" } 329 `, "", "BOTH"}, 330 // Built-in Line and Column counters. 331 // Ugly hack to import fmt. 332 {`"fmt" 333 /\*/ { *lval += yySymType(fmt.Sprintf("[%d,%d]", yylex.Line(), yylex.Column())) } 334 `, 335 `..*. 336 ** 337 ... 338 ...*.* 339 * 340 `, "[0,2][1,0][1,1][3,3][3,5][4,0]"}, 341 // Patterns like awk's BEGIN and END. 342 {` 343 < { *lval += "[" } 344 /[0-9]*/ { *lval += "N" } 345 /;/ { *lval += ";" } 346 /./ { *lval += "." } 347 > { *lval += "]\n" } 348 `, "abc 123 xyz;a1b2c3;42", "[....N....;.N.N.N;N]\n"}, 349 // A partial match regex has no effect on an immediately following match. 350 {` 351 /abcd/ { *lval += "ABCD" } 352 /\n/ { *lval += "\n" } 353 `, "abcd\nbabcd\naabcd\nabcabcd\n", "ABCD\nABCD\nABCD\nABCD\n"}, 354 355 // Nested regex test. The simplistic parser means we must use commented 356 // braces to balance out quoted braces. 357 // Sprinkle in a couple of return statements to check Lex() saves stack 358 // state correctly between calls. 359 {` 360 /a[bcd]*e/ < { *lval += "[" } 361 /a/ { *lval += "A" } 362 /bcd/ < { *lval += "(" } 363 /c/ { *lval += "X"; return 1 } 364 > { *lval += ")" } 365 /e/ { *lval += "E" } 366 /ccc/ < { 367 *lval += "{" 368 // } [balance out the quoted left brace] 369 } 370 /./ { *lval += "?" } 371 > { 372 // { [balance out the quoted right brace] 373 *lval += "}" 374 return 2 375 } 376 > { *lval += "]" } 377 /\n/ { *lval += "\n" } 378 /./ { *lval += "." } 379 `, "abcdeabcabcdabcdddcccbbbcde", "[A(X)E].......[A(X){???}(X)E]"}, 380 381 // Exercise hyphens in character classes. 382 {` 383 /[a-z-]*/ < { *lval += "[" } 384 /[^-a-df-m]/ { *lval += "0" } 385 /./ { *lval += "1" } 386 > { *lval += "]" } 387 /\n/ { *lval += "\n" } 388 /./ { *lval += "." } 389 `, "-azb-ycx@d--w-e-", "[11011010].[1110101]"}, 390 391 // Overlapping character classes. 392 {` 393 /[a-e]+[d-h]+/ { *lval += "0" } 394 /[m-n]+[k-p]+[^k-r]+[o-p]+/ { *lval += "1" } 395 /./ { *(*string)(lval) += yylex.Text() } 396 `, "abcdefghijmnopabcoq", "0ij1q"}, 397 } { 398 id := fmt.Sprintf("%v", i) 399 s += `import "./nex_test` + id + "\"\n" 400 dieErr(t, os.Mkdir("nex_test"+id, 0777), "Mkdir") 401 // Ugly hack to import packages. 402 prog := x.prog 403 importLine := "" 404 if prog[0] != '\n' { 405 v := strings.SplitN(prog, "\n", 2) 406 prog = v[1] 407 importLine = "import " + v[0] 408 } 409 dieErr(t, ioutil.WriteFile(id+".nex", []byte(prog+`// 410 package nex_test`+id+` 411 412 `+importLine+` 413 414 type yySymType string 415 416 func Go() { 417 x := NewLexer(bufio.NewReader(strings.NewReader(`+"`"+x.in+"`"+`))) 418 lval := new(yySymType) 419 for x.Lex(lval) != 0 { } 420 s := string(*lval) 421 if s != `+"`"+x.out+"`"+`{ 422 panic(`+"`"+x.prog+": want "+x.out+", got ` + s"+`) 423 } 424 } 425 `), 0777), "WriteFile") 426 _, cerr := exec.Command(nexBin, "-o", filepath.Join("nex_test"+id, "tmp.go"), id+".nex").CombinedOutput() 427 dieErr(t, cerr, "nex: "+s) 428 body += "nex_test" + id + ".Go()\n" 429 } 430 s += "func main() {\n" + body + "}\n" 431 err = ioutil.WriteFile("tmp.go", []byte(s), 0777) 432 dieErr(t, err, "WriteFile") 433 output, err := exec.Command("go", "run", "tmp.go").CombinedOutput() 434 dieErr(t, err, string(output)) 435 } 436 437 func copy(dst, src string) error { 438 s, err := os.Open(src) 439 if err != nil { 440 return err 441 } 442 defer s.Close() 443 d, err := os.Create(dst) 444 if err != nil { 445 return err 446 } 447 if _, err := io.Copy(d, s); err != nil { 448 d.Close() 449 return err 450 } 451 return d.Close() 452 } 453 454 func copyToDir(dst, src string) error { 455 return copy(filepath.Join(dst, filepath.Base(src)), src) 456 }