github.com/NeowayLabs/nash@v0.2.2-0.20200127205349-a227041ffd50/parser/parse_test.go (about) 1 package parser 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/madlambda/nash/ast" 8 "github.com/madlambda/nash/token" 9 ) 10 11 func parserTest(name, content string, expected *ast.Tree, t *testing.T, enableReverse bool) *ast.Tree { 12 parser := NewParser(name, content) 13 tr, err := parser.Parse() 14 15 if err != nil { 16 t.Error(err) 17 t.Logf("Failed syntax: '%s'", content) 18 return nil 19 } 20 21 if tr == nil { 22 t.Errorf("Failed to parse") 23 return nil 24 } 25 26 if !expected.IsEqual(tr) { 27 t.Errorf("Expected: %s\n\nResult: %s\n", expected, tr) 28 t.Logf("Failed syntax: '%s'", content) 29 return tr 30 } 31 32 if !enableReverse { 33 return tr 34 } 35 36 // Test if the reverse of tree is the content again... *hard* 37 trcontent := strings.TrimSpace(tr.String()) 38 content = strings.TrimSpace(content) 39 40 if content != trcontent { 41 t.Errorf(`Failed to reverse the tree. 42 Expected: 43 '%s' 44 45 But got: 46 '%s' 47 `, content, trcontent) 48 } 49 50 return tr 51 } 52 53 func parserTestFail(t *testing.T, execStr string) { 54 parser := NewParser("", execStr) 55 56 tr, err := parser.Parse() 57 58 if err == nil { 59 t.Errorf("Parsing '%s' must fail", execStr) 60 return 61 } 62 63 if tr != nil { 64 t.Error("tr must be nil") 65 return 66 } 67 } 68 69 func TestParseSimple(t *testing.T) { 70 expected := ast.NewTree("parser simple") 71 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 72 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "echo", false) 73 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 6), "hello world", true)) 74 ln.Push(cmd) 75 76 expected.Root = ln 77 78 parserTest("parser simple", `echo "hello world"`, expected, t, true) 79 80 cmd1 := ast.NewCommandNode(token.NewFileInfo(1, 0), "cat", false) 81 arg1 := ast.NewStringExpr(token.NewFileInfo(1, 4), "/etc/resolv.conf", false) 82 arg2 := ast.NewStringExpr(token.NewFileInfo(1, 21), "/etc/hosts", false) 83 cmd1.AddArg(arg1) 84 cmd1.AddArg(arg2) 85 86 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 87 ln.Push(cmd1) 88 expected.Root = ln 89 90 parserTest("parser simple", `cat /etc/resolv.conf /etc/hosts`, expected, t, true) 91 } 92 93 func TestParseReverseGetSame(t *testing.T) { 94 parser := NewParser("reverse simple", "echo \"hello world\"") 95 96 tr, err := parser.Parse() 97 98 if err != nil { 99 t.Error(err) 100 return 101 } 102 103 if tr.String() != "echo \"hello world\"" { 104 t.Errorf("Failed to reverse tree: %s", tr.String()) 105 return 106 } 107 } 108 109 func TestParsePipe(t *testing.T) { 110 expected := ast.NewTree("parser pipe") 111 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 112 first := ast.NewCommandNode(token.NewFileInfo(1, 0), "echo", false) 113 first.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 6), "hello world", true)) 114 115 second := ast.NewCommandNode(token.NewFileInfo(1, 21), "awk", false) 116 second.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 26), "{print $1}", true)) 117 118 pipe := ast.NewPipeNode(token.NewFileInfo(1, 19), false) 119 pipe.AddCmd(first) 120 pipe.AddCmd(second) 121 122 ln.Push(pipe) 123 124 expected.Root = ln 125 126 parserTest("parser pipe", `echo "hello world" | awk "{print $1}"`, expected, t, true) 127 } 128 129 func TestBasicSetEnvAssignment(t *testing.T) { 130 expected := ast.NewTree("simple set assignment") 131 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 132 set, err := ast.NewSetenvNode(token.NewFileInfo(1, 0), "test", nil) 133 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 ln.Push(set) 139 expected.Root = ln 140 141 parserTest("simple set assignment", `setenv test`, expected, t, true) 142 143 // setenv with assignment 144 expected = ast.NewTree("setenv with simple assignment") 145 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 146 assign := ast.NewSingleAssignNode(token.NewFileInfo(1, 7), 147 ast.NewNameNode(token.NewFileInfo(1, 7), "test", nil), 148 ast.NewStringExpr(token.NewFileInfo(1, 15), "hello", true)) 149 set, err = ast.NewSetenvNode(token.NewFileInfo(1, 0), "test", assign) 150 151 if err != nil { 152 t.Fatal(err) 153 } 154 155 ln.Push(set) 156 expected.Root = ln 157 158 parserTest("setenv with simple assignment", `setenv test = "hello"`, expected, t, true) 159 160 expected = ast.NewTree("setenv with simple cmd assignment") 161 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 162 163 cmd := ast.NewCommandNode(token.NewFileInfo(1, 15), "ls", false) 164 165 cmdAssign, err := ast.NewExecAssignNode( 166 token.NewFileInfo(1, 7), 167 []*ast.NameNode{ast.NewNameNode(token.NewFileInfo(1, 7), "test", nil)}, 168 cmd, 169 ) 170 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 set, err = ast.NewSetenvNode(token.NewFileInfo(1, 0), "test", cmdAssign) 176 177 if err != nil { 178 t.Fatal(err) 179 } 180 181 ln.Push(set) 182 expected.Root = ln 183 184 parserTest("simple assignment", `setenv test <= ls`, expected, t, true) 185 } 186 187 func TestBasicAssignment(t *testing.T) { 188 expected := ast.NewTree("simple assignment") 189 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 190 assign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 191 ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil), 192 ast.NewStringExpr(token.NewFileInfo(1, 8), "hello", true)) 193 ln.Push(assign) 194 expected.Root = ln 195 196 parserTest("simple assignment", `test = "hello"`, expected, t, true) 197 198 // test concatenation of strings and variables 199 200 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 201 202 concats := make([]ast.Expr, 2, 2) 203 concats[0] = ast.NewStringExpr(token.NewFileInfo(1, 8), "hello", true) 204 concats[1] = ast.NewVarExpr(token.NewFileInfo(1, 15), "$var") 205 206 arg1 := ast.NewConcatExpr(token.NewFileInfo(1, 8), concats) 207 208 assign = ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 209 ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil), 210 arg1, 211 ) 212 213 ln.Push(assign) 214 expected.Root = ln 215 216 parserTest("test", `test = "hello"+$var`, expected, t, true) 217 218 for _, test := range []string{ 219 "test=hello", 220 "test = hello", 221 "test = 1", 222 "test = false", 223 "test = -1", 224 `test = "1", "2"`, 225 } { 226 parserTestFail(t, test) 227 } 228 } 229 230 func TestVarAssignment(t *testing.T) { 231 expected := ast.NewTree("var assignment") 232 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 233 varAssign := ast.NewVarAssignDecl(token.NewFileInfo(1, 0), 234 ast.NewSingleAssignNode(token.NewFileInfo(1, 4), 235 ast.NewNameNode(token.NewFileInfo(1, 4), "test", nil), 236 ast.NewStringExpr(token.NewFileInfo(1, 12), "hello", true)), 237 ) 238 ln.Push(varAssign) 239 expected.Root = ln 240 241 parserTest("var assignment", `var test = "hello"`, expected, t, true) 242 243 for _, test := range []string{ 244 "var test=hello", 245 "var", 246 "var test", 247 "var test = false", 248 "var test = -1", 249 `var test = "1", "2"`, 250 } { 251 parserTestFail(t, test) 252 } 253 } 254 255 func TestParseMultipleAssign(t *testing.T) { 256 one := ast.NewNameNode(token.NewFileInfo(1, 0), "one", nil) 257 two := ast.NewNameNode(token.NewFileInfo(1, 5), "two", nil) 258 value1 := ast.NewStringExpr(token.NewFileInfo(1, 12), "1", true) 259 value2 := ast.NewStringExpr(token.NewFileInfo(1, 17), "2", true) 260 assign := ast.NewAssignNode(token.NewFileInfo(1, 0), 261 []*ast.NameNode{one, two}, 262 []ast.Expr{value1, value2}, 263 ) 264 265 expected := ast.NewTree("tuple assignment") 266 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 267 ln.Push(assign) 268 expected.Root = ln 269 270 parserTest("tuple assignment", `one, two = "1", "2"`, expected, t, true) 271 272 lvalue1 := ast.NewListExpr(token.NewFileInfo(1, 11), []ast.Expr{}) 273 value2 = ast.NewStringExpr(token.NewFileInfo(1, 16), "2", true) 274 assign = ast.NewAssignNode(token.NewFileInfo(1, 0), 275 []*ast.NameNode{one, two}, 276 []ast.Expr{lvalue1, value2}, 277 ) 278 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 279 ln.Push(assign) 280 expected.Root = ln 281 parserTest("tuple assignment", `one, two = (), "2"`, expected, t, true) 282 283 } 284 285 func TestParseMultipleExecAssignment(t *testing.T) { 286 expected := ast.NewTree("multiple cmd assignment") 287 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 288 289 cmd := ast.NewCommandNode(token.NewFileInfo(1, 16), "ls", false) 290 291 assign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 0), 292 []*ast.NameNode{ 293 ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil), 294 ast.NewNameNode(token.NewFileInfo(1, 6), "status", nil), 295 }, 296 cmd, 297 ) 298 299 if err != nil { 300 t.Error(err) 301 return 302 } 303 304 ln.Push(assign) 305 expected.Root = ln 306 307 parserTest("multiple cmd assignment", `test, status <= ls`, expected, t, true) 308 309 expected = ast.NewTree("multiple cmd assignment") 310 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 311 312 cmd = ast.NewCommandNode(token.NewFileInfo(1, 19), "ls", false) 313 314 assign, err = ast.NewExecAssignNode(token.NewFileInfo(1, 0), 315 []*ast.NameNode{ 316 ast.NewNameNode(token.NewFileInfo(1, 0), "test", ast.NewIntExpr(token.NewFileInfo(1, 5), 0)), 317 ast.NewNameNode(token.NewFileInfo(1, 9), "status", nil), 318 }, 319 cmd, 320 ) 321 322 if err != nil { 323 t.Error(err) 324 return 325 } 326 327 ln.Push(assign) 328 expected.Root = ln 329 330 parserTest("multiple cmd assignment", `test[0], status <= ls`, expected, t, true) 331 } 332 333 func TestParseInvalidIndexing(t *testing.T) { 334 // test indexed assignment 335 parser := NewParser("invalid", `test[a] = "a"`) 336 337 _, err := parser.Parse() 338 339 if err == nil { 340 t.Error("Parse must fail") 341 return 342 } else if err.Error() != "invalid:1:5: Expected number or variable in index. Found ARG" { 343 t.Error("Invalid err msg") 344 return 345 } 346 347 parser = NewParser("invalid", `test[] = "a"`) 348 349 _, err = parser.Parse() 350 351 if err == nil { 352 t.Error("Parse must fail") 353 return 354 } else if err.Error() != "invalid:1:5: Expected number or variable in index. Found ]" { 355 t.Error("Invalid err msg") 356 return 357 } 358 359 parser = NewParser("invalid", `test[10.0] = "a"`) 360 361 _, err = parser.Parse() 362 363 if err == nil { 364 t.Error("Parse must fail") 365 return 366 } else if err.Error() != "invalid:1:5: Expected number or variable in index. Found ARG" { 367 t.Error("Invalid err msg") 368 return 369 } 370 } 371 372 func TestParseListAssignment(t *testing.T) { 373 expected := ast.NewTree("list assignment") 374 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 375 376 values := make([]ast.Expr, 0, 4) 377 378 values = append(values, 379 ast.NewStringExpr(token.NewFileInfo(2, 1), "plan9", false), 380 ast.NewStringExpr(token.NewFileInfo(3, 1), "from", false), 381 ast.NewStringExpr(token.NewFileInfo(4, 1), "bell", false), 382 ast.NewStringExpr(token.NewFileInfo(5, 1), "labs", false), 383 ) 384 385 elem := ast.NewListExpr(token.NewFileInfo(1, 7), values) 386 387 assign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 388 ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil), 389 elem, 390 ) 391 392 ln.Push(assign) 393 expected.Root = ln 394 395 parserTest("list assignment", `test = ( 396 plan9 397 from 398 bell 399 labs 400 )`, expected, t, false) 401 } 402 403 func TestParseListOfListsAssignment(t *testing.T) { 404 expected := ast.NewTree("list assignment") 405 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 406 407 plan9 := make([]ast.Expr, 0, 4) 408 plan9 = append(plan9, 409 ast.NewStringExpr(token.NewFileInfo(2, 2), "plan9", false), 410 ast.NewStringExpr(token.NewFileInfo(2, 8), "from", false), 411 ast.NewStringExpr(token.NewFileInfo(2, 13), "bell", false), 412 ast.NewStringExpr(token.NewFileInfo(2, 18), "labs", false), 413 ) 414 415 elem1 := ast.NewListExpr(token.NewFileInfo(2, 1), plan9) 416 417 linux := make([]ast.Expr, 0, 2) 418 linux = append(linux, ast.NewStringExpr(token.NewFileInfo(3, 2), "linux", false)) 419 linux = append(linux, ast.NewStringExpr(token.NewFileInfo(3, 8), "kernel", false)) 420 421 elem2 := ast.NewListExpr(token.NewFileInfo(3, 1), linux) 422 423 values := make([]ast.Expr, 2) 424 values[0] = elem1 425 values[1] = elem2 426 427 elem := ast.NewListExpr(token.NewFileInfo(1, 7), values) 428 429 assign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 430 ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil), 431 elem, 432 ) 433 434 ln.Push(assign) 435 expected.Root = ln 436 437 parserTest("list assignment", `test = ( 438 (plan9 from bell labs) 439 (linux kernel) 440 )`, expected, t, false) 441 } 442 443 func TestParseCmdAssignment(t *testing.T) { 444 expected := ast.NewTree("simple cmd assignment") 445 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 446 447 cmd := ast.NewCommandNode(token.NewFileInfo(1, 8), "ls", false) 448 449 assign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 0), 450 []*ast.NameNode{ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil)}, 451 cmd, 452 ) 453 454 if err != nil { 455 t.Error(err) 456 return 457 } 458 459 ln.Push(assign) 460 expected.Root = ln 461 462 parserTest("simple assignment", `test <= ls`, expected, t, true) 463 } 464 465 func TestParseInvalidEmpty(t *testing.T) { 466 parser := NewParser("invalid", ";") 467 468 _, err := parser.Parse() 469 470 if err == nil { 471 t.Error("Parse must fail") 472 return 473 } 474 } 475 476 func TestParsePathCommand(t *testing.T) { 477 expected := ast.NewTree("parser simple") 478 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 479 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "/bin/echo", false) 480 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 11), "hello world", true)) 481 ln.Push(cmd) 482 483 expected.Root = ln 484 485 parserTest("parser simple", `/bin/echo "hello world"`, expected, t, true) 486 } 487 488 func TestParseWithShebang(t *testing.T) { 489 expected := ast.NewTree("parser shebang") 490 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 491 cmt := ast.NewCommentNode(token.NewFileInfo(1, 0), "#!/bin/nash") 492 cmd := ast.NewCommandNode(token.NewFileInfo(3, 0), "echo", false) 493 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 5), "bleh", false)) 494 ln.Push(cmt) 495 ln.Push(cmd) 496 497 expected.Root = ln 498 499 parserTest("parser shebang", `#!/bin/nash 500 501 echo bleh 502 `, expected, t, true) 503 } 504 505 func TestParseEmptyFile(t *testing.T) { 506 expected := ast.NewTree("empty file") 507 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 508 expected.Root = ln 509 510 parserTest("empty file", "", expected, t, true) 511 } 512 513 func TestParseSingleCommand(t *testing.T) { 514 expected := ast.NewTree("single command") 515 expected.Root = ast.NewBlockNode(token.NewFileInfo(1, 0)) 516 expected.Root.Push(ast.NewCommandNode(token.NewFileInfo(1, 0), "bleh", false)) 517 518 parserTest("single command", `bleh`, expected, t, true) 519 } 520 521 func TestParseRedirectSimple(t *testing.T) { 522 expected := ast.NewTree("redirect") 523 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 524 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "cmd", false) 525 redir := ast.NewRedirectNode(token.NewFileInfo(1, 4)) 526 redir.SetMap(2, ast.RedirMapSupress) 527 cmd.AddRedirect(redir) 528 ln.Push(cmd) 529 530 expected.Root = ln 531 532 parserTest("simple redirect", `cmd >[2=]`, expected, t, true) 533 534 expected = ast.NewTree("redirect2") 535 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 536 cmd = ast.NewCommandNode(token.NewFileInfo(1, 0), "cmd", false) 537 redir = ast.NewRedirectNode(token.NewFileInfo(1, 4)) 538 redir.SetMap(2, 1) 539 cmd.AddRedirect(redir) 540 ln.Push(cmd) 541 542 expected.Root = ln 543 544 parserTest("simple redirect", `cmd >[2=1]`, expected, t, true) 545 } 546 547 func TestParseRedirectWithLocation(t *testing.T) { 548 expected := ast.NewTree("redirect with location") 549 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 550 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "cmd", false) 551 redir := ast.NewRedirectNode(token.NewFileInfo(1, 4)) 552 redir.SetMap(2, ast.RedirMapNoValue) 553 redir.SetLocation(ast.NewStringExpr(token.NewFileInfo(1, 9), "/var/log/service.log", false)) 554 cmd.AddRedirect(redir) 555 ln.Push(cmd) 556 557 expected.Root = ln 558 559 parserTest("simple redirect", `cmd >[2] /var/log/service.log`, expected, t, true) 560 } 561 562 func TestParseRedirectMultiples(t *testing.T) { 563 expected := ast.NewTree("redirect multiples") 564 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 565 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "cmd", false) 566 redir1 := ast.NewRedirectNode(token.NewFileInfo(1, 4)) 567 redir2 := ast.NewRedirectNode(token.NewFileInfo(1, 11)) 568 569 redir1.SetMap(1, 2) 570 redir2.SetMap(2, ast.RedirMapSupress) 571 572 cmd.AddRedirect(redir1) 573 cmd.AddRedirect(redir2) 574 ln.Push(cmd) 575 576 expected.Root = ln 577 578 parserTest("multiple redirects", `cmd >[1=2] >[2=]`, expected, t, true) 579 } 580 581 func TestParseCommandWithStringsEqualsNot(t *testing.T) { 582 expected := ast.NewTree("strings works as expected") 583 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 584 cmd1 := ast.NewCommandNode(token.NewFileInfo(1, 0), "echo", false) 585 cmd2 := ast.NewCommandNode(token.NewFileInfo(2, 0), "echo", false) 586 cmd1.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 5), "hello", false)) 587 cmd2.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 6), "hello", true)) 588 589 ln.Push(cmd1) 590 ln.Push(cmd2) 591 expected.Root = ln 592 593 parserTest("strings works as expected", `echo hello 594 echo "hello" 595 `, expected, t, true) 596 } 597 598 func TestParseCommandSeparatedBySemicolon(t *testing.T) { 599 expected := ast.NewTree("semicolon") 600 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 601 cmd1 := ast.NewCommandNode(token.NewFileInfo(1, 0), "echo", false) 602 cmd2 := ast.NewCommandNode(token.NewFileInfo(1, 11), "echo", false) 603 cmd1.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 5), "hello", false)) 604 cmd2.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), "world", false)) 605 606 ln.Push(cmd1) 607 ln.Push(cmd2) 608 expected.Root = ln 609 610 parserTest("strings works as expected", `echo hello;echo world`, expected, t, false) 611 } 612 613 func TestParseStringNotFinished(t *testing.T) { 614 parser := NewParser("string not finished", `echo "hello world`) 615 tr, err := parser.Parse() 616 617 if err == nil { 618 t.Error("Error: should fail") 619 return 620 } 621 622 if tr != nil { 623 t.Errorf("Failed to parse") 624 return 625 } 626 } 627 628 func TestParseCd(t *testing.T) { 629 expected := ast.NewTree("test cd") 630 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 631 cd := ast.NewCommandNode(token.NewFileInfo(1, 0), "cd", false) 632 arg := ast.NewStringExpr(token.NewFileInfo(1, 3), "/tmp", false) 633 cd.AddArg(arg) 634 ln.Push(cd) 635 expected.Root = ln 636 637 parserTest("test cd", "cd /tmp", expected, t, true) 638 639 // test cd into home 640 expected = ast.NewTree("test cd into home") 641 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 642 cd = ast.NewCommandNode(token.NewFileInfo(1, 0), "cd", false) 643 ln.Push(cd) 644 expected.Root = ln 645 646 parserTest("test cd into home", "cd", expected, t, true) 647 648 // test cd .. 649 expected = ast.NewTree("test cd ..") 650 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 651 cd = ast.NewCommandNode(token.NewFileInfo(1, 0), "cd", false) 652 cd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 3), "..", false)) 653 ln.Push(cd) 654 expected.Root = ln 655 656 parserTest("test cd ..", "cd ..", expected, t, true) 657 658 expected = ast.NewTree("cd into HOME by setenv") 659 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 660 661 assign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 662 ast.NewNameNode(token.NewFileInfo(1, 0), "HOME", nil), 663 ast.NewStringExpr(token.NewFileInfo(1, 8), "/", true), 664 ) 665 666 set, err := ast.NewSetenvNode(token.NewFileInfo(3, 0), "HOME", nil) 667 668 if err != nil { 669 t.Fatal(err) 670 } 671 672 cd = ast.NewCommandNode(token.NewFileInfo(5, 0), "cd", false) 673 pwd := ast.NewCommandNode(token.NewFileInfo(6, 0), "pwd", false) 674 675 ln.Push(assign) 676 ln.Push(set) 677 ln.Push(cd) 678 ln.Push(pwd) 679 680 expected.Root = ln 681 682 parserTest("test cd into HOME by setenv", `HOME = "/" 683 684 setenv HOME 685 686 cd 687 pwd`, expected, t, true) 688 689 // Test cd into custom variable 690 expected = ast.NewTree("cd into variable value") 691 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 692 693 arg = ast.NewStringExpr(token.NewFileInfo(1, 10), "/home/i4k/gopath", true) 694 695 assign = ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 696 ast.NewNameNode(token.NewFileInfo(1, 0), "GOPATH", nil), 697 arg, 698 ) 699 700 cd = ast.NewCommandNode(token.NewFileInfo(3, 0), "cd", false) 701 arg2 := ast.NewVarExpr(token.NewFileInfo(3, 3), "$GOPATH") 702 cd.AddArg(arg2) 703 704 ln.Push(assign) 705 ln.Push(cd) 706 707 expected.Root = ln 708 709 parserTest("test cd into variable value", `GOPATH = "/home/i4k/gopath" 710 711 cd $GOPATH`, expected, t, true) 712 713 // Test cd into custom variable 714 expected = ast.NewTree("cd into variable value with concat") 715 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 716 717 arg = ast.NewStringExpr(token.NewFileInfo(1, 10), "/home/i4k/gopath", true) 718 719 assign = ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 720 ast.NewNameNode(token.NewFileInfo(1, 0), "GOPATH", nil), 721 arg, 722 ) 723 724 concat := make([]ast.Expr, 0, 2) 725 concat = append(concat, ast.NewVarExpr(token.NewFileInfo(3, 3), "$GOPATH")) 726 concat = append(concat, ast.NewStringExpr(token.NewFileInfo(3, 12), "/src/github.com", true)) 727 728 cd = ast.NewCommandNode(token.NewFileInfo(3, 0), "cd", false) 729 carg := ast.NewConcatExpr(token.NewFileInfo(3, 3), concat) 730 cd.AddArg(carg) 731 732 ln.Push(assign) 733 ln.Push(cd) 734 735 expected.Root = ln 736 737 parserTest("test cd into variable value", `GOPATH = "/home/i4k/gopath" 738 739 cd $GOPATH+"/src/github.com"`, expected, t, true) 740 741 } 742 743 func TestParseConcatOfIndexedVar(t *testing.T) { 744 expected := ast.NewTree("concat indexed var") 745 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 746 arg1 := ast.NewStringExpr(token.NewFileInfo(1, 4), "ec2", false) 747 arg2 := ast.NewStringExpr(token.NewFileInfo(1, 8), "create-tags", false) 748 arg3 := ast.NewStringExpr(token.NewFileInfo(1, 20), "--resources", false) 749 arg4 := ast.NewVarExpr(token.NewFileInfo(1, 32), "$resource") 750 arg5 := ast.NewStringExpr(token.NewFileInfo(1, 42), "--tags", false) 751 752 c1 := ast.NewStringExpr(token.NewFileInfo(1, 50), "Key=", true) 753 c2 := ast.NewIndexExpr(token.NewFileInfo(1, 56), 754 ast.NewVarExpr(token.NewFileInfo(1, 56), "$tag"), 755 ast.NewIntExpr(token.NewFileInfo(1, 61), 0)) 756 c3 := ast.NewStringExpr(token.NewFileInfo(1, 65), ",Value=", true) 757 c4 := ast.NewIndexExpr(token.NewFileInfo(1, 74), 758 ast.NewVarExpr(token.NewFileInfo(1, 74), "$tag"), 759 ast.NewIntExpr(token.NewFileInfo(1, 79), 1)) 760 cvalues := make([]ast.Expr, 4) 761 cvalues[0] = c1 762 cvalues[1] = c2 763 cvalues[2] = c3 764 cvalues[3] = c4 765 766 arg6 := ast.NewConcatExpr(token.NewFileInfo(1, 50), cvalues) 767 768 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "aws", false) 769 cmd.AddArg(arg1) 770 cmd.AddArg(arg2) 771 cmd.AddArg(arg3) 772 cmd.AddArg(arg4) 773 cmd.AddArg(arg5) 774 cmd.AddArg(arg6) 775 776 ln.Push(cmd) 777 expected.Root = ln 778 779 parserTest("concat indexed var", 780 `aws ec2 create-tags --resources $resource --tags "Key="+$tag[0]+",Value="+$tag[1]`, 781 expected, t, true) 782 } 783 784 func TestParseRfork(t *testing.T) { 785 expected := ast.NewTree("test rfork") 786 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 787 cmd1 := ast.NewRforkNode(token.NewFileInfo(1, 0)) 788 f1 := ast.NewStringExpr(token.NewFileInfo(1, 6), "u", false) 789 cmd1.SetFlags(f1) 790 ln.Push(cmd1) 791 expected.Root = ln 792 793 parserTest("test rfork", "rfork u", expected, t, true) 794 } 795 796 func TestParseRforkWithBlock(t *testing.T) { 797 expected := ast.NewTree("rfork with block") 798 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 799 rfork := ast.NewRforkNode(token.NewFileInfo(1, 0)) 800 arg := ast.NewStringExpr(token.NewFileInfo(1, 6), "u", false) 801 rfork.SetFlags(arg) 802 803 insideFork := ast.NewCommandNode(token.NewFileInfo(2, 1), "mount", false) 804 insideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 7), "-t", false)) 805 insideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 10), "proc", false)) 806 insideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 15), "proc", false)) 807 insideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 20), "/proc", false)) 808 809 bln := ast.NewBlockNode(token.NewFileInfo(1, 8)) 810 bln.Push(insideFork) 811 subtree := ast.NewTree("rfork") 812 subtree.Root = bln 813 814 rfork.SetTree(subtree) 815 816 ln.Push(rfork) 817 expected.Root = ln 818 819 parserTest("rfork with block", `rfork u { 820 mount -t proc proc /proc 821 } 822 `, expected, t, true) 823 824 } 825 826 func TestUnpairedRforkBlocks(t *testing.T) { 827 parser := NewParser("unpaired", "rfork u {") 828 829 _, err := parser.Parse() 830 831 if err == nil { 832 t.Errorf("Should fail because of unpaired open/close blocks") 833 return 834 } 835 } 836 837 func TestParseImport(t *testing.T) { 838 expected := ast.NewTree("test import") 839 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 840 importStmt := ast.NewImportNode(token.NewFileInfo(1, 0), 841 ast.NewStringExpr(token.NewFileInfo(1, 7), "env.sh", false)) 842 ln.Push(importStmt) 843 expected.Root = ln 844 845 parserTest("test import", "import env.sh", expected, t, true) 846 847 expected = ast.NewTree("test import with quotes") 848 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 849 importStmt = ast.NewImportNode(token.NewFileInfo(1, 0), 850 ast.NewStringExpr(token.NewFileInfo(1, 8), "env.sh", true)) 851 ln.Push(importStmt) 852 expected.Root = ln 853 854 parserTest("test import", `import "env.sh"`, expected, t, true) 855 } 856 857 func TestParseIf(t *testing.T) { 858 expected := ast.NewTree("test if") 859 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 860 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 861 ifDecl.SetLvalue(ast.NewStringExpr(token.NewFileInfo(1, 4), "test", true)) 862 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 14), "other", true)) 863 ifDecl.SetOp("==") 864 865 subBlock := ast.NewBlockNode(token.NewFileInfo(1, 21)) 866 cmd := ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 867 subBlock.Push(cmd) 868 869 ifTree := ast.NewTree("if block") 870 ifTree.Root = subBlock 871 872 ifDecl.SetIfTree(ifTree) 873 874 ln.Push(ifDecl) 875 expected.Root = ln 876 877 parserTest("test if", `if "test" == "other" { 878 pwd 879 }`, expected, t, true) 880 881 expected = ast.NewTree("test if") 882 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 883 ifDecl = ast.NewIfNode(token.NewFileInfo(1, 0)) 884 ifDecl.SetLvalue(ast.NewStringExpr(token.NewFileInfo(1, 4), "", true)) 885 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 10), "other", true)) 886 ifDecl.SetOp("!=") 887 888 subBlock = ast.NewBlockNode(token.NewFileInfo(1, 17)) 889 cmd = ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 890 subBlock.Push(cmd) 891 892 ifTree = ast.NewTree("if block") 893 ifTree.Root = subBlock 894 895 ifDecl.SetIfTree(ifTree) 896 897 ln.Push(ifDecl) 898 expected.Root = ln 899 900 parserTest("test if", `if "" != "other" { 901 pwd 902 }`, expected, t, true) 903 } 904 905 func TestParseFuncall(t *testing.T) { 906 expected := ast.NewTree("fn inv") 907 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 908 aFn := ast.NewFnInvNode(token.NewFileInfo(1, 0), "a") 909 ln.Push(aFn) 910 expected.Root = ln 911 912 parserTest("test basic fn inv", `a()`, expected, t, true) 913 914 expected = ast.NewTree("fn inv") 915 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 916 aFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), "a") 917 bFn := ast.NewFnInvNode(token.NewFileInfo(1, 2), "b") 918 aFn.AddArg(bFn) 919 ln.Push(aFn) 920 expected.Root = ln 921 922 parserTest("test fn composition", `a(b())`, expected, t, true) 923 924 expected = ast.NewTree("fn inv") 925 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 926 aFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), "a") 927 bFn = ast.NewFnInvNode(token.NewFileInfo(1, 2), "b") 928 b2Fn := ast.NewFnInvNode(token.NewFileInfo(1, 7), "b") 929 aFn.AddArg(bFn) 930 aFn.AddArg(b2Fn) 931 ln.Push(aFn) 932 expected.Root = ln 933 934 parserTest("test fn composition", `a(b(), b())`, expected, t, true) 935 936 expected = ast.NewTree("fn inv") 937 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 938 aFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), "a") 939 bFn = ast.NewFnInvNode(token.NewFileInfo(1, 2), "b") 940 b2Fn = ast.NewFnInvNode(token.NewFileInfo(1, 4), "b") 941 bFn.AddArg(b2Fn) 942 aFn.AddArg(bFn) 943 ln.Push(aFn) 944 expected.Root = ln 945 946 parserTest("test fn composition", `a(b(b()))`, expected, t, true) 947 948 expected = ast.NewTree("fn inv list") 949 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 950 aFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), "a") 951 lExpr := ast.NewListExpr(token.NewFileInfo(1, 2), []ast.Expr{ 952 ast.NewStringExpr(token.NewFileInfo(1, 4), "1", true), 953 ast.NewStringExpr(token.NewFileInfo(1, 8), "2", true), 954 ast.NewStringExpr(token.NewFileInfo(1, 12), "3", true), 955 }) 956 aFn.AddArg(lExpr) 957 ln.Push(aFn) 958 expected.Root = ln 959 parserTest("test fn list arg", `a(("1" "2" "3"))`, expected, t, true) 960 961 // test valid funcall syntaxes (do not verify AST) 962 for _, tc := range []string{ 963 `func()`, 964 `func(())`, // empty list 965 `func($a)`, 966 `_($a, $b)`, 967 `func($a())`, 968 `func($a(), $b())`, 969 `func($a($b($c())))`, 970 `func($a("a"))`, 971 `__((((()))))`, // perfect fit for a nash obfuscating code contest 972 `_( 973 () 974 )`, 975 `_( 976 (), (), (), (), (), 977 )`, 978 `_((() () () () ()))`, 979 `deploy((bomb shell))`, // unquoted list elements are still supported :-( 980 `func("a", ())`, 981 `_($a+$b)`, 982 `_($a+"")`, 983 `_(""+$a)`, 984 `func((()()))`, 985 } { 986 parser := NewParser("test", tc) 987 _, err := parser.Parse() 988 if err != nil { 989 t.Fatal(err) 990 } 991 } 992 } 993 994 func TestParseFuncallInvalid(t *testing.T) { 995 for _, tc := range []string{ 996 `test(()`, 997 `_())`, 998 `func(a)`, 999 `func("a", a)`, 1000 `func(_(((((()))))`, 1001 `func(()+())`, 1002 `func("1"+("2" "3"))`, 1003 `func(()())`, 1004 } { 1005 parser := NewParser("test", tc) 1006 _, err := parser.Parse() 1007 if err == nil { 1008 t.Fatalf("Syntax '%s' must fail...", tc) 1009 } 1010 } 1011 } 1012 1013 func TestParseIfFnInv(t *testing.T) { 1014 expected := ast.NewTree("test if") 1015 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1016 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 1017 ifDecl.SetLvalue(ast.NewFnInvNode(token.NewFileInfo(1, 3), "test")) 1018 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 14), "other", true)) 1019 ifDecl.SetOp("==") 1020 1021 subBlock := ast.NewBlockNode(token.NewFileInfo(1, 21)) 1022 cmd := ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 1023 subBlock.Push(cmd) 1024 1025 ifTree := ast.NewTree("if block") 1026 ifTree.Root = subBlock 1027 1028 ifDecl.SetIfTree(ifTree) 1029 1030 ln.Push(ifDecl) 1031 expected.Root = ln 1032 1033 parserTest("test if", `if test() == "other" { 1034 pwd 1035 }`, expected, t, true) 1036 1037 expected = ast.NewTree("test if") 1038 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1039 ifDecl = ast.NewIfNode(token.NewFileInfo(1, 0)) 1040 1041 fnInv := ast.NewFnInvNode(token.NewFileInfo(1, 3), "test") 1042 fnInv.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 9), "bleh", true)) 1043 ifDecl.SetLvalue(fnInv) 1044 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 20), "other", true)) 1045 ifDecl.SetOp("!=") 1046 1047 subBlock = ast.NewBlockNode(token.NewFileInfo(1, 27)) 1048 cmd = ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 1049 subBlock.Push(cmd) 1050 1051 ifTree = ast.NewTree("if block") 1052 ifTree.Root = subBlock 1053 1054 ifDecl.SetIfTree(ifTree) 1055 1056 ln.Push(ifDecl) 1057 expected.Root = ln 1058 1059 parserTest("test if", `if test("bleh") != "other" { 1060 pwd 1061 }`, expected, t, true) 1062 } 1063 1064 func TestParseIfLvariable(t *testing.T) { 1065 expected := ast.NewTree("test if with variable") 1066 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1067 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 1068 ifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), "$test")) 1069 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 13), "other", true)) 1070 ifDecl.SetOp("==") 1071 1072 subBlock := ast.NewBlockNode(token.NewFileInfo(1, 20)) 1073 cmd := ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 1074 subBlock.Push(cmd) 1075 1076 ifTree := ast.NewTree("if block") 1077 ifTree.Root = subBlock 1078 1079 ifDecl.SetIfTree(ifTree) 1080 1081 ln.Push(ifDecl) 1082 expected.Root = ln 1083 1084 parserTest("test if", `if $test == "other" { 1085 pwd 1086 }`, expected, t, true) 1087 } 1088 1089 func TestParseIfRvariable(t *testing.T) { 1090 expected := ast.NewTree("test if with variable") 1091 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1092 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 1093 ifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), "$test")) 1094 ifDecl.SetRvalue(ast.NewVarExpr(token.NewFileInfo(1, 12), "$other")) 1095 ifDecl.SetOp("==") 1096 1097 subBlock := ast.NewBlockNode(token.NewFileInfo(1, 19)) 1098 cmd := ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 1099 subBlock.Push(cmd) 1100 1101 ifTree := ast.NewTree("if block") 1102 ifTree.Root = subBlock 1103 1104 ifDecl.SetIfTree(ifTree) 1105 1106 ln.Push(ifDecl) 1107 expected.Root = ln 1108 1109 parserTest("test if", `if $test == $other { 1110 pwd 1111 }`, expected, t, true) 1112 } 1113 1114 func TestParseIfElse(t *testing.T) { 1115 expected := ast.NewTree("test if else with variable") 1116 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1117 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 1118 ifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), "$test")) 1119 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 13), "other", true)) 1120 ifDecl.SetOp("==") 1121 1122 subBlock := ast.NewBlockNode(token.NewFileInfo(1, 20)) 1123 cmd := ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 1124 subBlock.Push(cmd) 1125 1126 ifTree := ast.NewTree("if block") 1127 ifTree.Root = subBlock 1128 1129 ifDecl.SetIfTree(ifTree) 1130 1131 elseBlock := ast.NewBlockNode(token.NewFileInfo(3, 7)) 1132 exitCmd := ast.NewCommandNode(token.NewFileInfo(4, 1), "exit", false) 1133 elseBlock.Push(exitCmd) 1134 1135 elseTree := ast.NewTree("else block") 1136 elseTree.Root = elseBlock 1137 1138 ifDecl.SetElseTree(elseTree) 1139 1140 ln.Push(ifDecl) 1141 expected.Root = ln 1142 1143 parserTest("test if", `if $test == "other" { 1144 pwd 1145 } else { 1146 exit 1147 }`, expected, t, true) 1148 } 1149 1150 func TestParseIfElseIf(t *testing.T) { 1151 expected := ast.NewTree("test if else with variable") 1152 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1153 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 1154 ifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), "$test")) 1155 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 13), "other", true)) 1156 ifDecl.SetOp("==") 1157 1158 subBlock := ast.NewBlockNode(token.NewFileInfo(1, 20)) 1159 cmd := ast.NewCommandNode(token.NewFileInfo(2, 1), "pwd", false) 1160 subBlock.Push(cmd) 1161 1162 ifTree := ast.NewTree("if block") 1163 ifTree.Root = subBlock 1164 1165 ifDecl.SetIfTree(ifTree) 1166 1167 elseIfDecl := ast.NewIfNode(token.NewFileInfo(3, 7)) 1168 1169 elseIfDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(3, 10), "$test")) 1170 elseIfDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(3, 20), "others", true)) 1171 elseIfDecl.SetOp("==") 1172 1173 elseIfBlock := ast.NewBlockNode(token.NewFileInfo(3, 28)) 1174 elseifCmd := ast.NewCommandNode(token.NewFileInfo(4, 1), "ls", false) 1175 elseIfBlock.Push(elseifCmd) 1176 1177 elseIfTree := ast.NewTree("if block") 1178 elseIfTree.Root = elseIfBlock 1179 1180 elseIfDecl.SetIfTree(elseIfTree) 1181 1182 elseBlock := ast.NewBlockNode(token.NewFileInfo(5, 7)) 1183 exitCmd := ast.NewCommandNode(token.NewFileInfo(6, 1), "exit", false) 1184 elseBlock.Push(exitCmd) 1185 1186 elseTree := ast.NewTree("else block") 1187 elseTree.Root = elseBlock 1188 1189 elseIfDecl.SetElseTree(elseTree) 1190 1191 elseBlock2 := ast.NewBlockNode(token.NewFileInfo(3, 7)) 1192 elseBlock2.Push(elseIfDecl) 1193 1194 elseTree2 := ast.NewTree("first else tree") 1195 elseTree2.Root = elseBlock2 1196 ifDecl.SetElseTree(elseTree2) 1197 1198 ln.Push(ifDecl) 1199 expected.Root = ln 1200 1201 parserTest("test if", `if $test == "other" { 1202 pwd 1203 } else if $test == "others" { 1204 ls 1205 } else { 1206 exit 1207 }`, expected, t, true) 1208 } 1209 1210 func TestParseFnBasic(t *testing.T) { 1211 // root 1212 expected := ast.NewTree("fn") 1213 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1214 1215 // fn 1216 fn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), "build") 1217 tree := ast.NewTree("fn body") 1218 lnBody := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1219 tree.Root = lnBody 1220 fn.SetTree(tree) 1221 1222 // root 1223 ln.Push(fn) 1224 expected.Root = ln 1225 1226 parserTest("fn", `fn build() { 1227 1228 }`, expected, t, true) 1229 1230 // root 1231 expected = ast.NewTree("fn") 1232 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1233 1234 // fn 1235 fn = ast.NewFnDeclNode(token.NewFileInfo(1, 3), "build") 1236 tree = ast.NewTree("fn body") 1237 lnBody = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1238 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "ls", false) 1239 lnBody.Push(cmd) 1240 tree.Root = lnBody 1241 fn.SetTree(tree) 1242 1243 // root 1244 ln.Push(fn) 1245 expected.Root = ln 1246 1247 parserTest("fn", `fn build() { 1248 ls 1249 }`, expected, t, true) 1250 1251 // root 1252 expected = ast.NewTree("fn") 1253 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1254 1255 // fn 1256 fn = ast.NewFnDeclNode(token.NewFileInfo(1, 3), "build") 1257 fn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 9), "image", false)) 1258 tree = ast.NewTree("fn body") 1259 lnBody = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1260 cmd = ast.NewCommandNode(token.NewFileInfo(1, 0), "ls", false) 1261 lnBody.Push(cmd) 1262 tree.Root = lnBody 1263 fn.SetTree(tree) 1264 1265 // root 1266 ln.Push(fn) 1267 expected.Root = ln 1268 1269 parserTest("fn", `fn build(image) { 1270 ls 1271 }`, expected, t, true) 1272 1273 // root 1274 expected = ast.NewTree("fn") 1275 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1276 1277 // fn 1278 fn = ast.NewFnDeclNode(token.NewFileInfo(1, 3), "build") 1279 fn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 9), "image", false)) 1280 fn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 16), "debug", false)) 1281 tree = ast.NewTree("fn body") 1282 lnBody = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1283 cmd = ast.NewCommandNode(token.NewFileInfo(1, 0), "ls", false) 1284 lnBody.Push(cmd) 1285 tree.Root = lnBody 1286 fn.SetTree(tree) 1287 1288 // root 1289 ln.Push(fn) 1290 expected.Root = ln 1291 1292 parserTest("fn", `fn build(image, debug) { 1293 ls 1294 }`, expected, t, true) 1295 } 1296 1297 func TestParseInlineFnDecl(t *testing.T) { 1298 expected := ast.NewTree("fn") 1299 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1300 1301 fn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), "cd") 1302 tree := ast.NewTree("fn body") 1303 lnBody := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1304 echo := ast.NewCommandNode(token.NewFileInfo(1, 11), "echo", false) 1305 echo.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), "hello", true)) 1306 lnBody.Push(echo) 1307 1308 tree.Root = lnBody 1309 fn.SetTree(tree) 1310 1311 // root 1312 ln.Push(fn) 1313 expected.Root = ln 1314 1315 parserTest("inline fn", `fn cd() { echo "hello" }`, 1316 expected, t, false) 1317 1318 test := ast.NewCommandNode(token.NewFileInfo(1, 26), "test", false) 1319 test.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 32), "-d", false)) 1320 test.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 35), "/etc", false)) 1321 1322 pipe := ast.NewPipeNode(token.NewFileInfo(1, 11), false) 1323 pipe.AddCmd(echo) 1324 pipe.AddCmd(test) 1325 lnBody = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1326 lnBody.Push(pipe) 1327 tree.Root = lnBody 1328 fn.SetTree(tree) 1329 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1330 ln.Push(fn) 1331 expected.Root = ln 1332 1333 parserTest("inline fn", `fn cd() { echo "hello" | test -d /etc }`, 1334 expected, t, false) 1335 } 1336 1337 func TestParseBindFn(t *testing.T) { 1338 expected := ast.NewTree("bindfn") 1339 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1340 1341 bindFn := ast.NewBindFnNode(token.NewFileInfo(1, 0), "cd", "cd2") 1342 ln.Push(bindFn) 1343 expected.Root = ln 1344 1345 parserTest("bindfn", `bindfn cd cd2`, expected, t, true) 1346 } 1347 1348 func TestParseRedirectionVariable(t *testing.T) { 1349 expected := ast.NewTree("redirection var") 1350 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1351 1352 cmd := ast.NewCommandNode(token.NewFileInfo(1, 0), "cmd", false) 1353 redir := ast.NewRedirectNode(token.NewFileInfo(1, 4)) 1354 redirArg := ast.NewVarExpr(token.NewFileInfo(1, 6), "$outFname") 1355 redir.SetLocation(redirArg) 1356 cmd.AddRedirect(redir) 1357 ln.Push(cmd) 1358 expected.Root = ln 1359 1360 parserTest("redir var", `cmd > $outFname`, expected, t, true) 1361 } 1362 1363 func TestParseReturn(t *testing.T) { 1364 expected := ast.NewTree("return") 1365 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1366 1367 ret := ast.NewReturnNode(token.NewFileInfo(1, 0)) 1368 ln.Push(ret) 1369 expected.Root = ln 1370 1371 parserTest("return", `return`, expected, t, true) 1372 1373 expected = ast.NewTree("return list") 1374 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1375 1376 ret = ast.NewReturnNode(token.NewFileInfo(1, 0)) 1377 1378 listvalues := make([]ast.Expr, 2) 1379 1380 listvalues[0] = ast.NewStringExpr(token.NewFileInfo(1, 9), "val1", true) 1381 listvalues[1] = ast.NewStringExpr(token.NewFileInfo(1, 16), "val2", true) 1382 1383 retReturn := ast.NewListExpr(token.NewFileInfo(1, 7), listvalues) 1384 1385 ret.Returns = []ast.Expr{retReturn} 1386 1387 ln.Push(ret) 1388 expected.Root = ln 1389 1390 parserTest("return", `return ("val1" "val2")`, expected, t, true) 1391 1392 expected = ast.NewTree("return variable") 1393 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1394 1395 ret = ast.NewReturnNode(token.NewFileInfo(1, 0)) 1396 1397 ret.Returns = []ast.Expr{ast.NewVarExpr(token.NewFileInfo(1, 7), "$var")} 1398 1399 ln.Push(ret) 1400 expected.Root = ln 1401 1402 parserTest("return", `return $var`, expected, t, true) 1403 1404 expected = ast.NewTree("return string") 1405 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1406 1407 ret = ast.NewReturnNode(token.NewFileInfo(1, 0)) 1408 1409 ret.Returns = []ast.Expr{ast.NewStringExpr(token.NewFileInfo(1, 8), "value", true)} 1410 1411 ln.Push(ret) 1412 expected.Root = ln 1413 1414 parserTest("return", `return "value"`, expected, t, true) 1415 1416 expected = ast.NewTree("return funcall") 1417 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1418 1419 ret = ast.NewReturnNode(token.NewFileInfo(1, 0)) 1420 1421 aFn := ast.NewFnInvNode(token.NewFileInfo(1, 7), "a") 1422 1423 ret.Returns = []ast.Expr{aFn} 1424 1425 ln.Push(ret) 1426 expected.Root = ln 1427 1428 parserTest("return", `return a()`, expected, t, true) 1429 1430 expected = ast.NewTree("return multiple values") 1431 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1432 ret = ast.NewReturnNode(token.NewFileInfo(1, 0)) 1433 1434 a1 := ast.NewStringExpr(token.NewFileInfo(1, 8), "1", true) 1435 a2 := ast.NewStringExpr(token.NewFileInfo(1, 13), "2", true) 1436 a3 := ast.NewStringExpr(token.NewFileInfo(1, 18), "3", true) 1437 1438 ret.Returns = []ast.Expr{a1, a2, a3} 1439 1440 ln.Push(ret) 1441 expected.Root = ln 1442 1443 parserTest("return", `return "1", "2", "3"`, expected, t, true) 1444 } 1445 1446 func TestParseIfInvalid(t *testing.T) { 1447 parser := NewParser("if invalid", `if a == b { pwd }`) 1448 _, err := parser.Parse() 1449 1450 if err == nil { 1451 t.Error("Must fail. Only quoted strings and variables on if clauses.") 1452 return 1453 } 1454 } 1455 1456 func TestParseFor(t *testing.T) { 1457 expected := ast.NewTree("for") 1458 1459 forStmt := ast.NewForNode(token.NewFileInfo(1, 0)) 1460 forTree := ast.NewTree("for block") 1461 forBlock := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1462 forTree.Root = forBlock 1463 forStmt.SetTree(forTree) 1464 1465 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1466 ln.Push(forStmt) 1467 expected.Root = ln 1468 1469 parserTest("for", `for { 1470 1471 }`, expected, t, true) 1472 1473 forStmt.SetIdentifier("f") 1474 forStmt.SetInExpr(ast.NewVarExpr(token.NewFileInfo(1, 9), "$files")) 1475 1476 parserTest("for", `for f in $files { 1477 1478 }`, expected, t, true) 1479 1480 forStmt.SetIdentifier("f") 1481 fnInv := ast.NewFnInvNode(token.NewFileInfo(1, 9), "getfiles") 1482 fnArg := ast.NewStringExpr(token.NewFileInfo(1, 19), "/", true) 1483 fnInv.AddArg(fnArg) 1484 forStmt.SetInExpr(fnInv) 1485 1486 parserTest("for", `for f in getfiles("/") { 1487 1488 }`, expected, t, true) 1489 1490 forStmt.SetIdentifier("f") 1491 value1 := ast.NewStringExpr(token.NewFileInfo(1, 10), "1", false) 1492 value2 := ast.NewStringExpr(token.NewFileInfo(1, 12), "2", false) 1493 value3 := ast.NewStringExpr(token.NewFileInfo(1, 14), "3", false) 1494 value4 := ast.NewStringExpr(token.NewFileInfo(1, 16), "4", false) 1495 value5 := ast.NewStringExpr(token.NewFileInfo(1, 18), "5", false) 1496 1497 list := ast.NewListExpr(token.NewFileInfo(1, 9), []ast.Expr{ 1498 value1, value2, value3, value4, value5, 1499 }) 1500 1501 forStmt.SetInExpr(list) 1502 1503 parserTest("for", `for f in (1 2 3 4 5) { 1504 1505 }`, expected, t, true) 1506 } 1507 1508 func TestParseVariableIndexing(t *testing.T) { 1509 expected := ast.NewTree("variable indexing") 1510 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1511 1512 indexedVar := ast.NewIndexExpr( 1513 token.NewFileInfo(1, 7), 1514 ast.NewVarExpr(token.NewFileInfo(1, 7), "$values"), 1515 ast.NewIntExpr(token.NewFileInfo(1, 15), 0), 1516 ) 1517 1518 assignment := ast.NewSingleAssignNode(token.NewFileInfo(1, 0), 1519 ast.NewNameNode(token.NewFileInfo(1, 0), "test", nil), 1520 indexedVar, 1521 ) 1522 1523 ln.Push(assignment) 1524 expected.Root = ln 1525 1526 parserTest("variable indexing", `test = $values[0]`, expected, t, true) 1527 1528 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1529 1530 ifDecl := ast.NewIfNode(token.NewFileInfo(1, 0)) 1531 lvalue := ast.NewVarExpr(token.NewFileInfo(1, 3), "$values") 1532 1533 indexedVar = ast.NewIndexExpr(token.NewFileInfo(1, 3), lvalue, 1534 ast.NewIntExpr(token.NewFileInfo(1, 11), 0)) 1535 1536 ifDecl.SetLvalue(indexedVar) 1537 ifDecl.SetOp("==") 1538 ifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 18), "1", true)) 1539 1540 ifBlock := ast.NewTree("if") 1541 lnBody := ast.NewBlockNode(token.NewFileInfo(1, 21)) 1542 ifBlock.Root = lnBody 1543 ifDecl.SetIfTree(ifBlock) 1544 1545 ln.Push(ifDecl) 1546 expected.Root = ln 1547 1548 parserTest("variable indexing", `if $values[0] == "1" { 1549 1550 }`, expected, t, true) 1551 } 1552 1553 func TestParseMultilineCmdExec(t *testing.T) { 1554 expected := ast.NewTree("parser simple") 1555 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1556 cmd := ast.NewCommandNode(token.NewFileInfo(1, 1), "echo", true) 1557 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 7), "hello world", true)) 1558 ln.Push(cmd) 1559 1560 expected.Root = ln 1561 1562 parserTest("parser simple", `(echo "hello world")`, expected, t, true) 1563 1564 expected = ast.NewTree("parser aws cmd") 1565 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1566 cmd = ast.NewCommandNode(token.NewFileInfo(2, 1), "aws", true) 1567 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 5), "ec2", false)) 1568 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 9), "run-instances", false)) 1569 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 3), "--image-id", false)) 1570 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 14), "ami-xxxxxxxx", false)) 1571 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(4, 3), "--count", false)) 1572 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(4, 11), "1", false)) 1573 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(5, 3), "--instance-type", false)) 1574 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(5, 19), "t1.micro", false)) 1575 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(6, 3), "--key-name", false)) 1576 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(6, 14), "MyKeyPair", false)) 1577 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(7, 3), "--security-groups", false)) 1578 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(7, 21), "my-sg", false)) 1579 1580 ln.Push(cmd) 1581 1582 expected.Root = ln 1583 1584 parserTest("parser simple", `( 1585 aws ec2 run-instances 1586 --image-id ami-xxxxxxxx 1587 --count 1 1588 --instance-type t1.micro 1589 --key-name MyKeyPair 1590 --security-groups my-sg 1591 )`, expected, t, true) 1592 } 1593 1594 func TestParseMultilineCmdAssign(t *testing.T) { 1595 expected := ast.NewTree("parser simple assign") 1596 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1597 cmd := ast.NewCommandNode(token.NewFileInfo(1, 10), "echo", true) 1598 cmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), "hello world", true)) 1599 assign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 0), 1600 []*ast.NameNode{ast.NewNameNode(token.NewFileInfo(1, 0), "hello", nil)}, 1601 cmd, 1602 ) 1603 1604 if err != nil { 1605 t.Error(err) 1606 return 1607 } 1608 1609 ln.Push(assign) 1610 1611 expected.Root = ln 1612 1613 parserTest("parser simple", `hello <= (echo "hello world")`, expected, t, true) 1614 } 1615 1616 func TestMultiPipe(t *testing.T) { 1617 expected := ast.NewTree("parser pipe") 1618 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1619 first := ast.NewCommandNode(token.NewFileInfo(1, 1), "echo", false) 1620 first.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 7), "hello world", true)) 1621 1622 second := ast.NewCommandNode(token.NewFileInfo(1, 22), "awk", false) 1623 second.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 27), "{print $1}", true)) 1624 1625 pipe := ast.NewPipeNode(token.NewFileInfo(1, 20), true) 1626 pipe.AddCmd(first) 1627 pipe.AddCmd(second) 1628 1629 ln.Push(pipe) 1630 1631 expected.Root = ln 1632 1633 parserTest("parser pipe", `(echo "hello world" | awk "{print $1}")`, expected, t, true) 1634 1635 // get longer stringify 1636 expected = ast.NewTree("parser pipe") 1637 ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) 1638 first = ast.NewCommandNode(token.NewFileInfo(2, 1), "echo", false) 1639 first.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 7), "hello world", true)) 1640 1641 second = ast.NewCommandNode(token.NewFileInfo(3, 1), "awk", false) 1642 second.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 6), "{print AAAAAAAAAAAAAAAAAAAAAA}", true)) 1643 1644 pipe = ast.NewPipeNode(token.NewFileInfo(2, 20), true) 1645 pipe.AddCmd(first) 1646 pipe.AddCmd(second) 1647 1648 ln.Push(pipe) 1649 1650 expected.Root = ln 1651 1652 parserTest("parser pipe", `( 1653 echo "hello world" | 1654 awk "{print AAAAAAAAAAAAAAAAAAAAAA}" 1655 )`, expected, t, true) 1656 } 1657 1658 func TestFnVariadic(t *testing.T) { 1659 // root 1660 expected := ast.NewTree("variadic") 1661 ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1662 1663 // fn 1664 fn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), "println") 1665 fn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 11), "fmt", false)) 1666 fn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 16), "arg", true)) 1667 tree := ast.NewTree("fn body") 1668 lnBody := ast.NewBlockNode(token.NewFileInfo(1, 0)) 1669 print := ast.NewFnInvNode(token.NewFileInfo(2, 2), "print") 1670 print.AddArg(ast.NewConcatExpr(token.NewFileInfo(1, 7), []ast.Expr{ 1671 ast.NewVarExpr(token.NewFileInfo(2, 7), "$fmt"), 1672 ast.NewStringExpr(token.NewFileInfo(2, 12), "\n", true), 1673 })) 1674 print.AddArg(ast.NewVarVariadicExpr(token.NewFileInfo(2, 12), "$arg", true)) 1675 lnBody.Push(print) 1676 tree.Root = lnBody 1677 fn.SetTree(tree) 1678 1679 // root 1680 ln.Push(fn) 1681 expected.Root = ln 1682 1683 parserTest("fn", `fn println(fmt, arg...) { 1684 print($fmt+"\n", $arg...) 1685 }`, expected, t, true) 1686 } 1687 1688 func TestParseValidDotdotdot(t *testing.T) { 1689 for _, tc := range []string{ 1690 // things that should not break 1691 "ls ...", 1692 "go get ./...", 1693 `echo "..."`, 1694 `strangecmd... -h`, 1695 `bad_designed...fail -f`, 1696 } { 1697 parser := NewParser("", tc) 1698 _, err := parser.Parse() 1699 if err != nil { 1700 t.Fatalf("Code: '%s' failed: %s", tc, err.Error()) 1701 } 1702 } 1703 } 1704 1705 func TestParseInvalidDotdotdot(t *testing.T) { 1706 for _, tc := range []string{ 1707 "...", 1708 `if ... == "" {}`, 1709 `if $var... == "" {}`, 1710 `a = $var...`, 1711 `a, b, c = ("a" "b" "c")...`, // please, no 1712 // `fn println(arg..., fmt) {}`, // Not sure if must fail at parsing... 1713 } { 1714 parser := NewParser("", tc) 1715 _, err := parser.Parse() 1716 if err == nil { 1717 t.Fatalf("Syntax '%s' must fail", tc) 1718 } 1719 } 1720 } 1721 1722 func TestFunctionPipes(t *testing.T) { 1723 parser := NewParser("invalid pipe with functions", 1724 `echo "some thing" | replace(" ", "|")`) 1725 1726 _, err := parser.Parse() 1727 1728 if err == nil { 1729 t.Error("Must fail. Function must be bind'ed to command name to use in pipe.") 1730 return 1731 } 1732 }