package otto import ( "testing" . "github.com/robertkrimen/terst" "strings" "regexp" "fmt" ) func parserTestNormalizeWant(want string) string { index := regexp.MustCompile(`(?m)^[ \t]*---$`).FindAllStringIndex(want, -1) if index != nil && len(index) > 0 { lastIndex := index[len(index)-1] want = want[lastIndex[1]+1:] } want = strings.TrimSpace(want) normal := "" aSpace := false inQuote := false ignoreNext := false for _, rune := range want { if inQuote { if !ignoreNext { switch rune { case '"': inQuote = false case '\\': ignoreNext = true case '\n': inQuote = false aSpace = true continue } } else { ignoreNext = false } } else { switch rune { case ' ', '\t', '\n': aSpace = true continue case '"': inQuote = true } } if aSpace { normal += " " aSpace = false } normal += string(rune) } return normal } func parserTest(sourceWant... string) { source, want := "", "" if len(sourceWant) == 1 { sourceWant := sourceWant[0] index := regexp.MustCompile(`(?m)^[ \t]*---$`).FindAllStringIndex(sourceWant, -1) if index != nil && len(index) > 0 { lastIndex := index[len(index)-1] source = sourceWant[:lastIndex[0]] source = source[:len(source)-1] want = sourceWant[lastIndex[1]+1:] } } else { source, want = sourceWant[0], sourceWant[1] } want = parserTestNormalizeWant(want) have, err := parse(source) if err == nil { Is(have, want) } else { switch err := err.(type) { case *_syntaxError: Is(fmt.Sprintf("%s %d:%d:%d", err.Message, err.Line, err.Column, err.Character), want) // Line:Column:Character default: panic(err) } } } func TestParseSuccess(t *testing.T) { Terst(t) test := parserTest test(` xyzzy --- { @ xyzzy } `) test(` "Nothing ha ppens." --- { @ "Nothing ha ppens." } `) node := mustParse("xyzzy") Is(node, "{ @ xyzzy }") Is(node.Body[0].(*_identifierNode).Value, "xyzzy") node = mustParse("1") Is(node, "{ @ 1 }") Is(node.Body[0].(*_valueNode).Value, "1") node = mustParse("\"Xyzzy\"") Is(node, "{ @ \"Xyzzy\" }") Is(node.Body[0].(*_valueNode).Value, "Xyzzy") test(` xyzzy = 1 --- { @ { = xyzzy 1 } } `) test(` xyzzy = 1 + 2 * 3 --- { @ { = xyzzy { + 1 { * 2 3 } } } } `) test(` xyzzy = 1 + 2 * 3 + 4 --- { @ { = xyzzy { + { + 1 { * 2 3 } } 4 } } } `) test(` xyzzy = 1 + 2 * 3 + 4; 1 + 1 --- { @ { = xyzzy { + { + 1 { * 2 3 } } 4 } } { + 1 1 } } `) test(` xyzzy = test --- { @ { = xyzzy test } } `) test(` xyzzy = test(1 + 2) --- { @ { = xyzzy { test { + 1 2 } } } } `) test(` xyzzy = test(1 + 2, 3 * 4) --- { @ { = xyzzy { test { + 1 2 } { * 3 4 } } } } `) test(` xyzzy = function() {} --- { @ { = xyzzy { _ } } } `) test(` xyzzy = function() { 1 + 1 } --- { @ { = xyzzy { { + 1 1 } } } } `) test(` xyzzy = function() { return 1 + 1 } --- { @ { = xyzzy { { { + 1 1 } } } } } `) test(` xyzzy = function() { return 1 } --- { @ { = xyzzy { { 1 } } } } `) test(` xyzzy = function() { return apple + 1 } --- { @ { = xyzzy { { { + apple 1 } } } } } `) test(` if (1) apple else banana --- { @ { 1 apple banana } } `) test(` if (1) { apple } else banana --- { @ { 1 { apple } banana } } `) test(` if (1) { apple } else { banana } --- { @ { 1 { apple } { banana } } } `) test(` do apple while (1 + 1) --- { @ { { + 1 1 } apple } } `) test(` while (1 + 1) apple --- { @ { { + 1 1 } apple } } `) test(` do apple; while (1 + 1) banana --- { @ { { + 1 1 } apple } banana } `) test(` do while (1 + 1) banana; while (1 - 1) --- { @ { { - 1 1 } { { + 1 1 } banana } } } `) test(` {} --- { @ {} } `) test(` if (1 + 1) {} --- { @ { { + 1 1 } {} } } `) test(` var result64 = 64 , result10 = 10 --- { @ { = result64 64 } { = result10 10 } } `) test(` l0: while (1 + 1) { banana } --- { @ { { + 1 1 } { banana } } } `) test(` l0: result = 0 l1: l0: while (1 + 1) { banana } --- { @ { = result 0 } { { + 1 1 } { banana } } } `) test(` l0: do { banana } while (1 + 1) --- { @ { { + 1 1 } { banana } } } `) test(` apple = 0 banana = 1 while (apple) { apple = 2 if (0) { } banana = 3 } --- { @ { = apple 0 } { = banana 1 } { apple { { = apple 2 } { 0 {} } { = banana 3 } } } } `) test(` with 1 + 1 { apple } --- { @ { { + 1 1 } { apple } } } `) test(` with 1 + 1 apple --- { @ { { + 1 1 } apple } } `) test(` if (1) { result = 1 } else { result = 0 } --- { @ { 1 { { = result 1 } } { { = result 0 } } } } `) test(` with 1 + 1; --- { @ { { + 1 1 } ; } } `) test(` if (1) ; else { } --- { @ { 1 ; {} } } `) test(` while (true) { continue xyzzy } --- { @ { true { xyzzy } } } `) test(` try { result = 1 } finally { result = 2 } --- { @ { { { = result 1 } } { { = result 2 } } } } `) test(` try { result = 1 } catch (xyzzy) { } finally { result = 2 } --- { @ { { { = result 1 } } xyzzy {} { { = result 2 } } } } `) test(` try { result = 1 } catch (xyzzy) { } --- { @ { { { = result 1 } } xyzzy {} } } `) test(` switch (0 + 0) { } --- { @ { { + 0 0 } _ } } `) test(` switch (0 + 0) { case 1: default: } --- { @ { { + 0 0 } { 1 _ } { _ } } } `) test(` for (;;) { } --- { @ { ; ; ; {} } } `) test(` result = apple[0] --- { @ { = result { [ apple 0 } } } `) test(` result = apple.banana --- { @ { = result { . apple banana } } } `) test(` result = { apple: banana } --- { @ { = result {[ { apple: banana } ]} } } `) test(` result = { apple: banana, 0: 1 } --- { @ { = result {[ { apple: banana } { 0: 1 } ]} } } `) test(` result = { apple: banana, 0: 1, cherry: {} } --- { @ { = result {[ { apple: banana } { 0: 1 } { cherry: {[]} } ]} } } `) test(` result = [] --- { @ { = result [] } } `) test(` result = [ apple, banana, 0, 1 ] --- { @ { = result [ apple banana 0 1 ] } } `) test(` result = 0 ++result --- { @ { = result 0 } { ++= result } } `) test(` result = 0 result++ --- { @ { = result 0 } { =++ result } } `) test(` result = 0 result++ result ++result --- { @ { = result 0 } { =++ result } result { ++= result } } `) test(` result = abc && def || ghi && jkl --- { @ { = result { || { && abc def } { && ghi jkl } } } } `) test(` result = abc & def - 1 | ghi ^ jkl & mno ^ pqr + 1 --- { @ { = result { | { & abc { - def 1 } } { ^ { ^ ghi { & jkl mno } } { + pqr 1 } } } } } `) test(` result = 0 << 1 >> 2 >>> 3 --- { @ { = result { >>> { >> { << 0 1 } 2 } 3 } } } `) test(` result = 0 instanceof 1 --- { @ { = result { instanceof 0 1 } } } `) test(` result = 0 in 1 --- { @ { = result { in 0 1 } } } `) test(` for (abc in def) { } --- { @ { abc in def {} } } `) test(` abc = new String def = new Object(0 + 1, 2, 3) ghi = new Function() --- { @ { = abc { String _ } } { = def { Object { + 0 1 } 2 3 } } { = ghi { Function _ } } } `) test(` abc = true ? 1 : 0 --- { @ { = abc { ?: true 1 0 } } } `) test(` abc = [].toString() --- { @ { = abc { { . [] toString } _ } } } `) test(` Array.prototype.join = function() {} --- { @ { = { . { . Array prototype } join } { _ } } } `) test(` /abc/ig --- { @ { /abc/ig } } `) test(` ""+/abc/g --- { @ { + "" { /abc/g } } } `) test(` (function(){})() --- { @ { { _ } _ } } `) test(` var abc = 1 var def = 2 var ghi = def = abc --- { @ { = abc 1 } { = def 2 } { = ghi { = def abc } } } `) test(` for (var abc = 1, def = 2; ghi < 3; jkl++) { } --- { @ { { = abc 1 } { = def 2 } { < ghi 3 } { =++ jkl } {} } } `) test(` if (!abc && abc.jkl(def) && abc[0] === +abc[0] && abc.length < ghi) { } --- { @ { { && { && { && { ! abc } { { . abc jkl } def } } { === { [ abc 0 } { + { [ abc 0 } } } } { < { . abc length } ghi } } {} } } `) test(` abc = { '"': "'", "'": '"', } --- { @ { = abc {[ { ": "'" } { ': """ } ]} } } `) test(` for (var abc in def) { } --- { @ { { abc } in def {} } } `) test(` ({ abc: 'def' }) --- { @ {[ { abc: "def" } ]} } `) test(` // This is not an object, this is a string literal with a label! { abc: 'def' } --- { @ { "def" } } `) test(`abc = function() { 'use strict' } --- { @ { = abc { "use strict" } } } `) test(` "use strict" --- { @ "use strict" } `) test(` "use strict" abc = 1 + 2 + 11 --- { @ "use strict" { = abc { + { + 1 2 } 11 } } } `) // When run, this will call a type error to be thrown // This is essentially the same as: // // var abc = 1(function(){})() // test(` var abc = 1 (function(){ })() --- { @ { = abc { { 1 { _ } } _ } } } `) test(` xyzzy throw new TypeError("Nothing happens.") --- { @ xyzzy { { TypeError "Nothing happens." } } } `) } func TestParseFailure(t *testing.T) { Terst(t) test := parserTest test(`{ --- Unexpected end of input 1:1:1 `) test(`} --- Unexpected token } 1:1:1 `) test(`3ea --- Unexpected token ILLEGAL (3e) 1:1:1 `) test(`3in [] --- Unexpected token ILLEGAL (3i) 1:1:1 `) test(`3e --- Unexpected token ILLEGAL (3e) 1:1:1 `) test(`3e+ --- Unexpected token ILLEGAL (3e+) 1:1:1 `) test(`3e- --- Unexpected token ILLEGAL (3e-) 1:1:1 `) test(`3x --- Unexpected token ILLEGAL (3x) 1:1:1 `) test(`3x0 --- Unexpected token ILLEGAL (3x) 1:1:1 `) test(`0x --- Unexpected token ILLEGAL (0x) 1:1:1 `) test(`09 --- Unexpected token ILLEGAL (09) 1:1:1 `) test(`018 --- Unexpected token ILLEGAL (018) 1:1:1 `) test(`01a --- Unexpected token ILLEGAL (01a) 1:1:1 `) test(`3in[] --- Unexpected token ILLEGAL (3i) 1:1:1 `) test(`0x3in[] --- Unexpected token ILLEGAL (0x3i) 1:1:1 `) test(`"Hello World" --- Unexpected token ILLEGAL ("Hello) 1:1:1 `) test(`x\ --- Unexpected token ILLEGAL () 1:2:2 `) test(`x\u005c --- Unexpected token ILLEGAL () 1:2:2 `) test(`x\u002a --- Unexpected token ILLEGAL () 1:2:2 `) test(`var x = /(s/g --- Invalid regular expression 1:9:9 `) test(`/ --- Invalid regular expression 1:1:1 `) test(`3 = 4 --- Invalid left-hand side in assignment 1:1:1 `) test(`func() = 4 --- Invalid left-hand side in assignment 1:6:6 `) test(`(1 + 1) = 10 --- Invalid left-hand side in assignment 1:7:7 `) test(`1++ --- Invalid left-hand side in assignment 1:1:1 `) test(`1-- --- Invalid left-hand side in assignment 1:1:1 `) test(`--1 --- Invalid left-hand side in assignment 1:3:3 `) test(`for((1 + 1) in list) process(x); --- Invalid left-hand side in for-in 1:13:13 `) test(`[ --- Unexpected end of input 1:1:1 `) test(`[, --- Unexpected token , 1:2:2 `) test(`1 + { --- Unexpected end of input 1:5:5 `) test(`1 + { t:t --- Unexpected end of input 1:9:9 `) test(`1 + { t:t, --- Unexpected end of input 1:10:10 `) test("var x = /\n/", ` --- Invalid regular expression 1:9:9 `) test("var x = \"\n", ` --- Unexpected token ILLEGAL (") 1:9:9 `) test(`var if = 42 --- Unexpected token if 1:5:5 `) test(`i + 2 = 42 --- Invalid left-hand side in assignment 1:5:5 `) test(`+i = 42 --- Invalid left-hand side in assignment 1:2:2 `) test(`1 + ( --- Unexpected end of input 1:5:5 `) test("\n\n\n{", ` --- Unexpected end of input 4:1:4 `) test("\n/* Some multiline\ncomment */\n)", ` --- Unexpected token ) 4:1:31 `) // TODO //{ set 1 } //{ get 2 } //({ set: s(if) { } }) //({ set s(.) { } }) //({ set: s() { } }) //({ set: s(a, b) { } }) //({ get: g(d) { } }) //({ get i() { }, i: 42 }) //({ i: 42, get i() { } }) //({ set i(x) { }, i: 42 }) //({ i: 42, set i(x) { } }) //({ get i() { }, get i() { } }) //({ set i(x) { }, set i(x) { } }) test(`function t(if) { } --- Unexpected token if 1:12:12 `) // TODO This should be "token true" test(`function t(true) { } --- Unexpected token boolean 1:12:12 `) // TODO This should be "token false" test(`function t(false) { } --- Unexpected token boolean 1:12:12 `) test(`function t(null) { } --- Unexpected token null 1:12:12 `) test(`function null() { } --- Unexpected token null 1:10:10 `) test(`function true() { } --- Unexpected token true 1:10:10 `) test(`function false() { } --- Unexpected token false 1:10:10 `) test(`function if() { } --- Unexpected token if 1:10:10 `) // TODO Should be Unexpected identifier test(`a b; --- Unexpected token b 1:3:3 `) test(`if.a; --- Unexpected token . 1:3:3 `) test(`a if; --- Unexpected token if 1:3:3 `) // TODO Should be Unexpected reserved word test(`a class; --- Unexpected token class 1:3:3 `) test("break\n", ` --- Illegal break statement 2:1:7 `) // TODO Should be Unexpected number test(`break 1; --- Unexpected token 1 1:7:7 `) test("continue\n", ` --- Illegal continue statement 2:1:10 `) // TODO Should be Unexpected number test(`continue 2; --- Unexpected token 2 1:10:10 `) test(`throw --- Unexpected end of input 1:1:1 `) test(`throw; --- Unexpected token ; 1:6:6 `) test("throw\n", ` --- Illegal newline after throw 2:1:7 `) test(`for (var i, i2 in {}); --- Unexpected token in 1:16:16 `) test(`for ((i in {})); --- Unexpected token ) 1:15:15 `) test(`for (+i in {}); --- Invalid left-hand side in for-in 1:9:9 `) test(`if(false) --- Unexpected end of input 1:9:9 `) test(`if(false) doThis(); else --- Unexpected end of input 1:21:21 `) test(`do --- Unexpected end of input 1:1:1 `) test(`while(false) --- Unexpected end of input 1:12:12 `) test(`for(;;) --- Unexpected end of input 1:7:7 `) test(`with(x) --- Unexpected end of input 1:7:7 `) test(`try { } --- Missing catch or finally after try 1:8:8 `) test("\u203f = 10", ` --- Unexpected token ILLEGAL () 1:1:1 `) // TODO // const x = 12, y; // const x, y = 12; // const x; // if(true) let a = 1; // if(true) const a = 1; // TODO "Unexpected string" test(`new X()."S" --- Unexpected token string 1:9:9 `) // TODO Incorrect cursor position test(`/* --- Unexpected token ILLEGAL 0:0:0 `) // TODO Incorrect cursor position test(`/* --- Unexpected token ILLEGAL 0:0:0 `) // TODO Incorrect cursor position test(`/** --- Unexpected token ILLEGAL 0:0:0 `) // TODO Incorrect cursor position test("/*\n\n*", ` --- Unexpected token ILLEGAL 0:0:0 `) // TODO Incorrect cursor position test(`/*hello --- Unexpected token ILLEGAL 0:0:0 `) // TODO Incorrect cursor position test(`/*hello * --- Unexpected token ILLEGAL 0:0:0 `) test("\n]", ` --- Unexpected token ] 2:1:2 `) test("\r]", ` --- Unexpected token ] 2:1:2 `) test("\r\n]", ` --- Unexpected token ] 2:1:3 `) test("\n\r]", ` --- Unexpected token ] 3:1:3 `) test("//\r\n]", ` --- Unexpected token ] 2:1:5 `) test("//\n\r]", ` --- Unexpected token ] 3:1:5 `) test("/a\\\n/", ` --- Invalid regular expression 1:1:1 `) test("//\r \n]", ` --- Unexpected token ] 3:1:6 `) test("/*\r\n*/]", ` --- Unexpected token ] 2:1:7 `) test("/*\r \n*/]", ` --- Unexpected token ] 3:1:8 `) test("\\\\", ` --- Unexpected token ILLEGAL () 1:1:1 `) test("\\u005c", ` --- Unexpected token ILLEGAL () 1:1:1 `) test("\\x", ` --- Unexpected token ILLEGAL () 1:1:1 `) test("\\u0000", ` --- Unexpected token ILLEGAL () 1:1:1 `) test("\\u200c = []", ` --- Unexpected token ILLEGAL () 1:1:1 `) test("\\u200D = []", ` --- Unexpected token ILLEGAL () 1:1:1 `) test("\"\\", ` --- Unexpected token ILLEGAL ("\) 1:1:1 `) test("\"\\u", ` --- Unexpected token ILLEGAL ("\u) 1:1:1 `) test("return", ` --- Illegal return statement 1:1:1 `) test("break", ` --- Illegal break statement 1:6:6 `) test("continue", ` --- Illegal continue statement 1:9:9 `) test(`switch (x) { default: continue; } --- Illegal continue statement 1:33:33 `) test(`do { x } * --- Unexpected token * 1:10:10 `) test(`while (true) { break x; } --- Undefined label 'x' 1:22:22 `) test(`while (true) { continue x; } --- Undefined label 'x' 1:25:25 `) test(`x: while (true) { (function () { break x; }); } --- Undefined label 'x' 1:40:40 `) test(`x: while (true) { (function () { continue x; }); } --- Undefined label 'x' 1:43:43 `) test(`x: while (true) { (function () { break; }); } --- Illegal break statement 1:41:41 `) test(`x: while (true) { (function () { continue; }); } --- Illegal continue statement 1:44:44 `) test(`x: while (true) { x: while (true) {} } --- Label 'x' has already been declared 1:19:19 `) // TODO When strict mode is implemented if false { test(`(function () { 'use strict'; delete i; }()) --- Delete of an unqualified identifier in strict mode. 0:0:0 `) } // ---- // ---- // ---- test(`_: _: while (true) {} --- Label '_' has already been declared 1:4:4 `) test(` _: _: while (1 + 1) { banana } --- Label '_' has already been declared 3:1:5 `) test(` _: _: while (apple) { } --- Label '_' has already been declared 3:5:9 `) } func TestParseComment(t *testing.T) { Terst(t) test := parserTest test(` xyzzy // Ignore it // Ignore this // And this /* And all.. ... of this! */ "Nothing happens." // And finally this --- { @ xyzzy "Nothing happens." } `) }