Răsfoiți Sursa

Initial commit

Dmitry Panov 9 ani în urmă
comite
25773609b6
79 a modificat fișierele cu 30373 adăugiri și 0 ștergeri
  1. 3 0
      .gitignore
  2. 15 0
      LICENSE
  3. 152 0
      README.md
  4. 499 0
      array.go
  5. 455 0
      array_sparse.go
  6. 43 0
      array_sparse_test.go
  7. 1068 0
      ast/README.markdown
  8. 497 0
      ast/node.go
  9. 879 0
      builtin_array.go
  10. 50 0
      builtin_boolean.go
  11. 917 0
      builtin_date.go
  12. 62 0
      builtin_error.go
  13. 165 0
      builtin_function.go
  14. 422 0
      builtin_global.go
  15. 21 0
      builtin_global_test.go
  16. 446 0
      builtin_json.go
  17. 192 0
      builtin_math.go
  18. 154 0
      builtin_number.go
  19. 411 0
      builtin_object.go
  20. 272 0
      builtin_regexp.go
  21. 700 0
      builtin_string.go
  22. 90 0
      builtin_string_test.go
  23. 105 0
      builtin_typedarrays.go
  24. 12 0
      builtin_typedarrays_test.go
  25. 473 0
      compiler.go
  26. 1560 0
      compiler_expr.go
  27. 679 0
      compiler_stmt.go
  28. 1808 0
      compiler_test.go
  29. 110 0
      date.go
  30. 185 0
      date_test.go
  31. 290 0
      dtoa.go
  32. 110 0
      file/README.markdown
  33. 135 0
      file/file.go
  34. 128 0
      goja/main.go
  35. 97 0
      ipow.go
  36. 858 0
      object.go
  37. 150 0
      object_args.go
  38. 225 0
      object_gomap.go
  39. 152 0
      object_gomap_test.go
  40. 352 0
      object_goreflect.go
  41. 399 0
      object_goreflect_test.go
  42. 303 0
      object_goslice.go
  43. 251 0
      object_goslice_reflect.go
  44. 89 0
      object_goslice_reflect_test.go
  45. 87 0
      object_goslice_test.go
  46. 200 0
      object_lazy.go
  47. 240 0
      object_test.go
  48. 184 0
      parser/README.markdown
  49. 175 0
      parser/error.go
  50. 797 0
      parser/expression.go
  51. 830 0
      parser/lexer.go
  52. 376 0
      parser/lexer_test.go
  53. 930 0
      parser/marshal_test.go
  54. 272 0
      parser/parser.go
  55. 994 0
      parser/parser_test.go
  56. 402 0
      parser/regexp.go
  57. 127 0
      parser/regexp_test.go
  58. 44 0
      parser/scope.go
  59. 663 0
      parser/statement.go
  60. 32 0
      parser/testutil_test.go
  61. 361 0
      regexp.go
  62. 175 0
      regexp_test.go
  63. 1196 0
      runtime.go
  64. 510 0
      runtime_test.go
  65. 73 0
      srcfile.go
  66. 30 0
      srcfile_test.go
  67. 226 0
      string.go
  68. 313 0
      string_ascii.go
  69. 316 0
      string_unicode.go
  70. 291 0
      tc39_test.go
  71. 14 0
      testdata/S15.10.2.12_A1_T1.js
  72. 2 0
      token/Makefile
  73. 171 0
      token/README.markdown
  74. 116 0
      token/token.go
  75. 349 0
      token/token_const.go
  76. 222 0
      token/tokenfmt
  77. 815 0
      value.go
  78. 2497 0
      vm.go
  79. 359 0
      vm_test.go

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.idea
+*.iml
+testdata/test262

+ 15 - 0
LICENSE

@@ -0,0 +1,15 @@
+Copyright (c) 2016 Dmitry Panov
+
+Copyright (c) 2012 Robert Krimen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 152 - 0
README.md

@@ -0,0 +1,152 @@
+goja
+====
+
+ECMAScript 5.1(+) implementation in Go.
+
+It is not a replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine as it is much slower.
+It can be used as an embedded scripting language where the cost of making a lot of cgo calls may
+outweight the benefits of a faster JavaScript engine or as a way to avoid having non-Go dependencies.
+
+This project was largely inspired by [otto](https://github.com/robertkrimen/otto).
+
+Features
+--------
+
+ * Full ECMAScript 5.1 support (yes, including regex and strict mode).
+ * Passes nearly all [tc39 tests](https://github.com/tc39/test262) tagged with es5id. The goal is to pass all of them.
+ * On average 6-7 times faster than otto. Also uses considerably less memory.
+
+Current Status
+--------------
+
+ * API is still work in progress and is subject to change.
+ * Some of the AnnexB functionality is missing.
+ * No typed arrays yet.
+
+Basic Example
+-------------
+
+```go
+vm := New()
+v, err := vm.RunString("2 + 2")
+if err != nil {
+    panic(err)
+}
+if num := v.Export(); num != 4 {
+    panic(num)
+}
+```
+
+Passing Values to JS
+--------------------
+
+Any Go value can be passed to JS using Runtime.ToValue() method. Primitive types (ints and uints, floats, string, bool)
+are converted to the corresponding JavaScript primitives.
+
+*func(FunctionCall) Value* is treated as a native JavaScript function.
+
+*map[string]interface{}* is converted into a host object that largely behaves like a JavaScript Object.
+
+*[]interface{}* is converted into a host object that behaves largely like a JavaScript Array, however it's not extensible
+because extending it can change the pointer so it becomes detached from the original.
+
+**[]interface{}* is same as above, but the array becomes extensible.
+
+A function is wrapped within a native JavaScript function. When called the arguments are automatically converted to
+the appropriate Go types. If conversion is not possible, a TypeError is thrown.
+
+A slice type is converted into a generic reflect based host object that behaves similar to an unexpandable Array.
+
+Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
+to a Number, String, Boolean or Object.
+
+Note that the underlying type is not lost, calling Export() returns the original Go value. This applies to all
+reflect based types.
+
+Exporting Values from JS
+------------------------
+
+A JS value can be exported into its default Go representation using Value.Export() method.
+
+Alternatively it can be exported into a specific Go variable using Runtime.ExportTo() method.
+
+Regular Expressions
+-------------------
+
+Goja uses the embedded Go regexp library where possible, otherwise it falls back to [regexp2](https://github.com/dlclark/regexp2).
+
+Exceptions
+----------
+
+Any exception thrown in JavaScript is returned as an error of type *Exception. It is possible to extract the value thrown
+by using the Value() method:
+
+```go
+vm := New()
+_, err := vm.RunString(`
+
+throw("Test");
+
+`)
+
+if jserr, ok := err.(*Exception); ok {
+    if jserr.Value().Export() != "Test" {
+        panic("wrong value")
+    }
+} else {
+    panic("wrong type")
+}
+```
+
+If a native Go function panics with a Value, it is thrown as a Javascript exception (and therefore can be caught):
+
+```go
+var vm *Runtime
+
+func Test() {
+    panic(vm.ToValue("Error"))
+}
+
+vm = New()
+vm.Set("Test", Test)
+_, err := vm.RunString(`
+
+try {
+    Test();
+} catch(e) {
+    if (e !== "Error") {
+        throw e;
+    }
+}
+
+`)
+
+if err != nil {
+    panic(err)
+}
+```
+
+Interrupting
+------------
+
+```go
+func TestInterrupt(t *testing.T) {
+    const SCRIPT = `
+    var i = 0;
+    for (;;) {
+        i++;
+    }
+    `
+
+    vm := New()
+    time.AfterFunc(200 * time.Millisecond, func() {
+        vm.Interrupt("halt")
+    })
+
+    _, err := vm.RunString(SCRIPT)
+    if err == nil {
+        t.Fatal("Err is nil")
+    }
+    // err is of type *InterruptError and its Value() method returns whatever has been passed to vm.Interrupt()
+}
+```

+ 499 - 0
array.go

@@ -0,0 +1,499 @@
+package goja
+
+import (
+	"math"
+	"reflect"
+	"strconv"
+)
+
+type arrayObject struct {
+	baseObject
+	values         []Value
+	length         int
+	objCount       int
+	propValueCount int
+	lengthProp     valueProperty
+}
+
+func (a *arrayObject) init() {
+	a.baseObject.init()
+	a.lengthProp.writable = true
+
+	a._put("length", &a.lengthProp)
+}
+
+func (a *arrayObject) getLength() Value {
+	return intToValue(int64(a.length))
+}
+
+func (a *arrayObject) _setLengthInt(l int, throw bool) bool {
+	if l >= 0 && l <= math.MaxUint32 {
+		ret := true
+		if l <= a.length {
+			if a.propValueCount > 0 {
+				// Slow path
+				var s int
+				if a.length < len(a.values) {
+					s = a.length - 1
+				} else {
+					s = len(a.values) - 1
+				}
+				for i := s; i >= l; i-- {
+					if prop, ok := a.values[i].(*valueProperty); ok {
+						if !prop.configurable {
+							l = i + 1
+							ret = false
+							break
+						}
+						a.propValueCount--
+					}
+				}
+			}
+		}
+		if l <= len(a.values) {
+			if l >= 16 && l < cap(a.values)>>2 {
+				ar := make([]Value, l)
+				copy(ar, a.values)
+				a.values = ar
+			} else {
+				ar := a.values[l:len(a.values)]
+				for i, _ := range ar {
+					ar[i] = nil
+				}
+				a.values = a.values[:l]
+			}
+		}
+		a.length = l
+		if !ret {
+			a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length")
+		}
+		return ret
+	}
+	panic(a.val.runtime.newError(a.val.runtime.global.RangeError, "Invalid array length"))
+}
+
+func (a *arrayObject) setLengthInt(l int64, throw bool) bool {
+	if l == int64(a.length) {
+		return true
+	}
+	if !a.lengthProp.writable {
+		a.val.runtime.typeErrorResult(throw, "length is not writable")
+		return false
+	}
+	return a._setLengthInt(int(l), throw)
+}
+
+func (a *arrayObject) setLength(v Value, throw bool) bool {
+	l, ok := toIntIgnoreNegZero(v)
+	if ok && l == int64(a.length) {
+		return true
+	}
+	if !a.lengthProp.writable {
+		a.val.runtime.typeErrorResult(throw, "length is not writable")
+		return false
+	}
+	if ok {
+		return a._setLengthInt(int(l), throw)
+	}
+	panic(a.val.runtime.newError(a.val.runtime.global.RangeError, "Invalid array length"))
+}
+
+func (a *arrayObject) getIdx(idx int, origNameStr string, origName Value) (v Value) {
+	if idx >= 0 && idx < len(a.values) {
+		v = a.values[idx]
+	}
+	if v == nil && a.prototype != nil {
+		if origName != nil {
+			v = a.prototype.self.getProp(origName)
+		} else {
+			v = a.prototype.self.getPropStr(origNameStr)
+		}
+	}
+	return
+}
+
+func (a *arrayObject) sortLen() int {
+	return len(a.values)
+}
+
+func (a *arrayObject) sortGet(i int) Value {
+	v := a.values[i]
+	if p, ok := v.(*valueProperty); ok {
+		v = p.get(a.val)
+	}
+	return v
+}
+
+func (a *arrayObject) swap(i, j int) {
+	a.values[i], a.values[j] = a.values[j], a.values[i]
+}
+
+func toIdx(v Value) int {
+	idx := -1
+	if idxVal, ok1 := v.(valueInt); ok1 {
+		idx = int(idxVal)
+	} else {
+		if i, err := strconv.Atoi(v.String()); err == nil {
+			idx = i
+		}
+	}
+	if idx >= 0 && idx < math.MaxUint32 {
+		return idx
+	}
+	return -1
+}
+
+func strToIdx(s string) int {
+	idx := -1
+	if i, err := strconv.Atoi(s); err == nil {
+		idx = i
+	}
+
+	if idx >= 0 && idx < math.MaxUint32 {
+		return idx
+	}
+	return -1
+}
+
+func (a *arrayObject) getProp(n Value) Value {
+	if idx := toIdx(n); idx >= 0 {
+		return a.getIdx(idx, "", n)
+	}
+
+	if n.String() == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getProp(n)
+}
+
+func (a *arrayObject) getLengthProp() Value {
+	a.lengthProp.value = intToValue(int64(a.length))
+	return &a.lengthProp
+}
+
+func (a *arrayObject) getPropStr(name string) Value {
+	if i := strToIdx(name); i >= 0 {
+		return a.getIdx(i, name, nil)
+	}
+	if name == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getPropStr(name)
+}
+
+func (a *arrayObject) getOwnProp(name string) Value {
+	if i := strToIdx(name); i >= 0 {
+		if i >= 0 && i < len(a.values) {
+			return a.values[i]
+		}
+	}
+	if name == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getOwnProp(name)
+}
+
+func (a *arrayObject) putIdx(idx int, val Value, throw bool, origNameStr string, origName Value) {
+	var prop Value
+	if idx < len(a.values) {
+		prop = a.values[idx]
+	}
+
+	if prop == nil {
+		if a.prototype != nil {
+			var pprop Value
+			if origName != nil {
+				pprop = a.prototype.self.getProp(origName)
+			} else {
+				pprop = a.prototype.self.getPropStr(origNameStr)
+			}
+			if pprop, ok := pprop.(*valueProperty); ok {
+				if !pprop.isWritable() {
+					a.val.runtime.typeErrorResult(throw)
+					return
+				}
+				if pprop.accessor {
+					pprop.set(a.val, val)
+					return
+				}
+			}
+		}
+
+		if !a.extensible {
+			a.val.runtime.typeErrorResult(throw)
+			return
+		}
+		if idx >= a.length {
+			if !a.setLengthInt(int64(idx+1), throw) {
+				return
+			}
+		}
+		if idx >= len(a.values) {
+			if !a.expand(idx) {
+				a.val.self.(*sparseArrayObject).putIdx(idx, val, throw, origNameStr, origName)
+				return
+			}
+		}
+	} else {
+		if prop, ok := prop.(*valueProperty); ok {
+			if !prop.isWritable() {
+				a.val.runtime.typeErrorResult(throw)
+				return
+			}
+			prop.set(a.val, val)
+			return
+		}
+	}
+
+	a.values[idx] = val
+	a.objCount++
+}
+
+func (a *arrayObject) put(n Value, val Value, throw bool) {
+	if idx := toIdx(n); idx >= 0 {
+		a.putIdx(idx, val, throw, "", n)
+	} else {
+		if n.String() == "length" {
+			a.setLength(val, throw)
+		} else {
+			a.baseObject.put(n, val, throw)
+		}
+	}
+}
+
+func (a *arrayObject) putStr(name string, val Value, throw bool) {
+	if idx := strToIdx(name); idx >= 0 {
+		a.putIdx(idx, val, throw, name, nil)
+	} else {
+		if name == "length" {
+			a.setLength(val, throw)
+		} else {
+			a.baseObject.putStr(name, val, throw)
+		}
+	}
+}
+
+type arrayPropIter struct {
+	a         *arrayObject
+	recursive bool
+	idx       int
+}
+
+func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
+	for i.idx < len(i.a.values) {
+		name := strconv.Itoa(i.idx)
+		prop := i.a.values[i.idx]
+		i.idx++
+		if prop != nil {
+			return propIterItem{name: name, value: prop}, i.next
+		}
+	}
+
+	return i.a.baseObject._enumerate(i.recursive)()
+}
+
+func (a *arrayObject) _enumerate(recusrive bool) iterNextFunc {
+	return (&arrayPropIter{
+		a:         a,
+		recursive: recusrive,
+	}).next
+}
+
+func (a *arrayObject) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: a._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (a *arrayObject) hasOwnProperty(n Value) bool {
+	if idx := toIdx(n); idx >= 0 {
+		return idx < len(a.values) && a.values[idx] != nil && a.values[idx] != _undefined
+	} else {
+		return a.baseObject.hasOwnProperty(n)
+	}
+}
+
+func (a *arrayObject) hasOwnPropertyStr(name string) bool {
+	if idx := strToIdx(name); idx >= 0 {
+		return idx < len(a.values) && a.values[idx] != nil && a.values[idx] != _undefined
+	} else {
+		return a.baseObject.hasOwnPropertyStr(name)
+	}
+}
+
+func (a *arrayObject) expand(idx int) bool {
+	targetLen := idx + 1
+	if targetLen > len(a.values) {
+		if targetLen < cap(a.values) {
+			a.values = a.values[:targetLen]
+		} else {
+			if idx > 4096 && (a.objCount == 0 || idx/a.objCount > 10) {
+				//log.Println("Switching standard->sparse")
+				sa := &sparseArrayObject{
+					baseObject: a.baseObject,
+					length:     a.length,
+				}
+				sa.setValues(a.values)
+				sa.val.self = sa
+				sa.init()
+				sa.lengthProp.writable = a.lengthProp.writable
+				return false
+			} else {
+				// Use the same algorithm as in runtime.growSlice
+				newcap := cap(a.values)
+				doublecap := newcap + newcap
+				if targetLen > doublecap {
+					newcap = targetLen
+				} else {
+					if len(a.values) < 1024 {
+						newcap = doublecap
+					} else {
+						for newcap < targetLen {
+							newcap += newcap / 4
+						}
+					}
+				}
+				newValues := make([]Value, targetLen, newcap)
+				copy(newValues, a.values)
+				a.values = newValues
+			}
+		}
+	}
+	return true
+}
+
+func (r *Runtime) defineArrayLength(prop *valueProperty, descr objectImpl, setter func(Value, bool) bool, throw bool) bool {
+	ret := true
+
+	if cfg := descr.getStr("configurable"); cfg != nil && cfg.ToBoolean() {
+		ret = false
+		goto Reject
+	}
+
+	if cfg := descr.getStr("enumerable"); cfg != nil && cfg.ToBoolean() {
+		ret = false
+		goto Reject
+	}
+
+	if cfg := descr.getStr("get"); cfg != nil {
+		ret = false
+		goto Reject
+	}
+
+	if cfg := descr.getStr("set"); cfg != nil {
+		ret = false
+		goto Reject
+	}
+
+	if newLen := descr.getStr("value"); newLen != nil {
+		ret = setter(newLen, false)
+	} else {
+		ret = true
+	}
+
+	if wr := descr.getStr("writable"); wr != nil {
+		w := wr.ToBoolean()
+		if prop.writable {
+			prop.writable = w
+		} else {
+			if w {
+				ret = false
+				goto Reject
+			}
+		}
+	}
+
+Reject:
+	if !ret {
+		r.typeErrorResult(throw, "Cannot redefine property: length")
+	}
+
+	return ret
+}
+
+func (a *arrayObject) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	if idx := toIdx(n); idx >= 0 {
+		var existing Value
+		if idx < len(a.values) {
+			existing = a.values[idx]
+		}
+		prop, ok := a.baseObject._defineOwnProperty(n, existing, descr, throw)
+		if ok {
+			if idx >= a.length {
+				if !a.setLengthInt(int64(idx+1), throw) {
+					return false
+				}
+			}
+			if a.expand(idx) {
+				a.values[idx] = prop
+				a.objCount++
+				if _, ok := prop.(*valueProperty); ok {
+					a.propValueCount++
+				}
+			} else {
+				a.val.self.(*sparseArrayObject).putIdx(idx, prop, throw, "", nil)
+			}
+		}
+		return ok
+	} else {
+		if n.String() == "length" {
+			return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw)
+		}
+		return a.baseObject.defineOwnProperty(n, descr, throw)
+	}
+}
+
+func (a *arrayObject) _deleteProp(idx int, throw bool) bool {
+	if idx < len(a.values) {
+		if v := a.values[idx]; v != nil {
+			if p, ok := v.(*valueProperty); ok {
+				if !p.configurable {
+					a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.ToString())
+					return false
+				}
+				a.propValueCount--
+			}
+			a.values[idx] = nil
+			a.objCount--
+		}
+	}
+	return true
+}
+
+func (a *arrayObject) delete(n Value, throw bool) bool {
+	if idx := toIdx(n); idx >= 0 {
+		return a._deleteProp(idx, throw)
+	}
+	return a.baseObject.delete(n, throw)
+}
+
+func (a *arrayObject) deleteStr(name string, throw bool) bool {
+	if idx := strToIdx(name); idx >= 0 {
+		return a._deleteProp(idx, throw)
+	}
+	return a.baseObject.deleteStr(name, throw)
+}
+
+func (a *arrayObject) export() interface{} {
+	arr := make([]interface{}, a.length)
+	for i, v := range a.values {
+		if v != nil {
+			arr[i] = v.Export()
+		}
+	}
+
+	return arr
+}
+
+func (a *arrayObject) exportType() reflect.Type {
+	return reflectTypeArray
+}
+
+func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem) {
+	a.values = make([]Value, int(items[len(items)-1].idx+1))
+	for _, item := range items {
+		a.values[item.idx] = item.value
+	}
+}

+ 455 - 0
array_sparse.go

@@ -0,0 +1,455 @@
+package goja
+
+import (
+	"math"
+	"reflect"
+	"sort"
+	"strconv"
+)
+
+type sparseArrayItem struct {
+	idx   int
+	value Value
+}
+
+type sparseArrayObject struct {
+	baseObject
+	items          []sparseArrayItem
+	length         int
+	propValueCount int
+	lengthProp     valueProperty
+}
+
+func (a *sparseArrayObject) init() {
+	a.baseObject.init()
+	a.lengthProp.writable = true
+
+	a._put("length", &a.lengthProp)
+}
+
+func (a *sparseArrayObject) getLength() Value {
+	return intToValue(int64(a.length))
+}
+
+func (a *sparseArrayObject) findIdx(idx int) int {
+	return sort.Search(len(a.items), func(i int) bool {
+		return a.items[i].idx >= idx
+	})
+}
+
+func (a *sparseArrayObject) _setLengthInt(l int, throw bool) bool {
+	if l >= 0 && l <= math.MaxUint32 {
+		ret := true
+
+		if l <= a.length {
+			if a.propValueCount > 0 {
+				// Slow path
+				for i := len(a.items) - 1; i >= 0; i-- {
+					item := a.items[i]
+					if item.idx <= l {
+						break
+					}
+					if prop, ok := item.value.(*valueProperty); ok {
+						if !prop.configurable {
+							l = item.idx + 1
+							ret = false
+							break
+						}
+						a.propValueCount--
+					}
+				}
+			}
+		}
+
+		idx := a.findIdx(l)
+
+		aa := a.items[idx:]
+		for i, _ := range aa {
+			aa[i].value = nil
+		}
+		a.items = a.items[:idx]
+		a.length = l
+		if !ret {
+			a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length")
+		}
+		return ret
+	}
+	panic(a.val.runtime.newError(a.val.runtime.global.RangeError, "Invalid array length"))
+}
+
+func (a *sparseArrayObject) setLengthInt(l int64, throw bool) bool {
+	if l == int64(a.length) {
+		return true
+	}
+	if !a.lengthProp.writable {
+		a.val.runtime.typeErrorResult(throw, "length is not writable")
+		return false
+	}
+	return a._setLengthInt(int(l), throw)
+}
+
+func (a *sparseArrayObject) setLength(v Value, throw bool) bool {
+	l, ok := toIntIgnoreNegZero(v)
+	if ok && l == int64(a.length) {
+		return true
+	}
+	if !a.lengthProp.writable {
+		a.val.runtime.typeErrorResult(throw, "length is not writable")
+		return false
+	}
+	if ok {
+		return a._setLengthInt(int(l), throw)
+	}
+	panic(a.val.runtime.newError(a.val.runtime.global.RangeError, "Invalid array length"))
+}
+
+func (a *sparseArrayObject) getIdx(idx int, origNameStr string, origName Value) (v Value) {
+	i := a.findIdx(idx)
+	if i < len(a.items) && a.items[i].idx == idx {
+		return a.items[i].value
+	}
+
+	if a.prototype != nil {
+		if origName != nil {
+			v = a.prototype.self.getProp(origName)
+		} else {
+			v = a.prototype.self.getPropStr(origNameStr)
+		}
+	}
+	return
+}
+
+func (a *sparseArrayObject) getProp(n Value) Value {
+	if idx := toIdx(n); idx >= 0 {
+		return a.getIdx(idx, "", n)
+	}
+
+	if n.String() == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getProp(n)
+}
+
+func (a *sparseArrayObject) getLengthProp() Value {
+	a.lengthProp.value = intToValue(int64(a.length))
+	return &a.lengthProp
+}
+
+func (a *sparseArrayObject) getOwnProp(name string) Value {
+	if idx := strToIdx(name); idx >= 0 {
+		i := a.findIdx(idx)
+		if i < len(a.items) && a.items[i].idx == idx {
+			return a.items[i].value
+		}
+		return nil
+	}
+	if name == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getOwnProp(name)
+}
+
+func (a *sparseArrayObject) getPropStr(name string) Value {
+	if i := strToIdx(name); i >= 0 {
+		return a.getIdx(i, name, nil)
+	}
+	if name == "length" {
+		return a.getLengthProp()
+	}
+	return a.baseObject.getPropStr(name)
+}
+
+func (a *sparseArrayObject) putIdx(idx int, val Value, throw bool, origNameStr string, origName Value) {
+	var prop Value
+	i := a.findIdx(idx)
+	if i < len(a.items) && a.items[i].idx == idx {
+		prop = a.items[i].value
+	}
+
+	if prop == nil {
+		if a.prototype != nil {
+			var pprop Value
+			if origName != nil {
+				pprop = a.prototype.self.getProp(origName)
+			} else {
+				pprop = a.prototype.self.getPropStr(origNameStr)
+			}
+			if pprop, ok := pprop.(*valueProperty); ok {
+				if !pprop.isWritable() {
+					a.val.runtime.typeErrorResult(throw)
+					return
+				}
+				if pprop.accessor {
+					pprop.set(a.val, val)
+					return
+				}
+			}
+		}
+
+		if !a.extensible {
+			a.val.runtime.typeErrorResult(throw)
+			return
+		}
+
+		if idx >= a.length {
+			if !a.setLengthInt(int64(idx+1), throw) {
+				return
+			}
+		}
+
+		if a.expand() {
+			a.items = append(a.items, sparseArrayItem{})
+			copy(a.items[i+1:], a.items[i:])
+			a.items[i] = sparseArrayItem{
+				idx:   idx,
+				value: val,
+			}
+		} else {
+			a.val.self.(*arrayObject).putIdx(idx, val, throw, origNameStr, origName)
+			return
+		}
+	} else {
+		if prop, ok := prop.(*valueProperty); ok {
+			if !prop.isWritable() {
+				a.val.runtime.typeErrorResult(throw)
+				return
+			}
+			prop.set(a.val, val)
+			return
+		} else {
+			a.items[i].value = val
+		}
+	}
+
+}
+
+func (a *sparseArrayObject) put(n Value, val Value, throw bool) {
+	if idx := toIdx(n); idx >= 0 {
+		a.putIdx(idx, val, throw, "", n)
+	} else {
+		if n.String() == "length" {
+			a.setLength(val, throw)
+		} else {
+			a.baseObject.put(n, val, throw)
+		}
+	}
+}
+
+func (a *sparseArrayObject) putStr(name string, val Value, throw bool) {
+	if idx := strToIdx(name); idx >= 0 {
+		a.putIdx(idx, val, throw, name, nil)
+	} else {
+		if name == "length" {
+			a.setLength(val, throw)
+		} else {
+			a.baseObject.putStr(name, val, throw)
+		}
+	}
+}
+
+type sparseArrayPropIter struct {
+	a         *sparseArrayObject
+	recursive bool
+	idx       int
+}
+
+func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
+	for i.idx < len(i.a.items) {
+		name := strconv.Itoa(int(i.a.items[i.idx].idx))
+		prop := i.a.items[i.idx].value
+		i.idx++
+		if prop != nil {
+			return propIterItem{name: name, value: prop}, i.next
+		}
+	}
+
+	return i.a.baseObject._enumerate(i.recursive)()
+}
+
+func (a *sparseArrayObject) _enumerate(recusrive bool) iterNextFunc {
+	return (&sparseArrayPropIter{
+		a:         a,
+		recursive: recusrive,
+	}).next
+}
+
+func (a *sparseArrayObject) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: a._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (a *sparseArrayObject) setValues(values []Value) {
+	a.items = nil
+	for i, val := range values {
+		if val != nil {
+			a.items = append(a.items, sparseArrayItem{
+				idx:   i,
+				value: val,
+			})
+		}
+	}
+}
+
+func (a *sparseArrayObject) hasOwnProperty(n Value) bool {
+	if idx := toIdx(n); idx >= 0 {
+		i := a.findIdx(idx)
+		if i < len(a.items) && a.items[i].idx == idx {
+			return a.items[i].value != _undefined
+		}
+		return false
+	} else {
+		return a.baseObject.hasOwnProperty(n)
+	}
+}
+
+func (a *sparseArrayObject) hasOwnPropertyStr(name string) bool {
+	if idx := strToIdx(name); idx >= 0 {
+		i := a.findIdx(idx)
+		if i < len(a.items) && a.items[i].idx == idx {
+			return a.items[i].value != _undefined
+		}
+		return false
+	} else {
+		return a.baseObject.hasOwnPropertyStr(name)
+	}
+}
+
+func (a *sparseArrayObject) expand() bool {
+	if l := len(a.items); l >= 1024 {
+		if int(a.items[l-1].idx)/l < 8 {
+			//log.Println("Switching sparse->standard")
+			ar := &arrayObject{
+				baseObject:     a.baseObject,
+				length:         int(a.length),
+				propValueCount: a.propValueCount,
+			}
+			ar.setValuesFromSparse(a.items)
+			ar.val.self = ar
+			ar.init()
+			ar.lengthProp.writable = a.lengthProp.writable
+			return false
+		}
+	}
+	return true
+}
+
+func (a *sparseArrayObject) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	if idx := toIdx(n); idx >= 0 {
+		var existing Value
+		i := a.findIdx(idx)
+		if i < len(a.items) && a.items[i].idx == idx {
+			existing = a.items[i].value
+		}
+		prop, ok := a.baseObject._defineOwnProperty(n, existing, descr, throw)
+		if ok {
+			if idx >= a.length {
+				if !a.setLengthInt(int64(idx+1), throw) {
+					return false
+				}
+			}
+			if i >= len(a.items) || a.items[i].idx != idx {
+				if a.expand() {
+					a.items = append(a.items, sparseArrayItem{})
+					copy(a.items[i+1:], a.items[i:])
+					a.items[i] = sparseArrayItem{
+						idx:   idx,
+						value: prop,
+					}
+					if idx >= a.length {
+						a.length = idx + 1
+					}
+				} else {
+					return a.val.self.defineOwnProperty(n, descr, throw)
+				}
+			} else {
+				a.items[i].value = prop
+			}
+			if _, ok := prop.(*valueProperty); ok {
+				a.propValueCount++
+			}
+		}
+		return ok
+	} else {
+		if n.String() == "length" {
+			return a.val.runtime.defineArrayLength(&a.lengthProp, descr, a.setLength, throw)
+		}
+		return a.baseObject.defineOwnProperty(n, descr, throw)
+	}
+}
+
+func (a *sparseArrayObject) _deleteProp(idx int, throw bool) bool {
+	i := a.findIdx(idx)
+	if i < len(a.items) && a.items[i].idx == idx {
+		if p, ok := a.items[i].value.(*valueProperty); ok {
+			if !p.configurable {
+				a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.ToString())
+				return false
+			}
+			a.propValueCount--
+		}
+		copy(a.items[i:], a.items[i+1:])
+		a.items[len(a.items)-1].value = nil
+		a.items = a.items[:len(a.items)-1]
+	}
+	return true
+}
+
+func (a *sparseArrayObject) delete(n Value, throw bool) bool {
+	if idx := toIdx(n); idx >= 0 {
+		return a._deleteProp(idx, throw)
+	}
+	return a.baseObject.delete(n, throw)
+}
+
+func (a *sparseArrayObject) deleteStr(name string, throw bool) bool {
+	if idx := strToIdx(name); idx >= 0 {
+		return a._deleteProp(idx, throw)
+	}
+	return a.baseObject.deleteStr(name, throw)
+}
+
+func (a *sparseArrayObject) sortLen() int {
+	if len(a.items) > 0 {
+		return int(a.items[len(a.items)-1].idx) + 1
+	}
+
+	return 0
+}
+
+func (a *sparseArrayObject) sortGet(i int) Value {
+	idx := a.findIdx(i)
+	if idx < len(a.items) && a.items[idx].idx == i {
+		v := a.items[idx].value
+		if p, ok := v.(*valueProperty); ok {
+			v = p.get(a.val)
+		}
+		return v
+	}
+	return nil
+}
+
+func (a *sparseArrayObject) swap(i, j int) {
+	idxI := a.findIdx(i)
+	idxJ := a.findIdx(j)
+
+	if idxI < len(a.items) && a.items[idxI].idx == i && idxJ < len(a.items) && a.items[idxJ].idx == j {
+		a.items[idxI].value, a.items[idxJ].value = a.items[idxJ].value, a.items[idxI].value
+	}
+}
+
+func (a *sparseArrayObject) export() interface{} {
+	arr := make([]interface{}, a.length)
+	for _, item := range a.items {
+		if item.value != nil {
+			arr[item.idx] = item.value.Export()
+		}
+	}
+	return arr
+}
+
+func (a *sparseArrayObject) exportType() reflect.Type {
+	return reflectTypeArray
+}

+ 43 - 0
array_sparse_test.go

@@ -0,0 +1,43 @@
+package goja
+
+import "testing"
+
+func TestSparseArraySetLengthWithPropItems(t *testing.T) {
+	const SCRIPT = `
+	var a = [1,2,3,4];
+	a[100000] = 5;
+	var thrown = false;
+
+	Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
+	try {
+		Object.defineProperty(a, "length", {value: 0, writable: false});
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+	thrown && a.length === 3;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestSparseArraySwitch(t *testing.T) {
+	const SCRIPT = `
+	var a = [];
+	a[20470] = 5; // switch to sparse
+	for (var i = a.length - 1; i >= 0; i--) {
+		a[i] = i; // switch to normal at some point
+	}
+
+	if (a.length != 20471) {
+		throw new Error("Invalid length: " + a.length);
+	}
+
+	for (var i = 0; i < a.length; i++) {
+		if (a[i] !== i) {
+			throw new Error("Invalid value at " + i + ": " + a[i]);
+		}
+	}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}

+ 1068 - 0
ast/README.markdown

@@ -0,0 +1,1068 @@
+# ast
+--
+    import "github.com/robertkrimen/otto/ast"
+
+Package ast declares types representing a JavaScript AST.
+
+
+### Warning
+
+The parser and AST interfaces are still works-in-progress (particularly where
+node types are concerned) and may change in the future.
+
+## Usage
+
+#### type ArrayLiteral
+
+```go
+type ArrayLiteral struct {
+	LeftBracket  file.Idx
+	RightBracket file.Idx
+	Value        []Expression
+}
+```
+
+
+#### func (*ArrayLiteral) Idx0
+
+```go
+func (self *ArrayLiteral) Idx0() file.Idx
+```
+
+#### func (*ArrayLiteral) Idx1
+
+```go
+func (self *ArrayLiteral) Idx1() file.Idx
+```
+
+#### type AssignExpression
+
+```go
+type AssignExpression struct {
+	Operator token.Token
+	Left     Expression
+	Right    Expression
+}
+```
+
+
+#### func (*AssignExpression) Idx0
+
+```go
+func (self *AssignExpression) Idx0() file.Idx
+```
+
+#### func (*AssignExpression) Idx1
+
+```go
+func (self *AssignExpression) Idx1() file.Idx
+```
+
+#### type BadExpression
+
+```go
+type BadExpression struct {
+	From file.Idx
+	To   file.Idx
+}
+```
+
+
+#### func (*BadExpression) Idx0
+
+```go
+func (self *BadExpression) Idx0() file.Idx
+```
+
+#### func (*BadExpression) Idx1
+
+```go
+func (self *BadExpression) Idx1() file.Idx
+```
+
+#### type BadStatement
+
+```go
+type BadStatement struct {
+	From file.Idx
+	To   file.Idx
+}
+```
+
+
+#### func (*BadStatement) Idx0
+
+```go
+func (self *BadStatement) Idx0() file.Idx
+```
+
+#### func (*BadStatement) Idx1
+
+```go
+func (self *BadStatement) Idx1() file.Idx
+```
+
+#### type BinaryExpression
+
+```go
+type BinaryExpression struct {
+	Operator   token.Token
+	Left       Expression
+	Right      Expression
+	Comparison bool
+}
+```
+
+
+#### func (*BinaryExpression) Idx0
+
+```go
+func (self *BinaryExpression) Idx0() file.Idx
+```
+
+#### func (*BinaryExpression) Idx1
+
+```go
+func (self *BinaryExpression) Idx1() file.Idx
+```
+
+#### type BlockStatement
+
+```go
+type BlockStatement struct {
+	LeftBrace  file.Idx
+	List       []Statement
+	RightBrace file.Idx
+}
+```
+
+
+#### func (*BlockStatement) Idx0
+
+```go
+func (self *BlockStatement) Idx0() file.Idx
+```
+
+#### func (*BlockStatement) Idx1
+
+```go
+func (self *BlockStatement) Idx1() file.Idx
+```
+
+#### type BooleanLiteral
+
+```go
+type BooleanLiteral struct {
+	Idx     file.Idx
+	Literal string
+	Value   bool
+}
+```
+
+
+#### func (*BooleanLiteral) Idx0
+
+```go
+func (self *BooleanLiteral) Idx0() file.Idx
+```
+
+#### func (*BooleanLiteral) Idx1
+
+```go
+func (self *BooleanLiteral) Idx1() file.Idx
+```
+
+#### type BracketExpression
+
+```go
+type BracketExpression struct {
+	Left         Expression
+	Member       Expression
+	LeftBracket  file.Idx
+	RightBracket file.Idx
+}
+```
+
+
+#### func (*BracketExpression) Idx0
+
+```go
+func (self *BracketExpression) Idx0() file.Idx
+```
+
+#### func (*BracketExpression) Idx1
+
+```go
+func (self *BracketExpression) Idx1() file.Idx
+```
+
+#### type BranchStatement
+
+```go
+type BranchStatement struct {
+	Idx   file.Idx
+	Token token.Token
+	Label *Identifier
+}
+```
+
+
+#### func (*BranchStatement) Idx0
+
+```go
+func (self *BranchStatement) Idx0() file.Idx
+```
+
+#### func (*BranchStatement) Idx1
+
+```go
+func (self *BranchStatement) Idx1() file.Idx
+```
+
+#### type CallExpression
+
+```go
+type CallExpression struct {
+	Callee           Expression
+	LeftParenthesis  file.Idx
+	ArgumentList     []Expression
+	RightParenthesis file.Idx
+}
+```
+
+
+#### func (*CallExpression) Idx0
+
+```go
+func (self *CallExpression) Idx0() file.Idx
+```
+
+#### func (*CallExpression) Idx1
+
+```go
+func (self *CallExpression) Idx1() file.Idx
+```
+
+#### type CaseStatement
+
+```go
+type CaseStatement struct {
+	Case       file.Idx
+	Test       Expression
+	Consequent []Statement
+}
+```
+
+
+#### func (*CaseStatement) Idx0
+
+```go
+func (self *CaseStatement) Idx0() file.Idx
+```
+
+#### func (*CaseStatement) Idx1
+
+```go
+func (self *CaseStatement) Idx1() file.Idx
+```
+
+#### type CatchStatement
+
+```go
+type CatchStatement struct {
+	Catch     file.Idx
+	Parameter *Identifier
+	Body      Statement
+}
+```
+
+
+#### func (*CatchStatement) Idx0
+
+```go
+func (self *CatchStatement) Idx0() file.Idx
+```
+
+#### func (*CatchStatement) Idx1
+
+```go
+func (self *CatchStatement) Idx1() file.Idx
+```
+
+#### type ConditionalExpression
+
+```go
+type ConditionalExpression struct {
+	Test       Expression
+	Consequent Expression
+	Alternate  Expression
+}
+```
+
+
+#### func (*ConditionalExpression) Idx0
+
+```go
+func (self *ConditionalExpression) Idx0() file.Idx
+```
+
+#### func (*ConditionalExpression) Idx1
+
+```go
+func (self *ConditionalExpression) Idx1() file.Idx
+```
+
+#### type DebuggerStatement
+
+```go
+type DebuggerStatement struct {
+	Debugger file.Idx
+}
+```
+
+
+#### func (*DebuggerStatement) Idx0
+
+```go
+func (self *DebuggerStatement) Idx0() file.Idx
+```
+
+#### func (*DebuggerStatement) Idx1
+
+```go
+func (self *DebuggerStatement) Idx1() file.Idx
+```
+
+#### type Declaration
+
+```go
+type Declaration interface {
+	// contains filtered or unexported methods
+}
+```
+
+All declaration nodes implement the Declaration interface.
+
+#### type DoWhileStatement
+
+```go
+type DoWhileStatement struct {
+	Do   file.Idx
+	Test Expression
+	Body Statement
+}
+```
+
+
+#### func (*DoWhileStatement) Idx0
+
+```go
+func (self *DoWhileStatement) Idx0() file.Idx
+```
+
+#### func (*DoWhileStatement) Idx1
+
+```go
+func (self *DoWhileStatement) Idx1() file.Idx
+```
+
+#### type DotExpression
+
+```go
+type DotExpression struct {
+	Left       Expression
+	Identifier Identifier
+}
+```
+
+
+#### func (*DotExpression) Idx0
+
+```go
+func (self *DotExpression) Idx0() file.Idx
+```
+
+#### func (*DotExpression) Idx1
+
+```go
+func (self *DotExpression) Idx1() file.Idx
+```
+
+#### type EmptyStatement
+
+```go
+type EmptyStatement struct {
+	Semicolon file.Idx
+}
+```
+
+
+#### func (*EmptyStatement) Idx0
+
+```go
+func (self *EmptyStatement) Idx0() file.Idx
+```
+
+#### func (*EmptyStatement) Idx1
+
+```go
+func (self *EmptyStatement) Idx1() file.Idx
+```
+
+#### type Expression
+
+```go
+type Expression interface {
+	Node
+	// contains filtered or unexported methods
+}
+```
+
+All expression nodes implement the Expression interface.
+
+#### type ExpressionStatement
+
+```go
+type ExpressionStatement struct {
+	Expression Expression
+}
+```
+
+
+#### func (*ExpressionStatement) Idx0
+
+```go
+func (self *ExpressionStatement) Idx0() file.Idx
+```
+
+#### func (*ExpressionStatement) Idx1
+
+```go
+func (self *ExpressionStatement) Idx1() file.Idx
+```
+
+#### type ForInStatement
+
+```go
+type ForInStatement struct {
+	For    file.Idx
+	Into   Expression
+	Source Expression
+	Body   Statement
+}
+```
+
+
+#### func (*ForInStatement) Idx0
+
+```go
+func (self *ForInStatement) Idx0() file.Idx
+```
+
+#### func (*ForInStatement) Idx1
+
+```go
+func (self *ForInStatement) Idx1() file.Idx
+```
+
+#### type ForStatement
+
+```go
+type ForStatement struct {
+	For         file.Idx
+	Initializer Expression
+	Update      Expression
+	Test        Expression
+	Body        Statement
+}
+```
+
+
+#### func (*ForStatement) Idx0
+
+```go
+func (self *ForStatement) Idx0() file.Idx
+```
+
+#### func (*ForStatement) Idx1
+
+```go
+func (self *ForStatement) Idx1() file.Idx
+```
+
+#### type FunctionDeclaration
+
+```go
+type FunctionDeclaration struct {
+	Function *FunctionLiteral
+}
+```
+
+
+#### type FunctionLiteral
+
+```go
+type FunctionLiteral struct {
+	Function      file.Idx
+	Name          *Identifier
+	ParameterList *ParameterList
+	Body          Statement
+	Source        string
+
+	DeclarationList []Declaration
+}
+```
+
+
+#### func (*FunctionLiteral) Idx0
+
+```go
+func (self *FunctionLiteral) Idx0() file.Idx
+```
+
+#### func (*FunctionLiteral) Idx1
+
+```go
+func (self *FunctionLiteral) Idx1() file.Idx
+```
+
+#### type Identifier
+
+```go
+type Identifier struct {
+	Name string
+	Idx  file.Idx
+}
+```
+
+
+#### func (*Identifier) Idx0
+
+```go
+func (self *Identifier) Idx0() file.Idx
+```
+
+#### func (*Identifier) Idx1
+
+```go
+func (self *Identifier) Idx1() file.Idx
+```
+
+#### type IfStatement
+
+```go
+type IfStatement struct {
+	If         file.Idx
+	Test       Expression
+	Consequent Statement
+	Alternate  Statement
+}
+```
+
+
+#### func (*IfStatement) Idx0
+
+```go
+func (self *IfStatement) Idx0() file.Idx
+```
+
+#### func (*IfStatement) Idx1
+
+```go
+func (self *IfStatement) Idx1() file.Idx
+```
+
+#### type LabelledStatement
+
+```go
+type LabelledStatement struct {
+	Label     *Identifier
+	Colon     file.Idx
+	Statement Statement
+}
+```
+
+
+#### func (*LabelledStatement) Idx0
+
+```go
+func (self *LabelledStatement) Idx0() file.Idx
+```
+
+#### func (*LabelledStatement) Idx1
+
+```go
+func (self *LabelledStatement) Idx1() file.Idx
+```
+
+#### type NewExpression
+
+```go
+type NewExpression struct {
+	New              file.Idx
+	Callee           Expression
+	LeftParenthesis  file.Idx
+	ArgumentList     []Expression
+	RightParenthesis file.Idx
+}
+```
+
+
+#### func (*NewExpression) Idx0
+
+```go
+func (self *NewExpression) Idx0() file.Idx
+```
+
+#### func (*NewExpression) Idx1
+
+```go
+func (self *NewExpression) Idx1() file.Idx
+```
+
+#### type Node
+
+```go
+type Node interface {
+	Idx0() file.Idx // The index of the first character belonging to the node
+	Idx1() file.Idx // The index of the first character immediately after the node
+}
+```
+
+All nodes implement the Node interface.
+
+#### type NullLiteral
+
+```go
+type NullLiteral struct {
+	Idx     file.Idx
+	Literal string
+}
+```
+
+
+#### func (*NullLiteral) Idx0
+
+```go
+func (self *NullLiteral) Idx0() file.Idx
+```
+
+#### func (*NullLiteral) Idx1
+
+```go
+func (self *NullLiteral) Idx1() file.Idx
+```
+
+#### type NumberLiteral
+
+```go
+type NumberLiteral struct {
+	Idx     file.Idx
+	Literal string
+	Value   interface{}
+}
+```
+
+
+#### func (*NumberLiteral) Idx0
+
+```go
+func (self *NumberLiteral) Idx0() file.Idx
+```
+
+#### func (*NumberLiteral) Idx1
+
+```go
+func (self *NumberLiteral) Idx1() file.Idx
+```
+
+#### type ObjectLiteral
+
+```go
+type ObjectLiteral struct {
+	LeftBrace  file.Idx
+	RightBrace file.Idx
+	Value      []Property
+}
+```
+
+
+#### func (*ObjectLiteral) Idx0
+
+```go
+func (self *ObjectLiteral) Idx0() file.Idx
+```
+
+#### func (*ObjectLiteral) Idx1
+
+```go
+func (self *ObjectLiteral) Idx1() file.Idx
+```
+
+#### type ParameterList
+
+```go
+type ParameterList struct {
+	Opening file.Idx
+	List    []*Identifier
+	Closing file.Idx
+}
+```
+
+
+#### type Program
+
+```go
+type Program struct {
+	Body []Statement
+
+	DeclarationList []Declaration
+
+	File *file.File
+}
+```
+
+
+#### func (*Program) Idx0
+
+```go
+func (self *Program) Idx0() file.Idx
+```
+
+#### func (*Program) Idx1
+
+```go
+func (self *Program) Idx1() file.Idx
+```
+
+#### type Property
+
+```go
+type Property struct {
+	Key   string
+	Kind  string
+	Value Expression
+}
+```
+
+
+#### type RegExpLiteral
+
+```go
+type RegExpLiteral struct {
+	Idx     file.Idx
+	Literal string
+	Pattern string
+	Flags   string
+	Value   string
+}
+```
+
+
+#### func (*RegExpLiteral) Idx0
+
+```go
+func (self *RegExpLiteral) Idx0() file.Idx
+```
+
+#### func (*RegExpLiteral) Idx1
+
+```go
+func (self *RegExpLiteral) Idx1() file.Idx
+```
+
+#### type ReturnStatement
+
+```go
+type ReturnStatement struct {
+	Return   file.Idx
+	Argument Expression
+}
+```
+
+
+#### func (*ReturnStatement) Idx0
+
+```go
+func (self *ReturnStatement) Idx0() file.Idx
+```
+
+#### func (*ReturnStatement) Idx1
+
+```go
+func (self *ReturnStatement) Idx1() file.Idx
+```
+
+#### type SequenceExpression
+
+```go
+type SequenceExpression struct {
+	Sequence []Expression
+}
+```
+
+
+#### func (*SequenceExpression) Idx0
+
+```go
+func (self *SequenceExpression) Idx0() file.Idx
+```
+
+#### func (*SequenceExpression) Idx1
+
+```go
+func (self *SequenceExpression) Idx1() file.Idx
+```
+
+#### type Statement
+
+```go
+type Statement interface {
+	Node
+	// contains filtered or unexported methods
+}
+```
+
+All statement nodes implement the Statement interface.
+
+#### type StringLiteral
+
+```go
+type StringLiteral struct {
+	Idx     file.Idx
+	Literal string
+	Value   string
+}
+```
+
+
+#### func (*StringLiteral) Idx0
+
+```go
+func (self *StringLiteral) Idx0() file.Idx
+```
+
+#### func (*StringLiteral) Idx1
+
+```go
+func (self *StringLiteral) Idx1() file.Idx
+```
+
+#### type SwitchStatement
+
+```go
+type SwitchStatement struct {
+	Switch       file.Idx
+	Discriminant Expression
+	Default      int
+	Body         []*CaseStatement
+}
+```
+
+
+#### func (*SwitchStatement) Idx0
+
+```go
+func (self *SwitchStatement) Idx0() file.Idx
+```
+
+#### func (*SwitchStatement) Idx1
+
+```go
+func (self *SwitchStatement) Idx1() file.Idx
+```
+
+#### type ThisExpression
+
+```go
+type ThisExpression struct {
+	Idx file.Idx
+}
+```
+
+
+#### func (*ThisExpression) Idx0
+
+```go
+func (self *ThisExpression) Idx0() file.Idx
+```
+
+#### func (*ThisExpression) Idx1
+
+```go
+func (self *ThisExpression) Idx1() file.Idx
+```
+
+#### type ThrowStatement
+
+```go
+type ThrowStatement struct {
+	Throw    file.Idx
+	Argument Expression
+}
+```
+
+
+#### func (*ThrowStatement) Idx0
+
+```go
+func (self *ThrowStatement) Idx0() file.Idx
+```
+
+#### func (*ThrowStatement) Idx1
+
+```go
+func (self *ThrowStatement) Idx1() file.Idx
+```
+
+#### type TryStatement
+
+```go
+type TryStatement struct {
+	Try     file.Idx
+	Body    Statement
+	Catch   *CatchStatement
+	Finally Statement
+}
+```
+
+
+#### func (*TryStatement) Idx0
+
+```go
+func (self *TryStatement) Idx0() file.Idx
+```
+
+#### func (*TryStatement) Idx1
+
+```go
+func (self *TryStatement) Idx1() file.Idx
+```
+
+#### type UnaryExpression
+
+```go
+type UnaryExpression struct {
+	Operator token.Token
+	Idx      file.Idx // If a prefix operation
+	Operand  Expression
+	Postfix  bool
+}
+```
+
+
+#### func (*UnaryExpression) Idx0
+
+```go
+func (self *UnaryExpression) Idx0() file.Idx
+```
+
+#### func (*UnaryExpression) Idx1
+
+```go
+func (self *UnaryExpression) Idx1() file.Idx
+```
+
+#### type VariableDeclaration
+
+```go
+type VariableDeclaration struct {
+	Var  file.Idx
+	List []*VariableExpression
+}
+```
+
+
+#### type VariableExpression
+
+```go
+type VariableExpression struct {
+	Name        string
+	Idx         file.Idx
+	Initializer Expression
+}
+```
+
+
+#### func (*VariableExpression) Idx0
+
+```go
+func (self *VariableExpression) Idx0() file.Idx
+```
+
+#### func (*VariableExpression) Idx1
+
+```go
+func (self *VariableExpression) Idx1() file.Idx
+```
+
+#### type VariableStatement
+
+```go
+type VariableStatement struct {
+	Var  file.Idx
+	List []Expression
+}
+```
+
+
+#### func (*VariableStatement) Idx0
+
+```go
+func (self *VariableStatement) Idx0() file.Idx
+```
+
+#### func (*VariableStatement) Idx1
+
+```go
+func (self *VariableStatement) Idx1() file.Idx
+```
+
+#### type WhileStatement
+
+```go
+type WhileStatement struct {
+	While file.Idx
+	Test  Expression
+	Body  Statement
+}
+```
+
+
+#### func (*WhileStatement) Idx0
+
+```go
+func (self *WhileStatement) Idx0() file.Idx
+```
+
+#### func (*WhileStatement) Idx1
+
+```go
+func (self *WhileStatement) Idx1() file.Idx
+```
+
+#### type WithStatement
+
+```go
+type WithStatement struct {
+	With   file.Idx
+	Object Expression
+	Body   Statement
+}
+```
+
+
+#### func (*WithStatement) Idx0
+
+```go
+func (self *WithStatement) Idx0() file.Idx
+```
+
+#### func (*WithStatement) Idx1
+
+```go
+func (self *WithStatement) Idx1() file.Idx
+```
+
+--
+**godocdown** http://github.com/robertkrimen/godocdown

+ 497 - 0
ast/node.go

@@ -0,0 +1,497 @@
+/*
+Package ast declares types representing a JavaScript AST.
+
+Warning
+
+The parser and AST interfaces are still works-in-progress (particularly where
+node types are concerned) and may change in the future.
+
+*/
+package ast
+
+import (
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+)
+
+// All nodes implement the Node interface.
+type Node interface {
+	Idx0() file.Idx // The index of the first character belonging to the node
+	Idx1() file.Idx // The index of the first character immediately after the node
+}
+
+// ========== //
+// Expression //
+// ========== //
+
+type (
+	// All expression nodes implement the Expression interface.
+	Expression interface {
+		Node
+		_expressionNode()
+	}
+
+	ArrayLiteral struct {
+		LeftBracket  file.Idx
+		RightBracket file.Idx
+		Value        []Expression
+	}
+
+	AssignExpression struct {
+		Operator token.Token
+		Left     Expression
+		Right    Expression
+	}
+
+	BadExpression struct {
+		From file.Idx
+		To   file.Idx
+	}
+
+	BinaryExpression struct {
+		Operator   token.Token
+		Left       Expression
+		Right      Expression
+		Comparison bool
+	}
+
+	BooleanLiteral struct {
+		Idx     file.Idx
+		Literal string
+		Value   bool
+	}
+
+	BracketExpression struct {
+		Left         Expression
+		Member       Expression
+		LeftBracket  file.Idx
+		RightBracket file.Idx
+	}
+
+	CallExpression struct {
+		Callee           Expression
+		LeftParenthesis  file.Idx
+		ArgumentList     []Expression
+		RightParenthesis file.Idx
+	}
+
+	ConditionalExpression struct {
+		Test       Expression
+		Consequent Expression
+		Alternate  Expression
+	}
+
+	DotExpression struct {
+		Left       Expression
+		Identifier Identifier
+	}
+
+	FunctionLiteral struct {
+		Function      file.Idx
+		Name          *Identifier
+		ParameterList *ParameterList
+		Body          Statement
+		Source        string
+
+		DeclarationList []Declaration
+	}
+
+	Identifier struct {
+		Name string
+		Idx  file.Idx
+	}
+
+	NewExpression struct {
+		New              file.Idx
+		Callee           Expression
+		LeftParenthesis  file.Idx
+		ArgumentList     []Expression
+		RightParenthesis file.Idx
+	}
+
+	NullLiteral struct {
+		Idx     file.Idx
+		Literal string
+	}
+
+	NumberLiteral struct {
+		Idx     file.Idx
+		Literal string
+		Value   interface{}
+	}
+
+	ObjectLiteral struct {
+		LeftBrace  file.Idx
+		RightBrace file.Idx
+		Value      []Property
+	}
+
+	ParameterList struct {
+		Opening file.Idx
+		List    []*Identifier
+		Closing file.Idx
+	}
+
+	Property struct {
+		Key   string
+		Kind  string
+		Value Expression
+	}
+
+	RegExpLiteral struct {
+		Idx     file.Idx
+		Literal string
+		Pattern string
+		Flags   string
+	}
+
+	SequenceExpression struct {
+		Sequence []Expression
+	}
+
+	StringLiteral struct {
+		Idx     file.Idx
+		Literal string
+		Value   string
+	}
+
+	ThisExpression struct {
+		Idx file.Idx
+	}
+
+	UnaryExpression struct {
+		Operator token.Token
+		Idx      file.Idx // If a prefix operation
+		Operand  Expression
+		Postfix  bool
+	}
+
+	VariableExpression struct {
+		Name        string
+		Idx         file.Idx
+		Initializer Expression
+	}
+)
+
+// _expressionNode
+
+func (*ArrayLiteral) _expressionNode()          {}
+func (*AssignExpression) _expressionNode()      {}
+func (*BadExpression) _expressionNode()         {}
+func (*BinaryExpression) _expressionNode()      {}
+func (*BooleanLiteral) _expressionNode()        {}
+func (*BracketExpression) _expressionNode()     {}
+func (*CallExpression) _expressionNode()        {}
+func (*ConditionalExpression) _expressionNode() {}
+func (*DotExpression) _expressionNode()         {}
+func (*FunctionLiteral) _expressionNode()       {}
+func (*Identifier) _expressionNode()            {}
+func (*NewExpression) _expressionNode()         {}
+func (*NullLiteral) _expressionNode()           {}
+func (*NumberLiteral) _expressionNode()         {}
+func (*ObjectLiteral) _expressionNode()         {}
+func (*RegExpLiteral) _expressionNode()         {}
+func (*SequenceExpression) _expressionNode()    {}
+func (*StringLiteral) _expressionNode()         {}
+func (*ThisExpression) _expressionNode()        {}
+func (*UnaryExpression) _expressionNode()       {}
+func (*VariableExpression) _expressionNode()    {}
+
+// ========= //
+// Statement //
+// ========= //
+
+type (
+	// All statement nodes implement the Statement interface.
+	Statement interface {
+		Node
+		_statementNode()
+	}
+
+	BadStatement struct {
+		From file.Idx
+		To   file.Idx
+	}
+
+	BlockStatement struct {
+		LeftBrace  file.Idx
+		List       []Statement
+		RightBrace file.Idx
+	}
+
+	BranchStatement struct {
+		Idx   file.Idx
+		Token token.Token
+		Label *Identifier
+	}
+
+	CaseStatement struct {
+		Case       file.Idx
+		Test       Expression
+		Consequent []Statement
+	}
+
+	CatchStatement struct {
+		Catch     file.Idx
+		Parameter *Identifier
+		Body      Statement
+	}
+
+	DebuggerStatement struct {
+		Debugger file.Idx
+	}
+
+	DoWhileStatement struct {
+		Do   file.Idx
+		Test Expression
+		Body Statement
+	}
+
+	EmptyStatement struct {
+		Semicolon file.Idx
+	}
+
+	ExpressionStatement struct {
+		Expression Expression
+	}
+
+	ForInStatement struct {
+		For    file.Idx
+		Into   Expression
+		Source Expression
+		Body   Statement
+	}
+
+	ForStatement struct {
+		For         file.Idx
+		Initializer Expression
+		Update      Expression
+		Test        Expression
+		Body        Statement
+	}
+
+	IfStatement struct {
+		If         file.Idx
+		Test       Expression
+		Consequent Statement
+		Alternate  Statement
+	}
+
+	LabelledStatement struct {
+		Label     *Identifier
+		Colon     file.Idx
+		Statement Statement
+	}
+
+	ReturnStatement struct {
+		Return   file.Idx
+		Argument Expression
+	}
+
+	SwitchStatement struct {
+		Switch       file.Idx
+		Discriminant Expression
+		Default      int
+		Body         []*CaseStatement
+	}
+
+	ThrowStatement struct {
+		Throw    file.Idx
+		Argument Expression
+	}
+
+	TryStatement struct {
+		Try     file.Idx
+		Body    Statement
+		Catch   *CatchStatement
+		Finally Statement
+	}
+
+	VariableStatement struct {
+		Var  file.Idx
+		List []Expression
+	}
+
+	WhileStatement struct {
+		While file.Idx
+		Test  Expression
+		Body  Statement
+	}
+
+	WithStatement struct {
+		With   file.Idx
+		Object Expression
+		Body   Statement
+	}
+)
+
+// _statementNode
+
+func (*BadStatement) _statementNode()        {}
+func (*BlockStatement) _statementNode()      {}
+func (*BranchStatement) _statementNode()     {}
+func (*CaseStatement) _statementNode()       {}
+func (*CatchStatement) _statementNode()      {}
+func (*DebuggerStatement) _statementNode()   {}
+func (*DoWhileStatement) _statementNode()    {}
+func (*EmptyStatement) _statementNode()      {}
+func (*ExpressionStatement) _statementNode() {}
+func (*ForInStatement) _statementNode()      {}
+func (*ForStatement) _statementNode()        {}
+func (*IfStatement) _statementNode()         {}
+func (*LabelledStatement) _statementNode()   {}
+func (*ReturnStatement) _statementNode()     {}
+func (*SwitchStatement) _statementNode()     {}
+func (*ThrowStatement) _statementNode()      {}
+func (*TryStatement) _statementNode()        {}
+func (*VariableStatement) _statementNode()   {}
+func (*WhileStatement) _statementNode()      {}
+func (*WithStatement) _statementNode()       {}
+
+// =========== //
+// Declaration //
+// =========== //
+
+type (
+	// All declaration nodes implement the Declaration interface.
+	Declaration interface {
+		_declarationNode()
+	}
+
+	FunctionDeclaration struct {
+		Function *FunctionLiteral
+	}
+
+	VariableDeclaration struct {
+		Var  file.Idx
+		List []*VariableExpression
+	}
+)
+
+// _declarationNode
+
+func (*FunctionDeclaration) _declarationNode() {}
+func (*VariableDeclaration) _declarationNode() {}
+
+// ==== //
+// Node //
+// ==== //
+
+type Program struct {
+	Body []Statement
+
+	DeclarationList []Declaration
+
+	File *file.File
+}
+
+// ==== //
+// Idx0 //
+// ==== //
+
+func (self *ArrayLiteral) Idx0() file.Idx          { return self.LeftBracket }
+func (self *AssignExpression) Idx0() file.Idx      { return self.Left.Idx0() }
+func (self *BadExpression) Idx0() file.Idx         { return self.From }
+func (self *BinaryExpression) Idx0() file.Idx      { return self.Left.Idx0() }
+func (self *BooleanLiteral) Idx0() file.Idx        { return self.Idx }
+func (self *BracketExpression) Idx0() file.Idx     { return self.Left.Idx0() }
+func (self *CallExpression) Idx0() file.Idx        { return self.Callee.Idx0() }
+func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() }
+func (self *DotExpression) Idx0() file.Idx         { return self.Left.Idx0() }
+func (self *FunctionLiteral) Idx0() file.Idx       { return self.Function }
+func (self *Identifier) Idx0() file.Idx            { return self.Idx }
+func (self *NewExpression) Idx0() file.Idx         { return self.New }
+func (self *NullLiteral) Idx0() file.Idx           { return self.Idx }
+func (self *NumberLiteral) Idx0() file.Idx         { return self.Idx }
+func (self *ObjectLiteral) Idx0() file.Idx         { return self.LeftBrace }
+func (self *RegExpLiteral) Idx0() file.Idx         { return self.Idx }
+func (self *SequenceExpression) Idx0() file.Idx    { return self.Sequence[0].Idx0() }
+func (self *StringLiteral) Idx0() file.Idx         { return self.Idx }
+func (self *ThisExpression) Idx0() file.Idx        { return self.Idx }
+func (self *UnaryExpression) Idx0() file.Idx       { return self.Idx }
+func (self *VariableExpression) Idx0() file.Idx    { return self.Idx }
+
+func (self *BadStatement) Idx0() file.Idx        { return self.From }
+func (self *BlockStatement) Idx0() file.Idx      { return self.LeftBrace }
+func (self *BranchStatement) Idx0() file.Idx     { return self.Idx }
+func (self *CaseStatement) Idx0() file.Idx       { return self.Case }
+func (self *CatchStatement) Idx0() file.Idx      { return self.Catch }
+func (self *DebuggerStatement) Idx0() file.Idx   { return self.Debugger }
+func (self *DoWhileStatement) Idx0() file.Idx    { return self.Do }
+func (self *EmptyStatement) Idx0() file.Idx      { return self.Semicolon }
+func (self *ExpressionStatement) Idx0() file.Idx { return self.Expression.Idx0() }
+func (self *ForInStatement) Idx0() file.Idx      { return self.For }
+func (self *ForStatement) Idx0() file.Idx        { return self.For }
+func (self *IfStatement) Idx0() file.Idx         { return self.If }
+func (self *LabelledStatement) Idx0() file.Idx   { return self.Label.Idx0() }
+func (self *Program) Idx0() file.Idx             { return self.Body[0].Idx0() }
+func (self *ReturnStatement) Idx0() file.Idx     { return self.Return }
+func (self *SwitchStatement) Idx0() file.Idx     { return self.Switch }
+func (self *ThrowStatement) Idx0() file.Idx      { return self.Throw }
+func (self *TryStatement) Idx0() file.Idx        { return self.Try }
+func (self *VariableStatement) Idx0() file.Idx   { return self.Var }
+func (self *WhileStatement) Idx0() file.Idx      { return self.While }
+func (self *WithStatement) Idx0() file.Idx       { return self.With }
+
+// ==== //
+// Idx1 //
+// ==== //
+
+func (self *ArrayLiteral) Idx1() file.Idx          { return self.RightBracket }
+func (self *AssignExpression) Idx1() file.Idx      { return self.Right.Idx1() }
+func (self *BadExpression) Idx1() file.Idx         { return self.To }
+func (self *BinaryExpression) Idx1() file.Idx      { return self.Right.Idx1() }
+func (self *BooleanLiteral) Idx1() file.Idx        { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *BracketExpression) Idx1() file.Idx     { return self.RightBracket + 1 }
+func (self *CallExpression) Idx1() file.Idx        { return self.RightParenthesis + 1 }
+func (self *ConditionalExpression) Idx1() file.Idx { return self.Test.Idx1() }
+func (self *DotExpression) Idx1() file.Idx         { return self.Identifier.Idx1() }
+func (self *FunctionLiteral) Idx1() file.Idx       { return self.Body.Idx1() }
+func (self *Identifier) Idx1() file.Idx            { return file.Idx(int(self.Idx) + len(self.Name)) }
+func (self *NewExpression) Idx1() file.Idx         { return self.RightParenthesis + 1 }
+func (self *NullLiteral) Idx1() file.Idx           { return file.Idx(int(self.Idx) + 4) } // "null"
+func (self *NumberLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *ObjectLiteral) Idx1() file.Idx         { return self.RightBrace }
+func (self *RegExpLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *SequenceExpression) Idx1() file.Idx    { return self.Sequence[0].Idx1() }
+func (self *StringLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
+func (self *ThisExpression) Idx1() file.Idx        { return self.Idx }
+func (self *UnaryExpression) Idx1() file.Idx {
+	if self.Postfix {
+		return self.Operand.Idx1() + 2 // ++ --
+	}
+	return self.Operand.Idx1()
+}
+func (self *VariableExpression) Idx1() file.Idx {
+	if self.Initializer == nil {
+		return file.Idx(int(self.Idx) + len(self.Name) + 1)
+	}
+	return self.Initializer.Idx1()
+}
+
+func (self *BadStatement) Idx1() file.Idx        { return self.To }
+func (self *BlockStatement) Idx1() file.Idx      { return self.RightBrace + 1 }
+func (self *BranchStatement) Idx1() file.Idx     { return self.Idx }
+func (self *CaseStatement) Idx1() file.Idx       { return self.Consequent[len(self.Consequent)-1].Idx1() }
+func (self *CatchStatement) Idx1() file.Idx      { return self.Body.Idx1() }
+func (self *DebuggerStatement) Idx1() file.Idx   { return self.Debugger + 8 }
+func (self *DoWhileStatement) Idx1() file.Idx    { return self.Test.Idx1() }
+func (self *EmptyStatement) Idx1() file.Idx      { return self.Semicolon + 1 }
+func (self *ExpressionStatement) Idx1() file.Idx { return self.Expression.Idx1() }
+func (self *ForInStatement) Idx1() file.Idx      { return self.Body.Idx1() }
+func (self *ForStatement) Idx1() file.Idx        { return self.Body.Idx1() }
+func (self *IfStatement) Idx1() file.Idx {
+	if self.Alternate != nil {
+		return self.Alternate.Idx1()
+	}
+	return self.Consequent.Idx1()
+}
+func (self *LabelledStatement) Idx1() file.Idx { return self.Colon + 1 }
+func (self *Program) Idx1() file.Idx           { return self.Body[len(self.Body)-1].Idx1() }
+func (self *ReturnStatement) Idx1() file.Idx   { return self.Return }
+func (self *SwitchStatement) Idx1() file.Idx   { return self.Body[len(self.Body)-1].Idx1() }
+func (self *ThrowStatement) Idx1() file.Idx    { return self.Throw }
+func (self *TryStatement) Idx1() file.Idx      { return self.Try }
+func (self *VariableStatement) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
+func (self *WhileStatement) Idx1() file.Idx    { return self.Body.Idx1() }
+func (self *WithStatement) Idx1() file.Idx     { return self.Body.Idx1() }

+ 879 - 0
builtin_array.go

@@ -0,0 +1,879 @@
+package goja
+
+import (
+	"bytes"
+	"sort"
+	"strings"
+)
+
+func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object {
+	l := len(args)
+	if l == 1 {
+		if al, ok := args[0].assertInt(); ok {
+			return r.newArrayLength(al)
+		} else if f, ok := args[0].assertFloat(); ok {
+			al := int64(f)
+			if float64(al) == f {
+				return r.newArrayLength(al)
+			} else {
+				panic(r.newError(r.global.RangeError, "Invalid array length"))
+			}
+		}
+		return r.newArrayValues([]Value{args[0]})
+	} else {
+		argsCopy := make([]Value, l)
+		copy(argsCopy, args)
+		return r.newArrayValues(argsCopy)
+	}
+}
+
+func (r *Runtime) generic_push(obj *Object, call FunctionCall) Value {
+	l := toLength(obj.self.getStr("length"))
+	nl := l + int64(len(call.Arguments))
+	if nl >= maxInt {
+		r.typeErrorResult(true, "Invalid array length")
+		panic("unreachable")
+	}
+	for i, arg := range call.Arguments {
+		obj.self.put(intToValue(l+int64(i)), arg, true)
+	}
+	n := intToValue(nl)
+	obj.self.putStr("length", n, true)
+	return n
+}
+
+func (r *Runtime) arrayproto_push(call FunctionCall) Value {
+	obj := call.This.ToObject(r)
+	return r.generic_push(obj, call)
+}
+
+func (r *Runtime) arrayproto_pop_generic(obj *Object, call FunctionCall) Value {
+	l := int(toLength(obj.self.getStr("length")))
+	if l == 0 {
+		obj.self.putStr("length", intToValue(0), true)
+		return _undefined
+	}
+	idx := intToValue(int64(l - 1))
+	val := obj.self.get(idx)
+	obj.self.delete(idx, true)
+	obj.self.putStr("length", idx, true)
+	return val
+}
+
+func (r *Runtime) arrayproto_pop(call FunctionCall) Value {
+	obj := call.This.ToObject(r)
+	if a, ok := obj.self.(*arrayObject); ok {
+		l := a.length
+		if l > 0 {
+			var val Value
+			l--
+			if l < len(a.values) {
+				val = a.values[l]
+			}
+			if val == nil {
+				// optimisation bail-out
+				return r.arrayproto_pop_generic(obj, call)
+			}
+			if _, ok := val.(*valueProperty); ok {
+				// optimisation bail-out
+				return r.arrayproto_pop_generic(obj, call)
+			}
+			//a._setLengthInt(l, false)
+			a.values[l] = nil
+			a.values = a.values[:l]
+			a.length = l
+			return val
+		}
+		return _undefined
+	} else {
+		return r.arrayproto_pop_generic(obj, call)
+	}
+}
+
+func (r *Runtime) arrayproto_join(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	l := int(toLength(o.self.getStr("length")))
+	sep := ""
+	if s := call.Argument(0); s != _undefined {
+		sep = s.String()
+	} else {
+		sep = ","
+	}
+	if l == 0 {
+		return stringEmpty
+	}
+
+	var buf bytes.Buffer
+
+	element0 := o.self.get(intToValue(0))
+	if element0 != nil && element0 != _undefined && element0 != _null {
+		buf.WriteString(element0.String())
+	}
+
+	for i := 1; i < l; i++ {
+		buf.WriteString(sep)
+		element := o.self.get(intToValue(int64(i)))
+		if element != nil && element != _undefined && element != _null {
+			buf.WriteString(element.String())
+		}
+	}
+
+	return newStringValue(buf.String())
+}
+
+func (r *Runtime) arrayproto_toString(call FunctionCall) Value {
+	array := call.This.ToObject(r)
+	f := array.self.getStr("join")
+	if fObj, ok := f.(*Object); ok {
+		if fcall, ok := fObj.self.assertCallable(); ok {
+			return fcall(FunctionCall{
+				This: array,
+			})
+		}
+	}
+	return r.objectproto_toString(FunctionCall{
+		This: array,
+	})
+}
+
+func (r *Runtime) writeItemLocaleString(item Value, buf *bytes.Buffer) {
+	if item != nil && item != _undefined && item != _null {
+		itemObj := item.ToObject(r)
+		if f, ok := itemObj.self.getStr("toLocaleString").(*Object); ok {
+			if c, ok := f.self.assertCallable(); ok {
+				strVal := c(FunctionCall{
+					This: itemObj,
+				})
+				buf.WriteString(strVal.String())
+				return
+			}
+		}
+		r.typeErrorResult(true, "Property 'toLocaleString' of object %s is not a function", itemObj)
+	}
+}
+
+func (r *Runtime) arrayproto_toLocaleString_generic(obj *Object, start int64, buf *bytes.Buffer) Value {
+	length := toLength(obj.self.getStr("length"))
+	for i := int64(start); i < length; i++ {
+		if i > 0 {
+			buf.WriteByte(',')
+		}
+		item := obj.self.get(intToValue(i))
+		r.writeItemLocaleString(item, buf)
+	}
+	return newStringValue(buf.String())
+}
+
+func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value {
+	array := call.This.ToObject(r)
+	if a, ok := array.self.(*arrayObject); ok {
+		var buf bytes.Buffer
+		for i := 0; i < a.length; i++ {
+			var item Value
+			if i < len(a.values) {
+				item = a.values[i]
+			}
+			if item == nil {
+				return r.arrayproto_toLocaleString_generic(array, int64(i), &buf)
+			}
+			if prop, ok := item.(*valueProperty); ok {
+				item = prop.get(array)
+			}
+			if i > 0 {
+				buf.WriteByte(',')
+			}
+			r.writeItemLocaleString(item, &buf)
+		}
+		return newStringValue(buf.String())
+	} else {
+		return r.arrayproto_toLocaleString_generic(array, 0, bytes.NewBuffer(nil))
+	}
+
+}
+
+func (r *Runtime) arrayproto_concat_append(a *Object, item Value) {
+	descr := r.NewObject().self
+	descr.putStr("writable", valueTrue, false)
+	descr.putStr("enumerable", valueTrue, false)
+	descr.putStr("configurable", valueTrue, false)
+	aLength := toLength(a.self.getStr("length"))
+	if obj, ok := item.(*Object); ok {
+		if isArray(obj) {
+			length := toLength(obj.self.getStr("length"))
+			for i := int64(0); i < length; i++ {
+				v := obj.self.get(intToValue(i))
+				if v != nil {
+					descr.putStr("value", v, false)
+					a.self.defineOwnProperty(intToValue(aLength), descr, false)
+					aLength++
+				} else {
+					aLength++
+					a.self.putStr("length", intToValue(aLength), false)
+				}
+			}
+			return
+		}
+	}
+	descr.putStr("value", item, false)
+	a.self.defineOwnProperty(intToValue(aLength), descr, false)
+}
+
+func (r *Runtime) arrayproto_concat(call FunctionCall) Value {
+	a := r.newArrayValues(nil)
+	r.arrayproto_concat_append(a, call.This.ToObject(r))
+	for _, item := range call.Arguments {
+		r.arrayproto_concat_append(a, item)
+	}
+	return a
+}
+
+func max(a, b int64) int64 {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+func min(a, b int64) int64 {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func (r *Runtime) arrayproto_slice(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	start := call.Argument(0).ToInteger()
+	if start < 0 {
+		start = max(length+start, 0)
+	} else {
+		start = min(start, length)
+	}
+	var end int64
+	if endArg := call.Argument(1); endArg != _undefined {
+		end = endArg.ToInteger()
+	} else {
+		end = length
+	}
+	if end < 0 {
+		end = max(length+end, 0)
+	} else {
+		end = min(end, length)
+	}
+
+	count := end - start
+	if count < 0 {
+		count = 0
+	}
+	a := r.newArrayLength(count)
+
+	n := int64(0)
+	descr := r.NewObject().self
+	descr.putStr("writable", valueTrue, false)
+	descr.putStr("enumerable", valueTrue, false)
+	descr.putStr("configurable", valueTrue, false)
+	for start < end {
+		p := o.self.get(intToValue(start))
+		if p != nil && p != _undefined {
+			descr.putStr("value", p, false)
+			a.self.defineOwnProperty(intToValue(n), descr, false)
+		}
+		start++
+		n++
+	}
+	return a
+}
+
+func (r *Runtime) arrayproto_sort(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+
+	var compareFn func(FunctionCall) Value
+
+	if arg, ok := call.Argument(0).(*Object); ok {
+		compareFn, _ = arg.self.assertCallable()
+	}
+
+	ctx := arraySortCtx{
+		obj:     o.self,
+		compare: compareFn,
+	}
+
+	sort.Sort(&ctx)
+	return o
+}
+
+func (r *Runtime) arrayproto_splice(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	a := r.newArrayValues(nil)
+	length := toLength(o.self.getStr("length"))
+	relativeStart := call.Argument(0).ToInteger()
+	var actualStart int64
+	if relativeStart < 0 {
+		actualStart = max(length+relativeStart, 0)
+	} else {
+		actualStart = min(relativeStart, length)
+	}
+
+	actualDeleteCount := min(max(call.Argument(1).ToInteger(), 0), length-actualStart)
+
+	for k := int64(0); k < actualDeleteCount; k++ {
+		from := intToValue(k + actualStart)
+		if o.self.hasProperty(from) {
+			a.self.put(intToValue(k), o.self.get(from), false)
+		}
+	}
+
+	itemCount := max(int64(len(call.Arguments)-2), 0)
+	if itemCount < actualDeleteCount {
+		for k := actualStart; k < length-actualDeleteCount; k++ {
+			from := intToValue(k + actualDeleteCount)
+			to := intToValue(k + itemCount)
+			if o.self.hasProperty(from) {
+				o.self.put(to, o.self.get(from), true)
+			} else {
+				o.self.delete(to, true)
+			}
+		}
+
+		for k := length; k > length-actualDeleteCount+itemCount; k-- {
+			o.self.delete(intToValue(k-1), true)
+		}
+	} else if itemCount > actualDeleteCount {
+		for k := length - actualDeleteCount; k > actualStart; k-- {
+			from := intToValue(k + actualDeleteCount - 1)
+			to := intToValue(k + itemCount - 1)
+			if o.self.hasProperty(from) {
+				o.self.put(to, o.self.get(from), true)
+			} else {
+				o.self.delete(to, true)
+			}
+		}
+	}
+
+	if itemCount > 0 {
+		for i, item := range call.Arguments[2:] {
+			o.self.put(intToValue(actualStart+int64(i)), item, true)
+		}
+	}
+
+	o.self.putStr("length", intToValue(length-actualDeleteCount+itemCount), true)
+
+	return a
+}
+
+func (r *Runtime) arrayproto_unshift(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	argCount := int64(len(call.Arguments))
+	for k := length - 1; k >= 0; k-- {
+		from := intToValue(k)
+		to := intToValue(k + argCount)
+		if o.self.hasProperty(from) {
+			o.self.put(to, o.self.get(from), true)
+		} else {
+			o.self.delete(to, true)
+		}
+	}
+
+	for k, arg := range call.Arguments {
+		o.self.put(intToValue(int64(k)), arg, true)
+	}
+
+	newLen := intToValue(length + argCount)
+	o.self.putStr("length", newLen, true)
+	return newLen
+}
+
+func (r *Runtime) arrayproto_indexOf(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	if length == 0 {
+		return intToValue(-1)
+	}
+
+	n := call.Argument(1).ToInteger()
+	if n >= length {
+		return intToValue(-1)
+	}
+
+	if n < 0 {
+		n = max(length+n, 0)
+	}
+
+	searchElement := call.Argument(0)
+
+	for ; n < length; n++ {
+		idx := intToValue(n)
+		if val := o.self.get(idx); val != nil {
+			if searchElement.StrictEquals(val) {
+				return idx
+			}
+		}
+	}
+
+	return intToValue(-1)
+}
+
+func (r *Runtime) arrayproto_lastIndexOf(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	if length == 0 {
+		return intToValue(-1)
+	}
+
+	var fromIndex int64
+
+	if len(call.Arguments) < 2 {
+		fromIndex = length - 1
+	} else {
+		fromIndex = call.Argument(1).ToInteger()
+		if fromIndex >= 0 {
+			fromIndex = min(fromIndex, length-1)
+		} else {
+			fromIndex += length
+		}
+	}
+
+	searchElement := call.Argument(0)
+
+	for k := fromIndex; k >= 0; k-- {
+		idx := intToValue(k)
+		if val := o.self.get(idx); val != nil {
+			if searchElement.StrictEquals(val) {
+				return idx
+			}
+		}
+	}
+
+	return intToValue(-1)
+}
+
+func (r *Runtime) arrayproto_every(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		fc := FunctionCall{
+			This:      call.Argument(1),
+			Arguments: []Value{nil, nil, o},
+		}
+		for k := int64(0); k < length; k++ {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[0] = val
+				fc.Arguments[1] = idx
+				if !callbackFn(fc).ToBoolean() {
+					return valueFalse
+				}
+			}
+		}
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	return valueTrue
+}
+
+func (r *Runtime) arrayproto_some(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		fc := FunctionCall{
+			This:      call.Argument(1),
+			Arguments: []Value{nil, nil, o},
+		}
+		for k := int64(0); k < length; k++ {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[0] = val
+				fc.Arguments[1] = idx
+				if callbackFn(fc).ToBoolean() {
+					return valueTrue
+				}
+			}
+		}
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	return valueFalse
+}
+
+func (r *Runtime) arrayproto_forEach(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		fc := FunctionCall{
+			This:      call.Argument(1),
+			Arguments: []Value{nil, nil, o},
+		}
+		for k := int64(0); k < length; k++ {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[0] = val
+				fc.Arguments[1] = idx
+				callbackFn(fc)
+			}
+		}
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	return _undefined
+}
+
+func (r *Runtime) arrayproto_map(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		fc := FunctionCall{
+			This:      call.Argument(1),
+			Arguments: []Value{nil, nil, o},
+		}
+		a := r.newArrayObject()
+		a._setLengthInt(int(length), true)
+		a.values = make([]Value, length)
+		for k := int64(0); k < length; k++ {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[0] = val
+				fc.Arguments[1] = idx
+				a.values[k] = callbackFn(fc)
+				a.objCount++
+			}
+		}
+		return a.val
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	panic("unreachable")
+}
+
+func (r *Runtime) arrayproto_filter(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		a := r.newArrayObject()
+		fc := FunctionCall{
+			This:      call.Argument(1),
+			Arguments: []Value{nil, nil, o},
+		}
+		for k := int64(0); k < length; k++ {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[0] = val
+				fc.Arguments[1] = idx
+				if callbackFn(fc).ToBoolean() {
+					a.values = append(a.values, val)
+				}
+			}
+		}
+		a.length = len(a.values)
+		a.objCount = a.length
+		return a.val
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	panic("unreachable")
+}
+
+func (r *Runtime) arrayproto_reduce(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		fc := FunctionCall{
+			This:      _undefined,
+			Arguments: []Value{nil, nil, nil, o},
+		}
+
+		var k int64
+
+		if len(call.Arguments) >= 2 {
+			fc.Arguments[0] = call.Argument(1)
+		} else {
+			for ; k < length; k++ {
+				idx := intToValue(k)
+				if val := o.self.get(idx); val != nil {
+					fc.Arguments[0] = val
+					break
+				}
+			}
+			if fc.Arguments[0] == nil {
+				r.typeErrorResult(true, "No initial value")
+				panic("unreachable")
+			}
+			k++
+		}
+
+		for ; k < length; k++ {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[1] = val
+				fc.Arguments[2] = idx
+				fc.Arguments[0] = callbackFn(fc)
+			}
+		}
+		return fc.Arguments[0]
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	panic("unreachable")
+}
+
+func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	callbackFn := call.Argument(0).ToObject(r)
+	if callbackFn, ok := callbackFn.self.assertCallable(); ok {
+		fc := FunctionCall{
+			This:      _undefined,
+			Arguments: []Value{nil, nil, nil, o},
+		}
+
+		k := length - 1
+
+		if len(call.Arguments) >= 2 {
+			fc.Arguments[0] = call.Argument(1)
+		} else {
+			for ; k >= 0; k-- {
+				idx := intToValue(k)
+				if val := o.self.get(idx); val != nil {
+					fc.Arguments[0] = val
+					break
+				}
+			}
+			if fc.Arguments[0] == nil {
+				r.typeErrorResult(true, "No initial value")
+				panic("unreachable")
+			}
+			k--
+		}
+
+		for ; k >= 0; k-- {
+			idx := intToValue(k)
+			if val := o.self.get(idx); val != nil {
+				fc.Arguments[1] = val
+				fc.Arguments[2] = idx
+				fc.Arguments[0] = callbackFn(fc)
+			}
+		}
+		return fc.Arguments[0]
+	} else {
+		r.typeErrorResult(true, "%s is not a function", call.Argument(0))
+	}
+	panic("unreachable")
+}
+
+func arrayproto_reverse_generic_step(o *Object, lower, upper int64) {
+	lowerP := intToValue(lower)
+	upperP := intToValue(upper)
+	lowerValue := o.self.get(lowerP)
+	upperValue := o.self.get(upperP)
+	if lowerValue != nil && upperValue != nil {
+		o.self.put(lowerP, upperValue, true)
+		o.self.put(upperP, lowerValue, true)
+	} else if lowerValue == nil && upperValue != nil {
+		o.self.put(lowerP, upperValue, true)
+		o.self.delete(upperP, true)
+	} else if lowerValue != nil && upperValue == nil {
+		o.self.delete(lowerP, true)
+		o.self.put(upperP, lowerValue, true)
+	}
+}
+
+func (r *Runtime) arrayproto_reverse_generic(o *Object, start int64) {
+	l := toLength(o.self.getStr("length"))
+	middle := l / 2
+	for lower := start; lower != middle; lower++ {
+		arrayproto_reverse_generic_step(o, lower, l-lower-1)
+	}
+}
+
+func (r *Runtime) arrayproto_reverse(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	if a, ok := o.self.(*arrayObject); ok {
+		l := a.length
+		middle := l / 2
+		for lower := 0; lower != middle; lower++ {
+			upper := l - lower - 1
+			var lowerValue, upperValue Value
+			if upper >= len(a.values) || lower >= len(a.values) {
+				goto bailout
+			}
+			lowerValue = a.values[lower]
+			if lowerValue == nil {
+				goto bailout
+			}
+			if _, ok := lowerValue.(*valueProperty); ok {
+				goto bailout
+			}
+			upperValue = a.values[upper]
+			if upperValue == nil {
+				goto bailout
+			}
+			if _, ok := upperValue.(*valueProperty); ok {
+				goto bailout
+			}
+
+			a.values[lower], a.values[upper] = upperValue, lowerValue
+			continue
+		bailout:
+			arrayproto_reverse_generic_step(o, int64(lower), int64(upper))
+		}
+		//TODO: go arrays
+	} else {
+		r.arrayproto_reverse_generic(o, 0)
+	}
+	return o
+}
+
+func (r *Runtime) arrayproto_shift(call FunctionCall) Value {
+	o := call.This.ToObject(r)
+	length := toLength(o.self.getStr("length"))
+	if length == 0 {
+		o.self.putStr("length", intToValue(0), true)
+		return _undefined
+	}
+	first := o.self.get(intToValue(0))
+	for i := int64(1); i < length; i++ {
+		v := o.self.get(intToValue(i))
+		if v != nil && v != _undefined {
+			o.self.put(intToValue(i-1), v, true)
+		} else {
+			o.self.delete(intToValue(i-1), true)
+		}
+	}
+
+	lv := intToValue(length - 1)
+	o.self.delete(lv, true)
+	o.self.putStr("length", lv, true)
+
+	return first
+}
+
+func (r *Runtime) array_isArray(call FunctionCall) Value {
+	if o, ok := call.Argument(0).(*Object); ok {
+		if isArray(o) {
+			return valueTrue
+		}
+	}
+	return valueFalse
+}
+
+func (r *Runtime) createArrayProto(val *Object) objectImpl {
+	o := &arrayObject{
+		baseObject: baseObject{
+			class:      classArray,
+			val:        val,
+			extensible: true,
+			prototype:  r.global.ObjectPrototype,
+		},
+	}
+	o.init()
+
+	o._putProp("constructor", r.global.Array, true, false, true)
+	o._putProp("pop", r.newNativeFunc(r.arrayproto_pop, nil, "pop", nil, 0), true, false, true)
+	o._putProp("push", r.newNativeFunc(r.arrayproto_push, nil, "push", nil, 1), true, false, true)
+	o._putProp("join", r.newNativeFunc(r.arrayproto_join, nil, "join", nil, 1), true, false, true)
+	o._putProp("toString", r.newNativeFunc(r.arrayproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("toLocaleString", r.newNativeFunc(r.arrayproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
+	o._putProp("concat", r.newNativeFunc(r.arrayproto_concat, nil, "concat", nil, 1), true, false, true)
+	o._putProp("reverse", r.newNativeFunc(r.arrayproto_reverse, nil, "reverse", nil, 0), true, false, true)
+	o._putProp("shift", r.newNativeFunc(r.arrayproto_shift, nil, "shift", nil, 0), true, false, true)
+	o._putProp("slice", r.newNativeFunc(r.arrayproto_slice, nil, "slice", nil, 2), true, false, true)
+	o._putProp("sort", r.newNativeFunc(r.arrayproto_sort, nil, "sort", nil, 1), true, false, true)
+	o._putProp("splice", r.newNativeFunc(r.arrayproto_splice, nil, "splice", nil, 2), true, false, true)
+	o._putProp("unshift", r.newNativeFunc(r.arrayproto_unshift, nil, "unshift", nil, 1), true, false, true)
+	o._putProp("indexOf", r.newNativeFunc(r.arrayproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
+	o._putProp("lastIndexOf", r.newNativeFunc(r.arrayproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
+	o._putProp("every", r.newNativeFunc(r.arrayproto_every, nil, "every", nil, 1), true, false, true)
+	o._putProp("some", r.newNativeFunc(r.arrayproto_some, nil, "some", nil, 1), true, false, true)
+	o._putProp("forEach", r.newNativeFunc(r.arrayproto_forEach, nil, "forEach", nil, 1), true, false, true)
+	o._putProp("map", r.newNativeFunc(r.arrayproto_map, nil, "map", nil, 1), true, false, true)
+	o._putProp("filter", r.newNativeFunc(r.arrayproto_filter, nil, "filter", nil, 1), true, false, true)
+	o._putProp("reduce", r.newNativeFunc(r.arrayproto_reduce, nil, "reduce", nil, 1), true, false, true)
+	o._putProp("reduceRight", r.newNativeFunc(r.arrayproto_reduceRight, nil, "reduceRight", nil, 1), true, false, true)
+
+	return o
+}
+
+func (r *Runtime) createArray(val *Object) objectImpl {
+	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
+	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
+	return o
+}
+
+func (r *Runtime) initArray() {
+	//r.global.ArrayPrototype = r.newArray(r.global.ObjectPrototype).val
+	//o := r.global.ArrayPrototype.self
+	r.global.ArrayPrototype = r.newLazyObject(r.createArrayProto)
+
+	//r.global.Array = r.newNativeFuncConstruct(r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
+	//o = r.global.Array.self
+	//o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
+	r.global.Array = r.newLazyObject(r.createArray)
+
+	r.addToGlobal("Array", r.global.Array)
+}
+
+type sortable interface {
+	sortLen() int
+	sortGet(int) Value
+	swap(int, int)
+}
+
+type arraySortCtx struct {
+	obj     sortable
+	compare func(FunctionCall) Value
+}
+
+func (ctx *arraySortCtx) sortCompare(x, y Value) int {
+	if x == nil && y == nil {
+		return 0
+	}
+
+	if x == nil {
+		return 1
+	}
+
+	if y == nil {
+		return -1
+	}
+
+	if x == _undefined && y == _undefined {
+		return 0
+	}
+
+	if x == _undefined {
+		return 1
+	}
+
+	if y == _undefined {
+		return -1
+	}
+
+	if ctx.compare != nil {
+		return int(ctx.compare(FunctionCall{
+			This:      _undefined,
+			Arguments: []Value{x, y},
+		}).ToInteger())
+	}
+	return strings.Compare(x.String(), y.String())
+}
+
+// sort.Interface
+
+func (a *arraySortCtx) Len() int {
+	return a.obj.sortLen()
+}
+
+func (a *arraySortCtx) Less(j, k int) bool {
+	return a.sortCompare(a.obj.sortGet(j), a.obj.sortGet(k)) == -1
+}
+
+func (a *arraySortCtx) Swap(j, k int) {
+	a.obj.swap(j, k)
+}

+ 50 - 0
builtin_boolean.go

@@ -0,0 +1,50 @@
+package goja
+
+func (r *Runtime) booleanproto_toString(call FunctionCall) Value {
+	var b bool
+	switch o := call.This.(type) {
+	case valueBool:
+		b = bool(o)
+		goto success
+	case *Object:
+		if p, ok := o.self.(*primitiveValueObject); ok {
+			if b1, ok := p.pValue.(valueBool); ok {
+				b = bool(b1)
+				goto success
+			}
+		}
+	}
+	r.typeErrorResult(true, "Method Boolean.prototype.toString is called on incompatible receiver")
+
+success:
+	if b {
+		return stringTrue
+	}
+	return stringFalse
+}
+
+func (r *Runtime) booleanproto_valueOf(call FunctionCall) Value {
+	switch o := call.This.(type) {
+	case valueBool:
+		return o
+	case *Object:
+		if p, ok := o.self.(*primitiveValueObject); ok {
+			if b, ok := p.pValue.(valueBool); ok {
+				return b
+			}
+		}
+	}
+
+	r.typeErrorResult(true, "Method Boolean.prototype.valueOf is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) initBoolean() {
+	r.global.BooleanPrototype = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
+	o := r.global.BooleanPrototype.self
+	o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+
+	r.global.Boolean = r.newNativeFunc(r.builtin_Boolean, r.builtin_newBoolean, "Boolean", r.global.BooleanPrototype, 1)
+	r.addToGlobal("Boolean", r.global.Boolean)
+}

+ 917 - 0
builtin_date.go

@@ -0,0 +1,917 @@
+package goja
+
+import (
+	"math"
+	"time"
+)
+
+const (
+	maxTime = 8.64e15
+)
+
+func timeFromMsec(msec int64) time.Time {
+	sec := msec / 1000
+	nsec := (msec % 1000) * 1e6
+	return time.Unix(sec, nsec)
+}
+
+func makeDate(args []Value, loc *time.Location) (t time.Time, valid bool) {
+	pick := func(index int, default_ int64) (int64, bool) {
+		if index >= len(args) {
+			return default_, true
+		}
+		value := args[index]
+		if valueInt, ok := value.assertInt(); ok {
+			return valueInt, true
+		}
+		valueFloat := value.ToFloat()
+		if math.IsNaN(valueFloat) || math.IsInf(valueFloat, 0) {
+			return 0, false
+		}
+		return int64(valueFloat), true
+	}
+
+	switch {
+	case len(args) >= 2:
+		var year, month, day, hour, minute, second, millisecond int64
+		if year, valid = pick(0, 1900); !valid {
+			return
+		}
+		if month, valid = pick(1, 0); !valid {
+			return
+		}
+		if day, valid = pick(2, 1); !valid {
+			return
+		}
+		if hour, valid = pick(3, 0); !valid {
+			return
+		}
+		if minute, valid = pick(4, 0); !valid {
+			return
+		}
+		if second, valid = pick(5, 0); !valid {
+			return
+		}
+		if millisecond, valid = pick(6, 0); !valid {
+			return
+		}
+
+		if year >= 0 && year <= 99 {
+			year += 1900
+		}
+
+		t = time.Date(int(year), time.Month(int(month)+1), int(day), int(hour), int(minute), int(second), int(millisecond)*1e6, loc)
+	case len(args) == 0:
+		t = time.Now()
+		valid = true
+	default: // one argument
+		pv := toPrimitiveNumber(args[0])
+		if val, ok := pv.assertString(); ok {
+			return dateParse(val.String())
+		}
+
+		var n int64
+		if i, ok := pv.assertInt(); ok {
+			n = i
+		} else if f, ok := pv.assertFloat(); ok {
+			if math.IsNaN(f) || math.IsInf(f, 0) {
+				return
+			}
+			if math.Abs(f) > maxTime {
+				return
+			}
+			n = int64(f)
+		} else {
+			n = pv.ToInteger()
+		}
+		t = timeFromMsec(n)
+		valid = true
+	}
+	msec := t.Unix()*1000 + int64(t.Nanosecond()/1e6)
+	if msec < 0 {
+		msec = -msec
+	}
+	if msec > maxTime {
+		valid = false
+	}
+	return
+}
+
+func (r *Runtime) newDateTime(args []Value, loc *time.Location) *Object {
+	t, isSet := makeDate(args, loc)
+	return r.newDateObject(t, isSet)
+}
+
+func (r *Runtime) builtin_newDate(args []Value) *Object {
+	return r.newDateTime(args, time.Local)
+}
+
+func (r *Runtime) builtin_date(call FunctionCall) Value {
+	return asciiString(dateFormat(time.Now()))
+}
+
+func (r *Runtime) date_parse(call FunctionCall) Value {
+	return r.newDateObject(dateParse(call.Argument(0).String()))
+}
+
+func (r *Runtime) date_UTC(call FunctionCall) Value {
+	t, valid := makeDate(call.Arguments, time.UTC)
+	if !valid {
+		return _NaN
+	}
+	return intToValue(int64(t.UnixNano() / 1e6))
+}
+
+func (r *Runtime) date_now(call FunctionCall) Value {
+	return intToValue(time.Now().UnixNano() / 1e6)
+}
+
+func (r *Runtime) dateproto_toString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.Format(dateTimeLayout))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toUTCString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.In(time.UTC).Format(dateTimeLayout))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toUTCString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toISOString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.In(time.UTC).Format(isoDateTimeLayout))
+		} else {
+			panic(r.newError(r.global.RangeError, "Invalid time value"))
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toISOString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toJSON(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	tv := obj.self.toPrimitiveNumber()
+	if f, ok := tv.assertFloat(); ok {
+		if math.IsNaN(f) || math.IsInf(f, 0) {
+			return _null
+		}
+	} else if _, ok := tv.assertInt(); !ok {
+		return _null
+	}
+
+	if toISO, ok := obj.self.getStr("toISOString").(*Object); ok {
+		if toISO, ok := toISO.self.assertCallable(); ok {
+			return toISO(FunctionCall{
+				This: obj,
+			})
+		}
+	}
+
+	r.typeErrorResult(true, "toISOString is not a function")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toDateString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.Format(dateLayout))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toDateString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toTimeString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.Format(timeLayout))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toTimeString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toLocaleString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.Format(datetimeLayout_en_GB))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toLocaleString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toLocaleDateString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.Format(dateLayout_en_GB))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toLocaleDateString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_toLocaleTimeString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return asciiString(d.time.Format(timeLayout_en_GB))
+		} else {
+			return stringInvalidDate
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.toLocaleTimeString is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_valueOf(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(d.time.Unix()*1000 + int64(d.time.Nanosecond()/1e6))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.valueOf is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getTime(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(d.time.UnixNano() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getTime is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getFullYear(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Year()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getFullYear is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCFullYear(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Year()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCFullYear is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getMonth(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Month()) - 1)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getMonth is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCMonth(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Month()) - 1)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCMonth is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getHours(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Hour()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getHours is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCHours(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Hour()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCHours is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getDate(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Day()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getDate is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCDate(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Day()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCDate is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getDay(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Weekday()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getDay is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCDay(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Weekday()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCDay is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getMinutes(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Minute()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getMinutes is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCMinutes(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Minute()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCMinutes is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getSeconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Second()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getSeconds is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCSeconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Second()))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCSeconds is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getMilliseconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.Nanosecond() / 1e6))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getMilliseconds is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getUTCMilliseconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			return intToValue(int64(d.time.In(time.UTC).Nanosecond() / 1e6))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getUTCMilliseconds is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_getTimezoneOffset(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			_, offset := d.time.Zone()
+			return intToValue(int64(-offset / 60))
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.getTimezoneOffset is called on incompatible receiver")
+	return nil
+}
+
+func (r *Runtime) dateproto_setTime(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		msec := call.Argument(0).ToInteger()
+		d.time = timeFromMsec(msec)
+		return intToValue(msec)
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setTime is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setMilliseconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			msec := int(call.Argument(0).ToInteger())
+			d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), d.time.Hour(), d.time.Minute(), d.time.Second(), msec*1e6, time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setMilliseconds is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCMilliseconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			msec := int(call.Argument(0).ToInteger())
+			t := d.time.In(time.UTC)
+			d.time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), msec*1e6, time.UTC).In(time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCMilliseconds is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setSeconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			sec := int(call.Argument(0).ToInteger())
+			var nsec int
+			if len(call.Arguments) > 1 {
+				nsec = int(call.Arguments[1].ToInteger() * 1e6)
+			} else {
+				nsec = d.time.Nanosecond()
+			}
+			d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), d.time.Hour(), d.time.Minute(), sec, nsec, time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setSeconds is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCSeconds(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			sec := int(call.Argument(0).ToInteger())
+			var nsec int
+			t := d.time.In(time.UTC)
+			if len(call.Arguments) > 1 {
+				nsec = int(call.Arguments[1].ToInteger() * 1e6)
+			} else {
+				nsec = t.Nanosecond()
+			}
+			d.time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), sec, nsec, time.UTC).In(time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCSeconds is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setMinutes(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			min := int(call.Argument(0).ToInteger())
+			var sec, nsec int
+			if len(call.Arguments) > 1 {
+				sec = int(call.Arguments[1].ToInteger())
+			} else {
+				sec = d.time.Second()
+			}
+			if len(call.Arguments) > 2 {
+				nsec = int(call.Arguments[2].ToInteger() * 1e6)
+			} else {
+				nsec = d.time.Nanosecond()
+			}
+			d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), d.time.Hour(), min, sec, nsec, time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setMinutes is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCMinutes(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			min := int(call.Argument(0).ToInteger())
+			var sec, nsec int
+			t := d.time.In(time.UTC)
+			if len(call.Arguments) > 1 {
+				sec = int(call.Arguments[1].ToInteger())
+			} else {
+				sec = t.Second()
+			}
+			if len(call.Arguments) > 2 {
+				nsec = int(call.Arguments[2].ToInteger() * 1e6)
+			} else {
+				nsec = t.Nanosecond()
+			}
+			d.time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), min, sec, nsec, time.UTC).In(time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCMinutes is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setHours(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			hour := int(call.Argument(0).ToInteger())
+			var min, sec, nsec int
+			if len(call.Arguments) > 1 {
+				min = int(call.Arguments[1].ToInteger())
+			} else {
+				min = d.time.Minute()
+			}
+			if len(call.Arguments) > 2 {
+				sec = int(call.Arguments[2].ToInteger())
+			} else {
+				sec = d.time.Second()
+			}
+			if len(call.Arguments) > 3 {
+				nsec = int(call.Arguments[3].ToInteger() * 1e6)
+			} else {
+				nsec = d.time.Nanosecond()
+			}
+			d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), hour, min, sec, nsec, time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setHours is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCHours(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			hour := int(call.Argument(0).ToInteger())
+			var min, sec, nsec int
+			t := d.time.In(time.UTC)
+			if len(call.Arguments) > 1 {
+				min = int(call.Arguments[1].ToInteger())
+			} else {
+				min = t.Minute()
+			}
+			if len(call.Arguments) > 2 {
+				sec = int(call.Arguments[2].ToInteger())
+			} else {
+				sec = t.Second()
+			}
+			if len(call.Arguments) > 3 {
+				nsec = int(call.Arguments[3].ToInteger() * 1e6)
+			} else {
+				nsec = t.Nanosecond()
+			}
+			d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), hour, min, sec, nsec, time.UTC).In(time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCHours is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setDate(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			d.time = time.Date(d.time.Year(), d.time.Month(), int(call.Argument(0).ToInteger()), d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setDate is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCDate(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			t := d.time.In(time.UTC)
+			d.time = time.Date(t.Year(), t.Month(), int(call.Argument(0).ToInteger()), d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.UTC).In(time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCDate is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setMonth(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			month := time.Month(int(call.Argument(0).ToInteger()) + 1)
+			var day int
+			if len(call.Arguments) > 1 {
+				day = int(call.Arguments[1].ToInteger())
+			} else {
+				day = d.time.Day()
+			}
+			d.time = time.Date(d.time.Year(), month, day, d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setMonth is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCMonth(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if d.isSet {
+			month := time.Month(int(call.Argument(0).ToInteger()) + 1)
+			var day int
+			t := d.time.In(time.UTC)
+			if len(call.Arguments) > 1 {
+				day = int(call.Arguments[1].ToInteger())
+			} else {
+				day = t.Day()
+			}
+			d.time = time.Date(t.Year(), month, day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC).In(time.Local)
+			return intToValue(d.time.Unix() / 1e6)
+		} else {
+			return _NaN
+		}
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCMonth is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setFullYear(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if !d.isSet {
+			d.time = time.Unix(0, 0)
+		}
+		year := int(call.Argument(0).ToInteger())
+		var month time.Month
+		var day int
+		if len(call.Arguments) > 1 {
+			month = time.Month(call.Arguments[1].ToInteger() + 1)
+		} else {
+			month = d.time.Month()
+		}
+		if len(call.Arguments) > 2 {
+			day = int(call.Arguments[2].ToInteger())
+		} else {
+			day = d.time.Day()
+		}
+		d.time = time.Date(year, month, day, d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.Local)
+		return intToValue(d.time.Unix() / 1e6)
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setFullYear is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) dateproto_setUTCFullYear(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	if d, ok := obj.self.(*dateObject); ok {
+		if !d.isSet {
+			d.time = time.Unix(0, 0)
+		}
+		year := int(call.Argument(0).ToInteger())
+		var month time.Month
+		var day int
+		t := d.time.In(time.UTC)
+		if len(call.Arguments) > 1 {
+			month = time.Month(call.Arguments[1].ToInteger() + 1)
+		} else {
+			month = t.Month()
+		}
+		if len(call.Arguments) > 2 {
+			day = int(call.Arguments[2].ToInteger())
+		} else {
+			day = t.Day()
+		}
+		d.time = time.Date(year, month, day, d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.UTC).In(time.Local)
+		return intToValue(d.time.Unix() / 1e6)
+	}
+	r.typeErrorResult(true, "Method Date.prototype.setUTCFullYear is called on incompatible receiver")
+	panic("Unreachable")
+}
+
+func (r *Runtime) createDateProto(val *Object) objectImpl {
+	o := &baseObject{
+		class:      classObject,
+		val:        val,
+		extensible: true,
+		prototype:  r.global.ObjectPrototype,
+	}
+	o.init()
+
+	o._putProp("constructor", r.global.Date, true, false, true)
+	o._putProp("toString", r.newNativeFunc(r.dateproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("toDateString", r.newNativeFunc(r.dateproto_toDateString, nil, "toDateString", nil, 0), true, false, true)
+	o._putProp("toTimeString", r.newNativeFunc(r.dateproto_toTimeString, nil, "toTimeString", nil, 0), true, false, true)
+	o._putProp("toLocaleString", r.newNativeFunc(r.dateproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
+	o._putProp("toLocaleDateString", r.newNativeFunc(r.dateproto_toLocaleDateString, nil, "toLocaleDateString", nil, 0), true, false, true)
+	o._putProp("toLocaleTimeString", r.newNativeFunc(r.dateproto_toLocaleTimeString, nil, "toLocaleTimeString", nil, 0), true, false, true)
+	o._putProp("valueOf", r.newNativeFunc(r.dateproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+	o._putProp("getTime", r.newNativeFunc(r.dateproto_getTime, nil, "getTime", nil, 0), true, false, true)
+	o._putProp("getFullYear", r.newNativeFunc(r.dateproto_getFullYear, nil, "getFullYear", nil, 0), true, false, true)
+	o._putProp("getUTCFullYear", r.newNativeFunc(r.dateproto_getUTCFullYear, nil, "getUTCFullYear", nil, 0), true, false, true)
+	o._putProp("getMonth", r.newNativeFunc(r.dateproto_getMonth, nil, "getMonth", nil, 0), true, false, true)
+	o._putProp("getUTCMonth", r.newNativeFunc(r.dateproto_getUTCMonth, nil, "getUTCMonth", nil, 0), true, false, true)
+	o._putProp("getDate", r.newNativeFunc(r.dateproto_getDate, nil, "getDate", nil, 0), true, false, true)
+	o._putProp("getUTCDate", r.newNativeFunc(r.dateproto_getUTCDate, nil, "getUTCDate", nil, 0), true, false, true)
+	o._putProp("getDay", r.newNativeFunc(r.dateproto_getDay, nil, "getDay", nil, 0), true, false, true)
+	o._putProp("getUTCDay", r.newNativeFunc(r.dateproto_getUTCDay, nil, "getUTCDay", nil, 0), true, false, true)
+	o._putProp("getHours", r.newNativeFunc(r.dateproto_getHours, nil, "getHours", nil, 0), true, false, true)
+	o._putProp("getUTCHours", r.newNativeFunc(r.dateproto_getUTCHours, nil, "getUTCHours", nil, 0), true, false, true)
+	o._putProp("getMinutes", r.newNativeFunc(r.dateproto_getMinutes, nil, "getMinutes", nil, 0), true, false, true)
+	o._putProp("getUTCMinutes", r.newNativeFunc(r.dateproto_getUTCMinutes, nil, "getUTCMinutes", nil, 0), true, false, true)
+	o._putProp("getSeconds", r.newNativeFunc(r.dateproto_getSeconds, nil, "getSeconds", nil, 0), true, false, true)
+	o._putProp("getUTCSeconds", r.newNativeFunc(r.dateproto_getUTCSeconds, nil, "getUTCSeconds", nil, 0), true, false, true)
+	o._putProp("getMilliseconds", r.newNativeFunc(r.dateproto_getMilliseconds, nil, "getMilliseconds", nil, 0), true, false, true)
+	o._putProp("getUTCMilliseconds", r.newNativeFunc(r.dateproto_getUTCMilliseconds, nil, "getUTCMilliseconds", nil, 0), true, false, true)
+	o._putProp("getTimezoneOffset", r.newNativeFunc(r.dateproto_getTimezoneOffset, nil, "getTimezoneOffset", nil, 0), true, false, true)
+	o._putProp("setTime", r.newNativeFunc(r.dateproto_setTime, nil, "setTime", nil, 1), true, false, true)
+	o._putProp("setMilliseconds", r.newNativeFunc(r.dateproto_setMilliseconds, nil, "setMilliseconds", nil, 1), true, false, true)
+	o._putProp("setUTCMilliseconds", r.newNativeFunc(r.dateproto_setUTCMilliseconds, nil, "setUTCMilliseconds", nil, 1), true, false, true)
+	o._putProp("setSeconds", r.newNativeFunc(r.dateproto_setSeconds, nil, "setSeconds", nil, 2), true, false, true)
+	o._putProp("setUTCSeconds", r.newNativeFunc(r.dateproto_setUTCSeconds, nil, "setUTCSeconds", nil, 2), true, false, true)
+	o._putProp("setMinutes", r.newNativeFunc(r.dateproto_setMinutes, nil, "setMinutes", nil, 3), true, false, true)
+	o._putProp("setUTCMinutes", r.newNativeFunc(r.dateproto_setUTCMinutes, nil, "setUTCMinutes", nil, 3), true, false, true)
+	o._putProp("setHours", r.newNativeFunc(r.dateproto_setHours, nil, "setHours", nil, 4), true, false, true)
+	o._putProp("setUTCHours", r.newNativeFunc(r.dateproto_setUTCHours, nil, "setUTCHours", nil, 4), true, false, true)
+	o._putProp("setDate", r.newNativeFunc(r.dateproto_setDate, nil, "setDate", nil, 1), true, false, true)
+	o._putProp("setUTCDate", r.newNativeFunc(r.dateproto_setUTCDate, nil, "setUTCDate", nil, 1), true, false, true)
+	o._putProp("setMonth", r.newNativeFunc(r.dateproto_setMonth, nil, "setMonth", nil, 2), true, false, true)
+	o._putProp("setUTCMonth", r.newNativeFunc(r.dateproto_setUTCMonth, nil, "setUTCMonth", nil, 2), true, false, true)
+	o._putProp("setFullYear", r.newNativeFunc(r.dateproto_setFullYear, nil, "setFullYear", nil, 3), true, false, true)
+	o._putProp("setUTCFullYear", r.newNativeFunc(r.dateproto_setUTCFullYear, nil, "setUTCFullYear", nil, 3), true, false, true)
+	o._putProp("toUTCString", r.newNativeFunc(r.dateproto_toUTCString, nil, "toUTCString", nil, 0), true, false, true)
+	o._putProp("toISOString", r.newNativeFunc(r.dateproto_toISOString, nil, "toISOString", nil, 0), true, false, true)
+	o._putProp("toJSON", r.newNativeFunc(r.dateproto_toJSON, nil, "toJSON", nil, 1), true, false, true)
+
+	return o
+}
+
+func (r *Runtime) createDate(val *Object) objectImpl {
+	o := r.newNativeFuncObj(val, r.builtin_date, r.builtin_newDate, "Date", r.global.DatePrototype, 7)
+
+	o._putProp("parse", r.newNativeFunc(r.date_parse, nil, "parse", nil, 1), true, false, true)
+	o._putProp("UTC", r.newNativeFunc(r.date_UTC, nil, "UTC", nil, 7), true, false, true)
+	o._putProp("now", r.newNativeFunc(r.date_now, nil, "now", nil, 0), true, false, true)
+
+	return o
+}
+
+func (r *Runtime) newLazyObject(create func(*Object) objectImpl) *Object {
+	val := &Object{runtime: r}
+	o := &lazyObject{
+		val:    val,
+		create: create,
+	}
+	val.self = o
+	return val
+}
+
+func (r *Runtime) initDate() {
+	//r.global.DatePrototype = r.newObject()
+	//o := r.global.DatePrototype.self
+	r.global.DatePrototype = r.newLazyObject(r.createDateProto)
+
+	//r.global.Date = r.newNativeFunc(r.builtin_date, r.builtin_newDate, "Date", r.global.DatePrototype, 7)
+	//o := r.global.Date.self
+	r.global.Date = r.newLazyObject(r.createDate)
+
+	r.addToGlobal("Date", r.global.Date)
+}

+ 62 - 0
builtin_error.go

@@ -0,0 +1,62 @@
+package goja
+
+func (r *Runtime) initErrors() {
+	r.global.ErrorPrototype = r.NewObject()
+	o := r.global.ErrorPrototype.self
+	o._putProp("message", stringEmpty, true, false, true)
+	o._putProp("name", stringError, true, false, true)
+	o._putProp("toString", r.newNativeFunc(r.error_toString, nil, "toString", nil, 0), true, false, true)
+
+	r.global.Error = r.newNativeFuncConstruct(r.builtin_Error, "Error", r.global.ErrorPrototype, 1)
+	o = r.global.Error.self
+	r.addToGlobal("Error", r.global.Error)
+
+	r.global.TypeErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.TypeErrorPrototype.self
+	o._putProp("name", stringTypeError, true, false, true)
+
+	r.global.TypeError = r.newNativeFuncConstructProto(r.builtin_Error, "TypeError", r.global.TypeErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("TypeError", r.global.TypeError)
+
+	r.global.ReferenceErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.ReferenceErrorPrototype.self
+	o._putProp("name", stringReferenceError, true, false, true)
+
+	r.global.ReferenceError = r.newNativeFuncConstructProto(r.builtin_Error, "ReferenceError", r.global.ReferenceErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("ReferenceError", r.global.ReferenceError)
+
+	r.global.SyntaxErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.SyntaxErrorPrototype.self
+	o._putProp("name", stringSyntaxError, true, false, true)
+
+	r.global.SyntaxError = r.newNativeFuncConstructProto(r.builtin_Error, "SyntaxError", r.global.SyntaxErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("SyntaxError", r.global.SyntaxError)
+
+	r.global.RangeErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.RangeErrorPrototype.self
+	o._putProp("name", stringRangeError, true, false, true)
+
+	r.global.RangeError = r.newNativeFuncConstructProto(r.builtin_Error, "RangeError", r.global.RangeErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("RangeError", r.global.RangeError)
+
+	r.global.EvalErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.EvalErrorPrototype.self
+	o._putProp("name", stringEvalError, true, false, true)
+
+	r.global.EvalError = r.newNativeFuncConstructProto(r.builtin_Error, "EvalError", r.global.EvalErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("EvalError", r.global.EvalError)
+
+	r.global.URIErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.URIErrorPrototype.self
+	o._putProp("name", stringURIError, true, false, true)
+
+	r.global.URIError = r.newNativeFuncConstructProto(r.builtin_Error, "URIError", r.global.URIErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("URIError", r.global.URIError)
+
+	r.global.GoErrorPrototype = r.builtin_new(r.global.Error, []Value{})
+	o = r.global.GoErrorPrototype.self
+	o._putProp("name", stringGoError, true, false, true)
+
+	r.global.GoError = r.newNativeFuncConstructProto(r.builtin_Error, "GoError", r.global.GoErrorPrototype, r.global.Error, 1)
+	r.addToGlobal("GoError", r.global.GoError)
+}

+ 165 - 0
builtin_function.go

@@ -0,0 +1,165 @@
+package goja
+
+import (
+	"fmt"
+)
+
+func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object {
+	src := "(function anonymous("
+	if len(args) > 1 {
+		for _, arg := range args[:len(args)-1] {
+			src += arg.String() + ","
+		}
+		src = src[:len(src)-1]
+	}
+	body := ""
+	if len(args) > 0 {
+		body = args[len(args)-1].String()
+	}
+	src += "){" + body + "})"
+
+	return r.toObject(r.eval(src, false, false, _undefined))
+}
+
+func (r *Runtime) functionproto_toString(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+repeat:
+	switch f := obj.self.(type) {
+	case *funcObject:
+		return newStringValue(f.src)
+	case *nativeFuncObject:
+		return newStringValue(fmt.Sprintf("function %s() { [native code] }", f.nameProp.get(call.This).ToString()))
+	case *boundFuncObject:
+		return newStringValue(fmt.Sprintf("function %s() { [native code] }", f.nameProp.get(call.This).ToString()))
+	case *lazyObject:
+		obj.self = f.create(obj)
+		goto repeat
+	}
+
+	r.typeErrorResult(true, "Object is not a function")
+	return nil
+}
+
+func (r *Runtime) toValueArray(a Value) []Value {
+	obj := r.toObject(a)
+	l := toUInt32(obj.self.getStr("length"))
+	ret := make([]Value, l)
+	for i := uint32(0); i < l; i++ {
+		ret[i] = obj.self.get(valueInt(i))
+	}
+	return ret
+}
+
+func (r *Runtime) functionproto_apply(call FunctionCall) Value {
+	f := r.toCallable(call.This)
+	var args []Value
+	if len(call.Arguments) >= 2 {
+		args = r.toValueArray(call.Arguments[1])
+	}
+	return f(FunctionCall{
+		This:      call.Argument(0),
+		Arguments: args,
+	})
+}
+
+func (r *Runtime) functionproto_call(call FunctionCall) Value {
+	f := r.toCallable(call.This)
+	var args []Value
+	if len(call.Arguments) > 0 {
+		args = call.Arguments[1:]
+	}
+	return f(FunctionCall{
+		This:      call.Argument(0),
+		Arguments: args,
+	})
+}
+
+func (r *Runtime) boundCallable(target func(FunctionCall) Value, boundArgs []Value) func(FunctionCall) Value {
+	var this Value
+	var args []Value
+	if len(boundArgs) > 0 {
+		this = boundArgs[0]
+		args = make([]Value, len(boundArgs)-1)
+		copy(args, boundArgs[1:])
+	} else {
+		this = _undefined
+	}
+	return func(call FunctionCall) Value {
+		a := append(args, call.Arguments...)
+		return target(FunctionCall{
+			This:      this,
+			Arguments: a,
+		})
+	}
+}
+
+func (r *Runtime) boundConstruct(target func([]Value) *Object, boundArgs []Value) func([]Value) *Object {
+	if target == nil {
+		return nil
+	}
+	var args []Value
+	if len(boundArgs) > 1 {
+		args = make([]Value, len(boundArgs)-1)
+		copy(args, boundArgs[1:])
+	}
+	return func(fargs []Value) *Object {
+		a := append(args, fargs...)
+		copy(a, args)
+		return target(a)
+	}
+}
+
+func (r *Runtime) functionproto_bind(call FunctionCall) Value {
+	obj := r.toObject(call.This)
+	f := obj.self
+	var fcall func(FunctionCall) Value
+	var construct func([]Value) *Object
+repeat:
+	switch ff := f.(type) {
+	case *funcObject:
+		fcall = ff.Call
+		construct = ff.construct
+	case *nativeFuncObject:
+		fcall = ff.f
+		construct = ff.construct
+	case *boundFuncObject:
+		f = &ff.nativeFuncObject
+		goto repeat
+	case *lazyObject:
+		f = ff.create(obj)
+		goto repeat
+	default:
+		r.typeErrorResult(true, "Value is not callable: %s", obj.ToString())
+	}
+
+	l := int(toUInt32(obj.self.getStr("length")))
+	l -= len(call.Arguments) - 1
+	if l < 0 {
+		l = 0
+	}
+
+	v := &Object{runtime: r}
+
+	ff := r.newNativeFuncObj(v, r.boundCallable(fcall, call.Arguments), r.boundConstruct(construct, call.Arguments), "", nil, l)
+	v.self = &boundFuncObject{
+		nativeFuncObject: *ff,
+	}
+
+	//ret := r.newNativeFunc(r.boundCallable(f, call.Arguments), nil, "", nil, l)
+	//o := ret.self
+	//o.putStr("caller", r.global.throwerProperty, false)
+	//o.putStr("arguments", r.global.throwerProperty, false)
+	return v
+}
+
+func (r *Runtime) initFunction() {
+	o := r.global.FunctionPrototype.self
+	o.(*nativeFuncObject).prototype = r.global.ObjectPrototype
+	o._putProp("toString", r.newNativeFunc(r.functionproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("apply", r.newNativeFunc(r.functionproto_apply, nil, "apply", nil, 2), true, false, true)
+	o._putProp("call", r.newNativeFunc(r.functionproto_call, nil, "call", nil, 1), true, false, true)
+	o._putProp("bind", r.newNativeFunc(r.functionproto_bind, nil, "bind", nil, 1), true, false, true)
+
+	r.global.Function = r.newNativeFuncConstruct(r.builtin_Function, "Function", r.global.FunctionPrototype, 1)
+	r.addToGlobal("Function", r.global.Function)
+}

+ 422 - 0
builtin_global.go

@@ -0,0 +1,422 @@
+package goja
+
+import (
+	"errors"
+	"io"
+	"math"
+	"regexp"
+	"strconv"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+var (
+	parseFloatRegexp = regexp.MustCompile(`^([+-]?(?:Infinity|[0-9]*\.?[0-9]*(?:[eE][+-]?[0-9]+)?))`)
+)
+
+func (r *Runtime) builtin_isNaN(call FunctionCall) Value {
+	if math.IsNaN(call.Argument(0).ToFloat()) {
+		return valueTrue
+	} else {
+		return valueFalse
+	}
+}
+
+func (r *Runtime) builtin_parseInt(call FunctionCall) Value {
+	str := call.Argument(0).ToString().toTrimmedUTF8()
+	radix := int(toInt32(call.Argument(1)))
+	v, _ := parseInt(str, radix)
+	return v
+}
+
+func (r *Runtime) builtin_parseFloat(call FunctionCall) Value {
+	m := parseFloatRegexp.FindStringSubmatch(call.Argument(0).ToString().toTrimmedUTF8())
+	if len(m) == 2 {
+		if s := m[1]; s != "" && s != "+" && s != "-" {
+			switch s {
+			case "+", "-":
+			case "Infinity", "+Infinity":
+				return _positiveInf
+			case "-Infinity":
+				return _negativeInf
+			default:
+				f, err := strconv.ParseFloat(s, 64)
+				if err == nil || isRangeErr(err) {
+					return floatToValue(f)
+				}
+			}
+		}
+	}
+	return _NaN
+}
+
+func (r *Runtime) builtin_isFinite(call FunctionCall) Value {
+	f := call.Argument(0).ToFloat()
+	if math.IsNaN(f) || math.IsInf(f, 0) {
+		return valueFalse
+	}
+	return valueTrue
+}
+
+func (r *Runtime) _encode(uriString valueString, unescaped *[256]bool) valueString {
+	reader := uriString.reader(0)
+	utf8Buf := make([]byte, utf8.UTFMax)
+	needed := false
+	l := 0
+	for {
+		rn, _, err := reader.ReadRune()
+		if err != nil {
+			if err != io.EOF {
+				panic(r.newError(r.global.URIError, "Malformed URI"))
+			}
+			break
+		}
+
+		if rn >= utf8.RuneSelf {
+			needed = true
+			l += utf8.EncodeRune(utf8Buf, rn) * 3
+		} else if !unescaped[rn] {
+			needed = true
+			l += 3
+		} else {
+			l++
+		}
+	}
+
+	if !needed {
+		return uriString
+	}
+
+	buf := make([]byte, l)
+	i := 0
+	reader = uriString.reader(0)
+	for {
+		rn, _, err := reader.ReadRune()
+		if err != nil {
+			break
+		}
+
+		if rn >= utf8.RuneSelf {
+			n := utf8.EncodeRune(utf8Buf, rn)
+			for _, b := range utf8Buf[:n] {
+				buf[i] = '%'
+				buf[i+1] = "0123456789ABCDEF"[b>>4]
+				buf[i+2] = "0123456789ABCDEF"[b&15]
+				i += 3
+			}
+		} else if !unescaped[rn] {
+			buf[i] = '%'
+			buf[i+1] = "0123456789ABCDEF"[rn>>4]
+			buf[i+2] = "0123456789ABCDEF"[rn&15]
+			i += 3
+		} else {
+			buf[i] = byte(rn)
+			i++
+		}
+	}
+	return asciiString(string(buf))
+}
+
+func (r *Runtime) _decode(sv valueString, reservedSet *[256]bool) valueString {
+	s := sv.String()
+	hexCount := 0
+	for i := 0; i < len(s); {
+		switch s[i] {
+		case '%':
+			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
+				panic(r.newError(r.global.URIError, "Malformed URI"))
+			}
+			c := unhex(s[i+1])<<4 | unhex(s[i+2])
+			if !reservedSet[c] {
+				hexCount++
+			}
+			i += 3
+		default:
+			i++
+		}
+	}
+
+	if hexCount == 0 {
+		return sv
+	}
+
+	t := make([]byte, len(s)-hexCount*2)
+	j := 0
+	isUnicode := false
+	for i := 0; i < len(s); {
+		ch := s[i]
+		switch ch {
+		case '%':
+			c := unhex(s[i+1])<<4 | unhex(s[i+2])
+			if reservedSet[c] {
+				t[j] = s[i]
+				t[j+1] = s[i+1]
+				t[j+2] = s[i+2]
+				j += 3
+			} else {
+				t[j] = c
+				if c >= utf8.RuneSelf {
+					isUnicode = true
+				}
+				j++
+			}
+			i += 3
+		default:
+			if ch >= utf8.RuneSelf {
+				isUnicode = true
+			}
+			t[j] = ch
+			j++
+			i++
+		}
+	}
+
+	if !isUnicode {
+		return asciiString(t)
+	}
+
+	us := make([]rune, 0, len(s))
+	for len(t) > 0 {
+		rn, size := utf8.DecodeRune(t)
+		if rn == utf8.RuneError {
+			if size != 3 || t[0] != 0xef || t[1] != 0xbf || t[2] != 0xbd {
+				panic(r.newError(r.global.URIError, "Malformed URI"))
+			}
+		}
+		us = append(us, rn)
+		t = t[size:]
+	}
+	return unicodeString(utf16.Encode(us))
+}
+
+func ishex(c byte) bool {
+	switch {
+	case '0' <= c && c <= '9':
+		return true
+	case 'a' <= c && c <= 'f':
+		return true
+	case 'A' <= c && c <= 'F':
+		return true
+	}
+	return false
+}
+
+func unhex(c byte) byte {
+	switch {
+	case '0' <= c && c <= '9':
+		return c - '0'
+	case 'a' <= c && c <= 'f':
+		return c - 'a' + 10
+	case 'A' <= c && c <= 'F':
+		return c - 'A' + 10
+	}
+	return 0
+}
+
+func (r *Runtime) builtin_decodeURI(call FunctionCall) Value {
+	uriString := call.Argument(0).ToString()
+	return r._decode(uriString, &uriReservedHash)
+}
+
+func (r *Runtime) builtin_decodeURIComponent(call FunctionCall) Value {
+	uriString := call.Argument(0).ToString()
+	return r._decode(uriString, &emptyEscapeSet)
+}
+
+func (r *Runtime) builtin_encodeURI(call FunctionCall) Value {
+	uriString := call.Argument(0).ToString()
+	return r._encode(uriString, &uriReservedUnescapedHash)
+}
+
+func (r *Runtime) builtin_encodeURIComponent(call FunctionCall) Value {
+	uriString := call.Argument(0).ToString()
+	return r._encode(uriString, &uriUnescaped)
+}
+
+func (r *Runtime) initGlobalObject() {
+	o := r.globalObject.self
+	o._putProp("NaN", _NaN, false, false, false)
+	o._putProp("undefined", _undefined, false, false, false)
+	o._putProp("Infinity", _positiveInf, false, false, false)
+
+	o._putProp("isNaN", r.newNativeFunc(r.builtin_isNaN, nil, "isNaN", nil, 1), true, false, true)
+	o._putProp("parseInt", r.newNativeFunc(r.builtin_parseInt, nil, "parseInt", nil, 2), true, false, true)
+	o._putProp("parseFloat", r.newNativeFunc(r.builtin_parseFloat, nil, "parseFloat", nil, 1), true, false, true)
+	o._putProp("isFinite", r.newNativeFunc(r.builtin_isFinite, nil, "isFinite", nil, 1), true, false, true)
+	o._putProp("decodeURI", r.newNativeFunc(r.builtin_decodeURI, nil, "decodeURI", nil, 1), true, false, true)
+	o._putProp("decodeURIComponent", r.newNativeFunc(r.builtin_decodeURIComponent, nil, "decodeURIComponent", nil, 1), true, false, true)
+	o._putProp("encodeURI", r.newNativeFunc(r.builtin_encodeURI, nil, "encodeURI", nil, 1), true, false, true)
+	o._putProp("encodeURIComponent", r.newNativeFunc(r.builtin_encodeURIComponent, nil, "encodeURIComponent", nil, 1), true, false, true)
+
+	o._putProp("toString", r.newNativeFunc(func(FunctionCall) Value {
+		return stringGlobalObject
+	}, nil, "toString", nil, 0), false, false, false)
+
+	// TODO: Annex B
+
+}
+
+func digitVal(d byte) int {
+	var v byte
+	switch {
+	case '0' <= d && d <= '9':
+		v = d - '0'
+	case 'a' <= d && d <= 'z':
+		v = d - 'a' + 10
+	case 'A' <= d && d <= 'Z':
+		v = d - 'A' + 10
+	default:
+		return 36
+	}
+	return int(v)
+}
+
+// ECMAScript compatible version of strconv.ParseInt
+func parseInt(s string, base int) (Value, error) {
+	var n int64
+	var err error
+	var cutoff, maxVal int64
+	var sign bool
+	i := 0
+
+	if len(s) < 1 {
+		err = strconv.ErrSyntax
+		goto Error
+	}
+
+	switch s[0] {
+	case '-':
+		sign = true
+		s = s[1:]
+	case '+':
+		s = s[1:]
+	}
+
+	if len(s) < 1 {
+		err = strconv.ErrSyntax
+		goto Error
+	}
+
+	// Look for hex prefix.
+	if s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X') {
+		if base == 0 || base == 16 {
+			base = 16
+			s = s[2:]
+		}
+	}
+
+	switch {
+	case len(s) < 1:
+		err = strconv.ErrSyntax
+		goto Error
+
+	case 2 <= base && base <= 36:
+	// valid base; nothing to do
+
+	case base == 0:
+		// Look for hex prefix.
+		switch {
+		case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
+			if len(s) < 3 {
+				err = strconv.ErrSyntax
+				goto Error
+			}
+			base = 16
+			s = s[2:]
+		default:
+			base = 10
+		}
+
+	default:
+		err = errors.New("invalid base " + strconv.Itoa(base))
+		goto Error
+	}
+
+	// Cutoff is the smallest number such that cutoff*base > maxInt64.
+	// Use compile-time constants for common cases.
+	switch base {
+	case 10:
+		cutoff = math.MaxInt64/10 + 1
+	case 16:
+		cutoff = math.MaxInt64/16 + 1
+	default:
+		cutoff = math.MaxInt64/int64(base) + 1
+	}
+
+	maxVal = math.MaxInt64
+	for ; i < len(s); i++ {
+		if n >= cutoff {
+			// n*base overflows
+			return parseLargeInt(float64(n), s[i:], base, sign)
+		}
+		v := digitVal(s[i])
+		if v >= base {
+			break
+		}
+		n *= int64(base)
+
+		n1 := n + int64(v)
+		if n1 < n || n1 > maxVal {
+			// n+v overflows
+			return parseLargeInt(float64(n)+float64(v), s[i+1:], base, sign)
+		}
+		n = n1
+	}
+
+	if i == 0 {
+		err = strconv.ErrSyntax
+		goto Error
+	}
+
+	if sign {
+		n = -n
+	}
+	return intToValue(n), nil
+
+Error:
+	return _NaN, err
+}
+
+func parseLargeInt(n float64, s string, base int, sign bool) (Value, error) {
+	i := 0
+	b := float64(base)
+	for ; i < len(s); i++ {
+		v := digitVal(s[i])
+		if v >= base {
+			break
+		}
+		n = n*b + float64(v)
+	}
+	if sign {
+		n = -n
+	}
+	// We know it can't be represented as int, so use valueFloat instead of floatToValue
+	return valueFloat(n), nil
+}
+
+var (
+	uriUnescaped             [256]bool
+	uriReserved              [256]bool
+	uriReservedHash          [256]bool
+	uriReservedUnescapedHash [256]bool
+	emptyEscapeSet           [256]bool
+)
+
+func init() {
+	for _, c := range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()" {
+		uriUnescaped[c] = true
+	}
+
+	for _, c := range ";/?:@&=+$," {
+		uriReserved[c] = true
+	}
+
+	for i := 0; i < 256; i++ {
+		if uriUnescaped[i] || uriReserved[i] {
+			uriReservedUnescapedHash[i] = true
+		}
+		uriReservedHash[i] = uriReserved[i]
+	}
+	uriReservedUnescapedHash['#'] = true
+	uriReservedHash['#'] = true
+}

+ 21 - 0
builtin_global_test.go

@@ -0,0 +1,21 @@
+package goja
+
+import (
+	"testing"
+)
+
+func TestEncodeURI(t *testing.T) {
+	const SCRIPT = `
+	encodeURI('тест')
+	`
+
+	testScript1(SCRIPT, asciiString("%D1%82%D0%B5%D1%81%D1%82"), t)
+}
+
+func TestDecodeURI(t *testing.T) {
+	const SCRIPT = `
+	decodeURI("http://ru.wikipedia.org/wiki/%d0%ae%D0%bd%D0%B8%D0%BA%D0%BE%D0%B4")
+	`
+
+	testScript1(SCRIPT, newStringValue("http://ru.wikipedia.org/wiki/Юникод"), t)
+}

+ 446 - 0
builtin_json.go

@@ -0,0 +1,446 @@
+package goja
+
+import (
+	"bytes"
+	"encoding/json"
+	"strings"
+)
+
+var hex = "0123456789abcdef"
+
+func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
+	var reviver func(FunctionCall) Value
+
+	if arg1 := call.Argument(1); arg1 != _undefined {
+		reviver, _ = arg1.ToObject(r).self.assertCallable()
+	}
+
+	var root interface{}
+	err := json.Unmarshal([]byte(call.Argument(0).String()), &root)
+
+	if err != nil {
+		panic(r.newError(r.global.SyntaxError, err.Error()))
+	}
+
+	value, exists := r.builtinJSON_parseWalk(root)
+	if !exists {
+		value = _undefined
+	}
+	if reviver != nil {
+		root := r.NewObject()
+		root.self.putStr("", value, false)
+		return r.builtinJSON_reviveWalk(reviver, root, stringEmpty)
+	}
+	return value
+}
+
+func (r *Runtime) builtinJSON_parseWalk(rawValue interface{}) (Value, bool) {
+	switch value := rawValue.(type) {
+	case nil:
+		return _null, true
+	case bool:
+		if value {
+			return valueTrue, true
+		} else {
+			return valueFalse, true
+		}
+	case string:
+		return newStringValue(value), true
+	case float64:
+		return floatToValue(value), true
+	case []interface{}:
+		arrayValue := make([]Value, len(value))
+		for index, rawValue := range value {
+			if value, exists := r.builtinJSON_parseWalk(rawValue); exists {
+				arrayValue[index] = value
+			}
+		}
+		return r.newArrayValues(arrayValue), true
+	case map[string]interface{}:
+		object := r.NewObject()
+		for name, rawValue := range value {
+			if value, exists := r.builtinJSON_parseWalk(rawValue); exists {
+				if name == "__proto__" {
+					descr := r.NewObject().self
+					descr.putStr("value", value, false)
+					descr.putStr("writable", valueTrue, false)
+					descr.putStr("enumerable", valueTrue, false)
+					descr.putStr("configurable", valueTrue, false)
+					object.self.defineOwnProperty(string__proto__, descr, false)
+				} else {
+					object.self.putStr(name, value, false)
+				}
+			}
+		}
+		return object, true
+	}
+	return _undefined, false
+}
+
+func isArray(object *Object) bool {
+	switch object.self.className() {
+	case classArray:
+		return true
+	default:
+		return false
+	}
+}
+
+func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value {
+	value := holder.self.get(name)
+	if value == nil {
+		value = _undefined
+	}
+
+	if object := value.(*Object); object != nil {
+		if isArray(object) {
+			length := object.self.getStr("length").ToInteger()
+			for index := int64(0); index < length; index++ {
+				name := intToValue(index)
+				value := r.builtinJSON_reviveWalk(reviver, object, name)
+				if value == _undefined {
+					object.self.delete(name, false)
+				} else {
+					object.self.put(name, value, false)
+				}
+			}
+		} else {
+			for item, f := object.self.enumerate(false, false)(); f != nil; item, f = f() {
+				value := r.builtinJSON_reviveWalk(reviver, object, name)
+				if value == _undefined {
+					object.self.deleteStr(item.name, false)
+				} else {
+					object.self.putStr(item.name, value, false)
+				}
+			}
+		}
+	}
+	return reviver(FunctionCall{
+		This:      holder,
+		Arguments: []Value{name, value},
+	})
+}
+
+type _builtinJSON_stringifyContext struct {
+	r                *Runtime
+	stack            []*Object
+	propertyList     []Value
+	replacerFunction func(FunctionCall) Value
+	gap, indent      string
+	buf              bytes.Buffer
+}
+
+func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
+	ctx := _builtinJSON_stringifyContext{
+		r: r,
+	}
+
+	replacer, _ := call.Argument(1).(*Object)
+	if replacer != nil {
+		if isArray(replacer) {
+			length := replacer.self.getStr("length").ToInteger()
+			seen := map[string]bool{}
+			propertyList := make([]Value, length)
+			length = 0
+			for index, _ := range propertyList {
+				var name string
+				value := replacer.self.get(intToValue(int64(index)))
+				if s, ok := value.assertString(); ok {
+					name = s.String()
+				} else if _, ok := value.assertInt(); ok {
+					name = value.String()
+				} else if _, ok := value.assertFloat(); ok {
+					name = value.String()
+				} else if o, ok := value.(*Object); ok {
+					switch o.self.className() {
+					case classNumber, classString:
+						name = value.String()
+					}
+				}
+				if seen[name] {
+					continue
+				}
+				seen[name] = true
+				length += 1
+				propertyList[index] = newStringValue(name)
+			}
+			ctx.propertyList = propertyList[0:length]
+		} else if c, ok := replacer.self.assertCallable(); ok {
+			ctx.replacerFunction = c
+		}
+	}
+	if spaceValue := call.Argument(2); spaceValue != _undefined {
+		if o, ok := spaceValue.(*Object); ok {
+			switch o := o.self.(type) {
+			case *primitiveValueObject:
+				spaceValue = o.pValue
+			case *stringObject:
+				spaceValue = o.value
+			}
+		}
+		isNum := false
+		var num int64
+		num, isNum = spaceValue.assertInt()
+		if !isNum {
+			if f, ok := spaceValue.assertFloat(); ok {
+				num = int64(f)
+				isNum = true
+			}
+		}
+		if isNum {
+			if num > 0 {
+				if num > 10 {
+					num = 10
+				}
+				ctx.gap = strings.Repeat(" ", int(num))
+			}
+		} else {
+			if s, ok := spaceValue.assertString(); ok {
+				str := s.String()
+				if len(str) > 10 {
+					ctx.gap = str[:10]
+				} else {
+					ctx.gap = str
+				}
+			}
+		}
+	}
+
+	holder := r.NewObject()
+	holder.self.putStr("", call.Argument(0), false)
+	if ctx.str(stringEmpty, holder) {
+		return newStringValue(ctx.buf.String())
+	}
+	return _undefined
+}
+
+func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
+	value := holder.self.get(key)
+	if value == nil {
+		value = _undefined
+	}
+
+	if object, ok := value.(*Object); ok {
+		if toJSON, ok := object.self.getStr("toJSON").(*Object); ok {
+			if c, ok := toJSON.self.assertCallable(); ok {
+				value = c(FunctionCall{
+					This:      value,
+					Arguments: []Value{key},
+				})
+			}
+		} /*else {
+			// If the object is a GoStruct or something that implements json.Marshaler
+			if object.objectClass.marshalJSON != nil {
+				marshaler := object.objectClass.marshalJSON(object)
+				if marshaler != nil {
+					return marshaler, true
+				}
+			}
+		}*/
+	}
+
+	if ctx.replacerFunction != nil {
+		value = ctx.replacerFunction(FunctionCall{
+			This:      holder,
+			Arguments: []Value{key, value},
+		})
+	}
+
+	if o, ok := value.(*Object); ok {
+		switch o1 := o.self.(type) {
+		case *primitiveValueObject:
+			value = o1.pValue
+		case *stringObject:
+			value = o1.value
+		case *objectGoReflect:
+			switch o.self.className() {
+			case classNumber:
+				value = o1.toPrimitiveNumber()
+			case classString:
+				value = o1.toPrimitiveString()
+			case classBoolean:
+				if o.ToInteger() != 0 {
+					value = valueTrue
+				} else {
+					value = valueFalse
+				}
+			}
+		}
+	}
+
+	switch value1 := value.(type) {
+	case valueBool:
+		if value1 {
+			ctx.buf.WriteString("true")
+		} else {
+			ctx.buf.WriteString("false")
+		}
+	case valueString:
+		ctx.quote(value1)
+	case valueInt, valueFloat:
+		ctx.buf.WriteString(value.String())
+	case valueNull:
+		ctx.buf.WriteString("null")
+	case *Object:
+		for _, object := range ctx.stack {
+			if value1 == object {
+				ctx.r.typeErrorResult(true, "Converting circular structure to JSON")
+			}
+		}
+		ctx.stack = append(ctx.stack, value1)
+		defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
+		if _, ok := value1.self.assertCallable(); !ok {
+			if isArray(value1) {
+				ctx.ja(value1)
+			} else {
+				ctx.jo(value1)
+			}
+		} else {
+			return false
+		}
+	default:
+		return false
+	}
+	return true
+}
+
+func (ctx *_builtinJSON_stringifyContext) ja(array *Object) {
+	var stepback string
+	if ctx.gap != "" {
+		stepback = ctx.indent
+		ctx.indent += ctx.gap
+	}
+	length := array.self.getStr("length").ToInteger()
+	if length == 0 {
+		ctx.buf.WriteString("[]")
+		return
+	}
+
+	ctx.buf.WriteByte('[')
+	var separator string
+	if ctx.gap != "" {
+		ctx.buf.WriteByte('\n')
+		ctx.buf.WriteString(ctx.indent)
+		separator = ",\n" + ctx.indent
+	} else {
+		separator = ","
+	}
+
+	for i := int64(0); i < length; i++ {
+		if !ctx.str(intToValue(i), array) {
+			ctx.buf.WriteString("null")
+		}
+		if i < length-1 {
+			ctx.buf.WriteString(separator)
+		}
+	}
+	if ctx.gap != "" {
+		ctx.buf.WriteByte('\n')
+		ctx.buf.WriteString(stepback)
+		ctx.indent = stepback
+	}
+	ctx.buf.WriteByte(']')
+}
+
+func (ctx *_builtinJSON_stringifyContext) jo(object *Object) {
+	var stepback string
+	if ctx.gap != "" {
+		stepback = ctx.indent
+		ctx.indent += ctx.gap
+	}
+
+	ctx.buf.WriteByte('{')
+	mark := ctx.buf.Len()
+	var separator string
+	if ctx.gap != "" {
+		ctx.buf.WriteByte('\n')
+		ctx.buf.WriteString(ctx.indent)
+		separator = ",\n" + ctx.indent
+	} else {
+		separator = ","
+	}
+
+	var props []Value
+	if ctx.propertyList == nil {
+		for item, f := object.self.enumerate(false, false)(); f != nil; item, f = f() {
+			props = append(props, newStringValue(item.name))
+		}
+	} else {
+		props = ctx.propertyList
+	}
+
+	empty := true
+	for _, name := range props {
+		off := ctx.buf.Len()
+		if !empty {
+			ctx.buf.WriteString(separator)
+		}
+		ctx.quote(name.ToString())
+		if ctx.gap != "" {
+			ctx.buf.WriteString(": ")
+		} else {
+			ctx.buf.WriteByte(':')
+		}
+		if ctx.str(name, object) {
+			if empty {
+				empty = false
+			}
+		} else {
+			ctx.buf.Truncate(off)
+		}
+	}
+
+	if empty {
+		ctx.buf.Truncate(mark)
+	} else {
+		if ctx.gap != "" {
+			ctx.buf.WriteByte('\n')
+			ctx.buf.WriteString(stepback)
+			ctx.indent = stepback
+		}
+	}
+	ctx.buf.WriteByte('}')
+}
+
+func (ctx *_builtinJSON_stringifyContext) quote(str valueString) {
+	ctx.buf.WriteByte('"')
+	reader := str.reader(0)
+	for {
+		r, _, err := reader.ReadRune()
+		if err != nil {
+			break
+		}
+		if r < 0x0020 {
+			switch r {
+			case '"', '\\':
+				ctx.buf.WriteByte('\\')
+				ctx.buf.WriteByte(byte(r))
+			case 0x0008:
+				ctx.buf.WriteString("\\b")
+			case 0x0009:
+				ctx.buf.WriteString("\\t")
+			case 0x000A:
+				ctx.buf.WriteString("\\n")
+			case 0x000C:
+				ctx.buf.WriteString("\\f")
+			case 0x000D:
+				ctx.buf.WriteString("\\r")
+			default:
+				ctx.buf.WriteString(`\u00`)
+				ctx.buf.WriteByte(hex[r>>4])
+				ctx.buf.WriteByte(hex[r&0xF])
+			}
+		} else {
+			ctx.buf.WriteRune(r)
+		}
+	}
+	ctx.buf.WriteByte('"')
+}
+
+func (r *Runtime) initJSON() {
+	JSON := r.newBaseObject(r.global.ObjectPrototype, "JSON")
+	JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, nil, "parse", nil, 2), true, false, true)
+	JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, nil, "stringify", nil, 3), true, false, true)
+
+	r.addToGlobal("JSON", JSON.val)
+}

+ 192 - 0
builtin_math.go

@@ -0,0 +1,192 @@
+package goja
+
+import (
+	"math"
+)
+
+func (r *Runtime) math_abs(call FunctionCall) Value {
+	return floatToValue(math.Abs(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_acos(call FunctionCall) Value {
+	return floatToValue(math.Acos(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_asin(call FunctionCall) Value {
+	return floatToValue(math.Asin(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_atan(call FunctionCall) Value {
+	return floatToValue(math.Atan(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_atan2(call FunctionCall) Value {
+	y := call.Argument(0).ToFloat()
+	x := call.Argument(1).ToFloat()
+
+	return floatToValue(math.Atan2(y, x))
+}
+
+func (r *Runtime) math_ceil(call FunctionCall) Value {
+	return floatToValue(math.Ceil(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_cos(call FunctionCall) Value {
+	return floatToValue(math.Cos(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_exp(call FunctionCall) Value {
+	return floatToValue(math.Exp(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_floor(call FunctionCall) Value {
+	return floatToValue(math.Floor(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_log(call FunctionCall) Value {
+	return floatToValue(math.Log(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_max(call FunctionCall) Value {
+	if len(call.Arguments) == 0 {
+		return _negativeInf
+	}
+
+	result := call.Arguments[0].ToFloat()
+	if math.IsNaN(result) {
+		return _NaN
+	}
+	for _, arg := range call.Arguments[1:] {
+		f := arg.ToFloat()
+		if math.IsNaN(f) {
+			return _NaN
+		}
+		result = math.Max(result, f)
+	}
+	return floatToValue(result)
+}
+
+func (r *Runtime) math_min(call FunctionCall) Value {
+	if len(call.Arguments) == 0 {
+		return _positiveInf
+	}
+
+	result := call.Arguments[0].ToFloat()
+	if math.IsNaN(result) {
+		return _NaN
+	}
+	for _, arg := range call.Arguments[1:] {
+		f := arg.ToFloat()
+		if math.IsNaN(f) {
+			return _NaN
+		}
+		result = math.Min(result, f)
+	}
+	return floatToValue(result)
+}
+
+func (r *Runtime) math_pow(call FunctionCall) Value {
+	x := call.Argument(0)
+	y := call.Argument(1)
+	if x, ok := x.assertInt(); ok {
+		if y, ok := y.assertInt(); ok && y >= 0 && y < 64 {
+			if y == 0 {
+				return intToValue(1)
+			}
+			if x == 0 {
+				return intToValue(0)
+			}
+			ip := ipow(x, y)
+			if ip != 0 {
+				return intToValue(ip)
+			}
+		}
+	}
+
+	return floatToValue(math.Pow(x.ToFloat(), y.ToFloat()))
+}
+
+func (r *Runtime) math_random(call FunctionCall) Value {
+	return floatToValue(r.rand())
+}
+
+func (r *Runtime) math_round(call FunctionCall) Value {
+	f := call.Argument(0).ToFloat()
+	if math.IsNaN(f) {
+		return _NaN
+	}
+
+	if f == 0 && math.Signbit(f) {
+		return _negativeZero
+	}
+
+	t := math.Trunc(f)
+
+	if f >= 0 {
+		if f-t >= 0.5 {
+			return floatToValue(t + 1)
+		}
+	} else {
+		if t-f > 0.5 {
+			return floatToValue(t - 1)
+		}
+	}
+
+	return floatToValue(t)
+}
+
+func (r *Runtime) math_sin(call FunctionCall) Value {
+	return floatToValue(math.Sin(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_sqrt(call FunctionCall) Value {
+	return floatToValue(math.Sqrt(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) math_tan(call FunctionCall) Value {
+	return floatToValue(math.Tan(call.Argument(0).ToFloat()))
+}
+
+func (r *Runtime) createMath(val *Object) objectImpl {
+	m := &baseObject{
+		class:      "Math",
+		val:        val,
+		extensible: true,
+		prototype:  r.global.ObjectPrototype,
+	}
+	m.init()
+
+	m._putProp("E", valueFloat(math.E), false, false, false)
+	m._putProp("LN10", valueFloat(math.Ln10), false, false, false)
+	m._putProp("LN2", valueFloat(math.Ln2), false, false, false)
+	m._putProp("LOG2E", valueFloat(math.Log2E), false, false, false)
+	m._putProp("LOG10E", valueFloat(math.Log10E), false, false, false)
+	m._putProp("PI", valueFloat(math.Pi), false, false, false)
+	m._putProp("SQRT1_2", valueFloat(sqrt1_2), false, false, false)
+	m._putProp("SQRT2", valueFloat(math.Sqrt2), false, false, false)
+
+	m._putProp("abs", r.newNativeFunc(r.math_abs, nil, "abs", nil, 1), true, false, true)
+	m._putProp("acos", r.newNativeFunc(r.math_acos, nil, "acos", nil, 1), true, false, true)
+	m._putProp("asin", r.newNativeFunc(r.math_asin, nil, "asin", nil, 1), true, false, true)
+	m._putProp("atan", r.newNativeFunc(r.math_atan, nil, "atan", nil, 1), true, false, true)
+	m._putProp("atan2", r.newNativeFunc(r.math_atan2, nil, "atan2", nil, 2), true, false, true)
+	m._putProp("ceil", r.newNativeFunc(r.math_ceil, nil, "ceil", nil, 1), true, false, true)
+	m._putProp("cos", r.newNativeFunc(r.math_cos, nil, "cos", nil, 1), true, false, true)
+	m._putProp("exp", r.newNativeFunc(r.math_exp, nil, "exp", nil, 1), true, false, true)
+	m._putProp("floor", r.newNativeFunc(r.math_floor, nil, "floor", nil, 1), true, false, true)
+	m._putProp("log", r.newNativeFunc(r.math_log, nil, "log", nil, 1), true, false, true)
+	m._putProp("max", r.newNativeFunc(r.math_max, nil, "max", nil, 2), true, false, true)
+	m._putProp("min", r.newNativeFunc(r.math_min, nil, "min", nil, 2), true, false, true)
+	m._putProp("pow", r.newNativeFunc(r.math_pow, nil, "pow", nil, 2), true, false, true)
+	m._putProp("random", r.newNativeFunc(r.math_random, nil, "random", nil, 0), true, false, true)
+	m._putProp("round", r.newNativeFunc(r.math_round, nil, "round", nil, 1), true, false, true)
+	m._putProp("sin", r.newNativeFunc(r.math_sin, nil, "sin", nil, 1), true, false, true)
+	m._putProp("sqrt", r.newNativeFunc(r.math_sqrt, nil, "sqrt", nil, 1), true, false, true)
+	m._putProp("tan", r.newNativeFunc(r.math_tan, nil, "tan", nil, 1), true, false, true)
+
+	return m
+}
+
+func (r *Runtime) initMath() {
+	r.addToGlobal("Math", r.newLazyObject(r.createMath))
+}

+ 154 - 0
builtin_number.go

@@ -0,0 +1,154 @@
+package goja
+
+import (
+	"math"
+	"strconv"
+)
+
+func (r *Runtime) numberproto_valueOf(call FunctionCall) Value {
+	this := call.This
+	if !isNumber(this) {
+		r.typeErrorResult(true, "Value is not a number")
+	}
+	if _, ok := this.assertInt(); ok {
+		return this
+	}
+
+	if _, ok := this.assertFloat(); ok {
+		return this
+	}
+
+	if obj, ok := this.(*Object); ok {
+		if v, ok := obj.self.(*primitiveValueObject); ok {
+			return v.pValue
+		}
+	}
+
+	r.typeErrorResult(true, "Number.prototype.valueOf is not generic")
+	return nil
+}
+
+func isNumber(v Value) bool {
+	switch t := v.(type) {
+	case valueFloat, valueInt:
+		return true
+	case *Object:
+		switch t := t.self.(type) {
+		case *primitiveValueObject:
+			return isNumber(t.pValue)
+		}
+	}
+	return false
+}
+
+func (r *Runtime) numberproto_toString(call FunctionCall) Value {
+	if !isNumber(call.This) {
+		r.typeErrorResult(true, "Value is not a number")
+	}
+	var radix int
+	if arg := call.Argument(0); arg != _undefined {
+		radix = int(arg.ToInteger())
+	} else {
+		radix = 10
+	}
+
+	if radix < 2 || radix > 36 {
+		panic(r.newError(r.global.RangeError, "toString() radix argument must be between 2 and 36"))
+	}
+
+	num := call.This.ToFloat()
+
+	if math.IsNaN(num) {
+		return stringNaN
+	}
+
+	if math.IsInf(num, 1) {
+		return stringInfinity
+	}
+
+	if math.IsInf(num, -1) {
+		return stringNegInfinity
+	}
+
+	if radix == 10 {
+		var fmt byte
+		if math.Abs(num) >= 1e21 {
+			fmt = 'e'
+		} else {
+			fmt = 'f'
+		}
+		return asciiString(strconv.FormatFloat(num, fmt, -1, 64))
+	}
+
+	return asciiString(dtobasestr(num, radix))
+}
+
+func (r *Runtime) numberproto_toFixed(call FunctionCall) Value {
+	prec := call.Argument(0).ToInteger()
+	if prec < 0 || prec > 20 {
+		panic(r.newError(r.global.RangeError, "toFixed() precision must be between 0 and 20"))
+	}
+
+	num := call.This.ToFloat()
+	if math.IsNaN(num) {
+		return stringNaN
+	}
+	if math.Abs(num) >= 1e21 {
+		return asciiString(strconv.FormatFloat(num, 'g', -1, 64))
+	}
+	return asciiString(strconv.FormatFloat(num, 'f', int(prec), 64))
+}
+
+func (r *Runtime) numberproto_toExponential(call FunctionCall) Value {
+	prec := call.Argument(0).ToInteger()
+	if prec < 0 || prec > 20 {
+		panic(r.newError(r.global.RangeError, "toExponential() precision must be between 0 and 20"))
+	}
+
+	num := call.This.ToFloat()
+	if math.IsNaN(num) {
+		return stringNaN
+	}
+	if math.Abs(num) >= 1e21 {
+		return asciiString(strconv.FormatFloat(num, 'g', -1, 64))
+	}
+	return asciiString(strconv.FormatFloat(num, 'e', int(prec), 64))
+}
+
+func (r *Runtime) numberproto_toPrecision(call FunctionCall) Value {
+	prec := call.Argument(0).ToInteger()
+	if prec < 0 || prec > 20 {
+		panic(r.newError(r.global.RangeError, "toPrecision() precision must be between 0 and 20"))
+	}
+
+	num := call.This.ToFloat()
+	if math.IsNaN(num) {
+		return stringNaN
+	}
+	if math.Abs(num) >= 1e21 {
+		return asciiString(strconv.FormatFloat(num, 'g', -1, 64))
+	}
+	return asciiString(strconv.FormatFloat(num, 'g', int(prec), 64))
+}
+
+func (r *Runtime) initNumber() {
+	r.global.NumberPrototype = r.newPrimitiveObject(valueInt(0), r.global.ObjectPrototype, classNumber)
+	o := r.global.NumberPrototype.self
+	o._putProp("valueOf", r.newNativeFunc(r.numberproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+	o._putProp("toString", r.newNativeFunc(r.numberproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("toLocaleString", r.newNativeFunc(r.numberproto_toString, nil, "toLocaleString", nil, 0), true, false, true)
+	o._putProp("toFixed", r.newNativeFunc(r.numberproto_toFixed, nil, "toFixed", nil, 1), true, false, true)
+	o._putProp("toExponential", r.newNativeFunc(r.numberproto_toExponential, nil, "toExponential", nil, 1), true, false, true)
+	o._putProp("toPrecision", r.newNativeFunc(r.numberproto_toPrecision, nil, "toPrecision", nil, 1), true, false, true)
+
+	r.global.Number = r.newNativeFunc(r.builtin_Number, r.builtin_newNumber, "Number", r.global.NumberPrototype, 1)
+	o = r.global.Number.self
+	o._putProp("MAX_VALUE", valueFloat(math.MaxFloat64), false, false, false)
+	o._putProp("MIN_VALUE", valueFloat(math.SmallestNonzeroFloat64), false, false, false)
+	o._putProp("NaN", _NaN, false, false, false)
+	o._putProp("NEGATIVE_INFINITY", _negativeInf, false, false, false)
+	o._putProp("POSITIVE_INFINITY", _positiveInf, false, false, false)
+	o._putProp("EPSILON", _epsilon, false, false, false)
+	r.addToGlobal("Number", r.global.Number)
+
+}

+ 411 - 0
builtin_object.go

@@ -0,0 +1,411 @@
+package goja
+
+import (
+	"fmt"
+)
+
+func (r *Runtime) builtin_Object(args []Value, proto *Object) *Object {
+	if len(args) > 0 {
+		arg := args[0]
+		if arg != _undefined && arg != _null {
+			return arg.ToObject(r)
+		}
+	}
+	return r.NewObject()
+}
+
+func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value {
+	o := call.Argument(0).ToObject(r)
+	p := o.self.proto()
+	if p == nil {
+		return _null
+	}
+	return p
+}
+
+func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
+	obj := call.Argument(0).ToObject(r)
+	propName := call.Argument(1).String()
+	desc := obj.self.getOwnProp(propName)
+	if desc == nil {
+		return _undefined
+	}
+	var writable, configurable, enumerable, accessor bool
+	var get, set *Object
+	var value Value
+	if v, ok := desc.(*valueProperty); ok {
+		writable = v.writable
+		configurable = v.configurable
+		enumerable = v.enumerable
+		accessor = v.accessor
+		value = v.value
+		get = v.getterFunc
+		set = v.setterFunc
+	} else {
+		writable = true
+		configurable = true
+		enumerable = true
+		value = desc
+	}
+
+	ret := r.NewObject()
+	o := ret.self
+	if !accessor {
+		o.putStr("value", value, false)
+		o.putStr("writable", r.toBoolean(writable), false)
+	} else {
+		if get != nil {
+			o.putStr("get", get, false)
+		} else {
+			o.putStr("get", _undefined, false)
+		}
+		if set != nil {
+			o.putStr("set", set, false)
+		} else {
+			o.putStr("set", _undefined, false)
+		}
+	}
+	o.putStr("enumerable", r.toBoolean(enumerable), false)
+	o.putStr("configurable", r.toBoolean(configurable), false)
+
+	return ret
+}
+
+func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
+	// ES6
+	obj := call.Argument(0).ToObject(r)
+	// obj := r.toObject(call.Argument(0))
+
+	var values []Value
+	for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
+		values = append(values, newStringValue(item.name))
+	}
+	return r.newArrayValues(values)
+}
+
+func (r *Runtime) toPropertyDescriptor(v Value) objectImpl {
+	if o, ok := v.(*Object); ok {
+		desc := o.self
+		hasValue := desc.getStr("value") != nil
+		hasWritable := desc.getStr("writable") != nil
+		hasGet := false
+		hasSet := false
+		if get := desc.getStr("get"); get != nil {
+			if get != _undefined {
+				if _, ok := r.toObject(get).self.assertCallable(); !ok {
+					r.typeErrorResult(true, "getter must be callable")
+				}
+			}
+			hasGet = true
+		}
+		if set := desc.getStr("set"); set != nil {
+			if set != _undefined {
+				if _, ok := r.toObject(set).self.assertCallable(); !ok {
+					r.typeErrorResult(true, "setter must be callable")
+				}
+			}
+			hasSet = true
+		}
+
+		if (hasGet || hasSet) && (hasValue || hasWritable) {
+			r.typeErrorResult(true, "Invalid property.  A property cannot both have accessors and be writable or have a value")
+		}
+
+		return desc
+	}
+	r.typeErrorResult(true, "Property description must be an object: %s", v.String())
+	return nil
+}
+
+func (r *Runtime) _defineProperties(o *Object, p Value) {
+	type propItem struct {
+		name string
+		prop objectImpl
+	}
+	props := p.ToObject(r)
+	var list []propItem
+	for item, f := props.self.enumerate(false, false)(); f != nil; item, f = f() {
+		list = append(list, propItem{
+			name: item.name,
+			prop: r.toPropertyDescriptor(props.self.getStr(item.name)),
+		})
+	}
+	for _, prop := range list {
+		o.self.defineOwnProperty(newStringValue(prop.name), prop.prop, true)
+	}
+}
+
+func (r *Runtime) object_create(call FunctionCall) Value {
+	var proto *Object
+	if arg := call.Argument(0); arg != _null {
+		if o, ok := arg.(*Object); ok {
+			proto = o
+		} else {
+			r.typeErrorResult(true, "Object prototype may only be an Object or null: %s", arg.String())
+		}
+	}
+	o := r.newBaseObject(proto, classObject).val
+
+	if props := call.Argument(1); props != _undefined {
+		r._defineProperties(o, props)
+	}
+
+	return o
+}
+
+func (r *Runtime) object_defineProperty(call FunctionCall) (ret Value) {
+	if obj, ok := call.Argument(0).(*Object); ok {
+		if descr, ok := call.Argument(2).(*Object); ok {
+			obj.self.defineOwnProperty(call.Argument(1), descr.self, true)
+			ret = call.Argument(0)
+		} else {
+			r.typeErrorResult(true, "Property description must be an object: %v", call.Argument(2))
+		}
+	} else {
+		r.typeErrorResult(true, "Object.defineProperty called on non-object")
+	}
+	return
+}
+
+func (r *Runtime) object_defineProperties(call FunctionCall) Value {
+	obj := r.toObject(call.Argument(0))
+	r._defineProperties(obj, call.Argument(1))
+	return obj
+}
+
+func (r *Runtime) object_seal(call FunctionCall) Value {
+	// ES6
+	arg := call.Argument(0)
+	if obj, ok := arg.(*Object); ok {
+		var descr objectImpl
+		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
+			v := obj.self.getOwnProp(item.name)
+			if prop, ok := v.(*valueProperty); ok {
+				if !prop.configurable {
+					continue
+				}
+				prop.configurable = false
+			} else {
+				if descr == nil {
+					descr = r.NewObject().self
+					descr.putStr("writable", valueTrue, false)
+					descr.putStr("enumerable", valueTrue, false)
+					descr.putStr("configurable", valueFalse, false)
+				}
+				descr.putStr("value", v, false)
+				obj.self.defineOwnProperty(newStringValue(item.name), descr, true)
+				//obj.self._putProp(item.name, v, true, true, false)
+			}
+		}
+		obj.self.preventExtensions()
+		return obj
+	}
+	return arg
+}
+
+func (r *Runtime) object_freeze(call FunctionCall) Value {
+	arg := call.Argument(0)
+	if obj, ok := arg.(*Object); ok {
+		var descr objectImpl
+		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
+			v := obj.self.getOwnProp(item.name)
+			if prop, ok := v.(*valueProperty); ok {
+				prop.configurable = false
+				if prop.value != nil {
+					prop.writable = false
+				}
+			} else {
+				if descr == nil {
+					descr = r.NewObject().self
+					descr.putStr("writable", valueFalse, false)
+					descr.putStr("enumerable", valueTrue, false)
+					descr.putStr("configurable", valueFalse, false)
+				}
+				descr.putStr("value", v, false)
+				obj.self.defineOwnProperty(newStringValue(item.name), descr, true)
+			}
+		}
+		obj.self.preventExtensions()
+		return obj
+	} else {
+		// ES6 behavior
+		return arg
+	}
+}
+
+func (r *Runtime) object_preventExtensions(call FunctionCall) (ret Value) {
+	arg := call.Argument(0)
+	if obj, ok := arg.(*Object); ok {
+		obj.self.preventExtensions()
+		return obj
+	}
+	// ES6
+	//r.typeErrorResult(true, "Object.preventExtensions called on non-object")
+	//panic("Unreachable")
+	return arg
+}
+
+func (r *Runtime) object_isSealed(call FunctionCall) Value {
+	if obj, ok := call.Argument(0).(*Object); ok {
+		if obj.self.isExtensible() {
+			return valueFalse
+		}
+		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
+			prop := obj.self.getOwnProp(item.name)
+			if prop, ok := prop.(*valueProperty); ok {
+				if prop.configurable {
+					return valueFalse
+				}
+			} else {
+				return valueFalse
+			}
+		}
+	} else {
+		// ES6
+		//r.typeErrorResult(true, "Object.isSealed called on non-object")
+		return valueTrue
+	}
+	return valueTrue
+}
+
+func (r *Runtime) object_isFrozen(call FunctionCall) Value {
+	if obj, ok := call.Argument(0).(*Object); ok {
+		if obj.self.isExtensible() {
+			return valueFalse
+		}
+		for item, f := obj.self.enumerate(true, false)(); f != nil; item, f = f() {
+			prop := obj.self.getOwnProp(item.name)
+			if prop, ok := prop.(*valueProperty); ok {
+				if prop.configurable || prop.value != nil && prop.writable {
+					return valueFalse
+				}
+			} else {
+				return valueFalse
+			}
+		}
+	} else {
+		// ES6
+		//r.typeErrorResult(true, "Object.isFrozen called on non-object")
+		return valueTrue
+	}
+	return valueTrue
+}
+
+func (r *Runtime) object_isExtensible(call FunctionCall) Value {
+	if obj, ok := call.Argument(0).(*Object); ok {
+		if obj.self.isExtensible() {
+			return valueTrue
+		}
+		return valueFalse
+	} else {
+		// ES6
+		//r.typeErrorResult(true, "Object.isExtensible called on non-object")
+		return valueFalse
+	}
+}
+
+func (r *Runtime) object_keys(call FunctionCall) Value {
+	// ES6
+	obj := call.Argument(0).ToObject(r)
+	//if obj, ok := call.Argument(0).(*valueObject); ok {
+	var keys []Value
+	for item, f := obj.self.enumerate(false, false)(); f != nil; item, f = f() {
+		keys = append(keys, newStringValue(item.name))
+	}
+	return r.newArrayValues(keys)
+	//} else {
+	//	r.typeErrorResult(true, "Object.keys called on non-object")
+	//}
+	//return nil
+}
+
+func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
+	p := call.Argument(0).String()
+	o := call.This.ToObject(r)
+	if o.self.hasOwnPropertyStr(p) {
+		return valueTrue
+	} else {
+		return valueFalse
+	}
+}
+
+func (r *Runtime) objectproto_isPrototypeOf(call FunctionCall) Value {
+	if v, ok := call.Argument(0).(*Object); ok {
+		o := call.This.ToObject(r)
+		for {
+			v = v.self.proto()
+			if v == nil {
+				break
+			}
+			if v == o {
+				return valueTrue
+			}
+		}
+	}
+	return valueFalse
+}
+
+func (r *Runtime) objectproto_propertyIsEnumerable(call FunctionCall) Value {
+	p := call.Argument(0).ToString()
+	o := call.This.ToObject(r)
+	pv := o.self.getOwnProp(p.String())
+	if pv == nil {
+		return valueFalse
+	}
+	if prop, ok := pv.(*valueProperty); ok {
+		if !prop.enumerable {
+			return valueFalse
+		}
+	}
+	return valueTrue
+}
+
+func (r *Runtime) objectproto_toString(call FunctionCall) Value {
+	switch o := call.This.(type) {
+	case valueNull:
+		return stringObjectNull
+	case valueUndefined:
+		return stringObjectUndefined
+	case *Object:
+		return newStringValue(fmt.Sprintf("[object %s]", o.self.className()))
+	default:
+		obj := call.This.ToObject(r)
+		return newStringValue(fmt.Sprintf("[object %s]", obj.self.className()))
+	}
+}
+
+func (r *Runtime) objectproto_toLocaleString(call FunctionCall) Value {
+	return call.This.ToObject(r).ToString()
+}
+
+func (r *Runtime) objectproto_valueOf(call FunctionCall) Value {
+	return call.This.ToObject(r)
+}
+
+func (r *Runtime) initObject() {
+	o := r.global.ObjectPrototype.self
+	o._putProp("toString", r.newNativeFunc(r.objectproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("toLocaleString", r.newNativeFunc(r.objectproto_toLocaleString, nil, "toLocaleString", nil, 0), true, false, true)
+	o._putProp("valueOf", r.newNativeFunc(r.objectproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+	o._putProp("hasOwnProperty", r.newNativeFunc(r.objectproto_hasOwnProperty, nil, "hasOwnProperty", nil, 1), true, false, true)
+	o._putProp("isPrototypeOf", r.newNativeFunc(r.objectproto_isPrototypeOf, nil, "isPrototypeOf", nil, 1), true, false, true)
+	o._putProp("propertyIsEnumerable", r.newNativeFunc(r.objectproto_propertyIsEnumerable, nil, "propertyIsEnumerable", nil, 1), true, false, true)
+
+	r.global.Object = r.newNativeFuncConstruct(r.builtin_Object, classObject, r.global.ObjectPrototype, 1)
+	o = r.global.Object.self
+	o._putProp("defineProperty", r.newNativeFunc(r.object_defineProperty, nil, "defineProperty", nil, 3), true, false, true)
+	o._putProp("defineProperties", r.newNativeFunc(r.object_defineProperties, nil, "defineProperties", nil, 2), true, false, true)
+	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.object_getOwnPropertyDescriptor, nil, "getOwnPropertyDescriptor", nil, 2), true, false, true)
+	o._putProp("getPrototypeOf", r.newNativeFunc(r.object_getPrototypeOf, nil, "getPrototypeOf", nil, 1), true, false, true)
+	o._putProp("getOwnPropertyNames", r.newNativeFunc(r.object_getOwnPropertyNames, nil, "getOwnPropertyNames", nil, 1), true, false, true)
+	o._putProp("create", r.newNativeFunc(r.object_create, nil, "create", nil, 2), true, false, true)
+	o._putProp("seal", r.newNativeFunc(r.object_seal, nil, "seal", nil, 1), true, false, true)
+	o._putProp("freeze", r.newNativeFunc(r.object_freeze, nil, "freeze", nil, 1), true, false, true)
+	o._putProp("preventExtensions", r.newNativeFunc(r.object_preventExtensions, nil, "preventExtensions", nil, 1), true, false, true)
+	o._putProp("isSealed", r.newNativeFunc(r.object_isSealed, nil, "isSealed", nil, 1), true, false, true)
+	o._putProp("isFrozen", r.newNativeFunc(r.object_isFrozen, nil, "isFrozen", nil, 1), true, false, true)
+	o._putProp("isExtensible", r.newNativeFunc(r.object_isExtensible, nil, "isExtensible", nil, 1), true, false, true)
+	o._putProp("keys", r.newNativeFunc(r.object_keys, nil, "keys", nil, 1), true, false, true)
+
+	r.addToGlobal("Object", r.global.Object)
+}

+ 272 - 0
builtin_regexp.go

@@ -0,0 +1,272 @@
+package goja
+
+import (
+	"fmt"
+	"github.com/dlclark/regexp2"
+	"github.com/dop251/goja/parser"
+	"regexp"
+)
+
+func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
+	v := &Object{runtime: r}
+
+	o := &regexpObject{}
+	o.class = classRegExp
+	o.val = v
+	o.extensible = true
+	v.self = o
+	o.prototype = proto
+	o.init()
+	return o
+}
+
+func (r *Runtime) newRegExpp(pattern regexpPattern, patternStr valueString, global, ignoreCase, multiline bool, proto *Object) *Object {
+	o := r.newRegexpObject(proto)
+
+	o.pattern = pattern
+	o.source = patternStr
+	o.global = global
+	o.ignoreCase = ignoreCase
+	o.multiline = multiline
+
+	return o.val
+}
+
+func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCase, multiline bool, err error) {
+
+	if flags != "" {
+		invalidFlags := func() {
+			err = fmt.Errorf("Invalid flags supplied to RegExp constructor '%s'", flags)
+		}
+		for _, chr := range flags {
+			switch chr {
+			case 'g':
+				if global {
+					invalidFlags()
+					return
+				}
+				global = true
+			case 'm':
+				if multiline {
+					invalidFlags()
+					return
+				}
+				multiline = true
+			case 'i':
+				if ignoreCase {
+					invalidFlags()
+					return
+				}
+				ignoreCase = true
+			default:
+				invalidFlags()
+				return
+			}
+		}
+	}
+
+	re2Str, err1 := parser.TransformRegExp(patternStr)
+	if /*false &&*/ err1 == nil {
+		re2flags := ""
+		if multiline {
+			re2flags += "m"
+		}
+		if ignoreCase {
+			re2flags += "i"
+		}
+		if len(re2flags) > 0 {
+			re2Str = fmt.Sprintf("(?%s:%s)", re2flags, re2Str)
+		}
+
+		pattern, err1 := regexp.Compile(re2Str)
+		if err1 != nil {
+			err = fmt.Errorf("Invalid regular expression (re2): %s (%v)", re2Str, err1)
+			return
+		}
+
+		p = (*regexpWrapper)(pattern)
+	} else {
+		var opts regexp2.RegexOptions = regexp2.ECMAScript
+		if multiline {
+			opts |= regexp2.Multiline
+		}
+		if ignoreCase {
+			opts |= regexp2.IgnoreCase
+		}
+		regexp2Pattern, err1 := regexp2.Compile(patternStr, opts)
+		if err1 != nil {
+			err = fmt.Errorf("Invalid regular expression (regexp2): %s (%v)", patternStr, err1)
+			return
+		}
+		p = (*regexp2Wrapper)(regexp2Pattern)
+	}
+	return
+}
+
+func (r *Runtime) newRegExp(patternStr valueString, flags string, proto *Object) *Object {
+	pattern, global, ignoreCase, multiline, err := compileRegexp(patternStr.String(), flags)
+	if err != nil {
+		panic(r.newSyntaxError(err.Error(), -1))
+	}
+	return r.newRegExpp(pattern, patternStr, global, ignoreCase, multiline, proto)
+}
+
+func (r *Runtime) builtin_newRegExp(args []Value) *Object {
+	var pattern valueString
+	var flags string
+	if len(args) > 0 {
+		if obj, ok := args[0].(*Object); ok {
+			if regexp, ok := obj.self.(*regexpObject); ok {
+				if len(args) < 2 || args[1] == _undefined {
+					return regexp.clone()
+				} else {
+					return r.newRegExp(regexp.source, args[1].String(), r.global.RegExpPrototype)
+				}
+			}
+		}
+		if args[0] != _undefined {
+			pattern = args[0].ToString()
+		}
+	}
+	if len(args) > 1 {
+		if a := args[1]; a != _undefined {
+			flags = a.String()
+		}
+	}
+	if pattern == nil {
+		pattern = stringEmpty
+	}
+	return r.newRegExp(pattern, flags, r.global.RegExpPrototype)
+}
+
+func (r *Runtime) builtin_RegExp(call FunctionCall) Value {
+	flags := call.Argument(1)
+	if flags == _undefined {
+		if obj, ok := call.Argument(0).(*Object); ok {
+			if _, ok := obj.self.(*regexpObject); ok {
+				return call.Arguments[0]
+			}
+		}
+	}
+	return r.builtin_newRegExp(call.Arguments)
+}
+
+func (r *Runtime) regexpproto_exec(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		return this.exec(call.Argument(0).ToString())
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.exec called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_test(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		if this.test(call.Argument(0).ToString()) {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.test called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		var g, i, m string
+		if this.global {
+			g = "g"
+		}
+		if this.ignoreCase {
+			i = "i"
+		}
+		if this.multiline {
+			m = "m"
+		}
+		return newStringValue(fmt.Sprintf("/%s/%s%s%s", this.source.String(), g, i, m))
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.toString called on incompatible receiver %s", call.This)
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_getSource(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		return this.source
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.source getter called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_getGlobal(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		if this.global {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.global getter called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_getMultiline(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		if this.multiline {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.multiline getter called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) regexpproto_getIgnoreCase(call FunctionCall) Value {
+	if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
+		if this.ignoreCase {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	} else {
+		r.typeErrorResult(true, "Method RegExp.prototype.ignoreCase getter called on incompatible receiver %s", call.This.ToString())
+		return nil
+	}
+}
+
+func (r *Runtime) initRegExp() {
+	r.global.RegExpPrototype = r.NewObject()
+	o := r.global.RegExpPrototype.self
+	o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
+	o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
+	o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
+	o.putStr("source", &valueProperty{
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.regexpproto_getSource, nil, "get source", nil, 0),
+		accessor:     true,
+	}, false)
+	o.putStr("global", &valueProperty{
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.regexpproto_getGlobal, nil, "get global", nil, 0),
+		accessor:     true,
+	}, false)
+	o.putStr("multiline", &valueProperty{
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.regexpproto_getMultiline, nil, "get multiline", nil, 0),
+		accessor:     true,
+	}, false)
+	o.putStr("ignoreCase", &valueProperty{
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
+		accessor:     true,
+	}, false)
+
+	r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
+	r.addToGlobal("RegExp", r.global.RegExp)
+}

+ 700 - 0
builtin_string.go

@@ -0,0 +1,700 @@
+package goja
+
+import (
+	"bytes"
+	"github.com/dop251/goja/parser"
+	"golang.org/x/text/collate"
+	"golang.org/x/text/language"
+	"golang.org/x/text/unicode/norm"
+	"math"
+	"strings"
+	"unicode/utf8"
+)
+
+var (
+	collator = collate.New(language.Und)
+)
+
+func (r *Runtime) builtin_String(call FunctionCall) Value {
+	if len(call.Arguments) > 0 {
+		arg := call.Arguments[0]
+		if _, ok := arg.assertString(); ok {
+			return arg
+		}
+		return arg.ToString()
+	} else {
+		return newStringValue("")
+	}
+}
+
+func (r *Runtime) _newString(s valueString) *Object {
+	v := &Object{runtime: r}
+
+	o := &stringObject{}
+	o.class = classString
+	o.val = v
+	o.extensible = true
+	v.self = o
+	o.prototype = r.global.StringPrototype
+	if s != nil {
+		o.value = s
+	}
+	o.init()
+	return v
+}
+
+func (r *Runtime) builtin_newString(args []Value) *Object {
+	var s valueString
+	if len(args) > 0 {
+		s = args[0].ToString()
+	} else {
+		s = stringEmpty
+	}
+	return r._newString(s)
+}
+
+func searchSubstring(str, search valueString) (ret [][]int) {
+	searchPos := 0
+	l := str.length()
+	if searchPos < l {
+		p := str.index(search, searchPos)
+		if p != -1 {
+			searchPos = p + search.length()
+			ret = append(ret, []int{p, searchPos})
+		}
+	}
+	return
+}
+
+func searchSubstringUTF8(str, search string) (ret [][]int) {
+	searchPos := 0
+	l := len(str)
+	if searchPos < l {
+		p := strings.Index(str[searchPos:], search)
+		if p != -1 {
+			p += searchPos
+			searchPos = p + len(search)
+			ret = append(ret, []int{p, searchPos})
+		}
+	}
+	return
+}
+
+func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value {
+	if str, ok := this.assertString(); ok {
+		return str
+	}
+	if obj, ok := this.(*Object); ok {
+		if strObj, ok := obj.self.(*stringObject); ok {
+			return strObj.value
+		}
+	}
+	r.typeErrorResult(true, "String.prototype.%s is called on incompatible receiver", funcName)
+	return nil
+}
+
+func (r *Runtime) stringproto_toString(call FunctionCall) Value {
+	return r.stringproto_toStringValueOf(call.This, "toString")
+}
+
+func (r *Runtime) stringproto_valueOf(call FunctionCall) Value {
+	return r.stringproto_toStringValueOf(call.This, "valueOf")
+}
+
+func (r *Runtime) string_fromcharcode(call FunctionCall) Value {
+	b := make([]byte, len(call.Arguments))
+	for i, arg := range call.Arguments {
+		chr := toUInt16(arg)
+		if chr >= utf8.RuneSelf {
+			bb := make([]uint16, len(call.Arguments))
+			for j := 0; i < i; j++ {
+				bb[j] = uint16(b[j])
+			}
+			bb[i] = chr
+			i++
+			for j, arg := range call.Arguments[i:] {
+				bb[i+j] = toUInt16(arg)
+			}
+			return unicodeString(bb)
+		}
+		b[i] = byte(chr)
+	}
+
+	return asciiString(b)
+}
+
+func (r *Runtime) stringproto_charAt(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+	pos := call.Argument(0).ToInteger()
+	if pos < 0 || pos >= int64(s.length()) {
+		return stringEmpty
+	}
+	return newStringValue(string(s.charAt(int(pos))))
+}
+
+func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+	pos := call.Argument(0).ToInteger()
+	if pos < 0 || pos >= int64(s.length()) {
+		return _NaN
+	}
+	return intToValue(int64(s.charAt(int(pos)) & 0xFFFF))
+}
+
+func (r *Runtime) stringproto_concat(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	strs := make([]valueString, len(call.Arguments)+1)
+	strs[0] = call.This.ToString()
+	_, allAscii := strs[0].(asciiString)
+	totalLen := strs[0].length()
+	for i, arg := range call.Arguments {
+		s := arg.ToString()
+		if allAscii {
+			_, allAscii = s.(asciiString)
+		}
+		strs[i+1] = s
+		totalLen += s.length()
+	}
+
+	if allAscii {
+		buf := bytes.NewBuffer(make([]byte, 0, totalLen))
+		for _, s := range strs {
+			buf.WriteString(s.String())
+		}
+		return asciiString(buf.String())
+	} else {
+		buf := make([]uint16, totalLen)
+		pos := 0
+		for _, s := range strs {
+			switch s := s.(type) {
+			case asciiString:
+				for i := 0; i < len(s); i++ {
+					buf[pos] = uint16(s[i])
+					pos++
+				}
+			case unicodeString:
+				copy(buf[pos:], s)
+				pos += s.length()
+			}
+		}
+		return unicodeString(buf)
+	}
+}
+
+func (r *Runtime) stringproto_indexOf(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	value := call.This.ToString()
+	target := call.Argument(0).ToString()
+	pos := int(call.Argument(1).ToInteger())
+
+	if pos < 0 {
+		pos = 0
+	} else {
+		l := value.length()
+		if pos > l {
+			pos = l
+		}
+	}
+
+	return intToValue(int64(value.index(target, pos)))
+}
+
+func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	value := call.This.ToString()
+	target := call.Argument(0).ToString()
+	numPos := call.Argument(1).ToNumber()
+
+	var pos int
+	if f, ok := numPos.assertFloat(); ok && math.IsNaN(f) {
+		pos = int(value.length())
+	} else {
+		pos = int(numPos.ToInteger())
+		if pos < 0 {
+			pos = 0
+		} else {
+			l := value.length()
+			if pos > l {
+				pos = l
+			}
+		}
+	}
+
+	return intToValue(int64(value.lastIndex(target, pos)))
+}
+
+func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	this := norm.NFD.String(call.This.String())
+	that := norm.NFD.String(call.Argument(0).String())
+	return intToValue(int64(collator.CompareString(this, that)))
+}
+
+func (r *Runtime) stringproto_match(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+	regexp := call.Argument(0)
+	var rx *regexpObject
+	if regexp, ok := regexp.(*Object); ok {
+		rx, _ = regexp.self.(*regexpObject)
+	}
+
+	if rx == nil {
+		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
+	}
+
+	if rx.global {
+		rx.putStr("lastIndex", intToValue(0), false)
+		var a []Value
+		var previousLastIndex int64
+		for {
+			match, result := rx.execRegexp(s)
+			if !match {
+				break
+			}
+			thisIndex := rx.getStr("lastIndex").ToInteger()
+			if thisIndex == previousLastIndex {
+				previousLastIndex++
+				rx.putStr("lastIndex", intToValue(previousLastIndex), false)
+			} else {
+				previousLastIndex = thisIndex
+			}
+			a = append(a, s.substring(result[0], result[1]))
+		}
+		if len(a) == 0 {
+			return _null
+		}
+		return r.newArrayValues(a)
+	} else {
+		return rx.exec(s)
+	}
+}
+
+func (r *Runtime) stringproto_replace(call FunctionCall) Value {
+	s := call.This.ToString()
+	var str string
+	var isASCII bool
+	if astr, ok := s.(asciiString); ok {
+		str = string(astr)
+		isASCII = true
+	} else {
+		str = s.String()
+	}
+	searchValue := call.Argument(0)
+	replaceValue := call.Argument(1)
+
+	var found [][]int
+
+	if searchValue, ok := searchValue.(*Object); ok {
+		if regexp, ok := searchValue.self.(*regexpObject); ok {
+			find := 1
+			if regexp.global {
+				find = -1
+			}
+			if isASCII {
+				found = regexp.pattern.FindAllSubmatchIndexASCII(str, find)
+			} else {
+				found = regexp.pattern.FindAllSubmatchIndexUTF8(str, find)
+			}
+			if found == nil {
+				return s
+			}
+		}
+	}
+
+	if found == nil {
+		found = searchSubstringUTF8(str, searchValue.String())
+	}
+
+	if len(found) == 0 {
+		return s
+	}
+
+	var buf bytes.Buffer
+	lastIndex := 0
+
+	var rcall func(FunctionCall) Value
+
+	if replaceValue, ok := replaceValue.(*Object); ok {
+		if c, ok := replaceValue.self.assertCallable(); ok {
+			rcall = c
+		}
+	}
+
+	if rcall != nil {
+		for _, item := range found {
+			if item[0] != lastIndex {
+				buf.WriteString(str[lastIndex:item[0]])
+			}
+			matchCount := len(item) / 2
+			argumentList := make([]Value, matchCount+2)
+			for index := 0; index < matchCount; index++ {
+				offset := 2 * index
+				if item[offset] != -1 {
+					if isASCII {
+						argumentList[index] = asciiString(str[item[offset]:item[offset+1]])
+					} else {
+						argumentList[index] = newStringValue(str[item[offset]:item[offset+1]])
+					}
+				} else {
+					argumentList[index] = _undefined
+				}
+			}
+			argumentList[matchCount] = valueInt(item[0])
+			argumentList[matchCount+1] = s
+			replacement := rcall(FunctionCall{
+				This:      _undefined,
+				Arguments: argumentList,
+			}).String()
+			buf.WriteString(replacement)
+			lastIndex = item[1]
+		}
+	} else {
+		newstring := replaceValue.String()
+
+		for _, item := range found {
+			if item[0] != lastIndex {
+				buf.WriteString(str[lastIndex:item[0]])
+			}
+			matches := len(item) / 2
+			for i := 0; i < len(newstring); i++ {
+				if newstring[i] == '$' && i < len(newstring)-1 {
+					ch := newstring[i+1]
+					switch ch {
+					case '$':
+						buf.WriteByte('$')
+					case '`':
+						buf.WriteString(str[0:item[0]])
+					case '\'':
+						buf.WriteString(str[item[1]:])
+					case '&':
+						buf.WriteString(str[item[0]:item[1]])
+					default:
+						matchNumber := 0
+						l := 0
+						for _, ch := range newstring[i+1:] {
+							if ch >= '0' && ch <= '9' {
+								m := matchNumber*10 + int(ch-'0')
+								if m >= matches {
+									break
+								}
+								matchNumber = m
+								l++
+							} else {
+								break
+							}
+						}
+						if l > 0 {
+							offset := 2 * matchNumber
+							if offset < len(item) && item[offset] != -1 {
+								buf.WriteString(str[item[offset]:item[offset+1]])
+							}
+							i += l - 1
+						} else {
+							buf.WriteByte('$')
+							buf.WriteByte(ch)
+						}
+
+					}
+					i++
+				} else {
+					buf.WriteByte(newstring[i])
+				}
+			}
+			lastIndex = item[1]
+		}
+	}
+
+	if lastIndex != len(str) {
+		buf.WriteString(str[lastIndex:])
+	}
+
+	return newStringValue(buf.String())
+}
+
+func (r *Runtime) stringproto_search(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+	regexp := call.Argument(0)
+	var rx *regexpObject
+	if regexp, ok := regexp.(*Object); ok {
+		rx, _ = regexp.self.(*regexpObject)
+	}
+
+	if rx == nil {
+		rx = r.builtin_newRegExp([]Value{regexp}).self.(*regexpObject)
+	}
+
+	match, result := rx.execRegexp(s)
+	if !match {
+		return intToValue(-1)
+	}
+	return intToValue(int64(result[0]))
+}
+
+func (r *Runtime) stringproto_slice(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+
+	l := s.length()
+	start := int(call.Argument(0).ToInteger())
+	var end int
+	if arg1 := call.Argument(1); arg1 != _undefined {
+		end = int(arg1.ToInteger())
+	} else {
+		end = l
+	}
+
+	if start < 0 {
+		start += l
+		if start < 0 {
+			start = 0
+		}
+	} else {
+		if start > l {
+			start = l
+		}
+	}
+
+	if end < 0 {
+		end += l
+		if end < 0 {
+			end = 0
+		}
+	} else {
+		if end > l {
+			end = l
+		}
+	}
+
+	if end > start {
+		return s.substring(start, end)
+	}
+	return stringEmpty
+}
+
+func (r *Runtime) stringproto_split(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+
+	separatorValue := call.Argument(0)
+	limitValue := call.Argument(1)
+	limit := -1
+	if limitValue != _undefined {
+		limit = int(toUInt32(limitValue))
+	}
+
+	if limit == 0 {
+		return r.newArrayValues(nil)
+	}
+
+	if separatorValue == _undefined {
+		return r.newArrayValues([]Value{s})
+	}
+
+	var search *regexpObject
+	if o, ok := separatorValue.(*Object); ok {
+		search, _ = o.self.(*regexpObject)
+	}
+
+	if search != nil {
+		targetLength := s.length()
+		valueArray := []Value{}
+		result := search.pattern.FindAllSubmatchIndex(s, -1)
+		lastIndex := 0
+		found := 0
+
+		for _, match := range result {
+			if match[0] == match[1] {
+				// FIXME Ugh, this is a hack
+				if match[0] == 0 || match[0] == targetLength {
+					continue
+				}
+			}
+
+			if lastIndex != match[0] {
+				valueArray = append(valueArray, s.substring(lastIndex, match[0]))
+				found++
+			} else if lastIndex == match[0] {
+				if lastIndex != -1 {
+					valueArray = append(valueArray, stringEmpty)
+					found++
+				}
+			}
+
+			lastIndex = match[1]
+			if found == limit {
+				goto RETURN
+			}
+
+			captureCount := len(match) / 2
+			for index := 1; index < captureCount; index++ {
+				offset := index * 2
+				var value Value
+				if match[offset] != -1 {
+					value = s.substring(match[offset], match[offset+1])
+				} else {
+					value = _undefined
+				}
+				valueArray = append(valueArray, value)
+				found++
+				if found == limit {
+					goto RETURN
+				}
+			}
+		}
+
+		if found != limit {
+			if lastIndex != targetLength {
+				valueArray = append(valueArray, s.substring(lastIndex, targetLength))
+			} else {
+				valueArray = append(valueArray, stringEmpty)
+			}
+		}
+
+	RETURN:
+		return r.newArrayValues(valueArray)
+
+	} else {
+		separator := separatorValue.String()
+
+		excess := false
+		str := s.String()
+		if limit > len(str) {
+			limit = len(str)
+		}
+		splitLimit := limit
+		if limit > 0 {
+			splitLimit = limit + 1
+			excess = true
+		}
+
+		split := strings.SplitN(str, separator, splitLimit)
+
+		if excess && len(split) > limit {
+			split = split[:limit]
+		}
+
+		valueArray := make([]Value, len(split))
+		for index, value := range split {
+			valueArray[index] = newStringValue(value)
+		}
+
+		return r.newArrayValues(valueArray)
+	}
+
+}
+
+func (r *Runtime) stringproto_substring(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+
+	l := s.length()
+	intStart := int(call.Argument(0).ToInteger())
+	var intEnd int
+	if end := call.Argument(1); end != _undefined {
+		intEnd = int(end.ToInteger())
+	} else {
+		intEnd = l
+	}
+	if intStart < 0 {
+		intStart = 0
+	} else if intStart > l {
+		intStart = l
+	}
+
+	if intEnd < 0 {
+		intEnd = 0
+	} else if intEnd > l {
+		intEnd = l
+	}
+
+	if intStart > intEnd {
+		intStart, intEnd = intEnd, intStart
+	}
+
+	return s.substring(intStart, intEnd)
+}
+
+func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+
+	return s.toLower()
+}
+
+func (r *Runtime) stringproto_toUpperCase(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+
+	return s.toUpper()
+}
+
+func (r *Runtime) stringproto_trim(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	s := call.This.ToString()
+
+	return newStringValue(strings.Trim(s.String(), parser.WhitespaceChars))
+}
+
+func (r *Runtime) stringproto_substr(call FunctionCall) Value {
+	s := call.This.ToString()
+	start := call.Argument(0).ToInteger()
+	var length int64
+	sl := int64(s.length())
+	if arg := call.Argument(1); arg != _undefined {
+		length = arg.ToInteger()
+	} else {
+		length = sl
+	}
+
+	if start < 0 {
+		start = max(sl+start, 0)
+	}
+
+	length = min(max(length, 0), sl-start)
+	if length <= 0 {
+		return stringEmpty
+	}
+
+	return s.substring(int(start), int(start+length))
+}
+
+func (r *Runtime) initString() {
+	r.global.StringPrototype = r.builtin_newString([]Value{stringEmpty})
+
+	o := r.global.StringPrototype.self
+	o.(*stringObject).prototype = r.global.ObjectPrototype
+	o._putProp("toString", r.newNativeFunc(r.stringproto_toString, nil, "toString", nil, 0), true, false, true)
+	o._putProp("valueOf", r.newNativeFunc(r.stringproto_valueOf, nil, "valueOf", nil, 0), true, false, true)
+	o._putProp("charAt", r.newNativeFunc(r.stringproto_charAt, nil, "charAt", nil, 1), true, false, true)
+	o._putProp("charCodeAt", r.newNativeFunc(r.stringproto_charCodeAt, nil, "charCodeAt", nil, 1), true, false, true)
+	o._putProp("concat", r.newNativeFunc(r.stringproto_concat, nil, "concat", nil, 1), true, false, true)
+	o._putProp("indexOf", r.newNativeFunc(r.stringproto_indexOf, nil, "indexOf", nil, 1), true, false, true)
+	o._putProp("lastIndexOf", r.newNativeFunc(r.stringproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
+	o._putProp("localeCompare", r.newNativeFunc(r.stringproto_localeCompare, nil, "localeCompare", nil, 1), true, false, true)
+	o._putProp("match", r.newNativeFunc(r.stringproto_match, nil, "match", nil, 1), true, false, true)
+	o._putProp("replace", r.newNativeFunc(r.stringproto_replace, nil, "replace", nil, 2), true, false, true)
+	o._putProp("search", r.newNativeFunc(r.stringproto_search, nil, "search", nil, 1), true, false, true)
+	o._putProp("slice", r.newNativeFunc(r.stringproto_slice, nil, "slice", nil, 2), true, false, true)
+	o._putProp("split", r.newNativeFunc(r.stringproto_split, nil, "split", nil, 2), true, false, true)
+	o._putProp("substring", r.newNativeFunc(r.stringproto_substring, nil, "substring", nil, 2), true, false, true)
+	o._putProp("toLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLowerCase", nil, 0), true, false, true)
+	o._putProp("toLocaleLowerCase", r.newNativeFunc(r.stringproto_toLowerCase, nil, "toLocaleLowerCase", nil, 0), true, false, true)
+	o._putProp("toUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toUpperCase", nil, 0), true, false, true)
+	o._putProp("toLocaleUpperCase", r.newNativeFunc(r.stringproto_toUpperCase, nil, "toLocaleUpperCase", nil, 0), true, false, true)
+	o._putProp("trim", r.newNativeFunc(r.stringproto_trim, nil, "trim", nil, 0), true, false, true)
+
+	// Annex B
+	o._putProp("substr", r.newNativeFunc(r.stringproto_substr, nil, "substr", nil, 2), true, false, true)
+
+	r.global.String = r.newNativeFunc(r.builtin_String, r.builtin_newString, "String", r.global.StringPrototype, 1)
+	o = r.global.String.self
+	o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, nil, "fromCharCode", nil, 1), true, false, true)
+
+	r.addToGlobal("String", r.global.String)
+
+	r.stringSingleton = r.builtin_new(r.global.String, nil).self.(*stringObject)
+}

+ 90 - 0
builtin_string_test.go

@@ -0,0 +1,90 @@
+package goja
+
+import "testing"
+
+func TestSubstr(t *testing.T) {
+	const SCRIPT = `
+assert.sameValue('abc'.substr(0, false), '', 'start: 0, length: false');
+assert.sameValue('abc'.substr(1, false), '', 'start: 1, length: false');
+assert.sameValue('abc'.substr(2, false), '', 'start: 2, length: false');
+assert.sameValue('abc'.substr(3, false), '', 'start: 3, length: false');
+
+assert.sameValue('abc'.substr(0, NaN), '', 'start: 0, length: NaN');
+assert.sameValue('abc'.substr(1, NaN), '', 'start: 1, length: NaN');
+assert.sameValue('abc'.substr(2, NaN), '', 'start: 2, length: NaN');
+assert.sameValue('abc'.substr(3, NaN), '', 'start: 3, length: NaN');
+
+assert.sameValue('abc'.substr(0, ''), '', 'start: 0, length: ""');
+assert.sameValue('abc'.substr(1, ''), '', 'start: 1, length: ""');
+assert.sameValue('abc'.substr(2, ''), '', 'start: 2, length: ""');
+assert.sameValue('abc'.substr(3, ''), '', 'start: 3, length: ""');
+
+assert.sameValue('abc'.substr(0, null), '', 'start: 0, length: null');
+assert.sameValue('abc'.substr(1, null), '', 'start: 1, length: null');
+assert.sameValue('abc'.substr(2, null), '', 'start: 2, length: null');
+assert.sameValue('abc'.substr(3, null), '', 'start: 3, length: null');
+
+assert.sameValue('abc'.substr(0, -1), '', '0, -1');
+assert.sameValue('abc'.substr(0, -2), '', '0, -2');
+assert.sameValue('abc'.substr(0, -3), '', '0, -3');
+assert.sameValue('abc'.substr(0, -4), '', '0, -4');
+
+assert.sameValue('abc'.substr(1, -1), '', '1, -1');
+assert.sameValue('abc'.substr(1, -2), '', '1, -2');
+assert.sameValue('abc'.substr(1, -3), '', '1, -3');
+assert.sameValue('abc'.substr(1, -4), '', '1, -4');
+
+assert.sameValue('abc'.substr(2, -1), '', '2, -1');
+assert.sameValue('abc'.substr(2, -2), '', '2, -2');
+assert.sameValue('abc'.substr(2, -3), '', '2, -3');
+assert.sameValue('abc'.substr(2, -4), '', '2, -4');
+
+assert.sameValue('abc'.substr(3, -1), '', '3, -1');
+assert.sameValue('abc'.substr(3, -2), '', '3, -2');
+assert.sameValue('abc'.substr(3, -3), '', '3, -3');
+assert.sameValue('abc'.substr(3, -4), '', '3, -4');
+
+assert.sameValue('abc'.substr(0, 1), 'a', '0, 1');
+assert.sameValue('abc'.substr(0, 2), 'ab', '0, 1');
+assert.sameValue('abc'.substr(0, 3), 'abc', '0, 1');
+assert.sameValue('abc'.substr(0, 4), 'abc', '0, 1');
+
+assert.sameValue('abc'.substr(1, 1), 'b', '1, 1');
+assert.sameValue('abc'.substr(1, 2), 'bc', '1, 1');
+assert.sameValue('abc'.substr(1, 3), 'bc', '1, 1');
+assert.sameValue('abc'.substr(1, 4), 'bc', '1, 1');
+
+assert.sameValue('abc'.substr(2, 1), 'c', '2, 1');
+assert.sameValue('abc'.substr(2, 2), 'c', '2, 1');
+assert.sameValue('abc'.substr(2, 3), 'c', '2, 1');
+assert.sameValue('abc'.substr(2, 4), 'c', '2, 1');
+
+assert.sameValue('abc'.substr(3, 1), '', '3, 1');
+assert.sameValue('abc'.substr(3, 2), '', '3, 1');
+assert.sameValue('abc'.substr(3, 3), '', '3, 1');
+assert.sameValue('abc'.substr(3, 4), '', '3, 1');
+
+assert.sameValue('abc'.substr(0), 'abc', 'start: 0, length: unspecified');
+assert.sameValue('abc'.substr(1), 'bc', 'start: 1, length: unspecified');
+assert.sameValue('abc'.substr(2), 'c', 'start: 2, length: unspecified');
+assert.sameValue('abc'.substr(3), '', 'start: 3, length: unspecified');
+
+assert.sameValue(
+  'abc'.substr(0, undefined), 'abc', 'start: 0, length: undefined'
+);
+assert.sameValue(
+  'abc'.substr(1, undefined), 'bc', 'start: 1, length: undefined'
+);
+assert.sameValue(
+  'abc'.substr(2, undefined), 'c', 'start: 2, length: undefined'
+);
+assert.sameValue(
+  'abc'.substr(3, undefined), '', 'start: 3, length: undefined'
+);
+
+
+
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}

+ 105 - 0
builtin_typedarrays.go

@@ -0,0 +1,105 @@
+package goja
+
+type objectArrayBuffer struct {
+	baseObject
+	data []byte
+}
+
+func (o *objectArrayBuffer) export() interface{} {
+	return o.data
+}
+
+func (r *Runtime) _newArrayBuffer(proto *Object, o *Object) *objectArrayBuffer {
+	if o == nil {
+		o = &Object{runtime: r}
+	}
+	b := &objectArrayBuffer{
+		baseObject: baseObject{
+			class:      classObject,
+			val:        o,
+			prototype:  proto,
+			extensible: true,
+		},
+	}
+	o.self = b
+	b.init()
+	return b
+}
+
+func (r *Runtime) builtin_ArrayBuffer(args []Value, proto *Object) *Object {
+	b := r._newArrayBuffer(proto, nil)
+	if len(args) > 0 {
+		b.data = make([]byte, toLength(args[0]))
+	}
+	return b.val
+}
+
+func (r *Runtime) arrayBufferProto_getByteLength(call FunctionCall) Value {
+	o := r.toObject(call.This)
+	if b, ok := o.self.(*objectArrayBuffer); ok {
+		return intToValue(int64(len(b.data)))
+	}
+	r.typeErrorResult(true, "Object is not ArrayBuffer: %s", o)
+	panic("unreachable")
+}
+
+func (r *Runtime) arrayBufferProto_slice(call FunctionCall) Value {
+	o := r.toObject(call.This)
+	if b, ok := o.self.(*objectArrayBuffer); ok {
+		l := int64(len(b.data))
+		start := toLength(call.Argument(0))
+		if start < 0 {
+			start = l + start
+		}
+		if start < 0 {
+			start = 0
+		} else if start > l {
+			start = l
+		}
+		var stop int64
+		if arg := call.Argument(1); arg != _undefined {
+			stop = toLength(arg)
+			if stop < 0 {
+				stop = int64(len(b.data)) + stop
+			}
+			if stop < 0 {
+				stop = 0
+			} else if stop > l {
+				stop = l
+			}
+
+		} else {
+			stop = l
+		}
+
+		ret := r._newArrayBuffer(r.global.ArrayBufferPrototype, nil)
+
+		if stop > start {
+			ret.data = b.data[start:stop]
+		}
+
+		return ret.val
+	}
+	r.typeErrorResult(true, "Object is not ArrayBuffer: %s", o)
+	panic("unreachable")
+}
+
+func (r *Runtime) createArrayBufferProto(val *Object) objectImpl {
+	b := r._newArrayBuffer(r.global.Object, val)
+	byteLengthProp := &valueProperty{
+		accessor:     true,
+		configurable: true,
+		getterFunc:   r.newNativeFunc(r.arrayBufferProto_getByteLength, nil, "get byteLength", nil, 0),
+	}
+	b._put("byteLength", byteLengthProp)
+	b._putProp("slice", r.newNativeFunc(r.arrayBufferProto_slice, nil, "slice", nil, 2), true, false, true)
+	return b
+}
+
+func (r *Runtime) initTypedArrays() {
+
+	r.global.ArrayBufferPrototype = r.newLazyObject(r.createArrayBufferProto)
+
+	r.global.ArrayBuffer = r.newNativeFuncConstruct(r.builtin_ArrayBuffer, "ArrayBuffer", r.global.ArrayBufferPrototype, 1)
+	r.addToGlobal("ArrayBuffer", r.global.ArrayBuffer)
+}

+ 12 - 0
builtin_typedarrays_test.go

@@ -0,0 +1,12 @@
+package goja
+
+/*
+func TestArrayBufferNew(t *testing.T) {
+	const SCRIPT = `
+	var b = new ArrayBuffer(16);
+	b.byteLength;
+	`
+
+	testScript1(SCRIPT, intToValue(16), t)
+}
+*/

+ 473 - 0
compiler.go

@@ -0,0 +1,473 @@
+package goja
+
+import (
+	"fmt"
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"strconv"
+)
+
+const (
+	blockLoop = iota
+	blockTry
+	blockBranch
+	blockSwitch
+	blockWith
+)
+
+type CompilerError struct {
+	Message string
+	File    *SrcFile
+	Offset  int
+}
+
+type CompilerSyntaxError struct {
+	CompilerError
+}
+
+type CompilerReferenceError struct {
+	CompilerError
+}
+
+type srcMapItem struct {
+	pc     int
+	srcPos int
+}
+
+type Program struct {
+	code   []instruction
+	values []Value
+
+	funcName string
+	src      *SrcFile
+	srcMap   []srcMapItem
+}
+
+type compiler struct {
+	p          *Program
+	scope      *scope
+	block      *block
+	blockStart int
+
+	enumGetExpr compiledEnumGetExpr
+
+	evalVM *vm
+}
+
+type scope struct {
+	names      map[string]uint32
+	outer      *scope
+	strict     bool
+	eval       bool
+	lexical    bool
+	dynamic    bool
+	accessed   bool
+	argsNeeded bool
+	thisNeeded bool
+
+	namesMap    map[string]string
+	lastFreeTmp int
+}
+
+type block struct {
+	typ        int
+	label      string
+	needResult bool
+	cont       int
+	breaks     []int
+	conts      []int
+	outer      *block
+}
+
+func (c *compiler) leaveBlock() {
+	lbl := len(c.p.code)
+	for _, item := range c.block.breaks {
+		c.p.code[item] = jump(lbl - item)
+	}
+	if c.block.typ == blockLoop {
+		for _, item := range c.block.conts {
+			c.p.code[item] = jump(c.block.cont - item)
+		}
+	}
+	c.block = c.block.outer
+}
+
+func (e *CompilerSyntaxError) Error() string {
+	if e.File != nil {
+		return fmt.Sprintf("SyntaxError: %s at %s", e.Message, e.File.Position(e.Offset))
+	}
+	return fmt.Sprintf("SyntaxError: %s", e.Message)
+}
+
+func (e *CompilerReferenceError) Error() string {
+	return fmt.Sprintf("ReferenceError: %s", e.Message)
+}
+
+func (c *compiler) newScope() {
+	strict := false
+	if c.scope != nil {
+		strict = c.scope.strict
+	}
+	c.scope = &scope{
+		outer:    c.scope,
+		names:    make(map[string]uint32),
+		strict:   strict,
+		namesMap: make(map[string]string),
+	}
+}
+
+func (c *compiler) popScope() {
+	c.scope = c.scope.outer
+}
+
+func newCompiler() *compiler {
+	c := &compiler{
+		p: &Program{},
+	}
+
+	c.enumGetExpr.init(c, file.Idx(0))
+
+	c.newScope()
+	c.scope.dynamic = true
+	c.evalVM = New().vm
+	return c
+}
+
+func (p *Program) defineLiteralValue(val Value) uint32 {
+	for idx, v := range p.values {
+		if v.SameAs(val) {
+			return uint32(idx)
+		}
+	}
+	idx := uint32(len(p.values))
+	p.values = append(p.values, val)
+	return idx
+}
+
+func (p *Program) dumpCode(logger func(format string, args ...interface{})) {
+	p._dumpCode("", logger)
+}
+
+func (p *Program) _dumpCode(indent string, logger func(format string, args ...interface{})) {
+	logger("values: %+v", p.values)
+	for pc, ins := range p.code {
+		logger("%s %d: %T(%v)", indent, pc, ins, ins)
+		if f, ok := ins.(*newFunc); ok {
+			f.prg._dumpCode(indent+">", logger)
+		}
+	}
+}
+
+func (p *Program) sourceOffset(pc int) int {
+	// TODO use sort.Search()
+	for i, item := range p.srcMap {
+		if item.pc > pc {
+			if i > 0 {
+				return p.srcMap[i-1].srcPos
+			}
+			return 0
+		}
+	}
+	return p.srcMap[len(p.srcMap)-1].srcPos
+}
+
+func (s *scope) isFunction() bool {
+	if !s.lexical {
+		return s.outer != nil
+	}
+	return s.outer.isFunction()
+}
+
+func (s *scope) lookupName(name string) (idx uint32, found, noDynamics bool) {
+	var level uint32 = 0
+	noDynamics = true
+	for curScope := s; curScope != nil; curScope = curScope.outer {
+		if curScope != s {
+			curScope.accessed = true
+		}
+		if curScope.dynamic {
+			noDynamics = false
+		} else {
+			var mapped string
+			if m, exists := curScope.namesMap[name]; exists {
+				mapped = m
+			} else {
+				mapped = name
+			}
+			if i, exists := curScope.names[mapped]; exists {
+				idx = i | (level << 24)
+				found = true
+				return
+			}
+		}
+		if name == "arguments" && !s.lexical && s.isFunction() {
+			s.argsNeeded = true
+			s.accessed = true
+			idx, _ = s.bindName(name)
+			found = true
+			return
+		}
+		level++
+	}
+	return
+}
+
+func (s *scope) bindName(name string) (uint32, bool) {
+	if s.lexical {
+		return s.outer.bindName(name)
+	}
+
+	if idx, exists := s.names[name]; exists {
+		return idx, false
+	}
+	idx := uint32(len(s.names))
+	s.names[name] = idx
+	return idx, true
+}
+
+func (s *scope) bindNameShadow(name string) (uint32, bool) {
+	if s.lexical {
+		return s.outer.bindName(name)
+	}
+
+	unique := true
+
+	if idx, exists := s.names[name]; exists {
+		unique = false
+		// shadow the var
+		delete(s.names, name)
+		n := strconv.Itoa(int(idx))
+		s.names[n] = idx
+	}
+	idx := uint32(len(s.names))
+	s.names[name] = idx
+	return idx, unique
+}
+
+func (c *compiler) markBlockStart() {
+	c.blockStart = len(c.p.code)
+}
+
+func (c *compiler) compile(in *ast.Program) {
+	c.p.src = NewSrcFile(in.File.Name(), in.File.Source())
+
+	if len(in.Body) > 0 {
+		if !c.scope.strict {
+			c.scope.strict = c.isStrict(in.Body)
+		}
+	}
+
+	c.compileDeclList(in.DeclarationList, false)
+	c.compileFunctions(in.DeclarationList)
+
+	if len(in.Body) > 0 {
+		for _, st := range in.Body[:len(in.Body)-1] {
+			c.compileStatement(st, false)
+		}
+
+		c.compileStatement(in.Body[len(in.Body)-1], true)
+	} else {
+		c.compileStatement(&ast.EmptyStatement{}, true)
+	}
+
+	c.p.code = append(c.p.code, halt)
+	code := c.p.code
+	c.p.code = make([]instruction, 0, len(code)+len(c.scope.names)+2)
+	if c.scope.eval {
+		if !c.scope.strict {
+			c.emit(jne(2), newStash)
+		} else {
+			c.emit(pop, newStash)
+		}
+	}
+	l := len(c.p.code)
+	c.p.code = c.p.code[:l+len(c.scope.names)]
+	for name, nameIdx := range c.scope.names {
+		c.p.code[l+int(nameIdx)] = bindName(name)
+	}
+
+	c.p.code = append(c.p.code, code...)
+	for i, _ := range c.p.srcMap {
+		c.p.srcMap[i].pc += len(c.scope.names)
+	}
+
+}
+
+func (c *compiler) compileDeclList(v []ast.Declaration, inFunc bool) {
+	for _, value := range v {
+		switch value := value.(type) {
+		case *ast.FunctionDeclaration:
+			c.compileFunctionDecl(value)
+		case *ast.VariableDeclaration:
+			c.compileVarDecl(value, inFunc)
+		default:
+			panic(fmt.Errorf("Unsupported declaration: %T", value))
+		}
+	}
+}
+
+func (c *compiler) compileFunctions(v []ast.Declaration) {
+	for _, value := range v {
+		if value, ok := value.(*ast.FunctionDeclaration); ok {
+			c.compileFunction(value)
+		}
+	}
+}
+
+func (c *compiler) compileVarDecl(v *ast.VariableDeclaration, inFunc bool) {
+	for _, item := range v.List {
+		if c.scope.strict {
+			c.checkIdentifierLName(item.Name, int(item.Idx)-1)
+			c.checkIdentifierName(item.Name, int(item.Idx)-1)
+		}
+		if !inFunc || item.Name != "arguments" {
+			idx, ok := c.scope.bindName(item.Name)
+			_ = idx
+			//log.Printf("Define var: %s: %x", item.Name, idx)
+			if !ok {
+				// TODO: error
+			}
+		}
+	}
+}
+
+func (c *compiler) addDecls() []instruction {
+	code := make([]instruction, len(c.scope.names))
+	for name, nameIdx := range c.scope.names {
+		code[nameIdx] = bindName(name)
+	}
+	return code
+}
+
+func (c *compiler) convertFunctionToStashless(code []instruction, args int) {
+	code[0] = enterFuncStashless{stackSize: uint32(len(c.scope.names) - args), args: uint32(args)}
+	for pc := 1; pc < len(code); pc++ {
+		instr := code[pc]
+		if instr == ret {
+			code[pc] = retStashless
+		}
+		switch instr := instr.(type) {
+		case getLocal:
+			level := int(uint32(instr) >> 24)
+			idx := int(uint32(instr) & 0x00FFFFFF)
+			if level > 0 {
+				level--
+				code[pc] = getLocal((level << 24) | idx)
+			} else {
+				if idx < args {
+					code[pc] = loadStack(-idx - 1)
+				} else {
+					code[pc] = loadStack(idx - args + 1)
+				}
+			}
+		case setLocal:
+			level := int(uint32(instr) >> 24)
+			idx := int(instr & 0x00FFFFFF)
+			if level > 0 {
+				level--
+				code[pc] = setLocal((level << 24) | idx)
+			} else {
+				if idx < args {
+					code[pc] = storeStack(-idx - 1)
+				} else {
+					code[pc] = storeStack(idx - args + 1)
+				}
+			}
+		case setLocalP:
+			level := int(uint32(instr) >> 24)
+			idx := int(instr & 0x00FFFFFF)
+			if level > 0 {
+				level--
+				code[pc] = setLocal((level << 24) | idx)
+			} else {
+				if idx < args {
+					code[pc] = storeStackP(-idx - 1)
+				} else {
+					code[pc] = storeStackP(idx - args + 1)
+				}
+			}
+		case getVar:
+			level := instr.idx >> 24
+			idx := instr.idx & 0x00FFFFFF
+			level--
+			instr.idx = level<<24 | idx
+			code[pc] = instr
+		case setVar:
+			level := instr.idx >> 24
+			idx := instr.idx & 0x00FFFFFF
+			level--
+			instr.idx = level<<24 | idx
+			code[pc] = instr
+		}
+	}
+}
+
+func (c *compiler) compileFunctionDecl(v *ast.FunctionDeclaration) {
+	idx, ok := c.scope.bindName(v.Function.Name.Name)
+	if !ok {
+		// TODO: error
+	}
+	_ = idx
+	// log.Printf("Define function: %s: %x", v.Function.Name.Name, idx)
+}
+
+func (c *compiler) compileFunction(v *ast.FunctionDeclaration) {
+	e := &compiledIdentifierExpr{
+		name: v.Function.Name.Name,
+	}
+	e.init(c, v.Function.Idx0())
+	e.emitSetter(c.compileFunctionLiteral(v.Function, false))
+	c.emit(pop)
+}
+
+func (c *compiler) emit(instructions ...instruction) {
+	c.p.code = append(c.p.code, instructions...)
+}
+
+func (c *compiler) throwSyntaxError(offset int, format string, args ...interface{}) {
+	panic(&CompilerSyntaxError{
+		CompilerError: CompilerError{
+			File:    c.p.src,
+			Offset:  offset,
+			Message: fmt.Sprintf(format, args...),
+		},
+	})
+}
+
+func (c *compiler) isStrict(list []ast.Statement) bool {
+	for _, st := range list {
+		if st, ok := st.(*ast.ExpressionStatement); ok {
+			if e, ok := st.Expression.(*ast.StringLiteral); ok {
+				if e.Literal == `"use strict"` || e.Literal == `'use strict'` {
+					return true
+				}
+			} else {
+				break
+			}
+		} else {
+			break
+		}
+	}
+	return false
+}
+
+func (c *compiler) isStrictStatement(s ast.Statement) bool {
+	if s, ok := s.(*ast.BlockStatement); ok {
+		return c.isStrict(s.List)
+	}
+	return false
+}
+
+func (c *compiler) checkIdentifierName(name string, offset int) {
+	switch name {
+	case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield":
+		c.throwSyntaxError(offset, "Unexpected strict mode reserved word")
+	}
+}
+
+func (c *compiler) checkIdentifierLName(name string, offset int) {
+	switch name {
+	case "eval", "arguments":
+		c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode")
+	}
+}

+ 1560 - 0
compiler_expr.go

@@ -0,0 +1,1560 @@
+package goja
+
+import (
+	"fmt"
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+	"regexp"
+)
+
+var (
+	octalRegexp = regexp.MustCompile(`^0[0-7]`)
+)
+
+type compiledExpr interface {
+	emitGetter(putOnStack bool)
+	emitSetter(valueExpr compiledExpr)
+	emitUnary(prepare, body func(), postfix, putOnStack bool)
+	deleteExpr() compiledExpr
+	constant() bool
+	addSrcMap()
+}
+
+type compiledExprOrRef interface {
+	compiledExpr
+	emitGetterOrRef()
+}
+
+type compiledCallExpr struct {
+	baseCompiledExpr
+	args   []compiledExpr
+	callee compiledExpr
+}
+
+type compiledObjectLiteral struct {
+	baseCompiledExpr
+	expr *ast.ObjectLiteral
+}
+
+type compiledArrayLiteral struct {
+	baseCompiledExpr
+	expr *ast.ArrayLiteral
+}
+
+type compiledRegexpLiteral struct {
+	baseCompiledExpr
+	expr *ast.RegExpLiteral
+}
+
+type compiledLiteral struct {
+	baseCompiledExpr
+	val Value
+}
+
+type compiledAssignExpr struct {
+	baseCompiledExpr
+	left, right compiledExpr
+	operator    token.Token
+}
+
+type deleteGlobalExpr struct {
+	baseCompiledExpr
+	name string
+}
+
+type deleteVarExpr struct {
+	baseCompiledExpr
+	name string
+}
+
+type deletePropExpr struct {
+	baseCompiledExpr
+	left compiledExpr
+	name string
+}
+
+type deleteElemExpr struct {
+	baseCompiledExpr
+	left, member compiledExpr
+}
+
+type constantExpr struct {
+	baseCompiledExpr
+	val Value
+}
+
+type baseCompiledExpr struct {
+	c      *compiler
+	offset int
+}
+
+type compiledIdentifierExpr struct {
+	baseCompiledExpr
+	name string
+}
+
+type compiledFunctionLiteral struct {
+	baseCompiledExpr
+	expr   *ast.FunctionLiteral
+	isExpr bool
+}
+
+type compiledBracketExpr struct {
+	baseCompiledExpr
+	left, member compiledExpr
+}
+
+type compiledThisExpr struct {
+	baseCompiledExpr
+}
+
+type compiledNewExpr struct {
+	baseCompiledExpr
+	callee compiledExpr
+	args   []compiledExpr
+}
+
+type compiledSequenceExpr struct {
+	baseCompiledExpr
+	sequence []compiledExpr
+}
+
+type compiledUnaryExpr struct {
+	baseCompiledExpr
+	operand  compiledExpr
+	operator token.Token
+	postfix  bool
+}
+
+type compiledConditionalExpr struct {
+	baseCompiledExpr
+	test, consequent, alternate compiledExpr
+}
+
+type compiledLogicalOr struct {
+	baseCompiledExpr
+	left, right compiledExpr
+}
+
+type compiledLogicalAnd struct {
+	baseCompiledExpr
+	left, right compiledExpr
+}
+
+type compiledBinaryExpr struct {
+	baseCompiledExpr
+	left, right compiledExpr
+	operator    token.Token
+}
+
+type compiledVariableExpr struct {
+	baseCompiledExpr
+	name        string
+	initializer compiledExpr
+	expr        *ast.VariableExpression
+}
+
+type compiledEnumGetExpr struct {
+	baseCompiledExpr
+}
+
+type defaultDeleteExpr struct {
+	baseCompiledExpr
+	expr compiledExpr
+}
+
+func (e *defaultDeleteExpr) emitGetter(putOnStack bool) {
+	e.expr.emitGetter(false)
+	if putOnStack {
+		e.c.emit(loadVal(e.c.p.defineLiteralValue(valueTrue)))
+	}
+}
+
+func (c *compiler) compileExpression(v ast.Expression) compiledExpr {
+	// log.Printf("compileExpression: %T", v)
+	switch v := v.(type) {
+	case nil:
+		return nil
+	case *ast.AssignExpression:
+		return c.compileAssignExpression(v)
+	case *ast.NumberLiteral:
+		return c.compileNumberLiteral(v)
+	case *ast.StringLiteral:
+		return c.compileStringLiteral(v)
+	case *ast.BooleanLiteral:
+		return c.compileBooleanLiteral(v)
+	case *ast.NullLiteral:
+		r := &compiledLiteral{
+			val: _null,
+		}
+		r.init(c, v.Idx0())
+		return r
+	case *ast.Identifier:
+		return c.compileIdentifierExpression(v)
+	case *ast.CallExpression:
+		return c.compileCallExpression(v)
+	case *ast.ObjectLiteral:
+		return c.compileObjectLiteral(v)
+	case *ast.ArrayLiteral:
+		return c.compileArrayLiteral(v)
+	case *ast.RegExpLiteral:
+		return c.compileRegexpLiteral(v)
+	case *ast.VariableExpression:
+		return c.compileVariableExpression(v)
+	case *ast.BinaryExpression:
+		return c.compileBinaryExpression(v)
+	case *ast.UnaryExpression:
+		return c.compileUnaryExpression(v)
+	case *ast.ConditionalExpression:
+		return c.compileConditionalExpression(v)
+	case *ast.FunctionLiteral:
+		return c.compileFunctionLiteral(v, true)
+	case *ast.DotExpression:
+		r := &compiledDotExpr{
+			left: c.compileExpression(v.Left),
+			name: v.Identifier.Name,
+		}
+		r.init(c, v.Idx0())
+		return r
+	case *ast.BracketExpression:
+		r := &compiledBracketExpr{
+			left:   c.compileExpression(v.Left),
+			member: c.compileExpression(v.Member),
+		}
+		r.init(c, v.Idx0())
+		return r
+	case *ast.ThisExpression:
+		r := &compiledThisExpr{}
+		r.init(c, v.Idx0())
+		return r
+	case *ast.SequenceExpression:
+		return c.compileSequenceExpression(v)
+	case *ast.NewExpression:
+		return c.compileNewExpression(v)
+	default:
+		panic(fmt.Errorf("Unknown expression type: %T", v))
+	}
+}
+
+func (e *baseCompiledExpr) constant() bool {
+	return false
+}
+
+func (e *baseCompiledExpr) init(c *compiler, idx file.Idx) {
+	e.c = c
+	e.offset = int(idx) - 1
+}
+
+func (e *baseCompiledExpr) emitSetter(valueExpr compiledExpr) {
+	e.c.throwSyntaxError(e.offset, "Not a valid left-value expression")
+}
+
+func (e *baseCompiledExpr) deleteExpr() compiledExpr {
+	r := &constantExpr{
+		val: valueTrue,
+	}
+	r.init(e.c, file.Idx(e.offset+1))
+	return r
+}
+
+func (e *baseCompiledExpr) emitUnary(prepare, body func(), postfix bool, putOnStack bool) {
+	e.c.throwSyntaxError(e.offset, "Not a valid left-value expression")
+}
+
+func (e *baseCompiledExpr) addSrcMap() {
+	if e.offset > 0 {
+		e.c.p.srcMap = append(e.c.p.srcMap, srcMapItem{pc: len(e.c.p.code), srcPos: e.offset})
+	}
+}
+
+func (e *constantExpr) emitGetter(putOnStack bool) {
+	if putOnStack {
+		e.addSrcMap()
+		e.c.emit(loadVal(e.c.p.defineLiteralValue(e.val)))
+	}
+}
+
+func (e *compiledIdentifierExpr) emitGetter(putOnStack bool) {
+	e.addSrcMap()
+	if idx, found, noDynamics := e.c.scope.lookupName(e.name); noDynamics {
+		if found {
+			if putOnStack {
+				e.c.emit(getLocal(idx))
+			}
+		} else {
+			panic("No dynamics and not found")
+		}
+	} else {
+		if found {
+			e.c.emit(getVar{name: e.name, idx: idx})
+		} else {
+			e.c.emit(getVar1(e.name))
+		}
+		if !putOnStack {
+			e.c.emit(pop)
+		}
+	}
+}
+
+func (e *compiledIdentifierExpr) emitGetterOrRef() {
+	e.addSrcMap()
+	if idx, found, noDynamics := e.c.scope.lookupName(e.name); noDynamics {
+		if found {
+			e.c.emit(getLocal(idx))
+		} else {
+			panic("No dynamics and not found")
+		}
+	} else {
+		if found {
+			e.c.emit(getVar{name: e.name, idx: idx, ref: true})
+		} else {
+			e.c.emit(getVar1Callee(e.name))
+		}
+	}
+}
+
+func (c *compiler) emitVarSetter1(name string, offset int, emitRight func(isRef bool)) {
+	if c.scope.strict {
+		c.checkIdentifierLName(name, offset)
+	}
+
+	if idx, found, noDynamics := c.scope.lookupName(name); noDynamics {
+		emitRight(false)
+		if found {
+			c.emit(setLocal(idx))
+		} else {
+			if c.scope.strict {
+				c.emit(setGlobalStrict(name))
+			} else {
+				c.emit(setGlobal(name))
+			}
+		}
+	} else {
+		if found {
+			c.emit(resolveVar{name: name, idx: idx, strict: c.scope.strict})
+			emitRight(true)
+			c.emit(putValue)
+		} else {
+			if c.scope.strict {
+				c.emit(resolveVar1Strict(name))
+			} else {
+				c.emit(resolveVar1(name))
+			}
+			emitRight(true)
+			c.emit(putValue)
+		}
+	}
+}
+
+func (c *compiler) emitVarSetter(name string, offset int, valueExpr compiledExpr) {
+	c.emitVarSetter1(name, offset, func(bool) {
+		c.emitExpr(valueExpr, true)
+	})
+}
+
+func (e *compiledVariableExpr) emitSetter(valueExpr compiledExpr) {
+	e.c.emitVarSetter(e.name, e.offset, valueExpr)
+}
+
+func (e *compiledIdentifierExpr) emitSetter(valueExpr compiledExpr) {
+	e.c.emitVarSetter(e.name, e.offset, valueExpr)
+}
+
+func (e *compiledIdentifierExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) {
+	if putOnStack {
+		e.c.emitVarSetter1(e.name, e.offset, func(isRef bool) {
+			e.c.emit(loadUndef)
+			if isRef {
+				e.c.emit(getValue)
+			} else {
+				e.emitGetter(true)
+			}
+			if prepare != nil {
+				prepare()
+			}
+			if !postfix {
+				body()
+			}
+			e.c.emit(rdupN(1))
+			if postfix {
+				body()
+			}
+		})
+		e.c.emit(pop)
+	} else {
+		e.c.emitVarSetter1(e.name, e.offset, func(isRef bool) {
+			if isRef {
+				e.c.emit(getValue)
+			} else {
+				e.emitGetter(true)
+			}
+			body()
+		})
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledIdentifierExpr) deleteExpr() compiledExpr {
+	if e.c.scope.strict {
+		e.c.throwSyntaxError(e.offset, "Delete of an unqualified identifier in strict mode")
+		panic("Unreachable")
+	}
+	if _, found, noDynamics := e.c.scope.lookupName(e.name); noDynamics {
+		if !found {
+			r := &deleteGlobalExpr{
+				name: e.name,
+			}
+			r.init(e.c, file.Idx(0))
+			return r
+		} else {
+			r := &constantExpr{
+				val: valueFalse,
+			}
+			r.init(e.c, file.Idx(0))
+			return r
+		}
+	} else {
+		r := &deleteVarExpr{
+			name: e.name,
+		}
+		r.init(e.c, file.Idx(e.offset+1))
+		return r
+	}
+}
+
+type compiledDotExpr struct {
+	baseCompiledExpr
+	left compiledExpr
+	name string
+}
+
+func (e *compiledDotExpr) emitGetter(putOnStack bool) {
+	e.left.emitGetter(true)
+	e.addSrcMap()
+	e.c.emit(getProp(e.name))
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledDotExpr) emitSetter(valueExpr compiledExpr) {
+	e.left.emitGetter(true)
+	valueExpr.emitGetter(true)
+	if e.c.scope.strict {
+		e.c.emit(setPropStrict(e.name))
+	} else {
+		e.c.emit(setProp(e.name))
+	}
+}
+
+func (e *compiledDotExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) {
+	if !putOnStack {
+		e.left.emitGetter(true)
+		e.c.emit(dup)
+		e.c.emit(getProp(e.name))
+		body()
+		if e.c.scope.strict {
+			e.c.emit(setPropStrict(e.name), pop)
+		} else {
+			e.c.emit(setProp(e.name), pop)
+		}
+	} else {
+		if !postfix {
+			e.left.emitGetter(true)
+			e.c.emit(dup)
+			e.c.emit(getProp(e.name))
+			if prepare != nil {
+				prepare()
+			}
+			body()
+			if e.c.scope.strict {
+				e.c.emit(setPropStrict(e.name))
+			} else {
+				e.c.emit(setProp(e.name))
+			}
+		} else {
+			e.c.emit(loadUndef)
+			e.left.emitGetter(true)
+			e.c.emit(dup)
+			e.c.emit(getProp(e.name))
+			if prepare != nil {
+				prepare()
+			}
+			e.c.emit(rdupN(2))
+			body()
+			if e.c.scope.strict {
+				e.c.emit(setPropStrict(e.name))
+			} else {
+				e.c.emit(setProp(e.name))
+			}
+			e.c.emit(pop)
+		}
+	}
+}
+
+func (e *compiledDotExpr) deleteExpr() compiledExpr {
+	r := &deletePropExpr{
+		left: e.left,
+		name: e.name,
+	}
+	r.init(e.c, file.Idx(0))
+	return r
+}
+
+func (e *compiledBracketExpr) emitGetter(putOnStack bool) {
+	e.left.emitGetter(true)
+	e.member.emitGetter(true)
+	e.addSrcMap()
+	e.c.emit(getElem)
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledBracketExpr) emitSetter(valueExpr compiledExpr) {
+	e.left.emitGetter(true)
+	e.member.emitGetter(true)
+	valueExpr.emitGetter(true)
+	if e.c.scope.strict {
+		e.c.emit(setElemStrict)
+	} else {
+		e.c.emit(setElem)
+	}
+}
+
+func (e *compiledBracketExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) {
+	if !putOnStack {
+		e.left.emitGetter(true)
+		e.member.emitGetter(true)
+		e.c.emit(dupN(1), dupN(1))
+		e.c.emit(getElem)
+		body()
+		if e.c.scope.strict {
+			e.c.emit(setElemStrict, pop)
+		} else {
+			e.c.emit(setElem, pop)
+		}
+	} else {
+		if !postfix {
+			e.left.emitGetter(true)
+			e.member.emitGetter(true)
+			e.c.emit(dupN(1), dupN(1))
+			e.c.emit(getElem)
+			if prepare != nil {
+				prepare()
+			}
+			body()
+			if e.c.scope.strict {
+				e.c.emit(setElemStrict)
+			} else {
+				e.c.emit(setElem)
+			}
+		} else {
+			e.c.emit(loadUndef)
+			e.left.emitGetter(true)
+			e.member.emitGetter(true)
+			e.c.emit(dupN(1), dupN(1))
+			e.c.emit(getElem)
+			if prepare != nil {
+				prepare()
+			}
+			e.c.emit(rdupN(3))
+			body()
+			if e.c.scope.strict {
+				e.c.emit(setElemStrict, pop)
+			} else {
+				e.c.emit(setElem, pop)
+			}
+		}
+	}
+}
+
+func (e *compiledBracketExpr) deleteExpr() compiledExpr {
+	r := &deleteElemExpr{
+		left:   e.left,
+		member: e.member,
+	}
+	r.init(e.c, file.Idx(0))
+	return r
+}
+
+func (e *deleteElemExpr) emitGetter(putOnStack bool) {
+	e.left.emitGetter(true)
+	e.member.emitGetter(true)
+	e.addSrcMap()
+	if e.c.scope.strict {
+		e.c.emit(deleteElemStrict)
+	} else {
+		e.c.emit(deleteElem)
+	}
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *deletePropExpr) emitGetter(putOnStack bool) {
+	e.left.emitGetter(true)
+	e.addSrcMap()
+	if e.c.scope.strict {
+		e.c.emit(deletePropStrict(e.name))
+	} else {
+		e.c.emit(deleteProp(e.name))
+	}
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *deleteVarExpr) emitGetter(putOnStack bool) {
+	/*if e.c.scope.strict {
+		e.c.throwSyntaxError(e.offset, "Delete of an unqualified identifier in strict mode")
+		return
+	}*/
+	e.c.emit(deleteVar(e.name))
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *deleteGlobalExpr) emitGetter(putOnStack bool) {
+	/*if e.c.scope.strict {
+		e.c.throwSyntaxError(e.offset, "Delete of an unqualified identifier in strict mode")
+		return
+	}*/
+
+	e.c.emit(deleteGlobal(e.name))
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledAssignExpr) emitGetter(putOnStack bool) {
+	e.addSrcMap()
+	switch e.operator {
+	case token.ASSIGN:
+		e.left.emitSetter(e.right)
+	case token.PLUS:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(add)
+		}, false, putOnStack)
+		return
+	case token.MINUS:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(sub)
+		}, false, putOnStack)
+		return
+	case token.MULTIPLY:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(mul)
+		}, false, putOnStack)
+		return
+	case token.SLASH:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(div)
+		}, false, putOnStack)
+		return
+	case token.REMAINDER:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(mod)
+		}, false, putOnStack)
+		return
+	case token.OR:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(or)
+		}, false, putOnStack)
+		return
+	case token.AND:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(and)
+		}, false, putOnStack)
+		return
+	case token.EXCLUSIVE_OR:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(xor)
+		}, false, putOnStack)
+		return
+	case token.SHIFT_LEFT:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(sal)
+		}, false, putOnStack)
+		return
+	case token.SHIFT_RIGHT:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(sar)
+		}, false, putOnStack)
+		return
+	case token.UNSIGNED_SHIFT_RIGHT:
+		e.left.emitUnary(nil, func() {
+			e.right.emitGetter(true)
+			e.c.emit(shr)
+		}, false, putOnStack)
+		return
+	default:
+		panic(fmt.Errorf("Unknown assign operator: %s", e.operator.String()))
+	}
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledLiteral) emitGetter(putOnStack bool) {
+	if putOnStack {
+		e.addSrcMap()
+		e.c.emit(loadVal(e.c.p.defineLiteralValue(e.val)))
+	}
+}
+
+func (e *compiledLiteral) constant() bool {
+	return true
+}
+
+func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
+	e.c.newScope()
+	savedBlockStart := e.c.blockStart
+	savedPrg := e.c.p
+	e.c.p = &Program{
+		src: e.c.p.src,
+	}
+	e.c.blockStart = 0
+
+	if e.expr.Name != nil {
+		e.c.p.funcName = e.expr.Name.Name
+	}
+	block := e.c.block
+	e.c.block = nil
+	defer func() {
+		e.c.block = block
+	}()
+
+	if !e.c.scope.strict {
+		e.c.scope.strict = e.c.isStrictStatement(e.expr.Body)
+	}
+
+	if e.c.scope.strict {
+		if e.expr.Name != nil {
+			e.c.checkIdentifierLName(e.expr.Name.Name, int(e.expr.Name.Idx)-1)
+		}
+		for _, item := range e.expr.ParameterList.List {
+			e.c.checkIdentifierName(item.Name, int(item.Idx)-1)
+			e.c.checkIdentifierLName(item.Name, int(item.Idx)-1)
+		}
+	}
+
+	length := len(e.expr.ParameterList.List)
+
+	for _, item := range e.expr.ParameterList.List {
+		_, unique := e.c.scope.bindNameShadow(item.Name)
+		if !unique && e.c.scope.strict {
+			e.c.throwSyntaxError(int(item.Idx)-1, "Strict mode function may not have duplicate parameter names (%s)", item.Name)
+			return
+		}
+	}
+	paramsCount := len(e.c.scope.names)
+	e.c.compileDeclList(e.expr.DeclarationList, true)
+	var needCallee bool
+	var calleeIdx uint32
+	if e.isExpr && e.expr.Name != nil {
+		if idx, ok := e.c.scope.bindName(e.expr.Name.Name); ok {
+			calleeIdx = idx
+			needCallee = true
+		}
+	}
+	lenBefore := len(e.c.scope.names)
+	namesBefore := make([]string, 0, lenBefore)
+	for key, _ := range e.c.scope.names {
+		namesBefore = append(namesBefore, key)
+	}
+	maxPreambleLen := 2
+	e.c.p.code = make([]instruction, maxPreambleLen)
+	if needCallee {
+		e.c.emit(loadCallee, setLocalP(calleeIdx))
+	}
+
+	e.c.compileFunctions(e.expr.DeclarationList)
+	e.c.compileStatement(e.expr.Body, false)
+
+	if e.c.blockStart >= len(e.c.p.code)-1 || e.c.p.code[len(e.c.p.code)-1] != ret {
+		e.c.emit(loadUndef, ret)
+	}
+
+	if !e.c.scope.dynamic && !e.c.scope.accessed {
+		// log.Printf("Function can use inline stash")
+		l := 0
+		if !e.c.scope.strict && e.c.scope.thisNeeded {
+			l = 2
+			e.c.p.code = e.c.p.code[maxPreambleLen-2:]
+			e.c.p.code[1] = boxThis
+		} else {
+			l = 1
+			e.c.p.code = e.c.p.code[maxPreambleLen-1:]
+		}
+		e.c.convertFunctionToStashless(e.c.p.code, paramsCount)
+		for i, _ := range e.c.p.srcMap {
+			e.c.p.srcMap[i].pc -= maxPreambleLen - l
+		}
+	} else {
+		l := 1 + len(e.c.scope.names)
+		if e.c.scope.argsNeeded {
+			l += 3
+		}
+		if !e.c.scope.strict && e.c.scope.thisNeeded {
+			l++
+		}
+
+		code := make([]instruction, l+len(e.c.p.code)-maxPreambleLen)
+		code[0] = enterFunc(length)
+		for name, nameIdx := range e.c.scope.names {
+			code[nameIdx+1] = bindName(name)
+		}
+		pos := 1 + len(e.c.scope.names)
+
+		if !e.c.scope.strict && e.c.scope.thisNeeded {
+			code[pos] = boxThis
+			pos++
+		}
+
+		if e.c.scope.argsNeeded {
+			if e.c.scope.strict {
+				code[pos] = createArgsStrict(length)
+			} else {
+				code[pos] = createArgs(length)
+			}
+			pos++
+			idx, exists := e.c.scope.names["arguments"]
+			if !exists {
+				panic("No arguments")
+			}
+			code[pos] = setLocal(idx)
+			pos++
+			code[pos] = pop
+			pos++
+		}
+
+		copy(code[l:], e.c.p.code[maxPreambleLen:])
+		e.c.p.code = code
+		for i, _ := range e.c.p.srcMap {
+			e.c.p.srcMap[i].pc += l - maxPreambleLen
+		}
+	}
+
+	strict := e.c.scope.strict
+	p := e.c.p
+	// e.c.p.dumpCode()
+	e.c.popScope()
+	e.c.p = savedPrg
+	e.c.blockStart = savedBlockStart
+	name := ""
+	if e.expr.Name != nil {
+		name = e.expr.Name.Name
+	}
+	e.c.emit(&newFunc{prg: p, length: uint32(length), name: name, srcStart: uint32(e.expr.Idx0() - 1), srcEnd: uint32(e.expr.Idx1() - 1), strict: strict})
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (c *compiler) compileFunctionLiteral(v *ast.FunctionLiteral, isExpr bool) compiledExpr {
+	if v.Name != nil && c.scope.strict {
+		c.checkIdentifierLName(v.Name.Name, int(v.Name.Idx)-1)
+	}
+	r := &compiledFunctionLiteral{
+		expr:   v,
+		isExpr: isExpr,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func nearestNonLexical(s *scope) *scope {
+	for ; s != nil && s.lexical; s = s.outer {
+	}
+	return s
+}
+
+func (e *compiledThisExpr) emitGetter(putOnStack bool) {
+	if putOnStack {
+		e.addSrcMap()
+		if e.c.scope.eval || e.c.scope.isFunction() {
+			nearestNonLexical(e.c.scope).thisNeeded = true
+			e.c.emit(loadStack(0))
+		} else {
+			e.c.emit(loadGlobalObject)
+		}
+	}
+}
+
+/*
+func (e *compiledThisExpr) deleteExpr() compiledExpr {
+	r := &compiledLiteral{
+		val: valueTrue,
+	}
+	r.init(e.c, 0)
+	return r
+}
+*/
+
+func (e *compiledNewExpr) emitGetter(putOnStack bool) {
+	e.callee.emitGetter(true)
+	for _, expr := range e.args {
+		expr.emitGetter(true)
+	}
+	e.addSrcMap()
+	e.c.emit(_new(len(e.args)))
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (c *compiler) compileNewExpression(v *ast.NewExpression) compiledExpr {
+	args := make([]compiledExpr, len(v.ArgumentList))
+	for i, expr := range v.ArgumentList {
+		args[i] = c.compileExpression(expr)
+	}
+	r := &compiledNewExpr{
+		callee: c.compileExpression(v.Callee),
+		args:   args,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledSequenceExpr) emitGetter(putOnStack bool) {
+	if len(e.sequence) > 0 {
+		for i := 0; i < len(e.sequence)-1; i++ {
+			e.sequence[i].emitGetter(false)
+		}
+		e.sequence[len(e.sequence)-1].emitGetter(putOnStack)
+	}
+}
+
+func (c *compiler) compileSequenceExpression(v *ast.SequenceExpression) compiledExpr {
+	s := make([]compiledExpr, len(v.Sequence))
+	for i, expr := range v.Sequence {
+		s[i] = c.compileExpression(expr)
+	}
+	r := &compiledSequenceExpr{
+		sequence: s,
+	}
+	var idx file.Idx
+	if len(v.Sequence) > 0 {
+		idx = v.Idx0()
+	}
+	r.init(c, idx)
+	return r
+}
+
+func (c *compiler) emitThrow(v Value) {
+	if o, ok := v.(*Object); ok {
+		t := o.self.getStr("name").String()
+		switch t {
+		case "TypeError":
+			c.emit(getVar1(t))
+			msg := o.self.getStr("message")
+			if msg != nil {
+				c.emit(loadVal(c.p.defineLiteralValue(msg)))
+				c.emit(_new(1))
+			} else {
+				c.emit(_new(0))
+			}
+			c.emit(throw)
+			return
+		}
+	}
+	panic(fmt.Errorf("Unknown exception type thrown while evaliating constant expression: %s", v.String()))
+}
+
+func (c *compiler) emitConst(expr compiledExpr, putOnStack bool) {
+	v, ex := c.evalConst(expr)
+	if ex == nil {
+		if putOnStack {
+			c.emit(loadVal(c.p.defineLiteralValue(v)))
+		}
+	} else {
+		c.emitThrow(ex.val)
+	}
+}
+
+func (c *compiler) emitExpr(expr compiledExpr, putOnStack bool) {
+	if expr.constant() {
+		c.emitConst(expr, putOnStack)
+	} else {
+		expr.emitGetter(putOnStack)
+	}
+}
+
+func (c *compiler) evalConst(expr compiledExpr) (Value, *Exception) {
+	if expr, ok := expr.(*compiledLiteral); ok {
+		return expr.val, nil
+	}
+	var savedPrg *Program
+	createdPrg := false
+	if c.evalVM.prg == nil {
+		c.evalVM.prg = &Program{}
+		savedPrg = c.p
+		c.p = c.evalVM.prg
+		createdPrg = true
+	}
+	savedPc := len(c.p.code)
+	expr.emitGetter(true)
+	c.emit(halt)
+	c.evalVM.pc = savedPc
+	ex := c.evalVM.runTry()
+	if createdPrg {
+		c.evalVM.prg = nil
+		c.evalVM.pc = 0
+		c.p = savedPrg
+	} else {
+		c.evalVM.prg.code = c.evalVM.prg.code[:savedPc]
+		c.p.code = c.evalVM.prg.code
+	}
+	if ex == nil {
+		return c.evalVM.pop(), nil
+	}
+	return nil, ex
+}
+
+func (e *compiledUnaryExpr) constant() bool {
+	return e.operand.constant()
+}
+
+func (e *compiledUnaryExpr) emitGetter(putOnStack bool) {
+	var prepare, body func()
+
+	toNumber := func() {
+		e.c.emit(toNumber)
+	}
+
+	switch e.operator {
+	case token.NOT:
+		e.operand.emitGetter(true)
+		e.c.emit(not)
+		goto end
+	case token.BITWISE_NOT:
+		e.operand.emitGetter(true)
+		e.c.emit(bnot)
+		goto end
+	case token.TYPEOF:
+		if o, ok := e.operand.(compiledExprOrRef); ok {
+			o.emitGetterOrRef()
+		} else {
+			e.operand.emitGetter(true)
+		}
+		e.c.emit(typeof)
+		goto end
+	case token.DELETE:
+		e.operand.deleteExpr().emitGetter(putOnStack)
+		return
+	case token.MINUS:
+		e.c.emitExpr(e.operand, true)
+		e.c.emit(neg)
+		goto end
+	case token.PLUS:
+		e.c.emitExpr(e.operand, true)
+		e.c.emit(plus)
+		goto end
+	case token.INCREMENT:
+		prepare = toNumber
+		body = func() {
+			e.c.emit(inc)
+		}
+	case token.DECREMENT:
+		prepare = toNumber
+		body = func() {
+			e.c.emit(dec)
+		}
+	case token.VOID:
+		e.c.emitExpr(e.operand, false)
+		if putOnStack {
+			e.c.emit(loadUndef)
+		}
+		return
+	default:
+		panic(fmt.Errorf("Unknown unary operator: %s", e.operator.String()))
+	}
+
+	e.operand.emitUnary(prepare, body, e.postfix, putOnStack)
+	return
+
+end:
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (c *compiler) compileUnaryExpression(v *ast.UnaryExpression) compiledExpr {
+	r := &compiledUnaryExpr{
+		operand:  c.compileExpression(v.Operand),
+		operator: v.Operator,
+		postfix:  v.Postfix,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledConditionalExpr) emitGetter(putOnStack bool) {
+	e.test.emitGetter(true)
+	j := len(e.c.p.code)
+	e.c.emit(nil)
+	e.consequent.emitGetter(putOnStack)
+	j1 := len(e.c.p.code)
+	e.c.emit(nil)
+	e.c.p.code[j] = jne(len(e.c.p.code) - j)
+	e.alternate.emitGetter(putOnStack)
+	e.c.p.code[j1] = jump(len(e.c.p.code) - j1)
+}
+
+func (c *compiler) compileConditionalExpression(v *ast.ConditionalExpression) compiledExpr {
+	r := &compiledConditionalExpr{
+		test:       c.compileExpression(v.Test),
+		consequent: c.compileExpression(v.Consequent),
+		alternate:  c.compileExpression(v.Alternate),
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledLogicalOr) constant() bool {
+	if e.left.constant() {
+		if v, ex := e.c.evalConst(e.left); ex == nil {
+			if v.ToBoolean() {
+				return true
+			}
+			return e.right.constant()
+		} else {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (e *compiledLogicalOr) emitGetter(putOnStack bool) {
+	if e.left.constant() {
+		if v, ex := e.c.evalConst(e.left); ex == nil {
+			if !v.ToBoolean() {
+				e.c.emitExpr(e.right, putOnStack)
+			} else {
+				if putOnStack {
+					e.c.emit(loadVal(e.c.p.defineLiteralValue(v)))
+				}
+			}
+		} else {
+			e.c.emitThrow(ex.val)
+		}
+		return
+	}
+	e.c.emitExpr(e.left, true)
+	e.c.markBlockStart()
+	j := len(e.c.p.code)
+	e.addSrcMap()
+	e.c.emit(nil)
+	e.c.emit(pop)
+	e.c.emitExpr(e.right, true)
+	e.c.p.code[j] = jeq1(len(e.c.p.code) - j)
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledLogicalAnd) constant() bool {
+	if e.left.constant() {
+		if v, ex := e.c.evalConst(e.left); ex == nil {
+			if !v.ToBoolean() {
+				return true
+			} else {
+				return e.right.constant()
+			}
+		} else {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (e *compiledLogicalAnd) emitGetter(putOnStack bool) {
+	var j int
+	if e.left.constant() {
+		if v, ex := e.c.evalConst(e.left); ex == nil {
+			if !v.ToBoolean() {
+				e.c.emit(loadVal(e.c.p.defineLiteralValue(v)))
+			} else {
+				e.c.emitExpr(e.right, putOnStack)
+			}
+		} else {
+			e.c.emitThrow(ex.val)
+		}
+		return
+	}
+	e.left.emitGetter(true)
+	e.c.markBlockStart()
+	j = len(e.c.p.code)
+	e.addSrcMap()
+	e.c.emit(nil)
+	e.c.emit(pop)
+	e.c.emitExpr(e.right, true)
+	e.c.p.code[j] = jneq1(len(e.c.p.code) - j)
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledBinaryExpr) constant() bool {
+	return e.left.constant() && e.right.constant()
+}
+
+func (e *compiledBinaryExpr) emitGetter(putOnStack bool) {
+	e.c.emitExpr(e.left, true)
+	e.c.emitExpr(e.right, true)
+	e.addSrcMap()
+
+	switch e.operator {
+	case token.LESS:
+		e.c.emit(op_lt)
+	case token.GREATER:
+		e.c.emit(op_gt)
+	case token.LESS_OR_EQUAL:
+		e.c.emit(op_lte)
+	case token.GREATER_OR_EQUAL:
+		e.c.emit(op_gte)
+	case token.EQUAL:
+		e.c.emit(op_eq)
+	case token.NOT_EQUAL:
+		e.c.emit(op_neq)
+	case token.STRICT_EQUAL:
+		e.c.emit(op_strict_eq)
+	case token.STRICT_NOT_EQUAL:
+		e.c.emit(op_strict_neq)
+	case token.PLUS:
+		e.c.emit(add)
+	case token.MINUS:
+		e.c.emit(sub)
+	case token.MULTIPLY:
+		e.c.emit(mul)
+	case token.SLASH:
+		e.c.emit(div)
+	case token.REMAINDER:
+		e.c.emit(mod)
+	case token.AND:
+		e.c.emit(and)
+	case token.OR:
+		e.c.emit(or)
+	case token.EXCLUSIVE_OR:
+		e.c.emit(xor)
+	case token.INSTANCEOF:
+		e.c.emit(op_instanceof)
+	case token.IN:
+		e.c.emit(op_in)
+	case token.SHIFT_LEFT:
+		e.c.emit(sal)
+	case token.SHIFT_RIGHT:
+		e.c.emit(sar)
+	case token.UNSIGNED_SHIFT_RIGHT:
+		e.c.emit(shr)
+	default:
+		panic(fmt.Errorf("Unknown operator: %s", e.operator.String()))
+	}
+
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (c *compiler) compileBinaryExpression(v *ast.BinaryExpression) compiledExpr {
+
+	switch v.Operator {
+	case token.LOGICAL_OR:
+		return c.compileLogicalOr(v.Left, v.Right, v.Idx0())
+	case token.LOGICAL_AND:
+		return c.compileLogicalAnd(v.Left, v.Right, v.Idx0())
+	}
+
+	r := &compiledBinaryExpr{
+		left:     c.compileExpression(v.Left),
+		right:    c.compileExpression(v.Right),
+		operator: v.Operator,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (c *compiler) compileLogicalOr(left, right ast.Expression, idx file.Idx) compiledExpr {
+	r := &compiledLogicalOr{
+		left:  c.compileExpression(left),
+		right: c.compileExpression(right),
+	}
+	r.init(c, idx)
+	return r
+}
+
+func (c *compiler) compileLogicalAnd(left, right ast.Expression, idx file.Idx) compiledExpr {
+	r := &compiledLogicalAnd{
+		left:  c.compileExpression(left),
+		right: c.compileExpression(right),
+	}
+	r.init(c, idx)
+	return r
+}
+
+func (e *compiledVariableExpr) emitGetter(putOnStack bool) {
+	if e.initializer != nil {
+		idExpr := &compiledIdentifierExpr{
+			name: e.name,
+		}
+		idExpr.init(e.c, file.Idx(0))
+		idExpr.emitSetter(e.initializer)
+		if !putOnStack {
+			e.c.emit(pop)
+		}
+	} else {
+		if putOnStack {
+			e.c.emit(loadUndef)
+		}
+	}
+}
+
+func (c *compiler) compileVariableExpression(v *ast.VariableExpression) compiledExpr {
+	r := &compiledVariableExpr{
+		name:        v.Name,
+		initializer: c.compileExpression(v.Initializer),
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledObjectLiteral) emitGetter(putOnStack bool) {
+	e.addSrcMap()
+	e.c.emit(newObject)
+	for _, prop := range e.expr.Value {
+		e.c.compileExpression(prop.Value).emitGetter(true)
+		switch prop.Kind {
+		case "value":
+			if prop.Key == "__proto__" {
+				e.c.emit(setProto)
+			} else {
+				e.c.emit(setProp1(prop.Key))
+			}
+		case "get":
+			e.c.emit(setPropGetter(prop.Key))
+		case "set":
+			e.c.emit(setPropSetter(prop.Key))
+		default:
+			panic(fmt.Errorf("Unknown property kind: %s", prop.Kind))
+		}
+	}
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (c *compiler) compileObjectLiteral(v *ast.ObjectLiteral) compiledExpr {
+	r := &compiledObjectLiteral{
+		expr: v,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledArrayLiteral) emitGetter(putOnStack bool) {
+	e.addSrcMap()
+	for _, v := range e.expr.Value {
+		if v != nil {
+			e.c.compileExpression(v).emitGetter(true)
+		} else {
+			e.c.emit(loadNil)
+		}
+	}
+	e.c.emit(newArray(len(e.expr.Value)))
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (c *compiler) compileArrayLiteral(v *ast.ArrayLiteral) compiledExpr {
+	r := &compiledArrayLiteral{
+		expr: v,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledRegexpLiteral) emitGetter(putOnStack bool) {
+	if putOnStack {
+		pattern, global, ignoreCase, multiline, err := compileRegexp(e.expr.Pattern, e.expr.Flags)
+		if err != nil {
+			e.c.throwSyntaxError(e.offset, err.Error())
+		}
+
+		e.c.emit(&newRegexp{pattern: pattern,
+			src:        newStringValue(e.expr.Pattern),
+			global:     global,
+			ignoreCase: ignoreCase,
+			multiline:  multiline,
+		})
+	}
+}
+
+func (c *compiler) compileRegexpLiteral(v *ast.RegExpLiteral) compiledExpr {
+	r := &compiledRegexpLiteral{
+		expr: v,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledCallExpr) emitGetter(putOnStack bool) {
+	var calleeName string
+	switch callee := e.callee.(type) {
+	case *compiledDotExpr:
+		callee.left.emitGetter(true)
+		e.c.emit(dup)
+		e.c.emit(getPropCallee(callee.name))
+	case *compiledBracketExpr:
+		callee.left.emitGetter(true)
+		e.c.emit(dup)
+		callee.member.emitGetter(true)
+		e.c.emit(getElemCallee)
+	case *compiledIdentifierExpr:
+		e.c.emit(loadUndef)
+		calleeName = callee.name
+		callee.emitGetterOrRef()
+	default:
+		e.c.emit(loadUndef)
+		callee.emitGetter(true)
+	}
+
+	for _, expr := range e.args {
+		expr.emitGetter(true)
+	}
+
+	e.addSrcMap()
+	if calleeName == "eval" {
+		e.c.scope.dynamic = true
+		e.c.scope.thisNeeded = true
+		if e.c.scope.lexical {
+			e.c.scope.outer.dynamic = true
+		}
+		e.c.scope.accessed = true
+		if e.c.scope.strict {
+			e.c.emit(callEvalStrict(len(e.args)))
+		} else {
+			e.c.emit(callEval(len(e.args)))
+		}
+	} else {
+		e.c.emit(call(len(e.args)))
+	}
+
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledCallExpr) deleteExpr() compiledExpr {
+	r := &defaultDeleteExpr{
+		expr: e,
+	}
+	r.init(e.c, file.Idx(e.offset+1))
+	return r
+}
+
+func (c *compiler) compileCallExpression(v *ast.CallExpression) compiledExpr {
+
+	args := make([]compiledExpr, len(v.ArgumentList))
+	for i, argExpr := range v.ArgumentList {
+		args[i] = c.compileExpression(argExpr)
+	}
+
+	r := &compiledCallExpr{
+		args:   args,
+		callee: c.compileExpression(v.Callee),
+	}
+	r.init(c, v.LeftParenthesis)
+	return r
+}
+
+func (c *compiler) compileIdentifierExpression(v *ast.Identifier) compiledExpr {
+	if c.scope.strict {
+		c.checkIdentifierName(v.Name, int(v.Idx)-1)
+	}
+
+	r := &compiledIdentifierExpr{
+		name: v.Name,
+	}
+	r.offset = int(v.Idx) - 1
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr {
+	if c.scope.strict && octalRegexp.MatchString(v.Literal) {
+		c.throwSyntaxError(int(v.Idx)-1, "Octal literals are not allowed in strict mode")
+		panic("Unreachable")
+	}
+	var val Value
+	switch num := v.Value.(type) {
+	case int64:
+		val = intToValue(num)
+	case float64:
+		val = floatToValue(num)
+	default:
+		panic(fmt.Errorf("Unsupported number literal type: %T", v.Value))
+	}
+	r := &compiledLiteral{
+		val: val,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr {
+	r := &compiledLiteral{
+		val: newStringValue(v.Value),
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (c *compiler) compileBooleanLiteral(v *ast.BooleanLiteral) compiledExpr {
+	var val Value
+	if v.Value {
+		val = valueTrue
+	} else {
+		val = valueFalse
+	}
+
+	r := &compiledLiteral{
+		val: val,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (c *compiler) compileAssignExpression(v *ast.AssignExpression) compiledExpr {
+	// log.Printf("compileAssignExpression(): %+v", v)
+
+	r := &compiledAssignExpr{
+		left:     c.compileExpression(v.Left),
+		right:    c.compileExpression(v.Right),
+		operator: v.Operator,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledEnumGetExpr) emitGetter(putOnStack bool) {
+	e.c.emit(enumGet)
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}

+ 679 - 0
compiler_stmt.go

@@ -0,0 +1,679 @@
+package goja
+
+import (
+	"fmt"
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+	"strconv"
+)
+
+func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
+	// log.Printf("compileStatement(): %T", v)
+
+	switch v := v.(type) {
+	case *ast.BlockStatement:
+		c.compileBlockStatement(v, needResult)
+	case *ast.ExpressionStatement:
+		c.compileExpressionStatement(v, needResult)
+	case *ast.VariableStatement:
+		c.compileVariableStatement(v, needResult)
+	case *ast.ReturnStatement:
+		c.compileReturnStatement(v)
+	case *ast.IfStatement:
+		c.compileIfStatement(v, needResult)
+	case *ast.DoWhileStatement:
+		c.compileDoWhileStatement(v, needResult)
+	case *ast.ForStatement:
+		c.compileForStatement(v, needResult)
+	case *ast.ForInStatement:
+		c.compileForInStatement(v, needResult)
+	case *ast.WhileStatement:
+		c.compileWhileStatement(v, needResult)
+	case *ast.BranchStatement:
+		c.compileBranchStatement(v, needResult)
+	case *ast.TryStatement:
+		c.compileTryStatement(v)
+		if needResult {
+			c.emit(loadUndef)
+		}
+	case *ast.ThrowStatement:
+		c.compileThrowStatement(v)
+	case *ast.SwitchStatement:
+		c.compileSwitchStatement(v, needResult)
+	case *ast.LabelledStatement:
+		c.compileLabeledStatement(v, needResult)
+	case *ast.EmptyStatement:
+		if needResult {
+			c.emit(loadUndef)
+		}
+	case *ast.WithStatement:
+		c.compileWithStatement(v, needResult)
+	default:
+		panic(fmt.Errorf("Unknown statement type: %T", v))
+	}
+}
+
+func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult bool) {
+	label := v.Label.Name
+	for b := c.block; b != nil; b = b.outer {
+		if b.label == label {
+			c.throwSyntaxError(int(v.Label.Idx-1), "Label '%s' has already been declared", label)
+		}
+	}
+	switch s := v.Statement.(type) {
+	case *ast.BlockStatement:
+		c.compileLabeledBlockStatement(s, needResult, label)
+	case *ast.ForInStatement:
+		c.compileLabeledForInStatement(s, needResult, label)
+	case *ast.ForStatement:
+		c.compileLabeledForStatement(s, needResult, label)
+	case *ast.WhileStatement:
+		c.compileLabeledWhileStatement(s, needResult, label)
+	case *ast.DoWhileStatement:
+		c.compileLabeledDoWhileStatement(s, needResult, label)
+	default:
+		c.compileStatement(v.Statement, needResult)
+	}
+}
+
+func (c *compiler) compileTryStatement(v *ast.TryStatement) {
+	if c.scope.strict && v.Catch != nil {
+		switch v.Catch.Parameter.Name {
+		case "arguments", "eval":
+			c.throwSyntaxError(int(v.Catch.Parameter.Idx)-1, "Catch variable may not be eval or arguments in strict mode")
+		}
+	}
+	c.block = &block{
+		typ:   blockTry,
+		outer: c.block,
+	}
+	lbl := len(c.p.code)
+	c.emit(nil)
+	c.compileStatement(v.Body, false)
+	c.emit(halt)
+	lbl2 := len(c.p.code)
+	c.emit(nil)
+	var catchOffset int
+	dynamicCatch := true
+	if v.Catch != nil {
+		dyn := nearestNonLexical(c.scope).dynamic
+		accessed := c.scope.accessed
+		c.newScope()
+		c.scope.bindName(v.Catch.Parameter.Name)
+		c.scope.lexical = true
+		start := len(c.p.code)
+		c.emit(nil)
+		catchOffset = len(c.p.code) - lbl
+		c.emit(enterCatch(v.Catch.Parameter.Name))
+		c.compileStatement(v.Catch.Body, false)
+		dyn1 := c.scope.dynamic
+		accessed1 := c.scope.accessed
+		c.popScope()
+		if !dyn && !dyn1 && !accessed1 {
+			c.scope.accessed = accessed
+			dynamicCatch = false
+			code := c.p.code[start+1:]
+			m := make(map[uint32]uint32)
+			for pc, instr := range code {
+				switch instr := instr.(type) {
+				case getLocal:
+					level := uint32(instr) >> 24
+					idx := uint32(instr) & 0x00FFFFFF
+					if level > 0 {
+						level--
+						code[pc] = getLocal((level << 24) | idx)
+					} else {
+						// remap
+						newIdx, exists := m[idx]
+						if !exists {
+							exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp)
+							c.scope.lastFreeTmp++
+							newIdx, _ = c.scope.bindName(exname)
+							m[idx] = newIdx
+						}
+						code[pc] = getLocal(newIdx)
+					}
+				case setLocal:
+					level := uint32(instr) >> 24
+					idx := uint32(instr) & 0x00FFFFFF
+					if level > 0 {
+						level--
+						code[pc] = setLocal((level << 24) | idx)
+					} else {
+						// remap
+						newIdx, exists := m[idx]
+						if !exists {
+							exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp)
+							c.scope.lastFreeTmp++
+							newIdx, _ = c.scope.bindName(exname)
+							m[idx] = newIdx
+						}
+						code[pc] = setLocal(newIdx)
+					}
+				}
+			}
+			if catchVarIdx, exists := m[0]; exists {
+				c.p.code[start] = setLocal(catchVarIdx)
+				c.p.code[start+1] = pop
+				catchOffset--
+			} else {
+				c.p.code[start+1] = nil
+				catchOffset++
+			}
+		} else {
+			c.scope.accessed = true
+		}
+
+		/*
+			if true/*sc.dynamic/ {
+				dynamicCatch = true
+				c.scope.accessed = true
+				c.newScope()
+				c.scope.bindName(v.Catch.Parameter.Name)
+				c.scope.lexical = true
+				c.emit(enterCatch(v.Catch.Parameter.Name))
+				c.compileStatement(v.Catch.Body, false)
+				c.popScope()
+			} else {
+				exname := " __tmp" + strconv.Itoa(c.scope.lastFreeTmp)
+				c.scope.lastFreeTmp++
+				catchVarIdx, _ := c.scope.bindName(exname)
+				c.emit(setLocal(catchVarIdx), pop)
+				saved, wasSaved := c.scope.namesMap[v.Catch.Parameter.Name]
+				c.scope.namesMap[v.Catch.Parameter.Name] = exname
+				c.compileStatement(v.Catch.Body, false)
+				if wasSaved {
+					c.scope.namesMap[v.Catch.Parameter.Name] = saved
+				} else {
+					delete(c.scope.namesMap, v.Catch.Parameter.Name)
+				}
+				c.scope.lastFreeTmp--
+			}*/
+		c.emit(halt)
+	}
+	var finallyOffset int
+	if v.Finally != nil {
+		lbl1 := len(c.p.code)
+		c.emit(nil)
+		finallyOffset = len(c.p.code) - lbl
+		c.compileStatement(v.Finally, false)
+		c.emit(halt, retFinally)
+		c.p.code[lbl1] = jump(len(c.p.code) - lbl1)
+	}
+	c.p.code[lbl] = try{catchOffset: int32(catchOffset), finallyOffset: int32(finallyOffset), dynamic: dynamicCatch}
+	c.p.code[lbl2] = jump(len(c.p.code) - lbl2)
+	c.leaveBlock()
+}
+
+func (c *compiler) compileThrowStatement(v *ast.ThrowStatement) {
+	//c.p.srcMap = append(c.p.srcMap, srcMapItem{pc: len(c.p.code), srcPos: int(v.Throw) - 1})
+	c.compileExpression(v.Argument).emitGetter(true)
+	c.emit(throw)
+}
+
+func (c *compiler) compileDoWhileStatement(v *ast.DoWhileStatement, needResult bool) {
+	c.compileLabeledDoWhileStatement(v, needResult, "")
+}
+
+func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label string) {
+	c.block = &block{
+		typ:   blockLoop,
+		outer: c.block,
+		label: label,
+	}
+
+	if needResult {
+		c.emit(loadUndef)
+	}
+	start := len(c.p.code)
+	c.markBlockStart()
+	c.compileStatement(v.Body, needResult)
+	if needResult {
+		c.emit(rdupN(1), pop)
+	}
+	c.block.cont = len(c.p.code)
+	c.emitExpr(c.compileExpression(v.Test), true)
+	c.emit(jeq(start - len(c.p.code)))
+	c.leaveBlock()
+}
+
+func (c *compiler) compileForStatement(v *ast.ForStatement, needResult bool) {
+	c.compileLabeledForStatement(v, needResult, "")
+}
+
+func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label string) {
+	c.block = &block{
+		typ:        blockLoop,
+		outer:      c.block,
+		label:      label,
+		needResult: needResult,
+	}
+
+	if v.Initializer != nil {
+		c.compileExpression(v.Initializer).emitGetter(false)
+	}
+	if needResult {
+		c.emit(loadUndef)
+	}
+	start := len(c.p.code)
+	c.markBlockStart()
+	var j int
+	testConst := false
+	if v.Test != nil {
+		expr := c.compileExpression(v.Test)
+		if expr.constant() {
+			r, ex := c.evalConst(expr)
+			if ex == nil {
+				if r.ToBoolean() {
+					testConst = true
+				} else {
+					// TODO: Properly implement dummy compilation (no garbage in block, scope, etc..)
+					/*
+						p := c.p
+						c.p = &program{}
+						c.compileStatement(v.Body, false)
+						if v.Update != nil {
+							c.compileExpression(v.Update).emitGetter(false)
+						}
+						c.p = p*/
+					goto end
+				}
+			} else {
+				expr.addSrcMap()
+				c.emitThrow(ex.val)
+				goto end
+			}
+		} else {
+			expr.emitGetter(true)
+			j = len(c.p.code)
+			c.emit(nil)
+		}
+	}
+	c.compileStatement(v.Body, needResult)
+	if needResult {
+		c.emit(rdupN(1), pop)
+	}
+	c.block.cont = len(c.p.code)
+	if v.Update != nil {
+		c.compileExpression(v.Update).emitGetter(false)
+	}
+	c.emit(jump(start - len(c.p.code)))
+	if v.Test != nil {
+		if !testConst {
+			c.p.code[j] = jne(len(c.p.code) - j)
+		}
+	}
+end:
+	c.leaveBlock()
+	c.markBlockStart()
+}
+
+func (c *compiler) compileForInStatement(v *ast.ForInStatement, needResult bool) {
+	c.compileLabeledForInStatement(v, needResult, "")
+}
+
+func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label string) {
+	c.block = &block{
+		typ:        blockLoop,
+		outer:      c.block,
+		label:      label,
+		needResult: needResult,
+	}
+
+	c.compileExpression(v.Source).emitGetter(true)
+	c.emit(enumerate)
+	if needResult {
+		c.emit(loadUndef)
+	}
+	start := len(c.p.code)
+	c.markBlockStart()
+	c.block.cont = start
+	c.emit(nil)
+	c.compileExpression(v.Into).emitSetter(&c.enumGetExpr)
+	c.emit(pop)
+	c.compileStatement(v.Body, needResult)
+	if needResult {
+		c.emit(rdupN(1), pop)
+	}
+	c.emit(jump(start - len(c.p.code)))
+	c.p.code[start] = enumNext(len(c.p.code) - start)
+	c.leaveBlock()
+	c.markBlockStart()
+	c.emit(enumPop)
+}
+
+func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool) {
+	c.compileLabeledWhileStatement(v, needResult, "")
+}
+
+func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label string) {
+	c.block = &block{
+		typ:   blockLoop,
+		outer: c.block,
+		label: label,
+	}
+
+	if needResult {
+		c.emit(loadUndef)
+	}
+	start := len(c.p.code)
+	c.markBlockStart()
+	c.block.cont = start
+	expr := c.compileExpression(v.Test)
+	testTrue := false
+	var j int
+	if expr.constant() {
+		if t, ex := c.evalConst(expr); ex == nil {
+			if t.ToBoolean() {
+				testTrue = true
+			} else {
+				p := c.p
+				c.p = &Program{}
+				c.compileStatement(v.Body, false)
+				c.p = p
+				goto end
+			}
+		} else {
+			c.emitThrow(ex.val)
+			goto end
+		}
+	} else {
+		expr.emitGetter(true)
+		j = len(c.p.code)
+		c.emit(nil)
+	}
+	c.compileStatement(v.Body, needResult)
+	if needResult {
+		c.emit(rdupN(1), pop)
+	}
+	c.emit(jump(start - len(c.p.code)))
+	if !testTrue {
+		c.p.code[j] = jne(len(c.p.code) - j)
+	}
+end:
+	c.leaveBlock()
+	c.markBlockStart()
+}
+
+func (c *compiler) compileBranchStatement(v *ast.BranchStatement, needResult bool) {
+	if needResult {
+		if c.p.code[len(c.p.code)-1] != pop {
+			// panic("Not pop")
+		} else {
+			c.p.code = c.p.code[:len(c.p.code)-1]
+		}
+	}
+	switch v.Token {
+	case token.BREAK:
+		c.compileBreak(v.Label, v.Idx)
+	case token.CONTINUE:
+		c.compileContinue(v.Label, v.Idx)
+	default:
+		panic(fmt.Errorf("Unknown branch statement token: %s", v.Token.String()))
+	}
+}
+
+func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
+	var block *block
+	if label != nil {
+		for b := c.block; b != nil; b = b.outer {
+			switch b.typ {
+			case blockTry:
+				c.emit(halt)
+			case blockWith:
+				c.emit(leaveWith)
+			}
+			if b.label == label.Name {
+				block = b
+				break
+			}
+		}
+	} else {
+		// find the nearest loop or switch
+	L:
+		for b := c.block; b != nil; b = b.outer {
+			switch b.typ {
+			case blockTry:
+				c.emit(halt)
+			case blockWith:
+				c.emit(leaveWith)
+			case blockLoop, blockSwitch:
+				block = b
+				break L
+			}
+		}
+	}
+
+	if block != nil {
+		if block.needResult {
+			c.emit(pop, loadUndef)
+		}
+		block.breaks = append(block.breaks, len(c.p.code))
+		c.emit(nil)
+	} else {
+		c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
+	}
+}
+
+func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
+	var block *block
+	if label != nil {
+
+		for b := c.block; b != nil; b = b.outer {
+			if b.typ == blockTry {
+				c.emit(halt)
+			} else if b.typ == blockLoop && b.label == label.Name {
+				block = b
+				break
+			}
+		}
+	} else {
+		// find the nearest loop
+		for b := c.block; b != nil; b = b.outer {
+			if b.typ == blockTry {
+				c.emit(halt)
+			} else if b.typ == blockLoop {
+				block = b
+				break
+			}
+		}
+	}
+
+	if block != nil {
+		block.conts = append(block.conts, len(c.p.code))
+		c.emit(nil)
+	} else {
+		c.throwSyntaxError(int(idx)-1, "Undefined label '%s'", label.Name)
+	}
+}
+
+func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
+	test := c.compileExpression(v.Test)
+	if test.constant() {
+		r, ex := c.evalConst(test)
+		if ex != nil {
+			test.addSrcMap()
+			c.emitThrow(ex.val)
+			return
+		}
+		if r.ToBoolean() {
+			c.compileStatement(v.Consequent, needResult)
+			if v.Alternate != nil {
+				p := c.p
+				c.p = &Program{}
+				c.compileStatement(v.Alternate, false)
+				c.p = p
+			}
+		} else {
+			p := c.p
+			c.p = &Program{}
+			c.compileStatement(v.Consequent, false)
+			c.p = p
+			if v.Alternate != nil {
+				c.compileStatement(v.Alternate, needResult)
+			} else {
+				if needResult {
+					c.emit(loadUndef)
+				}
+			}
+		}
+		return
+	}
+	test.emitGetter(true)
+	jmp := len(c.p.code)
+	c.emit(nil)
+	c.compileStatement(v.Consequent, needResult)
+	if v.Alternate != nil {
+		jmp1 := len(c.p.code)
+		c.emit(nil)
+		c.p.code[jmp] = jne(len(c.p.code) - jmp)
+		c.markBlockStart()
+		c.compileStatement(v.Alternate, needResult)
+		c.p.code[jmp1] = jump(len(c.p.code) - jmp1)
+		c.markBlockStart()
+	} else {
+		c.p.code[jmp] = jne(len(c.p.code) - jmp)
+		c.markBlockStart()
+		if needResult {
+			c.emit(loadUndef)
+		}
+	}
+}
+
+func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) {
+	if v.Argument != nil {
+		c.compileExpression(v.Argument).emitGetter(true)
+		//c.emit(checkResolve)
+	} else {
+		c.emit(loadUndef)
+	}
+	for b := c.block; b != nil; b = b.outer {
+		if b.typ == blockTry {
+			c.emit(halt)
+		}
+	}
+	c.emit(ret)
+}
+
+func (c *compiler) compileVariableStatement(v *ast.VariableStatement, needResult bool) {
+	for _, expr := range v.List {
+		c.compileExpression(expr).emitGetter(false)
+	}
+	if needResult {
+		c.emit(loadUndef)
+	}
+}
+
+func (c *compiler) compileStatements(list []ast.Statement, needResult bool) {
+	if len(list) > 0 {
+		for _, s := range list[:len(list)-1] {
+			c.compileStatement(s, needResult)
+			if needResult {
+				c.emit(pop)
+			}
+		}
+		c.compileStatement(list[len(list)-1], needResult)
+	} else {
+		if needResult {
+			c.emit(loadUndef)
+		}
+	}
+}
+
+func (c *compiler) compileLabeledBlockStatement(v *ast.BlockStatement, needResult bool, label string) {
+	c.block = &block{
+		typ:   blockBranch,
+		outer: c.block,
+		label: label,
+	}
+	c.compileBlockStatement(v, needResult)
+	c.leaveBlock()
+}
+
+func (c *compiler) compileBlockStatement(v *ast.BlockStatement, needResult bool) {
+	c.compileStatements(v.List, needResult)
+}
+
+func (c *compiler) compileExpressionStatement(v *ast.ExpressionStatement, needResult bool) {
+	expr := c.compileExpression(v.Expression)
+	if expr.constant() {
+		c.emitConst(expr, needResult)
+	} else {
+		expr.emitGetter(needResult)
+	}
+}
+
+func (c *compiler) compileWithStatement(v *ast.WithStatement, needResult bool) {
+	if c.scope.strict {
+		c.throwSyntaxError(int(v.With)-1, "Strict mode code may not include a with statement")
+		return
+	}
+	c.compileExpression(v.Object).emitGetter(true)
+	c.emit(enterWith)
+	c.block = &block{
+		outer: c.block,
+		typ:   blockWith,
+	}
+	c.newScope()
+	c.scope.dynamic = true
+	c.scope.lexical = true
+	c.compileStatement(v.Body, needResult)
+	c.emit(leaveWith)
+	c.leaveBlock()
+	c.popScope()
+}
+
+func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult bool) {
+	c.block = &block{
+		typ:   blockSwitch,
+		outer: c.block,
+	}
+
+	if needResult {
+		c.emit(loadUndef)
+	}
+
+	c.compileExpression(v.Discriminant).emitGetter(true)
+
+	jumps := make([]int, len(v.Body))
+
+	for i, s := range v.Body {
+		if s.Test != nil {
+			c.emit(dup)
+			c.compileExpression(s.Test).emitGetter(true)
+			c.emit(op_strict_eq)
+			c.emit(jne(3), pop)
+			jumps[i] = len(c.p.code)
+			c.emit(nil)
+		}
+	}
+
+	c.emit(pop)
+	jumpNoMatch := -1
+	if v.Default != -1 {
+		if v.Default != 0 {
+			jumps[v.Default] = len(c.p.code)
+			c.emit(nil)
+		}
+	} else {
+		jumpNoMatch = len(c.p.code)
+		c.emit(nil)
+	}
+
+	for i, s := range v.Body {
+		if s.Test != nil || i != 0 {
+			c.p.code[jumps[i]] = jump(len(c.p.code) - jumps[i])
+			c.markBlockStart()
+		}
+		if needResult {
+			c.emit(pop)
+		}
+		c.compileStatements(s.Consequent, needResult)
+	}
+	if jumpNoMatch != -1 {
+		c.p.code[jumpNoMatch] = jump(len(c.p.code) - jumpNoMatch)
+	}
+	c.leaveBlock()
+	c.markBlockStart()
+}

+ 1808 - 0
compiler_test.go

@@ -0,0 +1,1808 @@
+package goja
+
+import (
+	"github.com/dop251/goja/parser"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func testScript(script string, expectedResult Value, t *testing.T) {
+	prg, err := parser.ParseFile(nil, "test.js", script, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	c := newCompiler()
+	c.compile(prg)
+
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+	vm.prg = c.p
+	vm.prg.dumpCode(t.Logf)
+	vm.run()
+	vm.pop()
+	t.Logf("stack size: %d", len(vm.stack))
+	t.Logf("stashAllocs: %d", vm.stashAllocs)
+
+	v := vm.r.globalObject.self.getStr("rv")
+	if v == nil {
+		v = _undefined
+	}
+	if !v.SameAs(expectedResult) {
+		t.Fatalf("Result: %+v, expected: %+v", v, expectedResult)
+	}
+
+	if vm.sp != 0 {
+		t.Fatalf("sp: %d", vm.sp)
+	}
+}
+
+func testScript1(script string, expectedResult Value, t *testing.T) {
+	prg, err := parser.ParseFile(nil, "test.js", script, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	c := newCompiler()
+	c.compile(prg)
+
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+	vm.prg = c.p
+	vm.prg.dumpCode(t.Logf)
+	vm.run()
+	v := vm.pop()
+	t.Logf("stack size: %d", len(vm.stack))
+	t.Logf("stashAllocs: %d", vm.stashAllocs)
+
+	if v == nil && expectedResult != nil || !v.SameAs(expectedResult) {
+		t.Fatalf("Result: %+v, expected: %+v", v, expectedResult)
+	}
+
+	if vm.sp != 0 {
+		t.Fatalf("sp: %d", vm.sp)
+	}
+}
+
+func TestEmptyProgram(t *testing.T) {
+	const SCRIPT = `
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestErrorProto(t *testing.T) {
+	const SCRIPT = `
+	var e = new TypeError();
+	e.name;
+	`
+
+	testScript1(SCRIPT, asciiString("TypeError"), t)
+}
+
+func TestThis1(t *testing.T) {
+	const SCRIPT = `
+	function independent() {
+		return this.prop;
+	}
+	var o = {};
+	o.b = {g: independent, prop: 42};
+
+	var rv = o.b.g();
+	`
+	testScript(SCRIPT, intToValue(42), t)
+}
+
+func TestThis2(t *testing.T) {
+	const SCRIPT = `
+var o = {
+  prop: 37,
+  f: function() {
+    return this.prop;
+  }
+};
+
+var rv = o.f();
+`
+
+	testScript(SCRIPT, intToValue(37), t)
+}
+
+func TestThisStrict(t *testing.T) {
+	const SCRIPT = `
+	"use strict";
+
+	Object.defineProperty(Object.prototype, "x", { get: function () { return this; } });
+
+	(5).x === 5;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestThisNoStrict(t *testing.T) {
+	const SCRIPT = `
+	Object.defineProperty(Object.prototype, "x", { get: function () { return this; } });
+
+	(5).x == 5;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestCallLessArgs(t *testing.T) {
+	const SCRIPT = `
+function A(a, b, c) {
+	return String(a) + " " + String(b) + " " + String(c);
+}
+
+var rv = A(1, 2);
+`
+	testScript(SCRIPT, asciiString("1 2 undefined"), t)
+}
+
+func TestCallMoreArgs(t *testing.T) {
+	const SCRIPT = `
+function A(a, b) {
+	var c = 4;
+	return a - b + c;
+}
+
+var rv = A(1, 2, 3);
+`
+	testScript(SCRIPT, intToValue(3), t)
+}
+
+func TestCallLessArgs1(t *testing.T) {
+	const SCRIPT = `
+function A(a, b, c) {
+	// Make it stashful
+	function B() {
+		return a;
+	}
+	return String(a) + " " + String(b) + " " + String(c);
+}
+
+var rv = A(1, 2);
+`
+	testScript(SCRIPT, asciiString("1 2 undefined"), t)
+}
+
+/*
+func TestFib(t *testing.T) {
+	testScript(TEST_FIB, valueInt(9227465), t)
+}
+*/
+
+func TestNativeCall(t *testing.T) {
+	const SCRIPT = `
+	var o = Object(1);
+	Object.defineProperty(o, "test", {value: 42});
+	var rv = o.test;
+	`
+	testScript(SCRIPT, intToValue(42), t)
+}
+
+func TestJSCall(t *testing.T) {
+	const SCRIPT = `
+	function getter() {
+		return this.x;
+	}
+	var o = Object(1);
+	o.x = 42;
+	Object.defineProperty(o, "test", {get: getter});
+	var rv = o.test;
+	`
+	testScript(SCRIPT, intToValue(42), t)
+
+}
+
+func TestLoop1(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+    		var x = 1;
+    		for (var i = 0; i < 1; i++) {
+        		var x = 2;
+    		}
+    		return x;
+	}
+
+	var rv = A();
+	`
+	testScript(SCRIPT, intToValue(2), t)
+}
+
+func TestLoopBreak(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+    		var x = 1;
+    		for (var i = 0; i < 1; i++) {
+        		break;
+        		var x = 2;
+    		}
+    		return x;
+	}
+
+	var rv = A();
+	`
+	testScript(SCRIPT, intToValue(1), t)
+}
+
+func TestForLoopOptionalExpr(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+    		var x = 1;
+    		for (;;) {
+        		break;
+        		var x = 2;
+    		}
+    		return x;
+	}
+
+	var rv = A();
+	`
+	testScript(SCRIPT, intToValue(1), t)
+}
+
+func TestBlockBreak(t *testing.T) {
+	const SCRIPT = `
+	var rv = 0;
+	B1: {
+		rv = 1;
+		B2: {
+			rv = 2;
+			break B1;
+		}
+		rv = 3;
+	}
+
+	`
+	testScript(SCRIPT, intToValue(2), t)
+
+}
+
+func TestTry(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		var x = 1;
+		try {
+			x = 2;
+		} catch(e) {
+			x = 3;
+		} finally {
+			x = 4;
+		}
+		return x;
+	}
+
+	var rv = A();
+	`
+	testScript(SCRIPT, intToValue(4), t)
+
+}
+
+func TestTryCatch(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		var x;
+		try {
+			throw 4;
+		} catch(e) {
+			x = e;
+		}
+		return x;
+	}
+
+	var rv = A();
+	`
+	testScript(SCRIPT, intToValue(4), t)
+
+}
+
+func TestTryExceptionInCatch(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		var x;
+		try {
+			throw 4;
+		} catch(e) {
+			throw 5;
+		}
+		return x;
+	}
+
+	var rv;
+	try {
+		A();
+	} catch (e) {
+		rv = e;
+	}
+	`
+	testScript(SCRIPT, intToValue(5), t)
+}
+
+func TestTryContinueInFinally(t *testing.T) {
+	const SCRIPT = `
+	var c3 = 0, fin3 = 0;
+	while (c3 < 2) {
+  		try {
+    			throw "ex1";
+  		} catch(er1) {
+    			c3 += 1;
+  		} finally {
+    			fin3 = 1;
+    			continue;
+  		}
+  		fin3 = 0;
+	}
+
+	fin3;
+	`
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestCatchLexicalEnv(t *testing.T) {
+	const SCRIPT = `
+	function F() {
+		try {
+			throw 1;
+		} catch (e) {
+			var x = e;
+		}
+		return x;
+	}
+
+	F();
+	`
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestThrowType(t *testing.T) {
+	const SCRIPT = `
+	function Exception(message) {
+		this.message = message;
+	}
+
+
+	function A() {
+		try {
+			throw new Exception("boo!");
+		} catch(e) {
+			return e;
+		}
+	}
+	var thrown = A();
+	var rv = thrown !== null && typeof thrown === "object" && thrown.constructor === Exception;
+	`
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestThrowConstructorName(t *testing.T) {
+	const SCRIPT = `
+	function Exception(message) {
+		this.message = message;
+	}
+
+
+	function A() {
+		try {
+			throw new Exception("boo!");
+		} catch(e) {
+			return e;
+		}
+	}
+	A().constructor.name;
+	`
+
+	testScript1(SCRIPT, asciiString("Exception"), t)
+}
+
+func TestThrowNativeConstructorName(t *testing.T) {
+	const SCRIPT = `
+
+
+	function A() {
+		try {
+			throw new TypeError();
+		} catch(e) {
+			return e;
+		}
+	}
+	A().constructor.name;
+	`
+
+	testScript1(SCRIPT, asciiString("TypeError"), t)
+}
+
+func TestEmptyTryNoCatch(t *testing.T) {
+	const SCRIPT = `
+	var called = false;
+	try {
+	} finally {
+		called = true;
+	}
+	called;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestIfElse(t *testing.T) {
+	const SCRIPT = `
+	var rv;
+	if (rv === undefined) {
+		rv = "passed";
+	} else {
+		rv = "failed";
+	}
+	`
+
+	testScript(SCRIPT, asciiString("passed"), t)
+}
+
+func TestIfElseRetVal(t *testing.T) {
+	const SCRIPT = `
+	var x;
+	if (x === undefined) {
+		"passed";
+	} else {
+		"failed";
+	}
+	`
+
+	testScript1(SCRIPT, asciiString("passed"), t)
+}
+
+func TestBreakOutOfTry(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		var x = 1;
+		B: {
+			try {
+				x = 2;
+			} catch(e) {
+				x = 3;
+			} finally {
+				break B;
+				x = 4;
+			}
+		}
+		return x;
+	}
+
+	A();
+	`
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestReturnOutOfTryNested(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		function nested() {
+			try {
+				return 1;
+			} catch(e) {
+				return 2;
+			}
+		}
+		return nested();
+	}
+
+	A();
+	`
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestContinueLoop(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		var r = 0;
+		for (var i = 0; i < 5; i++) {
+			if (i > 1) {
+				continue;
+			}
+			r++;
+		}
+		return r;
+	}
+
+	A();
+	`
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestContinueOutOfTry(t *testing.T) {
+	const SCRIPT = `
+	function A() {
+		var r = 0;
+		for (var i = 0; i < 5; i++) {
+			try {
+				if (i > 1) {
+					continue;
+				}
+			} catch(e) {
+				return 99;
+			}
+			r++;
+		}
+		return r;
+	}
+
+	A();
+	`
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestThisInCatch(t *testing.T) {
+	const SCRIPT = `
+	function O() {
+		try {
+			f();
+		} catch (e) {
+			this.value = e.toString();
+		}
+	}
+
+	function f() {
+		throw "ex";
+	}
+
+	var o = new O();
+	o.value;
+	`
+	testScript1(SCRIPT, asciiString("ex"), t)
+}
+
+func TestNestedTry(t *testing.T) {
+	const SCRIPT = `
+	var ex;
+	try {
+  		throw "ex1";
+	} catch (er1) {
+  		try {
+    			throw "ex2";
+  		} catch (er1) {
+			ex = er1;
+		}
+	}
+	ex;
+	`
+	testScript1(SCRIPT, asciiString("ex2"), t)
+}
+
+func TestNestedTryInStashlessFunc(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+		var ex1, ex2;
+		try {
+			throw "ex1";
+		} catch (er1) {
+			try {
+				throw "ex2";
+			} catch (er1) {
+				ex2 = er1;
+			}
+			ex1 = er1;
+		}
+		return ex1 == "ex1" && ex2 == "ex2";
+	}
+	f();
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestEvalInCatchInStashlessFunc(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+		var ex;
+		try {
+			throw "ex1";
+		} catch (er1) {
+			eval("ex = er1");
+		}
+		return ex;
+	}
+	f();
+	`
+	testScript1(SCRIPT, asciiString("ex1"), t)
+}
+
+func TestCatchClosureInStashlessFunc(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+		var ex;
+		try {
+			throw "ex1";
+		} catch (er1) {
+			return function() {
+				return er1;
+			}
+		}
+	}
+	f()();
+	`
+	testScript1(SCRIPT, asciiString("ex1"), t)
+}
+
+func TestCatchVarNotUsedInStashlessFunc(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+		var ex;
+		try {
+			throw "ex1";
+		} catch (er1) {
+			ex = "ok";
+		}
+		return ex;
+	}
+	f();
+	`
+	testScript1(SCRIPT, asciiString("ok"), t)
+}
+
+func TestNew(t *testing.T) {
+	const SCRIPT = `
+	function O() {
+		this.x = 42;
+	}
+
+	new O().x;
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestStringConstructor(t *testing.T) {
+	const SCRIPT = `
+	function F() {
+		return String(33) + " " + String("cows");
+	}
+
+	F();
+	`
+	testScript1(SCRIPT, asciiString("33 cows"), t)
+}
+
+func TestError(t *testing.T) {
+	const SCRIPT = `
+	function F() {
+		return new Error("test");
+	}
+
+	var e = F();
+	var rv = e.message == "test" && e.name == "Error";
+	`
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestTypeError(t *testing.T) {
+	const SCRIPT = `
+	function F() {
+		return new TypeError("test");
+	}
+
+	var e = F();
+	e.message == "test" && e.name == "TypeError";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestToString(t *testing.T) {
+	const SCRIPT = `
+	var o = {x: 42};
+	o.toString = function() {
+		return String(this.x);
+	}
+
+	var o1 = {};
+	o.toString() + " ### " + o1.toString();
+	`
+	testScript1(SCRIPT, asciiString("42 ### [object Object]"), t)
+}
+
+func TestEvalOrder(t *testing.T) {
+	const SCRIPT = `
+	var o = {f: function() {return 42}, x: 0};
+	var trace = "";
+
+	function F1() {
+	    trace += "First!";
+	    return o;
+	}
+
+	function F2() {
+	    trace += "Second!";
+	    return "f";
+	}
+
+	function F3() {
+	    trace += "Third!";
+	}
+
+	var rv = F1()[F2()](F3());
+	rv += trace;
+	`
+
+	testScript(SCRIPT, asciiString("42First!Second!Third!"), t)
+}
+
+func TestPostfixIncBracket(t *testing.T) {
+	const SCRIPT = `
+	var o = {x: 42};
+	var trace = "";
+
+	function F1() {
+	    trace += "First!";
+	    return o;
+	}
+
+	function F2() {
+	    trace += "Second!";
+	    return "x";
+	}
+
+
+	var rv = F1()[F2()]++;
+	rv += trace + o.x;
+	`
+	testScript(SCRIPT, asciiString("42First!Second!43"), t)
+}
+
+func TestPostfixIncDot(t *testing.T) {
+	const SCRIPT = `
+	var o = {x: 42};
+	var trace = "";
+
+	function F1() {
+	    trace += "First!";
+	    return o;
+	}
+
+	var rv = F1().x++;
+	rv += trace + o.x;
+	`
+	testScript(SCRIPT, asciiString("42First!43"), t)
+}
+
+func TestPrefixIncBracket(t *testing.T) {
+	const SCRIPT = `
+	var o = {x: 42};
+	var trace = "";
+
+	function F1() {
+	    trace += "First!";
+	    return o;
+	}
+
+	function F2() {
+	    trace += "Second!";
+	    return "x";
+	}
+
+
+	var rv = ++F1()[F2()];
+	rv += trace + o.x;
+	`
+	testScript(SCRIPT, asciiString("43First!Second!43"), t)
+}
+
+func TestPrefixIncDot(t *testing.T) {
+	const SCRIPT = `
+	var o = {x: 42};
+	var trace = "";
+
+	function F1() {
+	    trace += "First!";
+	    return o;
+	}
+
+	var rv = ++F1().x;
+	rv += trace + o.x;
+	`
+	testScript(SCRIPT, asciiString("43First!43"), t)
+}
+
+func TestPostDecObj(t *testing.T) {
+	const SCRIPT = `
+	var object = {valueOf: function() {return 1}};
+	var y = object--;
+	var ok = false;
+	if (y === 1) {
+		ok = true;
+	}
+	ok;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestPropAcc1(t *testing.T) {
+	const SCRIPT = `
+	1..toString() === "1"
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestEvalDirect(t *testing.T) {
+	const SCRIPT = `
+	var rv = false;
+    	function foo(){ rv = true; }
+
+    	var o = { };
+    	function f() {
+	    	try {
+		    	eval("o.bar( foo() );");
+		} catch (e) {
+
+		}
+    	}
+    	f();
+	`
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestEvalRet(t *testing.T) {
+	const SCRIPT = `
+	eval("for (var i = 0; i < 3; i++) {i}")
+	`
+
+	testScript1(SCRIPT, valueInt(2), t)
+}
+
+func TestEvalFunctionDecl(t *testing.T) {
+	const SCRIPT = `
+	eval("function F() {}")
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestEvalFunctionExpr(t *testing.T) {
+	const SCRIPT = `
+	eval("(function F() {return 42;})")()
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestLoopRet(t *testing.T) {
+	const SCRIPT = `
+	for (var i = 0; i < 20; i++) { if (i > 1) {break;} else { i }}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestLoopRet1(t *testing.T) {
+	const SCRIPT = `
+	for (var i = 0; i < 20; i++) { }
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestInstanceof(t *testing.T) {
+	const SCRIPT = `
+	var rv;
+	try {
+		true();
+	} catch (e) {
+		rv = e instanceof TypeError;
+	}
+	`
+
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestStrictAssign(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+	var rv;
+	var called = false;
+	function F() {
+		called = true;
+		return 1;
+	}
+	try {
+		x = F();
+	} catch (e) {
+		rv = e instanceof ReferenceError;
+	}
+	rv += " " + called;
+	`
+
+	testScript(SCRIPT, asciiString("true true"), t)
+}
+
+func TestStrictScope(t *testing.T) {
+	const SCRIPT = `
+	var rv;
+	var called = false;
+	function F() {
+		'use strict';
+		x = 1;
+	}
+	try {
+		F();
+	} catch (e) {
+		rv = e instanceof ReferenceError;
+	}
+	x = 1;
+	rv += " " + x;
+	`
+
+	testScript(SCRIPT, asciiString("true 1"), t)
+}
+
+func TestStringObj(t *testing.T) {
+	const SCRIPT = `
+	var s = new String("test");
+	s[0] + s[2] + s[1];
+	`
+
+	testScript1(SCRIPT, asciiString("tse"), t)
+}
+
+func TestStringPrimitive(t *testing.T) {
+	const SCRIPT = `
+	var s = "test";
+	s[0] + s[2] + s[1];
+	`
+
+	testScript1(SCRIPT, asciiString("tse"), t)
+}
+
+func TestCallGlobalObject(t *testing.T) {
+	const SCRIPT = `
+	var rv;
+	try {
+		this();
+	} catch (e) {
+		rv = e instanceof TypeError
+	}
+	`
+
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestFuncLength(t *testing.T) {
+	const SCRIPT = `
+	function F(x, y) {
+
+	}
+	F.length
+	`
+
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestNativeFuncLength(t *testing.T) {
+	const SCRIPT = `
+	eval.length + Object.defineProperty.length + String.length
+	`
+
+	testScript1(SCRIPT, intToValue(5), t)
+}
+
+func TestArguments(t *testing.T) {
+	const SCRIPT = `
+	function F() {
+		return arguments.length + " " + arguments[1];
+	}
+
+	F(1,2,3)
+	`
+
+	testScript1(SCRIPT, asciiString("3 2"), t)
+}
+
+func TestArgumentsPut(t *testing.T) {
+	const SCRIPT = `
+	function F(x, y) {
+		arguments[0] -= arguments[1];
+		return x;
+	}
+
+	F(5, 2)
+	`
+
+	testScript1(SCRIPT, intToValue(3), t)
+}
+
+func TestArgumentsPutStrict(t *testing.T) {
+	const SCRIPT = `
+	function F(x, y) {
+		'use strict';
+		arguments[0] -= arguments[1];
+		return x;
+	}
+
+	F(5, 2)
+	`
+
+	testScript1(SCRIPT, intToValue(5), t)
+}
+
+func TestArgumentsExtra(t *testing.T) {
+	const SCRIPT = `
+	function F(x, y) {
+		return arguments[2];
+	}
+
+	F(1, 2, 42)
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestArgumentsExist(t *testing.T) {
+	const SCRIPT = `
+	function F(x, arguments) {
+		return arguments;
+	}
+
+	F(1, 42)
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestArgumentsDelete(t *testing.T) {
+	const SCRIPT = `
+	function f(x) {
+		delete arguments[0];
+		arguments[0] = 42;
+		return x;
+	}
+	f(1)
+	`
+
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestWith(t *testing.T) {
+	const SCRIPT = `
+	var b = 1;
+	var o = {a: 41};
+	with(o) {
+		a += b;
+	}
+	o.a;
+
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestWithInFunc(t *testing.T) {
+	const SCRIPT = `
+	function F() {
+		var b = 1;
+		var c = 0;
+		var o = {a: 40, c: 1};
+		with(o) {
+			a += b + c;
+		}
+		return o.a;
+	}
+
+	F();
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestAssignNonExtendable(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	function F() {
+    		this.x = 1;
+	}
+
+	var o = new F();
+	Object.preventExtensions(o);
+	o.x = 42;
+	o.x;
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestAssignNonExtendable1(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	function F() {
+	}
+
+	var o = new F();
+	var rv;
+
+	Object.preventExtensions(o);
+	try {
+		o.x = 42;
+	} catch (e) {
+		rv = e.constructor === TypeError;
+	}
+
+	rv += " " + o.x;
+
+	`
+
+	testScript(SCRIPT, asciiString("true undefined"), t)
+}
+
+func TestAssignStrict(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	try {
+		eval("eval = 42");
+	} catch(e) {
+		var rv = e instanceof SyntaxError
+	}
+	`
+
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestIllegalArgmentName(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	try {
+		eval("function F(eval) {}");
+	} catch (e) {
+		var rv = e instanceof SyntaxError
+	}
+
+	`
+
+	testScript(SCRIPT, valueTrue, t)
+}
+
+func TestFunction(t *testing.T) {
+	const SCRIPT = `
+
+	var f0 = Function("");
+	var f1 = Function("return ' one'");
+	var f2 = Function("arg", "return ' ' + arg");
+	f0() + f1() + f2("two");
+	`
+
+	testScript1(SCRIPT, asciiString("undefined one two"), t)
+}
+
+func TestFunction1(t *testing.T) {
+	const SCRIPT = `
+
+	var f = function f1(count) {
+		if (count == 0) {
+			return true;
+		}
+		return f1(count-1);
+	}
+
+	f(1);
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestFunction2(t *testing.T) {
+	const SCRIPT = `
+	var trace = "";
+	function f(count) {
+    		trace += "f("+count+")";
+    		if (count == 0) {
+        		return;
+    		}
+    		return f(count-1);
+	}
+
+	function f1() {
+    		trace += "f1";
+	}
+
+	var f2 = f;
+	f = f1;
+	f2(1);
+	trace;
+
+	`
+
+	testScript1(SCRIPT, asciiString("f(1)f1"), t)
+}
+
+func TestFunctionToString(t *testing.T) {
+	const SCRIPT = `
+
+	Function("arg1", "arg2", "return 42").toString();
+	`
+
+	testScript1(SCRIPT, asciiString("function anonymous(arg1,arg2){return 42}"), t)
+}
+
+func TestObjectLiteral(t *testing.T) {
+	const SCRIPT = `
+	var getterCalled = false;
+	var setterCalled = false;
+
+	var o = {get x() {getterCalled = true}, set x() {setterCalled = true}};
+
+	o.x;
+	o.x = 42;
+
+	getterCalled && setterCalled;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestConst(t *testing.T) {
+	const SCRIPT = `
+
+	var v1 = true && true;
+	var v2 = 1/(-1 * 0);
+	var v3 = 1 == 2 || v1;
+	var v4 = true && false
+	v1 === true && v2 === -Infinity && v3 === v1 && v4 === false;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestConstWhile(t *testing.T) {
+	const SCRIPT = `
+	var c = 0;
+	while (2 + 2 === 4) {
+		if (++c > 9) {
+			break;
+		}
+	}
+	c === 10;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestConstWhileThrow(t *testing.T) {
+	const SCRIPT = `
+	var thrown = false;
+	try {
+		while ('s' in true) {
+			break;
+		}
+	} catch (e) {
+		thrown = e instanceof TypeError
+	}
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestDupParams(t *testing.T) {
+	const SCRIPT = `
+	function F(x, y, x) {
+		return x;
+	}
+
+	F(1, 2);
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestUseUnsuppliedParam(t *testing.T) {
+	const SCRIPT = `
+	function getMessage(message) {
+		if (message === undefined) {
+			message = '';
+		}
+		message += " 123 456";
+		return message;
+	}
+
+	getMessage();
+	`
+
+	testScript1(SCRIPT, asciiString(" 123 456"), t)
+}
+
+func TestForInLoop(t *testing.T) {
+	const SCRIPT = `
+	function Proto() {}
+	Proto.prototype.x = 42;
+	var o = new Proto();
+	o.y = 44;
+	o.x = 45;
+	var hasX = false;
+	var hasY = false;
+
+	for (var i in o) {
+    		switch(i) {
+    		case "x":
+        		if (hasX) {
+            			throw new Error("Already has X");
+        		}
+        		hasX = true;
+        		break;
+    		case "y":
+        		if (hasY) {
+            			throw new Error("Already has Y");
+        		}
+        		hasY = true;
+        		break;
+    		}
+	}
+
+	hasX && hasY;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestForInLoopRet(t *testing.T) {
+	const SCRIPT = `
+	var o = {};
+	o.x = 1;
+	o.y = 2;
+	for (var i in o) {
+		true;
+	}
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestWhileLoopResult(t *testing.T) {
+	const SCRIPT = `
+	while(false);
+
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestSwitch(t *testing.T) {
+	const SCRIPT = `
+	function F(x) {
+		var i = 0;
+		switch (x) {
+		case 0:
+			i++;
+		case 1:
+			i++;
+		default:
+			i++;
+		case 2:
+			i++;
+			break;
+		case 3:
+			i++;
+		}
+		return i;
+	}
+
+	F(0) + F(1) + F(2) + F(4);
+
+	`
+
+	testScript1(SCRIPT, intToValue(10), t)
+}
+
+func TestSwitchDefFirst(t *testing.T) {
+	const SCRIPT = `
+	function F(x) {
+		var i = 0;
+		switch (x) {
+		default:
+			i++;
+		case 0:
+			i++;
+		case 1:
+			i++;
+		case 2:
+			i++;
+			break;
+		case 3:
+			i++;
+		}
+		return i;
+	}
+
+	F(0) + F(1) + F(2) + F(4);
+
+	`
+
+	testScript1(SCRIPT, intToValue(10), t)
+}
+
+func TestSwitchResult(t *testing.T) {
+	const SCRIPT = `
+	var x = 2;
+
+	switch (x) {
+	case 0:
+		"zero";
+	case 1:
+		"one";
+	case 2:
+		"two";
+		break;
+	case 3:
+		"three";
+	default:
+		"default";
+	}
+
+	`
+
+	testScript1(SCRIPT, asciiString("two"), t)
+}
+
+func TestSwitchNoMatch(t *testing.T) {
+	const SCRIPT = `
+	var x = 5;
+	var result;
+	switch (x) {
+	case 0:
+		result = "2";
+		break;
+	}
+
+	result;
+
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestGetOwnPropertyNames(t *testing.T) {
+	const SCRIPT = `
+	var o = {
+		prop1: 42,
+		prop2: "test"
+	}
+
+	var hasProp1 = false;
+	var hasProp2 = false;
+
+	var names = Object.getOwnPropertyNames(o);
+	for (var i in names) {
+		var p = names[i];
+		switch(p) {
+		case "prop1":
+			hasProp1 = true;
+			break;
+		case "prop2":
+			hasProp2 = true;
+			break;
+		}
+	}
+
+	hasProp1 && hasProp2;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayLiteral(t *testing.T) {
+	const SCRIPT = `
+
+	var f1Called = false;
+	var f2Called = false;
+	var f3Called = false;
+	var errorThrown = false;
+
+	function F1() {
+		f1Called = true;
+	}
+
+	function F2() {
+		f2Called = true;
+	}
+
+	function F3() {
+		f3Called = true;
+	}
+
+
+	try {
+		var a = [F1(), x(F3()), F2()];
+	} catch(e) {
+		if (e instanceof ReferenceError) {
+			errorThrown = true;
+		} else {
+			throw e;
+		}
+	}
+
+	f1Called && !f2Called && f3Called && errorThrown && a === undefined;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestJumpOutOfReturn(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+		var a;
+		if (a == 0) {
+			return true;
+		}
+	}
+
+	f();
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestSwitchJumpOutOfReturn(t *testing.T) {
+	const SCRIPT = `
+	function f(x) {
+		switch(x) {
+		case 0:
+			break;
+		default:
+			return x;
+		}
+	}
+
+	f(0);
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestSetToReadOnlyPropertyStrictBracket(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	var o = {};
+	var thrown = false;
+	Object.defineProperty(o, "test", {value: 42, configurable: true});
+	try {
+		o["test"] = 43;
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestSetToReadOnlyPropertyStrictDot(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	var o = {};
+	var thrown = false;
+	Object.defineProperty(o, "test", {value: 42, configurable: true});
+	try {
+		o.test = 43;
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestDeleteNonConfigurablePropertyStrictBracket(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	var o = {};
+	var thrown = false;
+	Object.defineProperty(o, "test", {value: 42});
+	try {
+		delete o["test"];
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestDeleteNonConfigurablePropertyStrictDot(t *testing.T) {
+	const SCRIPT = `
+	'use strict';
+
+	var o = {};
+	var thrown = false;
+	Object.defineProperty(o, "test", {value: 42});
+	try {
+		delete o.test;
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestCompound1(t *testing.T) {
+	const SCRIPT = `
+	var x = 0;
+  	var scope = {x: 1};
+    	var f;
+  	with (scope) {
+    		f = function() {
+        		x *= (delete scope.x, 2);
+    		}
+  	}
+	f();
+
+	scope.x === 2 && x === 0;
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestCompound2(t *testing.T) {
+	const SCRIPT = `
+
+var x;
+x = "x";
+x ^= "1";
+
+	`
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestDeleteArguments(t *testing.T) {
+	defer func() {
+		if _, ok := recover().(*CompilerSyntaxError); !ok {
+			t.Fatal("Expected syntax error")
+		}
+	}()
+	const SCRIPT = `
+	'use strict';
+
+	function f() {
+		delete arguments;
+	}
+
+	`
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestReturnUndefined(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+    		return x;
+	}
+
+	var thrown = false;
+	try {
+		f();
+	} catch (e) {
+		thrown = e instanceof ReferenceError;
+	}
+
+	thrown;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestForBreak(t *testing.T) {
+	const SCRIPT = `
+	var supreme, count;
+	supreme = 5;
+	var __evaluated =  eval("for(count=0;;) {if (count===supreme)break;else count++; }");
+    	if (__evaluated !== void 0) {
+        	throw new Error('#1: __evaluated === 4. Actual:  __evaluated ==='+ __evaluated  );
+    	}
+
+	`
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestLargeNumberLiteral(t *testing.T) {
+	const SCRIPT = `
+	var x = 0x800000000000000000000;
+	x.toString();
+	`
+	testScript1(SCRIPT, asciiString("9.671406556917033e+24"), t)
+}
+
+func TestIncDelete(t *testing.T) {
+	const SCRIPT = `
+	var o = {x: 1};
+	o.x += (delete o.x, 1);
+	o.x;
+	`
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestCompoundAssignRefError(t *testing.T) {
+	const SCRIPT = `
+	var thrown = false;
+	try {
+		a *= 1;
+	} catch (e) {
+		if (e instanceof ReferenceError) {
+			thrown = true;
+		} else {
+			throw e;
+		}
+	}
+	thrown;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestObjectLiteral__Proto__(t *testing.T) {
+	const SCRIPT = `
+	var o = {
+		__proto__: null,
+		test: 42
+	}
+
+	Object.getPrototypeOf(o);
+	`
+
+	testScript1(SCRIPT, _null, t)
+}
+
+// FIXME
+/*
+func TestDummyCompile(t *testing.T) {
+	const SCRIPT = `
+'use strict';
+
+for (;false;) {
+    eval = 1;
+}
+
+	`
+	defer func() {
+		if recover() == nil {
+			t.Fatal("Expected panic")
+		}
+	}()
+
+	testScript1(SCRIPT, _undefined, t)
+}*/
+
+func BenchmarkCompile(b *testing.B) {
+	f, err := os.Open("testdata/S15.10.2.12_A1_T1.js")
+
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		b.Fatal(err)
+	}
+	f.Close()
+
+	src := string(data)
+
+	for i := 0; i < b.N; i++ {
+		_, err := Compile("test.js", src, false)
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+
+}

+ 110 - 0
date.go

@@ -0,0 +1,110 @@
+package goja
+
+import (
+	"regexp"
+	"time"
+)
+
+const (
+	dateTimeLayout       = "Mon Jan 02 2006 15:04:05 GMT-0700 (MST)"
+	isoDateTimeLayout    = "2006-01-02T15:04:05.000Z"
+	dateLayout           = "Mon Jan 02 2006"
+	timeLayout           = "15:04:05 GMT-0700 (MST)"
+	datetimeLayout_en_GB = "01/02/2006, 15:04:05"
+	dateLayout_en_GB     = "01/02/2006"
+	timeLayout_en_GB     = "15:04:05"
+)
+
+type dateObject struct {
+	baseObject
+	time  time.Time
+	isSet bool
+}
+
+var (
+	dateLayoutList = []string{
+		"2006",
+		"2006-01",
+		"2006-01-02",
+
+		"2006T15:04",
+		"2006-01T15:04",
+		"2006-01-02T15:04",
+
+		"2006T15:04:05",
+		"2006-01T15:04:05",
+		"2006-01-02T15:04:05",
+
+		"2006T15:04:05.000",
+		"2006-01T15:04:05.000",
+		"2006-01-02T15:04:05.000",
+
+		"2006T15:04-0700",
+		"2006-01T15:04-0700",
+		"2006-01-02T15:04-0700",
+
+		"2006T15:04:05-0700",
+		"2006-01T15:04:05-0700",
+		"2006-01-02T15:04:05-0700",
+
+		"2006T15:04:05.000-0700",
+		"2006-01T15:04:05.000-0700",
+		"2006-01-02T15:04:05.000-0700",
+
+		time.RFC1123,
+		dateTimeLayout,
+	}
+	matchDateTimeZone = regexp.MustCompile(`^(.*)(?:(Z)|([\+\-]\d{2}):(\d{2}))$`)
+)
+
+func dateParse(date string) (time.Time, bool) {
+	// YYYY-MM-DDTHH:mm:ss.sssZ
+	var t time.Time
+	var err error
+	{
+		date := date
+		if match := matchDateTimeZone.FindStringSubmatch(date); match != nil {
+			if match[2] == "Z" {
+				date = match[1] + "+0000"
+			} else {
+				date = match[1] + match[3] + match[4]
+			}
+		}
+		for _, layout := range dateLayoutList {
+			t, err = time.Parse(layout, date)
+			if err == nil {
+				break
+			}
+		}
+	}
+	return t, err == nil
+}
+
+func (r *Runtime) newDateObject(t time.Time, isSet bool) *Object {
+	v := &Object{runtime: r}
+	d := &dateObject{}
+	v.self = d
+	d.val = v
+	d.class = classDate
+	d.prototype = r.global.DatePrototype
+	d.extensible = true
+	d.init()
+	d.time = t.In(time.Local)
+	d.isSet = isSet
+	return v
+}
+
+func dateFormat(t time.Time) string {
+	return t.Local().Format(dateTimeLayout)
+}
+
+func (d *dateObject) toPrimitive() Value {
+	return d.toPrimitiveString()
+}
+
+func (d *dateObject) export() interface{} {
+	if d.isSet {
+		return d.time
+	}
+	return nil
+}

+ 185 - 0
date_test.go

@@ -0,0 +1,185 @@
+package goja
+
+import (
+	"testing"
+	"time"
+)
+
+const TESTLIB = `
+function $ERROR(message) {
+	throw new Error(message);
+}
+
+function assert(mustBeTrue, message) {
+    if (mustBeTrue === true) {
+        return;
+    }
+
+    if (message === undefined) {
+        message = 'Expected true but got ' + String(mustBeTrue);
+    }
+    $ERROR(message);
+}
+
+assert._isSameValue = function (a, b) {
+    if (a === b) {
+        // Handle +/-0 vs. -/+0
+        return a !== 0 || 1 / a === 1 / b;
+    }
+
+    // Handle NaN vs. NaN
+    return a !== a && b !== b;
+};
+
+assert.sameValue = function (actual, expected, message) {
+    if (assert._isSameValue(actual, expected)) {
+        return;
+    }
+
+    if (message === undefined) {
+        message = '';
+    } else {
+        message += ' ';
+    }
+
+    message += 'Expected SameValue(«' + String(actual) + '», «' + String(expected) + '») to be true';
+
+    $ERROR(message);
+};
+
+
+`
+
+func TestDateUTC(t *testing.T) {
+	const SCRIPT = `
+	assert.sameValue(Date.UTC(1970, 0), 0, '1970, 0');
+	assert.sameValue(Date.UTC(2016, 0), 1451606400000, '2016, 0');
+	assert.sameValue(Date.UTC(2016, 6), 1467331200000, '2016, 6');
+
+	assert.sameValue(Date.UTC(2016, 6, 1), 1467331200000, '2016, 6, 1');
+	assert.sameValue(Date.UTC(2016, 6, 5), 1467676800000, '2016, 6, 5');
+
+	assert.sameValue(Date.UTC(2016, 6, 5, 0), 1467676800000, '2016, 6, 5, 0');
+	assert.sameValue(Date.UTC(2016, 6, 5, 15), 1467730800000, '2016, 6, 5, 15');
+
+	assert.sameValue(
+  		Date.UTC(2016, 6, 5, 15, 0), 1467730800000, '2016, 6, 5, 15, 0'
+	);
+	assert.sameValue(
+  		Date.UTC(2016, 6, 5, 15, 34), 1467732840000, '2016, 6, 5, 15, 34'
+	);
+
+	assert.sameValue(
+  		Date.UTC(2016, 6, 5, 15, 34, 0), 1467732840000, '2016, 6, 5, 15, 34, 0'
+	);
+	assert.sameValue(
+  		Date.UTC(2016, 6, 5, 15, 34, 45), 1467732885000, '2016, 6, 5, 15, 34, 45'
+	);
+
+	`
+
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestNewDate(t *testing.T) {
+	const SCRIPT = `
+	var d1 = new Date("2016-09-01T12:34:56Z");
+	d1.getHours() === 13 && d1.getUTCHours() === 12;
+
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestNewDate0(t *testing.T) {
+	const SCRIPT = `
+	(new Date(0)).toString();
+
+	`
+	testScript1(SCRIPT, asciiString("Thu Jan 01 1970 01:00:00 GMT+0100 (BST)"), t)
+}
+
+func TestSetHour(t *testing.T) {
+	l := time.Local
+	defer func() {
+		time.Local = l
+	}()
+	var err error
+	time.Local, err = time.LoadLocation("America/New_York")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	const SCRIPT = `
+	var d = new Date(2016, 8, 1, 12, 23, 45)
+	assert.sameValue(d.getHours(), 12);
+	assert.sameValue(d.getUTCHours(), 16);
+
+	d.setHours(13);
+	assert.sameValue(d.getHours(), 13);
+	assert.sameValue(d.getMinutes(), 23);
+	assert.sameValue(d.getSeconds(), 45);
+
+	d.setUTCHours(13);
+	assert.sameValue(d.getHours(), 9);
+	assert.sameValue(d.getMinutes(), 23);
+	assert.sameValue(d.getSeconds(), 45);
+
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+
+}
+
+func TestSetMinute(t *testing.T) {
+	l := time.Local
+	defer func() {
+		time.Local = l
+	}()
+	time.Local = time.FixedZone("Asia/Delhi", 5*60*60+30*60)
+
+	const SCRIPT = `
+	var d = new Date(2016, 8, 1, 12, 23, 45)
+	assert.sameValue(d.getHours(), 12);
+	assert.sameValue(d.getUTCHours(), 6);
+	assert.sameValue(d.getMinutes(), 23);
+	assert.sameValue(d.getUTCMinutes(), 53);
+
+	d.setMinutes(55);
+	assert.sameValue(d.getMinutes(), 55);
+	assert.sameValue(d.getSeconds(), 45);
+
+	d.setUTCMinutes(52);
+	assert.sameValue(d.getMinutes(), 22);
+	assert.sameValue(d.getHours(), 13);
+
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+
+}
+
+func TestTimezoneOffset(t *testing.T) {
+	const SCRIPT = `
+	var d = new Date(0);
+	d.getTimezoneOffset();
+	`
+
+	l := time.Local
+	defer func() {
+		time.Local = l
+	}()
+	var err error
+	time.Local, err = time.LoadLocation("Europe/London")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testScript1(SCRIPT, intToValue(-60), t)
+}
+
+func TestDateValueOf(t *testing.T) {
+	const SCRIPT = `
+	var d9 = new Date(1.23e15);
+	d9.valueOf();
+	`
+
+	testScript1(SCRIPT, intToValue(1.23e15), t)
+}

+ 290 - 0
dtoa.go

@@ -0,0 +1,290 @@
+package goja
+
+// Ported from Rhino (https://github.com/mozilla/rhino/blob/master/src/org/mozilla/javascript/DToA.java)
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+	"math/big"
+	"strconv"
+)
+
+const (
+	frac_mask = 0xfffff
+	exp_shift = 20
+	exp_msk1  = 0x100000
+
+	exp_shiftL       = 52
+	exp_mask_shifted = 0x7ff
+	frac_maskL       = 0xfffffffffffff
+	exp_msk1L        = 0x10000000000000
+	exp_shift1       = 20
+	exp_mask         = 0x7ff00000
+	bias             = 1023
+	p                = 53
+	bndry_mask       = 0xfffff
+	log2P            = 1
+
+	digits = "0123456789abcdefghijklmnopqrstuvwxyz"
+)
+
+func lo0bits(x uint32) (k uint32) {
+
+	if (x & 7) != 0 {
+		if (x & 1) != 0 {
+			return 0
+		}
+		if (x & 2) != 0 {
+			return 1
+		}
+		return 2
+	}
+	if (x & 0xffff) == 0 {
+		k = 16
+		x >>= 16
+	}
+	if (x & 0xff) == 0 {
+		k += 8
+		x >>= 8
+	}
+	if (x & 0xf) == 0 {
+		k += 4
+		x >>= 4
+	}
+	if (x & 0x3) == 0 {
+		k += 2
+		x >>= 2
+	}
+	if (x & 1) == 0 {
+		k++
+		x >>= 1
+		if (x & 1) == 0 {
+			return 32
+		}
+	}
+	return
+}
+
+func hi0bits(x uint32) (k uint32) {
+
+	if (x & 0xffff0000) == 0 {
+		k = 16
+		x <<= 16
+	}
+	if (x & 0xff000000) == 0 {
+		k += 8
+		x <<= 8
+	}
+	if (x & 0xf0000000) == 0 {
+		k += 4
+		x <<= 4
+	}
+	if (x & 0xc0000000) == 0 {
+		k += 2
+		x <<= 2
+	}
+	if (x & 0x80000000) == 0 {
+		k++
+		if (x & 0x40000000) == 0 {
+			return 32
+		}
+	}
+	return
+}
+
+func stuffBits(bits []byte, offset int, val uint32) {
+	bits[offset] = byte(val >> 24)
+	bits[offset+1] = byte(val >> 16)
+	bits[offset+2] = byte(val >> 8)
+	bits[offset+3] = byte(val)
+}
+
+func d2b(d float64) (b *big.Int, e int32, bits uint32) {
+	dBits := math.Float64bits(d)
+	d0 := uint32(dBits >> 32)
+	d1 := uint32(dBits)
+
+	z := d0 & frac_mask
+	d0 &= 0x7fffffff /* clear sign bit, which we ignore */
+
+	var de, k, i uint32
+	var dbl_bits []byte
+	if de = (d0 >> exp_shift); de != 0 {
+		z |= exp_msk1
+	}
+
+	y := d1
+	if y != 0 {
+		dbl_bits = make([]byte, 8)
+		k = lo0bits(y)
+		y >>= k
+		if k != 0 {
+			stuffBits(dbl_bits, 4, y|z<<(32-k))
+			z >>= k
+		} else {
+			stuffBits(dbl_bits, 4, y)
+		}
+		stuffBits(dbl_bits, 0, z)
+		if z != 0 {
+			i = 2
+		} else {
+			i = 1
+		}
+	} else {
+		dbl_bits = make([]byte, 4)
+		k = lo0bits(z)
+		z >>= k
+		stuffBits(dbl_bits, 0, z)
+		k += 32
+		i = 1
+	}
+
+	if de != 0 {
+		e = int32(de - bias - (p - 1) + k)
+		bits = p - k
+	} else {
+		e = int32(de - bias - (p - 1) + 1 + k)
+		bits = 32*i - hi0bits(z)
+	}
+	b = (&big.Int{}).SetBytes(dbl_bits)
+	return
+}
+
+func dtobasestr(num float64, radix int) string {
+	var negative bool
+	if num < 0 {
+		num = -num
+		negative = true
+	}
+
+	dfloor := math.Floor(num)
+	ldfloor := int64(dfloor)
+	var intDigits string
+	if dfloor == float64(ldfloor) {
+		if negative {
+			ldfloor = -ldfloor
+		}
+		intDigits = strconv.FormatInt(ldfloor, radix)
+	} else {
+		floorBits := math.Float64bits(num)
+		exp := int(floorBits>>exp_shiftL) & exp_mask_shifted
+		var mantissa int64
+		if exp == 0 {
+			mantissa = int64((floorBits & frac_maskL) << 1)
+		} else {
+			mantissa = int64((floorBits & frac_maskL) | exp_msk1L)
+		}
+
+		if negative {
+			mantissa = -mantissa
+		}
+		exp -= 1075
+		x := big.NewInt(mantissa)
+		if exp > 0 {
+			x.Lsh(x, uint(exp))
+		} else if exp < 0 {
+			x.Rsh(x, uint(-exp))
+		}
+		intDigits = x.Text(radix)
+	}
+
+	if num == dfloor {
+		// No fraction part
+		return intDigits
+	} else {
+		/* We have a fraction. */
+		var buffer bytes.Buffer
+		buffer.WriteString(intDigits)
+		buffer.WriteByte('.')
+		df := num - dfloor
+
+		dBits := math.Float64bits(num)
+		word0 := uint32(dBits >> 32)
+		word1 := uint32(dBits)
+
+		b, e, _ := d2b(df)
+		//            JS_ASSERT(e < 0);
+		/* At this point df = b * 2^e.  e must be less than zero because 0 < df < 1. */
+
+		s2 := -int32((word0 >> exp_shift1) & (exp_mask >> exp_shift1))
+		if s2 == 0 {
+			s2 = -1
+		}
+		s2 += bias + p
+		/* 1/2^s2 = (nextDouble(d) - d)/2 */
+		//            JS_ASSERT(-s2 < e);
+		if -s2 >= e {
+			panic(fmt.Errorf("-s2 >= e: %d, %d", -s2, e))
+		}
+		mlo := big.NewInt(1)
+		mhi := mlo
+		if (word1 == 0) && ((word0 & bndry_mask) == 0) && ((word0 & (exp_mask & exp_mask << 1)) != 0) {
+			/* The special case.  Here we want to be within a quarter of the last input
+			   significant digit instead of one half of it when the output string's value is less than d.  */
+			s2 += log2P
+			mhi = big.NewInt(1 << log2P)
+		}
+
+		b.Lsh(b, uint(e+s2))
+		s := big.NewInt(1)
+		s.Lsh(s, uint(s2))
+		/* At this point we have the following:
+		 *   s = 2^s2;
+		 *   1 > df = b/2^s2 > 0;
+		 *   (d - prevDouble(d))/2 = mlo/2^s2;
+		 *   (nextDouble(d) - d)/2 = mhi/2^s2. */
+		bigBase := big.NewInt(int64(radix))
+
+		done := false
+		m := &big.Int{}
+		delta := &big.Int{}
+		for !done {
+			b.Mul(b, bigBase)
+			b.DivMod(b, s, m)
+			digit := byte(b.Int64())
+			b, m = m, b
+			mlo.Mul(mlo, bigBase)
+			if mlo != mhi {
+				mhi.Mul(mhi, bigBase)
+			}
+
+			/* Do we yet have the shortest string that will round to d? */
+			j := b.Cmp(mlo)
+			/* j is b/2^s2 compared with mlo/2^s2. */
+
+			delta.Sub(s, mhi)
+			var j1 int
+			if delta.Sign() <= 0 {
+				j1 = 1
+			} else {
+				j1 = b.Cmp(delta)
+			}
+			/* j1 is b/2^s2 compared with 1 - mhi/2^s2. */
+			if j1 == 0 && (word1&1) == 0 {
+				if j > 0 {
+					digit++
+				}
+				done = true
+			} else if j < 0 || (j == 0 && ((word1 & 1) == 0)) {
+				if j1 > 0 {
+					/* Either dig or dig+1 would work here as the least significant digit.
+					Use whichever would produce an output value closer to d. */
+					b.Lsh(b, 1)
+					j1 = b.Cmp(s)
+					if j1 > 0 { /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output such as 3.5 in base 3.  */
+						digit++
+					}
+				}
+				done = true
+			} else if j1 > 0 {
+				digit++
+				done = true
+			}
+			//                JS_ASSERT(digit < (uint32)base);
+			buffer.WriteByte(digits[digit])
+		}
+
+		return buffer.String()
+	}
+}

+ 110 - 0
file/README.markdown

@@ -0,0 +1,110 @@
+# file
+--
+    import "github.com/robertkrimen/otto/file"
+
+Package file encapsulates the file abstractions used by the ast & parser.
+
+## Usage
+
+#### type File
+
+```go
+type File struct {
+}
+```
+
+
+#### func  NewFile
+
+```go
+func NewFile(filename, src string, base int) *File
+```
+
+#### func (*File) Base
+
+```go
+func (fl *File) Base() int
+```
+
+#### func (*File) Name
+
+```go
+func (fl *File) Name() string
+```
+
+#### func (*File) Source
+
+```go
+func (fl *File) Source() string
+```
+
+#### type FileSet
+
+```go
+type FileSet struct {
+}
+```
+
+A FileSet represents a set of source files.
+
+#### func (*FileSet) AddFile
+
+```go
+func (self *FileSet) AddFile(filename, src string) int
+```
+AddFile adds a new file with the given filename and src.
+
+This an internal method, but exported for cross-package use.
+
+#### func (*FileSet) File
+
+```go
+func (self *FileSet) File(idx Idx) *File
+```
+
+#### func (*FileSet) Position
+
+```go
+func (self *FileSet) Position(idx Idx) *Position
+```
+Position converts an Idx in the FileSet into a Position.
+
+#### type Idx
+
+```go
+type Idx int
+```
+
+Idx is a compact encoding of a source position within a file set. It can be
+converted into a Position for a more convenient, but much larger,
+representation.
+
+#### type Position
+
+```go
+type Position struct {
+	Filename string // The filename where the error occurred, if any
+	Offset   int    // The src offset
+	Line     int    // The line number, starting at 1
+	Column   int    // The column number, starting at 1 (The character count)
+
+}
+```
+
+Position describes an arbitrary source position including the filename, line,
+and column location.
+
+#### func (*Position) String
+
+```go
+func (self *Position) String() string
+```
+String returns a string in one of several forms:
+
+    file:line:column    A valid position with filename
+    line:column         A valid position without filename
+    file                An invalid position with filename
+    -                   An invalid position without filename
+
+--
+**godocdown** http://github.com/robertkrimen/godocdown

+ 135 - 0
file/file.go

@@ -0,0 +1,135 @@
+// Package file encapsulates the file abstractions used by the ast & parser.
+//
+package file
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Idx is a compact encoding of a source position within a file set.
+// It can be converted into a Position for a more convenient, but much
+// larger, representation.
+type Idx int
+
+// Position describes an arbitrary source position
+// including the filename, line, and column location.
+type Position struct {
+	Filename string // The filename where the error occurred, if any
+	Offset   int    // The src offset
+	Line     int    // The line number, starting at 1
+	Column   int    // The column number, starting at 1 (The character count)
+
+}
+
+// A Position is valid if the line number is > 0.
+
+func (self *Position) isValid() bool {
+	return self.Line > 0
+}
+
+// String returns a string in one of several forms:
+//
+//	file:line:column    A valid position with filename
+//	line:column         A valid position without filename
+//	file                An invalid position with filename
+//	-                   An invalid position without filename
+//
+func (self *Position) String() string {
+	str := self.Filename
+	if self.isValid() {
+		if str != "" {
+			str += ":"
+		}
+		str += fmt.Sprintf("%d:%d", self.Line, self.Column)
+	}
+	if str == "" {
+		str = "-"
+	}
+	return str
+}
+
+// FileSet
+
+// A FileSet represents a set of source files.
+type FileSet struct {
+	files []*File
+	last  *File
+}
+
+// AddFile adds a new file with the given filename and src.
+//
+// This an internal method, but exported for cross-package use.
+func (self *FileSet) AddFile(filename, src string) int {
+	base := self.nextBase()
+	file := &File{
+		name: filename,
+		src:  src,
+		base: base,
+	}
+	self.files = append(self.files, file)
+	self.last = file
+	return base
+}
+
+func (self *FileSet) nextBase() int {
+	if self.last == nil {
+		return 1
+	}
+	return self.last.base + len(self.last.src) + 1
+}
+
+func (self *FileSet) File(idx Idx) *File {
+	for _, file := range self.files {
+		if idx <= Idx(file.base+len(file.src)) {
+			return file
+		}
+	}
+	return nil
+}
+
+// Position converts an Idx in the FileSet into a Position.
+func (self *FileSet) Position(idx Idx) *Position {
+	position := &Position{}
+	for _, file := range self.files {
+		if idx <= Idx(file.base+len(file.src)) {
+			offset := int(idx) - file.base
+			src := file.src[:offset]
+			position.Filename = file.name
+			position.Offset = offset
+			position.Line = 1 + strings.Count(src, "\n")
+			if index := strings.LastIndex(src, "\n"); index >= 0 {
+				position.Column = offset - index
+			} else {
+				position.Column = 1 + len(src)
+			}
+		}
+	}
+	return position
+}
+
+type File struct {
+	name string
+	src  string
+	base int // This will always be 1 or greater
+}
+
+func NewFile(filename, src string, base int) *File {
+	return &File{
+		name: filename,
+		src:  src,
+		base: base,
+	}
+}
+
+func (fl *File) Name() string {
+	return fl.name
+}
+
+func (fl *File) Source() string {
+	return fl.src
+}
+
+func (fl *File) Base() int {
+	return fl.base
+}

+ 128 - 0
goja/main.go

@@ -0,0 +1,128 @@
+package main
+
+import (
+	"flag"
+	"os"
+	"fmt"
+	"io/ioutil"
+	"github.com/dop251/goja"
+	"log"
+	"runtime/pprof"
+	"time"
+	"runtime/debug"
+	crand "crypto/rand"
+	"math/rand"
+	"encoding/binary"
+)
+
+var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
+var timelimit = flag.Int("timelimit", 0, "max time to run (in seconds)")
+
+
+func readSource(filename string) ([]byte, error) {
+	if filename == "" || filename == "-" {
+		return ioutil.ReadAll(os.Stdin)
+	}
+	return ioutil.ReadFile(filename)
+}
+
+func load(vm *goja.Runtime, call goja.FunctionCall) goja.Value {
+	p := call.Argument(0).String()
+	b, err := readSource(p)
+	if err != nil {
+		panic(vm.ToValue(fmt.Sprintf("Could not read %s: %v", p, err)))
+	}
+	v, err := vm.RunScript(p, string(b))
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+func console_log(call goja.FunctionCall) goja.Value {
+	args := make([]interface{}, len(call.Arguments))
+	for i, a := range call.Arguments {
+		args[i] = a.String()
+	}
+	log.Print(args...)
+	return nil
+}
+
+func createConsole(vm *goja.Runtime) *goja.Object {
+	o := vm.NewObject()
+	o.Set("log", console_log)
+	return o
+}
+
+func newRandSource() goja.RandSource {
+	var seed int64
+	if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil {
+		panic(fmt.Errorf("Could not read random bytes: %v", err))
+	}
+	return rand.New(rand.NewSource(seed)).Float64
+}
+
+func run() error {
+	filename := flag.Arg(0)
+	src, err := readSource(filename)
+	if err != nil {
+		return err
+	}
+
+	if filename == "" || filename == "-" {
+		filename = "<stdin>"
+	}
+
+	vm := goja.New()
+	vm.SetRandSource(newRandSource())
+
+	vm.Set("console", createConsole(vm))
+	vm.Set("load", func(call goja.FunctionCall) goja.Value{
+		return load(vm, call)
+	})
+
+	if *timelimit > 0 {
+		time.AfterFunc(time.Duration(*timelimit) * time.Second, func() {
+			vm.Interrupt("timeout")
+		})
+	}
+
+	//log.Println("Compiling...")
+	prg, err := goja.Compile(filename, string(src))
+	log.Println("Running...")
+	_, err = vm.RunProgram(prg)
+	log.Println("Finished.")
+	return err
+}
+
+func main() {
+	defer func() {
+		if x := recover(); x != nil {
+			debug.Stack()
+			panic(x)
+		}
+	}()
+	flag.Parse()
+	if *cpuprofile != "" {
+		f, err := os.Create(*cpuprofile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
+
+	if err := run(); err != nil {
+		fmt.Printf("err type: %T\n", err)
+		switch err := err.(type) {
+		case *goja.Exception:
+			fmt.Print(err.String())
+		case *goja.InterruptedError:
+			fmt.Print(err.String())
+		default:
+			fmt.Printf("err: %v\n", err)
+		}
+		os.Exit(64)
+	}
+}
+

+ 97 - 0
ipow.go

@@ -0,0 +1,97 @@
+package goja
+
+// ported from https://gist.github.com/orlp/3551590
+
+var highest_bit_set = [256]byte{
+	0, 1, 2, 2, 3, 3, 3, 3,
+	4, 4, 4, 4, 4, 4, 4, 4,
+	5, 5, 5, 5, 5, 5, 5, 5,
+	5, 5, 5, 5, 5, 5, 5, 5,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 6, 6, 6, 6, 6, 255, // anything past 63 is a guaranteed overflow with base > 1
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255,
+}
+
+func ipow(base, exp int64) (result int64) {
+	result = 1
+
+	switch highest_bit_set[byte(exp)] {
+	case 255: // we use 255 as an overflow marker and return 0 on overflow/underflow
+		if base == 1 {
+			return 1
+		}
+
+		if base == -1 {
+			return 1 - 2*(exp&1)
+		}
+
+		return 0
+	case 6:
+		if exp&1 != 0 {
+			result *= base
+		}
+		exp >>= 1
+		base *= base
+		fallthrough
+	case 5:
+		if exp&1 != 0 {
+			result *= base
+		}
+		exp >>= 1
+		base *= base
+		fallthrough
+	case 4:
+		if exp&1 != 0 {
+			result *= base
+		}
+		exp >>= 1
+		base *= base
+		fallthrough
+	case 3:
+		if exp&1 != 0 {
+			result *= base
+		}
+		exp >>= 1
+		base *= base
+		fallthrough
+	case 2:
+		if exp&1 != 0 {
+			result *= base
+		}
+		exp >>= 1
+		base *= base
+		fallthrough
+	case 1:
+		if exp&1 != 0 {
+			result *= base
+		}
+		fallthrough
+	default:
+		return result
+	}
+}

+ 858 - 0
object.go

@@ -0,0 +1,858 @@
+package goja
+
+import "reflect"
+
+const (
+	classObject   = "Object"
+	classArray    = "Array"
+	classFunction = "Function"
+	classNumber   = "Number"
+	classString   = "String"
+	classBoolean  = "Boolean"
+	classError    = "Error"
+	classRegExp   = "RegExp"
+	classDate     = "Date"
+)
+
+type Object struct {
+	runtime *Runtime
+	self    objectImpl
+}
+
+type iterNextFunc func() (propIterItem, iterNextFunc)
+
+type objectImpl interface {
+	sortable
+	className() string
+	get(Value) Value
+	getProp(Value) Value
+	getPropStr(string) Value
+	getStr(string) Value
+	getOwnProp(string) Value
+	put(Value, Value, bool)
+	putStr(string, Value, bool)
+	hasProperty(Value) bool
+	hasPropertyStr(string) bool
+	hasOwnProperty(Value) bool
+	hasOwnPropertyStr(string) bool
+	_putProp(name string, value Value, writable, enumerable, configurable bool) Value
+	defineOwnProperty(name Value, descr objectImpl, throw bool) bool
+	toPrimitiveNumber() Value
+	toPrimitiveString() Value
+	toPrimitive() Value
+	assertCallable() (call func(FunctionCall) Value, ok bool)
+	// defineOwnProperty(Value, property, bool) bool
+	deleteStr(name string, throw bool) bool
+	delete(name Value, throw bool) bool
+	proto() *Object
+	hasInstance(v Value) bool
+	isExtensible() bool
+	preventExtensions()
+	enumerate(all, recusrive bool) iterNextFunc
+	_enumerate(recursive bool) iterNextFunc
+	export() interface{}
+	exportType() reflect.Type
+	equal(objectImpl) bool
+
+	// clone(*_object, *_object, *_clone) *_object
+	// marshalJSON() json.Marshaler
+
+}
+
+type baseObject struct {
+	class      string
+	val        *Object
+	prototype  *Object
+	extensible bool
+
+	values    map[string]Value
+	propNames []string
+}
+
+type funcObject struct {
+	baseObject
+
+	nameProp, lenProp valueProperty
+
+	stash *stash
+	prg   *Program
+	src   string
+}
+
+type primitiveValueObject struct {
+	baseObject
+	pValue Value
+}
+
+func (o *primitiveValueObject) export() interface{} {
+	return o.pValue.Export()
+}
+
+func (o *primitiveValueObject) exportType() reflect.Type {
+	return o.pValue.ExportType()
+}
+
+type FunctionCall struct {
+	This      Value
+	Arguments []Value
+}
+
+func (f FunctionCall) Argument(idx int) Value {
+	if idx < len(f.Arguments) {
+		return f.Arguments[idx]
+	}
+	return _undefined
+}
+
+type nativeFuncObject struct {
+	baseObject
+	nameProp, lenProp valueProperty
+	f                 func(FunctionCall) Value
+	construct         func(args []Value) *Object
+}
+
+func (f *nativeFuncObject) export() interface{} {
+	return f.f
+}
+
+func (f *nativeFuncObject) exportType() reflect.Type {
+	return reflect.TypeOf(f.f)
+}
+
+type boundFuncObject struct {
+	nativeFuncObject
+}
+
+func (o *baseObject) init() {
+	o.values = make(map[string]Value)
+}
+
+func (o *baseObject) className() string {
+	return o.class
+}
+
+func (o *baseObject) getPropStr(name string) Value {
+	if val, exists := o.values[name]; exists {
+		return val
+	}
+	if o.prototype != nil {
+		return o.prototype.self.getPropStr(name)
+	}
+	return nil
+}
+
+func (o *baseObject) getProp(n Value) Value {
+	return o.val.self.getPropStr(n.String())
+}
+
+func (o *baseObject) hasProperty(n Value) bool {
+	return o.val.self.getProp(n) != nil
+}
+
+func (o *baseObject) hasPropertyStr(name string) bool {
+	return o.val.self.getPropStr(name) != nil
+}
+
+func (o *baseObject) getStr(name string) Value {
+	p := o.val.self.getPropStr(name)
+	if p, ok := p.(*valueProperty); ok {
+		return p.get(o.val)
+	}
+
+	if p == nil && name == "__proto__" {
+		return o.prototype
+	}
+	return p
+}
+
+func (o *baseObject) get(n Value) Value {
+	return o.getStr(n.String())
+}
+
+func (o *baseObject) checkDeleteProp(name string, prop *valueProperty, throw bool) bool {
+	if !prop.configurable {
+		o.val.runtime.typeErrorResult(throw, "Cannot delete property '%s' of %s", name, o.val.ToString())
+		return false
+	}
+	return true
+}
+
+func (o *baseObject) checkDelete(name string, val Value, throw bool) bool {
+	if val, ok := val.(*valueProperty); ok {
+		return o.checkDeleteProp(name, val, throw)
+	}
+	return true
+}
+
+func (o *baseObject) _delete(name string) {
+	delete(o.values, name)
+	for i, n := range o.propNames {
+		if n == name {
+			copy(o.propNames[i:], o.propNames[i+1:])
+			o.propNames = o.propNames[:len(o.propNames)-1]
+			break
+		}
+	}
+}
+
+func (o *baseObject) deleteStr(name string, throw bool) bool {
+	if val, exists := o.values[name]; exists {
+		if !o.checkDelete(name, val, throw) {
+			return false
+		}
+		o._delete(name)
+		return true
+	}
+	return true
+}
+
+func (o *baseObject) delete(n Value, throw bool) bool {
+	return o.deleteStr(n.String(), throw)
+}
+
+func (o *baseObject) put(n Value, val Value, throw bool) {
+	o.putStr(n.String(), val, throw)
+}
+
+func (o *baseObject) getOwnProp(name string) Value {
+	return o.values[name]
+}
+
+func (o *baseObject) putStr(name string, val Value, throw bool) {
+	if v, exists := o.values[name]; exists {
+		if prop, ok := v.(*valueProperty); ok {
+			if !prop.isWritable() {
+				o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name)
+				return
+			}
+			prop.set(o.val, val)
+			return
+		}
+		o.values[name] = val
+		return
+	}
+
+	if name == "__proto__" {
+		if !o.extensible {
+			o.val.runtime.typeErrorResult(throw, "%s is not extensible", o.val)
+			return
+		}
+		if val == _undefined || val == _null {
+			o.prototype = nil
+			return
+		} else {
+			if val, ok := val.(*Object); ok {
+				o.prototype = val
+			}
+		}
+		return
+	}
+
+	var pprop Value
+	if proto := o.prototype; proto != nil {
+		pprop = proto.self.getPropStr(name)
+	}
+
+	if pprop != nil {
+		if prop, ok := pprop.(*valueProperty); ok {
+			if !prop.isWritable() {
+				o.val.runtime.typeErrorResult(throw)
+				return
+			}
+			if prop.accessor {
+				prop.set(o.val, val)
+				return
+			}
+		}
+	} else {
+		if !o.extensible {
+			o.val.runtime.typeErrorResult(throw)
+			return
+		}
+	}
+
+	o.values[name] = val
+	o.propNames = append(o.propNames, name)
+}
+
+func (o *baseObject) hasOwnProperty(n Value) bool {
+	v := o.values[n.String()]
+	return v != nil
+}
+
+func (o *baseObject) hasOwnPropertyStr(name string) bool {
+	v := o.values[name]
+	return v != nil
+}
+
+func (o *baseObject) _defineOwnProperty(name Value, existingValue Value, descr objectImpl, throw bool) (val Value, ok bool) {
+	var hasWritable, hasEnumerable, hasConfigurable bool
+	var writable, enumerable, configurable bool
+
+	value := descr.getStr("value")
+
+	if p := descr.getStr("writable"); p != nil {
+		hasWritable = true
+		writable = p.ToBoolean()
+	}
+	if p := descr.getStr("enumerable"); p != nil {
+		hasEnumerable = true
+		enumerable = p.ToBoolean()
+	}
+	if p := descr.getStr("configurable"); p != nil {
+		hasConfigurable = true
+		configurable = p.ToBoolean()
+	}
+
+	getter := descr.getStr("get")
+	setter := descr.getStr("set")
+
+	if (getter != nil || setter != nil) && (value != nil || hasWritable) {
+		o.val.runtime.typeErrorResult(throw, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
+		return nil, false
+	}
+
+	getterObj, _ := getter.(*Object)
+	setterObj, _ := setter.(*Object)
+
+	var existing *valueProperty
+
+	if existingValue == nil {
+		if !o.extensible {
+			o.val.runtime.typeErrorResult(throw)
+			return nil, false
+		}
+		existing = &valueProperty{}
+	} else {
+		if existing, ok = existingValue.(*valueProperty); !ok {
+			existing = &valueProperty{
+				writable:     true,
+				enumerable:   true,
+				configurable: true,
+				value:        existingValue,
+			}
+		}
+
+		if !existing.configurable {
+			if configurable {
+				goto Reject
+			}
+			if hasEnumerable && enumerable != existing.enumerable {
+				goto Reject
+			}
+		}
+		if existing.accessor && value != nil || !existing.accessor && (getterObj != nil || setterObj != nil) {
+			if !existing.configurable {
+				goto Reject
+			}
+		} else if !existing.accessor {
+			if !existing.configurable {
+				if !existing.writable {
+					if writable {
+						goto Reject
+					}
+					if value != nil && !value.SameAs(existing.value) {
+						goto Reject
+					}
+				}
+			}
+		} else {
+			if !existing.configurable {
+				if getter != nil && existing.getterFunc != getterObj || setter != nil && existing.setterFunc != setterObj {
+					goto Reject
+				}
+			}
+		}
+	}
+
+	if writable && enumerable && configurable && value != nil {
+		return value, true
+	}
+
+	if hasWritable {
+		existing.writable = writable
+	}
+	if hasEnumerable {
+		existing.enumerable = enumerable
+	}
+	if hasConfigurable {
+		existing.configurable = configurable
+	}
+
+	if value != nil {
+		existing.value = value
+		existing.getterFunc = nil
+		existing.setterFunc = nil
+	}
+
+	if value != nil || hasWritable {
+		existing.accessor = false
+	}
+
+	if getter != nil {
+		existing.getterFunc = propGetter(o.val, getter, o.val.runtime)
+		existing.value = nil
+		existing.accessor = true
+	}
+
+	if setter != nil {
+		existing.setterFunc = propSetter(o.val, setter, o.val.runtime)
+		existing.value = nil
+		existing.accessor = true
+	}
+
+	if !existing.accessor && existing.value == nil {
+		existing.value = _undefined
+	}
+
+	return existing, true
+
+Reject:
+	o.val.runtime.typeErrorResult(throw, "Cannot redefine property: %s", name.ToString())
+	return nil, false
+}
+
+func (o *baseObject) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	name := n.String()
+	val := o.values[name]
+	if v, ok := o._defineOwnProperty(n, val, descr, throw); ok {
+		o.values[name] = v
+		if val == nil {
+			o.propNames = append(o.propNames, name)
+		}
+		return true
+	}
+	return false
+}
+
+func (o *baseObject) _put(name string, v Value) {
+	if _, exists := o.values[name]; !exists {
+		o.propNames = append(o.propNames, name)
+	}
+
+	o.values[name] = v
+}
+
+func (o *baseObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	if writable && enumerable && configurable {
+		o._put(name, value)
+		return value
+	} else {
+		p := &valueProperty{
+			value:        value,
+			writable:     writable,
+			enumerable:   enumerable,
+			configurable: configurable,
+		}
+		o._put(name, p)
+		return p
+	}
+}
+
+func (o *baseObject) tryPrimitive(methodName string) Value {
+	if method, ok := o.getStr(methodName).(*Object); ok {
+		if call, ok := method.self.assertCallable(); ok {
+			v := call(FunctionCall{
+				This: o.val,
+			})
+			if _, fail := v.(*Object); !fail {
+				return v
+			}
+		}
+	}
+	return nil
+}
+
+func (o *baseObject) toPrimitiveNumber() Value {
+	if v := o.tryPrimitive("valueOf"); v != nil {
+		return v
+	}
+
+	if v := o.tryPrimitive("toString"); v != nil {
+		return v
+	}
+
+	o.val.runtime.typeErrorResult(true, "Could not convert %v to primitive", o)
+	return nil
+}
+
+func (o *baseObject) toPrimitiveString() Value {
+	if v := o.tryPrimitive("toString"); v != nil {
+		return v
+	}
+
+	if v := o.tryPrimitive("valueOf"); v != nil {
+		return v
+	}
+
+	o.val.runtime.typeErrorResult(true, "Could not convert %v to primitive", o)
+	return nil
+}
+
+func (o *baseObject) toPrimitive() Value {
+	return o.toPrimitiveNumber()
+}
+
+func (o *baseObject) assertCallable() (func(FunctionCall) Value, bool) {
+	return nil, false
+}
+
+func (o *baseObject) proto() *Object {
+	return o.prototype
+}
+
+func (o *baseObject) isExtensible() bool {
+	return o.extensible
+}
+
+func (o *baseObject) preventExtensions() {
+	o.extensible = false
+}
+
+func (o *baseObject) sortLen() int {
+	return int(toLength(o.val.self.getStr("length")))
+}
+
+func (o *baseObject) sortGet(i int) Value {
+	return o.val.self.get(intToValue(int64(i)))
+}
+
+func (o *baseObject) swap(i, j int) {
+	ii := intToValue(int64(i))
+	jj := intToValue(int64(j))
+
+	x := o.val.self.get(ii)
+	y := o.val.self.get(jj)
+
+	o.val.self.put(ii, y, false)
+	o.val.self.put(jj, x, false)
+}
+
+func (o *baseObject) export() interface{} {
+	m := make(map[string]interface{})
+
+	for item, f := o.enumerate(false, false)(); f != nil; item, f = f() {
+		v := item.value
+		if v == nil {
+			v = o.getStr(item.name)
+		}
+		if v != nil {
+			m[item.name] = v.Export()
+		} else {
+			m[item.name] = nil
+		}
+	}
+	return m
+}
+
+func (o *baseObject) exportType() reflect.Type {
+	return reflectTypeMap
+}
+
+type enumerableFlag int
+
+const (
+	_ENUM_UNKNOWN enumerableFlag = iota
+	_ENUM_FALSE
+	_ENUM_TRUE
+)
+
+type propIterItem struct {
+	name       string
+	value      Value // set only when enumerable == _ENUM_UNKNOWN
+	enumerable enumerableFlag
+}
+
+type objectPropIter struct {
+	o         *baseObject
+	propNames []string
+	recursive bool
+	idx       int
+}
+
+type propFilterIter struct {
+	wrapped iterNextFunc
+	all     bool
+	seen    map[string]bool
+}
+
+func (i *propFilterIter) next() (propIterItem, iterNextFunc) {
+	for {
+		var item propIterItem
+		item, i.wrapped = i.wrapped()
+		if i.wrapped == nil {
+			return propIterItem{}, nil
+		}
+
+		if !i.seen[item.name] {
+			i.seen[item.name] = true
+			if !i.all {
+				if item.enumerable == _ENUM_FALSE {
+					continue
+				}
+				if item.enumerable == _ENUM_UNKNOWN {
+					if prop, ok := item.value.(*valueProperty); ok {
+						if !prop.enumerable {
+							continue
+						}
+					}
+				}
+			}
+			return item, i.next
+		}
+	}
+}
+
+func (i *objectPropIter) next() (propIterItem, iterNextFunc) {
+	for i.idx < len(i.propNames) {
+		name := i.propNames[i.idx]
+		i.idx++
+		prop := i.o.values[name]
+		if prop != nil {
+			return propIterItem{name: name, value: prop}, i.next
+		}
+	}
+
+	if i.recursive && i.o.prototype != nil {
+		return i.o.prototype.self._enumerate(i.recursive)()
+	}
+	return propIterItem{}, nil
+}
+
+func (o *baseObject) _enumerate(recusrive bool) iterNextFunc {
+	propNames := make([]string, len(o.propNames))
+	copy(propNames, o.propNames)
+	return (&objectPropIter{
+		o:         o,
+		propNames: propNames,
+		recursive: recusrive,
+	}).next
+}
+
+func (o *baseObject) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: o._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (o *baseObject) equal(other objectImpl) bool {
+	// Rely on parent reference comparison
+	return false
+}
+
+func (f *funcObject) getPropStr(name string) Value {
+	switch name {
+	case "prototype":
+		if _, exists := f.values["prototype"]; !exists {
+			return f.addPrototype()
+		}
+	}
+
+	return f.baseObject.getPropStr(name)
+}
+
+func (f *funcObject) addPrototype() Value {
+	proto := f.val.runtime.NewObject()
+	proto.self._putProp("constructor", f.val, true, false, true)
+	return f._putProp("prototype", proto, true, false, false)
+}
+
+func (f *funcObject) getProp(n Value) Value {
+	return f.getPropStr(n.String())
+}
+
+func (f *funcObject) hasOwnProperty(n Value) bool {
+	if r := f.baseObject.hasOwnProperty(n); r {
+		return true
+	}
+
+	name := n.String()
+	if name == "prototype" {
+		return true
+	}
+	return false
+}
+
+func (f *funcObject) hasOwnPropertyStr(name string) bool {
+	if r := f.baseObject.hasOwnPropertyStr(name); r {
+		return true
+	}
+
+	if name == "prototype" {
+		return true
+	}
+	return false
+}
+
+func (f *funcObject) construct(args []Value) *Object {
+	proto := f.getStr("prototype")
+	var protoObj *Object
+	if p, ok := proto.(*Object); ok {
+		protoObj = p
+	} else {
+		protoObj = f.val.runtime.global.ObjectPrototype
+	}
+	obj := f.val.runtime.newBaseObject(protoObj, classObject).val
+	ret := f.Call(FunctionCall{
+		This:      obj,
+		Arguments: args,
+	})
+
+	if ret, ok := ret.(*Object); ok {
+		return ret
+	}
+	return obj
+}
+
+func (f *funcObject) Call(call FunctionCall) Value {
+	vm := f.val.runtime.vm
+	pc := vm.pc
+	vm.push(f.val)
+	vm.push(call.This)
+	for _, arg := range call.Arguments {
+		vm.push(arg)
+	}
+	vm.pc = -1
+	vm.pushCtx()
+	vm.args = len(call.Arguments)
+	vm.prg = f.prg
+	vm.stash = f.stash
+	vm.pc = 0
+	vm.run()
+	vm.pc = pc
+	vm.halt = false
+	return vm.pop()
+}
+
+func (f *funcObject) export() interface{} {
+	return f.Call
+}
+
+func (f *funcObject) exportType() reflect.Type {
+	return reflect.TypeOf(f.Call)
+}
+
+func (f *funcObject) assertCallable() (func(FunctionCall) Value, bool) {
+	return f.Call, true
+}
+
+func (f *funcObject) init(name string, length int) {
+	f.baseObject.init()
+
+	f.nameProp.configurable = true
+	f.nameProp.value = newStringValue(name)
+	f.values["name"] = &f.nameProp
+
+	f.lenProp.configurable = true
+	f.lenProp.value = valueInt(length)
+	f.values["length"] = &f.lenProp
+}
+
+func (o *baseObject) hasInstance(v Value) bool {
+	o.val.runtime.typeErrorResult(true, "Expecting a function in instanceof check, but got %s", o.val.ToString())
+	panic("Unreachable")
+}
+
+func (f *funcObject) hasInstance(v Value) bool {
+	return f._hasInstance(v)
+}
+
+func (f *nativeFuncObject) hasInstance(v Value) bool {
+	return f._hasInstance(v)
+}
+
+func (f *baseObject) _hasInstance(v Value) bool {
+	if v, ok := v.(*Object); ok {
+		o := f.val.self.getStr("prototype")
+		if o1, ok := o.(*Object); ok {
+			for {
+				v = v.self.proto()
+				if v == nil {
+					return false
+				}
+				if o1 == v {
+					return true
+				}
+			}
+		} else {
+			f.val.runtime.typeErrorResult(true, "prototype is not an object")
+		}
+	}
+
+	return false
+}
+
+func (f *nativeFuncObject) defaultConstruct(args []Value) Value {
+	proto := f.getStr("prototype")
+	var protoObj *Object
+	if p, ok := proto.(*Object); ok {
+		protoObj = p
+	} else {
+		protoObj = f.val.runtime.global.ObjectPrototype
+	}
+	obj := f.val.runtime.newBaseObject(protoObj, classObject).val
+	ret := f.f(FunctionCall{
+		This:      obj,
+		Arguments: args,
+	})
+
+	if ret, ok := ret.(*Object); ok {
+		return ret
+	}
+	return obj
+}
+
+func (f *nativeFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
+	if f.f != nil {
+		return f.f, true
+	}
+	return nil, false
+}
+
+func (f *nativeFuncObject) init(name string, length int) {
+	f.baseObject.init()
+
+	f.nameProp.configurable = true
+	f.nameProp.value = newStringValue(name)
+	f._put("name", &f.nameProp)
+
+	f.lenProp.configurable = true
+	f.lenProp.value = valueInt(length)
+	f._put("length", &f.lenProp)
+}
+
+func (f *boundFuncObject) getProp(n Value) Value {
+	return f.getPropStr(n.String())
+}
+
+func (f *boundFuncObject) getPropStr(name string) Value {
+	if name == "caller" || name == "arguments" {
+		//f.runtime.typeErrorResult(true, "'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")
+		return f.val.runtime.global.throwerProperty
+	}
+	return f.nativeFuncObject.getPropStr(name)
+}
+
+func (f *boundFuncObject) delete(n Value, throw bool) bool {
+	return f.deleteStr(n.String(), throw)
+}
+
+func (f *boundFuncObject) deleteStr(name string, throw bool) bool {
+	if name == "caller" || name == "arguments" {
+		return true
+	}
+	return f.nativeFuncObject.deleteStr(name, throw)
+}
+
+func (f *boundFuncObject) putStr(name string, val Value, throw bool) {
+	if name == "caller" || name == "arguments" {
+		f.val.runtime.typeErrorResult(true, "'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.")
+	}
+	f.nativeFuncObject.putStr(name, val, throw)
+}
+
+func (f *boundFuncObject) put(n Value, val Value, throw bool) {
+	f.putStr(n.String(), val, throw)
+}

+ 150 - 0
object_args.go

@@ -0,0 +1,150 @@
+package goja
+
+type argumentsObject struct {
+	baseObject
+	length int
+}
+
+type mappedProperty struct {
+	valueProperty
+	v *Value
+}
+
+func (a *argumentsObject) getPropStr(name string) Value {
+	if prop, ok := a.values[name].(*mappedProperty); ok {
+		return *prop.v
+	}
+	return a.baseObject.getPropStr(name)
+}
+
+func (a *argumentsObject) getProp(n Value) Value {
+	return a.getPropStr(n.String())
+}
+
+func (a *argumentsObject) init() {
+	a.baseObject.init()
+	a._putProp("length", intToValue(int64(a.length)), true, false, true)
+}
+
+func (a *argumentsObject) put(n Value, val Value, throw bool) {
+	a.putStr(n.String(), val, throw)
+}
+
+func (a *argumentsObject) putStr(name string, val Value, throw bool) {
+	if prop, ok := a.values[name].(*mappedProperty); ok {
+		if !prop.writable {
+			a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
+			return
+		}
+		*prop.v = val
+		return
+	}
+	a.baseObject.putStr(name, val, throw)
+}
+
+func (a *argumentsObject) deleteStr(name string, throw bool) bool {
+	if prop, ok := a.values[name].(*mappedProperty); ok {
+		if !a.checkDeleteProp(name, &prop.valueProperty, throw) {
+			return false
+		}
+		a._delete(name)
+		return true
+	}
+
+	return a.baseObject.deleteStr(name, throw)
+}
+
+func (a *argumentsObject) delete(n Value, throw bool) bool {
+	return a.deleteStr(n.String(), throw)
+}
+
+type argumentsPropIter1 struct {
+	a         *argumentsObject
+	idx       int
+	recursive bool
+}
+
+type argumentsPropIter struct {
+	wrapped iterNextFunc
+}
+
+func (i *argumentsPropIter) next() (propIterItem, iterNextFunc) {
+	var item propIterItem
+	item, i.wrapped = i.wrapped()
+	if i.wrapped == nil {
+		return propIterItem{}, nil
+	}
+	if prop, ok := item.value.(*mappedProperty); ok {
+		item.value = *prop.v
+	}
+	return item, i.next
+}
+
+func (a *argumentsObject) _enumerate(recursive bool) iterNextFunc {
+	return (&argumentsPropIter{
+		wrapped: a.baseObject._enumerate(recursive),
+	}).next
+
+}
+
+func (a *argumentsObject) enumerate(all, recursive bool) iterNextFunc {
+	return (&argumentsPropIter{
+		wrapped: a.baseObject.enumerate(all, recursive),
+	}).next
+}
+
+func (a *argumentsObject) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	name := n.String()
+	if mapped, ok := a.values[name].(*mappedProperty); ok {
+		existing := &valueProperty{
+			configurable: mapped.configurable,
+			writable:     true,
+			enumerable:   mapped.enumerable,
+			value:        mapped.get(a.val),
+		}
+
+		val, ok := a.baseObject._defineOwnProperty(n, existing, descr, throw)
+		if !ok {
+			return false
+		}
+
+		if prop, ok := val.(*valueProperty); ok {
+			if !prop.accessor {
+				*mapped.v = prop.value
+			}
+			if prop.accessor || !prop.writable {
+				a._put(name, prop)
+				return true
+			}
+			mapped.configurable = prop.configurable
+			mapped.enumerable = prop.enumerable
+		} else {
+			*mapped.v = val
+			mapped.configurable = true
+			mapped.enumerable = true
+		}
+
+		return true
+	}
+
+	return a.baseObject.defineOwnProperty(n, descr, throw)
+}
+
+func (a *argumentsObject) getOwnProp(name string) Value {
+	if mapped, ok := a.values[name].(*mappedProperty); ok {
+		return *mapped.v
+	}
+
+	return a.baseObject.getOwnProp(name)
+}
+
+func (a *argumentsObject) export() interface{} {
+	arr := make([]interface{}, a.length)
+	for i, _ := range arr {
+		v := a.get(intToValue(int64(i)))
+		if v != nil {
+			arr[i] = v.Export()
+		}
+	}
+	return arr
+}

+ 225 - 0
object_gomap.go

@@ -0,0 +1,225 @@
+package goja
+
+import (
+	"reflect"
+	"strconv"
+)
+
+type objectGoMapSimple struct {
+	baseObject
+	data map[string]interface{}
+}
+
+func (o *objectGoMapSimple) init() {
+	o.baseObject.init()
+	o.prototype = o.val.runtime.global.ObjectPrototype
+	o.class = classObject
+}
+
+func (o *objectGoMapSimple) _get(n Value) Value {
+	return o._getStr(n.String())
+}
+
+func (o *objectGoMapSimple) _getStr(name string) Value {
+	v, exists := o.data[name]
+	if !exists {
+		return nil
+	}
+	return o.val.runtime.ToValue(v)
+}
+
+func (o *objectGoMapSimple) get(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.baseObject.get(n)
+}
+
+func (o *objectGoMapSimple) getProp(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.baseObject.getProp(n)
+}
+
+func (o *objectGoMapSimple) getPropStr(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.baseObject.getPropStr(name)
+}
+
+func (o *objectGoMapSimple) getStr(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.baseObject.getStr(name)
+}
+
+func (o *objectGoMapSimple) getOwnProp(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.baseObject.getOwnProp(name)
+}
+
+func (o *objectGoMapSimple) put(n Value, val Value, throw bool) {
+	o.putStr(n.String(), val, throw)
+}
+
+func (o *objectGoMapSimple) _hasStr(name string) bool {
+	_, exists := o.data[name]
+	return exists
+}
+
+func (o *objectGoMapSimple) _has(n Value) bool {
+	return o._hasStr(n.String())
+}
+
+func (o *objectGoMapSimple) putStr(name string, val Value, throw bool) {
+	if o.extensible || o._hasStr(name) {
+		o.data[name] = val.Export()
+	}
+	o.val.runtime.typeErrorResult(throw, "Host object is not extensible")
+}
+
+func (o *objectGoMapSimple) hasProperty(n Value) bool {
+	if o._has(n) {
+		return true
+	}
+	return o.baseObject.hasProperty(n)
+}
+
+func (o *objectGoMapSimple) hasPropertyStr(name string) bool {
+	if o._hasStr(name) {
+		return true
+	}
+	return o.baseObject.hasOwnPropertyStr(name)
+}
+
+func (o *objectGoMapSimple) hasOwnProperty(n Value) bool {
+	return o._has(n)
+}
+
+func (o *objectGoMapSimple) hasOwnPropertyStr(name string) bool {
+	return o._hasStr(name)
+}
+
+func (o *objectGoMapSimple) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	o.putStr(name, value, false)
+	return value
+}
+
+func (o *objectGoMapSimple) defineOwnProperty(name Value, descr objectImpl, throw bool) bool {
+	if descr.hasPropertyStr("get") || descr.hasPropertyStr("set") {
+		o.val.runtime.typeErrorResult(throw, "Host objects do not support accessor properties")
+		return false
+	}
+	o.put(name, descr.getStr("value"), throw)
+	return true
+}
+
+/*
+func (o *objectGoMapSimple) toPrimitiveNumber() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoMapSimple) toPrimitiveString() Value {
+	return stringObjectObject
+}
+
+func (o *objectGoMapSimple) toPrimitive() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoMapSimple) assertCallable() (call func(FunctionCall) Value, ok bool) {
+	return nil, false
+}
+*/
+
+func (o *objectGoMapSimple) deleteStr(name string, throw bool) bool {
+	delete(o.data, name)
+	return true
+}
+
+func (o *objectGoMapSimple) delete(name Value, throw bool) bool {
+	return o.deleteStr(name.String(), throw)
+}
+
+type gomapPropIter struct {
+	o         *objectGoMapSimple
+	propNames []string
+	recursive bool
+	idx       int
+}
+
+func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
+	for i.idx < len(i.propNames) {
+		name := i.propNames[i.idx]
+		i.idx++
+		if _, exists := i.o.data[name]; exists {
+			return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+		}
+	}
+
+	if i.recursive {
+		return i.o.prototype.self._enumerate(true)()
+	}
+
+	return propIterItem{}, nil
+}
+
+func (o *objectGoMapSimple) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: o._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (o *objectGoMapSimple) _enumerate(recursive bool) iterNextFunc {
+	propNames := make([]string, len(o.data))
+	i := 0
+	for key, _ := range o.data {
+		propNames[i] = key
+		i++
+	}
+	return (&gomapPropIter{
+		o:         o,
+		propNames: propNames,
+		recursive: recursive,
+	}).next
+}
+
+func (o *objectGoMapSimple) export() interface{} {
+	return o.data
+}
+
+func (o *objectGoMapSimple) exportType() reflect.Type {
+	return reflectTypeMap
+}
+
+func (o *objectGoMapSimple) equal(other objectImpl) bool {
+	if other, ok := other.(*objectGoMapSimple); ok {
+		return o == other
+	}
+	return false
+}
+
+func (o *objectGoMapSimple) sortLen() int {
+	return len(o.data)
+}
+
+func (o *objectGoMapSimple) sortGet(i int) Value {
+	return o.getStr(strconv.Itoa(i))
+}
+
+func (o *objectGoMapSimple) swap(i, j int) {
+	ii := strconv.Itoa(i)
+	jj := strconv.Itoa(j)
+	x := o.getStr(ii)
+	y := o.getStr(jj)
+
+	o.putStr(ii, y, false)
+	o.putStr(jj, x, false)
+}

+ 152 - 0
object_gomap_test.go

@@ -0,0 +1,152 @@
+package goja
+
+import "testing"
+
+func TestGomapProp(t *testing.T) {
+	const SCRIPT = `
+	o.a + o.b;
+	`
+	r := New()
+	r.Set("o", map[string]interface{}{
+		"a": 40,
+		"b": 2,
+	})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i := v.ToInteger(); i != 42 {
+		t.Fatalf("Expected 42, got: %d", i)
+	}
+}
+
+func TestGomapEnumerate(t *testing.T) {
+	const SCRIPT = `
+	var hasX = false;
+	var hasY = false;
+	for (var key in o) {
+		switch (key) {
+		case "x":
+			if (hasX) {
+				throw "Already have x";
+			}
+			hasX = true;
+			break;
+		case "y":
+			if (hasY) {
+				throw "Already have y";
+			}
+			hasY = true;
+			break;
+		default:
+			throw "Unexpected property: " + key;
+		}
+	}
+	hasX && hasY;
+	`
+	r := New()
+	r.Set("o", map[string]interface{}{
+		"x": 40,
+		"y": 2,
+	})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+}
+
+func TestGomapDeleteWhileEnumerate(t *testing.T) {
+	const SCRIPT = `
+	var hasX = false;
+	var hasY = false;
+	for (var key in o) {
+		switch (key) {
+		case "x":
+			if (hasX) {
+				throw "Already have x";
+			}
+			hasX = true;
+			delete o.y;
+			break;
+		case "y":
+			if (hasY) {
+				throw "Already have y";
+			}
+			hasY = true;
+			delete o.x;
+			break;
+		default:
+			throw "Unexpected property: " + key;
+		}
+	}
+	hasX && !hasY || hasY && !hasX;
+	`
+	r := New()
+	r.Set("o", map[string]interface{}{
+		"x": 40,
+		"y": 2,
+	})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+}
+
+func TestGomapInstanceOf(t *testing.T) {
+	const SCRIPT = `
+	(o instanceof Object) && !(o instanceof Error);
+	`
+	r := New()
+	r.Set("o", map[string]interface{}{})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+}
+
+func TestGomapTypeOf(t *testing.T) {
+	const SCRIPT = `
+	typeof o;
+	`
+	r := New()
+	r.Set("o", map[string]interface{}{})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(asciiString("object")) {
+		t.Fatalf("Expected object, got %v", v)
+	}
+}
+
+func TestGomapProto(t *testing.T) {
+	const SCRIPT = `
+	o.hasOwnProperty("test");
+	`
+	r := New()
+	r.Set("o", map[string]interface{}{
+		"test": 42,
+	})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+
+}

+ 352 - 0
object_goreflect.go

@@ -0,0 +1,352 @@
+package goja
+
+import (
+	"fmt"
+	"go/ast"
+	"reflect"
+)
+
+type objectGoReflect struct {
+	baseObject
+	origValue, value reflect.Value
+}
+
+func (o *objectGoReflect) init() {
+	o.baseObject.init()
+	switch o.value.Kind() {
+	case reflect.Bool:
+		o.class = classBoolean
+		o.prototype = o.val.runtime.global.BooleanPrototype
+	case reflect.String:
+		o.class = classString
+		o.prototype = o.val.runtime.global.StringPrototype
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
+		reflect.Float32, reflect.Float64:
+
+		o.class = classNumber
+		o.prototype = o.val.runtime.global.NumberPrototype
+	default:
+		o.class = classObject
+		o.prototype = o.val.runtime.global.ObjectPrototype
+	}
+
+	o.baseObject._putProp("toString", o.val.runtime.newNativeFunc(o.toStringFunc, nil, "toString", nil, 0), true, false, true)
+	o.baseObject._putProp("valueOf", o.val.runtime.newNativeFunc(o.valueOfFunc, nil, "valueOf", nil, 0), true, false, true)
+}
+
+func (o *objectGoReflect) toStringFunc(call FunctionCall) Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoReflect) valueOfFunc(call FunctionCall) Value {
+	return o.toPrimitive()
+}
+
+func (o *objectGoReflect) get(n Value) Value {
+	return o.getStr(n.String())
+}
+
+func (o *objectGoReflect) _get(name string) Value {
+	if o.value.Kind() == reflect.Struct {
+		if v := o.value.FieldByName(name); v.IsValid() {
+			return o.val.runtime.ToValue(v.Interface())
+		}
+	}
+
+	if v := o.origValue.MethodByName(name); v.IsValid() {
+		return o.val.runtime.ToValue(v.Interface())
+	}
+	return nil
+}
+
+func (o *objectGoReflect) getStr(name string) Value {
+	if v := o._get(name); v != nil {
+		return v
+	}
+	return o.baseObject.getStr(name)
+}
+
+func (o *objectGoReflect) getProp(n Value) Value {
+	name := n.String()
+	if p := o.getOwnProp(name); p != nil {
+		return p
+	}
+	return o.baseObject.getProp(n)
+}
+
+func (o *objectGoReflect) getPropStr(name string) Value {
+	if v := o.getOwnProp(name); v != nil {
+		return v
+	}
+	return o.baseObject.getPropStr(name)
+}
+
+func (o *objectGoReflect) getOwnProp(name string) Value {
+	if o.value.Kind() == reflect.Struct {
+		if v := o.value.FieldByName(name); v.IsValid() {
+			return &valueProperty{
+				value:      o.val.runtime.ToValue(v.Interface()),
+				writable:   true,
+				enumerable: true,
+			}
+		}
+	}
+
+	if v := o.origValue.MethodByName(name); v.IsValid() {
+		return &valueProperty{
+			value:      o.val.runtime.ToValue(v.Interface()),
+			enumerable: true,
+		}
+	}
+
+	return nil
+}
+
+func (o *objectGoReflect) put(n Value, val Value, throw bool) {
+	o.putStr(n.String(), val, throw)
+}
+
+func (o *objectGoReflect) putStr(name string, val Value, throw bool) {
+	if !o._put(name, val, throw) {
+		o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
+	}
+}
+
+func (o *objectGoReflect) _put(name string, val Value, throw bool) bool {
+	if o.value.Kind() == reflect.Struct {
+		if v := o.value.FieldByName(name); v.IsValid() {
+			vv, err := o.val.runtime.toReflectValue(val, v.Type())
+			if err != nil {
+				o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
+				return false
+			}
+			v.Set(vv)
+			return true
+		}
+	}
+	return false
+}
+
+func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	if o._put(name, value, false) {
+		return value
+	}
+	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
+}
+
+func (r *Runtime) checkHostObjectPropertyDescr(name string, descr objectImpl, throw bool) bool {
+	if descr.hasPropertyStr("get") || descr.hasPropertyStr("set") {
+		r.typeErrorResult(throw, "Host objects do not support accessor properties")
+		return false
+	}
+	if wr := descr.getStr("writable"); wr != nil && !wr.ToBoolean() {
+		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name)
+		return false
+	}
+	if cfg := descr.getStr("configurable"); cfg != nil && cfg.ToBoolean() {
+		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name)
+		return false
+	}
+	return true
+}
+
+func (o *objectGoReflect) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	name := n.String()
+	if ast.IsExported(name) {
+		if o.value.Kind() == reflect.Struct {
+			if v := o.value.FieldByName(name); v.IsValid() {
+				if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
+					return false
+				}
+				val := descr.getStr("value")
+				if val == nil {
+					val = _undefined
+				}
+				vv, err := o.val.runtime.toReflectValue(val, v.Type())
+				if err != nil {
+					o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
+					return false
+				}
+				v.Set(vv)
+				return true
+			}
+		}
+	}
+
+	return o.baseObject.defineOwnProperty(n, descr, throw)
+}
+
+func (o *objectGoReflect) _has(name string) bool {
+	if !ast.IsExported(name) {
+		return false
+	}
+	if o.value.Kind() == reflect.Struct {
+		if v := o.value.FieldByName(name); v.IsValid() {
+			return true
+		}
+	}
+	if v := o.origValue.MethodByName(name); v.IsValid() {
+		return true
+	}
+	return false
+}
+
+func (o *objectGoReflect) hasProperty(n Value) bool {
+	name := n.String()
+	if o._has(name) {
+		return true
+	}
+	return o.baseObject.hasProperty(n)
+}
+
+func (o *objectGoReflect) hasPropertyStr(name string) bool {
+	if o._has(name) {
+		return true
+	}
+	return o.baseObject.hasPropertyStr(name)
+}
+
+func (o *objectGoReflect) hasOwnProperty(n Value) bool {
+	return o._has(n.String())
+}
+
+func (o *objectGoReflect) hasOwnPropertyStr(name string) bool {
+	return o._has(name)
+}
+
+func (o *objectGoReflect) _toNumber() Value {
+	switch o.value.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return intToValue(o.value.Int())
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return intToValue(int64(o.value.Uint()))
+	case reflect.Bool:
+		if o.value.Bool() {
+			return intToValue(1)
+		} else {
+			return intToValue(0)
+		}
+	case reflect.Float32, reflect.Float64:
+		return floatToValue(o.value.Float())
+	}
+	return nil
+}
+
+func (o *objectGoReflect) _toString() Value {
+	switch o.value.Kind() {
+	case reflect.String:
+		return newStringValue(o.value.String())
+	case reflect.Bool:
+		if o.value.Interface().(bool) {
+			return stringTrue
+		} else {
+			return stringFalse
+		}
+	}
+	switch v := o.value.Interface().(type) {
+	case fmt.Stringer:
+		return newStringValue(v.String())
+	}
+	return stringObjectObject
+}
+
+func (o *objectGoReflect) toPrimitiveNumber() Value {
+	if v := o._toNumber(); v != nil {
+		return v
+	}
+	return o._toString()
+}
+
+func (o *objectGoReflect) toPrimitiveString() Value {
+	if v := o._toNumber(); v != nil {
+		return v.ToString()
+	}
+	return o._toString()
+}
+
+func (o *objectGoReflect) toPrimitive() Value {
+	if o.prototype == o.val.runtime.global.NumberPrototype {
+		return o.toPrimitiveNumber()
+	}
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoReflect) deleteStr(name string, throw bool) bool {
+	if o._has(name) {
+		o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type")
+		return false
+	}
+	return o.baseObject.deleteStr(name, throw)
+}
+
+func (o *objectGoReflect) delete(name Value, throw bool) bool {
+	return o.deleteStr(name.String(), throw)
+}
+
+type goreflectPropIter struct {
+	o         *objectGoReflect
+	idx       int
+	recursive bool
+}
+
+func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
+	l := i.o.value.NumField()
+	for i.idx < l {
+		name := i.o.value.Type().Field(i.idx).Name
+		i.idx++
+		if ast.IsExported(name) {
+			return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextField
+		}
+	}
+
+	i.idx = 0
+	return i.nextMethod()
+}
+
+func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
+	l := i.o.origValue.NumMethod()
+	for i.idx < l {
+		name := i.o.origValue.Type().Method(i.idx).Name
+		i.idx++
+		if ast.IsExported(name) {
+			return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextMethod
+		}
+	}
+
+	return i.o.baseObject._enumerate(i.recursive)()
+}
+
+func (o *objectGoReflect) _enumerate(recusrive bool) iterNextFunc {
+	r := &goreflectPropIter{
+		o:         o,
+		recursive: recusrive,
+	}
+	if o.value.Kind() == reflect.Struct {
+		return r.nextField
+	}
+	return r.nextMethod
+}
+
+func (o *objectGoReflect) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: o._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (o *objectGoReflect) export() interface{} {
+	return o.origValue.Interface()
+}
+
+func (o *objectGoReflect) exportType() reflect.Type {
+	return o.origValue.Type()
+}
+
+func (o *objectGoReflect) equal(other objectImpl) bool {
+	if other, ok := other.(*objectGoReflect); ok {
+		return o.value.Interface() == other.value.Interface()
+	}
+	return false
+}

+ 399 - 0
object_goreflect_test.go

@@ -0,0 +1,399 @@
+package goja
+
+import "testing"
+
+func TestGoReflectGet(t *testing.T) {
+	const SCRIPT = `
+	o.X + o.Y;
+	`
+	type O struct {
+		X int
+		Y string
+	}
+	r := New()
+	o := O{X: 4, Y: "2"}
+	r.Set("o", o)
+
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if s, ok := v.assertString(); ok {
+		if s.String() != "42" {
+			t.Fatalf("Unexpected string: %s", s)
+		}
+	} else {
+		t.Fatalf("Unexpected type: %s", v)
+	}
+}
+
+func TestGoReflectSet(t *testing.T) {
+	const SCRIPT = `
+	o.X++;
+	o.Y += "P";
+	`
+	type O struct {
+		X int
+		Y string
+	}
+	r := New()
+	o := O{X: 4, Y: "2"}
+	r.Set("o", &o)
+
+	_, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if o.X != 5 {
+		t.Fatalf("Unexpected X: %d", o.X)
+	}
+
+	if o.Y != "2P" {
+		t.Fatalf("Unexpected Y: %s", o.Y)
+	}
+}
+
+type TestGoReflectMethod_Struct struct {
+}
+
+func (s *TestGoReflectMethod_Struct) M() int {
+	return 42
+}
+
+func TestGoReflectEnumerate(t *testing.T) {
+	const SCRIPT = `
+	var hasX = false;
+	var hasY = false;
+	for (var key in o) {
+		switch (key) {
+		case "X":
+			if (hasX) {
+				throw "Already have X";
+			}
+			hasX = true;
+			break;
+		case "Y":
+			if (hasY) {
+				throw "Already have Y";
+			}
+			hasY = true;
+			break;
+		default:
+			throw "Unexpected property: " + key;
+		}
+	}
+	hasX && hasY;
+	`
+
+	type S struct {
+		X, Y int
+	}
+
+	r := New()
+	r.Set("o", S{X: 40, Y: 2})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+
+}
+
+func TestGoReflectCustomIntUnbox(t *testing.T) {
+	const SCRIPT = `
+	i + 2;
+	`
+
+	type CustomInt int
+	var i CustomInt = 40
+
+	r := New()
+	r.Set("i", i)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(intToValue(42)) {
+		t.Fatalf("Expected int 42, got %v", v)
+	}
+}
+
+func TestGoReflectPreserveCustomType(t *testing.T) {
+	const SCRIPT = `
+	i;
+	`
+
+	type CustomInt int
+	var i CustomInt = 42
+
+	r := New()
+	r.Set("i", i)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ve := v.Export()
+
+	if ii, ok := ve.(CustomInt); ok {
+		if ii != i {
+			t.Fatalf("Wrong value: %v", ii)
+		}
+	} else {
+		t.Fatalf("Wrong type: %v", ve)
+	}
+}
+
+func TestGoReflectCustomIntValueOf(t *testing.T) {
+	const SCRIPT = `
+	if (i instanceof Number) {
+		i.valueOf();
+	} else {
+		throw new Error("Value is not a number");
+	}
+	`
+
+	type CustomInt int
+	var i CustomInt = 42
+
+	r := New()
+	r.Set("i", i)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(intToValue(42)) {
+		t.Fatalf("Expected int 42, got %v", v)
+	}
+}
+
+func TestGoReflectEqual(t *testing.T) {
+	const SCRIPT = `
+	x === y;
+	`
+
+	type CustomInt int
+	var x CustomInt = 42
+	var y CustomInt = 42
+
+	r := New()
+	r.Set("x", x)
+	r.Set("y", y)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+}
+
+type testGoReflectMethod_O struct {
+	field string
+	Test  string
+}
+
+func (o testGoReflectMethod_O) Method(s string) string {
+	return o.field + s
+}
+
+func TestGoReflectMethod(t *testing.T) {
+	const SCRIPT = `
+	o.Method(" 123")
+	`
+
+	o := testGoReflectMethod_O{
+		field: "test",
+	}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(asciiString("test 123")) {
+		t.Fatalf("Expected 'test 123', got %v", v)
+	}
+}
+
+func (o *testGoReflectMethod_O) Set(s string) {
+	o.field = s
+}
+
+func (o *testGoReflectMethod_O) Get() string {
+	return o.field
+}
+
+func TestGoReflectMethodPtr(t *testing.T) {
+	const SCRIPT = `
+	o.Set("42")
+	o.Get()
+	`
+
+	o := testGoReflectMethod_O{
+		field: "test",
+	}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(asciiString("42")) {
+		t.Fatalf("Expected '42', got %v", v)
+	}
+}
+
+func TestGoReflectProp(t *testing.T) {
+	const SCRIPT = `
+	var d1 = Object.getOwnPropertyDescriptor(o, "Get");
+	var d2 = Object.getOwnPropertyDescriptor(o, "Test");
+	!d1.writable && !d1.configurable && d2.writable && !d2.configurable;
+	`
+
+	o := testGoReflectMethod_O{
+		field: "test",
+	}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+}
+
+func TestGoReflectRedefineFieldSuccess(t *testing.T) {
+	const SCRIPT = `
+	!!Object.defineProperty(o, "Test", {value: "AAA"});
+	`
+
+	o := testGoReflectMethod_O{}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+
+	if o.Test != "AAA" {
+		t.Fatalf("Expected 'AAA', got '%s'", o.Test)
+	}
+
+}
+
+func TestGoReflectRedefineFieldNonWritable(t *testing.T) {
+	const SCRIPT = `
+	var thrown = false;
+	try {
+		Object.defineProperty(o, "Test", {value: "AAA", writable: false});
+	} catch (e) {
+		if (e instanceof TypeError) {
+			thrown = true;
+		} else {
+			throw e;
+		}
+	}
+	thrown;
+	`
+
+	o := testGoReflectMethod_O{Test: "Test"}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+
+	if o.Test != "Test" {
+		t.Fatalf("Expected 'Test', got: '%s'", o.Test)
+	}
+}
+
+func TestGoReflectRedefineFieldConfigurable(t *testing.T) {
+	const SCRIPT = `
+	var thrown = false;
+	try {
+		Object.defineProperty(o, "Test", {value: "AAA", configurable: true});
+	} catch (e) {
+		if (e instanceof TypeError) {
+			thrown = true;
+		} else {
+			throw e;
+		}
+	}
+	thrown;
+	`
+
+	o := testGoReflectMethod_O{Test: "Test"}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+
+	if o.Test != "Test" {
+		t.Fatalf("Expected 'Test', got: '%s'", o.Test)
+	}
+}
+
+func TestGoReflectRedefineMethod(t *testing.T) {
+	const SCRIPT = `
+	var thrown = false;
+	try {
+		Object.defineProperty(o, "Method", {value: "AAA", configurable: true});
+	} catch (e) {
+		if (e instanceof TypeError) {
+			thrown = true;
+		} else {
+			throw e;
+		}
+	}
+	thrown;
+	`
+
+	o := testGoReflectMethod_O{Test: "Test"}
+
+	r := New()
+	r.Set("o", &o)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.StrictEquals(valueTrue) {
+		t.Fatalf("Expected true, got %v", v)
+	}
+}

+ 303 - 0
object_goslice.go

@@ -0,0 +1,303 @@
+package goja
+
+import (
+	"reflect"
+	"strconv"
+)
+
+type objectGoSlice struct {
+	baseObject
+	data            *[]interface{}
+	lengthProp      valueProperty
+	sliceExtensible bool
+}
+
+func (o *objectGoSlice) init() {
+	o.baseObject.init()
+	o.class = classArray
+	o.prototype = o.val.runtime.global.ArrayPrototype
+	o.lengthProp.writable = o.sliceExtensible
+	o._setLen()
+	o.baseObject._put("length", &o.lengthProp)
+}
+
+func (o *objectGoSlice) _setLen() {
+	o.lengthProp.value = intToValue(int64(len(*o.data)))
+}
+
+func (o *objectGoSlice) getIdx(idx int) Value {
+	if idx < len(*o.data) {
+		return o.val.runtime.ToValue((*o.data)[idx])
+	}
+	return nil
+}
+
+func (o *objectGoSlice) _get(n Value) Value {
+	if idx := toIdx(n); idx >= 0 {
+		return o.getIdx(idx)
+	}
+	return nil
+}
+
+func (o *objectGoSlice) _getStr(name string) Value {
+	if idx := strToIdx(name); idx >= 0 {
+		return o.getIdx(idx)
+	}
+	return nil
+}
+
+func (o *objectGoSlice) get(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.baseObject.get(n)
+}
+
+func (o *objectGoSlice) getProp(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.baseObject.getProp(n)
+}
+
+func (o *objectGoSlice) getPropStr(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.baseObject.getPropStr(name)
+}
+
+func (o *objectGoSlice) getStr(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.baseObject.getStr(name)
+}
+
+func (o *objectGoSlice) getOwnProp(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return &valueProperty{
+			value:      v,
+			writable:   true,
+			enumerable: true,
+		}
+	}
+	return o.baseObject.getOwnProp(name)
+}
+
+func (o *objectGoSlice) grow(size int) {
+	newcap := cap(*o.data)
+	if newcap < size {
+		// Use the same algorithm as in runtime.growSlice
+		doublecap := newcap + newcap
+		if size > doublecap {
+			newcap = size
+		} else {
+			if len(*o.data) < 1024 {
+				newcap = doublecap
+			} else {
+				for newcap < size {
+					newcap += newcap / 4
+				}
+			}
+		}
+
+		n := make([]interface{}, size, newcap)
+		copy(n, *o.data)
+		*o.data = n
+	} else {
+		*o.data = (*o.data)[:size]
+	}
+	o._setLen()
+}
+
+func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
+	if idx >= len(*o.data) {
+		if !o.sliceExtensible {
+			o.val.runtime.typeErrorResult(throw, "Cannot extend Go slice")
+			return
+		}
+		o.grow(idx + 1)
+	}
+	(*o.data)[idx] = v.Export()
+}
+
+func (o *objectGoSlice) put(n Value, val Value, throw bool) {
+	if idx := toIdx(n); idx >= 0 {
+		o.putIdx(idx, val, throw)
+		return
+	}
+	// TODO: length
+	o.baseObject.put(n, val, throw)
+}
+
+func (o *objectGoSlice) putStr(name string, val Value, throw bool) {
+	if idx := strToIdx(name); idx >= 0 {
+		o.putIdx(idx, val, throw)
+		return
+	}
+	// TODO: length
+	o.baseObject.putStr(name, val, throw)
+}
+
+func (o *objectGoSlice) _has(n Value) bool {
+	if idx := toIdx(n); idx >= 0 {
+		return idx < len(*o.data)
+	}
+	return false
+}
+
+func (o *objectGoSlice) _hasStr(name string) bool {
+	if idx := strToIdx(name); idx >= 0 {
+		return idx < len(*o.data)
+	}
+	return false
+}
+
+func (o *objectGoSlice) hasProperty(n Value) bool {
+	if o._has(n) {
+		return true
+	}
+	return o.baseObject.hasProperty(n)
+}
+
+func (o *objectGoSlice) hasPropertyStr(name string) bool {
+	if o._hasStr(name) {
+		return true
+	}
+	return o.baseObject.hasPropertyStr(name)
+}
+
+func (o *objectGoSlice) hasOwnProperty(n Value) bool {
+	if o._has(n) {
+		return true
+	}
+	return o.baseObject.hasOwnProperty(n)
+}
+
+func (o *objectGoSlice) hasOwnPropertyStr(name string) bool {
+	if o._hasStr(name) {
+		return true
+	}
+	return o.baseObject.hasOwnPropertyStr(name)
+}
+
+func (o *objectGoSlice) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	o.putStr(name, value, false)
+	return value
+}
+
+func (o *objectGoSlice) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	if idx := toIdx(n); idx >= 0 {
+		if !o.val.runtime.checkHostObjectPropertyDescr(n.String(), descr, throw) {
+			return false
+		}
+		val := descr.getStr("value")
+		if val == nil {
+			val = _undefined
+		}
+		o.putIdx(idx, val, throw)
+		return true
+	}
+	return o.baseObject.defineOwnProperty(n, descr, throw)
+}
+
+func (o *objectGoSlice) toPrimitiveNumber() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoSlice) toPrimitiveString() Value {
+	return o.val.runtime.arrayproto_join(FunctionCall{
+		This: o.val,
+	})
+}
+
+func (o *objectGoSlice) toPrimitive() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoSlice) deleteStr(name string, throw bool) bool {
+	if idx := strToIdx(name); idx >= 0 && idx < len(*o.data) {
+		(*o.data)[idx] = nil
+		return true
+	}
+	return o.baseObject.deleteStr(name, throw)
+}
+
+func (o *objectGoSlice) delete(name Value, throw bool) bool {
+	if idx := toIdx(name); idx >= 0 && idx < len(*o.data) {
+		(*o.data)[idx] = nil
+		return true
+	}
+	return o.baseObject.delete(name, throw)
+}
+
+type goslicePropIter struct {
+	o          *objectGoSlice
+	recursive  bool
+	idx, limit int
+}
+
+func (i *goslicePropIter) next() (propIterItem, iterNextFunc) {
+	if i.idx < i.limit && i.idx < len(*i.o.data) {
+		name := strconv.Itoa(i.idx)
+		i.idx++
+		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+	}
+
+	if i.recursive {
+		return i.o.prototype.self._enumerate(i.recursive)()
+	}
+
+	return propIterItem{}, nil
+}
+
+func (o *objectGoSlice) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: o._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+
+}
+
+func (o *objectGoSlice) _enumerate(recursive bool) iterNextFunc {
+	return (&goslicePropIter{
+		o:         o,
+		recursive: recursive,
+		limit:     len(*o.data),
+	}).next
+}
+
+func (o *objectGoSlice) export() interface{} {
+	return *o.data
+}
+
+func (o *objectGoSlice) exportType() reflect.Type {
+	return reflectTypeArray
+}
+
+func (o *objectGoSlice) equal(other objectImpl) bool {
+	if other, ok := other.(*objectGoSlice); ok {
+		return o.data == other.data
+	}
+	return false
+}
+
+func (o *objectGoSlice) sortLen() int {
+	return len(*o.data)
+}
+
+func (o *objectGoSlice) sortGet(i int) Value {
+	return o.getStr(strconv.Itoa(i))
+}
+
+func (o *objectGoSlice) swap(i, j int) {
+	ii := strconv.Itoa(i)
+	jj := strconv.Itoa(j)
+	x := o.getStr(ii)
+	y := o.getStr(jj)
+
+	o.putStr(ii, y, false)
+	o.putStr(jj, x, false)
+}

+ 251 - 0
object_goslice_reflect.go

@@ -0,0 +1,251 @@
+package goja
+
+import (
+	"reflect"
+	"strconv"
+)
+
+type objectGoSliceReflect struct {
+	objectGoReflect
+	lengthProp valueProperty
+}
+
+func (o *objectGoSliceReflect) init() {
+	o.baseObject.init()
+	o.class = classArray
+	o.prototype = o.val.runtime.global.ArrayPrototype
+	o.lengthProp.writable = false
+	o._setLen()
+	o.baseObject._put("length", &o.lengthProp)
+}
+
+func (o *objectGoSliceReflect) _setLen() {
+	o.lengthProp.value = intToValue(int64(o.value.Len()))
+}
+
+func (o *objectGoSliceReflect) _has(n Value) bool {
+	if idx := toIdx(n); idx >= 0 {
+		return idx < o.value.Len()
+	}
+	return false
+}
+
+func (o *objectGoSliceReflect) _hasStr(name string) bool {
+	if idx := strToIdx(name); idx >= 0 {
+		return idx < o.value.Len()
+	}
+	return false
+}
+
+func (o *objectGoSliceReflect) getIdx(idx int) Value {
+	if idx < o.value.Len() {
+		return o.val.runtime.ToValue(o.value.Index(idx).Interface())
+	}
+	return nil
+}
+
+func (o *objectGoSliceReflect) _get(n Value) Value {
+	if idx := toIdx(n); idx >= 0 {
+		return o.getIdx(idx)
+	}
+	return nil
+}
+
+func (o *objectGoSliceReflect) _getStr(name string) Value {
+	if idx := strToIdx(name); idx >= 0 {
+		return o.getIdx(idx)
+	}
+	return nil
+}
+
+func (o *objectGoSliceReflect) get(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.objectGoReflect.get(n)
+}
+
+func (o *objectGoSliceReflect) getProp(n Value) Value {
+	if v := o._get(n); v != nil {
+		return v
+	}
+	return o.objectGoReflect.getProp(n)
+}
+
+func (o *objectGoSliceReflect) getPropStr(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.objectGoReflect.getPropStr(name)
+}
+
+func (o *objectGoSliceReflect) getOwnProp(name string) Value {
+	if v := o._getStr(name); v != nil {
+		return v
+	}
+	return o.objectGoReflect.getOwnProp(name)
+}
+
+func (o *objectGoSliceReflect) putIdx(idx int, v Value, throw bool) {
+	if idx >= o.value.Len() {
+		o.val.runtime.typeErrorResult(throw, "Cannot extend a Go reflect slice")
+		return
+	}
+	val, err := o.val.runtime.toReflectValue(v, o.value.Type().Elem())
+	if err != nil {
+		o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err)
+		return
+	}
+	o.value.Index(idx).Set(val)
+}
+
+func (o *objectGoSliceReflect) put(n Value, val Value, throw bool) {
+	if idx := toIdx(n); idx >= 0 {
+		o.putIdx(idx, val, throw)
+		return
+	}
+	// TODO: length
+	o.objectGoReflect.put(n, val, throw)
+}
+
+func (o *objectGoSliceReflect) putStr(name string, val Value, throw bool) {
+	if idx := strToIdx(name); idx >= 0 {
+		o.putIdx(idx, val, throw)
+		return
+	}
+	// TODO: length
+	o.objectGoReflect.putStr(name, val, throw)
+}
+
+func (o *objectGoSliceReflect) hasProperty(n Value) bool {
+	if o._has(n) {
+		return true
+	}
+	return o.objectGoReflect.hasProperty(n)
+}
+
+func (o *objectGoSliceReflect) hasPropertyStr(name string) bool {
+	if o._hasStr(name) {
+		return true
+	}
+	return o.objectGoReflect.hasOwnPropertyStr(name)
+}
+
+func (o *objectGoSliceReflect) hasOwnProperty(n Value) bool {
+	if o._has(n) {
+		return true
+	}
+	return o.objectGoReflect.hasOwnProperty(n)
+}
+
+func (o *objectGoSliceReflect) hasOwnPropertyStr(name string) bool {
+	if o._hasStr(name) {
+		return true
+	}
+	return o.objectGoReflect.hasOwnPropertyStr(name)
+}
+
+func (o *objectGoSliceReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	o.putStr(name, value, false)
+	return value
+}
+
+func (o *objectGoSliceReflect) defineOwnProperty(name Value, descr objectImpl, throw bool) bool {
+	if descr.hasPropertyStr("get") || descr.hasPropertyStr("set") {
+		o.val.runtime.typeErrorResult(throw, "Host objects do not support accessor properties")
+		return false
+	}
+	o.put(name, descr.getStr("value"), throw)
+	return true
+}
+
+func (o *objectGoSliceReflect) toPrimitiveNumber() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoSliceReflect) toPrimitiveString() Value {
+	return o.val.runtime.arrayproto_join(FunctionCall{
+		This: o.val,
+	})
+}
+
+func (o *objectGoSliceReflect) toPrimitive() Value {
+	return o.toPrimitiveString()
+}
+
+func (o *objectGoSliceReflect) deleteStr(name string, throw bool) bool {
+	if idx := strToIdx(name); idx >= 0 && idx < o.value.Len() {
+		o.value.Index(idx).Set(reflect.Zero(o.value.Type().Elem()))
+		return true
+	}
+	return o.objectGoReflect.deleteStr(name, throw)
+}
+
+func (o *objectGoSliceReflect) delete(name Value, throw bool) bool {
+	if idx := toIdx(name); idx >= 0 && idx < o.value.Len() {
+		o.value.Index(idx).Set(reflect.Zero(o.value.Type().Elem()))
+		return true
+	}
+	return true
+}
+
+type gosliceReflectPropIter struct {
+	o          *objectGoSliceReflect
+	recursive  bool
+	idx, limit int
+}
+
+func (i *gosliceReflectPropIter) next() (propIterItem, iterNextFunc) {
+	if i.idx < i.limit && i.idx < i.o.value.Len() {
+		name := strconv.Itoa(i.idx)
+		i.idx++
+		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+	}
+
+	if i.recursive {
+		return i.o.prototype.self._enumerate(i.recursive)()
+	}
+
+	return propIterItem{}, nil
+}
+
+func (o *objectGoSliceReflect) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: o._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (o *objectGoSliceReflect) _enumerate(recursive bool) iterNextFunc {
+	return (&gosliceReflectPropIter{
+		o:         o,
+		recursive: recursive,
+		limit:     o.value.Len(),
+	}).next
+}
+
+func (o *objectGoSliceReflect) equal(other objectImpl) bool {
+	if other, ok := other.(*objectGoSliceReflect); ok {
+		return o.value.Interface() == other.value.Interface()
+	}
+	return false
+}
+
+func (o *objectGoSliceReflect) sortLen() int {
+	return o.value.Len()
+}
+
+func (o *objectGoSliceReflect) sortGet(i int) Value {
+	return o.getStr(strconv.Itoa(i))
+}
+
+func (o *objectGoSliceReflect) swap(i, j int) {
+	ii := strconv.Itoa(i)
+	jj := strconv.Itoa(j)
+	x := o.getStr(ii)
+	y := o.getStr(jj)
+
+	o.putStr(ii, y, false)
+	o.putStr(jj, x, false)
+}

+ 89 - 0
object_goslice_reflect_test.go

@@ -0,0 +1,89 @@
+package goja
+
+import "testing"
+
+func TestGoSliceReflectBasic(t *testing.T) {
+	const SCRIPT = `
+	var sum = 0;
+	for (var i = 0; i < a.length; i++) {
+		sum += a[i];
+	}
+	sum;
+	`
+	r := New()
+	r.Set("a", []int{1, 2, 3, 4})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i := v.ToInteger(); i != 10 {
+		t.Fatalf("Expected 10, got: %d", i)
+	}
+
+}
+
+func TestGoSliceReflectIn(t *testing.T) {
+	const SCRIPT = `
+	var idx = "";
+	for (var i in a) {
+		idx += i;
+	}
+	idx;
+	`
+	r := New()
+	r.Set("a", []int{1, 2, 3, 4})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i := v.String(); i != "0123" {
+		t.Fatalf("Expected '0123', got: '%s'", i)
+	}
+}
+
+func TestGoSliceReflectSet(t *testing.T) {
+	const SCRIPT = `
+	a[0] = 33;
+	a[1] = 333;
+	a[2] = "42";
+	a[3] = {};
+	a[4] = 0;
+	`
+	r := New()
+	a := []int8{1, 2, 3, 4}
+	r.Set("a", a)
+	_, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if a[0] != 33 {
+		t.Fatalf("a[0] = %d, expected 33", a[0])
+	}
+	if a[1] != 77 {
+		t.Fatalf("a[1] = %d, expected 77", a[0])
+	}
+	if a[2] != 42 {
+		t.Fatalf("a[2] = %d, expected 42", a[0])
+	}
+	if a[3] != 0 {
+		t.Fatalf("a[3] = %d, expected 0", a[0])
+	}
+}
+
+func TestGoSliceReflectProto(t *testing.T) {
+	const SCRIPT = `
+	a.join(",")
+	`
+
+	r := New()
+	a := []int8{1, 2, 3, 4}
+	r.Set("a", a)
+	ret, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if s := ret.String(); s != "1,2,3,4" {
+		t.Fatalf("Unexpected result: '%s'", s)
+	}
+}

+ 87 - 0
object_goslice_test.go

@@ -0,0 +1,87 @@
+package goja
+
+import "testing"
+
+func TestGoSliceBasic(t *testing.T) {
+	const SCRIPT = `
+	var sum = 0;
+	for (var i = 0; i < a.length; i++) {
+		sum += a[i];
+	}
+	sum;
+	`
+	r := New()
+	r.Set("a", []interface{}{1, 2, 3, 4})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i := v.ToInteger(); i != 10 {
+		t.Fatalf("Expected 10, got: %d", i)
+	}
+}
+
+func TestGoSliceIn(t *testing.T) {
+	const SCRIPT = `
+	var idx = "";
+	for (var i in a) {
+		idx += i;
+	}
+	idx;
+	`
+	r := New()
+	r.Set("a", []interface{}{1, 2, 3, 4})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i := v.String(); i != "0123" {
+		t.Fatalf("Expected '0123', got: '%s'", i)
+	}
+}
+
+func TestGoSliceExpand(t *testing.T) {
+	const SCRIPT = `
+	var l = a.length;
+	for (var i = 0; i < l; i++) {
+		a[l + i] = a[i] * 2;
+	}
+
+	var sum = 0;
+	for (var i = 0; i < a.length; i++) {
+		sum += a[i];
+	}
+	sum;
+	`
+	r := New()
+	a := []interface{}{int64(1), int64(2), int64(3), int64(4)}
+	r.Set("a", &a)
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sum := int64(0)
+	for _, v := range a {
+		sum += v.(int64)
+	}
+	if i := v.ToInteger(); i != sum {
+		t.Fatalf("Expected %d, got: %d", sum, i)
+	}
+}
+
+func TestGoSliceProto(t *testing.T) {
+	const SCRIPT = `
+	a.join(",")
+	`
+
+	r := New()
+	a := []interface{}{1, 2, 3, 4}
+	r.Set("a", a)
+	ret, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if s := ret.String(); s != "1,2,3,4" {
+		t.Fatalf("Unexpected result: '%s'", s)
+	}
+}

+ 200 - 0
object_lazy.go

@@ -0,0 +1,200 @@
+package goja
+
+import "reflect"
+
+type lazyObject struct {
+	val    *Object
+	create func(*Object) objectImpl
+}
+
+func (o *lazyObject) className() string {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.className()
+}
+
+func (o *lazyObject) get(n Value) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.get(n)
+}
+
+func (o *lazyObject) getProp(n Value) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getProp(n)
+}
+
+func (o *lazyObject) getPropStr(name string) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getPropStr(name)
+}
+
+func (o *lazyObject) getStr(name string) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getStr(name)
+}
+
+func (o *lazyObject) getOwnProp(name string) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.getOwnProp(name)
+}
+
+func (o *lazyObject) put(n Value, val Value, throw bool) {
+	obj := o.create(o.val)
+	o.val.self = obj
+	obj.put(n, val, throw)
+}
+
+func (o *lazyObject) putStr(name string, val Value, throw bool) {
+	obj := o.create(o.val)
+	o.val.self = obj
+	obj.putStr(name, val, throw)
+}
+
+func (o *lazyObject) hasProperty(n Value) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasProperty(n)
+}
+
+func (o *lazyObject) hasPropertyStr(name string) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasPropertyStr(name)
+}
+
+func (o *lazyObject) hasOwnProperty(n Value) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasOwnProperty(n)
+}
+
+func (o *lazyObject) hasOwnPropertyStr(name string) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasOwnPropertyStr(name)
+}
+
+func (o *lazyObject) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj._putProp(name, value, writable, enumerable, configurable)
+}
+
+func (o *lazyObject) defineOwnProperty(name Value, descr objectImpl, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.defineOwnProperty(name, descr, throw)
+}
+
+func (o *lazyObject) toPrimitiveNumber() Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.toPrimitiveNumber()
+}
+
+func (o *lazyObject) toPrimitiveString() Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.toPrimitiveString()
+}
+
+func (o *lazyObject) toPrimitive() Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.toPrimitive()
+}
+
+func (o *lazyObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.assertCallable()
+}
+
+func (o *lazyObject) deleteStr(name string, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.deleteStr(name, throw)
+}
+
+func (o *lazyObject) delete(name Value, throw bool) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.delete(name, throw)
+}
+
+func (o *lazyObject) proto() *Object {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.proto()
+}
+
+func (o *lazyObject) hasInstance(v Value) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.hasInstance(v)
+}
+
+func (o *lazyObject) isExtensible() bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.isExtensible()
+}
+
+func (o *lazyObject) preventExtensions() {
+	obj := o.create(o.val)
+	o.val.self = obj
+	obj.preventExtensions()
+}
+
+func (o *lazyObject) enumerate(all, recusrive bool) iterNextFunc {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.enumerate(all, recusrive)
+}
+
+func (o *lazyObject) _enumerate(recursive bool) iterNextFunc {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj._enumerate(recursive)
+}
+
+func (o *lazyObject) export() interface{} {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.export()
+}
+
+func (o *lazyObject) exportType() reflect.Type {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.exportType()
+}
+
+func (o *lazyObject) equal(other objectImpl) bool {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.equal(other)
+}
+
+func (o *lazyObject) sortLen() int {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.sortLen()
+}
+
+func (o *lazyObject) sortGet(i int) Value {
+	obj := o.create(o.val)
+	o.val.self = obj
+	return obj.sortGet(i)
+}
+
+func (o *lazyObject) swap(i, j int) {
+	obj := o.create(o.val)
+	o.val.self = obj
+	obj.swap(i, j)
+}

+ 240 - 0
object_test.go

@@ -0,0 +1,240 @@
+package goja
+
+import "testing"
+
+func TestArray1(t *testing.T) {
+	r := &Runtime{}
+	a := r.newArray(nil)
+	a.put(valueInt(0), asciiString("test"), true)
+	if l := a.getStr("length").ToInteger(); l != 1 {
+		t.Fatalf("Unexpected length: %d", l)
+	}
+}
+
+func BenchmarkPut(b *testing.B) {
+	v := &Object{}
+
+	o := &baseObject{
+		val:        v,
+		extensible: true,
+	}
+	v.self = o
+
+	o.init()
+
+	var key Value = asciiString("test")
+	var val Value = valueInt(123)
+
+	for i := 0; i < b.N; i++ {
+		o.put(key, val, false)
+	}
+}
+
+func BenchmarkPutStr(b *testing.B) {
+	v := &Object{}
+
+	o := &baseObject{
+		val:        v,
+		extensible: true,
+	}
+
+	o.init()
+
+	v.self = o
+
+	var val Value = valueInt(123)
+
+	for i := 0; i < b.N; i++ {
+		o.putStr("test", val, false)
+	}
+}
+
+func BenchmarkGet(b *testing.B) {
+	v := &Object{}
+
+	o := &baseObject{
+		val:        v,
+		extensible: true,
+	}
+
+	o.init()
+
+	v.self = o
+	var n Value = asciiString("test")
+
+	for i := 0; i < b.N; i++ {
+		o.get(n)
+	}
+
+}
+
+func BenchmarkGetStr(b *testing.B) {
+	v := &Object{}
+
+	o := &baseObject{
+		val:        v,
+		extensible: true,
+	}
+	v.self = o
+
+	o.init()
+
+	for i := 0; i < b.N; i++ {
+		o.getStr("test")
+	}
+}
+
+func _toString(v Value) string {
+	switch v := v.(type) {
+	case asciiString:
+		return string(v)
+	default:
+		return ""
+	}
+}
+
+func BenchmarkToString1(b *testing.B) {
+	v := asciiString("test")
+
+	for i := 0; i < b.N; i++ {
+		v.ToString()
+	}
+}
+
+func BenchmarkToString2(b *testing.B) {
+	v := asciiString("test")
+
+	for i := 0; i < b.N; i++ {
+		_toString(v)
+	}
+}
+
+func BenchmarkConv(b *testing.B) {
+	count := int64(0)
+	for i := 0; i < b.N; i++ {
+		count += valueInt(123).ToInteger()
+	}
+	if count == 0 {
+		b.Fatal("zero")
+	}
+}
+
+func BenchmarkArrayGetStr(b *testing.B) {
+	b.StopTimer()
+	r := New()
+	v := &Object{runtime: r}
+
+	a := &arrayObject{
+		baseObject: baseObject{
+			val:        v,
+			extensible: true,
+		},
+	}
+	v.self = a
+
+	a.init()
+
+	a.put(valueInt(0), asciiString("test"), false)
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		a.getStr("0")
+	}
+
+}
+
+func BenchmarkArrayGet(b *testing.B) {
+	b.StopTimer()
+	r := New()
+	v := &Object{runtime: r}
+
+	a := &arrayObject{
+		baseObject: baseObject{
+			val:        v,
+			extensible: true,
+		},
+	}
+	v.self = a
+
+	a.init()
+
+	var idx Value = valueInt(0)
+
+	a.put(idx, asciiString("test"), false)
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		a.get(idx)
+	}
+
+}
+
+func BenchmarkArrayPut(b *testing.B) {
+	b.StopTimer()
+	r := New()
+
+	v := &Object{runtime: r}
+
+	a := &arrayObject{
+		baseObject: baseObject{
+			val:        v,
+			extensible: true,
+		},
+	}
+
+	v.self = a
+
+	a.init()
+
+	var idx Value = valueInt(0)
+	var val Value = asciiString("test")
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		a.put(idx, val, false)
+	}
+
+}
+
+func BenchmarkToUTF8String(b *testing.B) {
+	var s valueString = asciiString("test")
+	for i := 0; i < b.N; i++ {
+		_ = s.String()
+	}
+}
+
+func BenchmarkAdd(b *testing.B) {
+	var x, y Value
+	x = valueInt(2)
+	y = valueInt(2)
+
+	for i := 0; i < b.N; i++ {
+		if xi, ok := x.assertInt(); ok {
+			if yi, ok := y.assertInt(); ok {
+				x = valueInt(xi + yi)
+			}
+		}
+	}
+}
+
+func BenchmarkAddString(b *testing.B) {
+	var x, y Value
+
+	tst := asciiString("22")
+	x = asciiString("2")
+	y = asciiString("2")
+
+	for i := 0; i < b.N; i++ {
+		var z Value
+		if xi, ok := x.assertString(); ok {
+			if yi, ok := y.assertString(); ok {
+				z = xi.concat(yi)
+			}
+		}
+		if !z.StrictEquals(tst) {
+			b.Fatalf("Unexpected result %v", x)
+		}
+	}
+}

+ 184 - 0
parser/README.markdown

@@ -0,0 +1,184 @@
+# parser
+--
+    import "github.com/dop251/goja/parser"
+
+Package parser implements a parser for JavaScript. Borrowed from https://github.com/robertkrimen/otto/tree/master/parser
+
+    import (
+        "github.com/dop251/goja/parser"
+    )
+
+Parse and return an AST
+
+    filename := "" // A filename is optional
+    src := `
+        // Sample xyzzy example
+        (function(){
+            if (3.14159 > 0) {
+                console.log("Hello, World.");
+                return;
+            }
+
+            var xyzzy = NaN;
+            console.log("Nothing happens.");
+            return xyzzy;
+        })();
+    `
+
+    // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
+    program, err := parser.ParseFile(nil, filename, src, 0)
+
+
+### Warning
+
+The parser and AST interfaces are still works-in-progress (particularly where
+node types are concerned) and may change in the future.
+
+## Usage
+
+#### func  ParseFile
+
+```go
+func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
+```
+ParseFile parses the source code of a single JavaScript/ECMAScript source file
+and returns the corresponding ast.Program node.
+
+If fileSet == nil, ParseFile parses source without a FileSet. If fileSet != nil,
+ParseFile first adds filename and src to fileSet.
+
+The filename argument is optional and is used for labelling errors, etc.
+
+src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST
+always be in UTF-8.
+
+    // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
+    program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
+
+#### func  ParseFunction
+
+```go
+func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error)
+```
+ParseFunction parses a given parameter list and body as a function and returns
+the corresponding ast.FunctionLiteral node.
+
+The parameter list, if any, should be a comma-separated list of identifiers.
+
+#### func  ReadSource
+
+```go
+func ReadSource(filename string, src interface{}) ([]byte, error)
+```
+
+#### func  TransformRegExp
+
+```go
+func TransformRegExp(pattern string) (string, error)
+```
+TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
+
+re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
+backreference (\1, \2, ...) will cause an error.
+
+re2 (Go) has a different definition for \s: [\t\n\f\r ]. The JavaScript
+definition, on the other hand, also includes \v, Unicode "Separator, Space",
+etc.
+
+If the pattern is invalid (not valid even in JavaScript), then this function
+returns the empty string and an error.
+
+If the pattern is valid, but incompatible (contains a lookahead or
+backreference), then this function returns the transformation (a non-empty
+string) AND an error.
+
+#### type Error
+
+```go
+type Error struct {
+	Position file.Position
+	Message  string
+}
+```
+
+An Error represents a parsing error. It includes the position where the error
+occurred and a message/description.
+
+#### func (Error) Error
+
+```go
+func (self Error) Error() string
+```
+
+#### type ErrorList
+
+```go
+type ErrorList []*Error
+```
+
+ErrorList is a list of *Errors.
+
+#### func (*ErrorList) Add
+
+```go
+func (self *ErrorList) Add(position file.Position, msg string)
+```
+Add adds an Error with given position and message to an ErrorList.
+
+#### func (ErrorList) Err
+
+```go
+func (self ErrorList) Err() error
+```
+Err returns an error equivalent to this ErrorList. If the list is empty, Err
+returns nil.
+
+#### func (ErrorList) Error
+
+```go
+func (self ErrorList) Error() string
+```
+Error implements the Error interface.
+
+#### func (ErrorList) Len
+
+```go
+func (self ErrorList) Len() int
+```
+
+#### func (ErrorList) Less
+
+```go
+func (self ErrorList) Less(i, j int) bool
+```
+
+#### func (*ErrorList) Reset
+
+```go
+func (self *ErrorList) Reset()
+```
+Reset resets an ErrorList to no errors.
+
+#### func (ErrorList) Sort
+
+```go
+func (self ErrorList) Sort()
+```
+
+#### func (ErrorList) Swap
+
+```go
+func (self ErrorList) Swap(i, j int)
+```
+
+#### type Mode
+
+```go
+type Mode uint
+```
+
+A Mode value is a set of flags (or 0). They control optional parser
+functionality.
+
+--
+**godocdown** http://github.com/robertkrimen/godocdown

+ 175 - 0
parser/error.go

@@ -0,0 +1,175 @@
+package parser
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+)
+
+const (
+	err_UnexpectedToken      = "Unexpected token %v"
+	err_UnexpectedEndOfInput = "Unexpected end of input"
+	err_UnexpectedEscape     = "Unexpected escape"
+)
+
+//    UnexpectedNumber:  'Unexpected number',
+//    UnexpectedString:  'Unexpected string',
+//    UnexpectedIdentifier:  'Unexpected identifier',
+//    UnexpectedReserved:  'Unexpected reserved word',
+//    NewlineAfterThrow:  'Illegal newline after throw',
+//    InvalidRegExp: 'Invalid regular expression',
+//    UnterminatedRegExp:  'Invalid regular expression: missing /',
+//    InvalidLHSInAssignment:  'Invalid left-hand side in assignment',
+//    InvalidLHSInForIn:  'Invalid left-hand side in for-in',
+//    MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
+//    NoCatchOrFinally:  'Missing catch or finally after try',
+//    UnknownLabel: 'Undefined label \'%0\'',
+//    Redeclaration: '%0 \'%1\' has already been declared',
+//    IllegalContinue: 'Illegal continue statement',
+//    IllegalBreak: 'Illegal break statement',
+//    IllegalReturn: 'Illegal return statement',
+//    StrictModeWith:  'Strict mode code may not include a with statement',
+//    StrictCatchVariable:  'Catch variable may not be eval or arguments in strict mode',
+//    StrictVarName:  'Variable name may not be eval or arguments in strict mode',
+//    StrictParamName:  'Parameter name eval or arguments is not allowed in strict mode',
+//    StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
+//    StrictFunctionName:  'Function name may not be eval or arguments in strict mode',
+//    StrictOctalLiteral:  'Octal literals are not allowed in strict mode.',
+//    StrictDelete:  'Delete of an unqualified identifier in strict mode.',
+//    StrictDuplicateProperty:  'Duplicate data property in object literal not allowed in strict mode',
+//    AccessorDataProperty:  'Object literal may not have data and accessor property with the same name',
+//    AccessorGetSet:  'Object literal may not have multiple get/set accessors with the same name',
+//    StrictLHSAssignment:  'Assignment to eval or arguments is not allowed in strict mode',
+//    StrictLHSPostfix:  'Postfix increment/decrement may not have eval or arguments operand in strict mode',
+//    StrictLHSPrefix:  'Prefix increment/decrement may not have eval or arguments operand in strict mode',
+//    StrictReservedWord:  'Use of future reserved word in strict mode'
+
+// A SyntaxError is a description of an ECMAScript syntax error.
+
+// An Error represents a parsing error. It includes the position where the error occurred and a message/description.
+type Error struct {
+	Position file.Position
+	Message  string
+}
+
+// FIXME Should this be "SyntaxError"?
+
+func (self Error) Error() string {
+	filename := self.Position.Filename
+	if filename == "" {
+		filename = "(anonymous)"
+	}
+	return fmt.Sprintf("%s: Line %d:%d %s",
+		filename,
+		self.Position.Line,
+		self.Position.Column,
+		self.Message,
+	)
+}
+
+func (self *_parser) error(place interface{}, msg string, msgValues ...interface{}) *Error {
+	idx := file.Idx(0)
+	switch place := place.(type) {
+	case int:
+		idx = self.idxOf(place)
+	case file.Idx:
+		if place == 0 {
+			idx = self.idxOf(self.chrOffset)
+		} else {
+			idx = place
+		}
+	default:
+		panic(fmt.Errorf("error(%T, ...)", place))
+	}
+
+	position := self.position(idx)
+	msg = fmt.Sprintf(msg, msgValues...)
+	self.errors.Add(position, msg)
+	return self.errors[len(self.errors)-1]
+}
+
+func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error {
+	if chr == -1 {
+		return self.error(idx, err_UnexpectedEndOfInput)
+	}
+	return self.error(idx, err_UnexpectedToken, token.ILLEGAL)
+}
+
+func (self *_parser) errorUnexpectedToken(tkn token.Token) error {
+	switch tkn {
+	case token.EOF:
+		return self.error(file.Idx(0), err_UnexpectedEndOfInput)
+	}
+	value := tkn.String()
+	switch tkn {
+	case token.BOOLEAN, token.NULL:
+		value = self.literal
+	case token.IDENTIFIER:
+		return self.error(self.idx, "Unexpected identifier")
+	case token.KEYWORD:
+		// TODO Might be a future reserved word
+		return self.error(self.idx, "Unexpected reserved word")
+	case token.NUMBER:
+		return self.error(self.idx, "Unexpected number")
+	case token.STRING:
+		return self.error(self.idx, "Unexpected string")
+	}
+	return self.error(self.idx, err_UnexpectedToken, value)
+}
+
+// ErrorList is a list of *Errors.
+//
+type ErrorList []*Error
+
+// Add adds an Error with given position and message to an ErrorList.
+func (self *ErrorList) Add(position file.Position, msg string) {
+	*self = append(*self, &Error{position, msg})
+}
+
+// Reset resets an ErrorList to no errors.
+func (self *ErrorList) Reset() { *self = (*self)[0:0] }
+
+func (self ErrorList) Len() int      { return len(self) }
+func (self ErrorList) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
+func (self ErrorList) Less(i, j int) bool {
+	x := &self[i].Position
+	y := &self[j].Position
+	if x.Filename < y.Filename {
+		return true
+	}
+	if x.Filename == y.Filename {
+		if x.Line < y.Line {
+			return true
+		}
+		if x.Line == y.Line {
+			return x.Column < y.Column
+		}
+	}
+	return false
+}
+
+func (self ErrorList) Sort() {
+	sort.Sort(self)
+}
+
+// Error implements the Error interface.
+func (self ErrorList) Error() string {
+	switch len(self) {
+	case 0:
+		return "no errors"
+	case 1:
+		return self[0].Error()
+	}
+	return fmt.Sprintf("%s (and %d more errors)", self[0].Error(), len(self)-1)
+}
+
+// Err returns an error equivalent to this ErrorList.
+// If the list is empty, Err returns nil.
+func (self ErrorList) Err() error {
+	if len(self) == 0 {
+		return nil
+	}
+	return self
+}

+ 797 - 0
parser/expression.go

@@ -0,0 +1,797 @@
+package parser
+
+import (
+
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+)
+
+func (self *_parser) parseIdentifier() *ast.Identifier {
+	literal := self.literal
+	idx := self.idx
+	self.next()
+	return &ast.Identifier{
+		Name: literal,
+		Idx:  idx,
+	}
+}
+
+func (self *_parser) parsePrimaryExpression() ast.Expression {
+	literal := self.literal
+	idx := self.idx
+	switch self.token {
+	case token.IDENTIFIER:
+		self.next()
+		if len(literal) > 1 {
+			tkn, strict := token.IsKeyword(literal)
+			if tkn == token.KEYWORD {
+				if !strict {
+					self.error(idx, "Unexpected reserved word")
+				}
+			}
+		}
+		return &ast.Identifier{
+			Name: literal,
+			Idx:  idx,
+		}
+	case token.NULL:
+		self.next()
+		return &ast.NullLiteral{
+			Idx:     idx,
+			Literal: literal,
+		}
+	case token.BOOLEAN:
+		self.next()
+		value := false
+		switch literal {
+		case "true":
+			value = true
+		case "false":
+			value = false
+		default:
+			self.error(idx, "Illegal boolean literal")
+		}
+		return &ast.BooleanLiteral{
+			Idx:     idx,
+			Literal: literal,
+			Value:   value,
+		}
+	case token.STRING:
+		self.next()
+		value, err := parseStringLiteral(literal[1 : len(literal)-1])
+		if err != nil {
+			self.error(idx, err.Error())
+		}
+		return &ast.StringLiteral{
+			Idx:     idx,
+			Literal: literal,
+			Value:   value,
+		}
+	case token.NUMBER:
+		self.next()
+		value, err := parseNumberLiteral(literal)
+		if err != nil {
+			self.error(idx, err.Error())
+			value = 0
+		}
+		return &ast.NumberLiteral{
+			Idx:     idx,
+			Literal: literal,
+			Value:   value,
+		}
+	case token.SLASH, token.QUOTIENT_ASSIGN:
+		return self.parseRegExpLiteral()
+	case token.LEFT_BRACE:
+		return self.parseObjectLiteral()
+	case token.LEFT_BRACKET:
+		return self.parseArrayLiteral()
+	case token.LEFT_PARENTHESIS:
+		self.expect(token.LEFT_PARENTHESIS)
+		expression := self.parseExpression()
+		self.expect(token.RIGHT_PARENTHESIS)
+		return expression
+	case token.THIS:
+		self.next()
+		return &ast.ThisExpression{
+			Idx: idx,
+		}
+	case token.FUNCTION:
+		return self.parseFunction(false)
+	}
+
+	self.errorUnexpectedToken(self.token)
+	self.nextStatement()
+	return &ast.BadExpression{From: idx, To: self.idx}
+}
+
+func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral {
+
+	offset := self.chrOffset - 1 // Opening slash already gotten
+	if self.token == token.QUOTIENT_ASSIGN {
+		offset -= 1 // =
+	}
+	idx := self.idxOf(offset)
+
+	pattern, err := self.scanString(offset)
+	endOffset := self.chrOffset
+
+	if err == nil {
+		pattern = pattern[1 : len(pattern)-1]
+	}
+
+	flags := ""
+	if !isLineTerminator(self.chr) && !isLineWhiteSpace(self.chr) {
+		self.next()
+
+		if self.token == token.IDENTIFIER { // gim
+
+			flags = self.literal
+			self.next()
+			endOffset = self.chrOffset - 1
+		}
+	} else {
+		self.next()
+	}
+
+	literal := self.str[offset:endOffset]
+
+	return &ast.RegExpLiteral{
+		Idx:     idx,
+		Literal: literal,
+		Pattern: pattern,
+		Flags:   flags,
+	}
+}
+
+func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.VariableExpression) ast.Expression {
+
+	if self.token != token.IDENTIFIER {
+		idx := self.expect(token.IDENTIFIER)
+		self.nextStatement()
+		return &ast.BadExpression{From: idx, To: self.idx}
+	}
+
+	literal := self.literal
+	idx := self.idx
+	self.next()
+	node := &ast.VariableExpression{
+		Name: literal,
+		Idx:  idx,
+	}
+
+	if declarationList != nil {
+		*declarationList = append(*declarationList, node)
+	}
+
+	if self.token == token.ASSIGN {
+		self.next()
+		node.Initializer = self.parseAssignmentExpression()
+	}
+
+	return node
+}
+
+func (self *_parser) parseVariableDeclarationList(var_ file.Idx) []ast.Expression {
+
+	var declarationList []*ast.VariableExpression // Avoid bad expressions
+	var list []ast.Expression
+
+	for {
+		list = append(list, self.parseVariableDeclaration(&declarationList))
+		if self.token != token.COMMA {
+			break
+		}
+		self.next()
+	}
+
+	self.scope.declare(&ast.VariableDeclaration{
+		Var:  var_,
+		List: declarationList,
+	})
+
+	return list
+}
+
+func (self *_parser) parseObjectPropertyKey() (string, string) {
+	idx, tkn, literal := self.idx, self.token, self.literal
+	value := ""
+	self.next()
+	switch tkn {
+	case token.IDENTIFIER:
+		value = literal
+	case token.NUMBER:
+		var err error
+		_, err = parseNumberLiteral(literal)
+		if err != nil {
+			self.error(idx, err.Error())
+		} else {
+			value = literal
+		}
+	case token.STRING:
+		var err error
+		value, err = parseStringLiteral(literal[1 : len(literal)-1])
+		if err != nil {
+			self.error(idx, err.Error())
+		}
+	default:
+		// null, false, class, etc.
+		if matchIdentifier.MatchString(literal) {
+			value = literal
+		}
+	}
+	return literal, value
+}
+
+func (self *_parser) parseObjectProperty() ast.Property {
+
+	literal, value := self.parseObjectPropertyKey()
+	if literal == "get" && self.token != token.COLON {
+		idx := self.idx
+		_, value := self.parseObjectPropertyKey()
+		parameterList := self.parseFunctionParameterList()
+
+		node := &ast.FunctionLiteral{
+			Function:      idx,
+			ParameterList: parameterList,
+		}
+		self.parseFunctionBlock(node)
+		return ast.Property{
+			Key:   value,
+			Kind:  "get",
+			Value: node,
+		}
+	} else if literal == "set" && self.token != token.COLON {
+		idx := self.idx
+		_, value := self.parseObjectPropertyKey()
+		parameterList := self.parseFunctionParameterList()
+
+		node := &ast.FunctionLiteral{
+			Function:      idx,
+			ParameterList: parameterList,
+		}
+		self.parseFunctionBlock(node)
+		return ast.Property{
+			Key:   value,
+			Kind:  "set",
+			Value: node,
+		}
+	}
+
+	self.expect(token.COLON)
+
+	return ast.Property{
+		Key:   value,
+		Kind:  "value",
+		Value: self.parseAssignmentExpression(),
+	}
+}
+
+func (self *_parser) parseObjectLiteral() ast.Expression {
+	var value []ast.Property
+	idx0 := self.expect(token.LEFT_BRACE)
+	for self.token != token.RIGHT_BRACE && self.token != token.EOF {
+		property := self.parseObjectProperty()
+		value = append(value, property)
+		if self.token == token.COMMA {
+			self.next()
+			continue
+		}
+	}
+	idx1 := self.expect(token.RIGHT_BRACE)
+
+	return &ast.ObjectLiteral{
+		LeftBrace:  idx0,
+		RightBrace: idx1,
+		Value:      value,
+	}
+}
+
+func (self *_parser) parseArrayLiteral() ast.Expression {
+
+	idx0 := self.expect(token.LEFT_BRACKET)
+	var value []ast.Expression
+	for self.token != token.RIGHT_BRACKET && self.token != token.EOF {
+		if self.token == token.COMMA {
+			self.next()
+			value = append(value, nil)
+			continue
+		}
+		value = append(value, self.parseAssignmentExpression())
+		if self.token != token.RIGHT_BRACKET {
+			self.expect(token.COMMA)
+		}
+	}
+	idx1 := self.expect(token.RIGHT_BRACKET)
+
+	return &ast.ArrayLiteral{
+		LeftBracket:  idx0,
+		RightBracket: idx1,
+		Value:        value,
+	}
+}
+
+func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, idx1 file.Idx) {
+	idx0 = self.expect(token.LEFT_PARENTHESIS)
+	if self.token != token.RIGHT_PARENTHESIS {
+		for {
+			argumentList = append(argumentList, self.parseAssignmentExpression())
+			if self.token != token.COMMA {
+				break
+			}
+			self.next()
+		}
+	}
+	idx1 = self.expect(token.RIGHT_PARENTHESIS)
+	return
+}
+
+func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression {
+	argumentList, idx0, idx1 := self.parseArgumentList()
+	return &ast.CallExpression{
+		Callee:           left,
+		LeftParenthesis:  idx0,
+		ArgumentList:     argumentList,
+		RightParenthesis: idx1,
+	}
+}
+
+func (self *_parser) parseDotMember(left ast.Expression) ast.Expression {
+	period := self.expect(token.PERIOD)
+
+	literal := self.literal
+	idx := self.idx
+
+	if !matchIdentifier.MatchString(literal) {
+		self.expect(token.IDENTIFIER)
+		self.nextStatement()
+		return &ast.BadExpression{From: period, To: self.idx}
+	}
+
+	self.next()
+
+	return &ast.DotExpression{
+		Left: left,
+		Identifier: ast.Identifier{
+			Idx:  idx,
+			Name: literal,
+		},
+	}
+}
+
+func (self *_parser) parseBracketMember(left ast.Expression) ast.Expression {
+	idx0 := self.expect(token.LEFT_BRACKET)
+	member := self.parseExpression()
+	idx1 := self.expect(token.RIGHT_BRACKET)
+	return &ast.BracketExpression{
+		LeftBracket:  idx0,
+		Left:         left,
+		Member:       member,
+		RightBracket: idx1,
+	}
+}
+
+func (self *_parser) parseNewExpression() ast.Expression {
+	idx := self.expect(token.NEW)
+	callee := self.parseLeftHandSideExpression()
+	node := &ast.NewExpression{
+		New:    idx,
+		Callee: callee,
+	}
+	if self.token == token.LEFT_PARENTHESIS {
+		argumentList, idx0, idx1 := self.parseArgumentList()
+		node.ArgumentList = argumentList
+		node.LeftParenthesis = idx0
+		node.RightParenthesis = idx1
+	}
+	return node
+}
+
+func (self *_parser) parseLeftHandSideExpression() ast.Expression {
+
+	var left ast.Expression
+	if self.token == token.NEW {
+		left = self.parseNewExpression()
+	} else {
+		left = self.parsePrimaryExpression()
+	}
+
+	for {
+		if self.token == token.PERIOD {
+			left = self.parseDotMember(left)
+		} else if self.token == token.LEFT_BRACE {
+			left = self.parseBracketMember(left)
+		} else {
+			break
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseLeftHandSideExpressionAllowCall() ast.Expression {
+
+	allowIn := self.scope.allowIn
+	self.scope.allowIn = true
+	defer func() {
+		self.scope.allowIn = allowIn
+	}()
+
+	var left ast.Expression
+	if self.token == token.NEW {
+		left = self.parseNewExpression()
+	} else {
+		left = self.parsePrimaryExpression()
+	}
+
+	for {
+		if self.token == token.PERIOD {
+			left = self.parseDotMember(left)
+		} else if self.token == token.LEFT_BRACKET {
+			left = self.parseBracketMember(left)
+		} else if self.token == token.LEFT_PARENTHESIS {
+			left = self.parseCallExpression(left)
+		} else {
+			break
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parsePostfixExpression() ast.Expression {
+	operand := self.parseLeftHandSideExpressionAllowCall()
+
+	switch self.token {
+	case token.INCREMENT, token.DECREMENT:
+		// Make sure there is no line terminator here
+		if self.implicitSemicolon {
+			break
+		}
+		tkn := self.token
+		idx := self.idx
+		self.next()
+		switch operand.(type) {
+		case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
+		default:
+			self.error(idx, "Invalid left-hand side in assignment")
+			self.nextStatement()
+			return &ast.BadExpression{From: idx, To: self.idx}
+		}
+		return &ast.UnaryExpression{
+			Operator: tkn,
+			Idx:      idx,
+			Operand:  operand,
+			Postfix:  true,
+		}
+	}
+
+	return operand
+}
+
+func (self *_parser) parseUnaryExpression() ast.Expression {
+
+	switch self.token {
+	case token.PLUS, token.MINUS, token.NOT, token.BITWISE_NOT:
+		fallthrough
+	case token.DELETE, token.VOID, token.TYPEOF:
+		tkn := self.token
+		idx := self.idx
+		self.next()
+		return &ast.UnaryExpression{
+			Operator: tkn,
+			Idx:      idx,
+			Operand:  self.parseUnaryExpression(),
+		}
+	case token.INCREMENT, token.DECREMENT:
+		tkn := self.token
+		idx := self.idx
+		self.next()
+		operand := self.parseUnaryExpression()
+		switch operand.(type) {
+		case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
+		default:
+			self.error(idx, "Invalid left-hand side in assignment")
+			self.nextStatement()
+			return &ast.BadExpression{From: idx, To: self.idx}
+		}
+		return &ast.UnaryExpression{
+			Operator: tkn,
+			Idx:      idx,
+			Operand:  operand,
+		}
+	}
+
+	return self.parsePostfixExpression()
+}
+
+func (self *_parser) parseMultiplicativeExpression() ast.Expression {
+	next := self.parseUnaryExpression
+	left := next()
+
+	for self.token == token.MULTIPLY || self.token == token.SLASH ||
+		self.token == token.REMAINDER {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseAdditiveExpression() ast.Expression {
+	next := self.parseMultiplicativeExpression
+	left := next()
+
+	for self.token == token.PLUS || self.token == token.MINUS {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseShiftExpression() ast.Expression {
+	next := self.parseAdditiveExpression
+	left := next()
+
+	for self.token == token.SHIFT_LEFT || self.token == token.SHIFT_RIGHT ||
+		self.token == token.UNSIGNED_SHIFT_RIGHT {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseRelationalExpression() ast.Expression {
+	next := self.parseShiftExpression
+	left := next()
+
+	allowIn := self.scope.allowIn
+	self.scope.allowIn = true
+	defer func() {
+		self.scope.allowIn = allowIn
+	}()
+
+	switch self.token {
+	case token.LESS, token.LESS_OR_EQUAL, token.GREATER, token.GREATER_OR_EQUAL:
+		tkn := self.token
+		self.next()
+		return &ast.BinaryExpression{
+			Operator:   tkn,
+			Left:       left,
+			Right:      self.parseRelationalExpression(),
+			Comparison: true,
+		}
+	case token.INSTANCEOF:
+		tkn := self.token
+		self.next()
+		return &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    self.parseRelationalExpression(),
+		}
+	case token.IN:
+		if !allowIn {
+			return left
+		}
+		tkn := self.token
+		self.next()
+		return &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    self.parseRelationalExpression(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseEqualityExpression() ast.Expression {
+	next := self.parseRelationalExpression
+	left := next()
+
+	for self.token == token.EQUAL || self.token == token.NOT_EQUAL ||
+		self.token == token.STRICT_EQUAL || self.token == token.STRICT_NOT_EQUAL {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator:   tkn,
+			Left:       left,
+			Right:      next(),
+			Comparison: true,
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseBitwiseAndExpression() ast.Expression {
+	next := self.parseEqualityExpression
+	left := next()
+
+	for self.token == token.AND {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseBitwiseExclusiveOrExpression() ast.Expression {
+	next := self.parseBitwiseAndExpression
+	left := next()
+
+	for self.token == token.EXCLUSIVE_OR {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseBitwiseOrExpression() ast.Expression {
+	next := self.parseBitwiseExclusiveOrExpression
+	left := next()
+
+	for self.token == token.OR {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseLogicalAndExpression() ast.Expression {
+	next := self.parseBitwiseOrExpression
+	left := next()
+
+	for self.token == token.LOGICAL_AND {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseLogicalOrExpression() ast.Expression {
+	next := self.parseLogicalAndExpression
+	left := next()
+
+	for self.token == token.LOGICAL_OR {
+		tkn := self.token
+		self.next()
+		left = &ast.BinaryExpression{
+			Operator: tkn,
+			Left:     left,
+			Right:    next(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseConditionlExpression() ast.Expression {
+	left := self.parseLogicalOrExpression()
+
+	if self.token == token.QUESTION_MARK {
+		self.next()
+		consequent := self.parseAssignmentExpression()
+		self.expect(token.COLON)
+		return &ast.ConditionalExpression{
+			Test:       left,
+			Consequent: consequent,
+			Alternate:  self.parseAssignmentExpression(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseAssignmentExpression() ast.Expression {
+	left := self.parseConditionlExpression()
+	var operator token.Token
+	switch self.token {
+	case token.ASSIGN:
+		operator = self.token
+	case token.ADD_ASSIGN:
+		operator = token.PLUS
+	case token.SUBTRACT_ASSIGN:
+		operator = token.MINUS
+	case token.MULTIPLY_ASSIGN:
+		operator = token.MULTIPLY
+	case token.QUOTIENT_ASSIGN:
+		operator = token.SLASH
+	case token.REMAINDER_ASSIGN:
+		operator = token.REMAINDER
+	case token.AND_ASSIGN:
+		operator = token.AND
+	case token.AND_NOT_ASSIGN:
+		operator = token.AND_NOT
+	case token.OR_ASSIGN:
+		operator = token.OR
+	case token.EXCLUSIVE_OR_ASSIGN:
+		operator = token.EXCLUSIVE_OR
+	case token.SHIFT_LEFT_ASSIGN:
+		operator = token.SHIFT_LEFT
+	case token.SHIFT_RIGHT_ASSIGN:
+		operator = token.SHIFT_RIGHT
+	case token.UNSIGNED_SHIFT_RIGHT_ASSIGN:
+		operator = token.UNSIGNED_SHIFT_RIGHT
+	}
+
+	if operator != 0 {
+		idx := self.idx
+		self.next()
+		switch left.(type) {
+		case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
+		default:
+			self.error(left.Idx0(), "Invalid left-hand side in assignment")
+			self.nextStatement()
+			return &ast.BadExpression{From: idx, To: self.idx}
+		}
+		return &ast.AssignExpression{
+			Left:     left,
+			Operator: operator,
+			Right:    self.parseAssignmentExpression(),
+		}
+	}
+
+	return left
+}
+
+func (self *_parser) parseExpression() ast.Expression {
+	next := self.parseAssignmentExpression
+	left := next()
+
+	if self.token == token.COMMA {
+		sequence := []ast.Expression{left}
+		for {
+			if self.token != token.COMMA {
+				break
+			}
+			self.next()
+			sequence = append(sequence, next())
+		}
+		return &ast.SequenceExpression{
+			Sequence: sequence,
+		}
+	}
+
+	return left
+}

+ 830 - 0
parser/lexer.go

@@ -0,0 +1,830 @@
+package parser
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"regexp"
+	"strconv"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+	"unicode/utf16"
+)
+
+type _chr struct {
+	value rune
+	width int
+}
+
+var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`)
+
+func isDecimalDigit(chr rune) bool {
+	return '0' <= chr && chr <= '9'
+}
+
+func digitValue(chr rune) int {
+	switch {
+	case '0' <= chr && chr <= '9':
+		return int(chr - '0')
+	case 'a' <= chr && chr <= 'f':
+		return int(chr - 'a' + 10)
+	case 'A' <= chr && chr <= 'F':
+		return int(chr - 'A' + 10)
+	}
+	return 16 // Larger than any legal digit value
+}
+
+func isDigit(chr rune, base int) bool {
+	return digitValue(chr) < base
+}
+
+func isIdentifierStart(chr rune) bool {
+	return chr == '$' || chr == '_' || chr == '\\' ||
+		'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
+		chr >= utf8.RuneSelf && unicode.IsLetter(chr)
+}
+
+func isIdentifierPart(chr rune) bool {
+	return chr == '$' || chr == '_' || chr == '\\' ||
+		'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
+		'0' <= chr && chr <= '9' ||
+		chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
+}
+
+func (self *_parser) scanIdentifier() (string, error) {
+	offset := self.chrOffset
+	parse := false
+	for isIdentifierPart(self.chr) {
+		if self.chr == '\\' {
+			distance := self.chrOffset - offset
+			self.read()
+			if self.chr != 'u' {
+				return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
+			}
+			parse = true
+			var value rune
+			for j := 0; j < 4; j++ {
+				self.read()
+				decimal, ok := hex2decimal(byte(self.chr))
+				if !ok {
+					return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
+				}
+				value = value<<4 | decimal
+			}
+			if value == '\\' {
+				return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
+			} else if distance == 0 {
+				if !isIdentifierStart(value) {
+					return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
+				}
+			} else if distance > 0 {
+				if !isIdentifierPart(value) {
+					return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
+				}
+			}
+		}
+		self.read()
+	}
+	literal := string(self.str[offset:self.chrOffset])
+	if parse {
+		return parseStringLiteral(literal)
+	}
+	return literal, nil
+}
+
+// 7.2
+func isLineWhiteSpace(chr rune) bool {
+	switch chr {
+	case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff':
+		return true
+	case '\u000a', '\u000d', '\u2028', '\u2029':
+		return false
+	case '\u0085':
+		return false
+	}
+	return unicode.IsSpace(chr)
+}
+
+// 7.3
+func isLineTerminator(chr rune) bool {
+	switch chr {
+	case '\u000a', '\u000d', '\u2028', '\u2029':
+		return true
+	}
+	return false
+}
+
+func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
+
+	self.implicitSemicolon = false
+
+	for {
+		self.skipWhiteSpace()
+
+		idx = self.idxOf(self.chrOffset)
+		insertSemicolon := false
+
+		switch chr := self.chr; {
+		case isIdentifierStart(chr):
+			var err error
+			literal, err = self.scanIdentifier()
+			if err != nil {
+				tkn = token.ILLEGAL
+				break
+			}
+			if len(literal) > 1 {
+				// Keywords are longer than 1 character, avoid lookup otherwise
+				var strict bool
+				tkn, strict = token.IsKeyword(literal)
+
+				switch tkn {
+
+				case 0: // Not a keyword
+					if literal == "true" || literal == "false" {
+						self.insertSemicolon = true
+						tkn = token.BOOLEAN
+						return
+					} else if literal == "null" {
+						self.insertSemicolon = true
+						tkn = token.NULL
+						return
+					}
+
+				case token.KEYWORD:
+					tkn = token.KEYWORD
+					if strict {
+						// TODO If strict and in strict mode, then this is not a break
+						break
+					}
+					return
+
+				case
+					token.THIS,
+					token.BREAK,
+					token.THROW, // A newline after a throw is not allowed, but we need to detect it
+					token.RETURN,
+					token.CONTINUE,
+					token.DEBUGGER:
+					self.insertSemicolon = true
+					return
+
+				default:
+					return
+
+				}
+			}
+			self.insertSemicolon = true
+			tkn = token.IDENTIFIER
+			return
+		case '0' <= chr && chr <= '9':
+			self.insertSemicolon = true
+			tkn, literal = self.scanNumericLiteral(false)
+			return
+		default:
+			self.read()
+			switch chr {
+			case -1:
+				if self.insertSemicolon {
+					self.insertSemicolon = false
+					self.implicitSemicolon = true
+				}
+				tkn = token.EOF
+			case '\r', '\n', '\u2028', '\u2029':
+				self.insertSemicolon = false
+				self.implicitSemicolon = true
+				continue
+			case ':':
+				tkn = token.COLON
+			case '.':
+				if digitValue(self.chr) < 10 {
+					insertSemicolon = true
+					tkn, literal = self.scanNumericLiteral(true)
+				} else {
+					tkn = token.PERIOD
+				}
+			case ',':
+				tkn = token.COMMA
+			case ';':
+				tkn = token.SEMICOLON
+			case '(':
+				tkn = token.LEFT_PARENTHESIS
+			case ')':
+				tkn = token.RIGHT_PARENTHESIS
+				insertSemicolon = true
+			case '[':
+				tkn = token.LEFT_BRACKET
+			case ']':
+				tkn = token.RIGHT_BRACKET
+				insertSemicolon = true
+			case '{':
+				tkn = token.LEFT_BRACE
+			case '}':
+				tkn = token.RIGHT_BRACE
+				insertSemicolon = true
+			case '+':
+				tkn = self.switch3(token.PLUS, token.ADD_ASSIGN, '+', token.INCREMENT)
+				if tkn == token.INCREMENT {
+					insertSemicolon = true
+				}
+			case '-':
+				tkn = self.switch3(token.MINUS, token.SUBTRACT_ASSIGN, '-', token.DECREMENT)
+				if tkn == token.DECREMENT {
+					insertSemicolon = true
+				}
+			case '*':
+				tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
+			case '/':
+				if self.chr == '/' {
+					self.skipSingleLineComment()
+					continue
+				} else if self.chr == '*' {
+					self.skipMultiLineComment()
+					continue
+				} else {
+					// Could be division, could be RegExp literal
+					tkn = self.switch2(token.SLASH, token.QUOTIENT_ASSIGN)
+					insertSemicolon = true
+				}
+			case '%':
+				tkn = self.switch2(token.REMAINDER, token.REMAINDER_ASSIGN)
+			case '^':
+				tkn = self.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN)
+			case '<':
+				tkn = self.switch4(token.LESS, token.LESS_OR_EQUAL, '<', token.SHIFT_LEFT, token.SHIFT_LEFT_ASSIGN)
+			case '>':
+				tkn = self.switch6(token.GREATER, token.GREATER_OR_EQUAL, '>', token.SHIFT_RIGHT, token.SHIFT_RIGHT_ASSIGN, '>', token.UNSIGNED_SHIFT_RIGHT, token.UNSIGNED_SHIFT_RIGHT_ASSIGN)
+			case '=':
+				tkn = self.switch2(token.ASSIGN, token.EQUAL)
+				if tkn == token.EQUAL && self.chr == '=' {
+					self.read()
+					tkn = token.STRICT_EQUAL
+				}
+			case '!':
+				tkn = self.switch2(token.NOT, token.NOT_EQUAL)
+				if tkn == token.NOT_EQUAL && self.chr == '=' {
+					self.read()
+					tkn = token.STRICT_NOT_EQUAL
+				}
+			case '&':
+				if self.chr == '^' {
+					self.read()
+					tkn = self.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
+				} else {
+					tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
+				}
+			case '|':
+				tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
+			case '~':
+				tkn = token.BITWISE_NOT
+			case '?':
+				tkn = token.QUESTION_MARK
+			case '"', '\'':
+				insertSemicolon = true
+				tkn = token.STRING
+				var err error
+				literal, err = self.scanString(self.chrOffset - 1)
+				if err != nil {
+					tkn = token.ILLEGAL
+				}
+			default:
+				self.errorUnexpected(idx, chr)
+				tkn = token.ILLEGAL
+			}
+		}
+		self.insertSemicolon = insertSemicolon
+		return
+	}
+}
+
+func (self *_parser) switch2(tkn0, tkn1 token.Token) token.Token {
+	if self.chr == '=' {
+		self.read()
+		return tkn1
+	}
+	return tkn0
+}
+
+func (self *_parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token {
+	if self.chr == '=' {
+		self.read()
+		return tkn1
+	}
+	if self.chr == chr2 {
+		self.read()
+		return tkn2
+	}
+	return tkn0
+}
+
+func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token {
+	if self.chr == '=' {
+		self.read()
+		return tkn1
+	}
+	if self.chr == chr2 {
+		self.read()
+		if self.chr == '=' {
+			self.read()
+			return tkn3
+		}
+		return tkn2
+	}
+	return tkn0
+}
+
+func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token {
+	if self.chr == '=' {
+		self.read()
+		return tkn1
+	}
+	if self.chr == chr2 {
+		self.read()
+		if self.chr == '=' {
+			self.read()
+			return tkn3
+		}
+		if self.chr == chr3 {
+			self.read()
+			if self.chr == '=' {
+				self.read()
+				return tkn5
+			}
+			return tkn4
+		}
+		return tkn2
+	}
+	return tkn0
+}
+
+func (self *_parser) chrAt(index int) _chr {
+	value, width := utf8.DecodeRuneInString(self.str[index:])
+	return _chr{
+		value: value,
+		width: width,
+	}
+}
+
+func (self *_parser) _peek() rune {
+	if self.offset+1 < self.length {
+		return rune(self.str[self.offset+1])
+	}
+	return -1
+}
+
+func (self *_parser) read() {
+	if self.offset < self.length {
+		self.chrOffset = self.offset
+		chr, width := rune(self.str[self.offset]), 1
+		if chr >= utf8.RuneSelf { // !ASCII
+			chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
+			if chr == utf8.RuneError && width == 1 {
+				self.error(self.chrOffset, "Invalid UTF-8 character")
+			}
+		}
+		self.offset += width
+		self.chr = chr
+	} else {
+		self.chrOffset = self.length
+		self.chr = -1 // EOF
+	}
+}
+
+// This is here since the functions are so similar
+func (self *_RegExp_parser) read() {
+	if self.offset < self.length {
+		self.chrOffset = self.offset
+		chr, width := rune(self.str[self.offset]), 1
+		if chr >= utf8.RuneSelf { // !ASCII
+			chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
+			if chr == utf8.RuneError && width == 1 {
+				self.error(self.chrOffset, "Invalid UTF-8 character")
+			}
+		}
+		self.offset += width
+		self.chr = chr
+	} else {
+		self.chrOffset = self.length
+		self.chr = -1 // EOF
+	}
+}
+
+func (self *_parser) skipSingleLineComment() {
+	for self.chr != -1 {
+		self.read()
+		if isLineTerminator(self.chr) {
+			return
+		}
+	}
+}
+
+func (self *_parser) skipMultiLineComment() {
+	self.read()
+	for self.chr >= 0 {
+		chr := self.chr
+		self.read()
+		if chr == '*' && self.chr == '/' {
+			self.read()
+			return
+		}
+	}
+
+	self.errorUnexpected(0, self.chr)
+}
+
+func (self *_parser) skipWhiteSpace() {
+	for {
+		switch self.chr {
+		case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff':
+			self.read()
+			continue
+		case '\r':
+			if self._peek() == '\n' {
+				self.read()
+			}
+			fallthrough
+		case '\u2028', '\u2029', '\n':
+			if self.insertSemicolon {
+				return
+			}
+			self.read()
+			continue
+		}
+		if self.chr >= utf8.RuneSelf {
+			if unicode.IsSpace(self.chr) {
+				self.read()
+				continue
+			}
+		}
+		break
+	}
+}
+
+func (self *_parser) skipLineWhiteSpace() {
+	for isLineWhiteSpace(self.chr) {
+		self.read()
+	}
+}
+
+func (self *_parser) scanMantissa(base int) {
+	for digitValue(self.chr) < base {
+		self.read()
+	}
+}
+
+func (self *_parser) scanEscape(quote rune) {
+
+	var length, base uint32
+	switch self.chr {
+	//case '0', '1', '2', '3', '4', '5', '6', '7':
+	//    Octal:
+	//    length, base, limit = 3, 8, 255
+	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0':
+		self.read()
+		return
+	case '\r', '\n', '\u2028', '\u2029':
+		self.scanNewline()
+		return
+	case 'x':
+		self.read()
+		length, base = 2, 16
+	case 'u':
+		self.read()
+		length, base = 4, 16
+	default:
+		self.read() // Always make progress
+		return
+	}
+
+	var value uint32
+	for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
+		digit := uint32(digitValue(self.chr))
+		if digit >= base {
+			break
+		}
+		value = value*base + digit
+		self.read()
+	}
+}
+
+func (self *_parser) scanString(offset int) (string, error) {
+	// " ' /
+	quote := rune(self.str[offset])
+
+	for self.chr != quote {
+		chr := self.chr
+		if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
+			goto newline
+		}
+		self.read()
+		if chr == '\\' {
+			if quote == '/' {
+				if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 {
+					goto newline
+				}
+				self.read()
+			} else {
+				self.scanEscape(quote)
+			}
+		} else if chr == '[' && quote == '/' {
+			// Allow a slash (/) in a bracket character class ([...])
+			// TODO Fix this, this is hacky...
+			quote = -1
+		} else if chr == ']' && quote == -1 {
+			quote = '/'
+		}
+	}
+
+	// " ' /
+	self.read()
+
+	return string(self.str[offset:self.chrOffset]), nil
+
+newline:
+	self.scanNewline()
+	err := "String not terminated"
+	if quote == '/' {
+		err = "Invalid regular expression: missing /"
+		self.error(self.idxOf(offset), err)
+	}
+	return "", errors.New(err)
+}
+
+func (self *_parser) scanNewline() {
+	if self.chr == '\r' {
+		self.read()
+		if self.chr != '\n' {
+			return
+		}
+	}
+	self.read()
+}
+
+func hex2decimal(chr byte) (value rune, ok bool) {
+	{
+		chr := rune(chr)
+		switch {
+		case '0' <= chr && chr <= '9':
+			return chr - '0', true
+		case 'a' <= chr && chr <= 'f':
+			return chr - 'a' + 10, true
+		case 'A' <= chr && chr <= 'F':
+			return chr - 'A' + 10, true
+		}
+		return
+	}
+}
+
+func parseNumberLiteral(literal string) (value interface{}, err error) {
+	// TODO Is Uint okay? What about -MAX_UINT
+	value, err = strconv.ParseInt(literal, 0, 64)
+	if err == nil {
+		return
+	}
+
+	parseIntErr := err // Save this first error, just in case
+
+	value, err = strconv.ParseFloat(literal, 64)
+	if err == nil {
+		return
+	} else if err.(*strconv.NumError).Err == strconv.ErrRange {
+		// Infinity, etc.
+		return value, nil
+	}
+
+	err = parseIntErr
+
+	if err.(*strconv.NumError).Err == strconv.ErrRange {
+		if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') {
+			// Could just be a very large number (e.g. 0x8000000000000000)
+			var value float64
+			literal = literal[2:]
+			for _, chr := range literal {
+				digit := digitValue(chr)
+				if digit >= 16 {
+					goto error
+				}
+				value = value*16 + float64(digit)
+			}
+			return value, nil
+		}
+	}
+
+error:
+	return nil, errors.New("Illegal numeric literal")
+}
+
+func parseStringLiteral(literal string) (string, error) {
+	// Best case scenario...
+	if literal == "" {
+		return "", nil
+	}
+
+	// Slightly less-best case scenario...
+	if !strings.ContainsRune(literal, '\\') {
+		return literal, nil
+	}
+
+	str := literal
+	buffer := bytes.NewBuffer(make([]byte, 0, 3*len(literal)/2))
+	var surrogate rune
+	S:
+	for len(str) > 0 {
+		switch chr := str[0]; {
+		// We do not explicitly handle the case of the quote
+		// value, which can be: " ' /
+		// This assumes we're already passed a partially well-formed literal
+		case chr >= utf8.RuneSelf:
+			chr, size := utf8.DecodeRuneInString(str)
+			buffer.WriteRune(chr)
+			str = str[size:]
+			continue
+		case chr != '\\':
+			buffer.WriteByte(chr)
+			str = str[1:]
+			continue
+		}
+
+		if len(str) <= 1 {
+			panic("len(str) <= 1")
+		}
+		chr := str[1]
+		var value rune
+		if chr >= utf8.RuneSelf {
+			str = str[1:]
+			var size int
+			value, size = utf8.DecodeRuneInString(str)
+			str = str[size:] // \ + <character>
+		} else {
+			str = str[2:] // \<character>
+			switch chr {
+			case 'b':
+				value = '\b'
+			case 'f':
+				value = '\f'
+			case 'n':
+				value = '\n'
+			case 'r':
+				value = '\r'
+			case 't':
+				value = '\t'
+			case 'v':
+				value = '\v'
+			case 'x', 'u':
+				size := 0
+				switch chr {
+				case 'x':
+					size = 2
+				case 'u':
+					size = 4
+				}
+				if len(str) < size {
+					return "", fmt.Errorf("invalid escape: \\%s: len(%q) != %d", string(chr), str, size)
+				}
+				for j := 0; j < size; j++ {
+					decimal, ok := hex2decimal(str[j])
+					if !ok {
+						return "", fmt.Errorf("invalid escape: \\%s: %q", string(chr), str[:size])
+					}
+					value = value<<4 | decimal
+				}
+				str = str[size:]
+				if chr == 'x' {
+					break
+				}
+				if value > utf8.MaxRune {
+					panic("value > utf8.MaxRune")
+				}
+			case '0':
+				if len(str) == 0 || '0' > str[0] || str[0] > '7' {
+					value = 0
+					break
+				}
+				fallthrough
+			case '1', '2', '3', '4', '5', '6', '7':
+				// TODO strict
+				value = rune(chr) - '0'
+				j := 0
+				for ; j < 2; j++ {
+					if len(str) < j+1 {
+						break
+					}
+					chr := str[j]
+					if '0' > chr || chr > '7' {
+						break
+					}
+					decimal := rune(str[j]) - '0'
+					value = (value << 3) | decimal
+				}
+				str = str[j:]
+			case '\\':
+				value = '\\'
+			case '\'', '"':
+				value = rune(chr)
+			case '\r':
+				if len(str) > 0 {
+					if str[0] == '\n' {
+						str = str[1:]
+					}
+				}
+				fallthrough
+			case '\n':
+				continue
+			default:
+				value = rune(chr)
+			}
+			if surrogate != 0 {
+				value = utf16.DecodeRune(surrogate, value)
+				surrogate = 0
+			} else {
+				if utf16.IsSurrogate(value) {
+					surrogate = value
+					continue S
+				}
+			}
+		}
+		buffer.WriteRune(value)
+	}
+
+	return buffer.String(), nil
+}
+
+func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {
+
+	offset := self.chrOffset
+	tkn := token.NUMBER
+
+	if decimalPoint {
+		offset--
+		self.scanMantissa(10)
+		goto exponent
+	}
+
+	if self.chr == '0' {
+		offset := self.chrOffset
+		self.read()
+		if self.chr == 'x' || self.chr == 'X' {
+			// Hexadecimal
+			self.read()
+			if isDigit(self.chr, 16) {
+				self.read()
+			} else {
+				return token.ILLEGAL, self.str[offset:self.chrOffset]
+			}
+			self.scanMantissa(16)
+
+			if self.chrOffset-offset <= 2 {
+				// Only "0x" or "0X"
+				self.error(0, "Illegal hexadecimal number")
+			}
+
+			goto hexadecimal
+		} else if self.chr == '.' {
+			// Float
+			goto float
+		} else {
+			// Octal, Float
+			if self.chr == 'e' || self.chr == 'E' {
+				goto exponent
+			}
+			self.scanMantissa(8)
+			if self.chr == '8' || self.chr == '9' {
+				return token.ILLEGAL, self.str[offset:self.chrOffset]
+			}
+			goto octal
+		}
+	}
+
+	self.scanMantissa(10)
+
+float:
+	if self.chr == '.' {
+		self.read()
+		self.scanMantissa(10)
+	}
+
+exponent:
+	if self.chr == 'e' || self.chr == 'E' {
+		self.read()
+		if self.chr == '-' || self.chr == '+' {
+			self.read()
+		}
+		if isDecimalDigit(self.chr) {
+			self.read()
+			self.scanMantissa(10)
+		} else {
+			return token.ILLEGAL, self.str[offset:self.chrOffset]
+		}
+	}
+
+hexadecimal:
+octal:
+	if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) {
+		return token.ILLEGAL, self.str[offset:self.chrOffset]
+	}
+
+	return tkn, self.str[offset:self.chrOffset]
+}

+ 376 - 0
parser/lexer_test.go

@@ -0,0 +1,376 @@
+package parser
+
+import (
+	"testing"
+
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+)
+
+func TestLexer(t *testing.T) {
+	tt(t, func() {
+		setup := func(src string) *_parser {
+			parser := newParser("", src)
+			return parser
+		}
+
+		test := func(src string, test ...interface{}) {
+			parser := setup(src)
+			for len(test) > 0 {
+				tkn, literal, idx := parser.scan()
+				if len(test) > 0 {
+					is(tkn, test[0].(token.Token))
+					test = test[1:]
+				}
+				if len(test) > 0 {
+					is(literal, test[0].(string))
+					test = test[1:]
+				}
+				if len(test) > 0 {
+					// FIXME terst, Fix this so that cast to file.Idx is not necessary?
+					is(idx, file.Idx(test[0].(int)))
+					test = test[1:]
+				}
+			}
+		}
+
+		test("",
+			token.EOF, "", 1,
+		)
+
+		test("1",
+			token.NUMBER, "1", 1,
+			token.EOF, "", 2,
+		)
+
+		test(".0",
+			token.NUMBER, ".0", 1,
+			token.EOF, "", 3,
+		)
+
+		test("abc",
+			token.IDENTIFIER, "abc", 1,
+			token.EOF, "", 4,
+		)
+
+		test("abc(1)",
+			token.IDENTIFIER, "abc", 1,
+			token.LEFT_PARENTHESIS, "", 4,
+			token.NUMBER, "1", 5,
+			token.RIGHT_PARENTHESIS, "", 6,
+			token.EOF, "", 7,
+		)
+
+		test(".",
+			token.PERIOD, "", 1,
+			token.EOF, "", 2,
+		)
+
+		test("===.",
+			token.STRICT_EQUAL, "", 1,
+			token.PERIOD, "", 4,
+			token.EOF, "", 5,
+		)
+
+		test(">>>=.0",
+			token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
+			token.NUMBER, ".0", 5,
+			token.EOF, "", 7,
+		)
+
+		test(">>>=0.0.",
+			token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
+			token.NUMBER, "0.0", 5,
+			token.PERIOD, "", 8,
+			token.EOF, "", 9,
+		)
+
+		test("\"abc\"",
+			token.STRING, "\"abc\"", 1,
+			token.EOF, "", 6,
+		)
+
+		test("abc = //",
+			token.IDENTIFIER, "abc", 1,
+			token.ASSIGN, "", 5,
+			token.EOF, "", 9,
+		)
+
+		test("abc = 1 / 2",
+			token.IDENTIFIER, "abc", 1,
+			token.ASSIGN, "", 5,
+			token.NUMBER, "1", 7,
+			token.SLASH, "", 9,
+			token.NUMBER, "2", 11,
+			token.EOF, "", 12,
+		)
+
+		test("xyzzy = 'Nothing happens.'",
+			token.IDENTIFIER, "xyzzy", 1,
+			token.ASSIGN, "", 7,
+			token.STRING, "'Nothing happens.'", 9,
+			token.EOF, "", 27,
+		)
+
+		test("abc = !false",
+			token.IDENTIFIER, "abc", 1,
+			token.ASSIGN, "", 5,
+			token.NOT, "", 7,
+			token.BOOLEAN, "false", 8,
+			token.EOF, "", 13,
+		)
+
+		test("abc = !!true",
+			token.IDENTIFIER, "abc", 1,
+			token.ASSIGN, "", 5,
+			token.NOT, "", 7,
+			token.NOT, "", 8,
+			token.BOOLEAN, "true", 9,
+			token.EOF, "", 13,
+		)
+
+		test("abc *= 1",
+			token.IDENTIFIER, "abc", 1,
+			token.MULTIPLY_ASSIGN, "", 5,
+			token.NUMBER, "1", 8,
+			token.EOF, "", 9,
+		)
+
+		test("if 1 else",
+			token.IF, "if", 1,
+			token.NUMBER, "1", 4,
+			token.ELSE, "else", 6,
+			token.EOF, "", 10,
+		)
+
+		test("null",
+			token.NULL, "null", 1,
+			token.EOF, "", 5,
+		)
+
+		test(`"\u007a\x79\u000a\x78"`,
+			token.STRING, "\"\\u007a\\x79\\u000a\\x78\"", 1,
+			token.EOF, "", 23,
+		)
+
+		test(`"[First line \
+Second line \
+ Third line\
+.     ]"
+	`,
+			token.STRING, "\"[First line \\\nSecond line \\\n Third line\\\n.     ]\"", 1,
+			token.EOF, "", 53,
+		)
+
+		test("/",
+			token.SLASH, "", 1,
+			token.EOF, "", 2,
+		)
+
+		test("var abc = \"abc\uFFFFabc\"",
+			token.VAR, "var", 1,
+			token.IDENTIFIER, "abc", 5,
+			token.ASSIGN, "", 9,
+			token.STRING, "\"abc\uFFFFabc\"", 11,
+			token.EOF, "", 22,
+		)
+
+		test(`'\t' === '\r'`,
+			token.STRING, "'\\t'", 1,
+			token.STRICT_EQUAL, "", 6,
+			token.STRING, "'\\r'", 10,
+			token.EOF, "", 14,
+		)
+
+		test(`var \u0024 = 1`,
+			token.VAR, "var", 1,
+			token.IDENTIFIER, "$", 5,
+			token.ASSIGN, "", 12,
+			token.NUMBER, "1", 14,
+			token.EOF, "", 15,
+		)
+
+		test("10e10000",
+			token.NUMBER, "10e10000", 1,
+			token.EOF, "", 9,
+		)
+
+		test(`var if var class`,
+			token.VAR, "var", 1,
+			token.IF, "if", 5,
+			token.VAR, "var", 8,
+			token.KEYWORD, "class", 12,
+			token.EOF, "", 17,
+		)
+
+		test(`-0`,
+			token.MINUS, "", 1,
+			token.NUMBER, "0", 2,
+			token.EOF, "", 3,
+		)
+
+		test(`.01`,
+			token.NUMBER, ".01", 1,
+			token.EOF, "", 4,
+		)
+
+		test(`.01e+2`,
+			token.NUMBER, ".01e+2", 1,
+			token.EOF, "", 7,
+		)
+
+		test(";",
+			token.SEMICOLON, "", 1,
+			token.EOF, "", 2,
+		)
+
+		test(";;",
+			token.SEMICOLON, "", 1,
+			token.SEMICOLON, "", 2,
+			token.EOF, "", 3,
+		)
+
+		test("//",
+			token.EOF, "", 3,
+		)
+
+		test(";;//",
+			token.SEMICOLON, "", 1,
+			token.SEMICOLON, "", 2,
+			token.EOF, "", 5,
+		)
+
+		test("1",
+			token.NUMBER, "1", 1,
+		)
+
+		test("12 123",
+			token.NUMBER, "12", 1,
+			token.NUMBER, "123", 4,
+		)
+
+		test("1.2 12.3",
+			token.NUMBER, "1.2", 1,
+			token.NUMBER, "12.3", 5,
+		)
+
+		test("/ /=",
+			token.SLASH, "", 1,
+			token.QUOTIENT_ASSIGN, "", 3,
+		)
+
+		test(`"abc"`,
+			token.STRING, `"abc"`, 1,
+		)
+
+		test(`'abc'`,
+			token.STRING, `'abc'`, 1,
+		)
+
+		test("++",
+			token.INCREMENT, "", 1,
+		)
+
+		test(">",
+			token.GREATER, "", 1,
+		)
+
+		test(">=",
+			token.GREATER_OR_EQUAL, "", 1,
+		)
+
+		test(">>",
+			token.SHIFT_RIGHT, "", 1,
+		)
+
+		test(">>=",
+			token.SHIFT_RIGHT_ASSIGN, "", 1,
+		)
+
+		test(">>>",
+			token.UNSIGNED_SHIFT_RIGHT, "", 1,
+		)
+
+		test(">>>=",
+			token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
+		)
+
+		test("1 \"abc\"",
+			token.NUMBER, "1", 1,
+			token.STRING, "\"abc\"", 3,
+		)
+
+		test(",",
+			token.COMMA, "", 1,
+		)
+
+		test("1, \"abc\"",
+			token.NUMBER, "1", 1,
+			token.COMMA, "", 2,
+			token.STRING, "\"abc\"", 4,
+		)
+
+		test("new abc(1, 3.14159);",
+			token.NEW, "new", 1,
+			token.IDENTIFIER, "abc", 5,
+			token.LEFT_PARENTHESIS, "", 8,
+			token.NUMBER, "1", 9,
+			token.COMMA, "", 10,
+			token.NUMBER, "3.14159", 12,
+			token.RIGHT_PARENTHESIS, "", 19,
+			token.SEMICOLON, "", 20,
+		)
+
+		test("1 == \"1\"",
+			token.NUMBER, "1", 1,
+			token.EQUAL, "", 3,
+			token.STRING, "\"1\"", 6,
+		)
+
+		test("1\n[]\n",
+			token.NUMBER, "1", 1,
+			token.LEFT_BRACKET, "", 3,
+			token.RIGHT_BRACKET, "", 4,
+		)
+
+		test("1\ufeff[]\ufeff",
+			token.NUMBER, "1", 1,
+			token.LEFT_BRACKET, "", 5,
+			token.RIGHT_BRACKET, "", 6,
+		)
+
+		// ILLEGAL
+
+		test(`3ea`,
+			token.ILLEGAL, "3e", 1,
+			token.IDENTIFIER, "a", 3,
+			token.EOF, "", 4,
+		)
+
+		test(`3in`,
+			token.ILLEGAL, "3", 1,
+			token.IN, "in", 2,
+			token.EOF, "", 4,
+		)
+
+		test("\"Hello\nWorld\"",
+			token.ILLEGAL, "", 1,
+			token.IDENTIFIER, "World", 8,
+			token.ILLEGAL, "", 13,
+			token.EOF, "", 14,
+		)
+
+		test("\u203f = 10",
+			token.ILLEGAL, "", 1,
+			token.ASSIGN, "", 5,
+			token.NUMBER, "10", 7,
+			token.EOF, "", 9,
+		)
+
+		test(`"\x0G"`,
+			token.STRING, "\"\\x0G\"", 1,
+			token.EOF, "", 7,
+		)
+
+	})
+}

+ 930 - 0
parser/marshal_test.go

@@ -0,0 +1,930 @@
+package parser
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"os"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/dop251/goja/ast"
+)
+
+func marshal(name string, children ...interface{}) interface{} {
+	if len(children) == 1 {
+		if name == "" {
+			return testMarshalNode(children[0])
+		}
+		return map[string]interface{}{
+			name: children[0],
+		}
+	}
+	map_ := map[string]interface{}{}
+	length := len(children) / 2
+	for i := 0; i < length; i++ {
+		name := children[i*2].(string)
+		value := children[i*2+1]
+		map_[name] = value
+	}
+	if name == "" {
+		return map_
+	}
+	return map[string]interface{}{
+		name: map_,
+	}
+}
+
+func testMarshalNode(node interface{}) interface{} {
+	switch node := node.(type) {
+
+	// Expression
+
+	case *ast.ArrayLiteral:
+		return marshal("Array", testMarshalNode(node.Value))
+
+	case *ast.AssignExpression:
+		return marshal("Assign",
+			"Left", testMarshalNode(node.Left),
+			"Right", testMarshalNode(node.Right),
+		)
+
+	case *ast.BinaryExpression:
+		return marshal("BinaryExpression",
+			"Operator", node.Operator.String(),
+			"Left", testMarshalNode(node.Left),
+			"Right", testMarshalNode(node.Right),
+		)
+
+	case *ast.BooleanLiteral:
+		return marshal("Literal", node.Value)
+
+	case *ast.CallExpression:
+		return marshal("Call",
+			"Callee", testMarshalNode(node.Callee),
+			"ArgumentList", testMarshalNode(node.ArgumentList),
+		)
+
+	case *ast.ConditionalExpression:
+		return marshal("Conditional",
+			"Test", testMarshalNode(node.Test),
+			"Consequent", testMarshalNode(node.Consequent),
+			"Alternate", testMarshalNode(node.Alternate),
+		)
+
+	case *ast.DotExpression:
+		return marshal("Dot",
+			"Left", testMarshalNode(node.Left),
+			"Member", node.Identifier.Name,
+		)
+
+	case *ast.NewExpression:
+		return marshal("New",
+			"Callee", testMarshalNode(node.Callee),
+			"ArgumentList", testMarshalNode(node.ArgumentList),
+		)
+
+	case *ast.NullLiteral:
+		return marshal("Literal", nil)
+
+	case *ast.NumberLiteral:
+		return marshal("Literal", node.Value)
+
+	case *ast.ObjectLiteral:
+		return marshal("Object", testMarshalNode(node.Value))
+
+	case *ast.RegExpLiteral:
+		return marshal("Literal", node.Literal)
+
+	case *ast.StringLiteral:
+		return marshal("Literal", node.Literal)
+
+	case *ast.VariableExpression:
+		return []interface{}{node.Name, testMarshalNode(node.Initializer)}
+
+	// Statement
+
+	case *ast.Program:
+		return testMarshalNode(node.Body)
+
+	case *ast.BlockStatement:
+		return marshal("BlockStatement", testMarshalNode(node.List))
+
+	case *ast.EmptyStatement:
+		return "EmptyStatement"
+
+	case *ast.ExpressionStatement:
+		return testMarshalNode(node.Expression)
+
+	case *ast.ForInStatement:
+		return marshal("ForIn",
+			"Into", marshal("", node.Into),
+			"Source", marshal("", node.Source),
+			"Body", marshal("", node.Body),
+		)
+
+	case *ast.FunctionLiteral:
+		return marshal("Function", testMarshalNode(node.Body))
+
+	case *ast.Identifier:
+		return marshal("Identifier", node.Name)
+
+	case *ast.IfStatement:
+		if_ := marshal("",
+			"Test", testMarshalNode(node.Test),
+			"Consequent", testMarshalNode(node.Consequent),
+		).(map[string]interface{})
+		if node.Alternate != nil {
+			if_["Alternate"] = testMarshalNode(node.Alternate)
+		}
+		return marshal("If", if_)
+
+	case *ast.LabelledStatement:
+		return marshal("Label",
+			"Name", node.Label.Name,
+			"Statement", testMarshalNode(node.Statement),
+		)
+	case ast.Property:
+		return marshal("",
+			"Key", node.Key,
+			"Value", testMarshalNode(node.Value),
+		)
+
+	case *ast.ReturnStatement:
+		return marshal("Return", testMarshalNode(node.Argument))
+
+	case *ast.SequenceExpression:
+		return marshal("Sequence", testMarshalNode(node.Sequence))
+
+	case *ast.ThrowStatement:
+		return marshal("Throw", testMarshalNode(node.Argument))
+
+	case *ast.VariableStatement:
+		return marshal("Var", testMarshalNode(node.List))
+
+	}
+
+	{
+		value := reflect.ValueOf(node)
+		if value.Kind() == reflect.Slice {
+			tmp0 := []interface{}{}
+			for index := 0; index < value.Len(); index++ {
+				tmp0 = append(tmp0, testMarshalNode(value.Index(index).Interface()))
+			}
+			return tmp0
+		}
+	}
+
+	if node != nil {
+		fmt.Fprintf(os.Stderr, "testMarshalNode(%T)\n", node)
+	}
+
+	return nil
+}
+
+func testMarshal(node interface{}) string {
+	value, err := json.Marshal(testMarshalNode(node))
+	if err != nil {
+		panic(err)
+	}
+	return string(value)
+}
+
+func TestParserAST(t *testing.T) {
+	tt(t, func() {
+
+		test := func(inputOutput string) {
+			match := matchBeforeAfterSeparator.FindStringIndex(inputOutput)
+			input := strings.TrimSpace(inputOutput[0:match[0]])
+			wantOutput := strings.TrimSpace(inputOutput[match[1]:])
+			_, program, err := testParse(input)
+			is(err, nil)
+			haveOutput := testMarshal(program)
+			tmp0, tmp1 := bytes.Buffer{}, bytes.Buffer{}
+			json.Indent(&tmp0, []byte(haveOutput), "\t\t", "   ")
+			json.Indent(&tmp1, []byte(wantOutput), "\t\t", "   ")
+			is("\n\t\t"+tmp0.String(), "\n\t\t"+tmp1.String())
+		}
+
+		test(`
+        ---
+[]
+        `)
+
+		test(`
+        ;
+        ---
+[
+  "EmptyStatement"
+]
+        `)
+
+		test(`
+        ;;;
+        ---
+[
+  "EmptyStatement",
+  "EmptyStatement",
+  "EmptyStatement"
+]
+        `)
+
+		test(`
+        1; true; abc; "abc"; null;
+        ---
+[
+  {
+    "Literal": 1
+  },
+  {
+    "Literal": true
+  },
+  {
+    "Identifier": "abc"
+  },
+  {
+    "Literal": "\"abc\""
+  },
+  {
+    "Literal": null
+  }
+]
+        `)
+
+		test(`
+        { 1; null; 3.14159; ; }
+        ---
+[
+  {
+    "BlockStatement": [
+      {
+        "Literal": 1
+      },
+      {
+        "Literal": null
+      },
+      {
+        "Literal": 3.14159
+      },
+      "EmptyStatement"
+    ]
+  }
+]
+        `)
+
+		test(`
+        new abc();
+        ---
+[
+  {
+    "New": {
+      "ArgumentList": [],
+      "Callee": {
+        "Identifier": "abc"
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        new abc(1, 3.14159)
+        ---
+[
+  {
+    "New": {
+      "ArgumentList": [
+        {
+          "Literal": 1
+        },
+        {
+          "Literal": 3.14159
+        }
+      ],
+      "Callee": {
+        "Identifier": "abc"
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        true ? false : true
+        ---
+[
+  {
+    "Conditional": {
+      "Alternate": {
+        "Literal": true
+      },
+      "Consequent": {
+        "Literal": false
+      },
+      "Test": {
+        "Literal": true
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        true || false
+        ---
+[
+  {
+    "BinaryExpression": {
+      "Left": {
+        "Literal": true
+      },
+      "Operator": "||",
+      "Right": {
+        "Literal": false
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        0 + { abc: true }
+        ---
+[
+  {
+    "BinaryExpression": {
+      "Left": {
+        "Literal": 0
+      },
+      "Operator": "+",
+      "Right": {
+        "Object": [
+          {
+            "Key": "abc",
+            "Value": {
+              "Literal": true
+            }
+          }
+        ]
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        1 == "1"
+        ---
+[
+  {
+    "BinaryExpression": {
+      "Left": {
+        "Literal": 1
+      },
+      "Operator": "==",
+      "Right": {
+        "Literal": "\"1\""
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        abc(1)
+        ---
+[
+  {
+    "Call": {
+      "ArgumentList": [
+        {
+          "Literal": 1
+        }
+      ],
+      "Callee": {
+        "Identifier": "abc"
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        Math.pow(3, 2)
+        ---
+[
+  {
+    "Call": {
+      "ArgumentList": [
+        {
+          "Literal": 3
+        },
+        {
+          "Literal": 2
+        }
+      ],
+      "Callee": {
+        "Dot": {
+          "Left": {
+            "Identifier": "Math"
+          },
+          "Member": "pow"
+        }
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        1, 2, 3
+        ---
+[
+  {
+    "Sequence": [
+      {
+        "Literal": 1
+      },
+      {
+        "Literal": 2
+      },
+      {
+        "Literal": 3
+      }
+    ]
+  }
+]
+        `)
+
+		test(`
+        / abc /gim;
+        ---
+[
+  {
+    "Literal": "/ abc /gim"
+  }
+]
+        `)
+
+		test(`
+        if (0)
+            1;
+        ---
+[
+  {
+    "If": {
+      "Consequent": {
+        "Literal": 1
+      },
+      "Test": {
+        "Literal": 0
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        0+function(){
+            return;
+        }
+        ---
+[
+  {
+    "BinaryExpression": {
+      "Left": {
+        "Literal": 0
+      },
+      "Operator": "+",
+      "Right": {
+        "Function": {
+          "BlockStatement": [
+            {
+              "Return": null
+            }
+          ]
+        }
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        xyzzy // Ignore it
+        // Ignore this
+        // And this
+        /* And all..
+
+
+
+        ... of this!
+        */
+        "Nothing happens."
+        // And finally this
+        ---
+[
+  {
+    "Identifier": "xyzzy"
+  },
+  {
+    "Literal": "\"Nothing happens.\""
+  }
+]
+        `)
+
+		test(`
+        ((x & (x = 1)) !== 0)
+        ---
+[
+  {
+    "BinaryExpression": {
+      "Left": {
+        "BinaryExpression": {
+          "Left": {
+            "Identifier": "x"
+          },
+          "Operator": "\u0026",
+          "Right": {
+            "Assign": {
+              "Left": {
+                "Identifier": "x"
+              },
+              "Right": {
+                "Literal": 1
+              }
+            }
+          }
+        }
+      },
+      "Operator": "!==",
+      "Right": {
+        "Literal": 0
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        { abc: 'def' }
+        ---
+[
+  {
+    "BlockStatement": [
+      {
+        "Label": {
+          "Name": "abc",
+          "Statement": {
+            "Literal": "'def'"
+          }
+        }
+      }
+    ]
+  }
+]
+        `)
+
+		test(`
+        // This is not an object, this is a string literal with a label!
+        ({ abc: 'def' })
+        ---
+[
+  {
+    "Object": [
+      {
+        "Key": "abc",
+        "Value": {
+          "Literal": "'def'"
+        }
+      }
+    ]
+  }
+]
+        `)
+
+		test(`
+        [,]
+        ---
+[
+  {
+    "Array": [
+      null
+    ]
+  }
+]
+        `)
+
+		test(`
+        [,,]
+        ---
+[
+  {
+    "Array": [
+      null,
+      null
+    ]
+  }
+]
+        `)
+
+		test(`
+        ({ get abc() {} })
+        ---
+[
+  {
+    "Object": [
+      {
+        "Key": "abc",
+        "Value": {
+          "Function": {
+            "BlockStatement": []
+          }
+        }
+      }
+    ]
+  }
+]
+        `)
+
+		test(`
+        /abc/.source
+        ---
+[
+  {
+    "Dot": {
+      "Left": {
+        "Literal": "/abc/"
+      },
+      "Member": "source"
+    }
+  }
+]
+        `)
+
+		test(`
+                xyzzy
+
+        throw new TypeError("Nothing happens.")
+        ---
+[
+  {
+    "Identifier": "xyzzy"
+  },
+  {
+    "Throw": {
+      "New": {
+        "ArgumentList": [
+          {
+            "Literal": "\"Nothing happens.\""
+          }
+        ],
+        "Callee": {
+          "Identifier": "TypeError"
+        }
+      }
+    }
+  }
+]
+	`)
+
+		// 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(){
+        })()
+        ---
+[
+  {
+    "Var": [
+      [
+        "abc",
+        {
+          "Call": {
+            "ArgumentList": [],
+            "Callee": {
+              "Call": {
+                "ArgumentList": [
+                  {
+                    "Function": {
+                      "BlockStatement": []
+                    }
+                  }
+                ],
+                "Callee": {
+                  "Literal": 1
+                }
+              }
+            }
+          }
+        }
+      ]
+    ]
+  }
+]
+        `)
+
+		test(`
+        "use strict"
+        ---
+[
+  {
+    "Literal": "\"use strict\""
+  }
+]
+        `)
+
+		test(`
+        "use strict"
+        abc = 1 + 2 + 11
+        ---
+[
+  {
+    "Literal": "\"use strict\""
+  },
+  {
+    "Assign": {
+      "Left": {
+        "Identifier": "abc"
+      },
+      "Right": {
+        "BinaryExpression": {
+          "Left": {
+            "BinaryExpression": {
+              "Left": {
+                "Literal": 1
+              },
+              "Operator": "+",
+              "Right": {
+                "Literal": 2
+              }
+            }
+          },
+          "Operator": "+",
+          "Right": {
+            "Literal": 11
+          }
+        }
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        abc = function() { 'use strict' }
+        ---
+[
+  {
+    "Assign": {
+      "Left": {
+        "Identifier": "abc"
+      },
+      "Right": {
+        "Function": {
+          "BlockStatement": [
+            {
+              "Literal": "'use strict'"
+            }
+          ]
+        }
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        for (var abc in def) {
+        }
+        ---
+[
+  {
+    "ForIn": {
+      "Body": {
+        "BlockStatement": []
+      },
+      "Into": [
+        "abc",
+        null
+      ],
+      "Source": {
+        "Identifier": "def"
+      }
+    }
+  }
+]
+        `)
+
+		test(`
+        abc = {
+            '"': "'",
+            "'": '"',
+        }
+        ---
+[
+  {
+    "Assign": {
+      "Left": {
+        "Identifier": "abc"
+      },
+      "Right": {
+        "Object": [
+          {
+            "Key": "\"",
+            "Value": {
+              "Literal": "\"'\""
+            }
+          },
+          {
+            "Key": "'",
+            "Value": {
+              "Literal": "'\"'"
+            }
+          }
+        ]
+      }
+    }
+  }
+]
+            `)
+
+		return
+
+		test(`
+        if (!abc && abc.jkl(def) && abc[0] === +abc[0] && abc.length < ghi) {
+        }
+        ---
+[
+  {
+    "If": {
+      "Consequent": {
+        "BlockStatement": []
+      },
+      "Test": {
+        "BinaryExpression": {
+          "Left": {
+            "BinaryExpression": {
+              "Left": {
+                "BinaryExpression": {
+                  "Left": null,
+                  "Operator": "\u0026\u0026",
+                  "Right": {
+                    "Call": {
+                      "ArgumentList": [
+                        {
+                          "Identifier": "def"
+                        }
+                      ],
+                      "Callee": {
+                        "Dot": {
+                          "Left": {
+                            "Identifier": "abc"
+                          },
+                          "Member": "jkl"
+                        }
+                      }
+                    }
+                  }
+                }
+              },
+              "Operator": "\u0026\u0026",
+              "Right": {
+                "BinaryExpression": {
+                  "Left": null,
+                  "Operator": "===",
+                  "Right": null
+                }
+              }
+            }
+          },
+          "Operator": "\u0026\u0026",
+          "Right": {
+            "BinaryExpression": {
+              "Left": {
+                "Dot": {
+                  "Left": {
+                    "Identifier": "abc"
+                  },
+                  "Member": "length"
+                }
+              },
+              "Operator": "\u003c",
+              "Right": {
+                "Identifier": "ghi"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+]
+        `)
+	})
+}

+ 272 - 0
parser/parser.go

@@ -0,0 +1,272 @@
+/*
+Package parser implements a parser for JavaScript.
+
+    import (
+        "github.com/dop251/goja/parser"
+    )
+
+Parse and return an AST
+
+    filename := "" // A filename is optional
+    src := `
+        // Sample xyzzy example
+        (function(){
+            if (3.14159 > 0) {
+                console.log("Hello, World.");
+                return;
+            }
+
+            var xyzzy = NaN;
+            console.log("Nothing happens.");
+            return xyzzy;
+        })();
+    `
+
+    // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
+    program, err := parser.ParseFile(nil, filename, src, 0)
+
+Warning
+
+The parser and AST interfaces are still works-in-progress (particularly where
+node types are concerned) and may change in the future.
+
+*/
+package parser
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"io/ioutil"
+
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+	"github.com/dop251/goja/token"
+)
+
+// A Mode value is a set of flags (or 0). They control optional parser functionality.
+type Mode uint
+
+const (
+	IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
+)
+
+type _parser struct {
+	str      string
+	length   int
+	base     int
+
+	chr       rune // The current character
+	chrOffset int  // The offset of current character
+	offset    int  // The offset after current character (may be greater than 1)
+
+	idx     file.Idx    // The index of token
+	token   token.Token // The token
+	literal string      // The literal of the token, if any
+
+	scope             *_scope
+	insertSemicolon   bool // If we see a newline, then insert an implicit semicolon
+	implicitSemicolon bool // An implicit semicolon exists
+
+	errors ErrorList
+
+	recover struct {
+		// Scratch when trying to seek to the next statement, etc.
+		idx   file.Idx
+		count int
+	}
+
+	mode Mode
+
+	file *file.File
+}
+
+func _newParser(filename, src string, base int) *_parser {
+	return &_parser{
+		chr:    ' ', // This is set so we can start scanning by skipping whitespace
+		str:    src,
+		length: len(src),
+		base:   base,
+		file:   file.NewFile(filename, src, base),
+	}
+}
+
+func newParser(filename, src string) *_parser {
+	return _newParser(filename, src, 1)
+}
+
+func ReadSource(filename string, src interface{}) ([]byte, error) {
+	if src != nil {
+		switch src := src.(type) {
+		case string:
+			return []byte(src), nil
+		case []byte:
+			return src, nil
+		case *bytes.Buffer:
+			if src != nil {
+				return src.Bytes(), nil
+			}
+		case io.Reader:
+			var bfr bytes.Buffer
+			if _, err := io.Copy(&bfr, src); err != nil {
+				return nil, err
+			}
+			return bfr.Bytes(), nil
+		}
+		return nil, errors.New("invalid source")
+	}
+	return ioutil.ReadFile(filename)
+}
+
+// ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
+// the corresponding ast.Program node.
+//
+// If fileSet == nil, ParseFile parses source without a FileSet.
+// If fileSet != nil, ParseFile first adds filename and src to fileSet.
+//
+// The filename argument is optional and is used for labelling errors, etc.
+//
+// src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
+//
+//      // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
+//      program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
+//
+func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) {
+	str, err := ReadSource(filename, src)
+	if err != nil {
+		return nil, err
+	}
+	{
+		str := string(str)
+
+		base := 1
+		if fileSet != nil {
+			base = fileSet.AddFile(filename, str)
+		}
+
+		parser := _newParser(filename, str, base)
+		parser.mode = mode
+		return parser.parse()
+	}
+}
+
+// ParseFunction parses a given parameter list and body as a function and returns the
+// corresponding ast.FunctionLiteral node.
+//
+// The parameter list, if any, should be a comma-separated list of identifiers.
+//
+func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
+
+	src := "(function(" + parameterList + ") {\n" + body + "\n})"
+
+	parser := _newParser("", src, 1)
+	program, err := parser.parse()
+	if err != nil {
+		return nil, err
+	}
+
+	return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
+}
+
+func (self *_parser) slice(idx0, idx1 file.Idx) string {
+	from := int(idx0) - self.base
+	to := int(idx1) - self.base
+	if from >= 0 && to <= len(self.str) {
+		return self.str[from:to]
+	}
+
+	return ""
+}
+
+func (self *_parser) parse() (*ast.Program, error) {
+	self.next()
+	program := self.parseProgram()
+	if false {
+		self.errors.Sort()
+	}
+	return program, self.errors.Err()
+}
+
+func (self *_parser) next() {
+	self.token, self.literal, self.idx = self.scan()
+}
+
+func (self *_parser) optionalSemicolon() {
+	if self.token == token.SEMICOLON {
+		self.next()
+		return
+	}
+
+	if self.implicitSemicolon {
+		self.implicitSemicolon = false
+		return
+	}
+
+	if self.token != token.EOF && self.token != token.RIGHT_BRACE {
+		self.expect(token.SEMICOLON)
+	}
+}
+
+func (self *_parser) semicolon() {
+	if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
+		if self.implicitSemicolon {
+			self.implicitSemicolon = false
+			return
+		}
+
+		self.expect(token.SEMICOLON)
+	}
+}
+
+func (self *_parser) idxOf(offset int) file.Idx {
+	return file.Idx(self.base + offset)
+}
+
+func (self *_parser) expect(value token.Token) file.Idx {
+	idx := self.idx
+	if self.token != value {
+		self.errorUnexpectedToken(self.token)
+	}
+	self.next()
+	return idx
+}
+
+func lineCount(str string) (int, int) {
+	line, last := 0, -1
+	pair := false
+	for index, chr := range str {
+		switch chr {
+		case '\r':
+			line += 1
+			last = index
+			pair = true
+			continue
+		case '\n':
+			if !pair {
+				line += 1
+			}
+			last = index
+		case '\u2028', '\u2029':
+			line += 1
+			last = index + 2
+		}
+		pair = false
+	}
+	return line, last
+}
+
+func (self *_parser) position(idx file.Idx) file.Position {
+	position := file.Position{}
+	offset := int(idx) - self.base
+	str := self.str[:offset]
+	position.Filename = self.file.Name()
+	line, last := lineCount(str)
+	position.Line = 1 + line
+	if last >= 0 {
+		position.Column = offset - last
+	} else {
+		position.Column = 1 + len(str)
+	}
+
+	return position
+}

+ 994 - 0
parser/parser_test.go

@@ -0,0 +1,994 @@
+package parser
+
+import (
+	"errors"
+	"regexp"
+	"strings"
+	"testing"
+
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/file"
+)
+
+func firstErr(err error) error {
+	switch err := err.(type) {
+	case ErrorList:
+		return err[0]
+	}
+	return err
+}
+
+var matchBeforeAfterSeparator = regexp.MustCompile(`(?m)^[ \t]*---$`)
+
+func testParse(src string) (parser *_parser, program *ast.Program, err error) {
+	defer func() {
+		if tmp := recover(); tmp != nil {
+			switch tmp := tmp.(type) {
+			case string:
+				if strings.HasPrefix(tmp, "SyntaxError:") {
+					parser = nil
+					program = nil
+					err = errors.New(tmp)
+					return
+				}
+			}
+			panic(tmp)
+		}
+	}()
+	parser = newParser("", src)
+	program, err = parser.parse()
+	return
+}
+
+func TestParseFile(t *testing.T) {
+	tt(t, func() {
+		_, err := ParseFile(nil, "", `/abc/`, 0)
+		is(err, nil)
+
+		_, err = ParseFile(nil, "", `/(?!def)abc/`, IgnoreRegExpErrors)
+		is(err, nil)
+
+		_, err = ParseFile(nil, "", `/(?!def)abc/; return`, IgnoreRegExpErrors)
+		is(err, "(anonymous): Line 1:15 Illegal return statement")
+	})
+}
+
+func TestParseFunction(t *testing.T) {
+	tt(t, func() {
+		test := func(prm, bdy string, expect interface{}) *ast.FunctionLiteral {
+			function, err := ParseFunction(prm, bdy)
+			is(firstErr(err), expect)
+			return function
+		}
+
+		test("a, b,c,d", "", nil)
+
+		test("a, b;,c,d", "", "(anonymous): Line 1:15 Unexpected token ;")
+
+		test("this", "", "(anonymous): Line 1:11 Unexpected token this")
+
+		test("a, b, c, null", "", "(anonymous): Line 1:20 Unexpected token null")
+
+		test("a, b,c,d", "return;", nil)
+
+		test("a, b,c,d", "break;", "(anonymous): Line 2:1 Illegal break statement")
+
+		test("a, b,c,d", "{}", nil)
+	})
+}
+
+func TestParserErr(t *testing.T) {
+	tt(t, func() {
+		test := func(input string, expect interface{}) (*ast.Program, *_parser) {
+			parser := newParser("", input)
+			program, err := parser.parse()
+			is(firstErr(err), expect)
+			return program, parser
+		}
+
+		program, parser := test("", nil)
+
+		program, parser = test(`
+        var abc;
+        break; do {
+        } while(true);
+    `, "(anonymous): Line 3:9 Illegal break statement")
+		{
+			stmt := program.Body[1].(*ast.BadStatement)
+			is(parser.position(stmt.From).Column, 9)
+			is(parser.position(stmt.To).Column, 16)
+			is(parser.slice(stmt.From, stmt.To), "break; ")
+		}
+
+		test("{", "(anonymous): Line 1:2 Unexpected end of input")
+
+		test("}", "(anonymous): Line 1:1 Unexpected token }")
+
+		test("3ea", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3in", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3in []", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3e", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3e+", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3e-", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3x", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("3x0", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("0x", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("09", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("018", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("01.0", "(anonymous): Line 1:3 Unexpected number")
+
+		test("01a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("0x3in[]", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\"Hello\nWorld\"", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\u203f = 10", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("x\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("x\\\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("x\\u005c", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("x\\u002a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("x\\\\u002a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("/\n", "(anonymous): Line 1:1 Invalid regular expression: missing /")
+
+		test("0 = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
+
+		test("func() = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
+
+		test("(1 + 1) = 2", "(anonymous): Line 1:2 Invalid left-hand side in assignment")
+
+		test("1++", "(anonymous): Line 1:2 Invalid left-hand side in assignment")
+
+		test("1--", "(anonymous): Line 1:2 Invalid left-hand side in assignment")
+
+		test("--1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
+
+		test("for((1 + 1) in abc) def();", "(anonymous): Line 1:1 Invalid left-hand side in for-in")
+
+		test("[", "(anonymous): Line 1:2 Unexpected end of input")
+
+		test("[,", "(anonymous): Line 1:3 Unexpected end of input")
+
+		test("1 + {", "(anonymous): Line 1:6 Unexpected end of input")
+
+		test("1 + { abc:abc", "(anonymous): Line 1:14 Unexpected end of input")
+
+		test("1 + { abc:abc,", "(anonymous): Line 1:15 Unexpected end of input")
+
+		test("var abc = /\n/", "(anonymous): Line 1:11 Invalid regular expression: missing /")
+
+		test("var abc = \"\n", "(anonymous): Line 1:11 Unexpected token ILLEGAL")
+
+		test("var if = 0", "(anonymous): Line 1:5 Unexpected token if")
+
+		test("abc + 0 = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
+
+		test("+abc = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
+
+		test("1 + (", "(anonymous): Line 1:6 Unexpected end of input")
+
+		test("\n\n\n{", "(anonymous): Line 4:2 Unexpected end of input")
+
+		test("\n/* Some multiline\ncomment */\n)", "(anonymous): Line 4:1 Unexpected token )")
+
+		// 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 abc(if) {}", "(anonymous): Line 1:14 Unexpected token if")
+
+		test("function abc(true) {}", "(anonymous): Line 1:14 Unexpected token true")
+
+		test("function abc(false) {}", "(anonymous): Line 1:14 Unexpected token false")
+
+		test("function abc(null) {}", "(anonymous): Line 1:14 Unexpected token null")
+
+		test("function null() {}", "(anonymous): Line 1:10 Unexpected token null")
+
+		test("function true() {}", "(anonymous): Line 1:10 Unexpected token true")
+
+		test("function false() {}", "(anonymous): Line 1:10 Unexpected token false")
+
+		test("function if() {}", "(anonymous): Line 1:10 Unexpected token if")
+
+		test("a b;", "(anonymous): Line 1:3 Unexpected identifier")
+
+		test("if.a", "(anonymous): Line 1:3 Unexpected token .")
+
+		test("a if", "(anonymous): Line 1:3 Unexpected token if")
+
+		test("a class", "(anonymous): Line 1:3 Unexpected reserved word")
+
+		test("break\n", "(anonymous): Line 1:1 Illegal break statement")
+
+		test("break 1;", "(anonymous): Line 1:7 Unexpected number")
+
+		test("for (;;) { break 1; }", "(anonymous): Line 1:18 Unexpected number")
+
+		test("continue\n", "(anonymous): Line 1:1 Illegal continue statement")
+
+		test("continue 1;", "(anonymous): Line 1:10 Unexpected number")
+
+		test("for (;;) { continue 1; }", "(anonymous): Line 1:21 Unexpected number")
+
+		test("throw", "(anonymous): Line 1:1 Unexpected end of input")
+
+		test("throw;", "(anonymous): Line 1:6 Unexpected token ;")
+
+		test("throw \n", "(anonymous): Line 1:1 Unexpected end of input")
+
+		test("for (var abc, def in {});", "(anonymous): Line 1:19 Unexpected token in")
+
+		test("for ((abc in {});;);", nil)
+
+		test("for ((abc in {}));", "(anonymous): Line 1:17 Unexpected token )")
+
+		test("for (+abc in {});", "(anonymous): Line 1:1 Invalid left-hand side in for-in")
+
+		test("if (false)", "(anonymous): Line 1:11 Unexpected end of input")
+
+		test("if (false) abc(); else", "(anonymous): Line 1:23 Unexpected end of input")
+
+		test("do", "(anonymous): Line 1:3 Unexpected end of input")
+
+		test("while (false)", "(anonymous): Line 1:14 Unexpected end of input")
+
+		test("for (;;)", "(anonymous): Line 1:9 Unexpected end of input")
+
+		test("with (abc)", "(anonymous): Line 1:11 Unexpected end of input")
+
+		test("try {}", "(anonymous): Line 1:1 Missing catch or finally after try")
+
+		test("try {} catch {}", "(anonymous): Line 1:14 Unexpected token {")
+
+		test("try {} catch () {}", "(anonymous): Line 1:15 Unexpected token )")
+
+		test("\u203f = 1", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		// TODO
+		// const x = 12, y;
+		// const x, y = 12;
+		// const x;
+		// if(true) let a = 1;
+		// if(true) const  a = 1;
+
+		test(`new abc()."def"`, "(anonymous): Line 1:11 Unexpected string")
+
+		test("/*", "(anonymous): Line 1:3 Unexpected end of input")
+
+		test("/**", "(anonymous): Line 1:4 Unexpected end of input")
+
+		test("/*\n\n\n", "(anonymous): Line 4:1 Unexpected end of input")
+
+		test("/*\n\n\n*", "(anonymous): Line 4:2 Unexpected end of input")
+
+		test("/*abc", "(anonymous): Line 1:6 Unexpected end of input")
+
+		test("/*abc  *", "(anonymous): Line 1:9 Unexpected end of input")
+
+		test("\n]", "(anonymous): Line 2:1 Unexpected token ]")
+
+		test("\r\n]", "(anonymous): Line 2:1 Unexpected token ]")
+
+		test("\n\r]", "(anonymous): Line 3:1 Unexpected token ]")
+
+		test("//\r\n]", "(anonymous): Line 2:1 Unexpected token ]")
+
+		test("//\n\r]", "(anonymous): Line 3:1 Unexpected token ]")
+
+		test("/abc\\\n/", "(anonymous): Line 1:1 Invalid regular expression: missing /")
+
+		test("//\r \n]", "(anonymous): Line 3:1 Unexpected token ]")
+
+		test("/*\r\n*/]", "(anonymous): Line 2:3 Unexpected token ]")
+
+		test("/*\r \n*/]", "(anonymous): Line 3:3 Unexpected token ]")
+
+		test("\\\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\\u005c", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\\abc", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\\u0000", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\\u200c = []", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("\\u200D = []", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test(`"\`, "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test(`"\u`, "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("return", "(anonymous): Line 1:1 Illegal return statement")
+
+		test("continue", "(anonymous): Line 1:1 Illegal continue statement")
+
+		test("break", "(anonymous): Line 1:1 Illegal break statement")
+
+		test("switch (abc) { default: continue; }", "(anonymous): Line 1:25 Illegal continue statement")
+
+		test("do { abc } *", "(anonymous): Line 1:12 Unexpected token *")
+
+		test("while (true) { break abc; }", "(anonymous): Line 1:16 Undefined label 'abc'")
+
+		test("while (true) { continue abc; }", "(anonymous): Line 1:16 Undefined label 'abc'")
+
+		test("abc: while (true) { (function(){ break abc; }); }", "(anonymous): Line 1:34 Undefined label 'abc'")
+
+		test("abc: while (true) { (function(){ abc: break abc; }); }", nil)
+
+		test("abc: while (true) { (function(){ continue abc; }); }", "(anonymous): Line 1:34 Undefined label 'abc'")
+
+		test(`abc: if (0) break abc; else {}`, nil)
+
+		test(`abc: if (0) { break abc; } else {}`, nil)
+
+		test(`abc: if (0) { break abc } else {}`, nil)
+
+		test("abc: while (true) { abc: while (true) {} }", "(anonymous): Line 1:21 Label 'abc' already exists")
+
+		if false {
+			// TODO When strict mode is implemented
+			test("(function () { 'use strict'; delete abc; }())", "")
+		}
+
+		test("_: _: while (true) {]", "(anonymous): Line 1:4 Label '_' already exists")
+
+		test("_:\n_:\nwhile (true) {]", "(anonymous): Line 2:1 Label '_' already exists")
+
+		test("_:\n   _:\nwhile (true) {]", "(anonymous): Line 2:4 Label '_' already exists")
+
+		test("function(){}", "(anonymous): Line 1:9 Unexpected token (")
+
+		test("\n/*/", "(anonymous): Line 2:4 Unexpected end of input")
+
+		test("/*/.source", "(anonymous): Line 1:11 Unexpected end of input")
+
+		test("var class", "(anonymous): Line 1:5 Unexpected reserved word")
+
+		test("var if", "(anonymous): Line 1:5 Unexpected token if")
+
+		test("object Object", "(anonymous): Line 1:8 Unexpected identifier")
+
+		test("[object Object]", "(anonymous): Line 1:9 Unexpected identifier")
+
+		test("\\u0xyz", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test(`for (var abc, def in {}) {}`, "(anonymous): Line 1:19 Unexpected token in")
+
+		test(`for (abc, def in {}) {}`, "(anonymous): Line 1:1 Invalid left-hand side in for-in")
+
+		test(`for (var abc=def, ghi=("abc" in {}); true;) {}`, nil)
+
+		{
+			// Semicolon insertion
+
+			test("this\nif (1);", nil)
+
+			test("while (1) { break\nif (1); }", nil)
+
+			test("throw\nif (1);", "(anonymous): Line 1:1 Illegal newline after throw")
+
+			test("(function(){ return\nif (1); })", nil)
+
+			test("while (1) { continue\nif (1); }", nil)
+
+			test("debugger\nif (1);", nil)
+		}
+
+		{ // Reserved words
+
+			test("class", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.class = 1", nil)
+			test("var class;", "(anonymous): Line 1:5 Unexpected reserved word")
+
+			test("const", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.const = 1", nil)
+			test("var const;", "(anonymous): Line 1:5 Unexpected reserved word")
+
+			test("enum", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.enum = 1", nil)
+			test("var enum;", "(anonymous): Line 1:5 Unexpected reserved word")
+
+			test("export", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.export = 1", nil)
+			test("var export;", "(anonymous): Line 1:5 Unexpected reserved word")
+
+			test("extends", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.extends = 1", nil)
+			test("var extends;", "(anonymous): Line 1:5 Unexpected reserved word")
+
+			test("import", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.import = 1", nil)
+			test("var import;", "(anonymous): Line 1:5 Unexpected reserved word")
+
+			test("super", "(anonymous): Line 1:1 Unexpected reserved word")
+			test("abc.super = 1", nil)
+			test("var super;", "(anonymous): Line 1:5 Unexpected reserved word")
+		}
+
+		{ // Reserved words (strict)
+
+			test(`implements`, nil)
+			test(`abc.implements = 1`, nil)
+			test(`var implements;`, nil)
+
+			test(`interface`, nil)
+			test(`abc.interface = 1`, nil)
+			test(`var interface;`, nil)
+
+			test(`let`, nil)
+			test(`abc.let = 1`, nil)
+			test(`var let;`, nil)
+
+			test(`package`, nil)
+			test(`abc.package = 1`, nil)
+			test(`var package;`, nil)
+
+			test(`private`, nil)
+			test(`abc.private = 1`, nil)
+			test(`var private;`, nil)
+
+			test(`protected`, nil)
+			test(`abc.protected = 1`, nil)
+			test(`var protected;`, nil)
+
+			test(`public`, nil)
+			test(`abc.public = 1`, nil)
+			test(`var public;`, nil)
+
+			test(`static`, nil)
+			test(`abc.static = 1`, nil)
+			test(`var static;`, nil)
+
+			test(`yield`, nil)
+			test(`abc.yield = 1`, nil)
+			test(`var yield;`, nil)
+		}
+	})
+}
+
+func TestParser(t *testing.T) {
+	tt(t, func() {
+		test := func(source string, chk interface{}) *ast.Program {
+			_, program, err := testParse(source)
+			is(firstErr(err), chk)
+			return program
+		}
+
+		test(`
+            abc
+            --
+            []
+        `, "(anonymous): Line 3:13 Invalid left-hand side in assignment")
+
+		test(`
+            abc--
+            []
+        `, nil)
+
+		test("1\n[]\n", "(anonymous): Line 2:2 Unexpected token ]")
+
+		test(`
+            function abc() {
+            }
+            abc()
+        `, nil)
+
+		program := test("", nil)
+
+		test("//", nil)
+
+		test("/* */", nil)
+
+		test("/** **/", nil)
+
+		test("/*****/", nil)
+
+		test("/*", "(anonymous): Line 1:3 Unexpected end of input")
+
+		test("#", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("/**/#", "(anonymous): Line 1:5 Unexpected token ILLEGAL")
+
+		test("new +", "(anonymous): Line 1:5 Unexpected token +")
+
+		program = test(";", nil)
+		is(len(program.Body), 1)
+		is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1))
+
+		program = test(";;", nil)
+		is(len(program.Body), 2)
+		is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1))
+		is(program.Body[1].(*ast.EmptyStatement).Semicolon, file.Idx(2))
+
+		program = test("1.2", nil)
+		is(len(program.Body), 1)
+		is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.NumberLiteral).Literal, "1.2")
+
+		program = test("/* */1.2", nil)
+		is(len(program.Body), 1)
+		is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.NumberLiteral).Literal, "1.2")
+
+		program = test("\n", nil)
+		is(len(program.Body), 0)
+
+		test(`
+            if (0) {
+                abc = 0
+            }
+            else abc = 0
+        `, nil)
+
+		test("if (0) abc = 0 else abc = 0", "(anonymous): Line 1:16 Unexpected token else")
+
+		test(`
+            if (0) {
+                abc = 0
+            } else abc = 0
+        `, nil)
+
+		test(`
+            if (0) {
+                abc = 1
+            } else {
+            }
+        `, nil)
+
+		test(`
+            do {
+            } while (true)
+        `, nil)
+
+		test(`
+            try {
+            } finally {
+            }
+        `, nil)
+
+		test(`
+            try {
+            } catch (abc) {
+            } finally {
+            }
+        `, nil)
+
+		test(`
+            try {
+            }
+            catch (abc) {
+            }
+            finally {
+            }
+        `, nil)
+
+		test(`try {} catch (abc) {} finally {}`, nil)
+
+		test(`
+            do {
+                do {
+                } while (0)
+            } while (0)
+        `, nil)
+
+		test(`
+            (function(){
+                try {
+                    if (
+                        1
+                    ) {
+                        return 1
+                    }
+                    return 0
+                } finally {
+                }
+            })()
+        `, nil)
+
+		test("abc = ''\ndef", nil)
+
+		test("abc = 1\ndef", nil)
+
+		test("abc = Math\ndef", nil)
+
+		test(`"\'"`, nil)
+
+		test(`
+            abc = function(){
+            }
+            abc = 0
+        `, nil)
+
+		test("abc.null = 0", nil)
+
+		test("0x41", nil)
+
+		test(`"\d"`, nil)
+
+		test(`(function(){return this})`, nil)
+
+		test(`
+            Object.defineProperty(Array.prototype, "0", {
+                value: 100,
+                writable: false,
+                configurable: true
+            });
+            abc = [101];
+            abc.hasOwnProperty("0") && abc[0] === 101;
+        `, nil)
+
+		test(`new abc()`, nil)
+		test(`new {}`, nil)
+
+		test(`
+            limit = 4
+            result = 0
+            while (limit) {
+                limit = limit - 1
+                if (limit) {
+                }
+                else {
+                    break
+                }
+                result = result + 1
+            }
+        `, nil)
+
+		test(`
+            while (0) {
+                if (0) {
+                    continue
+                }
+            }
+        `, nil)
+
+		test("var \u0061\u0062\u0063 = 0", nil)
+
+		// 7_3_1
+		test("var test7_3_1\nabc = 66;", nil)
+		test("var test7_3_1\u2028abc = 66;", nil)
+
+		// 7_3_3
+		test("//\u2028 =;", "(anonymous): Line 2:2 Unexpected token =")
+
+		// 7_3_10
+		test("var abc = \u2029;", "(anonymous): Line 2:1 Unexpected token ;")
+		test("var abc = \\u2029;", "(anonymous): Line 1:11 Unexpected token ILLEGAL")
+		test("var \\u0061\\u0062\\u0063 = 0;", nil)
+
+		test("'", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		test("'\nstr\ning\n'", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
+		// S7.6_A4.3_T1
+		test(`var $\u0030 = 0;`, nil)
+
+		// S7.6.1.1_A1.1
+		test(`switch = 1`, "(anonymous): Line 1:8 Unexpected token =")
+
+		// S7.8.3_A2.1_T1
+		test(`.0 === 0.0`, nil)
+
+		// 7.8.5-1
+		test("var regExp = /\\\rn/;", "(anonymous): Line 1:14 Invalid regular expression: missing /")
+
+		// S7.8.5_A1.1_T2
+		test("var regExp = /=/;", nil)
+
+		// S7.8.5_A1.2_T1
+		test("/*/", "(anonymous): Line 1:4 Unexpected end of input")
+
+		// Sbp_7.9_A9_T3
+		test(`
+            do {
+            ;
+            } while (false) true
+        `, nil)
+
+		// S7.9_A10_T10
+		test(`
+            {a:1
+            } 3
+        `, nil)
+
+		test(`
+            abc
+            ++def
+        `, nil)
+
+		// S7.9_A5.2_T1
+		test(`
+            for(false;false
+            ) {
+            break;
+            }
+        `, "(anonymous): Line 3:13 Unexpected token )")
+
+		// S7.9_A9_T8
+		test(`
+            do {};
+            while (false)
+        `, "(anonymous): Line 2:18 Unexpected token ;")
+
+		// S8.4_A5
+		test(`
+            "x\0y"
+        `, nil)
+
+		// S9.3.1_A6_T1
+		test(`
+            10e10000
+        `, nil)
+
+		// 10.4.2-1-5
+		test(`
+            "abc\
+            def"
+        `, nil)
+
+		test("'\\\n'", nil)
+
+		test("'\\\r\n'", nil)
+
+		//// 11.13.1-1-1
+		test("42 = 42;", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
+
+		// S11.13.2_A4.2_T1.3
+		test(`
+            abc /= "1"
+        `, nil)
+
+		// 12.1-1
+		test(`
+            try{};catch(){}
+        `, "(anonymous): Line 2:13 Missing catch or finally after try")
+
+		// 12.1-3
+		test(`
+            try{};finally{}
+        `, "(anonymous): Line 2:13 Missing catch or finally after try")
+
+		// S12.6.3_A11.1_T3
+		test(`
+            while (true) {
+                break abc;
+            }
+        `, "(anonymous): Line 3:17 Undefined label 'abc'")
+
+		// S15.3_A2_T1
+		test(`var x / = 1;`, "(anonymous): Line 1:7 Unexpected token /")
+
+		test(`
+            function abc() {
+                if (0)
+                    return;
+                else {
+                }
+            }
+        `, nil)
+
+		test("//\u2028 var =;", "(anonymous): Line 2:6 Unexpected token =")
+
+		test(`
+            throw
+            {}
+        `, "(anonymous): Line 2:13 Illegal newline after throw")
+
+		// S7.6.1.1_A1.11
+		test(`
+            function = 1
+        `, "(anonymous): Line 2:22 Unexpected token =")
+
+		// S7.8.3_A1.2_T1
+		test(`0e1`, nil)
+
+		test("abc = 1; abc\n++", "(anonymous): Line 2:3 Unexpected end of input")
+
+		// ---
+
+		test("({ get abc() {} })", nil)
+
+		test(`for (abc.def in {}) {}`, nil)
+
+		test(`while (true) { break }`, nil)
+
+		test(`while (true) { continue }`, nil)
+
+		test(`abc=/^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?)|(.{0,2}\/{1}))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/,def=/^(?:(\w+:)\/{2})|(.{0,2}\/{1})?([/.]*?(?:[^?]+)?\/?)?$/`, nil)
+
+		test(`(function() { try {} catch (err) {} finally {} return })`, nil)
+
+		test(`0xde0b6b3a7640080.toFixed(0)`, nil)
+
+		test(`/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/`, nil)
+
+		test(`/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/`, nil)
+
+		test("var abc = 1;\ufeff", nil)
+
+		test("\ufeff/* var abc = 1; */", nil)
+
+		test(`if (-0x8000000000000000<=abc&&abc<=0x8000000000000000) {}`, nil)
+
+		test(`(function(){debugger;return this;})`, nil)
+
+		test(`
+
+        `, nil)
+
+		test(`
+            var abc = ""
+            debugger
+        `, nil)
+
+		test(`
+            var abc = /\[\]$/
+            debugger
+        `, nil)
+
+		test(`
+            var abc = 1 /
+                2
+            debugger
+        `, nil)
+	})
+}
+
+func Test_parseStringLiteral(t *testing.T) {
+	tt(t, func() {
+		test := func(have, want string) {
+			have, err := parseStringLiteral(have)
+			is(err, nil)
+			is(have, want)
+		}
+
+		test("", "")
+
+		test("1(\\\\d+)", "1(\\d+)")
+
+		test("\\u2029", "\u2029")
+
+		test("abc\\uFFFFabc", "abc\uFFFFabc")
+
+		test("[First line \\\nSecond line \\\n Third line\\\n.     ]",
+			"[First line Second line  Third line.     ]")
+
+		test("\\u007a\\x79\\u000a\\x78", "zy\nx")
+
+		// S7.8.4_A4.2_T3
+		test("\\a", "a")
+		test("\u0410", "\u0410")
+
+		// S7.8.4_A5.1_T1
+		test("\\0", "\u0000")
+
+		// S8.4_A5
+		test("\u0000", "\u0000")
+
+		// 15.5.4.20
+		test("'abc'\\\n'def'", "'abc''def'")
+
+		// 15.5.4.20-4-1
+		test("'abc'\\\r\n'def'", "'abc''def'")
+
+		// Octal
+		test("\\0", "\000")
+		test("\\00", "\000")
+		test("\\000", "\000")
+		test("\\09", "\0009")
+		test("\\009", "\0009")
+		test("\\0009", "\0009")
+		test("\\1", "\001")
+		test("\\01", "\001")
+		test("\\001", "\001")
+		test("\\0011", "\0011")
+		test("\\1abc", "\001abc")
+
+		test("\\\u4e16", "\u4e16")
+
+		// err
+		test = func(have, want string) {
+			have, err := parseStringLiteral(have)
+			is(err.Error(), want)
+			is(have, "")
+		}
+
+		test(`\u`, `invalid escape: \u: len("") != 4`)
+		test(`\u0`, `invalid escape: \u: len("0") != 4`)
+		test(`\u00`, `invalid escape: \u: len("00") != 4`)
+		test(`\u000`, `invalid escape: \u: len("000") != 4`)
+
+		test(`\x`, `invalid escape: \x: len("") != 2`)
+		test(`\x0`, `invalid escape: \x: len("0") != 2`)
+		test(`\x0`, `invalid escape: \x: len("0") != 2`)
+	})
+}
+
+func Test_parseNumberLiteral(t *testing.T) {
+	tt(t, func() {
+		test := func(input string, expect interface{}) {
+			result, err := parseNumberLiteral(input)
+			is(err, nil)
+			is(result, expect)
+		}
+
+		test("0", 0)
+
+		test("0x8000000000000000", float64(9.223372036854776e+18))
+	})
+}
+
+func TestPosition(t *testing.T) {
+	tt(t, func() {
+		parser := newParser("", "// Lorem ipsum")
+
+		// Out of range, idx0 (error condition)
+		is(parser.slice(0, 1), "")
+		is(parser.slice(0, 10), "")
+
+		// Out of range, idx1 (error condition)
+		is(parser.slice(1, 128), "")
+
+		is(parser.str[0:0], "")
+		is(parser.slice(1, 1), "")
+
+		is(parser.str[0:1], "/")
+		is(parser.slice(1, 2), "/")
+
+		is(parser.str[0:14], "// Lorem ipsum")
+		is(parser.slice(1, 15), "// Lorem ipsum")
+
+		parser = newParser("", "(function(){ return 0; })")
+		program, err := parser.parse()
+		is(err, nil)
+
+		var node ast.Node
+		node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral)
+		is(node.Idx0(), file.Idx(2))
+		is(node.Idx1(), file.Idx(25))
+		is(parser.slice(node.Idx0(), node.Idx1()), "function(){ return 0; }")
+		is(parser.slice(node.Idx0(), node.Idx1()+1), "function(){ return 0; })")
+		is(parser.slice(node.Idx0(), node.Idx1()+2), "")
+		is(node.(*ast.FunctionLiteral).Source, "function(){ return 0; }")
+
+		node = program
+		is(node.Idx0(), file.Idx(2))
+		is(node.Idx1(), file.Idx(25))
+		is(parser.slice(node.Idx0(), node.Idx1()), "function(){ return 0; }")
+
+		parser = newParser("", "(function(){ return abc; })")
+		program, err = parser.parse()
+		is(err, nil)
+		node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral)
+		is(node.(*ast.FunctionLiteral).Source, "function(){ return abc; }")
+	})
+}

+ 402 - 0
parser/regexp.go

@@ -0,0 +1,402 @@
+package parser
+
+import (
+	"bytes"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+const (
+	WhitespaceChars = " \f\n\r\t\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff"
+)
+
+type _RegExp_parser struct {
+	str    string
+	length int
+
+	chr       rune // The current character
+	chrOffset int  // The offset of current character
+	offset    int  // The offset after current character (may be greater than 1)
+
+	errors  []error
+	invalid bool // The input is an invalid JavaScript RegExp
+
+	goRegexp *bytes.Buffer
+}
+
+// TransformRegExp transforms a JavaScript pattern into  a Go "regexp" pattern.
+//
+// re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
+// backreference (\1, \2, ...) will cause an error.
+//
+// re2 (Go) has a different definition for \s: [\t\n\f\r ].
+// The JavaScript definition, on the other hand, also includes \v, Unicode "Separator, Space", etc.
+//
+// If the pattern is invalid (not valid even in JavaScript), then this function
+// returns the empty string and an error.
+//
+// If the pattern is valid, but incompatible (contains a lookahead or backreference),
+// then this function returns the transformation (a non-empty string) AND an error.
+func TransformRegExp(pattern string) (string, error) {
+
+	if pattern == "" {
+		return "", nil
+	}
+
+	// TODO If without \, if without (?=, (?!, then another shortcut
+
+	parser := _RegExp_parser{
+		str:      pattern,
+		length:   len(pattern),
+		goRegexp: bytes.NewBuffer(make([]byte, 0, 3*len(pattern)/2)),
+	}
+	parser.read() // Pull in the first character
+	parser.scan()
+	var err error
+	if len(parser.errors) > 0 {
+		err = parser.errors[0]
+	}
+	if parser.invalid {
+		return "", err
+	}
+
+	// Might not be re2 compatible, but is still a valid JavaScript RegExp
+	return parser.goRegexp.String(), err
+}
+
+func (self *_RegExp_parser) scan() {
+	for self.chr != -1 {
+		switch self.chr {
+		case '\\':
+			self.read()
+			self.scanEscape(false)
+		case '(':
+			self.pass()
+			self.scanGroup()
+		case '[':
+			self.scanBracket()
+		case ')':
+			self.error(-1, "Unmatched ')'")
+			self.invalid = true
+			self.pass()
+		case '.':
+			self.goRegexp.WriteString("[^\\r\\n]")
+			self.read()
+		default:
+			self.pass()
+		}
+	}
+}
+
+// (...)
+func (self *_RegExp_parser) scanGroup() {
+	str := self.str[self.chrOffset:]
+	if len(str) > 1 { // A possibility of (?= or (?!
+		if str[0] == '?' {
+			if str[1] == '=' || str[1] == '!' {
+				self.error(-1, "re2: Invalid (%s) <lookahead>", self.str[self.chrOffset:self.chrOffset+2])
+			}
+		}
+	}
+	for self.chr != -1 && self.chr != ')' {
+		switch self.chr {
+		case '\\':
+			self.read()
+			self.scanEscape(false)
+		case '(':
+			self.pass()
+			self.scanGroup()
+		case '[':
+			self.scanBracket()
+		case '.':
+			self.goRegexp.WriteString("[^\\r\\n]")
+			self.read()
+		default:
+			self.pass()
+			continue
+		}
+	}
+	if self.chr != ')' {
+		self.error(-1, "Unterminated group")
+		self.invalid = true
+		return
+	}
+	self.pass()
+}
+
+// [...]
+func (self *_RegExp_parser) scanBracket() {
+	str := self.str[self.chrOffset:]
+	if strings.HasPrefix(str, "[]") {
+		// [] -- Empty character class
+		self.goRegexp.WriteString("[^\u0000-uffff]")
+		self.offset += 1
+		self.read()
+		return
+	}
+
+	if strings.HasPrefix(str, "[^]") {
+		self.goRegexp.WriteString("[\u0000-\uffff]")
+		self.offset += 2
+		self.read()
+		return
+	}
+
+
+	self.pass()
+	for self.chr != -1 {
+		if self.chr == ']' {
+			break
+		} else if self.chr == '\\' {
+			self.read()
+			self.scanEscape(true)
+			continue
+		}
+		self.pass()
+	}
+	if self.chr != ']' {
+		self.error(-1, "Unterminated character class")
+		self.invalid = true
+		return
+	}
+	self.pass()
+}
+
+// \...
+func (self *_RegExp_parser) scanEscape(inClass bool) {
+	offset := self.chrOffset
+
+	var length, base uint32
+	switch self.chr {
+
+	case '0', '1', '2', '3', '4', '5', '6', '7':
+		var value int64
+		size := 0
+		for {
+			digit := int64(digitValue(self.chr))
+			if digit >= 8 {
+				// Not a valid digit
+				break
+			}
+			value = value*8 + digit
+			self.read()
+			size += 1
+		}
+		if size == 1 { // The number of characters read
+			_, err := self.goRegexp.Write([]byte{'\\', byte(value) + '0'})
+			if err != nil {
+				self.errors = append(self.errors, err)
+			}
+			if value != 0 {
+				// An invalid backreference
+				self.error(-1, "re2: Invalid \\%d <backreference>", value)
+			}
+			return
+		}
+		tmp := []byte{'\\', 'x', '0', 0}
+		if value >= 16 {
+			tmp = tmp[0:2]
+		} else {
+			tmp = tmp[0:3]
+		}
+		tmp = strconv.AppendInt(tmp, value, 16)
+		_, err := self.goRegexp.Write(tmp)
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+		return
+
+	case '8', '9':
+		size := 0
+		for {
+			digit := digitValue(self.chr)
+			if digit >= 10 {
+				// Not a valid digit
+				break
+			}
+			self.read()
+			size += 1
+		}
+		err := self.goRegexp.WriteByte('\\')
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+		_, err = self.goRegexp.WriteString(self.str[offset:self.chrOffset])
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+		self.error(-1, "re2: Invalid \\%s <backreference>", self.str[offset:self.chrOffset])
+		return
+
+	case 'x':
+		self.read()
+		length, base = 2, 16
+
+	case 'u':
+		self.read()
+		length, base = 4, 16
+
+	case 'b':
+		if inClass {
+			_, err := self.goRegexp.Write([]byte{'\\', 'x', '0', '8'})
+			if err != nil {
+				self.errors = append(self.errors, err)
+			}
+			self.read()
+			return
+		}
+		fallthrough
+
+	case 'B':
+		fallthrough
+
+	case 'd', 'D', 'w', 'W':
+		// This is slightly broken, because ECMAScript
+		// includes \v in \s, \S, while re2 does not
+		fallthrough
+
+	case '\\':
+		fallthrough
+
+	case 'f', 'n', 'r', 't', 'v':
+		err := self.goRegexp.WriteByte('\\')
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+		self.pass()
+		return
+
+	case 'c':
+		self.read()
+		var value int64
+		if 'a' <= self.chr && self.chr <= 'z' {
+			value = int64(self.chr) - 'a' + 1
+		} else if 'A' <= self.chr && self.chr <= 'Z' {
+			value = int64(self.chr) - 'A' + 1
+		} else {
+			err := self.goRegexp.WriteByte('c')
+			if err != nil {
+				self.errors = append(self.errors, err)
+			}
+			return
+		}
+		tmp := []byte{'\\', 'x', '0', 0}
+		if value >= 16 {
+			tmp = tmp[0:2]
+		} else {
+			tmp = tmp[0:3]
+		}
+		tmp = strconv.AppendInt(tmp, value, 16)
+		_, err := self.goRegexp.Write(tmp)
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+		self.read()
+		return
+	case 's':
+		if inClass {
+			self.goRegexp.WriteString(WhitespaceChars)
+		} else {
+			self.goRegexp.WriteString("[" + WhitespaceChars + "]")
+		}
+		self.read()
+		return
+	case 'S':
+		if inClass {
+			self.error(self.chrOffset, "S in class")
+			self.invalid = true
+			return
+		} else {
+			self.goRegexp.WriteString("[^" + WhitespaceChars + "]")
+		}
+		self.read()
+		return
+	default:
+		// $ is an identifier character, so we have to have
+		// a special case for it here
+		if self.chr == '$' || !isIdentifierPart(self.chr) {
+			// A non-identifier character needs escaping
+			err := self.goRegexp.WriteByte('\\')
+			if err != nil {
+				self.errors = append(self.errors, err)
+			}
+		} else {
+			// Unescape the character for re2
+		}
+		self.pass()
+		return
+	}
+
+	// Otherwise, we're a \u.... or \x...
+	valueOffset := self.chrOffset
+
+	var value uint32
+	{
+		length := length
+		for ; length > 0; length-- {
+			digit := uint32(digitValue(self.chr))
+			if digit >= base {
+				// Not a valid digit
+				goto skip
+			}
+			value = value*base + digit
+			self.read()
+		}
+	}
+
+	if length == 4 {
+		_, err := self.goRegexp.Write([]byte{
+			'\\',
+			'x',
+			'{',
+			self.str[valueOffset+0],
+			self.str[valueOffset+1],
+			self.str[valueOffset+2],
+			self.str[valueOffset+3],
+			'}',
+		})
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+	} else if length == 2 {
+		_, err := self.goRegexp.Write([]byte{
+			'\\',
+			'x',
+			self.str[valueOffset+0],
+			self.str[valueOffset+1],
+		})
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+	} else {
+		// Should never, ever get here...
+		self.error(-1, "re2: Illegal branch in scanEscape")
+		goto skip
+	}
+
+	return
+
+skip:
+	_, err := self.goRegexp.WriteString(self.str[offset:self.chrOffset])
+	if err != nil {
+		self.errors = append(self.errors, err)
+	}
+}
+
+func (self *_RegExp_parser) pass() {
+	if self.chr != -1 {
+		_, err := self.goRegexp.WriteRune(self.chr)
+		if err != nil {
+			self.errors = append(self.errors, err)
+		}
+	}
+	self.read()
+}
+
+// TODO Better error reporting, use the offset, etc.
+func (self *_RegExp_parser) error(offset int, msg string, msgValues ...interface{}) error {
+	err := fmt.Errorf(msg, msgValues...)
+	self.errors = append(self.errors, err)
+	return err
+}

+ 127 - 0
parser/regexp_test.go

@@ -0,0 +1,127 @@
+package parser
+
+import (
+	"regexp"
+	"testing"
+)
+
+func TestRegExp(t *testing.T) {
+	tt(t, func() {
+		{
+			// err
+			test := func(input string, expect interface{}) {
+				_, err := TransformRegExp(input)
+				is(err, expect)
+			}
+
+			test("[", "Unterminated character class")
+
+			test("(", "Unterminated group")
+
+			test("\\(?=)", "Unmatched ')'")
+
+			test(")", "Unmatched ')'")
+		}
+
+		{
+			// err
+			test := func(input, expect string, expectErr interface{}) {
+				output, err := TransformRegExp(input)
+				is(output, expect)
+				is(err, expectErr)
+			}
+
+			test(")", "", "Unmatched ')'")
+
+			test("\\0", "\\0", nil)
+
+		}
+
+		{
+			// err
+			test := func(input string, expect string) {
+				result, err := TransformRegExp(input)
+				is(err, nil)
+				is(result, expect)
+				_, err = regexp.Compile(result)
+				is(err, nil)
+			}
+
+			testErr := func(input string, expectErr string) {
+				_, err := TransformRegExp(input)
+				is(err, expectErr)
+			}
+
+			test("", "")
+
+			test("abc", "abc")
+
+			test(`\abc`, `abc`)
+
+			test(`\a\b\c`, `a\bc`)
+
+			test(`\x`, `x`)
+
+			test(`\c`, `c`)
+
+			test(`\cA`, `\x01`)
+
+			test(`\cz`, `\x1a`)
+
+			test(`\ca`, `\x01`)
+
+			test(`\cj`, `\x0a`)
+
+			test(`\ck`, `\x0b`)
+
+			test(`\+`, `\+`)
+
+			test(`[\b]`, `[\x08]`)
+
+			test(`\u0z01\x\undefined`, `u0z01xundefined`)
+
+			test(`\\|'|\r|\n|\t|\u2028|\u2029`, `\\|'|\r|\n|\t|\x{2028}|\x{2029}`)
+
+			test("]", "]")
+
+			test("}", "}")
+
+			test("%", "%")
+
+			test("(%)", "(%)")
+
+			test("(?:[%\\s])", "(?:[%" + WhitespaceChars +"])")
+
+			test("[[]", "[[]")
+
+			test("\\101", "\\x41")
+
+			test("\\51", "\\x29")
+
+			test("\\051", "\\x29")
+
+			test("\\175", "\\x7d")
+
+			test("\\04", "\\x04")
+
+			testErr(`<%([\s\S]+?)%>`, "S in class")
+
+			test(`(.)^`, "([^\\r\\n])^")
+
+			test(`\$`, `\$`)
+
+			test(`[G-b]`, `[G-b]`)
+
+			test(`[G-b\0]`, `[G-b\0]`)
+		}
+	})
+}
+
+func TestTransformRegExp(t *testing.T) {
+	tt(t, func() {
+		pattern, err := TransformRegExp(`\s+abc\s+`)
+		is(err, nil)
+		is(pattern, `[` + WhitespaceChars + `]+abc[` + WhitespaceChars +`]+`)
+		is(regexp.MustCompile(pattern).MatchString("\t abc def"), true)
+	})
+}

+ 44 - 0
parser/scope.go

@@ -0,0 +1,44 @@
+package parser
+
+import (
+	"github.com/dop251/goja/ast"
+)
+
+type _scope struct {
+	outer           *_scope
+	allowIn         bool
+	inIteration     bool
+	inSwitch        bool
+	inFunction      bool
+	declarationList []ast.Declaration
+
+	labels []string
+}
+
+func (self *_parser) openScope() {
+	self.scope = &_scope{
+		outer:   self.scope,
+		allowIn: true,
+	}
+}
+
+func (self *_parser) closeScope() {
+	self.scope = self.scope.outer
+}
+
+func (self *_scope) declare(declaration ast.Declaration) {
+	self.declarationList = append(self.declarationList, declaration)
+}
+
+func (self *_scope) hasLabel(name string) bool {
+	for _, label := range self.labels {
+		if label == name {
+			return true
+		}
+	}
+	if self.outer != nil && !self.inFunction {
+		// Crossing a function boundary to look for a label is verboten
+		return self.outer.hasLabel(name)
+	}
+	return false
+}

+ 663 - 0
parser/statement.go

@@ -0,0 +1,663 @@
+package parser
+
+import (
+	"github.com/dop251/goja/ast"
+	"github.com/dop251/goja/token"
+)
+
+func (self *_parser) parseBlockStatement() *ast.BlockStatement {
+	node := &ast.BlockStatement{}
+	node.LeftBrace = self.expect(token.LEFT_BRACE)
+	node.List = self.parseStatementList()
+	node.RightBrace = self.expect(token.RIGHT_BRACE)
+
+	return node
+}
+
+func (self *_parser) parseEmptyStatement() ast.Statement {
+	idx := self.expect(token.SEMICOLON)
+	return &ast.EmptyStatement{Semicolon: idx}
+}
+
+func (self *_parser) parseStatementList() (list []ast.Statement) {
+	for self.token != token.RIGHT_BRACE && self.token != token.EOF {
+		list = append(list, self.parseStatement())
+	}
+
+	return
+}
+
+func (self *_parser) parseStatement() ast.Statement {
+
+	if self.token == token.EOF {
+		self.errorUnexpectedToken(self.token)
+		return &ast.BadStatement{From: self.idx, To: self.idx + 1}
+	}
+
+	switch self.token {
+	case token.SEMICOLON:
+		return self.parseEmptyStatement()
+	case token.LEFT_BRACE:
+		return self.parseBlockStatement()
+	case token.IF:
+		return self.parseIfStatement()
+	case token.DO:
+		return self.parseDoWhileStatement()
+	case token.WHILE:
+		return self.parseWhileStatement()
+	case token.FOR:
+		return self.parseForOrForInStatement()
+	case token.BREAK:
+		return self.parseBreakStatement()
+	case token.CONTINUE:
+		return self.parseContinueStatement()
+	case token.DEBUGGER:
+		return self.parseDebuggerStatement()
+	case token.WITH:
+		return self.parseWithStatement()
+	case token.VAR:
+		return self.parseVariableStatement()
+	case token.FUNCTION:
+		self.parseFunction(true)
+		// FIXME
+		return &ast.EmptyStatement{}
+	case token.SWITCH:
+		return self.parseSwitchStatement()
+	case token.RETURN:
+		return self.parseReturnStatement()
+	case token.THROW:
+		return self.parseThrowStatement()
+	case token.TRY:
+		return self.parseTryStatement()
+	}
+
+	expression := self.parseExpression()
+
+	if identifier, isIdentifier := expression.(*ast.Identifier); isIdentifier && self.token == token.COLON {
+		// LabelledStatement
+		colon := self.idx
+		self.next() // :
+		label := identifier.Name
+		for _, value := range self.scope.labels {
+			if label == value {
+				self.error(identifier.Idx0(), "Label '%s' already exists", label)
+			}
+		}
+		self.scope.labels = append(self.scope.labels, label) // Push the label
+		statement := self.parseStatement()
+		self.scope.labels = self.scope.labels[:len(self.scope.labels)-1] // Pop the label
+		return &ast.LabelledStatement{
+			Label:     identifier,
+			Colon:     colon,
+			Statement: statement,
+		}
+	}
+
+	self.optionalSemicolon()
+
+	return &ast.ExpressionStatement{
+		Expression: expression,
+	}
+}
+
+func (self *_parser) parseTryStatement() ast.Statement {
+
+	node := &ast.TryStatement{
+		Try:  self.expect(token.TRY),
+		Body: self.parseBlockStatement(),
+	}
+
+	if self.token == token.CATCH {
+		catch := self.idx
+		self.next()
+		self.expect(token.LEFT_PARENTHESIS)
+		if self.token != token.IDENTIFIER {
+			self.expect(token.IDENTIFIER)
+			self.nextStatement()
+			return &ast.BadStatement{From: catch, To: self.idx}
+		} else {
+			identifier := self.parseIdentifier()
+			self.expect(token.RIGHT_PARENTHESIS)
+			node.Catch = &ast.CatchStatement{
+				Catch:     catch,
+				Parameter: identifier,
+				Body:      self.parseBlockStatement(),
+			}
+		}
+	}
+
+	if self.token == token.FINALLY {
+		self.next()
+		node.Finally = self.parseBlockStatement()
+	}
+
+	if node.Catch == nil && node.Finally == nil {
+		self.error(node.Try, "Missing catch or finally after try")
+		return &ast.BadStatement{From: node.Try, To: node.Body.Idx1()}
+	}
+
+	return node
+}
+
+func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
+	opening := self.expect(token.LEFT_PARENTHESIS)
+	var list []*ast.Identifier
+	for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF {
+		if self.token != token.IDENTIFIER {
+			self.expect(token.IDENTIFIER)
+		} else {
+			list = append(list, self.parseIdentifier())
+		}
+		if self.token != token.RIGHT_PARENTHESIS {
+			self.expect(token.COMMA)
+		}
+	}
+	closing := self.expect(token.RIGHT_PARENTHESIS)
+
+	return &ast.ParameterList{
+		Opening: opening,
+		List:    list,
+		Closing: closing,
+	}
+}
+
+func (self *_parser) parseParameterList() (list []string) {
+	for self.token != token.EOF {
+		if self.token != token.IDENTIFIER {
+			self.expect(token.IDENTIFIER)
+		}
+		list = append(list, self.literal)
+		self.next()
+		if self.token != token.EOF {
+			self.expect(token.COMMA)
+		}
+	}
+	return
+}
+
+func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral {
+
+	node := &ast.FunctionLiteral{
+		Function: self.expect(token.FUNCTION),
+	}
+
+	var name *ast.Identifier
+	if self.token == token.IDENTIFIER {
+		name = self.parseIdentifier()
+		if declaration {
+			self.scope.declare(&ast.FunctionDeclaration{
+				Function: node,
+			})
+		}
+	} else if declaration {
+		// Use expect error handling
+		self.expect(token.IDENTIFIER)
+	}
+	node.Name = name
+	node.ParameterList = self.parseFunctionParameterList()
+	self.parseFunctionBlock(node)
+	node.Source = self.slice(node.Idx0(), node.Idx1())
+
+	return node
+}
+
+func (self *_parser) parseFunctionBlock(node *ast.FunctionLiteral) {
+	{
+		self.openScope()
+		inFunction := self.scope.inFunction
+		self.scope.inFunction = true
+		defer func() {
+			self.scope.inFunction = inFunction
+			self.closeScope()
+		}()
+		node.Body = self.parseBlockStatement()
+		node.DeclarationList = self.scope.declarationList
+	}
+}
+
+func (self *_parser) parseDebuggerStatement() ast.Statement {
+	idx := self.expect(token.DEBUGGER)
+
+	node := &ast.DebuggerStatement{
+		Debugger: idx,
+	}
+
+	self.semicolon()
+
+	return node
+}
+
+func (self *_parser) parseReturnStatement() ast.Statement {
+	idx := self.expect(token.RETURN)
+
+	if !self.scope.inFunction {
+		self.error(idx, "Illegal return statement")
+		self.nextStatement()
+		return &ast.BadStatement{From: idx, To: self.idx}
+	}
+
+	node := &ast.ReturnStatement{
+		Return: idx,
+	}
+
+	if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE && self.token != token.EOF {
+		node.Argument = self.parseExpression()
+	}
+
+	self.semicolon()
+
+	return node
+}
+
+func (self *_parser) parseThrowStatement() ast.Statement {
+	idx := self.expect(token.THROW)
+
+	if self.implicitSemicolon {
+		if self.chr == -1 { // Hackish
+			self.error(idx, "Unexpected end of input")
+		} else {
+			self.error(idx, "Illegal newline after throw")
+		}
+		self.nextStatement()
+		return &ast.BadStatement{From: idx, To: self.idx}
+	}
+
+	node := &ast.ThrowStatement{
+		Argument: self.parseExpression(),
+	}
+
+	self.semicolon()
+
+	return node
+}
+
+func (self *_parser) parseSwitchStatement() ast.Statement {
+	self.expect(token.SWITCH)
+	self.expect(token.LEFT_PARENTHESIS)
+	node := &ast.SwitchStatement{
+		Discriminant: self.parseExpression(),
+		Default:      -1,
+	}
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	self.expect(token.LEFT_BRACE)
+
+	inSwitch := self.scope.inSwitch
+	self.scope.inSwitch = true
+	defer func() {
+		self.scope.inSwitch = inSwitch
+	}()
+
+	for index := 0; self.token != token.EOF; index++ {
+		if self.token == token.RIGHT_BRACE {
+			self.next()
+			break
+		}
+
+		clause := self.parseCaseStatement()
+		if clause.Test == nil {
+			if node.Default != -1 {
+				self.error(clause.Case, "Already saw a default in switch")
+			}
+			node.Default = index
+		}
+		node.Body = append(node.Body, clause)
+	}
+
+	return node
+}
+
+func (self *_parser) parseWithStatement() ast.Statement {
+	self.expect(token.WITH)
+	self.expect(token.LEFT_PARENTHESIS)
+	node := &ast.WithStatement{
+		Object: self.parseExpression(),
+	}
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	node.Body = self.parseStatement()
+
+	return node
+}
+
+func (self *_parser) parseCaseStatement() *ast.CaseStatement {
+
+	node := &ast.CaseStatement{
+		Case: self.idx,
+	}
+	if self.token == token.DEFAULT {
+		self.next()
+	} else {
+		self.expect(token.CASE)
+		node.Test = self.parseExpression()
+	}
+	self.expect(token.COLON)
+
+	for {
+		if self.token == token.EOF ||
+			self.token == token.RIGHT_BRACE ||
+			self.token == token.CASE ||
+			self.token == token.DEFAULT {
+			break
+		}
+		node.Consequent = append(node.Consequent, self.parseStatement())
+
+	}
+
+	return node
+}
+
+func (self *_parser) parseIterationStatement() ast.Statement {
+	inIteration := self.scope.inIteration
+	self.scope.inIteration = true
+	defer func() {
+		self.scope.inIteration = inIteration
+	}()
+	return self.parseStatement()
+}
+
+func (self *_parser) parseForIn(into ast.Expression) *ast.ForInStatement {
+
+	// Already have consumed "<into> in"
+
+	source := self.parseExpression()
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	return &ast.ForInStatement{
+		Into:   into,
+		Source: source,
+		Body:   self.parseIterationStatement(),
+	}
+}
+
+func (self *_parser) parseFor(initializer ast.Expression) *ast.ForStatement {
+
+	// Already have consumed "<initializer> ;"
+
+	var test, update ast.Expression
+
+	if self.token != token.SEMICOLON {
+		test = self.parseExpression()
+	}
+	self.expect(token.SEMICOLON)
+
+	if self.token != token.RIGHT_PARENTHESIS {
+		update = self.parseExpression()
+	}
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	return &ast.ForStatement{
+		Initializer: initializer,
+		Test:        test,
+		Update:      update,
+		Body:        self.parseIterationStatement(),
+	}
+}
+
+func (self *_parser) parseForOrForInStatement() ast.Statement {
+	idx := self.expect(token.FOR)
+	self.expect(token.LEFT_PARENTHESIS)
+
+	var left []ast.Expression
+
+	forIn := false
+	if self.token != token.SEMICOLON {
+
+		allowIn := self.scope.allowIn
+		self.scope.allowIn = false
+		if self.token == token.VAR {
+			var_ := self.idx
+			self.next()
+			list := self.parseVariableDeclarationList(var_)
+			if len(list) == 1 && self.token == token.IN {
+				self.next() // in
+				forIn = true
+				left = []ast.Expression{list[0]} // There is only one declaration
+			} else {
+				left = list
+			}
+		} else {
+			left = append(left, self.parseExpression())
+			if self.token == token.IN {
+				self.next()
+				forIn = true
+			}
+		}
+		self.scope.allowIn = allowIn
+	}
+
+	if forIn {
+		switch left[0].(type) {
+		case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression, *ast.VariableExpression:
+			// These are all acceptable
+		default:
+			self.error(idx, "Invalid left-hand side in for-in")
+			self.nextStatement()
+			return &ast.BadStatement{From: idx, To: self.idx}
+		}
+		return self.parseForIn(left[0])
+	}
+
+	self.expect(token.SEMICOLON)
+	return self.parseFor(&ast.SequenceExpression{Sequence: left})
+}
+
+func (self *_parser) parseVariableStatement() *ast.VariableStatement {
+
+	idx := self.expect(token.VAR)
+
+	list := self.parseVariableDeclarationList(idx)
+	self.semicolon()
+
+	return &ast.VariableStatement{
+		Var:  idx,
+		List: list,
+	}
+}
+
+func (self *_parser) parseDoWhileStatement() ast.Statement {
+	inIteration := self.scope.inIteration
+	self.scope.inIteration = true
+	defer func() {
+		self.scope.inIteration = inIteration
+	}()
+
+	self.expect(token.DO)
+	node := &ast.DoWhileStatement{}
+	if self.token == token.LEFT_BRACE {
+		node.Body = self.parseBlockStatement()
+	} else {
+		node.Body = self.parseStatement()
+	}
+
+	self.expect(token.WHILE)
+	self.expect(token.LEFT_PARENTHESIS)
+	node.Test = self.parseExpression()
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	return node
+}
+
+func (self *_parser) parseWhileStatement() ast.Statement {
+	self.expect(token.WHILE)
+	self.expect(token.LEFT_PARENTHESIS)
+	node := &ast.WhileStatement{
+		Test: self.parseExpression(),
+	}
+	self.expect(token.RIGHT_PARENTHESIS)
+	node.Body = self.parseIterationStatement()
+
+	return node
+}
+
+func (self *_parser) parseIfStatement() ast.Statement {
+	self.expect(token.IF)
+	self.expect(token.LEFT_PARENTHESIS)
+	node := &ast.IfStatement{
+		Test: self.parseExpression(),
+	}
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	if self.token == token.LEFT_BRACE {
+		node.Consequent = self.parseBlockStatement()
+	} else {
+		node.Consequent = self.parseStatement()
+	}
+
+	if self.token == token.ELSE {
+		self.next()
+		node.Alternate = self.parseStatement()
+	}
+
+	return node
+}
+
+func (self *_parser) parseSourceElement() ast.Statement {
+	return self.parseStatement()
+}
+
+func (self *_parser) parseSourceElements() []ast.Statement {
+	body := []ast.Statement(nil)
+
+	for {
+		if self.token != token.STRING {
+			break
+		}
+
+		body = append(body, self.parseSourceElement())
+	}
+
+	for self.token != token.EOF {
+		body = append(body, self.parseSourceElement())
+	}
+
+	return body
+}
+
+func (self *_parser) parseProgram() *ast.Program {
+	self.openScope()
+	defer self.closeScope()
+	return &ast.Program{
+		Body:            self.parseSourceElements(),
+		DeclarationList: self.scope.declarationList,
+		File:            self.file,
+	}
+}
+
+func (self *_parser) parseBreakStatement() ast.Statement {
+	idx := self.expect(token.BREAK)
+	semicolon := self.implicitSemicolon
+	if self.token == token.SEMICOLON {
+		semicolon = true
+		self.next()
+	}
+
+	if semicolon || self.token == token.RIGHT_BRACE {
+		self.implicitSemicolon = false
+		if !self.scope.inIteration && !self.scope.inSwitch {
+			goto illegal
+		}
+		return &ast.BranchStatement{
+			Idx:   idx,
+			Token: token.BREAK,
+		}
+	}
+
+	if self.token == token.IDENTIFIER {
+		identifier := self.parseIdentifier()
+		if !self.scope.hasLabel(identifier.Name) {
+			self.error(idx, "Undefined label '%s'", identifier.Name)
+			return &ast.BadStatement{From: idx, To: identifier.Idx1()}
+		}
+		self.semicolon()
+		return &ast.BranchStatement{
+			Idx:   idx,
+			Token: token.BREAK,
+			Label: identifier,
+		}
+	}
+
+	self.expect(token.IDENTIFIER)
+
+illegal:
+	self.error(idx, "Illegal break statement")
+	self.nextStatement()
+	return &ast.BadStatement{From: idx, To: self.idx}
+}
+
+func (self *_parser) parseContinueStatement() ast.Statement {
+	idx := self.expect(token.CONTINUE)
+	semicolon := self.implicitSemicolon
+	if self.token == token.SEMICOLON {
+		semicolon = true
+		self.next()
+	}
+
+	if semicolon || self.token == token.RIGHT_BRACE {
+		self.implicitSemicolon = false
+		if !self.scope.inIteration {
+			goto illegal
+		}
+		return &ast.BranchStatement{
+			Idx:   idx,
+			Token: token.CONTINUE,
+		}
+	}
+
+	if self.token == token.IDENTIFIER {
+		identifier := self.parseIdentifier()
+		if !self.scope.hasLabel(identifier.Name) {
+			self.error(idx, "Undefined label '%s'", identifier.Name)
+			return &ast.BadStatement{From: idx, To: identifier.Idx1()}
+		}
+		if !self.scope.inIteration {
+			goto illegal
+		}
+		self.semicolon()
+		return &ast.BranchStatement{
+			Idx:   idx,
+			Token: token.CONTINUE,
+			Label: identifier,
+		}
+	}
+
+	self.expect(token.IDENTIFIER)
+
+illegal:
+	self.error(idx, "Illegal continue statement")
+	self.nextStatement()
+	return &ast.BadStatement{From: idx, To: self.idx}
+}
+
+// Find the next statement after an error (recover)
+func (self *_parser) nextStatement() {
+	for {
+		switch self.token {
+		case token.BREAK, token.CONTINUE,
+			token.FOR, token.IF, token.RETURN, token.SWITCH,
+			token.VAR, token.DO, token.TRY, token.WITH,
+			token.WHILE, token.THROW, token.CATCH, token.FINALLY:
+			// Return only if parser made some progress since last
+			// sync or if it has not reached 10 next calls without
+			// progress. Otherwise consume at least one token to
+			// avoid an endless parser loop
+			if self.idx == self.recover.idx && self.recover.count < 10 {
+				self.recover.count++
+				return
+			}
+			if self.idx > self.recover.idx {
+				self.recover.idx = self.idx
+				self.recover.count = 0
+				return
+			}
+			// Reaching here indicates a parser bug, likely an
+			// incorrect token list in this function, but it only
+			// leads to skipping of possibly correct code if a
+			// previous error is present, and thus is preferred
+			// over a non-terminating parse.
+		case token.EOF:
+			return
+		}
+		self.next()
+	}
+}

+ 32 - 0
parser/testutil_test.go

@@ -0,0 +1,32 @@
+package parser
+
+import (
+	"fmt"
+	"runtime"
+	"testing"
+	"path/filepath"
+)
+
+// Quick and dirty replacement for terst
+
+func tt(t *testing.T, f func()) {
+	defer func() {
+		if x := recover(); x != nil {
+			_, file, line, _ := runtime.Caller(5)
+			t.Errorf("Error at %s:%d: %v", filepath.Base(file), line, x)
+		}
+	}()
+
+	f()
+}
+
+
+func is(a, b interface{}) {
+	as := fmt.Sprintf("%v", a)
+	bs := fmt.Sprintf("%v", b)
+	if as != bs {
+		panic(fmt.Errorf("%+v(%T) != %+v(%T)", a, a, b, b))
+	}
+}
+
+

+ 361 - 0
regexp.go

@@ -0,0 +1,361 @@
+package goja
+
+import (
+	"fmt"
+	"github.com/dlclark/regexp2"
+	"regexp"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+type regexpPattern interface {
+	FindSubmatchIndex(valueString, int) []int
+	FindAllSubmatchIndex(valueString, int) [][]int
+	FindAllSubmatchIndexUTF8(string, int) [][]int
+	FindAllSubmatchIndexASCII(string, int) [][]int
+	MatchString(valueString) bool
+}
+
+type regexp2Wrapper regexp2.Regexp
+type regexpWrapper regexp.Regexp
+
+type regexpObject struct {
+	baseObject
+	pattern regexpPattern
+	source  valueString
+
+	global, multiline, ignoreCase bool
+}
+
+func (r *regexp2Wrapper) FindSubmatchIndex(s valueString, start int) (result []int) {
+	wrapped := (*regexp2.Regexp)(r)
+	var match *regexp2.Match
+	var err error
+	switch s := s.(type) {
+	case asciiString:
+		match, err = wrapped.FindStringMatch(string(s)[start:])
+	case unicodeString:
+		match, err = wrapped.FindRunesMatch(utf16.Decode(s[start:]))
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", s))
+	}
+	if err != nil {
+		return
+	}
+
+	if match == nil {
+		return
+	}
+	groups := match.Groups()
+
+	result = make([]int, 0, len(groups)<<1)
+	for _, group := range groups {
+		if len(group.Captures) > 0 {
+			result = append(result, group.Index, group.Index+group.Length)
+		} else {
+			result = append(result, -1, 0)
+		}
+	}
+	return
+}
+
+func (r *regexp2Wrapper) FindAllSubmatchIndexUTF8(s string, n int) [][]int {
+	wrapped := (*regexp2.Regexp)(r)
+	if n < 0 {
+		n = len(s) + 1
+	}
+	results := make([][]int, 0, n)
+
+	idxMap := make([]int, 0, len(s))
+	runes := make([]rune, 0, len(s))
+	for pos, rr := range s {
+		runes = append(runes, rr)
+		idxMap = append(idxMap, pos)
+	}
+	idxMap = append(idxMap, len(s))
+
+	match, err := wrapped.FindRunesMatch(runes)
+	if err != nil {
+		return nil
+	}
+	i := 0
+	for match != nil && i < n {
+		groups := match.Groups()
+
+		result := make([]int, 0, len(groups)<<1)
+
+		for _, group := range groups {
+			if len(group.Captures) > 0 {
+				result = append(result, idxMap[group.Index], idxMap[group.Index+group.Length])
+			} else {
+				result = append(result, -1, 0)
+			}
+		}
+
+		results = append(results, result)
+		match, err = wrapped.FindNextMatch(match)
+		if err != nil {
+			return nil
+		}
+		i++
+	}
+	return results
+}
+
+func (r *regexp2Wrapper) FindAllSubmatchIndexASCII(s string, n int) [][]int {
+	wrapped := (*regexp2.Regexp)(r)
+	if n < 0 {
+		n = len(s) + 1
+	}
+	results := make([][]int, 0, n)
+
+	match, err := wrapped.FindStringMatch(s)
+	if err != nil {
+		return nil
+	}
+	i := 0
+	for match != nil && i < n {
+		groups := match.Groups()
+
+		result := make([]int, 0, len(groups)<<1)
+
+		for _, group := range groups {
+			if len(group.Captures) > 0 {
+				result = append(result, group.Index, group.Index+group.Length)
+			} else {
+				result = append(result, -1, 0)
+			}
+		}
+
+		results = append(results, result)
+		match, err = wrapped.FindNextMatch(match)
+		if err != nil {
+			return nil
+		}
+		i++
+	}
+	return results
+}
+
+func (r *regexp2Wrapper) findAllSubmatchIndexUTF16(s unicodeString, n int) [][]int {
+	wrapped := (*regexp2.Regexp)(r)
+	if n < 0 {
+		n = len(s) + 1
+	}
+	results := make([][]int, 0, n)
+
+	rd := runeReaderReplace{s.reader(0)}
+	posMap := make([]int, s.length()+1)
+	curPos := 0
+	curRuneIdx := 0
+	runes := make([]rune, 0, s.length())
+	for {
+		rn, size, err := rd.ReadRune()
+		if err != nil {
+			break
+		}
+		runes = append(runes, rn)
+		posMap[curRuneIdx] = curPos
+		curRuneIdx++
+		curPos += size
+	}
+	posMap[curRuneIdx] = curPos
+
+	match, err := wrapped.FindRunesMatch(runes)
+	if err != nil {
+		return nil
+	}
+	for match != nil {
+		groups := match.Groups()
+
+		result := make([]int, 0, len(groups)<<1)
+
+		for _, group := range groups {
+			if len(group.Captures) > 0 {
+				start := posMap[group.Index]
+				end := posMap[group.Index+group.Length]
+				result = append(result, start, end)
+			} else {
+				result = append(result, -1, 0)
+			}
+		}
+
+		results = append(results, result)
+		match, err = wrapped.FindNextMatch(match)
+		if err != nil {
+			return nil
+		}
+	}
+	return results
+}
+
+func (r *regexp2Wrapper) FindAllSubmatchIndex(s valueString, n int) [][]int {
+	switch s := s.(type) {
+	case asciiString:
+		return r.FindAllSubmatchIndexUTF8(string(s), n)
+	case unicodeString:
+		return r.findAllSubmatchIndexUTF16(s, n)
+	default:
+		panic("Unsupported string type")
+	}
+}
+
+func (r *regexp2Wrapper) MatchString(s valueString) bool {
+	wrapped := (*regexp2.Regexp)(r)
+
+	switch s := s.(type) {
+	case asciiString:
+		matched, _ := wrapped.MatchString(string(s))
+		return matched
+	case unicodeString:
+		matched, _ := wrapped.MatchRunes(utf16.Decode(s))
+		return matched
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", s))
+	}
+}
+
+func (r *regexpWrapper) FindSubmatchIndex(s valueString, start int) (result []int) {
+	wrapped := (*regexp.Regexp)(r)
+	return wrapped.FindReaderSubmatchIndex(runeReaderReplace{s.reader(start)})
+}
+
+func (r *regexpWrapper) MatchString(s valueString) bool {
+	wrapped := (*regexp.Regexp)(r)
+	return wrapped.MatchReader(runeReaderReplace{s.reader(0)})
+}
+
+func (r *regexpWrapper) FindAllSubmatchIndex(s valueString, n int) [][]int {
+	wrapped := (*regexp.Regexp)(r)
+	switch s := s.(type) {
+	case asciiString:
+		return wrapped.FindAllStringSubmatchIndex(string(s), n)
+	case unicodeString:
+		return r.findAllSubmatchIndexUTF16(s, n)
+	default:
+		panic("Unsupported string type")
+	}
+}
+
+func (r *regexpWrapper) FindAllSubmatchIndexUTF8(s string, n int) [][]int {
+	wrapped := (*regexp.Regexp)(r)
+	return wrapped.FindAllStringSubmatchIndex(s, n)
+}
+
+func (r *regexpWrapper) FindAllSubmatchIndexASCII(s string, n int) [][]int {
+	return r.FindAllSubmatchIndexUTF8(s, n)
+}
+
+func (r *regexpWrapper) findAllSubmatchIndexUTF16(s unicodeString, n int) [][]int {
+	wrapped := (*regexp.Regexp)(r)
+	utf8Bytes := make([]byte, 0, len(s)*2)
+	posMap := make(map[int]int)
+	curPos := 0
+	rd := runeReaderReplace{s.reader(0)}
+	for {
+		rn, size, err := rd.ReadRune()
+		if err != nil {
+			break
+		}
+		l := len(utf8Bytes)
+		utf8Bytes = append(utf8Bytes, 0, 0, 0, 0)
+		n := utf8.EncodeRune(utf8Bytes[l:], rn)
+		utf8Bytes = utf8Bytes[:l+n]
+		posMap[l] = curPos
+		curPos += size
+	}
+	posMap[len(utf8Bytes)] = curPos
+
+	rr := wrapped.FindAllSubmatchIndex(utf8Bytes, n)
+	for _, res := range rr {
+		for j, pos := range res {
+			mapped, exists := posMap[pos]
+			if !exists {
+				panic("Unicode match is not on rune boundary")
+			}
+			res[j] = mapped
+		}
+	}
+	return rr
+}
+
+func (r *regexpObject) execResultToArray(target valueString, result []int) Value {
+	captureCount := len(result) >> 1
+	valueArray := make([]Value, captureCount)
+	matchIndex := result[0]
+	lowerBound := matchIndex
+	for index := 0; index < captureCount; index++ {
+		offset := index << 1
+		if result[offset] >= lowerBound {
+			valueArray[index] = target.substring(result[offset], result[offset+1])
+			lowerBound = result[offset]
+		} else {
+			valueArray[index] = _undefined
+		}
+	}
+	match := r.val.runtime.newArrayValues(valueArray)
+	match.self.putStr("input", target, false)
+	match.self.putStr("index", intToValue(int64(matchIndex)), false)
+	return match
+}
+
+func (r *regexpObject) execRegexp(target valueString) (match bool, result []int) {
+	lastIndex := 0
+	if p := r.getStr("lastIndex"); p != nil {
+		lastIndex = int(p.ToInteger())
+		if lastIndex < 0 {
+			lastIndex = 0
+		}
+	}
+	index := lastIndex
+	if !r.global {
+		index = 0
+	}
+	if index >= 0 && index <= target.length() {
+		result = r.pattern.FindSubmatchIndex(target, index)
+	}
+	if result == nil {
+		r.putStr("lastIndex", intToValue(0), true)
+		return
+	}
+	match = true
+	startIndex := index
+	endIndex := int(lastIndex) + result[1]
+	// We do this shift here because the .FindStringSubmatchIndex above
+	// was done on a local subordinate slice of the string, not the whole string
+	for index, _ := range result {
+		result[index] += int(startIndex)
+	}
+	if r.global {
+		r.putStr("lastIndex", intToValue(int64(endIndex)), true)
+	}
+	return
+}
+
+func (r *regexpObject) exec(target valueString) Value {
+	match, result := r.execRegexp(target)
+	if match {
+		return r.execResultToArray(target, result)
+	}
+	return _null
+}
+
+func (r *regexpObject) test(target valueString) bool {
+	match, _ := r.execRegexp(target)
+	return match
+}
+
+func (r *regexpObject) clone() *Object {
+	r1 := r.val.runtime.newRegexpObject(r.prototype)
+	r1.source = r.source
+	r1.pattern = r.pattern
+	r1.global = r.global
+	r1.ignoreCase = r.ignoreCase
+	r1.multiline = r.multiline
+	return r1.val
+}
+
+func (r *regexpObject) init() {
+	r.baseObject.init()
+	r._putProp("lastIndex", intToValue(0), true, false, false)
+}

+ 175 - 0
regexp_test.go

@@ -0,0 +1,175 @@
+package goja
+
+import (
+	"testing"
+)
+
+func TestRegexp1(t *testing.T) {
+	const SCRIPT = `
+	var r = new RegExp("(['\"])(.*?)\\1");
+	var m = r.exec("'test'");
+	m !== null && m.length == 3 && m[2] === "test";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexp2(t *testing.T) {
+	const SCRIPT = `
+	var r = new RegExp("(['\"])(.*?)['\"]");
+	var m = r.exec("'test'");
+	m !== null && m.length == 3 && m[2] === "test";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpLiteral(t *testing.T) {
+	const SCRIPT = `
+	var r = /(['\"])(.*?)\1/;
+	var m = r.exec("'test'");
+	m !== null && m.length == 3 && m[2] === "test";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpRe2Unicode(t *testing.T) {
+	const SCRIPT = `
+	var r = /(тест)/i;
+	var m = r.exec("'Тест'");
+	m !== null && m.length == 2 && m[1] === "Тест";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpRe2UnicodeTarget(t *testing.T) {
+	const SCRIPT = `
+	var r = /(['\"])(.*?)['\"]/i;
+	var m = r.exec("'Тест'");
+	m !== null && m.length == 3 && m[2] === "Тест";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpRegexp2Unicode(t *testing.T) {
+	const SCRIPT = `
+	var r = /(['\"])(тест)\1/i;
+	var m = r.exec("'Тест'");
+	m !== null && m.length == 3 && m[2] === "Тест";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpRegexp2UnicodeTarget(t *testing.T) {
+	const SCRIPT = `
+	var r = /(['\"])(.*?)\1/;
+	var m = r.exec("'Тест'");
+	m !== null && m.length == 3 && m[2] === "Тест";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpRe2Whitespace(t *testing.T) {
+	const SCRIPT = `
+	"\u2000\u2001\u2002\u200b".replace(/\s+/g, "") === "\u200b";
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpRegexp2Whitespace(t *testing.T) {
+	const SCRIPT = `
+	"A\u2000\u2001\u2002A\u200b".replace(/(A)\s+\1/g, "") === "\u200b"
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestEmptyCharClassRe2(t *testing.T) {
+	const SCRIPT = `
+	/[]/.test("\u0000");
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestNegatedEmptyCharClassRe2(t *testing.T) {
+	const SCRIPT = `
+	/[^]/.test("\u0000");
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestEmptyCharClassRegexp2(t *testing.T) {
+	const SCRIPT = `
+	/([])\1/.test("\u0000\u0000");
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestRegexp2Negate(t *testing.T) {
+	const SCRIPT = `
+	/([\D1])\1/.test("aa");
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestAlternativeRe2(t *testing.T) {
+	const SCRIPT = `
+	/()|/.exec("") !== null;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestRegexpReplaceGlobal(t *testing.T) {
+	const SCRIPT = `
+	"QBZPbage\ny_cynprubyqre".replace(/^\s*|\s*$/g, '')
+	`
+
+	testScript1(SCRIPT, asciiString("QBZPbage\ny_cynprubyqre"), t)
+}
+
+func TestRegexpNumCaptures(t *testing.T) {
+	const SCRIPT = `
+	"Fubpxjnir Synfu 9.0  e115".replace(/([a-zA-Z]|\s)+/, '')
+	`
+	testScript1(SCRIPT, asciiString("9.0  e115"), t)
+}
+
+func TestRegexpNumCaptures1(t *testing.T) {
+	const SCRIPT = `
+	"Fubpxjnir Sy\tfu 9.0  e115".replace(/^.*\s+(\S+\s+\S+$)/, '')
+	`
+	testScript1(SCRIPT, asciiString(""), t)
+}
+
+func TestRegexpSInClass(t *testing.T) {
+	const SCRIPT = `
+	/[\S]/.test("\u2028");
+	`
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestRegexpDotMatchSlashR(t *testing.T) {
+	const SCRIPT = `
+	/./.test("\r");
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}
+
+func TestRegexpDotMatchSlashRInGroup(t *testing.T) {
+	const SCRIPT = `
+	/(.)/.test("\r");
+	`
+
+	testScript1(SCRIPT, valueFalse, t)
+}

+ 1196 - 0
runtime.go

@@ -0,0 +1,1196 @@
+package goja
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"github.com/dop251/goja/parser"
+	"go/ast"
+	"math"
+	"math/rand"
+	"reflect"
+	"strconv"
+)
+
+const (
+	sqrt1_2 float64 = math.Sqrt2 / 2
+)
+
+type global struct {
+	Object   *Object
+	Array    *Object
+	Function *Object
+	String   *Object
+	Number   *Object
+	Boolean  *Object
+	RegExp   *Object
+	Date     *Object
+
+	ArrayBuffer *Object
+
+	Error          *Object
+	TypeError      *Object
+	ReferenceError *Object
+	SyntaxError    *Object
+	RangeError     *Object
+	EvalError      *Object
+	URIError       *Object
+
+	GoError *Object
+
+	ObjectPrototype   *Object
+	ArrayPrototype    *Object
+	NumberPrototype   *Object
+	StringPrototype   *Object
+	BooleanPrototype  *Object
+	FunctionPrototype *Object
+	RegExpPrototype   *Object
+	DatePrototype     *Object
+
+	ArrayBufferPrototype *Object
+
+	ErrorPrototype          *Object
+	TypeErrorPrototype      *Object
+	SyntaxErrorPrototype    *Object
+	RangeErrorPrototype     *Object
+	ReferenceErrorPrototype *Object
+	EvalErrorPrototype      *Object
+	URIErrorPrototype       *Object
+
+	GoErrorPrototype *Object
+
+	Eval *Object
+
+	thrower         *Object
+	throwerProperty Value
+}
+
+type RandSource func() float64
+
+type Runtime struct {
+	global          global
+	globalObject    *Object
+	stringSingleton *stringObject
+	rand            RandSource
+
+	vm *vm
+}
+
+type stackFrame struct {
+	prg      *Program
+	funcName string
+	pc       int
+}
+
+func (f stackFrame) position() Position {
+	return f.prg.src.Position(f.prg.sourceOffset(f.pc))
+}
+
+type Exception struct {
+	val   Value
+	stack []stackFrame
+}
+
+type InterruptedError struct {
+	Exception
+	iface interface{}
+}
+
+func (e *InterruptedError) Value() interface{} {
+	return e.iface
+}
+
+func (e *Exception) String() string {
+	if e == nil {
+		return "<nil>"
+	}
+	var b bytes.Buffer
+	if e.val != nil {
+		b.WriteString(e.val.String())
+	}
+	b.WriteByte('\n')
+	for _, frame := range e.stack {
+		b.WriteString("\tat ")
+		if frame.prg != nil {
+			if n := frame.prg.funcName; n != "" {
+				b.WriteString(n)
+				b.WriteString(" (")
+			}
+			if n := frame.prg.src.name; n != "" {
+				b.WriteString(n)
+			} else {
+				b.WriteString("<eval>")
+			}
+			b.WriteByte(':')
+			b.WriteString(frame.position().String())
+			b.WriteByte('(')
+			b.WriteString(strconv.Itoa(frame.pc))
+			b.WriteByte(')')
+			if frame.prg.funcName != "" {
+				b.WriteByte(')')
+			}
+		} else {
+			if frame.funcName != "" {
+				b.WriteString(frame.funcName)
+				b.WriteString(" (")
+			}
+			b.WriteString("native")
+			if frame.funcName != "" {
+				b.WriteByte(')')
+			}
+		}
+		b.WriteByte('\n')
+	}
+	return b.String()
+}
+
+func (e *Exception) Error() string {
+	if e == nil || e.val == nil {
+		return "<nil>"
+	}
+	return e.val.String()
+}
+
+func (e *Exception) Value() Value {
+	return e.val
+}
+
+func (r *Runtime) addToGlobal(name string, value Value) {
+	r.globalObject.self._putProp(name, value, true, false, true)
+}
+
+func (r *Runtime) init() {
+	r.rand = rand.Float64
+	r.global.ObjectPrototype = r.newBaseObject(nil, classObject).val
+	r.globalObject = r.NewObject()
+
+	r.vm = &vm{
+		r: r,
+	}
+	r.vm.init()
+
+	r.global.FunctionPrototype = r.newNativeFunc(nil, nil, "Empty", nil, 0)
+	r.initObject()
+	r.initFunction()
+	r.initArray()
+	r.initString()
+	r.initNumber()
+	r.initRegExp()
+	r.initDate()
+	r.initBoolean()
+
+	r.initErrors()
+
+	r.global.Eval = r.newNativeFunc(r.builtin_eval, nil, "eval", nil, 1)
+	r.addToGlobal("eval", r.global.Eval)
+
+	r.initGlobalObject()
+
+	r.initMath()
+	r.initJSON()
+
+	//r.initTypedArrays()
+
+	r.global.thrower = r.newNativeFunc(r.builtin_thrower, nil, "thrower", nil, 0)
+	r.global.throwerProperty = &valueProperty{
+		getterFunc: r.global.thrower,
+		setterFunc: r.global.thrower,
+		accessor:   true,
+	}
+}
+
+func (r *Runtime) typeErrorResult(throw bool, args ...interface{}) {
+	if throw {
+		msg := ""
+		if len(args) > 0 {
+			f, _ := args[0].(string)
+			msg = fmt.Sprintf(f, args[1:]...)
+		}
+		panic(r.builtin_new(r.global.TypeError, []Value{newStringValue(msg)}))
+	}
+}
+
+func (r *Runtime) newError(typ *Object, format string, args ...interface{}) Value {
+	msg := fmt.Sprintf(format, args...)
+	return r.builtin_new(typ, []Value{newStringValue(msg)})
+}
+
+func (r *Runtime) throwReferenceError(name string) {
+	panic(r.newError(r.global.ReferenceError, "%s is not defined", name))
+}
+
+func (r *Runtime) newSyntaxError(msg string, offset int) Value {
+	return r.builtin_new((r.global.SyntaxError), []Value{newStringValue(msg)})
+}
+
+func (r *Runtime) newArray(prototype *Object) (a *arrayObject) {
+	v := &Object{runtime: r}
+
+	a = &arrayObject{}
+	a.class = classArray
+	a.val = v
+	a.extensible = true
+	v.self = a
+	a.prototype = prototype
+	a.init()
+	return
+}
+
+func (r *Runtime) newArrayObject() *arrayObject {
+	return r.newArray(r.global.ArrayPrototype)
+}
+
+func (r *Runtime) newArrayValues(values []Value) *Object {
+	v := &Object{runtime: r}
+
+	a := &arrayObject{}
+	a.class = classArray
+	a.val = v
+	a.extensible = true
+	v.self = a
+	a.prototype = r.global.ArrayPrototype
+	a.init()
+	a.values = values
+	a.length = len(values)
+	a.objCount = a.length
+	return v
+}
+
+func (r *Runtime) newArrayLength(l int64) *Object {
+	a := r.newArrayValues(nil)
+	a.self.putStr("length", intToValue(l), true)
+	return a
+}
+
+func (r *Runtime) newBaseObject(proto *Object, class string) (o *baseObject) {
+	v := &Object{runtime: r}
+
+	o = &baseObject{}
+	o.class = class
+	o.val = v
+	o.extensible = true
+	v.self = o
+	o.prototype = proto
+	o.init()
+	return
+}
+
+func (r *Runtime) NewObject() (v *Object) {
+	return r.newBaseObject(r.global.ObjectPrototype, classObject).val
+}
+
+func (r *Runtime) newFunc(name string, len int, strict bool) (f *funcObject) {
+	v := &Object{runtime: r}
+
+	f = &funcObject{}
+	f.class = classFunction
+	f.val = v
+	f.extensible = true
+	v.self = f
+	f.prototype = r.global.FunctionPrototype
+	f.init(name, len)
+	if strict {
+		f._put("caller", r.global.throwerProperty)
+		f._put("arguments", r.global.throwerProperty)
+	}
+	return
+}
+
+func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *nativeFuncObject {
+	f := &nativeFuncObject{
+		baseObject: baseObject{
+			class:      classFunction,
+			val:        v,
+			extensible: true,
+			prototype:  r.global.FunctionPrototype,
+		},
+		f:         call,
+		construct: construct,
+	}
+	v.self = f
+	f.init(name, length)
+	if proto != nil {
+		f._putProp("prototype", proto, false, false, false)
+	}
+	return f
+}
+
+func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *Object {
+	v := &Object{runtime: r}
+
+	f := &nativeFuncObject{
+		baseObject: baseObject{
+			class:      classFunction,
+			val:        v,
+			extensible: true,
+			prototype:  r.global.FunctionPrototype,
+		},
+		f:         call,
+		construct: construct,
+	}
+	v.self = f
+	f.init(name, length)
+	if proto != nil {
+		f._putProp("prototype", proto, false, false, false)
+		proto.self._putProp("constructor", v, true, false, true)
+	}
+	return v
+}
+
+func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name string, proto *Object, length int) objectImpl {
+	f := &nativeFuncObject{
+		baseObject: baseObject{
+			class:      classFunction,
+			val:        v,
+			extensible: true,
+			prototype:  r.global.FunctionPrototype,
+		},
+		f: r.constructWrap(construct, proto),
+		construct: func(args []Value) *Object {
+			return construct(args, proto)
+		},
+	}
+
+	f.init(name, length)
+	if proto != nil {
+		f._putProp("prototype", proto, false, false, false)
+	}
+	return f
+}
+
+func (r *Runtime) newNativeFuncConstruct(construct func(args []Value, proto *Object) *Object, name string, prototype *Object, length int) *Object {
+	return r.newNativeFuncConstructProto(construct, name, prototype, r.global.FunctionPrototype, length)
+}
+
+func (r *Runtime) newNativeFuncConstructProto(construct func(args []Value, proto *Object) *Object, name string, prototype, proto *Object, length int) *Object {
+	v := &Object{runtime: r}
+
+	f := &nativeFuncObject{}
+	f.class = classFunction
+	f.val = v
+	f.extensible = true
+	v.self = f
+	f.prototype = proto
+	f.f = r.constructWrap(construct, prototype)
+	f.construct = func(args []Value) *Object {
+		return construct(args, prototype)
+	}
+	f.init(name, length)
+	if prototype != nil {
+		f._putProp("prototype", prototype, false, false, false)
+		prototype.self._putProp("constructor", v, true, false, true)
+	}
+	return v
+}
+
+func (r *Runtime) newPrimitiveObject(value Value, proto *Object, class string) *Object {
+	v := &Object{runtime: r}
+
+	o := &primitiveValueObject{}
+	o.class = class
+	o.val = v
+	o.extensible = true
+	v.self = o
+	o.prototype = proto
+	o.pValue = value
+	o.init()
+	return v
+}
+
+func (r *Runtime) builtin_Number(call FunctionCall) Value {
+	if len(call.Arguments) > 0 {
+		return call.Arguments[0].ToNumber()
+	} else {
+		return intToValue(0)
+	}
+}
+
+func (r *Runtime) builtin_newNumber(args []Value) *Object {
+	var v Value
+	if len(args) > 0 {
+		v = args[0].ToNumber()
+	} else {
+		v = intToValue(0)
+	}
+	return r.newPrimitiveObject(v, r.global.NumberPrototype, classNumber)
+}
+
+func (r *Runtime) builtin_Boolean(call FunctionCall) Value {
+	if len(call.Arguments) > 0 {
+		if call.Arguments[0].ToBoolean() {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	} else {
+		return valueFalse
+	}
+}
+
+func (r *Runtime) builtin_newBoolean(args []Value) *Object {
+	var v Value
+	if len(args) > 0 {
+		if args[0].ToBoolean() {
+			v = valueTrue
+		} else {
+			v = valueFalse
+		}
+	} else {
+		v = valueFalse
+	}
+	return r.newPrimitiveObject(v, r.global.BooleanPrototype, classBoolean)
+}
+
+func (r *Runtime) error_toString(call FunctionCall) Value {
+	obj := call.This.ToObject(r).self
+	msg := obj.getStr("message")
+	name := obj.getStr("name")
+	var nameStr, msgStr string
+	if name != nil && name != _undefined {
+		nameStr = name.String()
+	}
+	if msg != nil && msg != _undefined {
+		msgStr = msg.String()
+	}
+	if nameStr != "" && msgStr != "" {
+		return newStringValue(fmt.Sprintf("%s: %s", name.String(), msgStr))
+	} else {
+		if nameStr != "" {
+			return name.ToString()
+		} else {
+			return msg.ToString()
+		}
+	}
+}
+
+func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
+	obj := r.newBaseObject(proto, classError)
+	if len(args) > 0 && args[0] != _undefined {
+		obj._putProp("message", args[0], true, false, true)
+	}
+	return obj.val
+}
+
+func (r *Runtime) builtin_new(construct *Object, args []Value) *Object {
+repeat:
+	switch f := construct.self.(type) {
+	case *nativeFuncObject:
+		if f.construct != nil {
+			return f.construct(args)
+		} else {
+			panic("Not a constructor")
+		}
+	case *boundFuncObject:
+		if f.construct != nil {
+			return f.construct(args)
+		} else {
+			panic("Not a constructor")
+		}
+	case *funcObject:
+		// TODO: implement
+		panic("Not implemented")
+	case *lazyObject:
+		construct.self = f.create(construct)
+		goto repeat
+	default:
+		panic("Not a constructor")
+	}
+}
+
+func (r *Runtime) throw(e Value) {
+	panic(e)
+}
+
+func (r *Runtime) builtin_thrower(call FunctionCall) Value {
+	r.typeErrorResult(true, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
+	return nil
+}
+
+func (r *Runtime) eval(src string, direct, strict bool, this Value) Value {
+
+	p, err := r.compile("<eval>", src, strict, true)
+	if err != nil {
+		panic(err)
+	}
+
+	vm := r.vm
+
+	vm.pushCtx()
+	vm.prg = p
+	vm.pc = 0
+	if !direct {
+		vm.stash = nil
+	}
+	vm.sb = vm.sp
+	vm.push(this)
+	if strict {
+		vm.push(valueTrue)
+	} else {
+		vm.push(valueFalse)
+	}
+	vm.run()
+	vm.popCtx()
+	vm.halt = false
+	retval := vm.stack[vm.sp-1]
+	vm.sp -= 2
+	return retval
+}
+
+func (r *Runtime) builtin_eval(call FunctionCall) Value {
+	if len(call.Arguments) == 0 {
+		return _undefined
+	}
+	if str, ok := call.Arguments[0].assertString(); ok {
+		return r.eval(str.String(), false, false, r.globalObject)
+	}
+	return call.Arguments[0]
+}
+
+func (r *Runtime) constructWrap(construct func(args []Value, proto *Object) *Object, proto *Object) func(call FunctionCall) Value {
+	return func(call FunctionCall) Value {
+		return construct(call.Arguments, proto)
+	}
+}
+
+func (r *Runtime) toCallable(v Value) func(FunctionCall) Value {
+	if call, ok := r.toObject(v).self.assertCallable(); ok {
+		return call
+	}
+	r.typeErrorResult(true, "Value is not callable: %s", v.ToString())
+	return nil
+}
+
+func (r *Runtime) checkObjectCoercible(v Value) {
+	switch v.(type) {
+	case valueUndefined, valueNull:
+		r.typeErrorResult(true, "Value is not object coercible")
+	}
+}
+
+func toUInt32(v Value) uint32 {
+	v = v.ToNumber()
+	if i, ok := v.assertInt(); ok {
+		return uint32(i)
+	}
+
+	if f, ok := v.assertFloat(); ok {
+		return uint32(int64(f))
+	}
+	return 0
+}
+
+func toUInt16(v Value) uint16 {
+	v = v.ToNumber()
+	if i, ok := v.assertInt(); ok {
+		return uint16(i)
+	}
+
+	if f, ok := v.assertFloat(); ok {
+		return uint16(int64(f))
+	}
+	return 0
+}
+
+func toLength(v Value) int64 {
+	if v == nil {
+		return 0
+	}
+	i := v.ToInteger()
+	if i < 0 {
+		return 0
+	}
+	if i >= maxInt {
+		return maxInt - 1
+	}
+	return i
+}
+
+func toInt32(v Value) int32 {
+	v = v.ToNumber()
+	if i, ok := v.assertInt(); ok {
+		return int32(i)
+	}
+
+	if f, ok := v.assertFloat(); ok {
+		if !math.IsNaN(f) && !math.IsInf(f, 0) {
+			return int32(int64(f))
+		}
+	}
+	return 0
+}
+
+func (r *Runtime) toBoolean(b bool) Value {
+	if b {
+		return valueTrue
+	} else {
+		return valueFalse
+	}
+}
+
+// New creates an instance of a Javascript runtime that can be used to run code. Multiple instances may be created and
+// used simultaneously, however it is not possible to pass JS values across runtimes.
+func New() *Runtime {
+	r := &Runtime{}
+	r.init()
+	return r
+}
+
+// Compile creates an internal representation of the JavaScript code that can be later run using the Runtime.RunProgram()
+// method. This representation is not linked to a runtime in any way and can be run in multiple runtimes (possibly
+// at the same time).
+func Compile(name, src string, strict bool) (p *Program, err error) {
+	return compile(name, src, strict, false)
+}
+
+func compile(name, src string, strict, eval bool) (p *Program, err error) {
+	prg, err1 := parser.ParseFile(nil, name, src, 0)
+	if err1 != nil {
+		switch err1 := err1.(type) {
+		case parser.ErrorList:
+			if len(err1) > 0 && err1[0].Message == "Invalid left-hand side in assignment" {
+				err = &CompilerReferenceError{
+					CompilerError: CompilerError{
+						Message: err1.Error(),
+					},
+				}
+				return
+			}
+		}
+		// FIXME offset
+		err = &CompilerSyntaxError{
+			CompilerError: CompilerError{
+				Message: err1.Error(),
+			},
+		}
+		return
+	}
+
+	c := newCompiler()
+	c.scope.strict = strict
+	c.scope.eval = eval
+
+	defer func() {
+		if x := recover(); x != nil {
+			p = nil
+			switch x1 := x.(type) {
+			case *CompilerSyntaxError:
+				err = x1
+			default:
+				panic(x)
+			}
+		}
+	}()
+
+	c.compile(prg)
+	p = c.p
+	return
+}
+
+func (r *Runtime) compile(name, src string, strict, eval bool) (p *Program, err error) {
+	p, err = compile(name, src, strict, eval)
+	if err != nil {
+		switch x1 := err.(type) {
+		case *CompilerSyntaxError:
+			err = &Exception{
+				val: r.builtin_new(r.global.SyntaxError, []Value{newStringValue(x1.Error())}),
+			}
+		case *CompilerReferenceError:
+			err = &Exception{
+				val: r.newError(r.global.ReferenceError, x1.Message),
+			} // TODO proper message
+		}
+	}
+	return
+}
+
+// RunString executes the given string in the global context.
+func (r *Runtime) RunString(str string) (Value, error) {
+	return r.RunScript("", str)
+}
+
+// RunScript executes the given string in the global context.
+func (r *Runtime) RunScript(name, src string) (Value, error) {
+	p, err := Compile(name, src, false)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return r.RunProgram(p)
+}
+
+// RunProgram executes a pre-compiled (see Compile()) code in the global context.
+func (r *Runtime) RunProgram(p *Program) (result Value, err error) {
+	defer func() {
+		if x := recover(); x != nil {
+			if intr, ok := x.(*InterruptedError); ok {
+				err = intr
+			} else {
+				panic(x)
+			}
+		}
+	}()
+	recursive := false
+	if len(r.vm.callStack) > 0 {
+		recursive = true
+		r.vm.pushCtx()
+	}
+	r.vm.prg = p
+	r.vm.pc = 0
+	ex := r.vm.runTry()
+	if ex == nil {
+		result = r.vm.pop()
+	} else {
+		err = ex
+	}
+	if recursive {
+		r.vm.popCtx()
+		r.vm.halt = false
+	} else {
+		r.vm.stack = nil
+	}
+	return
+}
+
+// Interrupt a running JavaScript. The corresponding Go call will return an *InterruptedError containing v.
+// Note, it only works while in JavaScript code, it does not interrupt native Go functions (which includes all built-ins).
+func (r *Runtime) Interrupt(v interface{}) {
+	r.vm.Interrupt(v)
+}
+
+/*
+ToValue converts a Go value into JavaScript value.
+
+Primitive types (ints and uints, floats, string, bool) are converted to the corresponding JavaScript primitives.
+
+func(FunctionCall) Value is treated as a native JavaScript function.
+
+map[string]interface{} is converted into a host object that largely behaves like a JavaScript Object.
+
+[]interface{} is converted into a host object that behaves largely like a JavaScript Array, however it's not extensible
+because extending it can change the pointer so it becomes detached from the original.
+
+*[]interface{} same as above, but the array becomes extensible.
+
+A function is wrapped within a native JavaScript function. When called the arguments are automatically converted to
+the appropriate Go types. If conversion is not possible, a TypeError is thrown.
+
+A slice type is converted into a generic reflect based host object that behaves similar to an unexpandable Array.
+
+Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar
+to a Number, String, Boolean or Object.
+
+Note that the underlying type is not lost, calling Export() returns the original Go value. This applies to all
+reflect based types.
+*/
+func (r *Runtime) ToValue(i interface{}) Value {
+	switch i := i.(type) {
+	case nil:
+		return _undefined
+	case Value:
+		// TODO: prevent importing Objects from a different runtime
+		return i
+	case string:
+		return newStringValue(i)
+	case bool:
+		if i {
+			return valueTrue
+		} else {
+			return valueFalse
+		}
+	case func(FunctionCall) Value:
+		return r.newNativeFunc(i, nil, "", nil, 0)
+	case int:
+		return intToValue(int64(i))
+	case int8:
+		return intToValue(int64(i))
+	case int16:
+		return intToValue(int64(i))
+	case int32:
+		return intToValue(int64(i))
+	case int64:
+		return intToValue(i)
+	case uint:
+		if i <= math.MaxInt64 {
+			return intToValue(int64(i))
+		} else {
+			return floatToValue(float64(i))
+		}
+	case uint64:
+		if i <= math.MaxInt64 {
+			return intToValue(int64(i))
+		} else {
+			return floatToValue(float64(i))
+		}
+	case uint8:
+		return intToValue(int64(i))
+	case uint16:
+		return intToValue(int64(i))
+	case uint32:
+		return intToValue(int64(i))
+	case map[string]interface{}:
+		obj := &Object{runtime: r}
+		m := &objectGoMapSimple{
+			baseObject: baseObject{
+				val:        obj,
+				extensible: true,
+			},
+			data: i,
+		}
+		obj.self = m
+		m.init()
+		return obj
+	case []interface{}:
+		obj := &Object{runtime: r}
+		a := &objectGoSlice{
+			baseObject: baseObject{
+				val: obj,
+			},
+			data: &i,
+		}
+		obj.self = a
+		a.init()
+		return obj
+	case *[]interface{}:
+		obj := &Object{runtime: r}
+		a := &objectGoSlice{
+			baseObject: baseObject{
+				val: obj,
+			},
+			data:            i,
+			sliceExtensible: true,
+		}
+		obj.self = a
+		a.init()
+		return obj
+	}
+
+	origValue := reflect.ValueOf(i)
+	value := origValue
+	for value.Kind() == reflect.Ptr {
+		value = reflect.Indirect(value)
+	}
+
+	switch value.Kind() {
+	case reflect.Map:
+		if value.Type().Key().Kind() == reflect.String {
+			//TODO: goMapReflect
+		}
+	case reflect.Slice:
+		obj := &Object{runtime: r}
+		a := &objectGoSliceReflect{
+			objectGoReflect: objectGoReflect{
+				baseObject: baseObject{
+					val: obj,
+				},
+				origValue: origValue,
+				value:     value,
+			},
+		}
+		a.init()
+		obj.self = a
+		return obj
+	case reflect.Func:
+		return r.newNativeFunc(r.wrapReflectFunc(value), nil, "", nil, value.Type().NumIn())
+	}
+
+	obj := &Object{runtime: r}
+	o := &objectGoReflect{
+		baseObject: baseObject{
+			val: obj,
+		},
+		origValue: origValue,
+		value:     value,
+	}
+	obj.self = o
+	o.init()
+	return obj
+}
+
+func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value {
+	return func(call FunctionCall) Value {
+		typ := value.Type()
+		nargs := typ.NumIn()
+		if len(call.Arguments) != nargs {
+			if typ.IsVariadic() {
+				if len(call.Arguments) < nargs-1 {
+					panic(r.newError(r.global.TypeError, "expected at least %d arguments; got %d", nargs-1, len(call.Arguments)))
+				}
+			} else {
+				panic(r.newError(r.global.TypeError, "expected %d argument(s); got %d", nargs, len(call.Arguments)))
+			}
+		}
+
+		in := make([]reflect.Value, len(call.Arguments))
+
+		callSlice := false
+		for i, a := range call.Arguments {
+			var t reflect.Type
+
+			n := i
+			if n >= nargs-1 && typ.IsVariadic() {
+				if n > nargs-1 {
+					n = nargs - 1
+				}
+
+				t = typ.In(n).Elem()
+			} else {
+				t = typ.In(n)
+			}
+
+			// if this is a variadic Go function, and the caller has supplied
+			// exactly the number of JavaScript arguments required, and this
+			// is the last JavaScript argument, try treating the it as the
+			// actual set of variadic Go arguments. if that succeeds, break
+			// out of the loop.
+			if typ.IsVariadic() && len(call.Arguments) == nargs && i == nargs-1 {
+				if v, err := r.toReflectValue(a, typ.In(n)); err == nil {
+					in[i] = v
+					callSlice = true
+					break
+				}
+			}
+			var err error
+			in[i], err = r.toReflectValue(a, t)
+			if err != nil {
+				panic(r.newError(r.global.TypeError, "Could not convert function call parameter %v to %v", a, t))
+			}
+		}
+
+		var out []reflect.Value
+		if callSlice {
+			out = value.CallSlice(in)
+		} else {
+			out = value.Call(in)
+		}
+
+		if len(out) == 0 {
+			return _undefined
+		}
+
+		if last := out[len(out)-1]; last.Type().Name() == "error" {
+			if !last.IsNil() {
+				err := last.Interface()
+				if _, ok := err.(*Exception); ok {
+					panic(err)
+				}
+				e := r.newError(r.global.GoError, last.Interface().(error).Error())
+				e.(*Object).Set("value", err)
+				panic(e)
+			}
+			out = out[:len(out)-1]
+		}
+
+		switch len(out) {
+		case 0:
+			return _undefined
+		case 1:
+			return r.ToValue(out[0].Interface())
+		default:
+			s := make([]interface{}, len(out))
+			for i, v := range out {
+				s[i] = v.Interface()
+			}
+
+			return r.ToValue(s)
+		}
+	}
+}
+
+func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, error) {
+	switch typ.Kind() {
+	case reflect.String:
+		return reflect.ValueOf(v.String()).Convert(typ), nil
+	case reflect.Bool:
+		return reflect.ValueOf(v.ToBoolean()).Convert(typ), nil
+	case reflect.Int:
+		i, _ := toInt(v)
+		return reflect.ValueOf(int(i)).Convert(typ), nil
+	case reflect.Int64:
+		i, _ := toInt(v)
+		return reflect.ValueOf(i).Convert(typ), nil
+	case reflect.Int32:
+		i, _ := toInt(v)
+		return reflect.ValueOf(int32(i)).Convert(typ), nil
+	case reflect.Int16:
+		i, _ := toInt(v)
+		return reflect.ValueOf(int16(i)).Convert(typ), nil
+	case reflect.Int8:
+		i, _ := toInt(v)
+		return reflect.ValueOf(int8(i)).Convert(typ), nil
+	case reflect.Uint:
+		i, _ := toInt(v)
+		return reflect.ValueOf(uint(i)).Convert(typ), nil
+	case reflect.Uint64:
+		i, _ := toInt(v)
+		return reflect.ValueOf(uint64(i)).Convert(typ), nil
+	case reflect.Uint32:
+		i, _ := toInt(v)
+		return reflect.ValueOf(uint32(i)).Convert(typ), nil
+	case reflect.Uint16:
+		i, _ := toInt(v)
+		return reflect.ValueOf(uint16(i)).Convert(typ), nil
+	case reflect.Uint8:
+		i, _ := toInt(v)
+		return reflect.ValueOf(uint8(i)).Convert(typ), nil
+	}
+
+	et := v.ExportType()
+
+	if et.AssignableTo(typ) {
+		return reflect.ValueOf(v.Export()), nil
+	} else if et.ConvertibleTo(typ) {
+		return reflect.ValueOf(v.Export()).Convert(typ), nil
+	}
+
+	switch typ.Kind() {
+	case reflect.Slice:
+		if o, ok := v.(*Object); ok {
+			if o.self.className() == classArray {
+				l := int(toLength(o.self.getStr("length")))
+				s := reflect.MakeSlice(typ, l, l)
+				elemTyp := typ.Elem()
+				for i := 0; i < l; i++ {
+					item := o.self.get(intToValue(int64(i)))
+					itemval, err := r.toReflectValue(item, elemTyp)
+					if err != nil {
+						return reflect.Value{}, fmt.Errorf("Could not convert array element %v to %v at %d", v, typ, i)
+					}
+					s.Index(i).Set(itemval)
+				}
+				return s, nil
+			}
+		}
+	case reflect.Map:
+		if o, ok := v.(*Object); ok {
+			m := reflect.MakeMap(typ)
+			keyTyp := typ.Key()
+			elemTyp := typ.Elem()
+			needConvertKeys := !reflect.ValueOf("").Type().AssignableTo(keyTyp)
+			for item, f := o.self.enumerate(false, false)(); f != nil; item, f = f() {
+				var kv reflect.Value
+				var err error
+				if needConvertKeys {
+					kv, err = r.toReflectValue(newStringValue(item.name), keyTyp)
+					if err != nil {
+						return reflect.Value{}, fmt.Errorf("Could not convert map key %s to %v", item.name, typ)
+					}
+				} else {
+					kv = reflect.ValueOf(item.name)
+				}
+
+				ival := item.value
+				if ival == nil {
+					ival = o.self.getStr(item.name)
+				}
+				if ival != nil {
+					vv, err := r.toReflectValue(ival, elemTyp)
+					if err != nil {
+						return reflect.Value{}, fmt.Errorf("Could not convert map value %v to %v at key %s", ival, typ, item.name)
+					}
+					m.SetMapIndex(kv, vv)
+				} else {
+					m.SetMapIndex(kv, reflect.Zero(elemTyp))
+				}
+			}
+			return m, nil
+		}
+	case reflect.Struct:
+		if o, ok := v.(*Object); ok {
+			s := reflect.New(typ).Elem()
+			for i := 0; i < typ.NumField(); i++ {
+				field := typ.Field(i)
+				if ast.IsExported(field.Name) {
+					v := o.self.getStr(field.Name)
+					if v != nil {
+						vv, err := r.toReflectValue(v, field.Type)
+						if err != nil {
+							return reflect.Value{}, fmt.Errorf("Could not convert struct value %v to %v for field %s", v, field.Type, field.Name)
+
+						}
+						s.Field(i).Set(vv)
+					}
+				}
+			}
+			return s, nil
+		}
+	}
+
+	return reflect.Value{}, fmt.Errorf("Could not convert %v to %v", v, typ)
+}
+
+// ExportTo converts a JavaScript value into the specified Go value. The second parameter must be a non-nil pointer.
+// Returns error if conversion is not possible.
+func (r *Runtime) ExportTo(v Value, target interface{}) error {
+	tval := reflect.ValueOf(target)
+	if tval.Kind() != reflect.Ptr || tval.IsNil() {
+		return errors.New("target must be a non-nil pointer")
+	}
+	vv, err := r.toReflectValue(v, tval.Elem().Type())
+	if err != nil {
+		return err
+	}
+	tval.Elem().Set(vv)
+	return nil
+}
+
+// Set the specified value as a property of the global object.
+// The value is first converted using ToValue()
+func (r *Runtime) Set(name string, value interface{}) {
+	r.globalObject.self.putStr(name, r.ToValue(value), false)
+}
+
+// Get the specified property of the global object.
+func (r *Runtime) Get(name string) Value {
+	return r.globalObject.self.getStr(name)
+}
+
+// SetRandSource sets random source for this Runtime. If not called, the default math/rand is used.
+func (r *Runtime) SetRandSource(source RandSource) {
+	r.rand = source
+}
+
+// Callable represents a JavaScript function that can be called from Go.
+type Callable func(this Value, args ...Value) (Value, error)
+
+// AssertFunction checks if the Value is a function and returns a Callable.
+func AssertFunction(v Value) (Callable, bool) {
+	if obj, ok := v.(*Object); ok {
+		if f, ok := obj.self.assertCallable(); ok {
+			return func(this Value, args ...Value) (ret Value, err error) {
+				ex := obj.runtime.vm.try(func() {
+					ret = f(FunctionCall{
+						This:      this,
+						Arguments: args,
+					})
+				})
+				if ex != nil {
+					err = ex
+				}
+				return
+			}, true
+		}
+	}
+	return nil, false
+}
+
+// IsUndefined returns true if the supplied Value is undefined. Note, it checks against the real undefined, not
+// against the global object's 'undefined' property.
+func IsUndefined(v Value) bool {
+	return v == _undefined
+}
+
+// IsNull returns true if the supplied Value is null.
+func IsNull(v Value) bool {
+	return v == _null
+}
+
+// Undefined returns JS undefined value. Note if global 'undefined' property is changed this still returns the original value.
+func Undefined() Value {
+	return _undefined
+}
+
+// Null returns JS null value.
+func Null() Value {
+	return _undefined
+}

+ 510 - 0
runtime_test.go

@@ -0,0 +1,510 @@
+package goja
+
+import (
+	"errors"
+	"testing"
+	"time"
+)
+
+func TestGlobalObjectProto(t *testing.T) {
+	const SCRIPT = `
+	this instanceof Object
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayProtoProp(t *testing.T) {
+	const SCRIPT = `
+	Object.defineProperty(Array.prototype, '0', {value: 42, configurable: true, writable: false})
+	var a = []
+	a[0] = 1
+	a[0]
+	`
+
+	testScript1(SCRIPT, valueInt(42), t)
+}
+
+func TestArrayDelete(t *testing.T) {
+	const SCRIPT = `
+	var a = [1, 2];
+	var deleted = delete a[0];
+	var undef = a[0] === undefined;
+	var len = a.length;
+
+	deleted && undef && len === 2;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayDeleteNonexisting(t *testing.T) {
+	const SCRIPT = `
+	Array.prototype[0] = 42;
+	var a = [];
+	delete a[0] && a[0] === 42;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArraySetLength(t *testing.T) {
+	const SCRIPT = `
+	var a = [1, 2];
+	var assert0 = a.length == 2;
+	a.length = "1";
+	a.length = 1.0;
+	a.length = 1;
+	var assert1 = a.length == 1;
+	a.length = 2;
+	var assert2 = a.length == 2;
+	assert0 && assert1 && assert2 && a[1] === undefined;
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestUnicodeString(t *testing.T) {
+	const SCRIPT = `
+	var s = "Тест";
+	s.length === 4 && s[1] === "е";
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayReverseNonOptimisable(t *testing.T) {
+	const SCRIPT = `
+	var a = [];
+	Object.defineProperty(a, "0", {get: function() {return 42}, set: function(v) {Object.defineProperty(a, "0", {value: v + 1, writable: true, configurable: true})}, configurable: true})
+	a[1] = 43;
+	a.reverse();
+
+	a.length === 2 && a[0] === 44 && a[1] === 42;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayPushNonOptimisable(t *testing.T) {
+	const SCRIPT = `
+	Object.defineProperty(Object.prototype, "0", {value: 42});
+	var a = [];
+	var thrown = false;
+	try {
+		a.push(1);
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArraySetLengthWithPropItems(t *testing.T) {
+	const SCRIPT = `
+	var a = [1,2,3,4];
+	var thrown = false;
+
+	Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
+	try {
+		Object.defineProperty(a, "length", {value: 0, writable: false});
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+	thrown && a.length === 3;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func Test2TierHierarchyProp(t *testing.T) {
+	const SCRIPT = `
+	var a = {};
+	Object.defineProperty(a, "test", {
+		value: 42,
+		writable: false,
+		enumerable: false,
+		configurable: true
+	});
+	var b = Object.create(a);
+	var c = Object.create(b);
+	c.test = 43;
+	c.test === 42 && !b.hasOwnProperty("test");
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestConstStringIter(t *testing.T) {
+	const SCRIPT = `
+
+	var count = 0;
+
+	for (var i in "1234") {
+    		for (var j in "1234567") {
+        		count++
+    		}
+	}
+
+	count;
+	`
+
+	testScript1(SCRIPT, intToValue(28), t)
+}
+
+func TestUnicodeConcat(t *testing.T) {
+	const SCRIPT = `
+
+	var s = "тест";
+	var s1 = "test";
+	var s2 = "абвгд";
+
+	s.concat(s1) === "тестtest" && s.concat(s1, s2) === "тестtestабвгд" && s1.concat(s, s2) === "testтестабвгд"
+		&& s.concat(s2) === "тестабвгд";
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestIndexOf(t *testing.T) {
+	const SCRIPT = `
+
+	"abc".indexOf("", 4)
+	`
+
+	testScript1(SCRIPT, intToValue(3), t)
+}
+
+func TestUnicodeIndexOf(t *testing.T) {
+	const SCRIPT = `
+
+	"абвгд".indexOf("вг", 1)
+
+	`
+
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestLastIndexOf(t *testing.T) {
+	const SCRIPT = `
+
+	"abcabab".lastIndexOf("ab", 3)
+	`
+
+	testScript1(SCRIPT, intToValue(3), t)
+}
+
+func TestUnicodeLastIndexOf(t *testing.T) {
+	const SCRIPT = `
+	"абвабаб".lastIndexOf("аб", 3)
+
+	`
+
+	testScript1(SCRIPT, intToValue(3), t)
+}
+
+func TestNumber(t *testing.T) {
+	const SCRIPT = `
+	(new Number(100111122133144155)).toString()
+	`
+
+	testScript1(SCRIPT, asciiString("100111122133144160"), t)
+}
+
+func TestFractionalNumberToStringRadix(t *testing.T) {
+	const SCRIPT = `
+	(new Number(123.456)).toString(36)
+	`
+
+	testScript1(SCRIPT, asciiString("3f.gez4w97ry"), t)
+}
+
+func TestSetFunc(t *testing.T) {
+	const SCRIPT = `
+	sum(40, 2);
+	`
+	r := New()
+	r.Set("sum", func(call FunctionCall) Value {
+		return r.ToValue(call.Argument(0).ToInteger() + call.Argument(1).ToInteger())
+	})
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i := v.ToInteger(); i != 42 {
+		t.Fatalf("Expected 42, got: %d", i)
+	}
+}
+
+func TestObjectGetSet(t *testing.T) {
+	const SCRIPT = `
+		input.test++;
+		input;
+	`
+	r := New()
+	o := r.NewObject()
+	o.Set("test", 42)
+	r.Set("input", o)
+
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if o1, ok := v.(*Object); ok {
+		if v1 := o1.Get("test"); v1.Export() != int64(43) {
+			t.Fatalf("Unexpected test value: %v (%T)", v1, v1.Export())
+		}
+	}
+}
+
+func TestThrowFromNativeFunc(t *testing.T) {
+	const SCRIPT = `
+	var thrown;
+	try {
+		f();
+	} catch (e) {
+		thrown = e;
+	}
+	thrown;
+	`
+	r := New()
+	r.Set("f", func(call FunctionCall) Value {
+		panic(r.ToValue("testError"))
+	})
+
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !v.Equals(asciiString("testError")) {
+		t.Fatalf("Unexpected result: %v", v)
+	}
+}
+
+func TestSetGoFunc(t *testing.T) {
+	const SCRIPT = `
+	f(40, 2)
+	`
+	r := New()
+	r.Set("f", func(a, b int) int {
+		return a + b
+	})
+
+	v, err := r.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if v.ToInteger() != 42 {
+		t.Fatalf("Unexpected result: %v", v)
+	}
+}
+
+func TestArgsKeys(t *testing.T) {
+	const SCRIPT = `
+	function testArgs2(x, y, z) {
+    		// Properties of the arguments object are enumerable.
+    		return Object.keys(arguments);
+	}
+
+	testArgs2(1,2).length
+	`
+
+	testScript1(SCRIPT, intToValue(2), t)
+}
+
+func TestIPowOverflow(t *testing.T) {
+	const SCRIPT = `
+	Math.pow(65536, 6)
+	`
+
+	testScript1(SCRIPT, floatToValue(7.922816251426434e+28), t)
+}
+
+func TestIPowZero(t *testing.T) {
+	const SCRIPT = `
+	Math.pow(0, 0)
+	`
+
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestInterrupt(t *testing.T) {
+	const SCRIPT = `
+	var i = 0;
+	for (;;) {
+		i++;
+	}
+	`
+
+	vm := New()
+	time.AfterFunc(200*time.Millisecond, func() {
+		vm.Interrupt("halt")
+	})
+
+	_, err := vm.RunString(SCRIPT)
+	if err == nil {
+		t.Fatal("Err is nil")
+	}
+}
+
+func TestRuntime_ExportToSlice(t *testing.T) {
+	const SCRIPT = `
+	var a = [1, 2, 3];
+	a;
+	`
+
+	vm := New()
+	v, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var a []string
+	err = vm.ExportTo(v, &a)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if l := len(a); l != 3 {
+		t.Fatalf("Unexpected len: %d", l)
+	}
+	if a[0] != "1" || a[1] != "2" || a[2] != "3" {
+		t.Fatalf("Unexpected value: %+v", a)
+	}
+}
+
+func TestRuntime_ExportToMap(t *testing.T) {
+	const SCRIPT = `
+	var m = {
+		"0": 1,
+		"1": 2,
+		"2": 3,
+	}
+	m;
+	`
+
+	vm := New()
+	v, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var m map[int]string
+	err = vm.ExportTo(v, &m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if l := len(m); l != 3 {
+		t.Fatalf("Unexpected len: %d", l)
+	}
+	if m[0] != "1" || m[1] != "2" || m[2] != "3" {
+		t.Fatalf("Unexpected value: %+v", m)
+	}
+}
+
+func TestRuntime_ExportToMap1(t *testing.T) {
+	const SCRIPT = `
+	var m = {
+		"0": 1,
+		"1": 2,
+		"2": 3,
+	}
+	m;
+	`
+
+	vm := New()
+	v, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var m map[string]string
+	err = vm.ExportTo(v, &m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if l := len(m); l != 3 {
+		t.Fatalf("Unexpected len: %d", l)
+	}
+	if m["0"] != "1" || m["1"] != "2" || m["2"] != "3" {
+		t.Fatalf("Unexpected value: %+v", m)
+	}
+}
+
+func TestRuntime_ExportToStruct(t *testing.T) {
+	const SCRIPT = `
+	var m = {
+		Test: 1,
+	}
+	m;
+	`
+	vm := New()
+	v, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var o testGoReflectMethod_O
+	err = vm.ExportTo(v, &o)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if o.Test != "1" {
+		t.Fatalf("Unexpected value: '%s'", o.Test)
+	}
+
+}
+
+func TestGoFuncError(t *testing.T) {
+	const SCRIPT = `
+	try {
+		f();
+	} catch (e) {
+		if (!(e instanceof GoError)) {
+			throw(e);
+		}
+		if (e.value.Error() !== "Test") {
+			throw("Unexpected value: " + e.value.Error());
+		}
+	}
+	`
+
+	f := func() error {
+		return errors.New("Test")
+	}
+
+	vm := New()
+	vm.Set("f", f)
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+/*
+func TestArrayConcatSparse(t *testing.T) {
+function foo(a,b,c)
+  {
+    arguments[0] = 1; arguments[1] = 'str'; arguments[2] = 2.1;
+    if(1 === a && 'str' === b && 2.1 === c)
+      return true;
+  }
+
+
+	const SCRIPT = `
+	var a1 = [];
+	var a2 = [];
+	a1[500000] = 1;
+	a2[1000000] = 2;
+	var a3 = a1.concat(a2);
+	a3.length === 1500002 && a3[500000] === 1 && a3[1500001] == 2;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+*/

+ 73 - 0
srcfile.go

@@ -0,0 +1,73 @@
+package goja
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+type Position struct {
+	Line, Col int
+}
+
+type SrcFile struct {
+	name string
+	src  string
+
+	lineOffsets       []int
+	lastScannedOffset int
+}
+
+func NewSrcFile(name, src string) *SrcFile {
+	return &SrcFile{
+		name: name,
+		src:  src,
+	}
+}
+
+func (f *SrcFile) Position(offset int) Position {
+	var line int
+	if offset > f.lastScannedOffset {
+		f.scanTo(offset)
+		line = len(f.lineOffsets) - 1
+	} else {
+		if len(f.lineOffsets) > 0 {
+			line = sort.SearchInts(f.lineOffsets, offset)
+		} else {
+			line = -1
+		}
+	}
+
+	if line >= 0 {
+		if f.lineOffsets[line] > offset {
+			line--
+		}
+	}
+
+	var lineStart int
+	if line >= 0 {
+		lineStart = f.lineOffsets[line]
+	}
+	return Position{
+		Line: line + 2,
+		Col:  offset - lineStart + 1,
+	}
+}
+
+func (f *SrcFile) scanTo(offset int) {
+	o := f.lastScannedOffset
+	for o < offset {
+		p := strings.Index(f.src[o:], "\n")
+		if p == -1 {
+			o = len(f.src)
+			break
+		}
+		o = o + p + 1
+		f.lineOffsets = append(f.lineOffsets, o)
+	}
+	f.lastScannedOffset = o
+}
+
+func (p Position) String() string {
+	return fmt.Sprintf("%d:%d", p.Line, p.Col)
+}

+ 30 - 0
srcfile_test.go

@@ -0,0 +1,30 @@
+package goja
+
+import "testing"
+
+func TestPosition(t *testing.T) {
+	const SRC = `line1
+line2
+line3`
+	f := NewSrcFile("", SRC)
+	if p := f.Position(12); p.Line != 3 || p.Col != 1 {
+		t.Fatalf("0. Line: %d, col: %d", p.Line, p.Col)
+	}
+
+	if p := f.Position(2); p.Line != 1 || p.Col != 3 {
+		t.Fatalf("1. Line: %d, col: %d", p.Line, p.Col)
+	}
+
+	if p := f.Position(2); p.Line != 1 || p.Col != 3 {
+		t.Fatalf("2. Line: %d, col: %d", p.Line, p.Col)
+	}
+
+	if p := f.Position(7); p.Line != 2 || p.Col != 2 {
+		t.Fatalf("3. Line: %d, col: %d", p.Line, p.Col)
+	}
+
+	if p := f.Position(12); p.Line != 3 || p.Col != 1 {
+		t.Fatalf("4. Line: %d, col: %d", p.Line, p.Col)
+	}
+
+}

+ 226 - 0
string.go

@@ -0,0 +1,226 @@
+package goja
+
+import (
+	"io"
+	"strconv"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+var (
+	stringTrue         valueString = asciiString("true")
+	stringFalse        valueString = asciiString("false")
+	stringNull         valueString = asciiString("null")
+	stringUndefined    valueString = asciiString("undefined")
+	stringObjectC      valueString = asciiString("object")
+	stringFunction     valueString = asciiString("function")
+	stringBoolean      valueString = asciiString("boolean")
+	stringString       valueString = asciiString("string")
+	stringNumber       valueString = asciiString("number")
+	stringNaN          valueString = asciiString("NaN")
+	stringInfinity                 = asciiString("Infinity")
+	stringPlusInfinity             = asciiString("+Infinity")
+	stringNegInfinity              = asciiString("-Infinity")
+	stringEmpty        valueString = asciiString("")
+	string__proto__    valueString = asciiString("__proto__")
+
+	stringError          valueString = asciiString("Error")
+	stringTypeError      valueString = asciiString("TypeError")
+	stringReferenceError valueString = asciiString("ReferenceError")
+	stringSyntaxError    valueString = asciiString("SyntaxError")
+	stringRangeError     valueString = asciiString("RangeError")
+	stringEvalError      valueString = asciiString("EvalError")
+	stringURIError       valueString = asciiString("URIError")
+	stringGoError        valueString = asciiString("GoError")
+
+	stringObjectNull      valueString = asciiString("[object Null]")
+	stringObjectObject    valueString = asciiString("[object Object]")
+	stringObjectUndefined valueString = asciiString("[object Undefined]")
+	stringGlobalObject    valueString = asciiString("Global Object")
+	stringInvalidDate     valueString = asciiString("Invalid Date")
+)
+
+type valueString interface {
+	Value
+	charAt(int) rune
+	length() int
+	concat(valueString) valueString
+	substring(start, end int) valueString
+	compareTo(valueString) int
+	reader(start int) io.RuneReader
+	index(valueString, int) int
+	lastIndex(valueString, int) int
+	toLower() valueString
+	toUpper() valueString
+	toTrimmedUTF8() string
+}
+
+type stringObject struct {
+	baseObject
+	value      valueString
+	length     int
+	lengthProp valueProperty
+}
+
+func newUnicodeString(s string) valueString {
+	return unicodeString(utf16.Encode([]rune(s)))
+}
+
+func newStringValue(s string) valueString {
+	for _, chr := range s {
+		if chr >= utf8.RuneSelf {
+			return newUnicodeString(s)
+		}
+	}
+	return asciiString(s)
+}
+
+func (s *stringObject) init() {
+	s.baseObject.init()
+	s.setLength()
+}
+
+func (s *stringObject) setLength() {
+	if s.value != nil {
+		s.length = s.value.length()
+	}
+	s.lengthProp.value = intToValue(int64(s.length))
+	s._put("length", &s.lengthProp)
+}
+
+func (s *stringObject) get(n Value) Value {
+	if idx := toIdx(n); idx >= 0 && idx < s.length {
+		return s.getIdx(idx)
+	}
+	return s.baseObject.get(n)
+}
+
+func (s *stringObject) getStr(name string) Value {
+	if i := strToIdx(name); i >= 0 && i < s.length {
+		return s.getIdx(i)
+	}
+	return s.baseObject.getStr(name)
+}
+
+func (s *stringObject) getPropStr(name string) Value {
+	if i := strToIdx(name); i >= 0 && i < s.length {
+		return s.getIdx(i)
+	}
+	return s.baseObject.getPropStr(name)
+}
+
+func (s *stringObject) getProp(n Value) Value {
+	if i := toIdx(n); i >= 0 && i < s.length {
+		return s.getIdx(i)
+	}
+	return s.baseObject.getProp(n)
+}
+
+func (s *stringObject) getOwnProp(name string) Value {
+	if i := strToIdx(name); i >= 0 && i < s.length {
+		val := s.getIdx(i)
+		return &valueProperty{
+			value:      val,
+			enumerable: true,
+		}
+	}
+
+	return s.baseObject.getOwnProp(name)
+}
+
+func (s *stringObject) getIdx(idx int) Value {
+	return s.value.substring(idx, idx+1)
+}
+
+func (s *stringObject) put(n Value, val Value, throw bool) {
+	if i := toIdx(n); i >= 0 && i < s.length {
+		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
+		return
+	}
+
+	s.baseObject.put(n, val, throw)
+}
+
+func (s *stringObject) putStr(name string, val Value, throw bool) {
+	if i := strToIdx(name); i >= 0 && i < s.length {
+		s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
+		return
+	}
+
+	s.baseObject.putStr(name, val, throw)
+}
+
+func (s *stringObject) defineOwnProperty(n Value, descr objectImpl, throw bool) bool {
+	if i := toIdx(n); i >= 0 && i < s.length {
+		s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
+		return false
+	}
+
+	return s.baseObject.defineOwnProperty(n, descr, throw)
+}
+
+type stringPropIter struct {
+	str         valueString // separate, because obj can be the singleton
+	obj         *stringObject
+	idx, length int
+	recursive   bool
+}
+
+func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
+	if i.idx < i.length {
+		name := strconv.Itoa(i.idx)
+		i.idx++
+		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.next
+	}
+
+	return i.obj.baseObject._enumerate(i.recursive)()
+}
+
+func (s *stringObject) _enumerate(recusrive bool) iterNextFunc {
+	return (&stringPropIter{
+		str:       s.value,
+		obj:       s,
+		length:    s.length,
+		recursive: recusrive,
+	}).next
+}
+
+func (s *stringObject) enumerate(all, recursive bool) iterNextFunc {
+	return (&propFilterIter{
+		wrapped: s._enumerate(recursive),
+		all:     all,
+		seen:    make(map[string]bool),
+	}).next
+}
+
+func (s *stringObject) deleteStr(name string, throw bool) bool {
+	if i := strToIdx(name); i >= 0 && i < s.length {
+		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
+		return false
+	}
+
+	return s.baseObject.deleteStr(name, throw)
+}
+
+func (s *stringObject) delete(n Value, throw bool) bool {
+	if i := toIdx(n); i >= 0 && i < s.length {
+		s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
+		return false
+	}
+
+	return s.baseObject.delete(n, throw)
+}
+
+func (s *stringObject) hasOwnProperty(n Value) bool {
+	if i := toIdx(n); i >= 0 && i < s.length {
+		return true
+	}
+	return s.baseObject.hasOwnProperty(n)
+}
+
+func (s *stringObject) hasOwnPropertyStr(name string) bool {
+	if i := strToIdx(name); i >= 0 && i < s.length {
+		return true
+	}
+	return s.baseObject.hasOwnPropertyStr(name)
+}

+ 313 - 0
string_ascii.go

@@ -0,0 +1,313 @@
+package goja
+
+import (
+	"fmt"
+	"io"
+	"math"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+type asciiString string
+
+type asciiRuneReader struct {
+	s   asciiString
+	pos int
+}
+
+func (rr *asciiRuneReader) ReadRune() (r rune, size int, err error) {
+	if rr.pos < len(rr.s) {
+		r = rune(rr.s[rr.pos])
+		size = 1
+		rr.pos++
+	} else {
+		err = io.EOF
+	}
+	return
+}
+
+func (s asciiString) reader(start int) io.RuneReader {
+	return &asciiRuneReader{
+		s: s[start:],
+	}
+}
+
+// ss must be trimmed
+func strToInt(ss string) (int64, error) {
+	if ss == "" {
+		return 0, nil
+	}
+	if ss == "-0" {
+		return 0, strconv.ErrSyntax
+	}
+	if len(ss) > 2 {
+		switch ss[:2] {
+		case "0x", "0X":
+			i, _ := strconv.ParseInt(ss[2:], 16, 64)
+			return i, nil
+		case "0b", "0B":
+			i, _ := strconv.ParseInt(ss[2:], 2, 64)
+			return i, nil
+		case "0o", "0O":
+			i, _ := strconv.ParseInt(ss[2:], 8, 64)
+			return i, nil
+		}
+	}
+	return strconv.ParseInt(ss, 10, 64)
+}
+
+func (s asciiString) _toInt() (int64, error) {
+	return strToInt(strings.TrimSpace(string(s)))
+}
+
+func isRangeErr(err error) bool {
+	if err, ok := err.(*strconv.NumError); ok {
+		return err.Err == strconv.ErrRange
+	}
+	return false
+}
+
+func (s asciiString) _toFloat() (float64, error) {
+	ss := strings.TrimSpace(string(s))
+	if ss == "" {
+		return 0, nil
+	}
+	if ss == "-0" {
+		var f float64
+		return -f, nil
+	}
+	f, err := strconv.ParseFloat(ss, 64)
+	if isRangeErr(err) {
+		err = nil
+	}
+	return f, err
+}
+
+func (s asciiString) ToInteger() int64 {
+	if s == "" {
+		return 0
+	}
+	if s == "Infinity" || s == "+Infinity" {
+		return math.MaxInt64
+	}
+	if s == "-Infinity" {
+		return math.MinInt64
+	}
+	i, err := s._toInt()
+	if err != nil {
+		f, err := s._toFloat()
+		if err == nil {
+			return int64(f)
+		}
+	}
+	return i
+}
+
+func (s asciiString) ToString() valueString {
+	return s
+}
+
+func (s asciiString) String() string {
+	return string(s)
+}
+
+func (s asciiString) ToFloat() float64 {
+	if s == "" {
+		return 0
+	}
+	if s == "Infinity" || s == "+Infinity" {
+		return math.Inf(1)
+	}
+	if s == "-Infinity" {
+		return math.Inf(-1)
+	}
+	f, err := s._toFloat()
+	if err != nil {
+		i, err := s._toInt()
+		if err == nil {
+			return float64(i)
+		}
+		f = math.NaN()
+	}
+	return f
+}
+
+func (s asciiString) ToBoolean() bool {
+	return s != ""
+}
+
+func (s asciiString) ToNumber() Value {
+	if s == "" {
+		return intToValue(0)
+	}
+	if s == "Infinity" || s == "+Infinity" {
+		return _positiveInf
+	}
+	if s == "-Infinity" {
+		return _negativeInf
+	}
+
+	if i, err := s._toInt(); err == nil {
+		return intToValue(i)
+	}
+
+	if f, err := s._toFloat(); err == nil {
+		return floatToValue(f)
+	}
+
+	return _NaN
+}
+
+func (s asciiString) ToObject(r *Runtime) *Object {
+	return r._newString(s)
+}
+
+func (s asciiString) SameAs(other Value) bool {
+	if otherStr, ok := other.(asciiString); ok {
+		return s == otherStr
+	}
+	return false
+}
+
+func (s asciiString) Equals(other Value) bool {
+	if o, ok := other.(asciiString); ok {
+		return s == o
+	}
+
+	if o, ok := other.assertInt(); ok {
+		if o1, e := s._toInt(); e == nil {
+			return o1 == o
+		}
+		return false
+	}
+
+	if o, ok := other.assertFloat(); ok {
+		return s.ToFloat() == o
+	}
+
+	if o, ok := other.(valueBool); ok {
+		if o1, e := s._toFloat(); e == nil {
+			return o1 == o.ToFloat()
+		}
+		return false
+	}
+
+	if o, ok := other.(*Object); ok {
+		return s.Equals(o.self.toPrimitive())
+	}
+	return false
+}
+
+func (s asciiString) StrictEquals(other Value) bool {
+	if otherStr, ok := other.(asciiString); ok {
+		return s == otherStr
+	}
+	return false
+}
+
+func (s asciiString) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (s asciiString) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (s asciiString) assertString() (valueString, bool) {
+	return s, true
+}
+
+func (s asciiString) baseObject(r *Runtime) *Object {
+	ss := r.stringSingleton
+	ss.value = s
+	ss.setLength()
+	return ss.val
+}
+
+func (s asciiString) charAt(idx int) rune {
+	return rune(s[idx])
+}
+
+func (s asciiString) length() int {
+	return len(s)
+}
+
+func (s asciiString) concat(other valueString) valueString {
+	switch other := other.(type) {
+	case asciiString:
+		b := make([]byte, len(s)+len(other))
+		copy(b, s)
+		copy(b[len(s):], other)
+		return asciiString(b)
+		//return asciiString(string(s) + string(other))
+	case unicodeString:
+		b := make([]uint16, len(s)+len(other))
+		for i := 0; i < len(s); i++ {
+			b[i] = uint16(s[i])
+		}
+		copy(b[len(s):], other)
+		return unicodeString(b)
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", other))
+	}
+}
+
+func (s asciiString) substring(start, end int) valueString {
+	return asciiString(s[start:end])
+}
+
+func (s asciiString) compareTo(other valueString) int {
+	switch other := other.(type) {
+	case asciiString:
+		return strings.Compare(string(s), string(other))
+	case unicodeString:
+		return strings.Compare(string(s), other.String())
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", other))
+	}
+}
+
+func (s asciiString) index(substr valueString, start int) int {
+	if substr, ok := substr.(asciiString); ok {
+		p := strings.Index(string(s[start:]), string(substr))
+		if p >= 0 {
+			return p + start
+		}
+	}
+	return -1
+}
+
+func (s asciiString) lastIndex(substr valueString, pos int) int {
+	if substr, ok := substr.(asciiString); ok {
+		end := pos + len(substr)
+		var ss string
+		if end > len(s) {
+			ss = string(s)
+		} else {
+			ss = string(s[:end])
+		}
+		return strings.LastIndex(ss, string(substr))
+	}
+	return -1
+}
+
+func (s asciiString) toLower() valueString {
+	return asciiString(strings.ToLower(string(s)))
+}
+
+func (s asciiString) toUpper() valueString {
+	return asciiString(strings.ToUpper(string(s)))
+}
+
+func (s asciiString) toTrimmedUTF8() string {
+	return strings.TrimSpace(string(s))
+}
+
+func (s asciiString) Export() interface{} {
+	return string(s)
+}
+
+func (s asciiString) ExportType() reflect.Type {
+	return reflectTypeString
+}

+ 316 - 0
string_unicode.go

@@ -0,0 +1,316 @@
+package goja
+
+import (
+	"errors"
+	"fmt"
+	"github.com/dop251/goja/parser"
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
+	"io"
+	"math"
+	"reflect"
+	"regexp"
+	"strings"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+type unicodeString []uint16
+
+type unicodeRuneReader struct {
+	s   unicodeString
+	pos int
+}
+
+type runeReaderReplace struct {
+	wrapped io.RuneReader
+}
+
+var (
+	InvalidRuneError = errors.New("Invalid rune")
+)
+
+var (
+	unicodeTrimRegexp = regexp.MustCompile("^[" + parser.WhitespaceChars + "]*(.*?)[" + parser.WhitespaceChars + "]*$")
+)
+
+func (rr runeReaderReplace) ReadRune() (r rune, size int, err error) {
+	r, size, err = rr.wrapped.ReadRune()
+	if err == InvalidRuneError {
+		err = nil
+		r = utf8.RuneError
+	}
+	return
+}
+
+func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) {
+	if rr.pos < len(rr.s) {
+		r = rune(rr.s[rr.pos])
+		if r != utf8.RuneError {
+			if utf16.IsSurrogate(r) {
+				if rr.pos+1 < len(rr.s) {
+					r1 := utf16.DecodeRune(r, rune(rr.s[rr.pos+1]))
+					size++
+					rr.pos++
+					if r1 == utf8.RuneError {
+						err = InvalidRuneError
+					} else {
+						r = r1
+					}
+				} else {
+					err = InvalidRuneError
+				}
+			}
+		}
+		size++
+		rr.pos++
+	} else {
+		err = io.EOF
+	}
+	return
+}
+
+func (s unicodeString) reader(start int) io.RuneReader {
+	return &unicodeRuneReader{
+		s: s[start:],
+	}
+}
+
+func (s unicodeString) ToInteger() int64 {
+	return 0
+}
+
+func (s unicodeString) ToString() valueString {
+	return s
+}
+
+func (s unicodeString) ToFloat() float64 {
+	return math.NaN()
+}
+
+func (s unicodeString) ToBoolean() bool {
+	return len(s) > 0
+}
+
+func (s unicodeString) toTrimmedUTF8() string {
+	if len(s) == 0 {
+		return ""
+	}
+	return unicodeTrimRegexp.FindStringSubmatch(s.String())[1]
+}
+
+func (s unicodeString) ToNumber() Value {
+	return asciiString(s.toTrimmedUTF8()).ToNumber()
+}
+
+func (s unicodeString) ToObject(r *Runtime) *Object {
+	return r._newString(s)
+}
+
+func (s unicodeString) equals(other unicodeString) bool {
+	if len(s) != len(other) {
+		return false
+	}
+	for i, r := range s {
+		if r != other[i] {
+			return false
+		}
+	}
+	return true
+}
+
+func (s unicodeString) SameAs(other Value) bool {
+	if otherStr, ok := other.(unicodeString); ok {
+		return s.equals(otherStr)
+	}
+
+	return false
+}
+
+func (s unicodeString) Equals(other Value) bool {
+	if s.SameAs(other) {
+		return true
+	}
+
+	if _, ok := other.assertInt(); ok {
+		return false
+	}
+
+	if _, ok := other.assertFloat(); ok {
+		return false
+	}
+
+	if _, ok := other.(valueBool); ok {
+		return false
+	}
+
+	if o, ok := other.(*Object); ok {
+		return s.Equals(o.self.toPrimitive())
+	}
+	return false
+}
+
+func (s unicodeString) StrictEquals(other Value) bool {
+	return s.SameAs(other)
+}
+
+func (s unicodeString) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (s unicodeString) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (s unicodeString) assertString() (valueString, bool) {
+	return s, true
+}
+
+func (s unicodeString) baseObject(r *Runtime) *Object {
+	ss := r.stringSingleton
+	ss.value = s
+	ss.setLength()
+	return ss.val
+}
+
+func (s unicodeString) charAt(idx int) rune {
+	return rune(s[idx])
+}
+
+func (s unicodeString) length() int {
+	return len(s)
+}
+
+func (s unicodeString) concat(other valueString) valueString {
+	switch other := other.(type) {
+	case unicodeString:
+		return unicodeString(append(s, other...))
+	case asciiString:
+		b := make([]uint16, len(s)+len(other))
+		copy(b, s)
+		b1 := b[len(s):]
+		for i := 0; i < len(other); i++ {
+			b1[i] = uint16(other[i])
+		}
+		return unicodeString(b)
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", other))
+	}
+}
+
+func (s unicodeString) substring(start, end int) valueString {
+	ss := s[start:end]
+	for _, c := range ss {
+		if c >= utf8.RuneSelf {
+			return unicodeString(ss)
+		}
+	}
+	as := make([]byte, end-start)
+	for i, c := range ss {
+		as[i] = byte(c)
+	}
+	return asciiString(as)
+}
+
+func (s unicodeString) String() string {
+	return string(utf16.Decode(s))
+}
+
+func (s unicodeString) compareTo(other valueString) int {
+	return strings.Compare(s.String(), other.String())
+}
+
+func (s unicodeString) index(substr valueString, start int) int {
+	var ss []uint16
+	switch substr := substr.(type) {
+	case unicodeString:
+		ss = substr
+	case asciiString:
+		ss = make([]uint16, len(substr))
+		for i := 0; i < len(substr); i++ {
+			ss[i] = uint16(substr[i])
+		}
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", substr))
+	}
+
+	// TODO: optimise
+	end := len(s) - len(ss)
+	for start < end {
+		for i := 0; i < len(ss); i++ {
+			if s[start+i] != ss[i] {
+				goto nomatch
+			}
+		}
+
+		return start
+	nomatch:
+		start++
+	}
+	return -1
+}
+
+func (s unicodeString) lastIndex(substr valueString, start int) int {
+	var ss []uint16
+	switch substr := substr.(type) {
+	case unicodeString:
+		ss = substr
+	case asciiString:
+		ss = make([]uint16, len(substr))
+		for i := 0; i < len(substr); i++ {
+			ss[i] = uint16(substr[i])
+		}
+	default:
+		panic(fmt.Errorf("Unknown string type: %T", substr))
+	}
+
+	// TODO: optimise
+	for start >= 0 {
+		for i := 0; i < len(ss); i++ {
+			if s[start+i] != ss[i] {
+				goto nomatch
+			}
+		}
+
+		return start
+	nomatch:
+		start--
+	}
+	return -1
+}
+
+func (s unicodeString) toLower() valueString {
+	caser := cases.Lower(language.Und)
+	r := []rune(caser.String(s.String()))
+	// Workaround
+	ascii := true
+	for i := 0; i < len(r)-1; i++ {
+		if (i == 0 || r[i-1] != 0x3b1) && r[i] == 0x345 && r[i+1] == 0x3c2 {
+			i++
+			r[i] = 0x3c3
+		}
+		if r[i] >= utf8.RuneSelf {
+			ascii = false
+		}
+	}
+	if ascii {
+		ascii = r[len(r)-1] < utf8.RuneSelf
+	}
+	if ascii {
+		return asciiString(r)
+	}
+	return unicodeString(utf16.Encode(r))
+}
+
+func (s unicodeString) toUpper() valueString {
+	caser := cases.Upper(language.Und)
+	return newStringValue(caser.String(s.String()))
+}
+
+func (s unicodeString) Export() interface{} {
+	return s.String()
+}
+
+func (s unicodeString) ExportType() reflect.Type {
+	return reflectTypeString
+}

+ 291 - 0
tc39_test.go

@@ -0,0 +1,291 @@
+package goja
+
+import (
+	"errors"
+	"gopkg.in/yaml.v2"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+	"testing"
+)
+
+const (
+	tc39BASE = "testdata/test262"
+)
+
+var (
+	invalidFormatError = errors.New("Invalid file format")
+)
+
+var (
+	skipList = map[string]bool{
+		"test/language/literals/regexp/S7.8.5_A1.1_T2.js":             true, // UTF-16
+		"test/language/literals/regexp/S7.8.5_A1.4_T2.js":             true, // UTF-16
+		"test/language/literals/regexp/S7.8.5_A2.1_T2.js":             true, // UTF-16
+		"test/language/literals/regexp/S7.8.5_A2.4_T2.js":             true, // UTF-16
+		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
+		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
+		"test/built-ins/Object/getOwnPropertyNames/15.2.3.4-4-44.js":  true, // property order
+	}
+)
+
+type tc39TestCtx struct {
+	prgCache map[string]*Program
+}
+
+type TC39MetaNegative struct {
+	Phase, Type string
+}
+
+type tc39Meta struct {
+	Negative TC39MetaNegative
+	Includes []string
+	Flags    []string
+	Es5id    string
+	Es6id    string
+	Esid     string
+}
+
+func (m *tc39Meta) hasFlag(flag string) bool {
+	for _, f := range m.Flags {
+		if f == flag {
+			return true
+		}
+	}
+	return false
+}
+
+func parseTC39File(name string) (*tc39Meta, string, error) {
+	f, err := os.Open(name)
+	if err != nil {
+		return nil, "", err
+	}
+	defer f.Close()
+
+	b, err := ioutil.ReadAll(f)
+	if err != nil {
+		return nil, "", err
+	}
+
+	str := string(b)
+	metaStart := strings.Index(str, "/*---")
+	if metaStart == -1 {
+		return nil, "", invalidFormatError
+	} else {
+		metaStart += 5
+	}
+	metaEnd := strings.Index(str, "---*/")
+	if metaEnd == -1 || metaEnd <= metaStart {
+		return nil, "", invalidFormatError
+	}
+
+	var meta tc39Meta
+	err = yaml.Unmarshal([]byte(str[metaStart:metaEnd]), &meta)
+	if err != nil {
+		return nil, "", err
+	}
+
+	if meta.Negative.Type != "" && meta.Negative.Phase == "" {
+		return nil, "", errors.New("negative type is set, but phase isn't")
+	}
+
+	return &meta, str, nil
+}
+
+func runTC39Test(base, name, src string, meta *tc39Meta, t testing.TB, ctx *tc39TestCtx) {
+	vm := New()
+	err, early := runTC39Script(base, name, src, meta.Includes, t, ctx, vm)
+
+	if err != nil {
+		if meta.Negative.Type == "" {
+			t.Fatalf("%s: %v", name, err)
+		} else {
+			if meta.Negative.Phase == "early" && !early || meta.Negative.Phase == "runtime" && early {
+				t.Fatalf("%s: error %v happened at the wrong phase (expected %s)", name, err, meta.Negative.Phase)
+			}
+			var errType string
+
+			switch err := err.(type) {
+			case *Exception:
+				if o, ok := err.Value().(*Object); ok {
+					if c := o.Get("constructor"); c != nil {
+						if c, ok := c.(*Object); ok {
+							errType = c.Get("name").String()
+						} else {
+							t.Fatalf("%s: error constructor is not an object (%v)", name, o)
+						}
+					} else {
+						t.Fatalf("%s: error does not have a constructor (%v)", name, o)
+					}
+				} else {
+					t.Fatalf("%s: error is not an object (%v)", name, err.Value())
+				}
+			case *CompilerSyntaxError:
+				errType = "SyntaxError"
+			case *CompilerReferenceError:
+				errType = "ReferenceError"
+			default:
+				t.Fatalf("%s: error is not a JS error: %v", name, err)
+			}
+
+			if errType != meta.Negative.Type {
+				vm.vm.prg.dumpCode(t.Logf)
+				t.Fatalf("%s: unexpected error type (%s), expected (%s)", name, errType, meta.Negative.Type)
+			}
+		}
+	} else {
+		if meta.Negative.Type != "" {
+			vm.vm.prg.dumpCode(t.Logf)
+			t.Fatalf("%s: Expected error: %v", name, err)
+		}
+	}
+}
+
+func runTC39File(base, name string, t testing.TB, ctx *tc39TestCtx) {
+	if skipList[name] {
+		t.Logf("Skipped %s", name)
+		return
+	}
+	p := path.Join(base, name)
+	meta, src, err := parseTC39File(p)
+	if err != nil {
+		//t.Fatalf("Could not parse %s: %v", name, err)
+		t.Errorf("Could not parse %s: %v", name, err)
+		return
+	}
+	if meta.Es5id == "" {
+		//t.Logf("%s: Not ES5, skipped", name)
+		return
+	}
+
+	hasRaw := meta.hasFlag("raw")
+
+	if hasRaw || !meta.hasFlag("onlyStrict") {
+		//log.Printf("Running normal test: %s", name)
+		//t.Logf("Running normal test: %s", name)
+		runTC39Test(base, name, src, meta, t, ctx)
+	}
+
+	if !hasRaw && !meta.hasFlag("noStrict") {
+		//log.Printf("Running strict test: %s", name)
+		//t.Logf("Running strict test: %s", name)
+		runTC39Test(base, name, "'use strict';\n"+src, meta, t, ctx)
+	}
+
+}
+
+func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error {
+	prg := ctx.prgCache[name]
+	if prg == nil {
+		fname := path.Join(base, name)
+		f, err := os.Open(fname)
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+
+		b, err := ioutil.ReadAll(f)
+		if err != nil {
+			return err
+		}
+
+		str := string(b)
+		prg, err = Compile(name, str, false)
+		if err != nil {
+			return err
+		}
+		ctx.prgCache[name] = prg
+	}
+	_, err := vm.RunProgram(prg)
+	return err
+}
+
+func runTC39Script(base, name, src string, includes []string, t testing.TB, ctx *tc39TestCtx, vm *Runtime) (err error, early bool) {
+	early = true
+	err = ctx.runFile(base, path.Join("harness", "assert.js"), vm)
+	if err != nil {
+		return
+	}
+
+	err = ctx.runFile(base, path.Join("harness", "sta.js"), vm)
+	if err != nil {
+		return
+	}
+
+	for _, include := range includes {
+		err = ctx.runFile(base, path.Join("harness", include), vm)
+		if err != nil {
+			return
+		}
+	}
+
+	var p *Program
+	p, err = Compile(name, src, false)
+
+	if err != nil {
+		return
+	}
+
+	early = false
+	_, err = vm.RunProgram(p)
+
+	return
+}
+
+func runTC39Tests(base, name string, t *testing.T, ctx *tc39TestCtx) {
+	files, err := ioutil.ReadDir(path.Join(base, name))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, file := range files {
+		if file.Name()[0] == '.' {
+			continue
+		}
+		if file.IsDir() {
+			runTC39Tests(base, path.Join(name, file.Name()), t, ctx)
+		} else {
+			if strings.HasSuffix(file.Name(), ".js") {
+				runTC39File(base, path.Join(name, file.Name()), t, ctx)
+			}
+		}
+	}
+
+}
+
+func TestTC39(t *testing.T) {
+	if testing.Short() {
+		t.Skip()
+	}
+
+	if _, err := os.Stat(tc39BASE); err != nil {
+		t.Skipf("If you want to run tc39 tests, download them from https://github.com/tc39/test262 and put into %s. (%v)", tc39BASE, err)
+	}
+
+	ctx := &tc39TestCtx{
+		prgCache: make(map[string]*Program),
+	}
+
+	//_ = "breakpoint"
+	//runTC39File(tc39BASE, "test/language/types/number/8.5.1.js", t, ctx)
+	//runTC39Tests(tc39BASE, "test/language", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/expressions", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/arguments-object", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/asi", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/directive-prologue", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/function-code", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/eval-code", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/global-code", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/identifier-resolution", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/identifiers", t, ctx)
+	//runTC39Tests(tc39BASE, "test/language/literals", t, ctx) // octal sequences in strict mode
+	runTC39Tests(tc39BASE, "test/language/punctuators", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/reserved-words", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/source-text", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/statements", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/types", t, ctx)
+	runTC39Tests(tc39BASE, "test/language/white-space", t, ctx)
+	runTC39Tests(tc39BASE, "test/built-ins", t, ctx)
+	runTC39Tests(tc39BASE, "test/annexB/built-ins/String/prototype/substr", t, ctx)
+}

Fișier diff suprimat deoarece este prea mare
+ 14 - 0
testdata/S15.10.2.12_A1_T1.js


+ 2 - 0
token/Makefile

@@ -0,0 +1,2 @@
+token_const.go: tokenfmt
+	./$^ | gofmt > $@

+ 171 - 0
token/README.markdown

@@ -0,0 +1,171 @@
+# token
+--
+    import "github.com/robertkrimen/otto/token"
+
+Package token defines constants representing the lexical tokens of JavaScript
+(ECMA5).
+
+## Usage
+
+```go
+const (
+	ILLEGAL
+	EOF
+	COMMENT
+	KEYWORD
+
+	STRING
+	BOOLEAN
+	NULL
+	NUMBER
+	IDENTIFIER
+
+	PLUS      // +
+	MINUS     // -
+	MULTIPLY  // *
+	SLASH     // /
+	REMAINDER // %
+
+	AND                  // &
+	OR                   // |
+	EXCLUSIVE_OR         // ^
+	SHIFT_LEFT           // <<
+	SHIFT_RIGHT          // >>
+	UNSIGNED_SHIFT_RIGHT // >>>
+	AND_NOT              // &^
+
+	ADD_ASSIGN       // +=
+	SUBTRACT_ASSIGN  // -=
+	MULTIPLY_ASSIGN  // *=
+	QUOTIENT_ASSIGN  // /=
+	REMAINDER_ASSIGN // %=
+
+	AND_ASSIGN                  // &=
+	OR_ASSIGN                   // |=
+	EXCLUSIVE_OR_ASSIGN         // ^=
+	SHIFT_LEFT_ASSIGN           // <<=
+	SHIFT_RIGHT_ASSIGN          // >>=
+	UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
+	AND_NOT_ASSIGN              // &^=
+
+	LOGICAL_AND // &&
+	LOGICAL_OR  // ||
+	INCREMENT   // ++
+	DECREMENT   // --
+
+	EQUAL        // ==
+	STRICT_EQUAL // ===
+	LESS         // <
+	GREATER      // >
+	ASSIGN       // =
+	NOT          // !
+
+	BITWISE_NOT // ~
+
+	NOT_EQUAL        // !=
+	STRICT_NOT_EQUAL // !==
+	LESS_OR_EQUAL    // <=
+	GREATER_OR_EQUAL // >=
+
+	LEFT_PARENTHESIS // (
+	LEFT_BRACKET     // [
+	LEFT_BRACE       // {
+	COMMA            // ,
+	PERIOD           // .
+
+	RIGHT_PARENTHESIS // )
+	RIGHT_BRACKET     // ]
+	RIGHT_BRACE       // }
+	SEMICOLON         // ;
+	COLON             // :
+	QUESTION_MARK     // ?
+
+	IF
+	IN
+	DO
+
+	VAR
+	FOR
+	NEW
+	TRY
+
+	THIS
+	ELSE
+	CASE
+	VOID
+	WITH
+
+	WHILE
+	BREAK
+	CATCH
+	THROW
+
+	RETURN
+	TYPEOF
+	DELETE
+	SWITCH
+
+	DEFAULT
+	FINALLY
+
+	FUNCTION
+	CONTINUE
+	DEBUGGER
+
+	INSTANCEOF
+)
+```
+
+#### type Token
+
+```go
+type Token int
+```
+
+Token is the set of lexical tokens in JavaScript (ECMA5).
+
+#### func  IsKeyword
+
+```go
+func IsKeyword(literal string) (Token, bool)
+```
+IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token if
+the literal is a future keyword (const, let, class, super, ...), or 0 if the
+literal is not a keyword.
+
+If the literal is a keyword, IsKeyword returns a second value indicating if the
+literal is considered a future keyword in strict-mode only.
+
+7.6.1.2 Future Reserved Words:
+
+    const
+    class
+    enum
+    export
+    extends
+    import
+    super
+
+7.6.1.2 Future Reserved Words (strict):
+
+    implements
+    interface
+    let
+    package
+    private
+    protected
+    public
+    static
+
+#### func (Token) String
+
+```go
+func (tkn Token) String() string
+```
+String returns the string corresponding to the token. For operators, delimiters,
+and keywords the string is the actual token string (e.g., for the token PLUS,
+the String() is "+"). For all other tokens the string corresponds to the token
+name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
+
+--
+**godocdown** http://github.com/robertkrimen/godocdown

+ 116 - 0
token/token.go

@@ -0,0 +1,116 @@
+// Package token defines constants representing the lexical tokens of JavaScript (ECMA5).
+package token
+
+import (
+	"strconv"
+)
+
+// Token is the set of lexical tokens in JavaScript (ECMA5).
+type Token int
+
+// String returns the string corresponding to the token.
+// For operators, delimiters, and keywords the string is the actual
+// token string (e.g., for the token PLUS, the String() is
+// "+"). For all other tokens the string corresponds to the token
+// name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
+//
+func (tkn Token) String() string {
+	if 0 == tkn {
+		return "UNKNOWN"
+	}
+	if tkn < Token(len(token2string)) {
+		return token2string[tkn]
+	}
+	return "token(" + strconv.Itoa(int(tkn)) + ")"
+}
+
+// This is not used for anything
+func (tkn Token) precedence(in bool) int {
+
+	switch tkn {
+	case LOGICAL_OR:
+		return 1
+
+	case LOGICAL_AND:
+		return 2
+
+	case OR, OR_ASSIGN:
+		return 3
+
+	case EXCLUSIVE_OR:
+		return 4
+
+	case AND, AND_ASSIGN, AND_NOT, AND_NOT_ASSIGN:
+		return 5
+
+	case EQUAL,
+		NOT_EQUAL,
+		STRICT_EQUAL,
+		STRICT_NOT_EQUAL:
+		return 6
+
+	case LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL, INSTANCEOF:
+		return 7
+
+	case IN:
+		if in {
+			return 7
+		}
+		return 0
+
+	case SHIFT_LEFT, SHIFT_RIGHT, UNSIGNED_SHIFT_RIGHT:
+		fallthrough
+	case SHIFT_LEFT_ASSIGN, SHIFT_RIGHT_ASSIGN, UNSIGNED_SHIFT_RIGHT_ASSIGN:
+		return 8
+
+	case PLUS, MINUS, ADD_ASSIGN, SUBTRACT_ASSIGN:
+		return 9
+
+	case MULTIPLY, SLASH, REMAINDER, MULTIPLY_ASSIGN, QUOTIENT_ASSIGN, REMAINDER_ASSIGN:
+		return 11
+	}
+	return 0
+}
+
+type _keyword struct {
+	token         Token
+	futureKeyword bool
+	strict        bool
+}
+
+// IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token
+// if the literal is a future keyword (const, let, class, super, ...), or 0 if the literal is not a keyword.
+//
+// If the literal is a keyword, IsKeyword returns a second value indicating if the literal
+// is considered a future keyword in strict-mode only.
+//
+// 7.6.1.2 Future Reserved Words:
+//
+//       const
+//       class
+//       enum
+//       export
+//       extends
+//       import
+//       super
+//
+// 7.6.1.2 Future Reserved Words (strict):
+//
+//       implements
+//       interface
+//       let
+//       package
+//       private
+//       protected
+//       public
+//       static
+//
+func IsKeyword(literal string) (Token, bool) {
+	if keyword, exists := keywordTable[literal]; exists {
+		if keyword.futureKeyword {
+			return KEYWORD, keyword.strict
+		}
+		return keyword.token, false
+	}
+	return 0, false
+}

+ 349 - 0
token/token_const.go

@@ -0,0 +1,349 @@
+package token
+
+const (
+	_ Token = iota
+
+	ILLEGAL
+	EOF
+	COMMENT
+	KEYWORD
+
+	STRING
+	BOOLEAN
+	NULL
+	NUMBER
+	IDENTIFIER
+
+	PLUS      // +
+	MINUS     // -
+	MULTIPLY  // *
+	SLASH     // /
+	REMAINDER // %
+
+	AND                  // &
+	OR                   // |
+	EXCLUSIVE_OR         // ^
+	SHIFT_LEFT           // <<
+	SHIFT_RIGHT          // >>
+	UNSIGNED_SHIFT_RIGHT // >>>
+	AND_NOT              // &^
+
+	ADD_ASSIGN       // +=
+	SUBTRACT_ASSIGN  // -=
+	MULTIPLY_ASSIGN  // *=
+	QUOTIENT_ASSIGN  // /=
+	REMAINDER_ASSIGN // %=
+
+	AND_ASSIGN                  // &=
+	OR_ASSIGN                   // |=
+	EXCLUSIVE_OR_ASSIGN         // ^=
+	SHIFT_LEFT_ASSIGN           // <<=
+	SHIFT_RIGHT_ASSIGN          // >>=
+	UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
+	AND_NOT_ASSIGN              // &^=
+
+	LOGICAL_AND // &&
+	LOGICAL_OR  // ||
+	INCREMENT   // ++
+	DECREMENT   // --
+
+	EQUAL        // ==
+	STRICT_EQUAL // ===
+	LESS         // <
+	GREATER      // >
+	ASSIGN       // =
+	NOT          // !
+
+	BITWISE_NOT // ~
+
+	NOT_EQUAL        // !=
+	STRICT_NOT_EQUAL // !==
+	LESS_OR_EQUAL    // <=
+	GREATER_OR_EQUAL // >=
+
+	LEFT_PARENTHESIS // (
+	LEFT_BRACKET     // [
+	LEFT_BRACE       // {
+	COMMA            // ,
+	PERIOD           // .
+
+	RIGHT_PARENTHESIS // )
+	RIGHT_BRACKET     // ]
+	RIGHT_BRACE       // }
+	SEMICOLON         // ;
+	COLON             // :
+	QUESTION_MARK     // ?
+
+	firstKeyword
+	IF
+	IN
+	DO
+
+	VAR
+	FOR
+	NEW
+	TRY
+
+	THIS
+	ELSE
+	CASE
+	VOID
+	WITH
+
+	WHILE
+	BREAK
+	CATCH
+	THROW
+
+	RETURN
+	TYPEOF
+	DELETE
+	SWITCH
+
+	DEFAULT
+	FINALLY
+
+	FUNCTION
+	CONTINUE
+	DEBUGGER
+
+	INSTANCEOF
+	lastKeyword
+)
+
+var token2string = [...]string{
+	ILLEGAL:                     "ILLEGAL",
+	EOF:                         "EOF",
+	COMMENT:                     "COMMENT",
+	KEYWORD:                     "KEYWORD",
+	STRING:                      "STRING",
+	BOOLEAN:                     "BOOLEAN",
+	NULL:                        "NULL",
+	NUMBER:                      "NUMBER",
+	IDENTIFIER:                  "IDENTIFIER",
+	PLUS:                        "+",
+	MINUS:                       "-",
+	MULTIPLY:                    "*",
+	SLASH:                       "/",
+	REMAINDER:                   "%",
+	AND:                         "&",
+	OR:                          "|",
+	EXCLUSIVE_OR:                "^",
+	SHIFT_LEFT:                  "<<",
+	SHIFT_RIGHT:                 ">>",
+	UNSIGNED_SHIFT_RIGHT:        ">>>",
+	AND_NOT:                     "&^",
+	ADD_ASSIGN:                  "+=",
+	SUBTRACT_ASSIGN:             "-=",
+	MULTIPLY_ASSIGN:             "*=",
+	QUOTIENT_ASSIGN:             "/=",
+	REMAINDER_ASSIGN:            "%=",
+	AND_ASSIGN:                  "&=",
+	OR_ASSIGN:                   "|=",
+	EXCLUSIVE_OR_ASSIGN:         "^=",
+	SHIFT_LEFT_ASSIGN:           "<<=",
+	SHIFT_RIGHT_ASSIGN:          ">>=",
+	UNSIGNED_SHIFT_RIGHT_ASSIGN: ">>>=",
+	AND_NOT_ASSIGN:              "&^=",
+	LOGICAL_AND:                 "&&",
+	LOGICAL_OR:                  "||",
+	INCREMENT:                   "++",
+	DECREMENT:                   "--",
+	EQUAL:                       "==",
+	STRICT_EQUAL:                "===",
+	LESS:                        "<",
+	GREATER:                     ">",
+	ASSIGN:                      "=",
+	NOT:                         "!",
+	BITWISE_NOT:                 "~",
+	NOT_EQUAL:                   "!=",
+	STRICT_NOT_EQUAL:            "!==",
+	LESS_OR_EQUAL:               "<=",
+	GREATER_OR_EQUAL:            ">=",
+	LEFT_PARENTHESIS:            "(",
+	LEFT_BRACKET:                "[",
+	LEFT_BRACE:                  "{",
+	COMMA:                       ",",
+	PERIOD:                      ".",
+	RIGHT_PARENTHESIS:           ")",
+	RIGHT_BRACKET:               "]",
+	RIGHT_BRACE:                 "}",
+	SEMICOLON:                   ";",
+	COLON:                       ":",
+	QUESTION_MARK:               "?",
+	IF:                          "if",
+	IN:                          "in",
+	DO:                          "do",
+	VAR:                         "var",
+	FOR:                         "for",
+	NEW:                         "new",
+	TRY:                         "try",
+	THIS:                        "this",
+	ELSE:                        "else",
+	CASE:                        "case",
+	VOID:                        "void",
+	WITH:                        "with",
+	WHILE:                       "while",
+	BREAK:                       "break",
+	CATCH:                       "catch",
+	THROW:                       "throw",
+	RETURN:                      "return",
+	TYPEOF:                      "typeof",
+	DELETE:                      "delete",
+	SWITCH:                      "switch",
+	DEFAULT:                     "default",
+	FINALLY:                     "finally",
+	FUNCTION:                    "function",
+	CONTINUE:                    "continue",
+	DEBUGGER:                    "debugger",
+	INSTANCEOF:                  "instanceof",
+}
+
+var keywordTable = map[string]_keyword{
+	"if": _keyword{
+		token: IF,
+	},
+	"in": _keyword{
+		token: IN,
+	},
+	"do": _keyword{
+		token: DO,
+	},
+	"var": _keyword{
+		token: VAR,
+	},
+	"for": _keyword{
+		token: FOR,
+	},
+	"new": _keyword{
+		token: NEW,
+	},
+	"try": _keyword{
+		token: TRY,
+	},
+	"this": _keyword{
+		token: THIS,
+	},
+	"else": _keyword{
+		token: ELSE,
+	},
+	"case": _keyword{
+		token: CASE,
+	},
+	"void": _keyword{
+		token: VOID,
+	},
+	"with": _keyword{
+		token: WITH,
+	},
+	"while": _keyword{
+		token: WHILE,
+	},
+	"break": _keyword{
+		token: BREAK,
+	},
+	"catch": _keyword{
+		token: CATCH,
+	},
+	"throw": _keyword{
+		token: THROW,
+	},
+	"return": _keyword{
+		token: RETURN,
+	},
+	"typeof": _keyword{
+		token: TYPEOF,
+	},
+	"delete": _keyword{
+		token: DELETE,
+	},
+	"switch": _keyword{
+		token: SWITCH,
+	},
+	"default": _keyword{
+		token: DEFAULT,
+	},
+	"finally": _keyword{
+		token: FINALLY,
+	},
+	"function": _keyword{
+		token: FUNCTION,
+	},
+	"continue": _keyword{
+		token: CONTINUE,
+	},
+	"debugger": _keyword{
+		token: DEBUGGER,
+	},
+	"instanceof": _keyword{
+		token: INSTANCEOF,
+	},
+	"const": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"class": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"enum": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"export": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"extends": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"import": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"super": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+	},
+	"implements": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"interface": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"let": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"package": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"private": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"protected": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"public": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+	"static": _keyword{
+		token:         KEYWORD,
+		futureKeyword: true,
+		strict:        true,
+	},
+}

+ 222 - 0
token/tokenfmt

@@ -0,0 +1,222 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+my (%token, @order, @keywords);
+
+{
+    my $keywords;
+    my @const;
+    push @const, <<_END_;
+package token
+
+const(
+    _ Token = iota
+_END_
+
+    for (split m/\n/, <<_END_) {
+ILLEGAL
+EOF
+COMMENT
+KEYWORD
+
+STRING
+BOOLEAN
+NULL
+NUMBER
+IDENTIFIER
+
+PLUS                            +
+MINUS                           -
+MULTIPLY                        *
+SLASH                           /
+REMAINDER                       %
+
+AND                             &
+OR                              |
+EXCLUSIVE_OR                    ^
+SHIFT_LEFT                      <<
+SHIFT_RIGHT                     >>
+UNSIGNED_SHIFT_RIGHT            >>>
+AND_NOT                         &^
+
+ADD_ASSIGN                     +=
+SUBTRACT_ASSIGN                -=
+MULTIPLY_ASSIGN                *=
+QUOTIENT_ASSIGN                /=
+REMAINDER_ASSIGN               %=
+
+AND_ASSIGN                     &=
+OR_ASSIGN                      |=
+EXCLUSIVE_OR_ASSIGN            ^=
+SHIFT_LEFT_ASSIGN              <<=
+SHIFT_RIGHT_ASSIGN             >>=
+UNSIGNED_SHIFT_RIGHT_ASSIGN    >>>=
+AND_NOT_ASSIGN                 &^=
+
+LOGICAL_AND                    &&
+LOGICAL_OR                     ||
+INCREMENT                      ++
+DECREMENT                      --
+
+EQUAL                          ==
+STRICT_EQUAL                   ===
+LESS                           <
+GREATER                        >
+ASSIGN                         =
+NOT                            !
+
+BITWISE_NOT                    ~
+
+NOT_EQUAL                      !=
+STRICT_NOT_EQUAL               !==
+LESS_OR_EQUAL                  <=
+GREATER_OR_EQUAL               <=
+
+LEFT_PARENTHESIS               (
+LEFT_BRACKET                   [
+LEFT_BRACE                     {
+COMMA                          ,
+PERIOD                         .
+
+RIGHT_PARENTHESIS              )
+RIGHT_BRACKET                  ]
+RIGHT_BRACE                    }
+SEMICOLON                      ;
+COLON                          :
+QUESTION_MARK                  ?
+
+firstKeyword
+IF
+IN
+DO
+
+VAR
+FOR
+NEW
+TRY
+
+THIS
+ELSE
+CASE
+VOID
+WITH
+
+WHILE
+BREAK
+CATCH
+THROW
+
+RETURN
+TYPEOF
+DELETE
+SWITCH
+
+DEFAULT
+FINALLY
+
+FUNCTION
+CONTINUE
+DEBUGGER
+
+INSTANCEOF
+lastKeyword
+_END_
+        chomp;
+
+        next if m/^\s*#/;
+
+        my ($name, $symbol) = m/(\w+)\s*(\S+)?/;
+
+        if (defined $symbol) {
+            push @order, $name;
+            push @const, "$name // $symbol";
+            $token{$name} = $symbol;
+        } elsif (defined $name) {
+            $keywords ||= $name eq 'firstKeyword';
+
+            push @const, $name;
+            #$const[-1] .= " Token = iota" if 2 == @const;
+            if ($name =~ m/^([A-Z]+)/) {
+                push @keywords, $name if $keywords;
+                push @order, $name;
+                if ($token{SEMICOLON}) {
+                    $token{$name} = lc $1;
+                } else {
+                    $token{$name} = $name;
+                }
+            }
+        } else {
+            push @const, "";
+        }
+
+    }
+    push @const, ")";
+    print join "\n", @const, "";
+}
+
+{
+    print <<_END_;
+
+var token2string = [...]string{
+_END_
+    for my $name (@order) {
+        print "$name: \"$token{$name}\",\n";
+    }
+    print <<_END_;
+}
+_END_
+
+    print <<_END_;
+
+var keywordTable = map[string]_keyword{
+_END_
+    for my $name (@keywords) {
+        print <<_END_
+			"@{[ lc $name ]}": _keyword{
+				token: $name,
+			},
+_END_
+    }
+
+    for my $name (qw/
+        const
+        class
+        enum
+        export
+        extends
+        import
+        super
+        /) {
+        print <<_END_
+			"$name": _keyword{
+				token: KEYWORD,
+                futureKeyword: true,
+			},
+_END_
+    }
+
+    for my $name (qw/
+        implements
+        interface
+        let
+        package
+        private
+        protected
+        public
+        static
+        /) {
+        print <<_END_
+			"$name": _keyword{
+				token: KEYWORD,
+                futureKeyword: true,
+                strict: true,
+			},
+_END_
+    }
+
+    print <<_END_;
+}
+_END_
+}

+ 815 - 0
value.go

@@ -0,0 +1,815 @@
+package goja
+
+import (
+	"math"
+	"reflect"
+	"regexp"
+	"strconv"
+)
+
+var (
+	valueFalse    Value = valueBool(false)
+	valueTrue     Value = valueBool(true)
+	_null         Value = valueNull{}
+	_NaN          Value = valueFloat(math.NaN())
+	_positiveInf  Value = valueFloat(math.Inf(+1))
+	_negativeInf  Value = valueFloat(math.Inf(-1))
+	_positiveZero Value
+	_negativeZero Value = valueFloat(math.Float64frombits(0 | (1 << 63)))
+	_epsilon            = valueFloat(2.2204460492503130808472633361816e-16)
+	_undefined    Value = valueUndefined{}
+)
+
+var (
+	reflectTypeInt    = reflect.TypeOf(int64(0))
+	reflectTypeBool   = reflect.TypeOf(false)
+	reflectTypeNil    = reflect.TypeOf(nil)
+	reflectTypeFloat  = reflect.TypeOf(float64(0))
+	reflectTypeMap    = reflect.TypeOf(map[string]interface{}{})
+	reflectTypeArray  = reflect.TypeOf([]interface{}{})
+	reflectTypeString = reflect.TypeOf("")
+)
+
+var intCache [256]Value
+
+type Value interface {
+	ToInteger() int64
+	ToString() valueString
+	String() string
+	ToFloat() float64
+	ToNumber() Value
+	ToBoolean() bool
+	ToObject(*Runtime) *Object
+	SameAs(Value) bool
+	Equals(Value) bool
+	StrictEquals(Value) bool
+	Export() interface{}
+	ExportType() reflect.Type
+
+	assertInt() (int64, bool)
+	assertString() (valueString, bool)
+	assertFloat() (float64, bool)
+
+	baseObject(r *Runtime) *Object
+}
+
+type valueInt int64
+type valueFloat float64
+type valueBool bool
+type valueNull struct{}
+type valueUndefined struct {
+	valueNull
+}
+
+type valueUnresolved struct {
+	r   *Runtime
+	ref string
+}
+
+type memberUnresolved struct {
+	valueUnresolved
+}
+
+type valueProperty struct {
+	value        Value
+	writable     bool
+	configurable bool
+	enumerable   bool
+	accessor     bool
+	getterFunc   *Object
+	setterFunc   *Object
+}
+
+func propGetter(o Value, v Value, r *Runtime) *Object {
+	if v == _undefined {
+		return nil
+	}
+	if obj, ok := v.(*Object); ok {
+		if _, ok := obj.self.assertCallable(); ok {
+			return obj
+		}
+	}
+	r.typeErrorResult(true, "Getter must be a function: %s", v.ToString())
+	return nil
+}
+
+func propSetter(o Value, v Value, r *Runtime) *Object {
+	if v == _undefined {
+		return nil
+	}
+	if obj, ok := v.(*Object); ok {
+		if _, ok := obj.self.assertCallable(); ok {
+			return obj
+		}
+	}
+	r.typeErrorResult(true, "Setter must be a function: %s", v.ToString())
+	return nil
+}
+
+func (i valueInt) ToInteger() int64 {
+	return int64(i)
+}
+
+func (i valueInt) ToString() valueString {
+	return asciiString(i.String())
+}
+
+func (i valueInt) String() string {
+	return strconv.FormatInt(int64(i), 10)
+}
+
+func (i valueInt) ToFloat() float64 {
+	return float64(int64(i))
+}
+
+func (i valueInt) ToBoolean() bool {
+	return i != 0
+}
+
+func (i valueInt) ToObject(r *Runtime) *Object {
+	return r.newPrimitiveObject(i, r.global.NumberPrototype, classNumber)
+}
+
+func (i valueInt) ToNumber() Value {
+	return i
+}
+
+func (i valueInt) SameAs(other Value) bool {
+	if otherInt, ok := other.assertInt(); ok {
+		return int64(i) == otherInt
+	}
+	return false
+}
+
+func (i valueInt) Equals(other Value) bool {
+	if o, ok := other.assertInt(); ok {
+		return int64(i) == o
+	}
+	if o, ok := other.assertFloat(); ok {
+		return float64(i) == o
+	}
+	if o, ok := other.assertString(); ok {
+		return o.ToNumber().Equals(i)
+	}
+	if o, ok := other.(valueBool); ok {
+		return int64(i) == o.ToInteger()
+	}
+	if o, ok := other.(*Object); ok {
+		return i.Equals(o.self.toPrimitiveNumber())
+	}
+	return false
+}
+
+func (i valueInt) StrictEquals(other Value) bool {
+	if otherInt, ok := other.assertInt(); ok {
+		return int64(i) == otherInt
+	} else if otherFloat, ok := other.assertFloat(); ok {
+		return float64(i) == otherFloat
+	}
+	return false
+}
+
+func (i valueInt) assertInt() (int64, bool) {
+	return int64(i), true
+}
+
+func (i valueInt) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (i valueInt) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (i valueInt) baseObject(r *Runtime) *Object {
+	return r.global.NumberPrototype
+}
+
+func (i valueInt) Export() interface{} {
+	return int64(i)
+}
+
+func (i valueInt) ExportType() reflect.Type {
+	return reflectTypeInt
+}
+
+func (o valueBool) ToInteger() int64 {
+	if o {
+		return 1
+	}
+	return 0
+}
+
+func (o valueBool) ToString() valueString {
+	if o {
+		return stringTrue
+	}
+	return stringFalse
+}
+
+func (o valueBool) String() string {
+	if o {
+		return "true"
+	}
+	return "false"
+}
+
+func (o valueBool) ToFloat() float64 {
+	if o {
+		return 1.0
+	}
+	return 0
+}
+
+func (o valueBool) ToBoolean() bool {
+	return bool(o)
+}
+
+func (o valueBool) ToObject(r *Runtime) *Object {
+	return r.newPrimitiveObject(o, r.global.BooleanPrototype, "Boolean")
+}
+
+func (o valueBool) ToNumber() Value {
+	if o {
+		return valueInt(1)
+	}
+	return valueInt(0)
+}
+
+func (o valueBool) SameAs(other Value) bool {
+	if other, ok := other.(valueBool); ok {
+		return o == other
+	}
+	return false
+}
+
+func (b valueBool) Equals(other Value) bool {
+	if o, ok := other.(valueBool); ok {
+		return b == o
+	}
+
+	if b {
+		return other.Equals(intToValue(1))
+	} else {
+		return other.Equals(intToValue(0))
+	}
+
+}
+
+func (o valueBool) StrictEquals(other Value) bool {
+	if other, ok := other.(valueBool); ok {
+		return o == other
+	}
+	return false
+}
+
+func (o valueBool) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (o valueBool) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (o valueBool) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (o valueBool) baseObject(r *Runtime) *Object {
+	return r.global.BooleanPrototype
+}
+
+func (o valueBool) Export() interface{} {
+	return bool(o)
+}
+
+func (o valueBool) ExportType() reflect.Type {
+	return reflectTypeBool
+}
+
+func (n valueNull) ToInteger() int64 {
+	return 0
+}
+
+func (n valueNull) ToString() valueString {
+	return stringNull
+}
+
+func (n valueNull) String() string {
+	return "null"
+}
+
+func (u valueUndefined) ToString() valueString {
+	return stringUndefined
+}
+
+func (u valueUndefined) String() string {
+	return "undefined"
+}
+
+func (u valueUndefined) ToNumber() Value {
+	return _NaN
+}
+
+func (u valueUndefined) SameAs(other Value) bool {
+	_, same := other.(valueUndefined)
+	return same
+}
+
+func (u valueUndefined) StrictEquals(other Value) bool {
+	_, same := other.(valueUndefined)
+	return same
+}
+
+func (u valueUndefined) ToFloat() float64 {
+	return math.NaN()
+}
+
+func (n valueNull) ToFloat() float64 {
+	return 0
+}
+
+func (n valueNull) ToBoolean() bool {
+	return false
+}
+
+func (n valueNull) ToObject(r *Runtime) *Object {
+	r.typeErrorResult(true, "Cannot convert undefined or null to object")
+	return nil
+	//return r.newObject()
+}
+
+func (n valueNull) ToNumber() Value {
+	return intToValue(0)
+}
+
+func (n valueNull) SameAs(other Value) bool {
+	_, same := other.(valueNull)
+	return same
+}
+
+func (n valueNull) Equals(other Value) bool {
+	switch other.(type) {
+	case valueUndefined, valueNull:
+		return true
+	}
+	return false
+}
+
+func (n valueNull) StrictEquals(other Value) bool {
+	_, same := other.(valueNull)
+	return same
+}
+
+func (n valueNull) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (n valueNull) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (n valueNull) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (n valueNull) baseObject(r *Runtime) *Object {
+	return nil
+}
+
+func (n valueNull) Export() interface{} {
+	return nil
+}
+
+func (n valueNull) ExportType() reflect.Type {
+	return reflectTypeNil
+}
+
+func (p *valueProperty) ToInteger() int64 {
+	return 0
+}
+
+func (p *valueProperty) ToString() valueString {
+	return stringEmpty
+}
+
+func (p *valueProperty) String() string {
+	return ""
+}
+
+func (p *valueProperty) ToFloat() float64 {
+	return math.NaN()
+}
+
+func (p *valueProperty) ToBoolean() bool {
+	return false
+}
+
+func (p *valueProperty) ToObject(r *Runtime) *Object {
+	return nil
+}
+
+func (p *valueProperty) ToNumber() Value {
+	return nil
+}
+
+func (p *valueProperty) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (p *valueProperty) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (p *valueProperty) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (p *valueProperty) isWritable() bool {
+	return p.writable || p.setterFunc != nil
+}
+
+func (p *valueProperty) get(this Value) Value {
+	if p.getterFunc == nil {
+		if p.value != nil {
+			return p.value
+		}
+		return _undefined
+	}
+	call, _ := p.getterFunc.self.assertCallable()
+	return call(FunctionCall{
+		This: this,
+	})
+}
+
+func (p *valueProperty) set(this, v Value) {
+	if p.setterFunc == nil {
+		p.value = v
+		return
+	}
+	call, _ := p.setterFunc.self.assertCallable()
+	call(FunctionCall{
+		This:      this,
+		Arguments: []Value{v},
+	})
+}
+
+func (p *valueProperty) SameAs(other Value) bool {
+	if otherProp, ok := other.(*valueProperty); ok {
+		return p == otherProp
+	}
+	return false
+}
+
+func (p *valueProperty) Equals(other Value) bool {
+	return false
+}
+
+func (p *valueProperty) StrictEquals(other Value) bool {
+	return false
+}
+
+func (n *valueProperty) baseObject(r *Runtime) *Object {
+	r.typeErrorResult(true, "BUG: baseObject() is called on valueProperty") // TODO error message
+	return nil
+}
+
+func (n *valueProperty) Export() interface{} {
+	panic("Cannot export valueProperty")
+}
+
+func (n *valueProperty) ExportType() reflect.Type {
+	panic("Cannot export valueProperty")
+}
+
+func (f valueFloat) ToInteger() int64 {
+	switch {
+	case math.IsNaN(float64(f)):
+		return 0
+	case math.IsInf(float64(f), 1):
+		return int64(math.MaxInt64)
+	case math.IsInf(float64(f), -1):
+		return int64(math.MinInt64)
+	}
+	return int64(f)
+}
+
+func (f valueFloat) ToString() valueString {
+	return asciiString(f.String())
+}
+
+var matchLeading0Exponent = regexp.MustCompile(`([eE][\+\-])0+([1-9])`) // 1e-07 => 1e-7
+
+func (f valueFloat) String() string {
+	value := float64(f)
+	if math.IsNaN(value) {
+		return "NaN"
+	} else if math.IsInf(value, 0) {
+		if math.Signbit(value) {
+			return "-Infinity"
+		}
+		return "Infinity"
+	} else if f == _negativeZero {
+		return "0"
+	}
+	exponent := math.Log10(math.Abs(value))
+	if exponent >= 21 || exponent < -6 {
+		return matchLeading0Exponent.ReplaceAllString(strconv.FormatFloat(value, 'g', -1, 64), "$1$2")
+	}
+	return strconv.FormatFloat(value, 'f', -1, 64)
+}
+
+func (f valueFloat) ToFloat() float64 {
+	return float64(f)
+}
+
+func (f valueFloat) ToBoolean() bool {
+	return float64(f) != 0.0 && !math.IsNaN(float64(f))
+}
+
+func (f valueFloat) ToObject(r *Runtime) *Object {
+	return r.newPrimitiveObject(f, r.global.NumberPrototype, "Number")
+}
+
+func (f valueFloat) ToNumber() Value {
+	return f
+}
+
+func (f valueFloat) SameAs(other Value) bool {
+	if o, ok := other.assertFloat(); ok {
+		this := float64(f)
+		if math.IsNaN(this) && math.IsNaN(o) {
+			return true
+		} else {
+			ret := this == o
+			if ret && this == 0 {
+				ret = math.Signbit(this) == math.Signbit(o)
+			}
+			return ret
+		}
+	} else if o, ok := other.assertInt(); ok {
+		this := float64(f)
+		ret := this == float64(o)
+		if ret && this == 0 {
+			ret = !math.Signbit(this)
+		}
+		return ret
+	}
+	return false
+}
+
+func (f valueFloat) Equals(other Value) bool {
+	if o, ok := other.assertFloat(); ok {
+		return float64(f) == o
+	}
+
+	if o, ok := other.assertInt(); ok {
+		return float64(f) == float64(o)
+	}
+
+	if _, ok := other.assertString(); ok {
+		return float64(f) == other.ToFloat()
+	}
+
+	if o, ok := other.(valueBool); ok {
+		return float64(f) == o.ToFloat()
+	}
+
+	if o, ok := other.(*Object); ok {
+		return f.Equals(o.self.toPrimitiveNumber())
+	}
+
+	return false
+}
+
+func (f valueFloat) StrictEquals(other Value) bool {
+	if o, ok := other.assertFloat(); ok {
+		return float64(f) == o
+	} else if o, ok := other.assertInt(); ok {
+		return float64(f) == float64(o)
+	}
+	return false
+}
+
+func (f valueFloat) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (f valueFloat) assertFloat() (float64, bool) {
+	return float64(f), true
+}
+
+func (f valueFloat) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (f valueFloat) baseObject(r *Runtime) *Object {
+	return r.global.NumberPrototype
+}
+
+func (f valueFloat) Export() interface{} {
+	return float64(f)
+}
+
+func (f valueFloat) ExportType() reflect.Type {
+	return reflectTypeFloat
+}
+
+func (o *Object) ToInteger() int64 {
+	return o.self.toPrimitiveNumber().ToNumber().ToInteger()
+}
+
+func (o *Object) ToString() valueString {
+	return o.self.toPrimitiveString().ToString()
+}
+
+func (o *Object) String() string {
+	return o.self.toPrimitiveString().String()
+}
+
+func (o *Object) ToFloat() float64 {
+	return o.self.toPrimitiveNumber().ToFloat()
+}
+
+func (o *Object) ToBoolean() bool {
+	return true
+}
+
+func (o *Object) ToObject(r *Runtime) *Object {
+	return o
+}
+
+func (o *Object) ToNumber() Value {
+	return o.self.toPrimitiveNumber().ToNumber()
+}
+
+func (o *Object) SameAs(other Value) bool {
+	if other, ok := other.(*Object); ok {
+		return o == other
+	}
+	return false
+}
+
+func (o *Object) Equals(other Value) bool {
+	if other, ok := other.(*Object); ok {
+		return o == other || o.self.equal(other.self)
+	}
+
+	if _, ok := other.assertInt(); ok {
+		return o.self.toPrimitive().Equals(other)
+	}
+
+	if _, ok := other.assertFloat(); ok {
+		return o.self.toPrimitive().Equals(other)
+	}
+
+	if other, ok := other.(valueBool); ok {
+		return o.Equals(other.ToNumber())
+	}
+
+	if _, ok := other.assertString(); ok {
+		return o.self.toPrimitive().Equals(other)
+	}
+	return false
+}
+
+func (o *Object) StrictEquals(other Value) bool {
+	if other, ok := other.(*Object); ok {
+		return o == other || o.self.equal(other.self)
+	}
+	return false
+}
+
+func (o *Object) assertInt() (int64, bool) {
+	return 0, false
+}
+
+func (o *Object) assertFloat() (float64, bool) {
+	return 0, false
+}
+
+func (o *Object) assertString() (valueString, bool) {
+	return nil, false
+}
+
+func (o *Object) baseObject(r *Runtime) *Object {
+	return o
+}
+
+func (o *Object) Export() interface{} {
+	return o.self.export()
+}
+
+func (o *Object) ExportType() reflect.Type {
+	return o.self.exportType()
+}
+
+func (o *Object) Get(name string) Value {
+	return o.self.getStr(name)
+}
+
+func (o *Object) Set(name string, value interface{}) (err error) {
+	defer func() {
+		if x := recover(); x != nil {
+			if ex, ok := x.(*Exception); ok {
+				err = ex
+			} else {
+				panic(x)
+			}
+		}
+	}()
+
+	o.self.putStr(name, o.runtime.ToValue(value), true)
+	return
+}
+
+func (o valueUnresolved) throw() {
+	o.r.throwReferenceError(o.ref)
+}
+
+func (o valueUnresolved) ToInteger() int64 {
+	o.throw()
+	return 0
+}
+
+func (o valueUnresolved) ToString() valueString {
+	o.throw()
+	return nil
+}
+
+func (o valueUnresolved) String() string {
+	o.throw()
+	return ""
+}
+
+func (o valueUnresolved) ToFloat() float64 {
+	o.throw()
+	return 0
+}
+
+func (o valueUnresolved) ToBoolean() bool {
+	o.throw()
+	return false
+}
+
+func (o valueUnresolved) ToObject(r *Runtime) *Object {
+	o.throw()
+	return nil
+}
+
+func (o valueUnresolved) ToNumber() Value {
+	o.throw()
+	return nil
+}
+
+func (o valueUnresolved) SameAs(other Value) bool {
+	o.throw()
+	return false
+}
+
+func (o valueUnresolved) Equals(other Value) bool {
+	o.throw()
+	return false
+}
+
+func (o valueUnresolved) StrictEquals(other Value) bool {
+	o.throw()
+	return false
+}
+
+func (o valueUnresolved) assertInt() (int64, bool) {
+	o.throw()
+	return 0, false
+}
+
+func (o valueUnresolved) assertFloat() (float64, bool) {
+	o.throw()
+	return 0, false
+}
+
+func (o valueUnresolved) assertString() (valueString, bool) {
+	o.throw()
+	return nil, false
+}
+
+func (o valueUnresolved) baseObject(r *Runtime) *Object {
+	o.throw()
+	return nil
+}
+
+func (o valueUnresolved) Export() interface{} {
+	o.throw()
+	return nil
+}
+
+func (o valueUnresolved) ExportType() reflect.Type {
+	o.throw()
+	return nil
+}
+
+func init() {
+	for i := 0; i < 256; i++ {
+		intCache[i] = valueInt(i - 128)
+	}
+	_positiveZero = intToValue(0)
+}

+ 2497 - 0
vm.go

@@ -0,0 +1,2497 @@
+package goja
+
+import (
+	"fmt"
+	"log"
+	"math"
+	"strconv"
+	"sync"
+)
+
+const (
+	maxInt = 1 << 53
+)
+
+type valueStack []Value
+
+type stash struct {
+	values    valueStack
+	extraArgs valueStack
+	names     map[string]uint32
+	obj       objectImpl
+
+	outer *stash
+}
+
+type context struct {
+	prg      *Program
+	funcName string
+	stash    *stash
+	pc, sb   int
+	args     int
+}
+
+type iterStackItem struct {
+	val Value
+	f   iterNextFunc
+}
+
+type ref interface {
+	get() Value
+	set(Value)
+	refname() string
+}
+
+type stashRef struct {
+	v *Value
+	n string
+}
+
+func (r stashRef) get() Value {
+	return *r.v
+}
+
+func (r *stashRef) set(v Value) {
+	*r.v = v
+}
+
+func (r *stashRef) refname() string {
+	return r.n
+}
+
+type objRef struct {
+	base   objectImpl
+	name   string
+	strict bool
+}
+
+func (r *objRef) get() Value {
+	return r.base.getStr(r.name)
+}
+
+func (r *objRef) set(v Value) {
+	r.base.putStr(r.name, v, r.strict)
+}
+
+func (r *objRef) refname() string {
+	return r.name
+}
+
+type unresolvedRef struct {
+	runtime *Runtime
+	name    string
+}
+
+func (r *unresolvedRef) get() Value {
+	r.runtime.throwReferenceError(r.name)
+	panic("Unreachable")
+}
+
+func (r *unresolvedRef) set(v Value) {
+	r.get()
+}
+
+func (r *unresolvedRef) refname() string {
+	return r.name
+}
+
+type vm struct {
+	r            *Runtime
+	prg          *Program
+	funcName     string
+	pc           int
+	stack        valueStack
+	sp, sb, args int
+
+	stash     *stash
+	callStack []context
+	iterStack []iterStackItem
+	refStack  []ref
+
+	stashAllocs int
+	halt        bool
+
+	interrupt     bool
+	interruptVal  interface{}
+	interruptLock sync.Mutex
+}
+
+type instruction interface {
+	exec(*vm)
+}
+
+func intToValue(i int64) Value {
+	if i >= -maxInt && i <= maxInt {
+		if i >= -128 && i <= 127 {
+			return intCache[i+128]
+		}
+		return valueInt(i)
+	}
+	return valueFloat(float64(i))
+}
+
+func floatToInt(f float64) (result int64, ok bool) {
+	if (f != 0 || !math.Signbit(f)) && !math.IsInf(f, 0) && f == math.Trunc(f) && f >= -maxInt && f <= maxInt {
+		return int64(f), true
+	}
+	return 0, false
+}
+
+func floatToValue(f float64) (result Value) {
+	if i, ok := floatToInt(f); ok {
+		return intToValue(i)
+	}
+	switch {
+	case f == 0:
+		return _negativeZero
+	case math.IsNaN(f):
+		return _NaN
+	case math.IsInf(f, 1):
+		return _positiveInf
+	case math.IsInf(f, -1):
+		return _negativeInf
+	}
+	return valueFloat(f)
+}
+
+func toInt(v Value) (int64, bool) {
+	num := v.ToNumber()
+	if i, ok := num.assertInt(); ok {
+		return i, true
+	}
+	if f, ok := num.assertFloat(); ok {
+		if i, ok := floatToInt(f); ok {
+			return i, true
+		}
+	}
+	return 0, false
+}
+
+func toIntIgnoreNegZero(v Value) (int64, bool) {
+	num := v.ToNumber()
+	if i, ok := num.assertInt(); ok {
+		return i, true
+	}
+	if f, ok := num.assertFloat(); ok {
+		if v == _negativeZero {
+			return 0, true
+		}
+		if i, ok := floatToInt(f); ok {
+			return i, true
+		}
+	}
+	return 0, false
+}
+
+func (s *valueStack) expand(idx int) {
+	if idx < len(*s) {
+		return
+	}
+
+	if idx < cap(*s) {
+		*s = (*s)[:idx+1]
+	} else {
+		n := make([]Value, idx+1, (idx+1)<<1)
+		copy(n, *s)
+		*s = n
+	}
+}
+
+func (s *stash) put(name string, v Value) bool {
+	if s.obj != nil {
+		if found := s.obj.getStr(name); found != nil {
+			s.obj.putStr(name, v, false)
+			return true
+		}
+		return false
+	} else {
+		if idx, found := s.names[name]; found {
+			s.values.expand(int(idx))
+			s.values[idx] = v
+			return true
+		}
+		return false
+	}
+}
+
+func (s *stash) putByIdx(idx uint32, v Value) {
+	if s.obj != nil {
+		panic("Attempt to put by idx into an object scope")
+	}
+	s.values.expand(int(idx))
+	s.values[idx] = v
+}
+
+func (s *stash) getByIdx(idx uint32) Value {
+	if int(idx) < len(s.values) {
+		return s.values[idx]
+	}
+	return _undefined
+}
+
+func (s *stash) getByName(name string, vm *vm) (v Value, exists bool) {
+	if s.obj != nil {
+		v = s.obj.getStr(name)
+		if v == nil {
+			return nil, false
+			//return valueUnresolved{r: vm.r, ref: name}, false
+		}
+		return v, true
+	}
+	if idx, exists := s.names[name]; exists {
+		return s.values[idx], true
+	}
+	return nil, false
+	//return valueUnresolved{r: vm.r, ref: name}, false
+}
+
+func (s *stash) createBinding(name string) {
+	if s.names == nil {
+		s.names = make(map[string]uint32)
+	}
+	s.names[name] = uint32(len(s.values))
+	s.values = append(s.values, _undefined)
+}
+
+func (s *stash) deleteBinding(name string) bool {
+	if s.obj != nil {
+		return s.obj.deleteStr(name, false)
+	}
+	if idx, found := s.names[name]; found {
+		s.values[idx] = nil
+		delete(s.names, name)
+		return true
+	}
+	return false
+}
+
+func (vm *vm) newStash() {
+	vm.stash = &stash{
+		outer: vm.stash,
+	}
+	vm.stashAllocs++
+}
+
+func (vm *vm) init() {
+}
+
+func (vm *vm) run() {
+	vm.halt = false
+	for !vm.halt && !vm.interrupt {
+		vm.prg.code[vm.pc].exec(vm)
+	}
+
+	if vm.interrupt {
+		vm.interruptLock.Lock()
+		v := &InterruptedError{
+			iface: vm.interruptVal,
+		}
+		vm.interrupt = false
+		vm.interruptVal = nil
+		vm.interruptLock.Unlock()
+		panic(v)
+	}
+}
+
+func (vm *vm) Interrupt(v interface{}) {
+	vm.interruptLock.Lock()
+	vm.interruptVal = v
+	vm.interrupt = true
+	vm.interruptLock.Unlock()
+}
+
+func (vm *vm) captureStack(stack []stackFrame, ctxOffset int) []stackFrame {
+	// Unroll the context stack
+	stack = append(stack, stackFrame{prg: vm.prg, pc: vm.pc, funcName: vm.funcName})
+	for i := len(vm.callStack) - 1; i > ctxOffset-1; i-- {
+		if vm.callStack[i].pc != -1 {
+			stack = append(stack, stackFrame{prg: vm.callStack[i].prg, pc: vm.callStack[i].pc - 1, funcName: vm.callStack[i].funcName})
+		}
+	}
+	return stack
+}
+
+func (vm *vm) try(f func()) (ex *Exception) {
+	var ctx context
+	vm.saveCtx(&ctx)
+
+	ctxOffset := len(vm.callStack)
+	sp := vm.sp
+	iterLen := len(vm.iterStack)
+	refLen := len(vm.refStack)
+
+	defer func() {
+		if x := recover(); x != nil {
+			defer func() {
+				vm.callStack = vm.callStack[:ctxOffset]
+				vm.restoreCtx(&ctx)
+				vm.sp = sp
+
+				// Restore other stacks
+				iterTail := vm.iterStack[iterLen:]
+				for i, _ := range iterTail {
+					iterTail[i] = iterStackItem{}
+				}
+				vm.iterStack = vm.iterStack[:iterLen]
+				refTail := vm.refStack[refLen:]
+				for i, _ := range refTail {
+					refTail[i] = nil
+				}
+				vm.refStack = vm.refStack[:refLen]
+			}()
+			switch x1 := x.(type) {
+			case Value:
+				ex = &Exception{
+					val: x1,
+				}
+			case *InterruptedError:
+				x1.stack = vm.captureStack(x1.stack, ctxOffset)
+				panic(x1)
+			case *Exception:
+				ex = x1
+			default:
+				if vm.prg != nil {
+					vm.prg.dumpCode(log.Printf)
+				}
+				//log.Print("Stack: ", string(debug.Stack()))
+				panic(fmt.Errorf("Panic at %d: %v", vm.pc, x))
+			}
+			ex.stack = vm.captureStack(ex.stack, ctxOffset)
+		}
+	}()
+
+	f()
+	return
+}
+
+func (vm *vm) runTry() (ex *Exception) {
+	return vm.try(vm.run)
+}
+
+func (vm *vm) push(v Value) {
+	vm.stack.expand(vm.sp)
+	vm.stack[vm.sp] = v
+	vm.sp++
+}
+
+func (vm *vm) pop() Value {
+	vm.sp--
+	return vm.stack[vm.sp]
+}
+
+func (vm *vm) peek() Value {
+	return vm.stack[vm.sp-1]
+}
+
+func (vm *vm) saveCtx(ctx *context) {
+	ctx.prg = vm.prg
+	ctx.funcName = vm.funcName
+	ctx.stash = vm.stash
+	ctx.pc = vm.pc
+	ctx.sb = vm.sb
+	ctx.args = vm.args
+}
+
+func (vm *vm) pushCtx() {
+	/*
+		vm.ctxStack = append(vm.ctxStack, context{
+			prg: vm.prg,
+			stash: vm.stash,
+			pc: vm.pc,
+			sb: vm.sb,
+			args: vm.args,
+		})*/
+	vm.callStack = append(vm.callStack, context{})
+	vm.saveCtx(&vm.callStack[len(vm.callStack)-1])
+}
+
+func (vm *vm) restoreCtx(ctx *context) {
+	vm.prg = ctx.prg
+	vm.funcName = ctx.funcName
+	vm.pc = ctx.pc
+	vm.stash = ctx.stash
+	vm.sb = ctx.sb
+	vm.args = ctx.args
+}
+
+func (vm *vm) popCtx() {
+	l := len(vm.callStack) - 1
+	vm.prg = vm.callStack[l].prg
+	vm.callStack[l].prg = nil
+	vm.funcName = vm.callStack[l].funcName
+	vm.pc = vm.callStack[l].pc
+	vm.stash = vm.callStack[l].stash
+	vm.callStack[l].stash = nil
+	vm.sb = vm.callStack[l].sb
+	vm.args = vm.callStack[l].args
+
+	vm.callStack = vm.callStack[:l]
+}
+
+func (r *Runtime) toObject(v Value, args ...interface{}) *Object {
+	//r.checkResolveable(v)
+	if obj, ok := v.(*Object); ok {
+		return obj
+	}
+	if len(args) > 0 {
+		r.typeErrorResult(true, args)
+	} else {
+		r.typeErrorResult(true, "Value is not an object: %s", v.ToString())
+	}
+	panic("Unreachable")
+}
+
+func (r *Runtime) toCallee(v Value) *Object {
+	if obj, ok := v.(*Object); ok {
+		return obj
+	}
+	switch unresolved := v.(type) {
+	case valueUnresolved:
+		unresolved.throw()
+		panic("Unreachable")
+	case memberUnresolved:
+		r.typeErrorResult(true, "Object has no member '%s'", unresolved.ref)
+		panic("Unreachable")
+	}
+	r.typeErrorResult(true, "Value is not an object: %s", v.ToString())
+	panic("Unreachable")
+}
+
+type _newStash struct{}
+
+var newStash _newStash
+
+func (_newStash) exec(vm *vm) {
+	vm.newStash()
+	vm.pc++
+}
+
+type _noop struct{}
+
+var noop _noop
+
+func (_noop) exec(vm *vm) {
+	vm.pc++
+}
+
+type loadVal uint32
+
+func (l loadVal) exec(vm *vm) {
+	vm.push(vm.prg.values[l])
+	vm.pc++
+}
+
+type loadVal1 uint32
+
+func (l *loadVal1) exec(vm *vm) {
+	vm.push(vm.prg.values[*l])
+	vm.pc++
+}
+
+type _loadUndef struct{}
+
+var loadUndef _loadUndef
+
+func (_loadUndef) exec(vm *vm) {
+	vm.push(_undefined)
+	vm.pc++
+}
+
+type _loadNil struct{}
+
+var loadNil _loadNil
+
+func (_loadNil) exec(vm *vm) {
+	vm.push(nil)
+	vm.pc++
+}
+
+type _loadGlobalObject struct{}
+
+var loadGlobalObject _loadGlobalObject
+
+func (_loadGlobalObject) exec(vm *vm) {
+	vm.push(vm.r.globalObject)
+	vm.pc++
+}
+
+type loadStack int
+
+func (l loadStack) exec(vm *vm) {
+	// l < 0 -- arg<-l-1>
+	// l > 0 -- var<l-1>
+	// l == 0 -- this
+
+	if l < 0 {
+		arg := int(-l)
+		if arg > vm.args {
+			vm.push(_undefined)
+		} else {
+			vm.push(vm.stack[vm.sb+arg])
+		}
+	} else if l > 0 {
+		vm.push(vm.stack[vm.sb+vm.args+int(l)])
+	} else {
+		vm.push(vm.stack[vm.sb])
+	}
+	vm.pc++
+}
+
+type _loadCallee struct{}
+
+var loadCallee _loadCallee
+
+func (_loadCallee) exec(vm *vm) {
+	vm.push(vm.stack[vm.sb-1])
+	vm.pc++
+}
+
+func (vm *vm) storeStack(s int) {
+	// l < 0 -- arg<-l-1>
+	// l > 0 -- var<l-1>
+	// l == 0 -- this
+
+	if s < 0 {
+		vm.stack[vm.sb-s] = vm.stack[vm.sp-1]
+	} else if s > 0 {
+		vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1]
+	} else {
+		panic("Attempt to modify this")
+	}
+	vm.pc++
+}
+
+type storeStack int
+
+func (s storeStack) exec(vm *vm) {
+	vm.storeStack(int(s))
+}
+
+type storeStackP int
+
+func (s storeStackP) exec(vm *vm) {
+	vm.storeStack(int(s))
+	vm.sp--
+}
+
+type _toNumber struct{}
+
+var toNumber _toNumber
+
+func (_toNumber) exec(vm *vm) {
+	vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber()
+	vm.pc++
+}
+
+type _add struct{}
+
+var add _add
+
+func (_add) exec(vm *vm) {
+	right := vm.stack[vm.sp-1]
+	left := vm.stack[vm.sp-2]
+
+	if o, ok := left.(*Object); ok {
+		left = o.self.toPrimitive()
+	}
+
+	if o, ok := right.(*Object); ok {
+		right = o.self.toPrimitive()
+	}
+
+	var ret Value
+
+	leftString, isLeftString := left.assertString()
+	rightString, isRightString := right.assertString()
+
+	if isLeftString || isRightString {
+		if !isLeftString {
+			leftString = left.ToString()
+		}
+		if !isRightString {
+			rightString = right.ToString()
+		}
+		ret = leftString.concat(rightString)
+	} else {
+		if leftInt, ok := left.assertInt(); ok {
+			if rightInt, ok := right.assertInt(); ok {
+				ret = intToValue(int64(leftInt) + int64(rightInt))
+			} else {
+				ret = floatToValue(float64(leftInt) + right.ToFloat())
+			}
+		} else {
+			ret = floatToValue(left.ToFloat() + right.ToFloat())
+		}
+	}
+
+	vm.stack[vm.sp-2] = ret
+	vm.sp--
+	vm.pc++
+}
+
+type _sub struct{}
+
+var sub _sub
+
+func (_sub) exec(vm *vm) {
+	right := vm.stack[vm.sp-1]
+	left := vm.stack[vm.sp-2]
+
+	var result Value
+
+	if left, ok := left.assertInt(); ok {
+		if right, ok := right.assertInt(); ok {
+			result = intToValue(left - right)
+			goto end
+		}
+	}
+
+	result = floatToValue(left.ToFloat() - right.ToFloat())
+end:
+	vm.sp--
+	vm.stack[vm.sp-1] = result
+	vm.pc++
+}
+
+type _mul struct{}
+
+var mul _mul
+
+func (_mul) exec(vm *vm) {
+	left := vm.stack[vm.sp-2]
+	right := vm.stack[vm.sp-1]
+
+	var result Value
+
+	if left, ok := toInt(left); ok {
+		if right, ok := toInt(right); ok {
+			if left == 0 && right == -1 || left == -1 && right == 0 {
+				result = _negativeZero
+				goto end
+			}
+			res := left * right
+			// check for overflow
+			if left == 0 || right == 0 || res/left == right {
+				result = intToValue(res)
+				goto end
+			}
+
+		}
+	}
+
+	result = floatToValue(left.ToFloat() * right.ToFloat())
+
+end:
+	vm.sp--
+	vm.stack[vm.sp-1] = result
+	vm.pc++
+}
+
+type _div struct{}
+
+var div _div
+
+func (_div) exec(vm *vm) {
+	left := vm.stack[vm.sp-2].ToFloat()
+	right := vm.stack[vm.sp-1].ToFloat()
+
+	var result Value
+
+	if math.IsNaN(left) || math.IsNaN(right) {
+		result = _NaN
+		goto end
+	}
+	if math.IsInf(left, 0) && math.IsInf(right, 0) {
+		result = _NaN
+		goto end
+	}
+	if left == 0 && right == 0 {
+		result = _NaN
+		goto end
+	}
+
+	if math.IsInf(left, 0) {
+		if math.Signbit(left) == math.Signbit(right) {
+			result = _positiveInf
+			goto end
+		} else {
+			result = _negativeInf
+			goto end
+		}
+	}
+	if math.IsInf(right, 0) {
+		if math.Signbit(left) == math.Signbit(right) {
+			result = _positiveZero
+			goto end
+		} else {
+			result = _negativeZero
+			goto end
+		}
+	}
+	if right == 0 {
+		if math.Signbit(left) == math.Signbit(right) {
+			result = _positiveInf
+			goto end
+		} else {
+			result = _negativeInf
+			goto end
+		}
+	}
+
+	result = floatToValue(left / right)
+
+end:
+	vm.sp--
+	vm.stack[vm.sp-1] = result
+	vm.pc++
+}
+
+type _mod struct{}
+
+var mod _mod
+
+func (_mod) exec(vm *vm) {
+	left := vm.stack[vm.sp-2]
+	right := vm.stack[vm.sp-1]
+
+	var result Value
+
+	if leftInt, ok := toInt(left); ok {
+		if rightInt, ok := toInt(right); ok {
+			if rightInt == 0 {
+				result = _NaN
+				goto end
+			}
+			r := leftInt % rightInt
+			if r == 0 && leftInt < 0 {
+				result = _negativeZero
+			} else {
+				result = intToValue(leftInt % rightInt)
+			}
+			goto end
+		}
+	}
+
+	result = floatToValue(math.Mod(left.ToFloat(), right.ToFloat()))
+end:
+	vm.sp--
+	vm.stack[vm.sp-1] = result
+	vm.pc++
+}
+
+type _neg struct{}
+
+var neg _neg
+
+func (_neg) exec(vm *vm) {
+	operand := vm.stack[vm.sp-1]
+
+	var result Value
+
+	if i, ok := toInt(operand); ok {
+		if i == 0 {
+			result = _negativeZero
+		} else {
+			result = valueInt(-i)
+		}
+	} else {
+		f := operand.ToFloat()
+		if !math.IsNaN(f) {
+			f = -f
+		}
+		result = valueFloat(f)
+	}
+
+	vm.stack[vm.sp-1] = result
+	vm.pc++
+}
+
+type _plus struct{}
+
+var plus _plus
+
+func (_plus) exec(vm *vm) {
+	vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber()
+	vm.pc++
+}
+
+type _inc struct{}
+
+var inc _inc
+
+func (_inc) exec(vm *vm) {
+	v := vm.stack[vm.sp-1]
+
+	if i, ok := toInt(v); ok {
+		v = intToValue(i + 1)
+		goto end
+	}
+
+	v = valueFloat(v.ToFloat() + 1)
+
+end:
+	vm.stack[vm.sp-1] = v
+	vm.pc++
+}
+
+type _dec struct{}
+
+var dec _dec
+
+func (_dec) exec(vm *vm) {
+	v := vm.stack[vm.sp-1]
+
+	if i, ok := toInt(v); ok {
+		v = intToValue(i - 1)
+		goto end
+	}
+
+	v = valueFloat(v.ToFloat() - 1)
+
+end:
+	vm.stack[vm.sp-1] = v
+	vm.pc++
+}
+
+type _and struct{}
+
+var and _and
+
+func (_and) exec(vm *vm) {
+	left := toInt32(vm.stack[vm.sp-2])
+	right := toInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-2] = intToValue(int64(left & right))
+	vm.sp--
+	vm.pc++
+}
+
+type _or struct{}
+
+var or _or
+
+func (_or) exec(vm *vm) {
+	left := toInt32(vm.stack[vm.sp-2])
+	right := toInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-2] = intToValue(int64(left | right))
+	vm.sp--
+	vm.pc++
+}
+
+type _xor struct{}
+
+var xor _xor
+
+func (_xor) exec(vm *vm) {
+	left := toInt32(vm.stack[vm.sp-2])
+	right := toInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-2] = intToValue(int64(left ^ right))
+	vm.sp--
+	vm.pc++
+}
+
+type _bnot struct{}
+
+var bnot _bnot
+
+func (_bnot) exec(vm *vm) {
+	op := toInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-1] = intToValue(int64(^op))
+	vm.pc++
+}
+
+type _sal struct{}
+
+var sal _sal
+
+func (_sal) exec(vm *vm) {
+	left := toInt32(vm.stack[vm.sp-2])
+	right := toUInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-2] = intToValue(int64(left << (right & 0x1F)))
+	vm.sp--
+	vm.pc++
+}
+
+type _sar struct{}
+
+var sar _sar
+
+func (_sar) exec(vm *vm) {
+	left := toInt32(vm.stack[vm.sp-2])
+	right := toUInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F)))
+	vm.sp--
+	vm.pc++
+}
+
+type _shr struct{}
+
+var shr _shr
+
+func (_shr) exec(vm *vm) {
+	left := toUInt32(vm.stack[vm.sp-2])
+	right := toUInt32(vm.stack[vm.sp-1])
+	vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F)))
+	vm.sp--
+	vm.pc++
+}
+
+type _halt struct{}
+
+var halt _halt
+
+func (_halt) exec(vm *vm) {
+	vm.halt = true
+	vm.pc++
+}
+
+type jump int32
+
+func (j jump) exec(vm *vm) {
+	vm.pc += int(j)
+}
+
+type _setElem struct{}
+
+var setElem _setElem
+
+func (_setElem) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-3])
+	propName := vm.stack[vm.sp-2]
+	val := vm.stack[vm.sp-1]
+
+	obj.self.put(propName, val, false)
+
+	vm.sp -= 2
+	vm.stack[vm.sp-1] = val
+	vm.pc++
+}
+
+type _setElemStrict struct{}
+
+var setElemStrict _setElemStrict
+
+func (_setElemStrict) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-3])
+	propName := vm.stack[vm.sp-2]
+	val := vm.stack[vm.sp-1]
+
+	obj.self.put(propName, val, true)
+
+	vm.sp -= 2
+	vm.stack[vm.sp-1] = val
+	vm.pc++
+}
+
+type _deleteElem struct{}
+
+var deleteElem _deleteElem
+
+func (_deleteElem) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-2])
+	propName := vm.stack[vm.sp-1]
+	if !obj.self.hasProperty(propName) || obj.self.delete(propName, false) {
+		vm.stack[vm.sp-2] = valueTrue
+	} else {
+		vm.stack[vm.sp-2] = valueFalse
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _deleteElemStrict struct{}
+
+var deleteElemStrict _deleteElemStrict
+
+func (_deleteElemStrict) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-2])
+	propName := vm.stack[vm.sp-1]
+	obj.self.delete(propName, true)
+	vm.stack[vm.sp-2] = valueTrue
+	vm.sp--
+	vm.pc++
+}
+
+type deleteProp string
+
+func (d deleteProp) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-1])
+	if !obj.self.hasPropertyStr(string(d)) || obj.self.deleteStr(string(d), false) {
+		vm.stack[vm.sp-1] = valueTrue
+	} else {
+		vm.stack[vm.sp-1] = valueFalse
+	}
+	vm.pc++
+}
+
+type deletePropStrict string
+
+func (d deletePropStrict) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-1])
+	obj.self.deleteStr(string(d), true)
+	vm.stack[vm.sp-1] = valueTrue
+	vm.pc++
+}
+
+type setProp string
+
+func (p setProp) exec(vm *vm) {
+	val := vm.stack[vm.sp-1]
+
+	vm.r.toObject(vm.stack[vm.sp-2]).self.putStr(string(p), val, false)
+	vm.stack[vm.sp-2] = val
+	vm.sp--
+	vm.pc++
+}
+
+type setPropStrict string
+
+func (p setPropStrict) exec(vm *vm) {
+	obj := vm.stack[vm.sp-2]
+	val := vm.stack[vm.sp-1]
+
+	obj1 := vm.r.toObject(obj)
+	obj1.self.putStr(string(p), val, true)
+	vm.stack[vm.sp-2] = val
+	vm.sp--
+	vm.pc++
+}
+
+type setProp1 string
+
+func (p setProp1) exec(vm *vm) {
+	vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(string(p), vm.stack[vm.sp-1], true, true, true)
+
+	vm.sp--
+	vm.pc++
+}
+
+type _setProto struct{}
+
+var setProto _setProto
+
+func (_setProto) exec(vm *vm) {
+	vm.r.toObject(vm.stack[vm.sp-2]).self.putStr("__proto__", vm.stack[vm.sp-1], true)
+
+	vm.sp--
+	vm.pc++
+}
+
+type setPropGetter string
+
+func (s setPropGetter) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-2])
+	val := vm.stack[vm.sp-1]
+
+	descr := vm.r.NewObject().self
+	descr.putStr("get", val, false)
+	descr.putStr("configurable", valueTrue, false)
+	descr.putStr("enumerable", valueTrue, false)
+
+	obj.self.defineOwnProperty(newStringValue(string(s)), descr, false)
+
+	vm.sp--
+	vm.pc++
+}
+
+type setPropSetter string
+
+func (s setPropSetter) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-2])
+	val := vm.stack[vm.sp-1]
+
+	descr := vm.r.NewObject().self
+	descr.putStr("set", val, false)
+	descr.putStr("configurable", valueTrue, false)
+	descr.putStr("enumerable", valueTrue, false)
+
+	obj.self.defineOwnProperty(newStringValue(string(s)), descr, false)
+
+	vm.sp--
+	vm.pc++
+}
+
+type getProp string
+
+func (g getProp) exec(vm *vm) {
+	v := vm.stack[vm.sp-1]
+	obj := v.baseObject(vm.r)
+	if obj == nil {
+		vm.r.typeErrorResult(true, "Cannot read property '%s' of undefined", g)
+	}
+	prop := obj.self.getPropStr(string(g))
+	if prop1, ok := prop.(*valueProperty); ok {
+		vm.stack[vm.sp-1] = prop1.get(v)
+	} else {
+		if prop == nil {
+			prop = _undefined
+		}
+		vm.stack[vm.sp-1] = prop
+	}
+
+	vm.pc++
+}
+
+type getPropCallee string
+
+func (g getPropCallee) exec(vm *vm) {
+	v := vm.stack[vm.sp-1]
+	obj := v.baseObject(vm.r)
+	if obj == nil {
+		vm.r.typeErrorResult(true, "Cannot read property '%s' of undefined", g)
+	}
+	prop := obj.self.getPropStr(string(g))
+	if prop1, ok := prop.(*valueProperty); ok {
+		vm.stack[vm.sp-1] = prop1.get(v)
+	} else {
+		if prop == nil {
+			prop = memberUnresolved{valueUnresolved{r: vm.r, ref: string(g)}}
+		}
+		vm.stack[vm.sp-1] = prop
+	}
+
+	vm.pc++
+}
+
+type _getElem struct{}
+
+var getElem _getElem
+
+func (_getElem) exec(vm *vm) {
+	v := vm.stack[vm.sp-2]
+	obj := v.baseObject(vm.r)
+	propName := vm.stack[vm.sp-1]
+	if obj == nil {
+		vm.r.typeErrorResult(true, "Cannot read property '%s' of undefined", propName.String())
+	}
+
+	prop := obj.self.getProp(propName)
+	if prop1, ok := prop.(*valueProperty); ok {
+		vm.stack[vm.sp-2] = prop1.get(v)
+	} else {
+		if prop == nil {
+			prop = _undefined
+		}
+		vm.stack[vm.sp-2] = prop
+	}
+
+	vm.sp--
+	vm.pc++
+}
+
+type _getElemCallee struct{}
+
+var getElemCallee _getElemCallee
+
+func (_getElemCallee) exec(vm *vm) {
+	v := vm.stack[vm.sp-2]
+	obj := v.baseObject(vm.r)
+	propName := vm.stack[vm.sp-1]
+	if obj == nil {
+		vm.r.typeErrorResult(true, "Cannot read property '%s' of undefined", propName.String())
+		panic("Unreachable")
+	}
+
+	prop := obj.self.getProp(propName)
+	if prop1, ok := prop.(*valueProperty); ok {
+		vm.stack[vm.sp-2] = prop1.get(v)
+	} else {
+		if prop == nil {
+			prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.String()}}
+		}
+		vm.stack[vm.sp-2] = prop
+	}
+
+	vm.sp--
+	vm.pc++
+}
+
+type _dup struct{}
+
+var dup _dup
+
+func (_dup) exec(vm *vm) {
+	vm.push(vm.stack[vm.sp-1])
+	vm.pc++
+}
+
+type dupN uint32
+
+func (d dupN) exec(vm *vm) {
+	vm.push(vm.stack[vm.sp-1-int(d)])
+	vm.pc++
+}
+
+type rdupN uint32
+
+func (d rdupN) exec(vm *vm) {
+	vm.stack[vm.sp-1-int(d)] = vm.stack[vm.sp-1]
+	vm.pc++
+}
+
+type _newObject struct{}
+
+var newObject _newObject
+
+func (_newObject) exec(vm *vm) {
+	vm.push(vm.r.NewObject())
+	vm.pc++
+}
+
+type newArray uint32
+
+func (l newArray) exec(vm *vm) {
+	values := make([]Value, l)
+	if l > 0 {
+		copy(values, vm.stack[vm.sp-int(l):vm.sp])
+	}
+	obj := vm.r.newArrayValues(values)
+	if l > 0 {
+		vm.sp -= int(l) - 1
+		vm.stack[vm.sp-1] = obj
+	} else {
+		vm.push(obj)
+	}
+	vm.pc++
+}
+
+type newRegexp struct {
+	pattern regexpPattern
+	src     valueString
+
+	global, ignoreCase, multiline bool
+}
+
+func (n *newRegexp) exec(vm *vm) {
+	vm.push(vm.r.newRegExpp(n.pattern, n.src, n.global, n.ignoreCase, n.multiline, vm.r.global.RegExpPrototype))
+	vm.pc++
+}
+
+func (vm *vm) setLocal(s int) {
+	v := vm.stack[vm.sp-1]
+	level := s >> 24
+	idx := uint32(s & 0x00FFFFFF)
+	stash := vm.stash
+	for i := 0; i < level; i++ {
+		stash = stash.outer
+	}
+	stash.putByIdx(idx, v)
+	vm.pc++
+}
+
+type setLocal uint32
+
+func (s setLocal) exec(vm *vm) {
+	vm.setLocal(int(s))
+}
+
+type setLocalP uint32
+
+func (s setLocalP) exec(vm *vm) {
+	vm.setLocal(int(s))
+	vm.sp--
+}
+
+type setVar struct {
+	name string
+	idx  uint32
+}
+
+func (s setVar) exec(vm *vm) {
+	v := vm.peek()
+
+	level := int(s.idx >> 24)
+	idx := uint32(s.idx & 0x00FFFFFF)
+	stash := vm.stash
+	name := s.name
+	for i := 0; i < level; i++ {
+		if stash.put(name, v) {
+			goto end
+		}
+		stash = stash.outer
+	}
+
+	if stash != nil {
+		stash.putByIdx(idx, v)
+	} else {
+		vm.r.globalObject.self.putStr(name, v, false)
+	}
+
+end:
+	vm.pc++
+}
+
+type resolveVar1 string
+
+func (s resolveVar1) exec(vm *vm) {
+	name := string(s)
+	var ref ref
+	for stash := vm.stash; stash != nil; stash = stash.outer {
+		if stash.obj != nil {
+			if stash.obj.hasPropertyStr(name) {
+				ref = &objRef{
+					base: stash.obj,
+					name: name,
+				}
+				goto end
+			}
+		} else {
+			if idx, exists := stash.names[name]; exists {
+				ref = &stashRef{
+					v: &stash.values[idx],
+				}
+				goto end
+			}
+		}
+	}
+
+	ref = &objRef{
+		base: vm.r.globalObject.self,
+		name: name,
+	}
+
+end:
+	vm.refStack = append(vm.refStack, ref)
+	vm.pc++
+}
+
+type deleteVar string
+
+func (d deleteVar) exec(vm *vm) {
+	name := string(d)
+	ret := true
+	for stash := vm.stash; stash != nil; stash = stash.outer {
+		if stash.obj != nil {
+			if stash.obj.hasPropertyStr(name) {
+				ret = stash.obj.deleteStr(name, false)
+				goto end
+			}
+		} else {
+			if _, exists := stash.names[name]; exists {
+				ret = false
+				goto end
+			}
+		}
+	}
+
+	if vm.r.globalObject.self.hasPropertyStr(name) {
+		ret = vm.r.globalObject.self.deleteStr(name, false)
+	}
+
+end:
+	if ret {
+		vm.push(valueTrue)
+	} else {
+		vm.push(valueFalse)
+	}
+	vm.pc++
+}
+
+type deleteGlobal string
+
+func (d deleteGlobal) exec(vm *vm) {
+	name := string(d)
+	var ret bool
+	if vm.r.globalObject.self.hasPropertyStr(name) {
+		ret = vm.r.globalObject.self.deleteStr(name, false)
+	} else {
+		ret = true
+	}
+	if ret {
+		vm.push(valueTrue)
+	} else {
+		vm.push(valueFalse)
+	}
+	vm.pc++
+}
+
+type resolveVar1Strict string
+
+func (s resolveVar1Strict) exec(vm *vm) {
+	name := string(s)
+	var ref ref
+	for stash := vm.stash; stash != nil; stash = stash.outer {
+		if stash.obj != nil {
+			if stash.obj.hasPropertyStr(name) {
+				ref = &objRef{
+					base:   stash.obj,
+					name:   name,
+					strict: true,
+				}
+				goto end
+			}
+		} else {
+			if idx, exists := stash.names[name]; exists {
+				ref = &stashRef{
+					v: &stash.values[idx],
+				}
+				goto end
+			}
+		}
+	}
+
+	if vm.r.globalObject.self.hasPropertyStr(name) {
+		ref = &objRef{
+			base:   vm.r.globalObject.self,
+			name:   name,
+			strict: true,
+		}
+		goto end
+	}
+
+	ref = &unresolvedRef{
+		runtime: vm.r,
+		name:    string(s),
+	}
+
+end:
+	vm.refStack = append(vm.refStack, ref)
+	vm.pc++
+}
+
+type setGlobal string
+
+func (s setGlobal) exec(vm *vm) {
+	v := vm.peek()
+
+	vm.r.globalObject.self.putStr(string(s), v, false)
+	vm.pc++
+}
+
+type setVarStrict struct {
+	name string
+	idx  uint32
+}
+
+func (s setVarStrict) exec(vm *vm) {
+	v := vm.peek()
+
+	level := int(s.idx >> 24)
+	idx := uint32(s.idx & 0x00FFFFFF)
+	stash := vm.stash
+	name := s.name
+	for i := 0; i < level; i++ {
+		if stash.put(name, v) {
+			goto end
+		}
+		stash = stash.outer
+	}
+
+	if stash != nil {
+		stash.putByIdx(idx, v)
+	} else {
+		o := vm.r.globalObject.self
+		if o.hasOwnPropertyStr(name) {
+			o.putStr(name, v, true)
+		} else {
+			vm.r.throwReferenceError(name)
+		}
+	}
+
+end:
+	vm.pc++
+}
+
+type setVar1Strict string
+
+func (s setVar1Strict) exec(vm *vm) {
+	v := vm.peek()
+	var o objectImpl
+
+	name := string(s)
+	for stash := vm.stash; stash != nil; stash = stash.outer {
+		if stash.put(name, v) {
+			goto end
+		}
+	}
+	o = vm.r.globalObject.self
+	if o.hasOwnPropertyStr(name) {
+		o.putStr(name, v, true)
+	} else {
+		vm.r.throwReferenceError(name)
+	}
+end:
+	vm.pc++
+}
+
+type setGlobalStrict string
+
+func (s setGlobalStrict) exec(vm *vm) {
+	v := vm.peek()
+
+	name := string(s)
+	o := vm.r.globalObject.self
+	if o.hasOwnPropertyStr(name) {
+		o.putStr(name, v, true)
+	} else {
+		vm.r.throwReferenceError(name)
+	}
+	vm.pc++
+}
+
+type getLocal uint32
+
+func (g getLocal) exec(vm *vm) {
+	level := int(g >> 24)
+	idx := uint32(g & 0x00FFFFFF)
+	stash := vm.stash
+	for i := 0; i < level; i++ {
+		stash = stash.outer
+	}
+
+	vm.push(stash.getByIdx(idx))
+	vm.pc++
+}
+
+type getVar struct {
+	name string
+	idx  uint32
+	ref  bool
+}
+
+func (g getVar) exec(vm *vm) {
+	level := int(g.idx >> 24)
+	idx := uint32(g.idx & 0x00FFFFFF)
+	stash := vm.stash
+	name := g.name
+	for i := 0; i < level; i++ {
+		if v, found := stash.getByName(name, vm); found {
+			vm.push(v)
+			goto end
+		}
+		stash = stash.outer
+	}
+	if stash != nil {
+		vm.push(stash.getByIdx(idx))
+	} else {
+		v := vm.r.globalObject.self.getStr(name)
+		if v == nil {
+			if g.ref {
+				v = valueUnresolved{r: vm.r, ref: name}
+			} else {
+				vm.r.throwReferenceError(name)
+			}
+		}
+		vm.push(v)
+	}
+end:
+	vm.pc++
+}
+
+type resolveVar struct {
+	name   string
+	idx    uint32
+	strict bool
+}
+
+func (r resolveVar) exec(vm *vm) {
+	level := int(r.idx >> 24)
+	idx := uint32(r.idx & 0x00FFFFFF)
+	stash := vm.stash
+	var ref ref
+	for i := 0; i < level; i++ {
+		if stash.obj != nil {
+			if stash.obj.hasPropertyStr(r.name) {
+				ref = &objRef{
+					base:   stash.obj,
+					name:   r.name,
+					strict: r.strict,
+				}
+				goto end
+			}
+		} else {
+			if idx, exists := stash.names[r.name]; exists {
+				ref = &stashRef{
+					v: &stash.values[idx],
+				}
+				goto end
+			}
+		}
+		stash = stash.outer
+	}
+
+	if stash != nil {
+		ref = &stashRef{
+			v: &stash.values[idx],
+		}
+		goto end
+	} /*else {
+		if vm.r.globalObject.self.hasProperty(nameVal) {
+			ref = &objRef{
+				base: vm.r.globalObject.self,
+				name: r.name,
+			}
+			goto end
+		}
+	} */
+
+	ref = &unresolvedRef{
+		runtime: vm.r,
+		name:    r.name,
+	}
+
+end:
+	vm.refStack = append(vm.refStack, ref)
+	vm.pc++
+}
+
+type _getValue struct{}
+
+var getValue _getValue
+
+func (_getValue) exec(vm *vm) {
+	ref := vm.refStack[len(vm.refStack)-1]
+	if v := ref.get(); v != nil {
+		vm.push(v)
+	} else {
+		vm.r.throwReferenceError(ref.refname())
+		panic("Unreachable")
+	}
+	vm.pc++
+}
+
+type _putValue struct{}
+
+var putValue _putValue
+
+func (_putValue) exec(vm *vm) {
+	l := len(vm.refStack) - 1
+	ref := vm.refStack[l]
+	vm.refStack[l] = nil
+	vm.refStack = vm.refStack[:l]
+	ref.set(vm.stack[vm.sp-1])
+	vm.pc++
+}
+
+type getVar1 string
+
+func (n getVar1) exec(vm *vm) {
+	name := string(n)
+	var val Value
+	for stash := vm.stash; stash != nil; stash = stash.outer {
+		if v, exists := stash.getByName(name, vm); exists {
+			val = v
+			break
+		}
+	}
+	if val == nil {
+		val = vm.r.globalObject.self.getStr(name)
+		if val == nil {
+			vm.r.throwReferenceError(name)
+		}
+	}
+	vm.push(val)
+	vm.pc++
+}
+
+type getVar1Callee string
+
+func (n getVar1Callee) exec(vm *vm) {
+	name := string(n)
+	var val Value
+	for stash := vm.stash; stash != nil; stash = stash.outer {
+		if v, exists := stash.getByName(name, vm); exists {
+			val = v
+			break
+		}
+	}
+	if val == nil {
+		val = vm.r.globalObject.self.getStr(name)
+		if val == nil {
+			val = valueUnresolved{r: vm.r, ref: name}
+		}
+	}
+	vm.push(val)
+	vm.pc++
+}
+
+type _pop struct{}
+
+var pop _pop
+
+func (_pop) exec(vm *vm) {
+	vm.sp--
+	vm.pc++
+}
+
+type _swap struct{}
+
+var swap _swap
+
+func (_swap) exec(vm *vm) {
+	vm.stack[vm.sp-1], vm.stack[vm.sp-2] = vm.stack[vm.sp-2], vm.stack[vm.sp-1]
+	vm.pc++
+}
+
+func (vm *vm) callEval(n int, strict bool) {
+	if vm.r.toObject(vm.stack[vm.sp-n-1]) == vm.r.global.Eval {
+		if n > 0 {
+			srcVal := vm.stack[vm.sp-n]
+			if src, ok := srcVal.assertString(); ok {
+				var this Value
+				if vm.sb != 0 {
+					this = vm.stack[vm.sb]
+				} else {
+					this = vm.r.globalObject
+				}
+				ret := vm.r.eval(src.String(), true, strict, this)
+				vm.stack[vm.sp-n-2] = ret
+			} else {
+				vm.stack[vm.sp-n-2] = srcVal
+			}
+		} else {
+			vm.stack[vm.sp-n-2] = _undefined
+		}
+
+		vm.sp -= n + 1
+		vm.pc++
+	} else {
+		call(n).exec(vm)
+	}
+}
+
+type callEval uint32
+
+func (numargs callEval) exec(vm *vm) {
+	vm.callEval(int(numargs), false)
+}
+
+type callEvalStrict uint32
+
+func (numargs callEvalStrict) exec(vm *vm) {
+	vm.callEval(int(numargs), true)
+}
+
+type _boxThis struct{}
+
+var boxThis _boxThis
+
+func (_boxThis) exec(vm *vm) {
+	v := vm.stack[vm.sb]
+	if v == _undefined || v == _null {
+		vm.stack[vm.sb] = vm.r.globalObject
+	} else {
+		vm.stack[vm.sb] = v.ToObject(vm.r)
+	}
+	vm.pc++
+}
+
+type call uint32
+
+func (numargs call) exec(vm *vm) {
+	// this
+	// callee
+	// arg0
+	// ...
+	// arg<numargs-1>
+	n := int(numargs)
+	v := vm.stack[vm.sp-n-1] // callee
+	obj := vm.r.toCallee(v)
+repeat:
+	switch f := obj.self.(type) {
+	case *funcObject:
+		vm.pc++
+		vm.pushCtx()
+		vm.args = n
+		vm.prg = f.prg
+		vm.stash = f.stash
+		vm.pc = 0
+		vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = vm.stack[vm.sp-n-2], vm.stack[vm.sp-n-1]
+		return
+	case *nativeFuncObject:
+		vm._nativeCall(f, n)
+	case *boundFuncObject:
+		vm._nativeCall(&f.nativeFuncObject, n)
+	case *lazyObject:
+		obj.self = f.create(obj)
+		goto repeat
+	default:
+		vm.r.typeErrorResult(true, "Not a function: %s", obj.ToString())
+	}
+}
+
+func (vm *vm) _nativeCall(f *nativeFuncObject, n int) {
+	if f.f != nil {
+		vm.pushCtx()
+		vm.prg = nil
+		vm.funcName = f.nameProp.get(nil).String()
+		ret := f.f(FunctionCall{
+			Arguments: vm.stack[vm.sp-n : vm.sp],
+			This:      vm.stack[vm.sp-n-2],
+		})
+		if ret == nil {
+			ret = _undefined
+		}
+		vm.stack[vm.sp-n-2] = ret
+		vm.popCtx()
+	} else {
+		vm.stack[vm.sp-n-2] = _undefined
+	}
+	vm.sp -= n + 1
+	vm.pc++
+}
+
+type enterFunc uint32
+
+func (e enterFunc) exec(vm *vm) {
+	// Input stack:
+	//
+	// callee
+	// this
+	// arg0
+	// ...
+	// argN
+	// <- sp
+
+	// Output stack:
+	//
+	// this <- sb
+	// <- sp
+
+	vm.newStash()
+	offset := vm.args - int(e)
+	vm.stash.values = make([]Value, e)
+	if offset > 0 {
+		copy(vm.stash.values, vm.stack[vm.sp-vm.args:])
+		vm.stash.extraArgs = make([]Value, offset)
+		copy(vm.stash.extraArgs, vm.stack[vm.sp-offset:])
+	} else {
+		copy(vm.stash.values, vm.stack[vm.sp-vm.args:])
+		vv := vm.stash.values[vm.args:]
+		for i, _ := range vv {
+			vv[i] = _undefined
+		}
+	}
+	vm.sp -= vm.args
+	vm.sb = vm.sp - 1
+	vm.pc++
+}
+
+type _ret struct{}
+
+var ret _ret
+
+func (_ret) exec(vm *vm) {
+	// callee -3
+	// this -2
+	// retval -1
+
+	vm.stack[vm.sp-3] = vm.stack[vm.sp-1]
+	vm.sp -= 2
+	vm.popCtx()
+	if vm.pc < 0 {
+		vm.halt = true
+	}
+}
+
+type enterFuncStashless struct {
+	stackSize uint32
+	args      uint32
+}
+
+func (e enterFuncStashless) exec(vm *vm) {
+	vm.sb = vm.sp - vm.args - 1
+	var ss int
+	d := int(e.args) - vm.args
+	if d > 0 {
+		ss = int(e.stackSize) + d
+		vm.args = int(e.args)
+	} else {
+		ss = int(e.stackSize)
+	}
+	sp := vm.sp
+	if ss > 0 {
+		vm.sp += int(ss)
+		vm.stack.expand(vm.sp)
+		s := vm.stack[sp:vm.sp]
+		for i, _ := range s {
+			s[i] = _undefined
+		}
+	}
+	vm.pc++
+}
+
+type _retStashless struct{}
+
+var retStashless _retStashless
+
+func (_retStashless) exec(vm *vm) {
+	retval := vm.stack[vm.sp-1]
+	vm.sp = vm.sb
+	vm.stack[vm.sp-1] = retval
+	vm.popCtx()
+	if vm.pc < 0 {
+		vm.halt = true
+	}
+}
+
+type newFunc struct {
+	prg    *Program
+	name   string
+	length uint32
+	strict bool
+
+	srcStart, srcEnd uint32
+}
+
+func (n *newFunc) exec(vm *vm) {
+	obj := vm.r.newFunc(n.name, int(n.length), n.strict)
+	obj.prg = n.prg
+	obj.stash = vm.stash
+	obj.src = n.prg.src.src[n.srcStart:n.srcEnd]
+	vm.push(obj.val)
+	vm.pc++
+}
+
+type bindName string
+
+func (d bindName) exec(vm *vm) {
+	if vm.stash != nil {
+		vm.stash.createBinding(string(d))
+	} else {
+		vm.r.globalObject.self._putProp(string(d), _undefined, true, true, false)
+	}
+	vm.pc++
+}
+
+type jne int32
+
+func (j jne) exec(vm *vm) {
+	vm.sp--
+	if !vm.stack[vm.sp].ToBoolean() {
+		vm.pc += int(j)
+	} else {
+		vm.pc++
+	}
+}
+
+type jeq int32
+
+func (j jeq) exec(vm *vm) {
+	vm.sp--
+	if vm.stack[vm.sp].ToBoolean() {
+		vm.pc += int(j)
+	} else {
+		vm.pc++
+	}
+}
+
+type jeq1 int32
+
+func (j jeq1) exec(vm *vm) {
+	if vm.stack[vm.sp-1].ToBoolean() {
+		vm.pc += int(j)
+	} else {
+		vm.pc++
+	}
+}
+
+type jneq1 int32
+
+func (j jneq1) exec(vm *vm) {
+	if !vm.stack[vm.sp-1].ToBoolean() {
+		vm.pc += int(j)
+	} else {
+		vm.pc++
+	}
+}
+
+type _not struct{}
+
+var not _not
+
+func (_not) exec(vm *vm) {
+	if vm.stack[vm.sp-1].ToBoolean() {
+		vm.stack[vm.sp-1] = valueFalse
+	} else {
+		vm.stack[vm.sp-1] = valueTrue
+	}
+	vm.pc++
+}
+
+func toPrimitiveNumber(v Value) Value {
+	if o, ok := v.(*Object); ok {
+		return o.self.toPrimitiveNumber()
+	}
+	return v
+}
+
+func cmp(px, py Value) Value {
+	var ret bool
+	var nx, ny float64
+
+	if xs, ok := px.assertString(); ok {
+		if ys, ok := py.assertString(); ok {
+			ret = xs.compareTo(ys) < 0
+			goto end
+		}
+	}
+
+	if xi, ok := px.assertInt(); ok {
+		if yi, ok := py.assertInt(); ok {
+			ret = xi < yi
+			goto end
+		}
+	}
+
+	nx = px.ToFloat()
+	ny = py.ToFloat()
+
+	if math.IsNaN(nx) || math.IsNaN(ny) {
+		return _undefined
+	}
+
+	ret = nx < ny
+
+end:
+	if ret {
+		return valueTrue
+	}
+	return valueFalse
+
+}
+
+type _op_lt struct{}
+
+var op_lt _op_lt
+
+func (_op_lt) exec(vm *vm) {
+	left := toPrimitiveNumber(vm.stack[vm.sp-2])
+	right := toPrimitiveNumber(vm.stack[vm.sp-1])
+
+	r := cmp(left, right)
+	if r == _undefined {
+		vm.stack[vm.sp-2] = valueFalse
+	} else {
+		vm.stack[vm.sp-2] = r
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _op_lte struct{}
+
+var op_lte _op_lte
+
+func (_op_lte) exec(vm *vm) {
+	left := toPrimitiveNumber(vm.stack[vm.sp-2])
+	right := toPrimitiveNumber(vm.stack[vm.sp-1])
+
+	r := cmp(right, left)
+	if r == _undefined || r == valueTrue {
+		vm.stack[vm.sp-2] = valueFalse
+	} else {
+		vm.stack[vm.sp-2] = valueTrue
+	}
+
+	vm.sp--
+	vm.pc++
+}
+
+type _op_gt struct{}
+
+var op_gt _op_gt
+
+func (_op_gt) exec(vm *vm) {
+	left := toPrimitiveNumber(vm.stack[vm.sp-2])
+	right := toPrimitiveNumber(vm.stack[vm.sp-1])
+
+	r := cmp(right, left)
+	if r == _undefined {
+		vm.stack[vm.sp-2] = valueFalse
+	} else {
+		vm.stack[vm.sp-2] = r
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _op_gte struct{}
+
+var op_gte _op_gte
+
+func (_op_gte) exec(vm *vm) {
+	left := toPrimitiveNumber(vm.stack[vm.sp-2])
+	right := toPrimitiveNumber(vm.stack[vm.sp-1])
+
+	r := cmp(left, right)
+	if r == _undefined || r == valueTrue {
+		vm.stack[vm.sp-2] = valueFalse
+	} else {
+		vm.stack[vm.sp-2] = valueTrue
+	}
+
+	vm.sp--
+	vm.pc++
+}
+
+type _op_eq struct{}
+
+var op_eq _op_eq
+
+func (_op_eq) exec(vm *vm) {
+	if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) {
+		vm.stack[vm.sp-2] = valueTrue
+	} else {
+		vm.stack[vm.sp-2] = valueFalse
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _op_neq struct{}
+
+var op_neq _op_neq
+
+func (_op_neq) exec(vm *vm) {
+	if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) {
+		vm.stack[vm.sp-2] = valueFalse
+	} else {
+		vm.stack[vm.sp-2] = valueTrue
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _op_strict_eq struct{}
+
+var op_strict_eq _op_strict_eq
+
+func (_op_strict_eq) exec(vm *vm) {
+	if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) {
+		vm.stack[vm.sp-2] = valueTrue
+	} else {
+		vm.stack[vm.sp-2] = valueFalse
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _op_strict_neq struct{}
+
+var op_strict_neq _op_strict_neq
+
+func (_op_strict_neq) exec(vm *vm) {
+	if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) {
+		vm.stack[vm.sp-2] = valueFalse
+	} else {
+		vm.stack[vm.sp-2] = valueTrue
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type _op_instanceof struct{}
+
+var op_instanceof _op_instanceof
+
+func (_op_instanceof) exec(vm *vm) {
+	left := vm.stack[vm.sp-2]
+	right := vm.r.toObject(vm.stack[vm.sp-1])
+
+	if right.self.hasInstance(left) {
+		vm.stack[vm.sp-2] = valueTrue
+	} else {
+		vm.stack[vm.sp-2] = valueFalse
+	}
+
+	vm.sp--
+	vm.pc++
+}
+
+type _op_in struct{}
+
+var op_in _op_in
+
+func (_op_in) exec(vm *vm) {
+	left := vm.stack[vm.sp-2]
+	right := vm.r.toObject(vm.stack[vm.sp-1])
+
+	if right.self.hasProperty(left) {
+		vm.stack[vm.sp-2] = valueTrue
+	} else {
+		vm.stack[vm.sp-2] = valueFalse
+	}
+
+	vm.sp--
+	vm.pc++
+}
+
+type try struct {
+	catchOffset   int32
+	finallyOffset int32
+	dynamic       bool
+}
+
+func (t try) exec(vm *vm) {
+	o := vm.pc
+	vm.pc++
+	ex := vm.runTry()
+	if ex != nil && t.catchOffset > 0 {
+		// run the catch block (in try)
+		vm.pc = o + int(t.catchOffset)
+		// TODO: if ex.val is an Error, set the stack property
+		if t.dynamic {
+			vm.newStash()
+			vm.stash.putByIdx(0, ex.val)
+		} else {
+			vm.push(ex.val)
+		}
+		ex = vm.runTry()
+		if t.dynamic {
+			vm.stash = vm.stash.outer
+		}
+	}
+
+	if t.finallyOffset > 0 {
+		pc := vm.pc
+		// Run finally
+		vm.pc = o + int(t.finallyOffset)
+		vm.run()
+		if vm.prg.code[vm.pc] == retFinally {
+			vm.pc = pc
+		} else {
+			// break or continue out of finally, dropping exception
+			ex = nil
+		}
+	}
+
+	vm.halt = false
+
+	if ex != nil {
+		panic(ex)
+	}
+}
+
+type _retFinally struct{}
+
+var retFinally _retFinally
+
+func (_retFinally) exec(vm *vm) {
+	vm.pc++
+}
+
+type enterCatch string
+
+func (varName enterCatch) exec(vm *vm) {
+	vm.stash.names = map[string]uint32{
+		string(varName): 0,
+	}
+	vm.pc++
+}
+
+type _throw struct{}
+
+var throw _throw
+
+func (_throw) exec(vm *vm) {
+	panic(vm.stack[vm.sp-1])
+}
+
+type _new uint32
+
+func (n _new) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-1-int(n)])
+repeat:
+	switch f := obj.self.(type) {
+	case *funcObject:
+		args := make([]Value, n)
+		copy(args, vm.stack[vm.sp-int(n):])
+		vm.sp -= int(n)
+		vm.stack[vm.sp-1] = f.construct(args)
+	case *nativeFuncObject:
+		vm._nativeNew(f, int(n))
+	case *boundFuncObject:
+		vm._nativeNew(&f.nativeFuncObject, int(n))
+	case *lazyObject:
+		obj.self = f.create(obj)
+		goto repeat
+	default:
+		vm.r.typeErrorResult(true, "Not a constructor")
+	}
+
+	vm.pc++
+}
+
+func (vm *vm) _nativeNew(f *nativeFuncObject, n int) {
+	if f.construct != nil {
+		args := make([]Value, n)
+		copy(args, vm.stack[vm.sp-n:])
+		vm.sp -= n
+		vm.stack[vm.sp-1] = f.construct(args)
+	} else {
+		vm.r.typeErrorResult(true, "Not a constructor")
+	}
+}
+
+type _typeof struct{}
+
+var typeof _typeof
+
+func (_typeof) exec(vm *vm) {
+	var r Value
+	switch v := vm.stack[vm.sp-1].(type) {
+	case valueUndefined, valueUnresolved:
+		r = stringUndefined
+	case valueNull:
+		r = stringObjectC
+	case *Object:
+	repeat:
+		switch s := v.self.(type) {
+		case *funcObject, *nativeFuncObject, *boundFuncObject:
+			r = stringFunction
+		case *lazyObject:
+			v.self = s.create(v)
+			goto repeat
+		default:
+			r = stringObjectC
+		}
+	case valueBool:
+		r = stringBoolean
+	case valueString:
+		r = stringString
+	case valueInt, valueFloat:
+		r = stringNumber
+	default:
+		panic(fmt.Errorf("Unknown type: %T", v))
+	}
+	vm.stack[vm.sp-1] = r
+	vm.pc++
+}
+
+type createArgs uint32
+
+func (formalArgs createArgs) exec(vm *vm) {
+	v := &Object{runtime: vm.r}
+	args := &argumentsObject{}
+	args.extensible = true
+	args.prototype = vm.r.global.ObjectPrototype
+	args.class = "Arguments"
+	v.self = args
+	args.val = v
+	args.length = vm.args
+	args.init()
+	i := 0
+	c := int(formalArgs)
+	if vm.args < c {
+		c = vm.args
+	}
+	for ; i < c; i++ {
+		args._put(strconv.Itoa(i), &mappedProperty{
+			valueProperty: valueProperty{
+				writable:     true,
+				configurable: true,
+				enumerable:   true,
+			},
+			v: &vm.stash.values[i],
+		})
+	}
+
+	for _, v := range vm.stash.extraArgs {
+		args._put(strconv.Itoa(i), v)
+		i++
+	}
+
+	args._putProp("callee", vm.stack[vm.sb-1], true, false, true)
+	vm.push(v)
+	vm.pc++
+}
+
+type createArgsStrict uint32
+
+func (formalArgs createArgsStrict) exec(vm *vm) {
+	args := vm.r.newBaseObject(vm.r.global.ObjectPrototype, "Arguments")
+	i := 0
+	c := int(formalArgs)
+	if vm.args < c {
+		c = vm.args
+	}
+	for _, v := range vm.stash.values[:c] {
+		args._put(strconv.Itoa(i), v)
+		i++
+	}
+
+	for _, v := range vm.stash.extraArgs {
+		args._put(strconv.Itoa(i), v)
+		i++
+	}
+
+	args._putProp("length", intToValue(int64(vm.args)), true, false, true)
+	args._put("callee", vm.r.global.throwerProperty)
+	args._put("caller", vm.r.global.throwerProperty)
+	vm.push(args.val)
+	vm.pc++
+}
+
+type _enterWith struct{}
+
+var enterWith _enterWith
+
+func (_enterWith) exec(vm *vm) {
+	vm.newStash()
+	vm.stash.obj = vm.stack[vm.sp-1].ToObject(vm.r).self
+	vm.sp--
+	vm.pc++
+}
+
+type _leaveWith struct{}
+
+var leaveWith _leaveWith
+
+func (_leaveWith) exec(vm *vm) {
+	vm.stash = vm.stash.outer
+	vm.pc++
+}
+
+func emptyIter() (propIterItem, iterNextFunc) {
+	return propIterItem{}, nil
+}
+
+type _enumerate struct{}
+
+var enumerate _enumerate
+
+func (_enumerate) exec(vm *vm) {
+	v := vm.stack[vm.sp-1]
+	if v == _undefined || v == _null {
+		vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter})
+	} else {
+		vm.iterStack = append(vm.iterStack, iterStackItem{f: v.ToObject(vm.r).self.enumerate(false, true)})
+	}
+	vm.sp--
+	vm.pc++
+}
+
+type enumNext int32
+
+func (jmp enumNext) exec(vm *vm) {
+	l := len(vm.iterStack) - 1
+	item, n := vm.iterStack[l].f()
+	if n != nil {
+		vm.iterStack[l].val = newStringValue(item.name)
+		vm.iterStack[l].f = n
+		vm.pc++
+	} else {
+		vm.pc += int(jmp)
+	}
+}
+
+type _enumGet struct{}
+
+var enumGet _enumGet
+
+func (_enumGet) exec(vm *vm) {
+	l := len(vm.iterStack) - 1
+	vm.push(vm.iterStack[l].val)
+	vm.pc++
+}
+
+type _enumPop struct{}
+
+var enumPop _enumPop
+
+func (_enumPop) exec(vm *vm) {
+	l := len(vm.iterStack) - 1
+	vm.iterStack[l] = iterStackItem{}
+	vm.iterStack = vm.iterStack[:l]
+	vm.pc++
+}

+ 359 - 0
vm_test.go

@@ -0,0 +1,359 @@
+package goja
+
+import (
+	"github.com/dop251/goja/parser"
+	"testing"
+)
+
+func TestVM1(t *testing.T) {
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+
+	vm.prg = &Program{
+		values: []Value{valueInt(2), valueInt(3), asciiString("test")},
+		code: []instruction{
+			bindName("v"),
+			newObject,
+			setGlobal("v"),
+			loadVal(2),
+			loadVal(1),
+			loadVal(0),
+			add,
+			setElem,
+			pop,
+			getVar1("v"),
+			halt,
+		},
+	}
+
+	vm.run()
+
+	rv := vm.pop()
+
+	if obj, ok := rv.(*Object); ok {
+		if v := obj.self.getStr("test").ToInteger(); v != 5 {
+			t.Fatalf("Unexpected property value: %v", v)
+		}
+	} else {
+		t.Fatalf("Unexpected result: %v", rv)
+	}
+
+}
+
+var jumptable = []func(*vm, *instr){
+	f_jump,
+	f_halt,
+}
+
+func f_jump(vm *vm, i *instr) {
+	vm.pc += i.prim
+}
+
+func f_halt(vm *vm, i *instr) {
+	vm.halt = true
+}
+
+func f_loadVal(vm *vm, i *instr) {
+	vm.push(vm.prg.values[i.prim])
+	vm.pc++
+}
+
+func f_add(vm *vm) {
+	right := vm.stack[vm.sp-1]
+	left := vm.stack[vm.sp-2]
+
+	if o, ok := left.(*Object); ok {
+		left = o.self.toPrimitive()
+	}
+
+	if o, ok := right.(*Object); ok {
+		right = o.self.toPrimitive()
+	}
+
+	var ret Value
+
+	leftString, isLeftString := left.assertString()
+	rightString, isRightString := right.assertString()
+
+	if isLeftString || isRightString {
+		if !isLeftString {
+			leftString = left.ToString()
+		}
+		if !isRightString {
+			rightString = right.ToString()
+		}
+		ret = leftString.concat(rightString)
+	} else {
+		if leftInt, ok := left.assertInt(); ok {
+			if rightInt, ok := right.assertInt(); ok {
+				ret = intToValue(int64(leftInt) + int64(rightInt))
+			} else {
+				ret = floatToValue(float64(leftInt) + right.ToFloat())
+			}
+		} else {
+			ret = floatToValue(left.ToFloat() + right.ToFloat())
+		}
+	}
+
+	vm.stack[vm.sp-2] = ret
+	vm.sp--
+	vm.pc++
+}
+
+type instr struct {
+	code int
+	prim int
+	arg  interface{}
+}
+
+type jumparg struct {
+	offset int
+	other  string
+}
+
+func BenchmarkVmNOP2(b *testing.B) {
+	prg := []func(*vm){
+		//loadVal(0).exec,
+		//loadVal(1).exec,
+		//add.exec,
+		jump(1).exec,
+		halt.exec,
+	}
+
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+	vm.prg = &Program{
+		values: []Value{intToValue(2), intToValue(3)},
+	}
+
+	for i := 0; i < b.N; i++ {
+		vm.halt = false
+		vm.pc = 0
+		for !vm.halt {
+			prg[vm.pc](vm)
+		}
+		//vm.sp--
+		/*r := vm.pop()
+		if r.ToInteger() != 5 {
+			b.Fatalf("Unexpected result: %+v", r)
+		}
+		if vm.sp != 0 {
+			b.Fatalf("Unexpected sp: %d", vm.sp)
+		}*/
+	}
+}
+
+func BenchmarkVmNOP1(b *testing.B) {
+	prg := []instr{
+		{code: 2, prim: 0},
+		{code: 2, prim: 1},
+		{code: 3},
+		{code: 1},
+	}
+
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+	vm.prg = &Program{
+		values: []Value{intToValue(2), intToValue(3)},
+	}
+	for i := 0; i < b.N; i++ {
+		vm.halt = false
+		vm.pc = 0
+	L:
+		for {
+			instr := &prg[vm.pc]
+			//jumptable[instr.code](vm, instr)
+			switch instr.code {
+			case 10:
+				vm.pc += 1
+			case 11:
+				vm.pc += 2
+			case 12:
+				vm.pc += 3
+			case 13:
+				vm.pc += 4
+			case 14:
+				vm.pc += 5
+			case 15:
+				vm.pc += 6
+			case 16:
+				vm.pc += 7
+			case 17:
+				vm.pc += 8
+			case 18:
+				vm.pc += 9
+			case 19:
+				vm.pc += 10
+			case 20:
+				vm.pc += 11
+			case 21:
+				vm.pc += 12
+			case 22:
+				vm.pc += 13
+			case 23:
+				vm.pc += 14
+			case 24:
+				vm.pc += 15
+			case 25:
+				vm.pc += 16
+			case 0:
+				//vm.pc += instr.prim
+				f_jump(vm, instr)
+			case 1:
+				break L
+			case 2:
+				f_loadVal(vm, instr)
+			case 3:
+				f_add(vm)
+			default:
+				jumptable[instr.code](vm, instr)
+			}
+
+		}
+		r := vm.pop()
+		if r.ToInteger() != 5 {
+			b.Fatalf("Unexpected result: %+v", r)
+		}
+		if vm.sp != 0 {
+			b.Fatalf("Unexpected sp: %d", vm.sp)
+		}
+
+		//vm.sp -= 1
+	}
+}
+
+func BenchmarkVmNOP(b *testing.B) {
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+	vm.prg = &Program{
+		code: []instruction{
+			jump(1),
+			//jump(1),
+			halt,
+		},
+	}
+
+	for i := 0; i < b.N; i++ {
+		vm.pc = 0
+		vm.run()
+	}
+
+}
+
+func BenchmarkVm1(b *testing.B) {
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+
+	//ins1 := loadVal1(0)
+	//ins2 := loadVal1(1)
+
+	vm.prg = &Program{
+		values: []Value{valueInt(2), valueInt(3)},
+		code: []instruction{
+			loadVal(0),
+			loadVal(1),
+			add,
+			halt,
+		},
+	}
+
+	for i := 0; i < b.N; i++ {
+		vm.pc = 0
+		vm.run()
+		r := vm.pop()
+		if r.ToInteger() != 5 {
+			b.Fatalf("Unexpected result: %+v", r)
+		}
+		if vm.sp != 0 {
+			b.Fatalf("Unexpected sp: %d", vm.sp)
+		}
+	}
+}
+
+func BenchmarkFib(b *testing.B) {
+	const TEST_FIB = `
+function fib(n) {
+if (n < 2) return n;
+return fib(n - 2) + fib(n - 1);
+}
+
+fib(35);
+`
+	b.StopTimer()
+	prg, err := parser.ParseFile(nil, "test.js", TEST_FIB, 0)
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	c := newCompiler()
+	c.compile(prg)
+	c.p.dumpCode(b.Logf)
+
+	r := &Runtime{}
+	r.init()
+
+	vm := r.vm
+
+	var expectedResult Value = valueInt(9227465)
+
+	b.StartTimer()
+
+	vm.prg = c.p
+	vm.run()
+	v := vm.pop()
+
+	b.Logf("stack size: %d", len(vm.stack))
+	b.Logf("stashAllocs: %d", vm.stashAllocs)
+
+	if !v.SameAs(expectedResult) {
+		b.Fatalf("Result: %+v, expected: %+v", v, expectedResult)
+	}
+
+}
+
+func BenchmarkEmptyLoop(b *testing.B) {
+	const SCRIPT = `
+	function f() {
+		for (var i = 0; i < 100; i++) {
+		}
+	}
+	f()
+	`
+	b.StopTimer()
+	vm := New()
+	prg, err := Compile("test.js", SCRIPT, false)
+	if err != nil {
+		b.Fatal(err)
+	}
+	// prg.dumpCode(log.Printf)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		vm.RunProgram(prg)
+	}
+}
+
+func BenchmarkVMAdd(b *testing.B) {
+	vm := &vm{}
+	vm.stack = append(vm.stack, nil, nil)
+	vm.sp = len(vm.stack)
+
+	var v1 Value = valueInt(3)
+	var v2 Value = valueInt(5)
+
+	for i := 0; i < b.N; i++ {
+		vm.stack[0] = v1
+		vm.stack[1] = v2
+		add.exec(vm)
+		vm.sp++
+	}
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff