add http
This commit is contained in:
		
							parent
							
								
									b7ae15cad0
								
							
						
					
					
						commit
						e690ae764b
					
				
							
								
								
									
										95
									
								
								ai/ai.go
									
									
									
									
									
								
							
							
						
						
									
										95
									
								
								ai/ai.go
									
									
									
									
									
								
							@ -2,17 +2,22 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"apigo.cc/ai/ai"
 | 
			
		||||
	"apigo.cc/ai/ai/ai/watcher"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/ssgo/u"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	if len(os.Args) > 1 && (os.Args[1] == "-e" || os.Args[1] == "export") {
 | 
			
		||||
		imports, err := ai.ExportForDev()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(err.Error())
 | 
			
		||||
			fmt.Println(u.BRed(err.Error()))
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println(`exported to ./lib
 | 
			
		||||
example:
 | 
			
		||||
@ -28,32 +33,100 @@ function main(...args) {
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	} else {
 | 
			
		||||
		isWatch := false
 | 
			
		||||
		var osArgs []string
 | 
			
		||||
		if len(os.Args) > 1 && (os.Args[1] == "-w" || os.Args[1] == "watch") {
 | 
			
		||||
			isWatch = true
 | 
			
		||||
			osArgs = os.Args[2:]
 | 
			
		||||
		} else {
 | 
			
		||||
			osArgs = os.Args[1:]
 | 
			
		||||
		}
 | 
			
		||||
		jsFile := "ai.js"
 | 
			
		||||
		if len(os.Args) > 1 {
 | 
			
		||||
			jsFile = os.Args[1]
 | 
			
		||||
		if len(osArgs) > 0 {
 | 
			
		||||
			jsFile = osArgs[0]
 | 
			
		||||
			if !strings.HasSuffix(jsFile, ".js") && !u.FileExists(jsFile) {
 | 
			
		||||
				jsFile = jsFile + ".js"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if u.FileExists(jsFile) {
 | 
			
		||||
			args := make([]any, len(os.Args)-2)
 | 
			
		||||
			for i := 2; i < len(os.Args); i++ {
 | 
			
		||||
				args[i-2] = os.Args[i]
 | 
			
		||||
			args := make([]any, len(osArgs)-1)
 | 
			
		||||
			for i := 1; i < len(osArgs); i++ {
 | 
			
		||||
				args[i-1] = osArgs[i]
 | 
			
		||||
			}
 | 
			
		||||
			result, err := ai.RunFile(jsFile, args...)
 | 
			
		||||
			var w *watcher.Watcher
 | 
			
		||||
			run := func() {
 | 
			
		||||
				rt := ai.New()
 | 
			
		||||
				if w != nil {
 | 
			
		||||
					rt.SetModuleLoader(func(filename string) string {
 | 
			
		||||
						filePath := filepath.Dir(filename)
 | 
			
		||||
						needWatch := true
 | 
			
		||||
						for _, v := range w.WatchList() {
 | 
			
		||||
							if v == filePath {
 | 
			
		||||
								needWatch = false
 | 
			
		||||
								break
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						if needWatch {
 | 
			
		||||
							fmt.Println(u.BMagenta("[watching module path]"), filePath)
 | 
			
		||||
							_ = w.Add(filePath)
 | 
			
		||||
						}
 | 
			
		||||
						return u.ReadFileN(filename)
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
				_, err := rt.StartFromFile(jsFile)
 | 
			
		||||
 | 
			
		||||
				result, err := rt.RunMain(args...)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
				fmt.Println(err.Error())
 | 
			
		||||
					fmt.Println(u.BRed(err.Error()))
 | 
			
		||||
				} else if result != nil {
 | 
			
		||||
				fmt.Println(u.JsonP(result))
 | 
			
		||||
					fmt.Println(u.Cyan(u.JsonP(result)))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			_, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J")
 | 
			
		||||
			if isWatch {
 | 
			
		||||
				watchStartPath := filepath.Dir(jsFile)
 | 
			
		||||
				fmt.Println(u.BMagenta("[watching root path]"), watchStartPath)
 | 
			
		||||
				var err error
 | 
			
		||||
				var isWaitingRun = false
 | 
			
		||||
				if w, err = watcher.Start([]string{watchStartPath}, []string{"js", "json", "yml"}, func(filename string, event string) {
 | 
			
		||||
					_, _ = os.Stdout.WriteString("\x1b[3;J\x1b[H\x1b[2J")
 | 
			
		||||
					fmt.Println(u.BYellow("[changed]"), filename)
 | 
			
		||||
					if !isWaitingRun {
 | 
			
		||||
						isWaitingRun = true
 | 
			
		||||
						go func() {
 | 
			
		||||
							time.Sleep(time.Millisecond * 50)
 | 
			
		||||
							isWaitingRun = false
 | 
			
		||||
							run()
 | 
			
		||||
						}()
 | 
			
		||||
					}
 | 
			
		||||
				}); err == nil {
 | 
			
		||||
					exitCh := make(chan os.Signal, 1)
 | 
			
		||||
					closeCh := make(chan bool, 1)
 | 
			
		||||
					signal.Notify(exitCh, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
 | 
			
		||||
					go func() {
 | 
			
		||||
						<-exitCh
 | 
			
		||||
						closeCh <- true
 | 
			
		||||
					}()
 | 
			
		||||
					run()
 | 
			
		||||
					<-closeCh
 | 
			
		||||
					w.Stop()
 | 
			
		||||
				} else {
 | 
			
		||||
					fmt.Println(u.BRed(err.Error()))
 | 
			
		||||
					run()
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				run()
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println(`Usage:
 | 
			
		||||
ai -h | --help		show usage
 | 
			
		||||
ai -e | --export	export ai.ts file for develop
 | 
			
		||||
ai -h | help		show usage
 | 
			
		||||
ai -e | export	export ai.ts file for develop
 | 
			
		||||
ai test | test.js	run test.js, if not specified, run ai.js
 | 
			
		||||
ai -w | watch test	run test.js, if .js files changed will be reloaded
 | 
			
		||||
`)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										183
									
								
								ai/watcher/watcher.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								ai/watcher/watcher.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
			
		||||
package watcher
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/fsnotify/fsnotify"
 | 
			
		||||
	"github.com/ssgo/u"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Create = "create"
 | 
			
		||||
	Change = "change"
 | 
			
		||||
	Remove = "remove"
 | 
			
		||||
	Rename = "rename"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Watcher struct {
 | 
			
		||||
	watcher   *fsnotify.Watcher
 | 
			
		||||
	isRunning bool
 | 
			
		||||
	fileTypes []string
 | 
			
		||||
	callback  func(string, string)
 | 
			
		||||
	stopChan  chan bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) Stop() {
 | 
			
		||||
	if !w.isRunning {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.stopChan = make(chan bool, 1)
 | 
			
		||||
	w.isRunning = false
 | 
			
		||||
	if w.watcher != nil {
 | 
			
		||||
		_ = w.watcher.Close()
 | 
			
		||||
	}
 | 
			
		||||
	<-w.stopChan
 | 
			
		||||
	w.watcher = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) inType(filename string) bool {
 | 
			
		||||
	if len(w.fileTypes) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, fileType := range w.fileTypes {
 | 
			
		||||
		if strings.HasSuffix(filename, fileType) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) Add(path string) error {
 | 
			
		||||
	return w.add(path, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) add(path string, checkFile bool) error {
 | 
			
		||||
	if !w.isRunning {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if absPath, err := filepath.Abs(path); err == nil {
 | 
			
		||||
		path = absPath
 | 
			
		||||
	}
 | 
			
		||||
	if !u.FileExists(path) {
 | 
			
		||||
		_ = os.MkdirAll(path, 0755)
 | 
			
		||||
	}
 | 
			
		||||
	if err := w.watcher.Add(path); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else {
 | 
			
		||||
		var outErr error
 | 
			
		||||
		for _, f := range u.ReadDirN(path) {
 | 
			
		||||
			if !w.isRunning {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			if f.IsDir {
 | 
			
		||||
				if err := w.Add(f.FullName); err != nil {
 | 
			
		||||
					outErr = err
 | 
			
		||||
				}
 | 
			
		||||
			} else if checkFile {
 | 
			
		||||
				if w.inType(f.FullName) {
 | 
			
		||||
					w.callback(f.FullName, Create)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return outErr
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) Remove(path string) {
 | 
			
		||||
	if !w.isRunning {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	eventFileDir := path + string(os.PathSeparator)
 | 
			
		||||
	for _, item := range w.watcher.WatchList() {
 | 
			
		||||
		if item == path || strings.HasPrefix(item, eventFileDir) {
 | 
			
		||||
			_ = w.watcher.Remove(item)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) SetFileTypes(fileTypes []string) {
 | 
			
		||||
	if !w.isRunning {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if fileTypes == nil {
 | 
			
		||||
		fileTypes = make([]string, 0)
 | 
			
		||||
	}
 | 
			
		||||
	for i, fileType := range fileTypes {
 | 
			
		||||
		if !strings.HasPrefix(fileType, ".") {
 | 
			
		||||
			fileTypes[i] = "." + fileType
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	w.fileTypes = fileTypes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *Watcher) WatchList() []string {
 | 
			
		||||
	if !w.isRunning {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return w.watcher.WatchList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Start(paths, fileTypes []string, callback func(filename string, event string)) (*Watcher, error) {
 | 
			
		||||
	if watcher, err := fsnotify.NewWatcher(); err == nil {
 | 
			
		||||
		if paths == nil {
 | 
			
		||||
			paths = make([]string, 0)
 | 
			
		||||
		}
 | 
			
		||||
		w := &Watcher{
 | 
			
		||||
			watcher:   watcher,
 | 
			
		||||
			callback:  callback,
 | 
			
		||||
			isRunning: true,
 | 
			
		||||
		}
 | 
			
		||||
		w.SetFileTypes(fileTypes)
 | 
			
		||||
		for _, path := range paths {
 | 
			
		||||
			_ = w.add(path, false)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		go func() {
 | 
			
		||||
			for w.isRunning {
 | 
			
		||||
				select {
 | 
			
		||||
				case event, ok := <-watcher.Events:
 | 
			
		||||
					if !ok {
 | 
			
		||||
						w.isRunning = false
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					eventFilename := event.Name
 | 
			
		||||
					if event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
 | 
			
		||||
						w.Remove(eventFilename)
 | 
			
		||||
						if w.inType(eventFilename) {
 | 
			
		||||
							if event.Has(fsnotify.Remove) {
 | 
			
		||||
								callback(eventFilename, Remove)
 | 
			
		||||
							} else {
 | 
			
		||||
								callback(eventFilename, Rename)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					} else if event.Has(fsnotify.Write) {
 | 
			
		||||
						if w.inType(eventFilename) {
 | 
			
		||||
							callback(eventFilename, Change)
 | 
			
		||||
						}
 | 
			
		||||
					} else if event.Has(fsnotify.Create) {
 | 
			
		||||
						fileInfo := u.GetFileInfo(event.Name)
 | 
			
		||||
						if fileInfo.IsDir {
 | 
			
		||||
							_ = w.add(eventFilename, true)
 | 
			
		||||
						} else {
 | 
			
		||||
							if w.inType(eventFilename) {
 | 
			
		||||
								callback(eventFilename, Create)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				case _, ok := <-w.watcher.Errors:
 | 
			
		||||
					if !ok {
 | 
			
		||||
						w.isRunning = false
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			w.stopChan <- true
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		return w, nil
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								go.mod
									
									
									
									
									
								
							@ -5,13 +5,16 @@ go 1.22
 | 
			
		||||
require (
 | 
			
		||||
	github.com/dop251/goja v0.0.0-20240828124009-016eb7256539
 | 
			
		||||
	github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.7.0
 | 
			
		||||
	github.com/go-resty/resty/v2 v2.15.0
 | 
			
		||||
	github.com/golang-jwt/jwt/v5 v5.2.1
 | 
			
		||||
	github.com/sashabaranov/go-openai v1.29.2
 | 
			
		||||
	github.com/ssgo/config v1.7.7
 | 
			
		||||
	github.com/ssgo/httpclient v1.7.7
 | 
			
		||||
	github.com/ssgo/log v1.7.7
 | 
			
		||||
	github.com/ssgo/u v1.7.7
 | 
			
		||||
	github.com/stretchr/testify v1.9.0
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
@ -22,6 +25,8 @@ require (
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/ssgo/standard v1.7.7 // indirect
 | 
			
		||||
	golang.org/x/net v0.29.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.25.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.18.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
replace github.com/dop251/goja v0.0.0-20240828124009-016eb7256539 => ./goja
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								goja/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								goja/LICENSE
									
									
									
									
									
										Normal file
									
								
							@ -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.
 | 
			
		||||
							
								
								
									
										334
									
								
								goja/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								goja/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,334 @@
 | 
			
		||||
goja
 | 
			
		||||
====
 | 
			
		||||
 | 
			
		||||
ECMAScript 5.1(+) implementation in Go.
 | 
			
		||||
 | 
			
		||||
[](https://pkg.go.dev/github.com/dop251/goja)
 | 
			
		||||
 | 
			
		||||
Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and
 | 
			
		||||
performance.
 | 
			
		||||
 | 
			
		||||
This project was largely inspired by [otto](https://github.com/robertkrimen/otto).
 | 
			
		||||
 | 
			
		||||
The minimum required Go version is 1.20.
 | 
			
		||||
 | 
			
		||||
Features
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
 * Full ECMAScript 5.1 support (including regex and strict mode).
 | 
			
		||||
 * Passes nearly all [tc39 tests](https://github.com/tc39/test262) for the features implemented so far. The goal is to
 | 
			
		||||
   pass all of them. See .tc39_test262_checkout.sh for the latest working commit id.
 | 
			
		||||
 * Capable of running Babel, Typescript compiler and pretty much anything written in ES5.
 | 
			
		||||
 * Sourcemaps.
 | 
			
		||||
 * Most of ES6 functionality, still work in progress, see https://github.com/dop251/goja/milestone/1?closed=1
 | 
			
		||||
 | 
			
		||||
Known incompatibilities and caveats
 | 
			
		||||
-----------------------------------
 | 
			
		||||
 | 
			
		||||
### WeakMap
 | 
			
		||||
WeakMap is implemented by embedding references to the values into the keys. This means that as long
 | 
			
		||||
as the key is reachable all values associated with it in any weak maps also remain reachable and therefore
 | 
			
		||||
cannot be garbage collected even if they are not otherwise referenced, even after the WeakMap is gone.
 | 
			
		||||
The reference to the value is dropped either when the key is explicitly removed from the WeakMap or when the
 | 
			
		||||
key becomes unreachable.
 | 
			
		||||
 | 
			
		||||
To illustrate this:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
var m = new WeakMap();
 | 
			
		||||
var key = {};
 | 
			
		||||
var value = {/* a very large object */};
 | 
			
		||||
m.set(key, value);
 | 
			
		||||
value = undefined;
 | 
			
		||||
m = undefined; // The value does NOT become garbage-collectable at this point
 | 
			
		||||
key = undefined; // Now it does
 | 
			
		||||
// m.delete(key); // This would work too
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The reason for it is the limitation of the Go runtime. At the time of writing (version 1.15) having a finalizer
 | 
			
		||||
set on an object which is part of a reference cycle makes the whole cycle non-garbage-collectable. The solution
 | 
			
		||||
above is the only reasonable way I can think of without involving finalizers. This is the third attempt
 | 
			
		||||
(see https://github.com/dop251/goja/issues/250 and https://github.com/dop251/goja/issues/199 for more details).
 | 
			
		||||
 | 
			
		||||
Note, this does not have any effect on the application logic, but may cause a higher-than-expected memory usage.
 | 
			
		||||
 | 
			
		||||
### WeakRef and FinalizationRegistry
 | 
			
		||||
For the reason mentioned above implementing WeakRef and FinalizationRegistry does not seem to be possible at this stage.
 | 
			
		||||
 | 
			
		||||
### JSON
 | 
			
		||||
`JSON.parse()` uses the standard Go library which operates in UTF-8. Therefore, it cannot correctly parse broken UTF-16
 | 
			
		||||
surrogate pairs, for example:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
JSON.parse(`"\\uD800"`).charCodeAt(0).toString(16) // returns "fffd" instead of "d800"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Date
 | 
			
		||||
Conversion from calendar date to epoch timestamp uses the standard Go library which uses `int`, rather than `float` as per
 | 
			
		||||
ECMAScript specification. This means if you pass arguments that overflow int to the `Date()` constructor or  if there is
 | 
			
		||||
an integer overflow, the result will be incorrect, for example:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740) // returns 29256 instead of 29312
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
FAQ
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### How fast is it?
 | 
			
		||||
 | 
			
		||||
Although it's faster than many scripting language implementations in Go I have seen
 | 
			
		||||
(for example it's 6-7 times faster than otto on average) it is not a
 | 
			
		||||
replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine.
 | 
			
		||||
You can find some benchmarks [here](https://github.com/dop251/goja/issues/2).
 | 
			
		||||
 | 
			
		||||
### Why would I want to use it over a V8 wrapper?
 | 
			
		||||
 | 
			
		||||
It greatly depends on your usage scenario. If most of the work is done in javascript
 | 
			
		||||
(for example crypto or any other heavy calculations) you are definitely better off with V8.
 | 
			
		||||
 | 
			
		||||
If you need a scripting language that drives an engine written in Go so that
 | 
			
		||||
you need to make frequent calls between Go and javascript passing complex data structures
 | 
			
		||||
then the cgo overhead may outweigh the benefits of having a faster javascript engine.
 | 
			
		||||
 | 
			
		||||
Because it's written in pure Go there are no cgo dependencies, it's very easy to build and it
 | 
			
		||||
should run on any platform supported by Go.
 | 
			
		||||
 | 
			
		||||
It gives you a much better control over execution environment so can be useful for research.
 | 
			
		||||
 | 
			
		||||
### Is it goroutine-safe?
 | 
			
		||||
 | 
			
		||||
No. An instance of goja.Runtime can only be used by a single goroutine
 | 
			
		||||
at a time. You can create as many instances of Runtime as you like but
 | 
			
		||||
it's not possible to pass object values between runtimes.
 | 
			
		||||
 | 
			
		||||
### Where is setTimeout()?
 | 
			
		||||
 | 
			
		||||
setTimeout() assumes concurrent execution of code which requires an execution
 | 
			
		||||
environment, for example an event loop similar to nodejs or a browser.
 | 
			
		||||
There is a [separate project](https://github.com/dop251/goja_nodejs) aimed at providing some NodeJS functionality,
 | 
			
		||||
and it includes an event loop.
 | 
			
		||||
 | 
			
		||||
### Can you implement (feature X from ES6 or higher)?
 | 
			
		||||
 | 
			
		||||
I will be adding features in their dependency order and as quickly as time permits. Please do not ask
 | 
			
		||||
for ETAs. Features that are open in the [milestone](https://github.com/dop251/goja/milestone/1) are either in progress
 | 
			
		||||
or will be worked on next.
 | 
			
		||||
 | 
			
		||||
The ongoing work is done in separate feature branches which are merged into master when appropriate.
 | 
			
		||||
Every commit in these branches represents a relatively stable state (i.e. it compiles and passes all enabled tc39 tests),
 | 
			
		||||
however because the version of tc39 tests I use is quite old, it may be not as well tested as the ES5.1 functionality. Because there are (usually) no major breaking changes between ECMAScript revisions
 | 
			
		||||
it should not break your existing code. You are encouraged to give it a try and report any bugs found. Please do not submit fixes though without discussing it first, as the code could be changed in the meantime.
 | 
			
		||||
 | 
			
		||||
### How do I contribute?
 | 
			
		||||
 | 
			
		||||
Before submitting a pull request please make sure that:
 | 
			
		||||
 | 
			
		||||
- You followed ECMA standard as close as possible. If adding a new feature make sure you've read the specification,
 | 
			
		||||
do not just base it on a couple of examples that work fine.
 | 
			
		||||
- Your change does not have a significant negative impact on performance (unless it's a bugfix and it's unavoidable)
 | 
			
		||||
- It passes all relevant tc39 tests.
 | 
			
		||||
 | 
			
		||||
Current Status
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 * There should be no breaking changes in the API, however it may be extended.
 | 
			
		||||
 * Some of the AnnexB functionality is missing.
 | 
			
		||||
 | 
			
		||||
Basic Example
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
Run JavaScript and get the result value.
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
vm := goja.New()
 | 
			
		||||
v, err := vm.RunString("2 + 2")
 | 
			
		||||
if err != nil {
 | 
			
		||||
    panic(err)
 | 
			
		||||
}
 | 
			
		||||
if num := v.Export().(int64); num != 4 {
 | 
			
		||||
    panic(num)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Passing Values to JS
 | 
			
		||||
--------------------
 | 
			
		||||
Any Go value can be passed to JS using Runtime.ToValue() method. See the method's [documentation](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue) for more details.
 | 
			
		||||
 | 
			
		||||
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()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ExportTo) method.
 | 
			
		||||
 | 
			
		||||
Within a single export operation the same Object will be represented by the same Go value (either the same map, slice or
 | 
			
		||||
a pointer to the same struct). This includes circular objects and makes it possible to export them.
 | 
			
		||||
 | 
			
		||||
Calling JS functions from Go
 | 
			
		||||
----------------------------
 | 
			
		||||
There are 2 approaches:
 | 
			
		||||
 | 
			
		||||
- Using [AssertFunction()](https://pkg.go.dev/github.com/dop251/goja#AssertFunction):
 | 
			
		||||
```go
 | 
			
		||||
const SCRIPT = `
 | 
			
		||||
function sum(a, b) {
 | 
			
		||||
    return +a + b;
 | 
			
		||||
}
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
vm := goja.New()
 | 
			
		||||
_, err := vm.RunString(SCRIPT)
 | 
			
		||||
if err != nil {
 | 
			
		||||
    panic(err)
 | 
			
		||||
}
 | 
			
		||||
sum, ok := goja.AssertFunction(vm.Get("sum"))
 | 
			
		||||
if !ok {
 | 
			
		||||
    panic("Not a function")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
res, err := sum(goja.Undefined(), vm.ToValue(40), vm.ToValue(2))
 | 
			
		||||
if err != nil {
 | 
			
		||||
    panic(err)
 | 
			
		||||
}
 | 
			
		||||
fmt.Println(res)
 | 
			
		||||
// Output: 42
 | 
			
		||||
```
 | 
			
		||||
- Using [Runtime.ExportTo()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ExportTo):
 | 
			
		||||
```go
 | 
			
		||||
const SCRIPT = `
 | 
			
		||||
function sum(a, b) {
 | 
			
		||||
    return +a + b;
 | 
			
		||||
}
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
vm := goja.New()
 | 
			
		||||
_, err := vm.RunString(SCRIPT)
 | 
			
		||||
if err != nil {
 | 
			
		||||
    panic(err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var sum func(int, int) int
 | 
			
		||||
err = vm.ExportTo(vm.Get("sum"), &sum)
 | 
			
		||||
if err != nil {
 | 
			
		||||
    panic(err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fmt.Println(sum(40, 2)) // note, _this_ value in the function will be undefined.
 | 
			
		||||
// Output: 42
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The first one is more low level and allows specifying _this_ value, whereas the second one makes the function look like
 | 
			
		||||
a normal Go function.
 | 
			
		||||
 | 
			
		||||
Mapping struct field and method names
 | 
			
		||||
-------------------------------------
 | 
			
		||||
By default, the names are passed through as is which means they are capitalised. This does not match
 | 
			
		||||
the standard JavaScript naming convention, so if you need to make your JS code look more natural or if you are
 | 
			
		||||
dealing with a 3rd party library, you can use a [FieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#FieldNameMapper):
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
vm := goja.New()
 | 
			
		||||
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
 | 
			
		||||
type S struct {
 | 
			
		||||
    Field int `json:"field"`
 | 
			
		||||
}
 | 
			
		||||
vm.Set("s", S{Field: 42})
 | 
			
		||||
res, _ := vm.RunString(`s.field`) // without the mapper it would have been s.Field
 | 
			
		||||
fmt.Println(res.Export())
 | 
			
		||||
// Output: 42
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
There are two standard mappers: [TagFieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#TagFieldNameMapper) and
 | 
			
		||||
[UncapFieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#UncapFieldNameMapper), or you can use your own implementation.
 | 
			
		||||
 | 
			
		||||
Native Constructors
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
In order to implement a constructor function in Go use `func (goja.ConstructorCall) *goja.Object`.
 | 
			
		||||
See [Runtime.ToValue()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue) documentation for more details.
 | 
			
		||||
 | 
			
		||||
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 := goja.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 = goja.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 := goja.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()
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
NodeJS Compatibility
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
There is a [separate project](https://github.com/dop251/goja_nodejs) aimed at providing some of the NodeJS functionality.
 | 
			
		||||
							
								
								
									
										565
									
								
								goja/array.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										565
									
								
								goja/array.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,565 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/bits"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type arrayIterObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	obj     *Object
 | 
			
		||||
	nextIdx int64
 | 
			
		||||
	kind    iterationKind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ai *arrayIterObject) next() Value {
 | 
			
		||||
	if ai.obj == nil {
 | 
			
		||||
		return ai.val.runtime.createIterResultObject(_undefined, true)
 | 
			
		||||
	}
 | 
			
		||||
	if ta, ok := ai.obj.self.(*typedArrayObject); ok {
 | 
			
		||||
		ta.viewedArrayBuf.ensureNotDetached(true)
 | 
			
		||||
	}
 | 
			
		||||
	l := toLength(ai.obj.self.getStr("length", nil))
 | 
			
		||||
	index := ai.nextIdx
 | 
			
		||||
	if index >= l {
 | 
			
		||||
		ai.obj = nil
 | 
			
		||||
		return ai.val.runtime.createIterResultObject(_undefined, true)
 | 
			
		||||
	}
 | 
			
		||||
	ai.nextIdx++
 | 
			
		||||
	idxVal := valueInt(index)
 | 
			
		||||
	if ai.kind == iterationKindKey {
 | 
			
		||||
		return ai.val.runtime.createIterResultObject(idxVal, false)
 | 
			
		||||
	}
 | 
			
		||||
	elementValue := nilSafe(ai.obj.self.getIdx(idxVal, nil))
 | 
			
		||||
	var result Value
 | 
			
		||||
	if ai.kind == iterationKindValue {
 | 
			
		||||
		result = elementValue
 | 
			
		||||
	} else {
 | 
			
		||||
		result = ai.val.runtime.newArrayValues([]Value{idxVal, elementValue})
 | 
			
		||||
	}
 | 
			
		||||
	return ai.val.runtime.createIterResultObject(result, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createArrayIterator(iterObj *Object, kind iterationKind) Value {
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	ai := &arrayIterObject{
 | 
			
		||||
		obj:  iterObj,
 | 
			
		||||
		kind: kind,
 | 
			
		||||
	}
 | 
			
		||||
	ai.class = classObject
 | 
			
		||||
	ai.val = o
 | 
			
		||||
	ai.extensible = true
 | 
			
		||||
	o.self = ai
 | 
			
		||||
	ai.prototype = r.getArrayIteratorPrototype()
 | 
			
		||||
	ai.init()
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type arrayObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	values         []Value
 | 
			
		||||
	length         uint32
 | 
			
		||||
	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) _setLengthInt(l uint32, throw bool) bool {
 | 
			
		||||
	ret := true
 | 
			
		||||
	if l <= a.length {
 | 
			
		||||
		if a.propValueCount > 0 {
 | 
			
		||||
			// Slow path
 | 
			
		||||
			for i := len(a.values) - 1; i >= int(l); i-- {
 | 
			
		||||
				if prop, ok := a.values[i].(*valueProperty); ok {
 | 
			
		||||
					if !prop.configurable {
 | 
			
		||||
						l = uint32(i) + 1
 | 
			
		||||
						ret = false
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					a.propValueCount--
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if l <= uint32(len(a.values)) {
 | 
			
		||||
		if l >= 16 && l < uint32(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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setLengthInt(l uint32, throw bool) bool {
 | 
			
		||||
	if l == a.length {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if !a.lengthProp.writable {
 | 
			
		||||
		a.val.runtime.typeErrorResult(throw, "length is not writable")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return a._setLengthInt(l, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setLength(v uint32, throw bool) bool {
 | 
			
		||||
	if !a.lengthProp.writable {
 | 
			
		||||
		a.val.runtime.typeErrorResult(throw, "length is not writable")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return a._setLengthInt(v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
 | 
			
		||||
	prop := a.getOwnPropIdx(idx)
 | 
			
		||||
	if prop == nil {
 | 
			
		||||
		if a.prototype != nil {
 | 
			
		||||
			if receiver == nil {
 | 
			
		||||
				return a.prototype.self.getIdx(idx, a.val)
 | 
			
		||||
			}
 | 
			
		||||
			return a.prototype.self.getIdx(idx, receiver)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if prop, ok := prop.(*valueProperty); ok {
 | 
			
		||||
		if receiver == nil {
 | 
			
		||||
			return prop.get(a.val)
 | 
			
		||||
		}
 | 
			
		||||
		return prop.get(receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return prop
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if len(a.values) > 0 {
 | 
			
		||||
		if i := strToArrayIdx(name); i != math.MaxUint32 {
 | 
			
		||||
			if i < uint32(len(a.values)) {
 | 
			
		||||
				return a.values[i]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return a.getLengthProp()
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.getOwnPropStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) getOwnPropIdx(idx valueInt) Value {
 | 
			
		||||
	if i := toIdx(idx); i != math.MaxUint32 {
 | 
			
		||||
		if i < uint32(len(a.values)) {
 | 
			
		||||
			return a.values[i]
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a.baseObject.getOwnPropStr(idx.string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 int, j int) {
 | 
			
		||||
	a.values[i], a.values[j] = a.values[j], a.values[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) getLengthProp() *valueProperty {
 | 
			
		||||
	a.lengthProp.value = intToValue(int64(a.length))
 | 
			
		||||
	return &a.lengthProp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 | 
			
		||||
	if i := toIdx(idx); i != math.MaxUint32 {
 | 
			
		||||
		return a._setOwnIdx(i, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		return a.baseObject.setOwnStr(idx.string(), val, throw)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 | 
			
		||||
	var prop Value
 | 
			
		||||
	if idx < uint32(len(a.values)) {
 | 
			
		||||
		prop = a.values[idx]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if prop == nil {
 | 
			
		||||
		if proto := a.prototype; proto != nil {
 | 
			
		||||
			// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
			if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// new property
 | 
			
		||||
		if !a.extensible {
 | 
			
		||||
			a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			if idx >= a.length {
 | 
			
		||||
				if !a.setLengthInt(idx+1, throw) {
 | 
			
		||||
					return false
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if idx >= uint32(len(a.values)) {
 | 
			
		||||
				if !a.expand(idx) {
 | 
			
		||||
					a.val.self.(*sparseArrayObject).add(idx, val)
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			a.objCount++
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if prop, ok := prop.(*valueProperty); ok {
 | 
			
		||||
			if !prop.isWritable() {
 | 
			
		||||
				a.val.runtime.typeErrorResult(throw)
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
			prop.set(a.val, val)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	a.values[idx] = val
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._setOwnIdx(idx, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		if name == "length" {
 | 
			
		||||
			return a.setLength(a.val.runtime.toLengthUint32(val), throw)
 | 
			
		||||
		} else {
 | 
			
		||||
			return a.baseObject.setOwnStr(name, val, throw)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type arrayPropIter struct {
 | 
			
		||||
	a     *arrayObject
 | 
			
		||||
	limit int
 | 
			
		||||
	idx   int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	for i.idx < len(i.a.values) && i.idx < i.limit {
 | 
			
		||||
		name := asciiString(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.iterateStringKeys()()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&arrayPropIter{
 | 
			
		||||
		a:     a,
 | 
			
		||||
		limit: len(a.values),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	for i, prop := range a.values {
 | 
			
		||||
		name := strconv.Itoa(i)
 | 
			
		||||
		if prop != nil {
 | 
			
		||||
			if !all {
 | 
			
		||||
				if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			accum = append(accum, asciiString(name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.stringKeys(all, accum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return idx < uint32(len(a.values)) && a.values[idx] != nil
 | 
			
		||||
	} else {
 | 
			
		||||
		return a.baseObject.hasOwnPropertyStr(name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return idx < uint32(len(a.values)) && a.values[idx] != nil
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.hasOwnPropertyStr(idx.string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) hasPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if a.hasOwnPropertyIdx(idx) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if a.prototype != nil {
 | 
			
		||||
		return a.prototype.self.hasPropertyIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) expand(idx uint32) bool {
 | 
			
		||||
	targetLen := idx + 1
 | 
			
		||||
	if targetLen > uint32(len(a.values)) {
 | 
			
		||||
		if targetLen < uint32(cap(a.values)) {
 | 
			
		||||
			a.values = a.values[:targetLen]
 | 
			
		||||
		} else {
 | 
			
		||||
			if idx > 4096 && (a.objCount == 0 || idx/uint32(a.objCount) > 10) {
 | 
			
		||||
				//log.Println("Switching standard->sparse")
 | 
			
		||||
				sa := &sparseArrayObject{
 | 
			
		||||
					baseObject:     a.baseObject,
 | 
			
		||||
					length:         a.length,
 | 
			
		||||
					propValueCount: a.propValueCount,
 | 
			
		||||
				}
 | 
			
		||||
				sa.setValues(a.values, a.objCount+1)
 | 
			
		||||
				sa.val.self = sa
 | 
			
		||||
				sa.lengthProp.writable = a.lengthProp.writable
 | 
			
		||||
				sa._put("length", &sa.lengthProp)
 | 
			
		||||
				return false
 | 
			
		||||
			} else {
 | 
			
		||||
				if bits.UintSize == 32 {
 | 
			
		||||
					if targetLen >= math.MaxInt32 {
 | 
			
		||||
						panic(a.val.runtime.NewTypeError("Array index overflows int"))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				tl := int(targetLen)
 | 
			
		||||
				newValues := make([]Value, tl, growCap(tl, len(a.values), cap(a.values)))
 | 
			
		||||
				copy(newValues, a.values)
 | 
			
		||||
				a.values = newValues
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) defineArrayLength(prop *valueProperty, descr PropertyDescriptor, setter func(uint32, bool) bool, throw bool) bool {
 | 
			
		||||
	var newLen uint32
 | 
			
		||||
	ret := true
 | 
			
		||||
	if descr.Value != nil {
 | 
			
		||||
		newLen = r.toLengthUint32(descr.Value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if descr.Configurable == FLAG_TRUE || descr.Enumerable == FLAG_TRUE || descr.Getter != nil || descr.Setter != nil {
 | 
			
		||||
		ret = false
 | 
			
		||||
		goto Reject
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if descr.Value != nil {
 | 
			
		||||
		oldLen := uint32(prop.value.ToInteger())
 | 
			
		||||
		if oldLen != newLen {
 | 
			
		||||
			ret = setter(newLen, false)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		ret = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if descr.Writable != FLAG_NOT_SET {
 | 
			
		||||
		w := descr.Writable.Bool()
 | 
			
		||||
		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) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	var existing Value
 | 
			
		||||
	if idx < uint32(len(a.values)) {
 | 
			
		||||
		existing = a.values[idx]
 | 
			
		||||
	}
 | 
			
		||||
	prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
 | 
			
		||||
	if ok {
 | 
			
		||||
		if idx >= a.length {
 | 
			
		||||
			if !a.setLengthInt(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).add(idx, prop)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._defineIdxProperty(idx, descr, throw)
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.defineOwnPropertyStr(name, descr, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return a._defineIdxProperty(idx, descr, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
 | 
			
		||||
	if idx < uint32(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) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._deleteIdxProp(idx, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return a._deleteIdxProp(idx, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.deleteStr(idx.string(), throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	if v, exists := ctx.get(a.val); exists {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	arr := make([]interface{}, a.length)
 | 
			
		||||
	ctx.put(a.val, arr)
 | 
			
		||||
	if a.propValueCount == 0 && a.length == uint32(len(a.values)) && uint32(a.objCount) == a.length {
 | 
			
		||||
		for i, v := range a.values {
 | 
			
		||||
			if v != nil {
 | 
			
		||||
				arr[i] = exportValue(v, ctx)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for i := uint32(0); i < a.length; i++ {
 | 
			
		||||
			v := a.getIdx(valueInt(i), nil)
 | 
			
		||||
			if v != nil {
 | 
			
		||||
				arr[i] = exportValue(v, ctx)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) exportType() reflect.Type {
 | 
			
		||||
	return reflectTypeArray
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	r := a.val.runtime
 | 
			
		||||
	if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
 | 
			
		||||
		l := toIntStrict(int64(a.length))
 | 
			
		||||
		if typ.Kind() == reflect.Array {
 | 
			
		||||
			if dst.Len() != l {
 | 
			
		||||
				return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			dst.Set(reflect.MakeSlice(typ, l, l))
 | 
			
		||||
		}
 | 
			
		||||
		ctx.putTyped(a.val, typ, dst.Interface())
 | 
			
		||||
		for i := 0; i < l; i++ {
 | 
			
		||||
			if i >= len(a.values) {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			val := a.values[i]
 | 
			
		||||
			if p, ok := val.(*valueProperty); ok {
 | 
			
		||||
				val = p.get(a.val)
 | 
			
		||||
			}
 | 
			
		||||
			err := r.toReflectValue(val, dst.Index(i), ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("could not convert array element %v to %v at %d: %w", val, typ, i, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) {
 | 
			
		||||
	a.values = make([]Value, newMaxIdx+1)
 | 
			
		||||
	for _, item := range items {
 | 
			
		||||
		a.values[item.idx] = item.value
 | 
			
		||||
	}
 | 
			
		||||
	a.objCount = len(items)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toIdx(v valueInt) uint32 {
 | 
			
		||||
	if v >= 0 && v < math.MaxUint32 {
 | 
			
		||||
		return uint32(v)
 | 
			
		||||
	}
 | 
			
		||||
	return math.MaxUint32
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										500
									
								
								goja/array_sparse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										500
									
								
								goja/array_sparse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,500 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/bits"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type sparseArrayItem struct {
 | 
			
		||||
	idx   uint32
 | 
			
		||||
	value Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type sparseArrayObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	items          []sparseArrayItem
 | 
			
		||||
	length         uint32
 | 
			
		||||
	propValueCount int
 | 
			
		||||
	lengthProp     valueProperty
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) findIdx(idx uint32) int {
 | 
			
		||||
	return sort.Search(len(a.items), func(i int) bool {
 | 
			
		||||
		return a.items[i].idx >= idx
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) _setLengthInt(l uint32, throw bool) bool {
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setLengthInt(l uint32, throw bool) bool {
 | 
			
		||||
	if l == a.length {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if !a.lengthProp.writable {
 | 
			
		||||
		a.val.runtime.typeErrorResult(throw, "length is not writable")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return a._setLengthInt(l, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setLength(v uint32, throw bool) bool {
 | 
			
		||||
	if !a.lengthProp.writable {
 | 
			
		||||
		a.val.runtime.typeErrorResult(throw, "length is not writable")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return a._setLengthInt(v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) _getIdx(idx uint32) Value {
 | 
			
		||||
	i := a.findIdx(idx)
 | 
			
		||||
	if i < len(a.items) && a.items[i].idx == idx {
 | 
			
		||||
		return a.items[i].value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) getIdx(idx valueInt, receiver Value) Value {
 | 
			
		||||
	prop := a.getOwnPropIdx(idx)
 | 
			
		||||
	if prop == nil {
 | 
			
		||||
		if a.prototype != nil {
 | 
			
		||||
			if receiver == nil {
 | 
			
		||||
				return a.prototype.self.getIdx(idx, a.val)
 | 
			
		||||
			}
 | 
			
		||||
			return a.prototype.self.getIdx(idx, receiver)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if prop, ok := prop.(*valueProperty); ok {
 | 
			
		||||
		if receiver == nil {
 | 
			
		||||
			return prop.get(a.val)
 | 
			
		||||
		}
 | 
			
		||||
		return prop.get(receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return prop
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) getLengthProp() *valueProperty {
 | 
			
		||||
	a.lengthProp.value = intToValue(int64(a.length))
 | 
			
		||||
	return &a.lengthProp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._getIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return a.getLengthProp()
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.getOwnPropStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return a._getIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.getOwnPropStr(idx.string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) add(idx uint32, val Value) {
 | 
			
		||||
	i := a.findIdx(idx)
 | 
			
		||||
	a.items = append(a.items, sparseArrayItem{})
 | 
			
		||||
	copy(a.items[i+1:], a.items[i:])
 | 
			
		||||
	a.items[i] = sparseArrayItem{
 | 
			
		||||
		idx:   idx,
 | 
			
		||||
		value: val,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
 | 
			
		||||
	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 proto := a.prototype; proto != nil {
 | 
			
		||||
			// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
			if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// new property
 | 
			
		||||
		if !a.extensible {
 | 
			
		||||
			a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if idx >= a.length {
 | 
			
		||||
			if !a.setLengthInt(idx+1, throw) {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if a.expand(idx) {
 | 
			
		||||
			a.items = append(a.items, sparseArrayItem{})
 | 
			
		||||
			copy(a.items[i+1:], a.items[i:])
 | 
			
		||||
			a.items[i] = sparseArrayItem{
 | 
			
		||||
				idx:   idx,
 | 
			
		||||
				value: val,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			ar := a.val.self.(*arrayObject)
 | 
			
		||||
			ar.values[idx] = val
 | 
			
		||||
			ar.objCount++
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if prop, ok := prop.(*valueProperty); ok {
 | 
			
		||||
			if !prop.isWritable() {
 | 
			
		||||
				a.val.runtime.typeErrorResult(throw)
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
			prop.set(a.val, val)
 | 
			
		||||
		} else {
 | 
			
		||||
			a.items[i].value = val
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._setOwnIdx(idx, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		if name == "length" {
 | 
			
		||||
			return a.setLength(a.val.runtime.toLengthUint32(val), throw)
 | 
			
		||||
		} else {
 | 
			
		||||
			return a.baseObject.setOwnStr(name, val, throw)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return a._setOwnIdx(idx, val, throw)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a.baseObject.setOwnStr(idx.string(), val, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return a._setForeignIdx(name, a.getOwnPropIdx(name), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type sparseArrayPropIter struct {
 | 
			
		||||
	a   *sparseArrayObject
 | 
			
		||||
	idx int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	for i.idx < len(i.a.items) {
 | 
			
		||||
		name := asciiString(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.iterateStringKeys()()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&sparseArrayPropIter{
 | 
			
		||||
		a: a,
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	if all {
 | 
			
		||||
		for _, item := range a.items {
 | 
			
		||||
			accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10)))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for _, item := range a.items {
 | 
			
		||||
			if prop, ok := item.value.(*valueProperty); ok && !prop.enumerable {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10)))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a.baseObject.stringKeys(all, accum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) setValues(values []Value, objCount int) {
 | 
			
		||||
	a.items = make([]sparseArrayItem, 0, objCount)
 | 
			
		||||
	for i, val := range values {
 | 
			
		||||
		if val != nil {
 | 
			
		||||
			a.items = append(a.items, sparseArrayItem{
 | 
			
		||||
				idx:   uint32(i),
 | 
			
		||||
				value: val,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		i := a.findIdx(idx)
 | 
			
		||||
		return i < len(a.items) && a.items[i].idx == idx
 | 
			
		||||
	} else {
 | 
			
		||||
		return a.baseObject.hasOwnPropertyStr(name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		i := a.findIdx(idx)
 | 
			
		||||
		return i < len(a.items) && a.items[i].idx == idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a.baseObject.hasOwnPropertyStr(idx.string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) hasPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if a.hasOwnPropertyIdx(idx) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if a.prototype != nil {
 | 
			
		||||
		return a.prototype.self.hasPropertyIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) expand(idx uint32) bool {
 | 
			
		||||
	if l := len(a.items); l >= 1024 {
 | 
			
		||||
		if ii := a.items[l-1].idx; ii > idx {
 | 
			
		||||
			idx = ii
 | 
			
		||||
		}
 | 
			
		||||
		if (bits.UintSize == 64 || idx < math.MaxInt32) && int(idx)>>3 < l {
 | 
			
		||||
			//log.Println("Switching sparse->standard")
 | 
			
		||||
			ar := &arrayObject{
 | 
			
		||||
				baseObject:     a.baseObject,
 | 
			
		||||
				length:         a.length,
 | 
			
		||||
				propValueCount: a.propValueCount,
 | 
			
		||||
			}
 | 
			
		||||
			ar.setValuesFromSparse(a.items, int(idx))
 | 
			
		||||
			ar.val.self = ar
 | 
			
		||||
			ar.lengthProp.writable = a.lengthProp.writable
 | 
			
		||||
			a._put("length", &ar.lengthProp)
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	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(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
 | 
			
		||||
	if ok {
 | 
			
		||||
		if idx >= a.length {
 | 
			
		||||
			if !a.setLengthInt(idx+1, throw) {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if i >= len(a.items) || a.items[i].idx != idx {
 | 
			
		||||
			if a.expand(idx) {
 | 
			
		||||
				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 {
 | 
			
		||||
				a.val.self.(*arrayObject).values[idx] = prop
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			a.items[i].value = prop
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := prop.(*valueProperty); ok {
 | 
			
		||||
			a.propValueCount++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._defineIdxProperty(idx, descr, throw)
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.defineOwnPropertyStr(name, descr, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return a._defineIdxProperty(idx, descr, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) _deleteIdxProp(idx uint32, 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) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		return a._deleteIdxProp(idx, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) deleteIdx(idx valueInt, throw bool) bool {
 | 
			
		||||
	if idx := toIdx(idx); idx != math.MaxUint32 {
 | 
			
		||||
		return a._deleteIdxProp(idx, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.deleteStr(idx.string(), throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) sortLen() int {
 | 
			
		||||
	if len(a.items) > 0 {
 | 
			
		||||
		return toIntStrict(int64(a.items[len(a.items)-1].idx) + 1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	if v, exists := ctx.get(a.val); exists {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	arr := make([]interface{}, a.length)
 | 
			
		||||
	ctx.put(a.val, arr)
 | 
			
		||||
	var prevIdx uint32
 | 
			
		||||
	for _, item := range a.items {
 | 
			
		||||
		idx := item.idx
 | 
			
		||||
		for i := prevIdx; i < idx; i++ {
 | 
			
		||||
			if a.prototype != nil {
 | 
			
		||||
				if v := a.prototype.self.getIdx(valueInt(i), nil); v != nil {
 | 
			
		||||
					arr[i] = exportValue(v, ctx)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		v := item.value
 | 
			
		||||
		if v != nil {
 | 
			
		||||
			if prop, ok := v.(*valueProperty); ok {
 | 
			
		||||
				v = prop.get(a.val)
 | 
			
		||||
			}
 | 
			
		||||
			arr[idx] = exportValue(v, ctx)
 | 
			
		||||
		}
 | 
			
		||||
		prevIdx = idx + 1
 | 
			
		||||
	}
 | 
			
		||||
	for i := prevIdx; i < a.length; i++ {
 | 
			
		||||
		if a.prototype != nil {
 | 
			
		||||
			if v := a.prototype.self.getIdx(valueInt(i), nil); v != nil {
 | 
			
		||||
				arr[i] = exportValue(v, ctx)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return arr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) exportType() reflect.Type {
 | 
			
		||||
	return reflectTypeArray
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *sparseArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	r := a.val.runtime
 | 
			
		||||
	if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
 | 
			
		||||
		l := toIntStrict(int64(a.length))
 | 
			
		||||
		if typ.Kind() == reflect.Array {
 | 
			
		||||
			if dst.Len() != l {
 | 
			
		||||
				return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			dst.Set(reflect.MakeSlice(typ, l, l))
 | 
			
		||||
		}
 | 
			
		||||
		ctx.putTyped(a.val, typ, dst.Interface())
 | 
			
		||||
		for _, item := range a.items {
 | 
			
		||||
			val := item.value
 | 
			
		||||
			if p, ok := val.(*valueProperty); ok {
 | 
			
		||||
				val = p.get(a.val)
 | 
			
		||||
			}
 | 
			
		||||
			idx := toIntStrict(int64(item.idx))
 | 
			
		||||
			if idx >= l {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			err := r.toReflectValue(val, dst.Index(idx), ctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("could not convert array element %v to %v at %d: %w", item.value, typ, idx, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										264
									
								
								goja/array_sparse_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								goja/array_sparse_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,264 @@
 | 
			
		||||
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;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSparseArraySwitch(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
	var a = [];
 | 
			
		||||
	a[20470] = 5; // switch to sparse`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	a := vm.Get("a").(*Object)
 | 
			
		||||
	if _, ok := a.self.(*sparseArrayObject); !ok {
 | 
			
		||||
		t.Fatal("1: array is not sparse")
 | 
			
		||||
	}
 | 
			
		||||
	_, err = vm.RunString(`
 | 
			
		||||
	var cutoffIdx = Math.round(20470 - 20470/8);
 | 
			
		||||
	for (var i = a.length - 1; i >= cutoffIdx; i--) {
 | 
			
		||||
		a[i] = i;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// At this point it will have switched to a normal array
 | 
			
		||||
	if (a.length != 20471) {
 | 
			
		||||
		throw new Error("Invalid length: " + a.length);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = 0; i < cutoffIdx; i++) {
 | 
			
		||||
		if (a[i] !== undefined) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = cutoffIdx; i < a.length; i++) {
 | 
			
		||||
		if (a[i] !== i) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := a.self.(*arrayObject); !ok {
 | 
			
		||||
		t.Fatal("2: array is not normal")
 | 
			
		||||
	}
 | 
			
		||||
	_, err = vm.RunString(`
 | 
			
		||||
	// Now try to expand. Should stay a normal array
 | 
			
		||||
	a[20471] = 20471;
 | 
			
		||||
	if (a.length != 20472) {
 | 
			
		||||
		throw new Error("Invalid length: " + a.length);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = 0; i < cutoffIdx; i++) {
 | 
			
		||||
		if (a[i] !== undefined) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = cutoffIdx; i < a.length; i++) {
 | 
			
		||||
		if (a[i] !== i) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := a.self.(*arrayObject); !ok {
 | 
			
		||||
		t.Fatal("3: array is not normal")
 | 
			
		||||
	}
 | 
			
		||||
	_, err = vm.RunString(`
 | 
			
		||||
	// Delete enough elements for it to become sparse again.
 | 
			
		||||
	var cutoffIdx1 = Math.round(20472 - 20472/10);
 | 
			
		||||
	for (var i = cutoffIdx; i < cutoffIdx1; i++) {
 | 
			
		||||
		delete a[i];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// This should switch it back to sparse.
 | 
			
		||||
	a[25590] = 25590;
 | 
			
		||||
	if (a.length != 25591) {
 | 
			
		||||
		throw new Error("Invalid length: " + a.length);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = 0; i < cutoffIdx1; i++) {
 | 
			
		||||
		if (a[i] !== undefined) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = cutoffIdx1; i < 20472; i++) {
 | 
			
		||||
		if (a[i] !== i) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var i = 20472; i < 25590; i++) {
 | 
			
		||||
		if (a[i] !== undefined) {
 | 
			
		||||
			throw new Error("Invalid value at " + i + ": " + a[i]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a[25590] !== 25590) {
 | 
			
		||||
		throw new Error("Invalid value at 25590: " + a[25590]);
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := a.self.(*sparseArrayObject); !ok {
 | 
			
		||||
		t.Fatal("4: array is not sparse")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSparseArrayOwnKeys(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a1 = [];
 | 
			
		||||
	a1[500000] = 1;
 | 
			
		||||
	var seen = false;
 | 
			
		||||
	var count = 0;
 | 
			
		||||
	var keys = Object.keys(a1);
 | 
			
		||||
	keys.length === 1 && keys[0] === "500000"; 
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSparseArrayEnumerate(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a1 = [];
 | 
			
		||||
	a1[500000] = 1;
 | 
			
		||||
	var seen = false;
 | 
			
		||||
	var count = 0;
 | 
			
		||||
	for (var i in a1) {
 | 
			
		||||
		if (i === "500000") {
 | 
			
		||||
			if (seen) {
 | 
			
		||||
				throw new Error("seen twice");
 | 
			
		||||
			}
 | 
			
		||||
			seen = true;
 | 
			
		||||
		}
 | 
			
		||||
		count++;
 | 
			
		||||
	}
 | 
			
		||||
	seen && count === 1;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArraySparseMaxLength(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a = [];
 | 
			
		||||
	a[4294967294]=1;
 | 
			
		||||
	a.length === 4294967295 && a[4294967294] === 1;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArraySparseExportProps(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	proto := vm.NewArray()
 | 
			
		||||
	for _, idx := range []string{"0", "500", "9999", "10001", "20471"} {
 | 
			
		||||
		err := proto.Set(idx, true)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	arr := vm.NewArray()
 | 
			
		||||
	err := arr.SetPrototype(proto)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = arr.DefineDataProperty("20470", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = arr.DefineDataProperty("10000", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = arr.Set("length", 20472)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	actual := arr.Export()
 | 
			
		||||
	if actualArr, ok := actual.([]interface{}); ok {
 | 
			
		||||
		if len(actualArr) == 20472 {
 | 
			
		||||
			expectedIdx := map[int]struct{}{
 | 
			
		||||
				0:     {},
 | 
			
		||||
				500:   {},
 | 
			
		||||
				9999:  {},
 | 
			
		||||
				10000: {},
 | 
			
		||||
				10001: {},
 | 
			
		||||
				20470: {},
 | 
			
		||||
				20471: {},
 | 
			
		||||
			}
 | 
			
		||||
			for i, v := range actualArr {
 | 
			
		||||
				if _, exists := expectedIdx[i]; exists {
 | 
			
		||||
					if v != true {
 | 
			
		||||
						t.Fatalf("Expected true at %d, got %v", i, v)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					if v != nil {
 | 
			
		||||
						t.Fatalf("Expected nil at %d, got %v", i, v)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			t.Fatalf("Expected len 20471, actual: %d", len(actualArr))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Invalid export type: %T", actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSparseArrayExportToSlice(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	arr := vm.NewArray()
 | 
			
		||||
	err := arr.Set("20470", 120470)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = arr.DefineDataProperty("20471", vm.ToValue(220471), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	var exp []int
 | 
			
		||||
	err = vm.ExportTo(arr, &exp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(exp) != 20472 {
 | 
			
		||||
		t.Fatalf("len: %d", len(exp))
 | 
			
		||||
	}
 | 
			
		||||
	if e := exp[20470]; e != 120470 {
 | 
			
		||||
		t.Fatalf("20470: %d", e)
 | 
			
		||||
	}
 | 
			
		||||
	if e := exp[20471]; e != 220471 {
 | 
			
		||||
		t.Fatalf("20471: %d", e)
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < 20470; i++ {
 | 
			
		||||
		if exp[i] != 0 {
 | 
			
		||||
			t.Fatalf("at %d: %d", i, exp[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								goja/array_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								goja/array_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,133 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestArray1(t *testing.T) {
 | 
			
		||||
	r := &Runtime{}
 | 
			
		||||
	a := r.newArray(nil)
 | 
			
		||||
	a.setOwnIdx(valueInt(0), asciiString("test"), true)
 | 
			
		||||
	if l := a.getStr("length", nil).ToInteger(); l != 1 {
 | 
			
		||||
		t.Fatalf("Unexpected length: %d", l)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayExportProps(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	arr := vm.NewArray()
 | 
			
		||||
	err := arr.DefineDataProperty("0", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	actual := arr.Export()
 | 
			
		||||
	expected := []interface{}{true}
 | 
			
		||||
	if !reflect.DeepEqual(actual, expected) {
 | 
			
		||||
		t.Fatalf("Expected: %#v, actual: %#v", expected, actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayCanonicalIndex(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a = [];
 | 
			
		||||
	a["00"] = 1;
 | 
			
		||||
	a["01"] = 2;
 | 
			
		||||
	if (a[0] !== undefined) {
 | 
			
		||||
		throw new Error("a[0]");
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
 | 
			
		||||
	v.setOwn(valueInt(0), asciiString("test"), false)
 | 
			
		||||
	b.StartTimer()
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		a.getStr("0", nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
	v.setOwn(idx, asciiString("test"), false)
 | 
			
		||||
 | 
			
		||||
	b.StartTimer()
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		v.get(idx, nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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++ {
 | 
			
		||||
		v.setOwn(idx, val, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkArraySetEmpty(b *testing.B) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	_ = r.Get("Array").(*Object).Get("prototype").String() // materialise Array.prototype
 | 
			
		||||
	a := r.NewArray(0, 0)
 | 
			
		||||
	values := a.self.(*arrayObject).values
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		values[0] = nil
 | 
			
		||||
		a.self.setOwnIdx(0, valueTrue, true)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1068
									
								
								goja/ast/README.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1068
									
								
								goja/ast/README.markdown
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										876
									
								
								goja/ast/node.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										876
									
								
								goja/ast/node.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,876 @@
 | 
			
		||||
/*
 | 
			
		||||
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"
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PropertyKind string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PropertyKindValue  PropertyKind = "value"
 | 
			
		||||
	PropertyKindGet    PropertyKind = "get"
 | 
			
		||||
	PropertyKindSet    PropertyKind = "set"
 | 
			
		||||
	PropertyKindMethod PropertyKind = "method"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	BindingTarget interface {
 | 
			
		||||
		Expression
 | 
			
		||||
		_bindingTarget()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Binding struct {
 | 
			
		||||
		Target      BindingTarget
 | 
			
		||||
		Initializer Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Pattern interface {
 | 
			
		||||
		BindingTarget
 | 
			
		||||
		_pattern()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	YieldExpression struct {
 | 
			
		||||
		Yield    file.Idx
 | 
			
		||||
		Argument Expression
 | 
			
		||||
		Delegate bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	AwaitExpression struct {
 | 
			
		||||
		Await    file.Idx
 | 
			
		||||
		Argument Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ArrayLiteral struct {
 | 
			
		||||
		LeftBracket  file.Idx
 | 
			
		||||
		RightBracket file.Idx
 | 
			
		||||
		Value        []Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ArrayPattern struct {
 | 
			
		||||
		LeftBracket  file.Idx
 | 
			
		||||
		RightBracket file.Idx
 | 
			
		||||
		Elements     []Expression
 | 
			
		||||
		Rest         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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PrivateDotExpression struct {
 | 
			
		||||
		Left       Expression
 | 
			
		||||
		Identifier PrivateIdentifier
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	OptionalChain struct {
 | 
			
		||||
		Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Optional struct {
 | 
			
		||||
		Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	FunctionLiteral struct {
 | 
			
		||||
		Function      file.Idx
 | 
			
		||||
		Name          *Identifier
 | 
			
		||||
		ParameterList *ParameterList
 | 
			
		||||
		Body          *BlockStatement
 | 
			
		||||
		Source        string
 | 
			
		||||
 | 
			
		||||
		DeclarationList []*VariableDeclaration
 | 
			
		||||
 | 
			
		||||
		Async, Generator bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ClassLiteral struct {
 | 
			
		||||
		Class      file.Idx
 | 
			
		||||
		RightBrace file.Idx
 | 
			
		||||
		Name       *Identifier
 | 
			
		||||
		SuperClass Expression
 | 
			
		||||
		Body       []ClassElement
 | 
			
		||||
		Source     string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ConciseBody interface {
 | 
			
		||||
		Node
 | 
			
		||||
		_conciseBody()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ExpressionBody struct {
 | 
			
		||||
		Expression Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ArrowFunctionLiteral struct {
 | 
			
		||||
		Start           file.Idx
 | 
			
		||||
		ParameterList   *ParameterList
 | 
			
		||||
		Body            ConciseBody
 | 
			
		||||
		Source          string
 | 
			
		||||
		DeclarationList []*VariableDeclaration
 | 
			
		||||
		Async           bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Identifier struct {
 | 
			
		||||
		Name unistring.String
 | 
			
		||||
		Idx  file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PrivateIdentifier struct {
 | 
			
		||||
		Identifier
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ObjectPattern struct {
 | 
			
		||||
		LeftBrace  file.Idx
 | 
			
		||||
		RightBrace file.Idx
 | 
			
		||||
		Properties []Property
 | 
			
		||||
		Rest       Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ParameterList struct {
 | 
			
		||||
		Opening file.Idx
 | 
			
		||||
		List    []*Binding
 | 
			
		||||
		Rest    Expression
 | 
			
		||||
		Closing file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Property interface {
 | 
			
		||||
		Expression
 | 
			
		||||
		_property()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PropertyShort struct {
 | 
			
		||||
		Name        Identifier
 | 
			
		||||
		Initializer Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PropertyKeyed struct {
 | 
			
		||||
		Key      Expression
 | 
			
		||||
		Kind     PropertyKind
 | 
			
		||||
		Value    Expression
 | 
			
		||||
		Computed bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SpreadElement struct {
 | 
			
		||||
		Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RegExpLiteral struct {
 | 
			
		||||
		Idx     file.Idx
 | 
			
		||||
		Literal string
 | 
			
		||||
		Pattern string
 | 
			
		||||
		Flags   string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SequenceExpression struct {
 | 
			
		||||
		Sequence []Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	StringLiteral struct {
 | 
			
		||||
		Idx     file.Idx
 | 
			
		||||
		Literal string
 | 
			
		||||
		Value   unistring.String
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TemplateElement struct {
 | 
			
		||||
		Idx     file.Idx
 | 
			
		||||
		Literal string
 | 
			
		||||
		Parsed  unistring.String
 | 
			
		||||
		Valid   bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TemplateLiteral struct {
 | 
			
		||||
		OpenQuote   file.Idx
 | 
			
		||||
		CloseQuote  file.Idx
 | 
			
		||||
		Tag         Expression
 | 
			
		||||
		Elements    []*TemplateElement
 | 
			
		||||
		Expressions []Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ThisExpression struct {
 | 
			
		||||
		Idx file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	SuperExpression struct {
 | 
			
		||||
		Idx file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	UnaryExpression struct {
 | 
			
		||||
		Operator token.Token
 | 
			
		||||
		Idx      file.Idx // If a prefix operation
 | 
			
		||||
		Operand  Expression
 | 
			
		||||
		Postfix  bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MetaProperty struct {
 | 
			
		||||
		Meta, Property *Identifier
 | 
			
		||||
		Idx            file.Idx
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// _expressionNode
 | 
			
		||||
 | 
			
		||||
func (*ArrayLiteral) _expressionNode()          {}
 | 
			
		||||
func (*AssignExpression) _expressionNode()      {}
 | 
			
		||||
func (*YieldExpression) _expressionNode()       {}
 | 
			
		||||
func (*AwaitExpression) _expressionNode()       {}
 | 
			
		||||
func (*BadExpression) _expressionNode()         {}
 | 
			
		||||
func (*BinaryExpression) _expressionNode()      {}
 | 
			
		||||
func (*BooleanLiteral) _expressionNode()        {}
 | 
			
		||||
func (*BracketExpression) _expressionNode()     {}
 | 
			
		||||
func (*CallExpression) _expressionNode()        {}
 | 
			
		||||
func (*ConditionalExpression) _expressionNode() {}
 | 
			
		||||
func (*DotExpression) _expressionNode()         {}
 | 
			
		||||
func (*PrivateDotExpression) _expressionNode()  {}
 | 
			
		||||
func (*FunctionLiteral) _expressionNode()       {}
 | 
			
		||||
func (*ClassLiteral) _expressionNode()          {}
 | 
			
		||||
func (*ArrowFunctionLiteral) _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 (*TemplateLiteral) _expressionNode()       {}
 | 
			
		||||
func (*ThisExpression) _expressionNode()        {}
 | 
			
		||||
func (*SuperExpression) _expressionNode()       {}
 | 
			
		||||
func (*UnaryExpression) _expressionNode()       {}
 | 
			
		||||
func (*MetaProperty) _expressionNode()          {}
 | 
			
		||||
func (*ObjectPattern) _expressionNode()         {}
 | 
			
		||||
func (*ArrayPattern) _expressionNode()          {}
 | 
			
		||||
func (*Binding) _expressionNode()               {}
 | 
			
		||||
 | 
			
		||||
func (*PropertyShort) _expressionNode() {}
 | 
			
		||||
func (*PropertyKeyed) _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 BindingTarget
 | 
			
		||||
		Body      *BlockStatement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DebuggerStatement struct {
 | 
			
		||||
		Debugger file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DoWhileStatement struct {
 | 
			
		||||
		Do               file.Idx
 | 
			
		||||
		Test             Expression
 | 
			
		||||
		Body             Statement
 | 
			
		||||
		RightParenthesis file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	EmptyStatement struct {
 | 
			
		||||
		Semicolon file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ExpressionStatement struct {
 | 
			
		||||
		Expression Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForInStatement struct {
 | 
			
		||||
		For    file.Idx
 | 
			
		||||
		Into   ForInto
 | 
			
		||||
		Source Expression
 | 
			
		||||
		Body   Statement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForOfStatement struct {
 | 
			
		||||
		For    file.Idx
 | 
			
		||||
		Into   ForInto
 | 
			
		||||
		Source Expression
 | 
			
		||||
		Body   Statement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForStatement struct {
 | 
			
		||||
		For         file.Idx
 | 
			
		||||
		Initializer ForLoopInitializer
 | 
			
		||||
		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
 | 
			
		||||
		RightBrace   file.Idx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ThrowStatement struct {
 | 
			
		||||
		Throw    file.Idx
 | 
			
		||||
		Argument Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TryStatement struct {
 | 
			
		||||
		Try     file.Idx
 | 
			
		||||
		Body    *BlockStatement
 | 
			
		||||
		Catch   *CatchStatement
 | 
			
		||||
		Finally *BlockStatement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	VariableStatement struct {
 | 
			
		||||
		Var  file.Idx
 | 
			
		||||
		List []*Binding
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LexicalDeclaration struct {
 | 
			
		||||
		Idx   file.Idx
 | 
			
		||||
		Token token.Token
 | 
			
		||||
		List  []*Binding
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	WhileStatement struct {
 | 
			
		||||
		While file.Idx
 | 
			
		||||
		Test  Expression
 | 
			
		||||
		Body  Statement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	WithStatement struct {
 | 
			
		||||
		With   file.Idx
 | 
			
		||||
		Object Expression
 | 
			
		||||
		Body   Statement
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	FunctionDeclaration struct {
 | 
			
		||||
		Function *FunctionLiteral
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ClassDeclaration struct {
 | 
			
		||||
		Class *ClassLiteral
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// _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 (*ForOfStatement) _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()       {}
 | 
			
		||||
func (*LexicalDeclaration) _statementNode()  {}
 | 
			
		||||
func (*FunctionDeclaration) _statementNode() {}
 | 
			
		||||
func (*ClassDeclaration) _statementNode()    {}
 | 
			
		||||
 | 
			
		||||
// =========== //
 | 
			
		||||
// Declaration //
 | 
			
		||||
// =========== //
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	VariableDeclaration struct {
 | 
			
		||||
		Var  file.Idx
 | 
			
		||||
		List []*Binding
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ClassElement interface {
 | 
			
		||||
		Node
 | 
			
		||||
		_classElement()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	FieldDefinition struct {
 | 
			
		||||
		Idx         file.Idx
 | 
			
		||||
		Key         Expression
 | 
			
		||||
		Initializer Expression
 | 
			
		||||
		Computed    bool
 | 
			
		||||
		Static      bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MethodDefinition struct {
 | 
			
		||||
		Idx      file.Idx
 | 
			
		||||
		Key      Expression
 | 
			
		||||
		Kind     PropertyKind // "method", "get" or "set"
 | 
			
		||||
		Body     *FunctionLiteral
 | 
			
		||||
		Computed bool
 | 
			
		||||
		Static   bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ClassStaticBlock struct {
 | 
			
		||||
		Static          file.Idx
 | 
			
		||||
		Block           *BlockStatement
 | 
			
		||||
		Source          string
 | 
			
		||||
		DeclarationList []*VariableDeclaration
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	ForLoopInitializer interface {
 | 
			
		||||
		Node
 | 
			
		||||
		_forLoopInitializer()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForLoopInitializerExpression struct {
 | 
			
		||||
		Expression Expression
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForLoopInitializerVarDeclList struct {
 | 
			
		||||
		Var  file.Idx
 | 
			
		||||
		List []*Binding
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForLoopInitializerLexicalDecl struct {
 | 
			
		||||
		LexicalDeclaration LexicalDeclaration
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForInto interface {
 | 
			
		||||
		Node
 | 
			
		||||
		_forInto()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForIntoVar struct {
 | 
			
		||||
		Binding *Binding
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForDeclaration struct {
 | 
			
		||||
		Idx     file.Idx
 | 
			
		||||
		IsConst bool
 | 
			
		||||
		Target  BindingTarget
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ForIntoExpression struct {
 | 
			
		||||
		Expression Expression
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (*ForLoopInitializerExpression) _forLoopInitializer()  {}
 | 
			
		||||
func (*ForLoopInitializerVarDeclList) _forLoopInitializer() {}
 | 
			
		||||
func (*ForLoopInitializerLexicalDecl) _forLoopInitializer() {}
 | 
			
		||||
 | 
			
		||||
func (*ForIntoVar) _forInto()        {}
 | 
			
		||||
func (*ForDeclaration) _forInto()    {}
 | 
			
		||||
func (*ForIntoExpression) _forInto() {}
 | 
			
		||||
 | 
			
		||||
func (*ArrayPattern) _pattern()       {}
 | 
			
		||||
func (*ArrayPattern) _bindingTarget() {}
 | 
			
		||||
 | 
			
		||||
func (*ObjectPattern) _pattern()       {}
 | 
			
		||||
func (*ObjectPattern) _bindingTarget() {}
 | 
			
		||||
 | 
			
		||||
func (*BadExpression) _bindingTarget() {}
 | 
			
		||||
 | 
			
		||||
func (*PropertyShort) _property() {}
 | 
			
		||||
func (*PropertyKeyed) _property() {}
 | 
			
		||||
func (*SpreadElement) _property() {}
 | 
			
		||||
 | 
			
		||||
func (*Identifier) _bindingTarget() {}
 | 
			
		||||
 | 
			
		||||
func (*BlockStatement) _conciseBody() {}
 | 
			
		||||
func (*ExpressionBody) _conciseBody() {}
 | 
			
		||||
 | 
			
		||||
func (*FieldDefinition) _classElement()  {}
 | 
			
		||||
func (*MethodDefinition) _classElement() {}
 | 
			
		||||
func (*ClassStaticBlock) _classElement() {}
 | 
			
		||||
 | 
			
		||||
// ==== //
 | 
			
		||||
// Node //
 | 
			
		||||
// ==== //
 | 
			
		||||
 | 
			
		||||
type Program struct {
 | 
			
		||||
	Body []Statement
 | 
			
		||||
 | 
			
		||||
	DeclarationList []*VariableDeclaration
 | 
			
		||||
 | 
			
		||||
	File *file.File
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==== //
 | 
			
		||||
// Idx0 //
 | 
			
		||||
// ==== //
 | 
			
		||||
 | 
			
		||||
func (self *ArrayLiteral) Idx0() file.Idx          { return self.LeftBracket }
 | 
			
		||||
func (self *ArrayPattern) Idx0() file.Idx          { return self.LeftBracket }
 | 
			
		||||
func (self *YieldExpression) Idx0() file.Idx       { return self.Yield }
 | 
			
		||||
func (self *AwaitExpression) Idx0() file.Idx       { return self.Await }
 | 
			
		||||
func (self *ObjectPattern) Idx0() file.Idx         { return self.LeftBrace }
 | 
			
		||||
func (self *ParameterList) Idx0() file.Idx         { return self.Opening }
 | 
			
		||||
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 *PrivateDotExpression) Idx0() file.Idx  { return self.Left.Idx0() }
 | 
			
		||||
func (self *FunctionLiteral) Idx0() file.Idx       { return self.Function }
 | 
			
		||||
func (self *ClassLiteral) Idx0() file.Idx          { return self.Class }
 | 
			
		||||
func (self *ArrowFunctionLiteral) Idx0() file.Idx  { return self.Start }
 | 
			
		||||
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 *TemplateElement) Idx0() file.Idx       { return self.Idx }
 | 
			
		||||
func (self *TemplateLiteral) Idx0() file.Idx       { return self.OpenQuote }
 | 
			
		||||
func (self *ThisExpression) Idx0() file.Idx        { return self.Idx }
 | 
			
		||||
func (self *SuperExpression) Idx0() file.Idx       { return self.Idx }
 | 
			
		||||
func (self *UnaryExpression) Idx0() file.Idx {
 | 
			
		||||
	if self.Postfix {
 | 
			
		||||
		return self.Operand.Idx0()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Idx
 | 
			
		||||
}
 | 
			
		||||
func (self *MetaProperty) 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 *ForOfStatement) 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 }
 | 
			
		||||
func (self *LexicalDeclaration) Idx0() file.Idx  { return self.Idx }
 | 
			
		||||
func (self *FunctionDeclaration) Idx0() file.Idx { return self.Function.Idx0() }
 | 
			
		||||
func (self *ClassDeclaration) Idx0() file.Idx    { return self.Class.Idx0() }
 | 
			
		||||
func (self *Binding) Idx0() file.Idx             { return self.Target.Idx0() }
 | 
			
		||||
 | 
			
		||||
func (self *ForLoopInitializerExpression) Idx0() file.Idx  { return self.Expression.Idx0() }
 | 
			
		||||
func (self *ForLoopInitializerVarDeclList) Idx0() file.Idx { return self.List[0].Idx0() }
 | 
			
		||||
func (self *ForLoopInitializerLexicalDecl) Idx0() file.Idx { return self.LexicalDeclaration.Idx0() }
 | 
			
		||||
func (self *PropertyShort) Idx0() file.Idx                 { return self.Name.Idx }
 | 
			
		||||
func (self *PropertyKeyed) Idx0() file.Idx                 { return self.Key.Idx0() }
 | 
			
		||||
func (self *ExpressionBody) Idx0() file.Idx                { return self.Expression.Idx0() }
 | 
			
		||||
 | 
			
		||||
func (self *VariableDeclaration) Idx0() file.Idx { return self.Var }
 | 
			
		||||
func (self *FieldDefinition) Idx0() file.Idx     { return self.Idx }
 | 
			
		||||
func (self *MethodDefinition) Idx0() file.Idx    { return self.Idx }
 | 
			
		||||
func (self *ClassStaticBlock) Idx0() file.Idx    { return self.Static }
 | 
			
		||||
 | 
			
		||||
func (self *ForDeclaration) Idx0() file.Idx    { return self.Idx }
 | 
			
		||||
func (self *ForIntoVar) Idx0() file.Idx        { return self.Binding.Idx0() }
 | 
			
		||||
func (self *ForIntoExpression) Idx0() file.Idx { return self.Expression.Idx0() }
 | 
			
		||||
 | 
			
		||||
// ==== //
 | 
			
		||||
// Idx1 //
 | 
			
		||||
// ==== //
 | 
			
		||||
 | 
			
		||||
func (self *ArrayLiteral) Idx1() file.Idx          { return self.RightBracket + 1 }
 | 
			
		||||
func (self *ArrayPattern) Idx1() file.Idx          { return self.RightBracket + 1 }
 | 
			
		||||
func (self *AssignExpression) Idx1() file.Idx      { return self.Right.Idx1() }
 | 
			
		||||
func (self *AwaitExpression) Idx1() file.Idx       { return self.Argument.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.Alternate.Idx1() }
 | 
			
		||||
func (self *DotExpression) Idx1() file.Idx         { return self.Identifier.Idx1() }
 | 
			
		||||
func (self *PrivateDotExpression) Idx1() file.Idx  { return self.Identifier.Idx1() }
 | 
			
		||||
func (self *FunctionLiteral) Idx1() file.Idx       { return self.Body.Idx1() }
 | 
			
		||||
func (self *ClassLiteral) Idx1() file.Idx          { return self.RightBrace + 1 }
 | 
			
		||||
func (self *ArrowFunctionLiteral) 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 {
 | 
			
		||||
	if self.ArgumentList != nil {
 | 
			
		||||
		return self.RightParenthesis + 1
 | 
			
		||||
	} else {
 | 
			
		||||
		return self.Callee.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
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 + 1 }
 | 
			
		||||
func (self *ObjectPattern) Idx1() file.Idx      { return self.RightBrace + 1 }
 | 
			
		||||
func (self *ParameterList) Idx1() file.Idx      { return self.Closing + 1 }
 | 
			
		||||
func (self *RegExpLiteral) Idx1() file.Idx      { return file.Idx(int(self.Idx) + len(self.Literal)) }
 | 
			
		||||
func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() }
 | 
			
		||||
func (self *StringLiteral) Idx1() file.Idx      { return file.Idx(int(self.Idx) + len(self.Literal)) }
 | 
			
		||||
func (self *TemplateElement) Idx1() file.Idx    { return file.Idx(int(self.Idx) + len(self.Literal)) }
 | 
			
		||||
func (self *TemplateLiteral) Idx1() file.Idx    { return self.CloseQuote + 1 }
 | 
			
		||||
func (self *ThisExpression) Idx1() file.Idx     { return self.Idx + 4 }
 | 
			
		||||
func (self *SuperExpression) Idx1() file.Idx    { return self.Idx + 5 }
 | 
			
		||||
func (self *UnaryExpression) Idx1() file.Idx {
 | 
			
		||||
	if self.Postfix {
 | 
			
		||||
		return self.Operand.Idx1() + 2 // ++ --
 | 
			
		||||
	}
 | 
			
		||||
	return self.Operand.Idx1()
 | 
			
		||||
}
 | 
			
		||||
func (self *MetaProperty) Idx1() file.Idx {
 | 
			
		||||
	return self.Property.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 {
 | 
			
		||||
	if self.Label == nil {
 | 
			
		||||
		return file.Idx(int(self.Idx) + len(self.Token.String()))
 | 
			
		||||
	}
 | 
			
		||||
	return self.Label.Idx1()
 | 
			
		||||
}
 | 
			
		||||
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.RightParenthesis + 1 }
 | 
			
		||||
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 *ForOfStatement) 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.Statement.Idx1() }
 | 
			
		||||
func (self *Program) Idx1() file.Idx           { return self.Body[len(self.Body)-1].Idx1() }
 | 
			
		||||
func (self *ReturnStatement) Idx1() file.Idx {
 | 
			
		||||
	if self.Argument != nil {
 | 
			
		||||
		return self.Argument.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Return + 6
 | 
			
		||||
}
 | 
			
		||||
func (self *SwitchStatement) Idx1() file.Idx { return self.RightBrace + 1 }
 | 
			
		||||
func (self *ThrowStatement) Idx1() file.Idx  { return self.Argument.Idx1() }
 | 
			
		||||
func (self *TryStatement) Idx1() file.Idx {
 | 
			
		||||
	if self.Finally != nil {
 | 
			
		||||
		return self.Finally.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	if self.Catch != nil {
 | 
			
		||||
		return self.Catch.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Body.Idx1()
 | 
			
		||||
}
 | 
			
		||||
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() }
 | 
			
		||||
func (self *LexicalDeclaration) Idx1() file.Idx  { return self.List[len(self.List)-1].Idx1() }
 | 
			
		||||
func (self *FunctionDeclaration) Idx1() file.Idx { return self.Function.Idx1() }
 | 
			
		||||
func (self *ClassDeclaration) Idx1() file.Idx    { return self.Class.Idx1() }
 | 
			
		||||
func (self *Binding) Idx1() file.Idx {
 | 
			
		||||
	if self.Initializer != nil {
 | 
			
		||||
		return self.Initializer.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Target.Idx1()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *ForLoopInitializerExpression) Idx1() file.Idx  { return self.Expression.Idx1() }
 | 
			
		||||
func (self *ForLoopInitializerVarDeclList) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
 | 
			
		||||
func (self *ForLoopInitializerLexicalDecl) Idx1() file.Idx { return self.LexicalDeclaration.Idx1() }
 | 
			
		||||
 | 
			
		||||
func (self *PropertyShort) Idx1() file.Idx {
 | 
			
		||||
	if self.Initializer != nil {
 | 
			
		||||
		return self.Initializer.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Name.Idx1()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() }
 | 
			
		||||
 | 
			
		||||
func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() }
 | 
			
		||||
 | 
			
		||||
func (self *VariableDeclaration) Idx1() file.Idx {
 | 
			
		||||
	if len(self.List) > 0 {
 | 
			
		||||
		return self.List[len(self.List)-1].Idx1()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return self.Var + 3
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FieldDefinition) Idx1() file.Idx {
 | 
			
		||||
	if self.Initializer != nil {
 | 
			
		||||
		return self.Initializer.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Key.Idx1()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *MethodDefinition) Idx1() file.Idx {
 | 
			
		||||
	return self.Body.Idx1()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *ClassStaticBlock) Idx1() file.Idx {
 | 
			
		||||
	return self.Block.Idx1()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *YieldExpression) Idx1() file.Idx {
 | 
			
		||||
	if self.Argument != nil {
 | 
			
		||||
		return self.Argument.Idx1()
 | 
			
		||||
	}
 | 
			
		||||
	return self.Yield + 5
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *ForDeclaration) Idx1() file.Idx    { return self.Target.Idx1() }
 | 
			
		||||
func (self *ForIntoVar) Idx1() file.Idx        { return self.Binding.Idx1() }
 | 
			
		||||
func (self *ForIntoExpression) Idx1() file.Idx { return self.Expression.Idx1() }
 | 
			
		||||
							
								
								
									
										1794
									
								
								goja/builtin_array.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1794
									
								
								goja/builtin_array.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										367
									
								
								goja/builtin_arrray_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										367
									
								
								goja/builtin_arrray_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,367 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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]
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(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;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayDeleteNonexisting(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	Array.prototype[0] = 42;
 | 
			
		||||
	var a = [];
 | 
			
		||||
	delete a[0] && a[0] === 42;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(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;
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(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;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(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;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(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;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayFrom(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	function checkDestHoles(dest, prefix) {
 | 
			
		||||
		assert(dest !== source, prefix + ": dest !== source");
 | 
			
		||||
		assert.sameValue(dest.length, 3, prefix + ": dest.length");
 | 
			
		||||
		assert.sameValue(dest[0], 1, prefix + ": [0]");
 | 
			
		||||
		assert.sameValue(dest[1], undefined, prefix + ": [1]");
 | 
			
		||||
		assert(dest.hasOwnProperty("1"), prefix + ': hasOwnProperty("1")');
 | 
			
		||||
		assert.sameValue(dest[2], 3, prefix + ": [2]");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function checkDest(dest, prefix) {
 | 
			
		||||
		assert(dest !== source, prefix + ": dest !== source");
 | 
			
		||||
		assert.sameValue(dest.length, 3, prefix + ": dest.length");
 | 
			
		||||
		assert.sameValue(dest[0], 1, prefix + ": [0]");
 | 
			
		||||
		assert.sameValue(dest[1], 2, prefix + ": [1]");
 | 
			
		||||
		assert.sameValue(dest[2], 3, prefix + ": [2]");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var source = [1,2,3];
 | 
			
		||||
	var srcHoles = [1,,3];
 | 
			
		||||
 | 
			
		||||
	checkDest(Array.from(source), "std source/std dest");
 | 
			
		||||
	checkDestHoles(Array.from(srcHoles), "std source (holes)/std dest");
 | 
			
		||||
 | 
			
		||||
	function Iter() {
 | 
			
		||||
		this.idx = 0;
 | 
			
		||||
	}
 | 
			
		||||
	Iter.prototype.next = function() {
 | 
			
		||||
		if (this.idx < source.length) {
 | 
			
		||||
			return {value: source[this.idx++]};
 | 
			
		||||
		} else {
 | 
			
		||||
			return {done: true};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var src = {};
 | 
			
		||||
	src[Symbol.iterator] = function() {
 | 
			
		||||
		return new Iter();
 | 
			
		||||
	}
 | 
			
		||||
	checkDest(Array.from(src), "iter src/std dest");
 | 
			
		||||
 | 
			
		||||
	src = {0: 1, 2: 3, length: 3};
 | 
			
		||||
	checkDestHoles(Array.from(src), "arrayLike src/std dest");
 | 
			
		||||
 | 
			
		||||
	function A() {}
 | 
			
		||||
	A.from = Array.from;
 | 
			
		||||
 | 
			
		||||
	checkDest(A.from(source), "std src/cust dest");
 | 
			
		||||
	checkDestHoles(A.from(srcHoles), "std src (holes)/cust dest");
 | 
			
		||||
	checkDestHoles(A.from(src), "arrayLike src/cust dest");
 | 
			
		||||
 | 
			
		||||
	function T2() {
 | 
			
		||||
	  Object.defineProperty(this, 0, {
 | 
			
		||||
		configurable: false,
 | 
			
		||||
		writable: true,
 | 
			
		||||
		enumerable: true
 | 
			
		||||
	  });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		Array.from.call(T2, source);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayOf(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	function T1() {
 | 
			
		||||
	  Object.preventExtensions(this);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
	  Array.of.call(T1, 'Bob');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	function T2() {
 | 
			
		||||
	  Object.defineProperty(this, 0, {
 | 
			
		||||
		configurable: false,
 | 
			
		||||
		writable: true,
 | 
			
		||||
		enumerable: true
 | 
			
		||||
	  });
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
	  Array.of.call(T2, 'Bob');
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	result = Array.of.call(undefined);
 | 
			
		||||
	assert(
 | 
			
		||||
	  result instanceof Array,
 | 
			
		||||
	  'this is not a constructor'
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	result = Array.of.call(Math.cos);
 | 
			
		||||
	assert(
 | 
			
		||||
	  result instanceof Array,
 | 
			
		||||
	  'this is a builtin function with no [[Construct]] slot'
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnscopables(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var keys = [];
 | 
			
		||||
	var _length;
 | 
			
		||||
	with (Array.prototype) {
 | 
			
		||||
		_length = length;
 | 
			
		||||
		keys.push('something');
 | 
			
		||||
	}
 | 
			
		||||
	_length === 0 && keys.length === 1 && keys[0] === "something";
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArraySort(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		[1,2].sort(null);
 | 
			
		||||
	}, "null compare function");
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		[1,2].sort({});
 | 
			
		||||
	}, "non-callable compare function");
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArraySortNonStdArray(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
 | 
			
		||||
 | 
			
		||||
	Object.defineProperty(array, '2', {
 | 
			
		||||
	  get() {
 | 
			
		||||
		array.pop();
 | 
			
		||||
		array.pop();
 | 
			
		||||
		return this.foo;
 | 
			
		||||
	  },
 | 
			
		||||
	  set(v) {
 | 
			
		||||
		this.foo = v;
 | 
			
		||||
	  }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	array.sort();
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(array[0], 'b');
 | 
			
		||||
	assert.sameValue(array[1], 'c');
 | 
			
		||||
	assert.sameValue(array[3], undefined);
 | 
			
		||||
	assert.sameValue(array[4], undefined);
 | 
			
		||||
	assert.sameValue('5' in array, false);
 | 
			
		||||
	assert.sameValue(array.hasOwnProperty('5'), false);
 | 
			
		||||
	assert.sameValue(array.length, 6);
 | 
			
		||||
	assert.sameValue(array.foo, undefined);
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(array[2], undefined);
 | 
			
		||||
	assert.sameValue(array.length, 4);
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayConcat(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var concat = Array.prototype.concat;
 | 
			
		||||
	var array = [1, 2];
 | 
			
		||||
	var sparseArray = [1, , 2];
 | 
			
		||||
	var nonSpreadableArray = [1, 2];
 | 
			
		||||
	nonSpreadableArray[Symbol.isConcatSpreadable] = false;
 | 
			
		||||
	var arrayLike = { 0: 1, 1: 2, length: 2 };
 | 
			
		||||
	var spreadableArrayLike = { 0: 1, 1: 2, length: 2 };
 | 
			
		||||
	spreadableArrayLike[Symbol.isConcatSpreadable] = true;
 | 
			
		||||
	assert(looksNative(concat));
 | 
			
		||||
	assert(deepEqual(array.concat(), [1, 2]), '#1');
 | 
			
		||||
	assert(deepEqual(sparseArray.concat(), [1, , 2]), '#2');
 | 
			
		||||
	assert(deepEqual(nonSpreadableArray.concat(), [[1, 2]]), '#3');
 | 
			
		||||
	assert(deepEqual(concat.call(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#4');
 | 
			
		||||
	assert(deepEqual(concat.call(spreadableArrayLike), [1, 2]), '#5');
 | 
			
		||||
	assert(deepEqual([].concat(array), [1, 2]), '#6');
 | 
			
		||||
	assert(deepEqual([].concat(sparseArray), [1, , 2]), '#7');
 | 
			
		||||
	assert(deepEqual([].concat(nonSpreadableArray), [[1, 2]]), '#8');
 | 
			
		||||
	assert(deepEqual([].concat(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#9');
 | 
			
		||||
	assert(deepEqual([].concat(spreadableArrayLike), [1, 2]), '#10');
 | 
			
		||||
	assert(deepEqual(array.concat(sparseArray, nonSpreadableArray, arrayLike, spreadableArrayLike), [
 | 
			
		||||
	1, 2, 1, , 2, [1, 2], { 0: 1, 1: 2, length: 2 }, 1, 2,
 | 
			
		||||
	]), '#11');
 | 
			
		||||
	array = [];
 | 
			
		||||
	array.constructor = {};
 | 
			
		||||
	array.constructor[Symbol.species] = function () {
 | 
			
		||||
		return { foo: 1 };
 | 
			
		||||
	}
 | 
			
		||||
	assert.sameValue(array.concat().foo, 1, '@@species');
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLibX(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayFlat(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]];
 | 
			
		||||
	assert(deepEqual(array.flat(), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#1');
 | 
			
		||||
	assert(deepEqual(array.flat(1), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#2');
 | 
			
		||||
	assert(deepEqual(array.flat(3), [1,2,3,4,5,6,[7,8,9]]), '#3');
 | 
			
		||||
	assert(deepEqual(array.flat(4), [1,2,3,4,5,6,7,8,9]), '#4');
 | 
			
		||||
	assert(deepEqual(array.flat(10), [1,2,3,4,5,6,7,8,9]), '#5');
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLibX(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayFlatMap(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var double = function(x) {
 | 
			
		||||
		if (isNaN(x)) {
 | 
			
		||||
			return x
 | 
			
		||||
		}
 | 
			
		||||
		return x * 2
 | 
			
		||||
	}
 | 
			
		||||
	var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]];
 | 
			
		||||
	assert(deepEqual(array.flatMap(double), [2,2,3,[4,5,6],[[[7,8,9]]]]), '#1');
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLibX(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayProto(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	const a = Array.prototype;
 | 
			
		||||
	a.push(1, 2, 3, 4, 5);
 | 
			
		||||
	assert.sameValue(a.length, 5);
 | 
			
		||||
	assert.sameValue(a[0], 1);
 | 
			
		||||
	a.length = 3;
 | 
			
		||||
	assert.sameValue(a.length, 3);
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3]));
 | 
			
		||||
	a.shift();
 | 
			
		||||
	assert.sameValue(a.length, 2);
 | 
			
		||||
	assert(compareArray(a, [2, 3]));
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayToSpliced(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	const a = [1, 2, 3];
 | 
			
		||||
	a.push(4)
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4]));
 | 
			
		||||
	const b = a.toSpliced(2)
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4]));
 | 
			
		||||
	assert(compareArray(b, [1, 2]));
 | 
			
		||||
	a.push(5)
 | 
			
		||||
	const c = a.toSpliced(1, 2);
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4, 5]));
 | 
			
		||||
	assert(compareArray(c, [1, 4, 5]));
 | 
			
		||||
	assert(compareArray(a.toSpliced(4, 2, 'a', 'b'), [1, 2, 3, 4, 'a', 'b']));
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4, 5]));
 | 
			
		||||
	assert(compareArray(a.toSpliced(-2, 2), [1, 2, 3]));
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4, 5]));
 | 
			
		||||
	assert(compareArray(a.toSpliced(2, 10), [1, 2]));
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4, 5]));
 | 
			
		||||
	assert(compareArray(a.toSpliced(1, 0, 'a'), [1, 'a', 2, 3, 4, 5]));
 | 
			
		||||
	assert(compareArray(a, [1, 2, 3, 4, 5]));
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										369
									
								
								goja/builtin_bigint.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								goja/builtin_bigint.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,369 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/maphash"
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type valueBigInt big.Int
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ToInteger() int64 {
 | 
			
		||||
	v.ToNumber()
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) toString() String {
 | 
			
		||||
	return asciiString((*big.Int)(v).String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) string() unistring.String {
 | 
			
		||||
	return unistring.String(v.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ToString() Value {
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) String() string {
 | 
			
		||||
	return (*big.Int)(v).String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ToFloat() float64 {
 | 
			
		||||
	v.ToNumber()
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ToNumber() Value {
 | 
			
		||||
	panic(typeError("Cannot convert a BigInt value to a number"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ToBoolean() bool {
 | 
			
		||||
	return (*big.Int)(v).Sign() != 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ToObject(r *Runtime) *Object {
 | 
			
		||||
	return r.newPrimitiveObject(v, r.getBigIntPrototype(), classObject)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) SameAs(other Value) bool {
 | 
			
		||||
	if o, ok := other.(*valueBigInt); ok {
 | 
			
		||||
		return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) Equals(other Value) bool {
 | 
			
		||||
	switch o := other.(type) {
 | 
			
		||||
	case *valueBigInt:
 | 
			
		||||
		return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
 | 
			
		||||
	case valueInt:
 | 
			
		||||
		return (*big.Int)(v).Cmp(big.NewInt(int64(o))) == 0
 | 
			
		||||
	case valueFloat:
 | 
			
		||||
		if IsInfinity(o) || math.IsNaN(float64(o)) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if f := big.NewFloat(float64(o)); f.IsInt() {
 | 
			
		||||
			i, _ := f.Int(nil)
 | 
			
		||||
			return (*big.Int)(v).Cmp(i) == 0
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	case String:
 | 
			
		||||
		bigInt, err := stringToBigInt(o.toTrimmedUTF8())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		return bigInt.Cmp((*big.Int)(v)) == 0
 | 
			
		||||
	case valueBool:
 | 
			
		||||
		return (*big.Int)(v).Int64() == o.ToInteger()
 | 
			
		||||
	case *Object:
 | 
			
		||||
		return v.Equals(o.toPrimitiveNumber())
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) StrictEquals(other Value) bool {
 | 
			
		||||
	o, ok := other.(*valueBigInt)
 | 
			
		||||
	if ok {
 | 
			
		||||
		return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) Export() interface{} {
 | 
			
		||||
	return new(big.Int).Set((*big.Int)(v))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) ExportType() reflect.Type {
 | 
			
		||||
	return typeBigInt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) baseObject(rt *Runtime) *Object {
 | 
			
		||||
	return rt.getBigIntPrototype()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *valueBigInt) hash(hash *maphash.Hash) uint64 {
 | 
			
		||||
	var sign byte
 | 
			
		||||
	if (*big.Int)(v).Sign() < 0 {
 | 
			
		||||
		sign = 0x01
 | 
			
		||||
	} else {
 | 
			
		||||
		sign = 0x00
 | 
			
		||||
	}
 | 
			
		||||
	_ = hash.WriteByte(sign)
 | 
			
		||||
	_, _ = hash.Write((*big.Int)(v).Bytes())
 | 
			
		||||
	h := hash.Sum64()
 | 
			
		||||
	hash.Reset()
 | 
			
		||||
	return h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toBigInt(value Value) *valueBigInt {
 | 
			
		||||
	// Undefined	Throw a TypeError exception.
 | 
			
		||||
	// Null			Throw a TypeError exception.
 | 
			
		||||
	// Boolean		Return 1n if prim is true and 0n if prim is false.
 | 
			
		||||
	// BigInt		Return prim.
 | 
			
		||||
	// Number		Throw a TypeError exception.
 | 
			
		||||
	// String		1. Let n be StringToBigInt(prim).
 | 
			
		||||
	//				2. If n is undefined, throw a SyntaxError exception.
 | 
			
		||||
	//				3. Return n.
 | 
			
		||||
	// Symbol		Throw a TypeError exception.
 | 
			
		||||
	switch prim := value.(type) {
 | 
			
		||||
	case *valueBigInt:
 | 
			
		||||
		return prim
 | 
			
		||||
	case String:
 | 
			
		||||
		bigInt, err := stringToBigInt(prim.toTrimmedUTF8())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(syntaxError(fmt.Sprintf("Cannot convert %s to a BigInt", prim)))
 | 
			
		||||
		}
 | 
			
		||||
		return (*valueBigInt)(bigInt)
 | 
			
		||||
	case valueBool:
 | 
			
		||||
		return (*valueBigInt)(big.NewInt(prim.ToInteger()))
 | 
			
		||||
	case *Symbol:
 | 
			
		||||
		panic(typeError("Cannot convert Symbol to a BigInt"))
 | 
			
		||||
	case *Object:
 | 
			
		||||
		return toBigInt(prim.toPrimitiveNumber())
 | 
			
		||||
	default:
 | 
			
		||||
		panic(typeError(fmt.Sprintf("Cannot convert %s to a BigInt", prim)))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func numberToBigInt(v Value) *valueBigInt {
 | 
			
		||||
	switch v := toNumeric(v).(type) {
 | 
			
		||||
	case *valueBigInt:
 | 
			
		||||
		return v
 | 
			
		||||
	case valueInt:
 | 
			
		||||
		return (*valueBigInt)(big.NewInt(v.ToInteger()))
 | 
			
		||||
	case valueFloat:
 | 
			
		||||
		if IsInfinity(v) || math.IsNaN(float64(v)) {
 | 
			
		||||
			panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v)))
 | 
			
		||||
		}
 | 
			
		||||
		if f := big.NewFloat(float64(v)); f.IsInt() {
 | 
			
		||||
			n, _ := f.Int(nil)
 | 
			
		||||
			return (*valueBigInt)(n)
 | 
			
		||||
		}
 | 
			
		||||
		panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v)))
 | 
			
		||||
	case *Object:
 | 
			
		||||
		prim := v.toPrimitiveNumber()
 | 
			
		||||
		switch prim.(type) {
 | 
			
		||||
		case valueInt, valueFloat:
 | 
			
		||||
			return numberToBigInt(prim)
 | 
			
		||||
		default:
 | 
			
		||||
			return toBigInt(prim)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		panic(newTypeError("Cannot convert %s to a BigInt", v))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func stringToBigInt(str string) (*big.Int, error) {
 | 
			
		||||
	var bigint big.Int
 | 
			
		||||
	n, err := stringToInt(str)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		switch {
 | 
			
		||||
		case isRangeErr(err):
 | 
			
		||||
			bigint.SetString(str, 0)
 | 
			
		||||
		case err == strconv.ErrSyntax:
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, strconv.ErrSyntax
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		bigint.SetInt64(n)
 | 
			
		||||
	}
 | 
			
		||||
	return &bigint, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) thisBigIntValue(value Value) Value {
 | 
			
		||||
	switch t := value.(type) {
 | 
			
		||||
	case *valueBigInt:
 | 
			
		||||
		return t
 | 
			
		||||
	case *Object:
 | 
			
		||||
		switch t := t.self.(type) {
 | 
			
		||||
		case *primitiveValueObject:
 | 
			
		||||
			return r.thisBigIntValue(t.pValue)
 | 
			
		||||
		case *objectGoReflect:
 | 
			
		||||
			if t.exportType() == typeBigInt && t.valueOf != nil {
 | 
			
		||||
				return t.valueOf()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("requires that 'this' be a BigInt"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) bigintproto_valueOf(call FunctionCall) Value {
 | 
			
		||||
	return r.thisBigIntValue(call.This)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) bigintproto_toString(call FunctionCall) Value {
 | 
			
		||||
	x := (*big.Int)(r.thisBigIntValue(call.This).(*valueBigInt))
 | 
			
		||||
	radix := call.Argument(0)
 | 
			
		||||
	var radixMV int
 | 
			
		||||
 | 
			
		||||
	if radix == _undefined {
 | 
			
		||||
		radixMV = 10
 | 
			
		||||
	} else {
 | 
			
		||||
		radixMV = int(radix.ToInteger())
 | 
			
		||||
		if radixMV < 2 || radixMV > 36 {
 | 
			
		||||
			panic(r.newError(r.getRangeError(), "radix must be an integer between 2 and 36"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return asciiString(x.Text(radixMV))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) bigint_asIntN(call FunctionCall) Value {
 | 
			
		||||
	if len(call.Arguments) < 2 {
 | 
			
		||||
		panic(r.NewTypeError("Cannot convert undefined to a BigInt"))
 | 
			
		||||
	}
 | 
			
		||||
	bits := r.toIndex(call.Argument(0).ToNumber())
 | 
			
		||||
	if bits < 0 {
 | 
			
		||||
		panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer"))
 | 
			
		||||
	}
 | 
			
		||||
	bigint := toBigInt(call.Argument(1))
 | 
			
		||||
 | 
			
		||||
	twoToBits := new(big.Int).Lsh(big.NewInt(1), uint(bits))
 | 
			
		||||
	mod := new(big.Int).Mod((*big.Int)(bigint), twoToBits)
 | 
			
		||||
	if bits > 0 && mod.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bits-1))) >= 0 {
 | 
			
		||||
		return (*valueBigInt)(mod.Sub(mod, twoToBits))
 | 
			
		||||
	} else {
 | 
			
		||||
		return (*valueBigInt)(mod)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) bigint_asUintN(call FunctionCall) Value {
 | 
			
		||||
	if len(call.Arguments) < 2 {
 | 
			
		||||
		panic(r.NewTypeError("Cannot convert undefined to a BigInt"))
 | 
			
		||||
	}
 | 
			
		||||
	bits := r.toIndex(call.Argument(0).ToNumber())
 | 
			
		||||
	if bits < 0 {
 | 
			
		||||
		panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer"))
 | 
			
		||||
	}
 | 
			
		||||
	bigint := (*big.Int)(toBigInt(call.Argument(1)))
 | 
			
		||||
	ret := new(big.Int).Mod(bigint, new(big.Int).Lsh(big.NewInt(1), uint(bits)))
 | 
			
		||||
	return (*valueBigInt)(ret)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var bigintTemplate *objectTemplate
 | 
			
		||||
var bigintTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getBigIntTemplate() *objectTemplate {
 | 
			
		||||
	bigintTemplateOnce.Do(func() {
 | 
			
		||||
		bigintTemplate = createBigIntTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return bigintTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createBigIntTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.getFunctionPrototype()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
 | 
			
		||||
	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
 | 
			
		||||
	t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getBigIntPrototype(), false, false, false) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("asIntN", func(r *Runtime) Value { return r.methodProp(r.bigint_asIntN, "asIntN", 2) })
 | 
			
		||||
	t.putStr("asUintN", func(r *Runtime) Value { return r.methodProp(r.bigint_asUintN, "asUintN", 2) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_BigInt(call FunctionCall) Value {
 | 
			
		||||
	if len(call.Arguments) > 0 {
 | 
			
		||||
		switch v := call.Argument(0).(type) {
 | 
			
		||||
		case *valueBigInt, valueInt, valueFloat, *Object:
 | 
			
		||||
			return numberToBigInt(v)
 | 
			
		||||
		default:
 | 
			
		||||
			return toBigInt(v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return (*valueBigInt)(big.NewInt(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget != nil {
 | 
			
		||||
		panic(r.NewTypeError("BigInt is not a constructor"))
 | 
			
		||||
	}
 | 
			
		||||
	var v Value
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		v = numberToBigInt(args[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		v = (*valueBigInt)(big.NewInt(0))
 | 
			
		||||
	}
 | 
			
		||||
	return r.newPrimitiveObject(v, newTarget, classObject)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getBigInt() *Object {
 | 
			
		||||
	ret := r.global.BigInt
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.BigInt = ret
 | 
			
		||||
		r.newTemplatedFuncObject(getBigIntTemplate(), ret, r.builtin_BigInt,
 | 
			
		||||
			r.wrapNativeConstruct(r.builtin_newBigInt, ret, r.getBigIntPrototype()))
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createBigIntProtoTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.global.ObjectPrototype
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, true) })
 | 
			
		||||
	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
 | 
			
		||||
	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toLocaleString", 0) })
 | 
			
		||||
	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toString", 0) })
 | 
			
		||||
	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.bigintproto_valueOf, "valueOf", 0) })
 | 
			
		||||
	t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var bigintProtoTemplate *objectTemplate
 | 
			
		||||
var bigintProtoTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getBigIntProtoTemplate() *objectTemplate {
 | 
			
		||||
	bigintProtoTemplateOnce.Do(func() {
 | 
			
		||||
		bigintProtoTemplate = createBigIntProtoTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return bigintProtoTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getBigIntPrototype() *Object {
 | 
			
		||||
	ret := r.global.BigIntPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.BigIntPrototype = ret
 | 
			
		||||
		o := r.newTemplatedObject(getBigIntProtoTemplate(), ret)
 | 
			
		||||
		o.class = classObject
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								goja/builtin_bigint_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								goja/builtin_bigint_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBigInt(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `0xabcdef0123456789abcdef0123n`
 | 
			
		||||
	b := new(big.Int)
 | 
			
		||||
	b.SetString("0xabcdef0123456789abcdef0123", 0)
 | 
			
		||||
	testScript(SCRIPT, (*valueBigInt)(b), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBigIntExportTo(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
 | 
			
		||||
	t.Run("bigint exportType", func(t *testing.T) {
 | 
			
		||||
		v, err := vm.RunString(`BigInt(Number.MAX_SAFE_INTEGER + 10);`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if typ := v.ExportType(); typ != typeBigInt {
 | 
			
		||||
			t.Fatal(typ)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("bigint", func(t *testing.T) {
 | 
			
		||||
		var b big.Int
 | 
			
		||||
		err := vm.ExportTo(vm.ToValue(big.NewInt(10)), &b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if b.Cmp(big.NewInt(10)) != 0 {
 | 
			
		||||
			t.Fatalf("bigint: %s", b.String())
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBigIntFormat(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
assert.sameValue((1n).toString(undefined), "1", "radius undefined");
 | 
			
		||||
assert.throws(RangeError, () => { (1n).toString(-1); }, "radius -1");
 | 
			
		||||
assert.throws(RangeError, () => { (1n).toString(37); }, "radius 37");
 | 
			
		||||
assert.sameValue((1n).toString(2), "1", "radius 2");
 | 
			
		||||
assert.sameValue((10n).toString(3), "101", "radius 3");
 | 
			
		||||
`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBigIntOperator(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
assert.throws(TypeError, () => { 1 - 1n; }, "mix type add");
 | 
			
		||||
assert.throws(TypeError, () => { 1n - 1; }, "mix type add");
 | 
			
		||||
assert.throws(TypeError, () => { 1n + 1; }, "mix type sub");
 | 
			
		||||
assert.throws(TypeError, () => { 1 + 1n; }, "mix type sub");
 | 
			
		||||
assert.throws(TypeError, () => { 1 * 1n; }, "mix type mul");
 | 
			
		||||
assert.throws(TypeError, () => { 1n * 1; }, "mix type mul");
 | 
			
		||||
assert.throws(TypeError, () => { 1 / 1n; }, "mix type div");
 | 
			
		||||
assert.throws(TypeError, () => { 1n / 1; }, "mix type div");
 | 
			
		||||
assert.throws(TypeError, () => { 1 % 1n; }, "mix type mod");
 | 
			
		||||
assert.throws(TypeError, () => { 1n % 1; }, "mix type mod");
 | 
			
		||||
assert.throws(TypeError, () => { 1n ** 1; }, "mix type exp");
 | 
			
		||||
assert.throws(TypeError, () => { 1 ** 1n; }, "mix type exp");
 | 
			
		||||
assert.throws(TypeError, () => { 1 & 1n; }, "mix type and");
 | 
			
		||||
assert.throws(TypeError, () => { 1n & 1; }, "mix type and");
 | 
			
		||||
assert.throws(TypeError, () => { 1 | 1n; }, "mix type or");
 | 
			
		||||
assert.throws(TypeError, () => { 1n | 1; }, "mix type or");
 | 
			
		||||
assert.throws(TypeError, () => { 1 ^ 1n; }, "mix type xor");
 | 
			
		||||
assert.throws(TypeError, () => { 1n ^ 1; }, "mix type xor");
 | 
			
		||||
assert.throws(TypeError, () => { 1 << 1n; }, "mix type lsh");
 | 
			
		||||
assert.throws(TypeError, () => { 1n << 1; }, "mix type lsh");
 | 
			
		||||
assert.throws(TypeError, () => { 1 >> 1n; }, "mix type rsh");
 | 
			
		||||
assert.throws(TypeError, () => { 1n >> 1; }, "mix type rsh");
 | 
			
		||||
assert.throws(TypeError, () => { 1 >>> 1n; }, "mix type ursh");
 | 
			
		||||
assert.throws(TypeError, () => { 1n >>> 1; }, "mix type ursh");
 | 
			
		||||
 | 
			
		||||
assert.sameValue(1n + 1n, 2n, "add");
 | 
			
		||||
assert.sameValue(1n - 1n, 0n, "sub");
 | 
			
		||||
assert.sameValue(1n * 2n, 2n, "mul");
 | 
			
		||||
assert.sameValue(1n / 2n, 0n, "div");
 | 
			
		||||
assert.sameValue(1n % 2n, 1n, "mod");
 | 
			
		||||
assert.sameValue(1n ** 2n, 1n, "exp");
 | 
			
		||||
assert.sameValue(1n & 1n, 1n, "and");
 | 
			
		||||
assert.sameValue(1n | 1n, 1n, "or");
 | 
			
		||||
assert.sameValue(2n ^ 1n, 3n, "xor");
 | 
			
		||||
assert.sameValue(1n << 1n, 2n, "lsh");
 | 
			
		||||
assert.sameValue(4n << -1n, 2n, "neg lsh");
 | 
			
		||||
assert.sameValue(4n >> 1n, 2n, "rsh");
 | 
			
		||||
assert.sameValue(2n >> -2n, 8n, "neg rsh");
 | 
			
		||||
 | 
			
		||||
let a = 1n;
 | 
			
		||||
assert.sameValue(++a, 2n, "inc");
 | 
			
		||||
assert.sameValue(--a, 1n, "dec");
 | 
			
		||||
 | 
			
		||||
assert.sameValue(Object(1n) - 1n, 0n, "primitive sub");
 | 
			
		||||
assert.sameValue(Object(Object(1n)) - 1n, 0n, "primitive sub");
 | 
			
		||||
assert.sameValue({ [Symbol.toPrimitive]: () => 1n } - 1n, 0n, "primitive sub");
 | 
			
		||||
assert.sameValue({ valueOf: () => 1n } - 1n, 0n, "valueOf sub");
 | 
			
		||||
 | 
			
		||||
assert.sameValue(1n > 0, true, "gt");
 | 
			
		||||
assert.sameValue(0 > 1n, false, "gt");
 | 
			
		||||
assert.sameValue(Object(1n) > 0, true, "gt");
 | 
			
		||||
assert.sameValue(0 > Object(1n), false, "gt");
 | 
			
		||||
 | 
			
		||||
assert.sameValue(1n < 0, false, "lt");
 | 
			
		||||
assert.sameValue(0 < 1n, true, "lt");
 | 
			
		||||
assert.sameValue(Object(1n) < 0, false, "lt");
 | 
			
		||||
assert.sameValue(0 < Object(1n), true, "lt");
 | 
			
		||||
 | 
			
		||||
assert.sameValue(1n >= 0, true, "ge");
 | 
			
		||||
assert.sameValue(0 >= 1n, false, "ge");
 | 
			
		||||
assert.sameValue(1n <= 0, false, "le");
 | 
			
		||||
assert.sameValue(0 <= 1n, true, "le");
 | 
			
		||||
`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								goja/builtin_boolean.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								goja/builtin_boolean.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
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
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if o, ok := o.self.(*objectGoReflect); ok {
 | 
			
		||||
			if o.class == classBoolean && o.toString != nil {
 | 
			
		||||
				return o.toString()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if o, ok := o.self.(*objectGoReflect); ok {
 | 
			
		||||
			if o.class == classBoolean && o.valueOf != nil {
 | 
			
		||||
				return o.valueOf()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.typeErrorResult(true, "Method Boolean.prototype.valueOf is called on incompatible receiver")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getBooleanPrototype() *Object {
 | 
			
		||||
	ret := r.global.BooleanPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
 | 
			
		||||
		r.global.BooleanPrototype = ret
 | 
			
		||||
		o := ret.self
 | 
			
		||||
		o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, "toString", 0), true, false, true)
 | 
			
		||||
		o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, "valueOf", 0), true, false, true)
 | 
			
		||||
		o._putProp("constructor", r.getBoolean(), true, false, true)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getBoolean() *Object {
 | 
			
		||||
	ret := r.global.Boolean
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Boolean = ret
 | 
			
		||||
		proto := r.getBooleanPrototype()
 | 
			
		||||
		r.newNativeFuncAndConstruct(ret, r.builtin_Boolean,
 | 
			
		||||
			r.wrapNativeConstruct(r.builtin_newBoolean, ret, proto), proto, "Boolean", intToValue(1))
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1058
									
								
								goja/builtin_date.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1058
									
								
								goja/builtin_date.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										314
									
								
								goja/builtin_error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								goja/builtin_error.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,314 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import "github.com/dop251/goja/unistring"
 | 
			
		||||
 | 
			
		||||
const propNameStack = "stack"
 | 
			
		||||
 | 
			
		||||
type errorObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	stack          []StackFrame
 | 
			
		||||
	stackPropAdded bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) formatStack() String {
 | 
			
		||||
	var b StringBuilder
 | 
			
		||||
	val := writeErrorString(&b, e.val)
 | 
			
		||||
	if val != nil {
 | 
			
		||||
		b.WriteString(val)
 | 
			
		||||
	}
 | 
			
		||||
	b.WriteRune('\n')
 | 
			
		||||
 | 
			
		||||
	for _, frame := range e.stack {
 | 
			
		||||
		b.writeASCII("\tat ")
 | 
			
		||||
		frame.WriteToValueBuilder(&b)
 | 
			
		||||
		b.WriteRune('\n')
 | 
			
		||||
	}
 | 
			
		||||
	return b.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) addStackProp() Value {
 | 
			
		||||
	if !e.stackPropAdded {
 | 
			
		||||
		res := e._putProp(propNameStack, e.formatStack(), true, false, true)
 | 
			
		||||
		if len(e.propNames) > 1 {
 | 
			
		||||
			// reorder property names to ensure 'stack' is the first one
 | 
			
		||||
			copy(e.propNames[1:], e.propNames)
 | 
			
		||||
			e.propNames[0] = propNameStack
 | 
			
		||||
		}
 | 
			
		||||
		e.stackPropAdded = true
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) getStr(p unistring.String, receiver Value) Value {
 | 
			
		||||
	return e.getStrWithOwnProp(e.getOwnPropStr(p), p, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	res := e.baseObject.getOwnPropStr(name)
 | 
			
		||||
	if res == nil && name == propNameStack {
 | 
			
		||||
		return e.addStackProp()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if name == propNameStack {
 | 
			
		||||
		e.addStackProp()
 | 
			
		||||
	}
 | 
			
		||||
	return e.baseObject.setOwnStr(name, val, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return e._setForeignStr(name, e.getOwnPropStr(name), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if name == propNameStack {
 | 
			
		||||
		e.addStackProp()
 | 
			
		||||
	}
 | 
			
		||||
	return e.baseObject.deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if name == propNameStack {
 | 
			
		||||
		e.addStackProp()
 | 
			
		||||
	}
 | 
			
		||||
	return e.baseObject.defineOwnPropertyStr(name, desc, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if e.baseObject.hasOwnPropertyStr(name) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return name == propNameStack && !e.stackPropAdded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	if all && !e.stackPropAdded {
 | 
			
		||||
		accum = append(accum, asciiString(propNameStack))
 | 
			
		||||
	}
 | 
			
		||||
	return e.baseObject.stringKeys(all, accum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	e.addStackProp()
 | 
			
		||||
	return e.baseObject.iterateStringKeys()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errorObject) init() {
 | 
			
		||||
	e.baseObject.init()
 | 
			
		||||
	vm := e.val.runtime.vm
 | 
			
		||||
	e.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newErrorObject(proto *Object, class string) *errorObject {
 | 
			
		||||
	obj := &Object{runtime: r}
 | 
			
		||||
	o := &errorObject{
 | 
			
		||||
		baseObject: baseObject{
 | 
			
		||||
			class:      class,
 | 
			
		||||
			val:        obj,
 | 
			
		||||
			extensible: true,
 | 
			
		||||
			prototype:  proto,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	obj.self = o
 | 
			
		||||
	o.init()
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
 | 
			
		||||
	obj := r.newErrorObject(proto, classError)
 | 
			
		||||
	if len(args) > 0 && args[0] != _undefined {
 | 
			
		||||
		obj._putProp("message", args[0].ToString(), true, false, true)
 | 
			
		||||
	}
 | 
			
		||||
	if len(args) > 1 && args[1] != _undefined {
 | 
			
		||||
		if options, ok := args[1].(*Object); ok {
 | 
			
		||||
			if options.hasProperty(asciiString("cause")) {
 | 
			
		||||
				obj.defineOwnPropertyStr("cause", PropertyDescriptor{
 | 
			
		||||
					Writable:     FLAG_TRUE,
 | 
			
		||||
					Enumerable:   FLAG_FALSE,
 | 
			
		||||
					Configurable: FLAG_TRUE,
 | 
			
		||||
					Value:        options.Get("cause"),
 | 
			
		||||
				}, true)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return obj.val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_AggregateError(args []Value, proto *Object) *Object {
 | 
			
		||||
	obj := r.newErrorObject(proto, classError)
 | 
			
		||||
	if len(args) > 1 && args[1] != nil && args[1] != _undefined {
 | 
			
		||||
		obj._putProp("message", args[1].toString(), true, false, true)
 | 
			
		||||
	}
 | 
			
		||||
	var errors []Value
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		errors = r.iterableToList(args[0], nil)
 | 
			
		||||
	}
 | 
			
		||||
	obj._putProp("errors", r.newArrayValues(errors), true, false, true)
 | 
			
		||||
 | 
			
		||||
	if len(args) > 2 && args[2] != _undefined {
 | 
			
		||||
		if options, ok := args[2].(*Object); ok {
 | 
			
		||||
			if options.hasProperty(asciiString("cause")) {
 | 
			
		||||
				obj.defineOwnPropertyStr("cause", PropertyDescriptor{
 | 
			
		||||
					Writable:     FLAG_TRUE,
 | 
			
		||||
					Enumerable:   FLAG_FALSE,
 | 
			
		||||
					Configurable: FLAG_TRUE,
 | 
			
		||||
					Value:        options.Get("cause"),
 | 
			
		||||
				}, true)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return obj.val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeErrorString(sb *StringBuilder, obj *Object) String {
 | 
			
		||||
	var nameStr, msgStr String
 | 
			
		||||
	name := obj.self.getStr("name", nil)
 | 
			
		||||
	if name == nil || name == _undefined {
 | 
			
		||||
		nameStr = asciiString("Error")
 | 
			
		||||
	} else {
 | 
			
		||||
		nameStr = name.toString()
 | 
			
		||||
	}
 | 
			
		||||
	msg := obj.self.getStr("message", nil)
 | 
			
		||||
	if msg == nil || msg == _undefined {
 | 
			
		||||
		msgStr = stringEmpty
 | 
			
		||||
	} else {
 | 
			
		||||
		msgStr = msg.toString()
 | 
			
		||||
	}
 | 
			
		||||
	if nameStr.Length() == 0 {
 | 
			
		||||
		return msgStr
 | 
			
		||||
	}
 | 
			
		||||
	if msgStr.Length() == 0 {
 | 
			
		||||
		return nameStr
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString(nameStr)
 | 
			
		||||
	sb.WriteString(asciiString(": "))
 | 
			
		||||
	sb.WriteString(msgStr)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) error_toString(call FunctionCall) Value {
 | 
			
		||||
	var sb StringBuilder
 | 
			
		||||
	val := writeErrorString(&sb, r.toObject(call.This))
 | 
			
		||||
	if val != nil {
 | 
			
		||||
		return val
 | 
			
		||||
	}
 | 
			
		||||
	return sb.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createErrorPrototype(name String, ctor *Object) *Object {
 | 
			
		||||
	o := r.newBaseObject(r.getErrorPrototype(), classObject)
 | 
			
		||||
	o._putProp("message", stringEmpty, true, false, true)
 | 
			
		||||
	o._putProp("name", name, true, false, true)
 | 
			
		||||
	o._putProp("constructor", ctor, true, false, true)
 | 
			
		||||
	return o.val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getErrorPrototype() *Object {
 | 
			
		||||
	ret := r.global.ErrorPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = r.NewObject()
 | 
			
		||||
		r.global.ErrorPrototype = ret
 | 
			
		||||
		o := ret.self
 | 
			
		||||
		o._putProp("message", stringEmpty, true, false, true)
 | 
			
		||||
		o._putProp("name", stringError, true, false, true)
 | 
			
		||||
		o._putProp("toString", r.newNativeFunc(r.error_toString, "toString", 0), true, false, true)
 | 
			
		||||
		o._putProp("constructor", r.getError(), true, false, true)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getError() *Object {
 | 
			
		||||
	ret := r.global.Error
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Error = ret
 | 
			
		||||
		r.newNativeFuncConstruct(ret, r.builtin_Error, "Error", r.getErrorPrototype(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getAggregateError() *Object {
 | 
			
		||||
	ret := r.global.AggregateError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.AggregateError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_AggregateError, "AggregateError", r.createErrorPrototype(stringAggregateError, ret), r.getError(), 2)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getTypeError() *Object {
 | 
			
		||||
	ret := r.global.TypeError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.TypeError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "TypeError", r.createErrorPrototype(stringTypeError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getReferenceError() *Object {
 | 
			
		||||
	ret := r.global.ReferenceError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.ReferenceError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "ReferenceError", r.createErrorPrototype(stringReferenceError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getSyntaxError() *Object {
 | 
			
		||||
	ret := r.global.SyntaxError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.SyntaxError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "SyntaxError", r.createErrorPrototype(stringSyntaxError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getRangeError() *Object {
 | 
			
		||||
	ret := r.global.RangeError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.RangeError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "RangeError", r.createErrorPrototype(stringRangeError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getEvalError() *Object {
 | 
			
		||||
	ret := r.global.EvalError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.EvalError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "EvalError", r.createErrorPrototype(stringEvalError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getURIError() *Object {
 | 
			
		||||
	ret := r.global.URIError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.URIError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "URIError", r.createErrorPrototype(stringURIError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getGoError() *Object {
 | 
			
		||||
	ret := r.global.GoError
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.GoError = ret
 | 
			
		||||
		r.newNativeFuncConstructProto(ret, r.builtin_Error, "GoError", r.createErrorPrototype(stringGoError, ret), r.getError(), 1)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										416
									
								
								goja/builtin_function.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								goja/builtin_function.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,416 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) functionCtor(args []Value, proto *Object, async, generator bool) *Object {
 | 
			
		||||
	var sb StringBuilder
 | 
			
		||||
	if async {
 | 
			
		||||
		if generator {
 | 
			
		||||
			sb.WriteString(asciiString("(async function* anonymous("))
 | 
			
		||||
		} else {
 | 
			
		||||
			sb.WriteString(asciiString("(async function anonymous("))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if generator {
 | 
			
		||||
			sb.WriteString(asciiString("(function* anonymous("))
 | 
			
		||||
		} else {
 | 
			
		||||
			sb.WriteString(asciiString("(function anonymous("))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(args) > 1 {
 | 
			
		||||
		ar := args[:len(args)-1]
 | 
			
		||||
		for i, arg := range ar {
 | 
			
		||||
			sb.WriteString(arg.toString())
 | 
			
		||||
			if i < len(ar)-1 {
 | 
			
		||||
				sb.WriteRune(',')
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString(asciiString("\n) {\n"))
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		sb.WriteString(args[len(args)-1].toString())
 | 
			
		||||
	}
 | 
			
		||||
	sb.WriteString(asciiString("\n})"))
 | 
			
		||||
 | 
			
		||||
	ret := r.toObject(r.eval(sb.String(), false, false))
 | 
			
		||||
	ret.self.setProto(proto, true)
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object {
 | 
			
		||||
	return r.functionCtor(args, proto, false, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_asyncFunction(args []Value, proto *Object) *Object {
 | 
			
		||||
	return r.functionCtor(args, proto, true, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_generatorFunction(args []Value, proto *Object) *Object {
 | 
			
		||||
	return r.functionCtor(args, proto, false, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) functionproto_toString(call FunctionCall) Value {
 | 
			
		||||
	obj := r.toObject(call.This)
 | 
			
		||||
	switch f := obj.self.(type) {
 | 
			
		||||
	case funcObjectImpl:
 | 
			
		||||
		return f.source()
 | 
			
		||||
	case *proxyObject:
 | 
			
		||||
		if _, ok := f.target.self.(funcObjectImpl); ok {
 | 
			
		||||
			return asciiString("function () { [native code] }")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Function.prototype.toString requires that 'this' be a Function"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) functionproto_hasInstance(call FunctionCall) Value {
 | 
			
		||||
	if o, ok := call.This.(*Object); ok {
 | 
			
		||||
		if _, ok = o.self.assertCallable(); ok {
 | 
			
		||||
			return r.toBoolean(o.self.hasInstance(call.Argument(0)))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createListFromArrayLike(a Value) []Value {
 | 
			
		||||
	o := r.toObject(a)
 | 
			
		||||
	if arr := r.checkStdArrayObj(o); arr != nil {
 | 
			
		||||
		return arr.values
 | 
			
		||||
	}
 | 
			
		||||
	l := toLength(o.self.getStr("length", nil))
 | 
			
		||||
	res := make([]Value, 0, l)
 | 
			
		||||
	for k := int64(0); k < l; k++ {
 | 
			
		||||
		res = append(res, nilSafe(o.self.getIdx(valueInt(k), nil)))
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) functionproto_apply(call FunctionCall) Value {
 | 
			
		||||
	var args []Value
 | 
			
		||||
	if len(call.Arguments) >= 2 {
 | 
			
		||||
		args = r.createListFromArrayLike(call.Arguments[1])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	f := r.toCallable(call.This)
 | 
			
		||||
	return f(FunctionCall{
 | 
			
		||||
		This:      call.Argument(0),
 | 
			
		||||
		Arguments: args,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) functionproto_call(call FunctionCall) Value {
 | 
			
		||||
	var args []Value
 | 
			
		||||
	if len(call.Arguments) > 0 {
 | 
			
		||||
		args = call.Arguments[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	f := r.toCallable(call.This)
 | 
			
		||||
	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(f *Object, target func([]Value, *Object) *Object, boundArgs []Value) func([]Value, *Object) *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, newTarget *Object) *Object {
 | 
			
		||||
		a := append(args, fargs...)
 | 
			
		||||
		if newTarget == f {
 | 
			
		||||
			newTarget = nil
 | 
			
		||||
		}
 | 
			
		||||
		return target(a, newTarget)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) functionproto_bind(call FunctionCall) Value {
 | 
			
		||||
	obj := r.toObject(call.This)
 | 
			
		||||
 | 
			
		||||
	fcall := r.toCallable(call.This)
 | 
			
		||||
	construct := obj.self.assertConstructor()
 | 
			
		||||
 | 
			
		||||
	var l = _positiveZero
 | 
			
		||||
	if obj.self.hasOwnPropertyStr("length") {
 | 
			
		||||
		var li int64
 | 
			
		||||
		switch lenProp := nilSafe(obj.self.getStr("length", nil)).(type) {
 | 
			
		||||
		case valueInt:
 | 
			
		||||
			li = lenProp.ToInteger()
 | 
			
		||||
		case valueFloat:
 | 
			
		||||
			switch lenProp {
 | 
			
		||||
			case _positiveInf:
 | 
			
		||||
				l = lenProp
 | 
			
		||||
				goto lenNotInt
 | 
			
		||||
			case _negativeInf:
 | 
			
		||||
				goto lenNotInt
 | 
			
		||||
			case _negativeZero:
 | 
			
		||||
				// no-op, li == 0
 | 
			
		||||
			default:
 | 
			
		||||
				if !math.IsNaN(float64(lenProp)) {
 | 
			
		||||
					li = int64(math.Abs(float64(lenProp)))
 | 
			
		||||
				} // else li = 0
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(call.Arguments) > 1 {
 | 
			
		||||
			li -= int64(len(call.Arguments)) - 1
 | 
			
		||||
		}
 | 
			
		||||
		if li < 0 {
 | 
			
		||||
			li = 0
 | 
			
		||||
		}
 | 
			
		||||
		l = intToValue(li)
 | 
			
		||||
	}
 | 
			
		||||
lenNotInt:
 | 
			
		||||
	name := obj.self.getStr("name", nil)
 | 
			
		||||
	nameStr := stringBound_
 | 
			
		||||
	if s, ok := name.(String); ok {
 | 
			
		||||
		nameStr = nameStr.Concat(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v := &Object{runtime: r}
 | 
			
		||||
	ff := r.newNativeFuncAndConstruct(v, r.boundCallable(fcall, call.Arguments), r.boundConstruct(v, construct, call.Arguments), nil, nameStr.string(), l)
 | 
			
		||||
	bf := &boundFuncObject{
 | 
			
		||||
		nativeFuncObject: *ff,
 | 
			
		||||
		wrapped:          obj,
 | 
			
		||||
	}
 | 
			
		||||
	bf.prototype = obj.self.proto()
 | 
			
		||||
	v.self = bf
 | 
			
		||||
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getThrower() *Object {
 | 
			
		||||
	ret := r.global.thrower
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = r.newNativeFunc(r.builtin_thrower, "", 0)
 | 
			
		||||
		r.global.thrower = ret
 | 
			
		||||
		r.object_freeze(FunctionCall{Arguments: []Value{ret}})
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newThrowerProperty(configurable bool) Value {
 | 
			
		||||
	thrower := r.getThrower()
 | 
			
		||||
	return &valueProperty{
 | 
			
		||||
		getterFunc:   thrower,
 | 
			
		||||
		setterFunc:   thrower,
 | 
			
		||||
		accessor:     true,
 | 
			
		||||
		configurable: configurable,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createFunctionProtoTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.global.ObjectPrototype
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("length", func(r *Runtime) Value { return valueProp(_positiveZero, false, false, true) })
 | 
			
		||||
	t.putStr("name", func(r *Runtime) Value { return valueProp(stringEmpty, false, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("apply", func(r *Runtime) Value { return r.methodProp(r.functionproto_apply, "apply", 2) })
 | 
			
		||||
	t.putStr("bind", func(r *Runtime) Value { return r.methodProp(r.functionproto_bind, "bind", 1) })
 | 
			
		||||
	t.putStr("call", func(r *Runtime) Value { return r.methodProp(r.functionproto_call, "call", 1) })
 | 
			
		||||
	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.functionproto_toString, "toString", 0) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("caller", func(r *Runtime) Value { return r.newThrowerProperty(true) })
 | 
			
		||||
	t.putStr("arguments", func(r *Runtime) Value { return r.newThrowerProperty(true) })
 | 
			
		||||
 | 
			
		||||
	t.putSym(SymHasInstance, func(r *Runtime) Value {
 | 
			
		||||
		return valueProp(r.newNativeFunc(r.functionproto_hasInstance, "[Symbol.hasInstance]", 1), false, false, false)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var functionProtoTemplate *objectTemplate
 | 
			
		||||
var functionProtoTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getFunctionProtoTemplate() *objectTemplate {
 | 
			
		||||
	functionProtoTemplateOnce.Do(func() {
 | 
			
		||||
		functionProtoTemplate = createFunctionProtoTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return functionProtoTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getFunctionPrototype() *Object {
 | 
			
		||||
	ret := r.global.FunctionPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.FunctionPrototype = ret
 | 
			
		||||
		r.newTemplatedFuncObject(getFunctionProtoTemplate(), ret, func(FunctionCall) Value {
 | 
			
		||||
			return _undefined
 | 
			
		||||
		}, nil)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createFunction(v *Object) objectImpl {
 | 
			
		||||
	return r.newNativeFuncConstructObj(v, r.builtin_Function, "Function", r.getFunctionPrototype(), 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createAsyncFunctionProto(val *Object) objectImpl {
 | 
			
		||||
	o := &baseObject{
 | 
			
		||||
		class:      classObject,
 | 
			
		||||
		val:        val,
 | 
			
		||||
		extensible: true,
 | 
			
		||||
		prototype:  r.getFunctionPrototype(),
 | 
			
		||||
	}
 | 
			
		||||
	o.init()
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getAsyncFunction(), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classAsyncFunction), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getAsyncFunctionPrototype() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.AsyncFunctionPrototype; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.AsyncFunctionPrototype = o
 | 
			
		||||
		o.self = r.createAsyncFunctionProto(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createAsyncFunction(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeFuncConstructObj(val, r.builtin_asyncFunction, "AsyncFunction", r.getAsyncFunctionPrototype(), 1)
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getAsyncFunction() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.AsyncFunction; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.AsyncFunction = o
 | 
			
		||||
		o.self = r.createAsyncFunction(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_genproto_next(call FunctionCall) Value {
 | 
			
		||||
	if o, ok := call.This.(*Object); ok {
 | 
			
		||||
		if gen, ok := o.self.(*generatorObject); ok {
 | 
			
		||||
			return gen.next(call.Argument(0))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Method [Generator].prototype.next called on incompatible receiver"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_genproto_return(call FunctionCall) Value {
 | 
			
		||||
	if o, ok := call.This.(*Object); ok {
 | 
			
		||||
		if gen, ok := o.self.(*generatorObject); ok {
 | 
			
		||||
			return gen._return(call.Argument(0))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Method [Generator].prototype.return called on incompatible receiver"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_genproto_throw(call FunctionCall) Value {
 | 
			
		||||
	if o, ok := call.This.(*Object); ok {
 | 
			
		||||
		if gen, ok := o.self.(*generatorObject); ok {
 | 
			
		||||
			return gen.throw(call.Argument(0))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Method [Generator].prototype.throw called on incompatible receiver"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createGeneratorFunctionProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.getFunctionPrototype(), classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getGeneratorFunction(), false, false, true)
 | 
			
		||||
	o._putProp("prototype", r.getGeneratorPrototype(), false, false, true)
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classGeneratorFunction), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getGeneratorFunctionPrototype() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.GeneratorFunctionPrototype; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.GeneratorFunctionPrototype = o
 | 
			
		||||
		o.self = r.createGeneratorFunctionProto(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createGeneratorFunction(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeFuncConstructObj(val, r.builtin_generatorFunction, "GeneratorFunction", r.getGeneratorFunctionPrototype(), 1)
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getGeneratorFunction() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.GeneratorFunction; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.GeneratorFunction = o
 | 
			
		||||
		o.self = r.createGeneratorFunction(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createGeneratorProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getGeneratorFunctionPrototype(), false, false, true)
 | 
			
		||||
	o._putProp("next", r.newNativeFunc(r.builtin_genproto_next, "next", 1), true, false, true)
 | 
			
		||||
	o._putProp("return", r.newNativeFunc(r.builtin_genproto_return, "return", 1), true, false, true)
 | 
			
		||||
	o._putProp("throw", r.newNativeFunc(r.builtin_genproto_throw, "throw", 1), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classGenerator), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getGeneratorPrototype() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.GeneratorPrototype; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.GeneratorPrototype = o
 | 
			
		||||
		o.self = r.createGeneratorProto(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getFunction() *Object {
 | 
			
		||||
	ret := r.global.Function
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Function = ret
 | 
			
		||||
		ret.self = r.createFunction(ret)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								goja/builtin_function_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								goja/builtin_function_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHashbangInFunctionConstructor(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert.throws(SyntaxError, function() {
 | 
			
		||||
		new Function("#!")
 | 
			
		||||
	});
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										576
									
								
								goja/builtin_global.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										576
									
								
								goja/builtin_global.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,576 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const hexUpper = "0123456789ABCDEF"
 | 
			
		||||
 | 
			
		||||
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 String, unescaped *[256]bool) String {
 | 
			
		||||
	reader := uriString.Reader()
 | 
			
		||||
	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.getURIError(), "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()
 | 
			
		||||
	for {
 | 
			
		||||
		rn, _, err := reader.ReadRune()
 | 
			
		||||
		if err == io.EOF {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if rn >= utf8.RuneSelf {
 | 
			
		||||
			n := utf8.EncodeRune(utf8Buf, rn)
 | 
			
		||||
			for _, b := range utf8Buf[:n] {
 | 
			
		||||
				buf[i] = '%'
 | 
			
		||||
				buf[i+1] = hexUpper[b>>4]
 | 
			
		||||
				buf[i+2] = hexUpper[b&15]
 | 
			
		||||
				i += 3
 | 
			
		||||
			}
 | 
			
		||||
		} else if !unescaped[rn] {
 | 
			
		||||
			buf[i] = '%'
 | 
			
		||||
			buf[i+1] = hexUpper[rn>>4]
 | 
			
		||||
			buf[i+2] = hexUpper[rn&15]
 | 
			
		||||
			i += 3
 | 
			
		||||
		} else {
 | 
			
		||||
			buf[i] = byte(rn)
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return asciiString(buf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String {
 | 
			
		||||
	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.getURIError(), "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.getURIError(), "Malformed URI"))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		us = append(us, rn)
 | 
			
		||||
		t = t[size:]
 | 
			
		||||
	}
 | 
			
		||||
	return unicodeStringFromRunes(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) builtin_escape(call FunctionCall) Value {
 | 
			
		||||
	s := call.Argument(0).toString()
 | 
			
		||||
	var sb strings.Builder
 | 
			
		||||
	l := s.Length()
 | 
			
		||||
	for i := 0; i < l; i++ {
 | 
			
		||||
		r := s.CharAt(i)
 | 
			
		||||
		if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' ||
 | 
			
		||||
			r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' {
 | 
			
		||||
			sb.WriteByte(byte(r))
 | 
			
		||||
		} else if r <= 0xff {
 | 
			
		||||
			sb.WriteByte('%')
 | 
			
		||||
			sb.WriteByte(hexUpper[r>>4])
 | 
			
		||||
			sb.WriteByte(hexUpper[r&0xf])
 | 
			
		||||
		} else {
 | 
			
		||||
			sb.WriteString("%u")
 | 
			
		||||
			sb.WriteByte(hexUpper[r>>12])
 | 
			
		||||
			sb.WriteByte(hexUpper[(r>>8)&0xf])
 | 
			
		||||
			sb.WriteByte(hexUpper[(r>>4)&0xf])
 | 
			
		||||
			sb.WriteByte(hexUpper[r&0xf])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return asciiString(sb.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_unescape(call FunctionCall) Value {
 | 
			
		||||
	s := call.Argument(0).toString()
 | 
			
		||||
	l := s.Length()
 | 
			
		||||
	var asciiBuf []byte
 | 
			
		||||
	var unicodeBuf []uint16
 | 
			
		||||
	_, u := devirtualizeString(s)
 | 
			
		||||
	unicode := u != nil
 | 
			
		||||
	if unicode {
 | 
			
		||||
		unicodeBuf = make([]uint16, 1, l+1)
 | 
			
		||||
		unicodeBuf[0] = unistring.BOM
 | 
			
		||||
	} else {
 | 
			
		||||
		asciiBuf = make([]byte, 0, l)
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < l; {
 | 
			
		||||
		r := s.CharAt(i)
 | 
			
		||||
		if r == '%' {
 | 
			
		||||
			if i <= l-6 && s.CharAt(i+1) == 'u' {
 | 
			
		||||
				c0 := s.CharAt(i + 2)
 | 
			
		||||
				c1 := s.CharAt(i + 3)
 | 
			
		||||
				c2 := s.CharAt(i + 4)
 | 
			
		||||
				c3 := s.CharAt(i + 5)
 | 
			
		||||
				if c0 <= 0xff && ishex(byte(c0)) &&
 | 
			
		||||
					c1 <= 0xff && ishex(byte(c1)) &&
 | 
			
		||||
					c2 <= 0xff && ishex(byte(c2)) &&
 | 
			
		||||
					c3 <= 0xff && ishex(byte(c3)) {
 | 
			
		||||
					r = uint16(unhex(byte(c0)))<<12 |
 | 
			
		||||
						uint16(unhex(byte(c1)))<<8 |
 | 
			
		||||
						uint16(unhex(byte(c2)))<<4 |
 | 
			
		||||
						uint16(unhex(byte(c3)))
 | 
			
		||||
					i += 5
 | 
			
		||||
					goto out
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if i <= l-3 {
 | 
			
		||||
				c0 := s.CharAt(i + 1)
 | 
			
		||||
				c1 := s.CharAt(i + 2)
 | 
			
		||||
				if c0 <= 0xff && ishex(byte(c0)) &&
 | 
			
		||||
					c1 <= 0xff && ishex(byte(c1)) {
 | 
			
		||||
					r = uint16(unhex(byte(c0))<<4 | unhex(byte(c1)))
 | 
			
		||||
					i += 2
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	out:
 | 
			
		||||
		if r >= utf8.RuneSelf && !unicode {
 | 
			
		||||
			unicodeBuf = make([]uint16, 1, l+1)
 | 
			
		||||
			unicodeBuf[0] = unistring.BOM
 | 
			
		||||
			for _, b := range asciiBuf {
 | 
			
		||||
				unicodeBuf = append(unicodeBuf, uint16(b))
 | 
			
		||||
			}
 | 
			
		||||
			asciiBuf = nil
 | 
			
		||||
			unicode = true
 | 
			
		||||
		}
 | 
			
		||||
		if unicode {
 | 
			
		||||
			unicodeBuf = append(unicodeBuf, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			asciiBuf = append(asciiBuf, byte(r))
 | 
			
		||||
		}
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if unicode {
 | 
			
		||||
		return unicodeString(unicodeBuf)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return asciiString(asciiBuf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createGlobalObjectTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.global.ObjectPrototype
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("Object", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) })
 | 
			
		||||
	t.putStr("Function", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) })
 | 
			
		||||
	t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) })
 | 
			
		||||
	t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) })
 | 
			
		||||
	t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) })
 | 
			
		||||
	t.putStr("BigInt", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) })
 | 
			
		||||
	t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) })
 | 
			
		||||
	t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) })
 | 
			
		||||
	t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) })
 | 
			
		||||
	t.putStr("Proxy", func(r *Runtime) Value { return valueProp(r.getProxy(), true, false, true) })
 | 
			
		||||
	t.putStr("Reflect", func(r *Runtime) Value { return valueProp(r.getReflect(), true, false, true) })
 | 
			
		||||
	t.putStr("Error", func(r *Runtime) Value { return valueProp(r.getError(), true, false, true) })
 | 
			
		||||
	t.putStr("AggregateError", func(r *Runtime) Value { return valueProp(r.getAggregateError(), true, false, true) })
 | 
			
		||||
	t.putStr("TypeError", func(r *Runtime) Value { return valueProp(r.getTypeError(), true, false, true) })
 | 
			
		||||
	t.putStr("ReferenceError", func(r *Runtime) Value { return valueProp(r.getReferenceError(), true, false, true) })
 | 
			
		||||
	t.putStr("SyntaxError", func(r *Runtime) Value { return valueProp(r.getSyntaxError(), true, false, true) })
 | 
			
		||||
	t.putStr("RangeError", func(r *Runtime) Value { return valueProp(r.getRangeError(), true, false, true) })
 | 
			
		||||
	t.putStr("EvalError", func(r *Runtime) Value { return valueProp(r.getEvalError(), true, false, true) })
 | 
			
		||||
	t.putStr("URIError", func(r *Runtime) Value { return valueProp(r.getURIError(), true, false, true) })
 | 
			
		||||
	t.putStr("GoError", func(r *Runtime) Value { return valueProp(r.getGoError(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("eval", func(r *Runtime) Value { return valueProp(r.getEval(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("Math", func(r *Runtime) Value { return valueProp(r.getMath(), true, false, true) })
 | 
			
		||||
	t.putStr("JSON", func(r *Runtime) Value { return valueProp(r.getJSON(), true, false, true) })
 | 
			
		||||
	addTypedArrays(t)
 | 
			
		||||
	t.putStr("Symbol", func(r *Runtime) Value { return valueProp(r.getSymbol(), true, false, true) })
 | 
			
		||||
	t.putStr("WeakSet", func(r *Runtime) Value { return valueProp(r.getWeakSet(), true, false, true) })
 | 
			
		||||
	t.putStr("WeakMap", func(r *Runtime) Value { return valueProp(r.getWeakMap(), true, false, true) })
 | 
			
		||||
	t.putStr("Map", func(r *Runtime) Value { return valueProp(r.getMap(), true, false, true) })
 | 
			
		||||
	t.putStr("Set", func(r *Runtime) Value { return valueProp(r.getSet(), true, false, true) })
 | 
			
		||||
	t.putStr("Promise", func(r *Runtime) Value { return valueProp(r.getPromise(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("globalThis", func(r *Runtime) Value { return valueProp(r.globalObject, true, false, true) })
 | 
			
		||||
	t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) })
 | 
			
		||||
	t.putStr("undefined", func(r *Runtime) Value { return valueProp(_undefined, false, false, false) })
 | 
			
		||||
	t.putStr("Infinity", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.builtin_isNaN, "isNaN", 1) })
 | 
			
		||||
	t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) })
 | 
			
		||||
	t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) })
 | 
			
		||||
	t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.builtin_isFinite, "isFinite", 1) })
 | 
			
		||||
	t.putStr("decodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURI, "decodeURI", 1) })
 | 
			
		||||
	t.putStr("decodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURIComponent, "decodeURIComponent", 1) })
 | 
			
		||||
	t.putStr("encodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURI, "encodeURI", 1) })
 | 
			
		||||
	t.putStr("encodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURIComponent, "encodeURIComponent", 1) })
 | 
			
		||||
	t.putStr("escape", func(r *Runtime) Value { return r.methodProp(r.builtin_escape, "escape", 1) })
 | 
			
		||||
	t.putStr("unescape", func(r *Runtime) Value { return r.methodProp(r.builtin_unescape, "unescape", 1) })
 | 
			
		||||
 | 
			
		||||
	// TODO: Annex B
 | 
			
		||||
 | 
			
		||||
	t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classGlobal), false, false, true) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var globalObjectTemplate *objectTemplate
 | 
			
		||||
var globalObjectTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getGlobalObjectTemplate() *objectTemplate {
 | 
			
		||||
	globalObjectTemplateOnce.Do(func() {
 | 
			
		||||
		globalObjectTemplate = createGlobalObjectTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return globalObjectTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getEval() *Object {
 | 
			
		||||
	ret := r.global.Eval
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = r.newNativeFunc(r.builtin_eval, "eval", 1)
 | 
			
		||||
		r.global.Eval = ret
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
									
								
								goja/builtin_global_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								goja/builtin_global_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEncodeURI(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	encodeURI('тест')
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(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")
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, newStringValue("http://ru.wikipedia.org/wiki/Юникод"), t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										542
									
								
								goja/builtin_json.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										542
									
								
								goja/builtin_json.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,542 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode/utf16"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const hex = "0123456789abcdef"
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
 | 
			
		||||
	d := json.NewDecoder(strings.NewReader(call.Argument(0).toString().String()))
 | 
			
		||||
 | 
			
		||||
	value, err := r.builtinJSON_decodeValue(d)
 | 
			
		||||
	if errors.Is(err, io.EOF) {
 | 
			
		||||
		panic(r.newError(r.getSyntaxError(), "Unexpected end of JSON input (%v)", err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(r.newError(r.getSyntaxError(), err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if tok, err := d.Token(); err != io.EOF {
 | 
			
		||||
		panic(r.newError(r.getSyntaxError(), "Unexpected token at the end: %v", tok))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var reviver func(FunctionCall) Value
 | 
			
		||||
 | 
			
		||||
	if arg1 := call.Argument(1); arg1 != _undefined {
 | 
			
		||||
		reviver, _ = arg1.ToObject(r).self.assertCallable()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if reviver != nil {
 | 
			
		||||
		root := r.NewObject()
 | 
			
		||||
		createDataPropertyOrThrow(root, stringEmpty, value)
 | 
			
		||||
		return r.builtinJSON_reviveWalk(reviver, root, stringEmpty)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_decodeToken(d *json.Decoder, tok json.Token) (Value, error) {
 | 
			
		||||
	switch tok := tok.(type) {
 | 
			
		||||
	case json.Delim:
 | 
			
		||||
		switch tok {
 | 
			
		||||
		case '{':
 | 
			
		||||
			return r.builtinJSON_decodeObject(d)
 | 
			
		||||
		case '[':
 | 
			
		||||
			return r.builtinJSON_decodeArray(d)
 | 
			
		||||
		}
 | 
			
		||||
	case nil:
 | 
			
		||||
		return _null, nil
 | 
			
		||||
	case string:
 | 
			
		||||
		return newStringValue(tok), nil
 | 
			
		||||
	case float64:
 | 
			
		||||
		return floatToValue(tok), nil
 | 
			
		||||
	case bool:
 | 
			
		||||
		if tok {
 | 
			
		||||
			return valueTrue, nil
 | 
			
		||||
		}
 | 
			
		||||
		return valueFalse, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, fmt.Errorf("Unexpected token (%T): %v", tok, tok)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_decodeValue(d *json.Decoder) (Value, error) {
 | 
			
		||||
	tok, err := d.Token()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return r.builtinJSON_decodeToken(d, tok)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) {
 | 
			
		||||
	object := r.NewObject()
 | 
			
		||||
	for {
 | 
			
		||||
		key, end, err := r.builtinJSON_decodeObjectKey(d)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if end {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		value, err := r.builtinJSON_decodeValue(d)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		object.self._putProp(unistring.NewFromString(key), value, true, true, true)
 | 
			
		||||
	}
 | 
			
		||||
	return object, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_decodeObjectKey(d *json.Decoder) (string, bool, error) {
 | 
			
		||||
	tok, err := d.Token()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", false, err
 | 
			
		||||
	}
 | 
			
		||||
	switch tok := tok.(type) {
 | 
			
		||||
	case json.Delim:
 | 
			
		||||
		if tok == '}' {
 | 
			
		||||
			return "", true, nil
 | 
			
		||||
		}
 | 
			
		||||
	case string:
 | 
			
		||||
		return tok, false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", false, fmt.Errorf("Unexpected token (%T): %v", tok, tok)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_decodeArray(d *json.Decoder) (*Object, error) {
 | 
			
		||||
	var arrayValue []Value
 | 
			
		||||
	for {
 | 
			
		||||
		tok, err := d.Token()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if delim, ok := tok.(json.Delim); ok {
 | 
			
		||||
			if delim == ']' {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		value, err := r.builtinJSON_decodeToken(d, tok)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		arrayValue = append(arrayValue, value)
 | 
			
		||||
	}
 | 
			
		||||
	return r.newArrayValues(arrayValue), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value {
 | 
			
		||||
	value := nilSafe(holder.get(name, nil))
 | 
			
		||||
 | 
			
		||||
	if object, ok := value.(*Object); ok {
 | 
			
		||||
		if isArray(object) {
 | 
			
		||||
			length := toLength(object.self.getStr("length", nil))
 | 
			
		||||
			for index := int64(0); index < length; index++ {
 | 
			
		||||
				name := asciiString(strconv.FormatInt(index, 10))
 | 
			
		||||
				value := r.builtinJSON_reviveWalk(reviver, object, name)
 | 
			
		||||
				if value == _undefined {
 | 
			
		||||
					object.delete(name, false)
 | 
			
		||||
				} else {
 | 
			
		||||
					createDataProperty(object, name, value)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			for _, name := range object.self.stringKeys(false, nil) {
 | 
			
		||||
				value := r.builtinJSON_reviveWalk(reviver, object, name)
 | 
			
		||||
				if value == _undefined {
 | 
			
		||||
					object.self.deleteStr(name.string(), false)
 | 
			
		||||
				} else {
 | 
			
		||||
					createDataProperty(object, name, value)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
	allAscii         bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
 | 
			
		||||
	ctx := _builtinJSON_stringifyContext{
 | 
			
		||||
		r:        r,
 | 
			
		||||
		allAscii: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	replacer, _ := call.Argument(1).(*Object)
 | 
			
		||||
	if replacer != nil {
 | 
			
		||||
		if isArray(replacer) {
 | 
			
		||||
			length := toLength(replacer.self.getStr("length", nil))
 | 
			
		||||
			seen := map[string]bool{}
 | 
			
		||||
			propertyList := make([]Value, length)
 | 
			
		||||
			length = 0
 | 
			
		||||
			for index := range propertyList {
 | 
			
		||||
				var name string
 | 
			
		||||
				value := replacer.self.getIdx(valueInt(int64(index)), nil)
 | 
			
		||||
				switch v := value.(type) {
 | 
			
		||||
				case valueFloat, valueInt, String:
 | 
			
		||||
					name = value.String()
 | 
			
		||||
				case *Object:
 | 
			
		||||
					switch v.self.className() {
 | 
			
		||||
					case classNumber, classString:
 | 
			
		||||
						name = value.String()
 | 
			
		||||
					default:
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				default:
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				if seen[name] {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				seen[name] = true
 | 
			
		||||
				propertyList[length] = newStringValue(name)
 | 
			
		||||
				length += 1
 | 
			
		||||
			}
 | 
			
		||||
			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 oImpl := o.self.(type) {
 | 
			
		||||
			case *primitiveValueObject:
 | 
			
		||||
				switch oImpl.pValue.(type) {
 | 
			
		||||
				case valueInt, valueFloat:
 | 
			
		||||
					spaceValue = o.ToNumber()
 | 
			
		||||
				}
 | 
			
		||||
			case *stringObject:
 | 
			
		||||
				spaceValue = o.ToString()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		isNum := false
 | 
			
		||||
		var num int64
 | 
			
		||||
		if i, ok := spaceValue.(valueInt); ok {
 | 
			
		||||
			num = int64(i)
 | 
			
		||||
			isNum = true
 | 
			
		||||
		} else if f, ok := spaceValue.(valueFloat); 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.(String); ok {
 | 
			
		||||
				str := s.String()
 | 
			
		||||
				if len(str) > 10 {
 | 
			
		||||
					ctx.gap = str[:10]
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.gap = str
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.do(call.Argument(0)) {
 | 
			
		||||
		if ctx.allAscii {
 | 
			
		||||
			return asciiString(ctx.buf.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			return &importedString{
 | 
			
		||||
				s: ctx.buf.String(),
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *_builtinJSON_stringifyContext) do(v Value) bool {
 | 
			
		||||
	holder := ctx.r.NewObject()
 | 
			
		||||
	createDataPropertyOrThrow(holder, stringEmpty, v)
 | 
			
		||||
	return ctx.str(stringEmpty, holder)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
 | 
			
		||||
	value := nilSafe(holder.get(key, nil))
 | 
			
		||||
 | 
			
		||||
	switch value.(type) {
 | 
			
		||||
	case *Object, *valueBigInt:
 | 
			
		||||
		if toJSON, ok := ctx.r.getVStr(value, "toJSON").(*Object); ok {
 | 
			
		||||
			if c, ok := toJSON.self.assertCallable(); ok {
 | 
			
		||||
				value = c(FunctionCall{
 | 
			
		||||
					This:      value,
 | 
			
		||||
					Arguments: []Value{key},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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:
 | 
			
		||||
			switch pValue := o1.pValue.(type) {
 | 
			
		||||
			case valueInt, valueFloat:
 | 
			
		||||
				value = o.ToNumber()
 | 
			
		||||
			default:
 | 
			
		||||
				value = pValue
 | 
			
		||||
			}
 | 
			
		||||
		case *stringObject:
 | 
			
		||||
			value = o.toString()
 | 
			
		||||
		case *objectGoReflect:
 | 
			
		||||
			if o1.toJson != nil {
 | 
			
		||||
				value = ctx.r.ToValue(o1.toJson())
 | 
			
		||||
			} else if v, ok := o1.origValue.Interface().(json.Marshaler); ok {
 | 
			
		||||
				b, err := v.MarshalJSON()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					panic(ctx.r.NewGoError(err))
 | 
			
		||||
				}
 | 
			
		||||
				ctx.buf.Write(b)
 | 
			
		||||
				ctx.allAscii = false
 | 
			
		||||
				return true
 | 
			
		||||
			} else {
 | 
			
		||||
				switch o1.className() {
 | 
			
		||||
				case classNumber:
 | 
			
		||||
					value = o1.val.ordinaryToPrimitiveNumber()
 | 
			
		||||
				case classString:
 | 
			
		||||
					value = o1.val.ordinaryToPrimitiveString()
 | 
			
		||||
				case classBoolean:
 | 
			
		||||
					if o.ToInteger() != 0 {
 | 
			
		||||
						value = valueTrue
 | 
			
		||||
					} else {
 | 
			
		||||
						value = valueFalse
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if o1.exportType() == typeBigInt {
 | 
			
		||||
					value = o1.val.ordinaryToPrimitiveNumber()
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch value1 := value.(type) {
 | 
			
		||||
	case valueBool:
 | 
			
		||||
		if value1 {
 | 
			
		||||
			ctx.buf.WriteString("true")
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.buf.WriteString("false")
 | 
			
		||||
		}
 | 
			
		||||
	case String:
 | 
			
		||||
		ctx.quote(value1)
 | 
			
		||||
	case valueInt:
 | 
			
		||||
		ctx.buf.WriteString(value.String())
 | 
			
		||||
	case valueFloat:
 | 
			
		||||
		if !math.IsNaN(float64(value1)) && !math.IsInf(float64(value1), 0) {
 | 
			
		||||
			ctx.buf.WriteString(value.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.buf.WriteString("null")
 | 
			
		||||
		}
 | 
			
		||||
	case valueNull:
 | 
			
		||||
		ctx.buf.WriteString("null")
 | 
			
		||||
	case *valueBigInt:
 | 
			
		||||
		ctx.r.typeErrorResult(true, "Do not know how to serialize a BigInt")
 | 
			
		||||
	case *Object:
 | 
			
		||||
		for _, object := range ctx.stack {
 | 
			
		||||
			if value1.SameAs(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 := toLength(array.self.getStr("length", nil))
 | 
			
		||||
	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(asciiString(strconv.FormatInt(i, 10)), 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 {
 | 
			
		||||
		props = object.self.stringKeys(false, nil)
 | 
			
		||||
	} 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 String) {
 | 
			
		||||
	ctx.buf.WriteByte('"')
 | 
			
		||||
	reader := &lenientUtf16Decoder{utf16Reader: str.utf16Reader()}
 | 
			
		||||
	for {
 | 
			
		||||
		r, _, err := reader.ReadRune()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		switch r {
 | 
			
		||||
		case '"', '\\':
 | 
			
		||||
			ctx.buf.WriteByte('\\')
 | 
			
		||||
			ctx.buf.WriteByte(byte(r))
 | 
			
		||||
		case 0x08:
 | 
			
		||||
			ctx.buf.WriteString(`\b`)
 | 
			
		||||
		case 0x09:
 | 
			
		||||
			ctx.buf.WriteString(`\t`)
 | 
			
		||||
		case 0x0A:
 | 
			
		||||
			ctx.buf.WriteString(`\n`)
 | 
			
		||||
		case 0x0C:
 | 
			
		||||
			ctx.buf.WriteString(`\f`)
 | 
			
		||||
		case 0x0D:
 | 
			
		||||
			ctx.buf.WriteString(`\r`)
 | 
			
		||||
		default:
 | 
			
		||||
			if r < 0x20 {
 | 
			
		||||
				ctx.buf.WriteString(`\u00`)
 | 
			
		||||
				ctx.buf.WriteByte(hex[r>>4])
 | 
			
		||||
				ctx.buf.WriteByte(hex[r&0xF])
 | 
			
		||||
			} else {
 | 
			
		||||
				if utf16.IsSurrogate(r) {
 | 
			
		||||
					ctx.buf.WriteString(`\u`)
 | 
			
		||||
					ctx.buf.WriteByte(hex[r>>12])
 | 
			
		||||
					ctx.buf.WriteByte(hex[(r>>8)&0xF])
 | 
			
		||||
					ctx.buf.WriteByte(hex[(r>>4)&0xF])
 | 
			
		||||
					ctx.buf.WriteByte(hex[r&0xF])
 | 
			
		||||
				} else {
 | 
			
		||||
					ctx.buf.WriteRune(r)
 | 
			
		||||
					if ctx.allAscii && r >= utf8.RuneSelf {
 | 
			
		||||
						ctx.allAscii = false
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.buf.WriteByte('"')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getJSON() *Object {
 | 
			
		||||
	ret := r.global.JSON
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		JSON := r.newBaseObject(r.global.ObjectPrototype, classObject)
 | 
			
		||||
		ret = JSON.val
 | 
			
		||||
		r.global.JSON = ret
 | 
			
		||||
		JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, "parse", 2), true, false, true)
 | 
			
		||||
		JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, "stringify", 3), true, false, true)
 | 
			
		||||
		JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true))
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								goja/builtin_json_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								goja/builtin_json_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestJSONMarshalObject(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	o := vm.NewObject()
 | 
			
		||||
	o.Set("test", 42)
 | 
			
		||||
	o.Set("testfunc", vm.Get("Error"))
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if string(b) != `{"test":42}` {
 | 
			
		||||
		t.Fatalf("Unexpected value: %s", b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestJSONMarshalGoDate(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	o := vm.NewObject()
 | 
			
		||||
	o.Set("test", time.Unix(86400, 0).UTC())
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if string(b) != `{"test":"1970-01-02T00:00:00Z"}` {
 | 
			
		||||
		t.Fatalf("Unexpected value: %s", b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestJSONMarshalObjectCircular(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	o := vm.NewObject()
 | 
			
		||||
	o.Set("o", o)
 | 
			
		||||
	_, err := json.Marshal(o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected error")
 | 
			
		||||
	}
 | 
			
		||||
	if !strings.HasSuffix(err.Error(), "Converting circular structure to JSON") {
 | 
			
		||||
		t.Fatalf("Unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestJSONStringifyCircularWrappedGo(t *testing.T) {
 | 
			
		||||
	type CircularType struct {
 | 
			
		||||
		Self *CircularType
 | 
			
		||||
	}
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v := CircularType{}
 | 
			
		||||
	v.Self = &v
 | 
			
		||||
	vm.Set("v", &v)
 | 
			
		||||
	_, err := vm.RunString("JSON.stringify(v)")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected error")
 | 
			
		||||
	}
 | 
			
		||||
	if !strings.HasPrefix(err.Error(), "TypeError: Converting circular structure to JSON") {
 | 
			
		||||
		t.Fatalf("Unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestJSONParseReviver(t *testing.T) {
 | 
			
		||||
	// example from
 | 
			
		||||
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	JSON.parse('{"p": 5}', function(key, value) {
 | 
			
		||||
	  return typeof value === 'number'
 | 
			
		||||
        ? value * 2 // return value * 2 for numbers
 | 
			
		||||
	    : value     // return everything else unchanged
 | 
			
		||||
	 })["p"]
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, intToValue(10), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestQuoteMalformedSurrogatePair(t *testing.T) {
 | 
			
		||||
	testScript(`JSON.stringify("\uD800")`, asciiString(`"\ud800"`), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEOFWrapping(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
 | 
			
		||||
	_, err := vm.RunString("JSON.parse('{')")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected error")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.Contains(err.Error(), "Unexpected end of JSON input") {
 | 
			
		||||
		t.Fatalf("Error doesn't contain human-friendly wrapper: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testMarshalJSONErrorStruct struct {
 | 
			
		||||
	e error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *testMarshalJSONErrorStruct) MarshalJSON() ([]byte, error) {
 | 
			
		||||
	return nil, s.e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalJSONError(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v := testMarshalJSONErrorStruct{e: errors.New("test error")}
 | 
			
		||||
	vm.Set("v", &v)
 | 
			
		||||
	_, err := vm.RunString("JSON.stringify(v)")
 | 
			
		||||
	if !errors.Is(err, v.e) {
 | 
			
		||||
		t.Fatalf("Unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkJSONStringify(b *testing.B) {
 | 
			
		||||
	b.StopTimer()
 | 
			
		||||
	vm := New()
 | 
			
		||||
	var createObj func(level int) *Object
 | 
			
		||||
	createObj = func(level int) *Object {
 | 
			
		||||
		o := vm.NewObject()
 | 
			
		||||
		o.Set("field1", "test")
 | 
			
		||||
		o.Set("field2", 42)
 | 
			
		||||
		if level > 0 {
 | 
			
		||||
			level--
 | 
			
		||||
			o.Set("obj1", createObj(level))
 | 
			
		||||
			o.Set("obj2", createObj(level))
 | 
			
		||||
		}
 | 
			
		||||
		return o
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o := createObj(3)
 | 
			
		||||
	json := vm.Get("JSON").(*Object)
 | 
			
		||||
	stringify, _ := AssertFunction(json.Get("stringify"))
 | 
			
		||||
	b.StartTimer()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		stringify(nil, o)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										342
									
								
								goja/builtin_map.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								goja/builtin_map.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,342 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var mapExportType = reflect.TypeOf([][2]interface{}{})
 | 
			
		||||
 | 
			
		||||
type mapObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	m *orderedMap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mapIterObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	iter *orderedMapIter
 | 
			
		||||
	kind iterationKind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *mapIterObject) next() Value {
 | 
			
		||||
	if o.iter == nil {
 | 
			
		||||
		return o.val.runtime.createIterResultObject(_undefined, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry := o.iter.next()
 | 
			
		||||
	if entry == nil {
 | 
			
		||||
		o.iter = nil
 | 
			
		||||
		return o.val.runtime.createIterResultObject(_undefined, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var result Value
 | 
			
		||||
	switch o.kind {
 | 
			
		||||
	case iterationKindKey:
 | 
			
		||||
		result = entry.key
 | 
			
		||||
	case iterationKindValue:
 | 
			
		||||
		result = entry.value
 | 
			
		||||
	default:
 | 
			
		||||
		result = o.val.runtime.newArrayValues([]Value{entry.key, entry.value})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.val.runtime.createIterResultObject(result, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mo *mapObject) init() {
 | 
			
		||||
	mo.baseObject.init()
 | 
			
		||||
	mo.m = newOrderedMap(mo.val.runtime.getHash())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mo *mapObject) exportType() reflect.Type {
 | 
			
		||||
	return mapExportType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mo *mapObject) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	m := make([][2]interface{}, mo.m.size)
 | 
			
		||||
	ctx.put(mo.val, m)
 | 
			
		||||
 | 
			
		||||
	iter := mo.m.newIter()
 | 
			
		||||
	for i := 0; i < len(m); i++ {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		m[i][0] = exportValue(entry.key, ctx)
 | 
			
		||||
		m[i][1] = exportValue(entry.value, ctx)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mo *mapObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	dst.Set(reflect.MakeMap(typ))
 | 
			
		||||
	ctx.putTyped(mo.val, typ, dst.Interface())
 | 
			
		||||
	keyTyp := typ.Key()
 | 
			
		||||
	elemTyp := typ.Elem()
 | 
			
		||||
	iter := mo.m.newIter()
 | 
			
		||||
	r := mo.val.runtime
 | 
			
		||||
	for {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		keyVal := reflect.New(keyTyp).Elem()
 | 
			
		||||
		err := r.toReflectValue(entry.key, keyVal, ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		elemVal := reflect.New(elemTyp).Elem()
 | 
			
		||||
		err = r.toReflectValue(entry.value, elemVal, ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		dst.SetMapIndex(keyVal, elemVal)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_clear(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Map.prototype.clear called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mo.m.clear()
 | 
			
		||||
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_delete(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Map.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.toBoolean(mo.m.remove(call.Argument(0)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_get(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Map.prototype.get called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nilSafe(mo.m.get(call.Argument(0)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_has(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Map.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	if mo.m.has(call.Argument(0)) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_set(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Map.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	mo.m.set(call.Argument(0), call.Argument(1))
 | 
			
		||||
	return call.This
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_entries(call FunctionCall) Value {
 | 
			
		||||
	return r.createMapIterator(call.This, iterationKindKeyValue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_forEach(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Map.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable()
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("object is not a function %s"))
 | 
			
		||||
	}
 | 
			
		||||
	t := call.Argument(1)
 | 
			
		||||
	iter := mo.m.newIter()
 | 
			
		||||
	for {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		callbackFn(FunctionCall{This: t, Arguments: []Value{entry.value, entry.key, thisObj}})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_keys(call FunctionCall) Value {
 | 
			
		||||
	return r.createMapIterator(call.This, iterationKindKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_values(call FunctionCall) Value {
 | 
			
		||||
	return r.createMapIterator(call.This, iterationKindValue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapProto_getSize(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	mo, ok := thisObj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method get Map.prototype.size called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	return intToValue(int64(mo.m.size))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newMap(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget == nil {
 | 
			
		||||
		panic(r.needNew("Map"))
 | 
			
		||||
	}
 | 
			
		||||
	proto := r.getPrototypeFromCtor(newTarget, r.global.Map, r.global.MapPrototype)
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	mo := &mapObject{}
 | 
			
		||||
	mo.class = classObject
 | 
			
		||||
	mo.val = o
 | 
			
		||||
	mo.extensible = true
 | 
			
		||||
	o.self = mo
 | 
			
		||||
	mo.prototype = proto
 | 
			
		||||
	mo.init()
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
 | 
			
		||||
			adder := mo.getStr("set", nil)
 | 
			
		||||
			adderFn := toMethod(adder)
 | 
			
		||||
			if adderFn == nil {
 | 
			
		||||
				panic(r.NewTypeError("Map.set in missing"))
 | 
			
		||||
			}
 | 
			
		||||
			iter := r.getIterator(arg, nil)
 | 
			
		||||
			i0 := valueInt(0)
 | 
			
		||||
			i1 := valueInt(1)
 | 
			
		||||
			if adder == r.global.mapAdder {
 | 
			
		||||
				iter.iterate(func(item Value) {
 | 
			
		||||
					itemObj := r.toObject(item)
 | 
			
		||||
					k := nilSafe(itemObj.self.getIdx(i0, nil))
 | 
			
		||||
					v := nilSafe(itemObj.self.getIdx(i1, nil))
 | 
			
		||||
					mo.m.set(k, v)
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				iter.iterate(func(item Value) {
 | 
			
		||||
					itemObj := r.toObject(item)
 | 
			
		||||
					k := itemObj.self.getIdx(i0, nil)
 | 
			
		||||
					v := itemObj.self.getIdx(i1, nil)
 | 
			
		||||
					adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createMapIterator(mapValue Value, kind iterationKind) Value {
 | 
			
		||||
	obj := r.toObject(mapValue)
 | 
			
		||||
	mapObj, ok := obj.self.(*mapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Object is not a Map"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	mi := &mapIterObject{
 | 
			
		||||
		iter: mapObj.m.newIter(),
 | 
			
		||||
		kind: kind,
 | 
			
		||||
	}
 | 
			
		||||
	mi.class = classObject
 | 
			
		||||
	mi.val = o
 | 
			
		||||
	mi.extensible = true
 | 
			
		||||
	o.self = mi
 | 
			
		||||
	mi.prototype = r.getMapIteratorPrototype()
 | 
			
		||||
	mi.init()
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) mapIterProto_next(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	if iter, ok := thisObj.self.(*mapIterObject); ok {
 | 
			
		||||
		return iter.next()
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Method Map Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createMapProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getMap(), true, false, true)
 | 
			
		||||
	o._putProp("clear", r.newNativeFunc(r.mapProto_clear, "clear", 0), true, false, true)
 | 
			
		||||
	r.global.mapAdder = r.newNativeFunc(r.mapProto_set, "set", 2)
 | 
			
		||||
	o._putProp("set", r.global.mapAdder, true, false, true)
 | 
			
		||||
	o._putProp("delete", r.newNativeFunc(r.mapProto_delete, "delete", 1), true, false, true)
 | 
			
		||||
	o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, "forEach", 1), true, false, true)
 | 
			
		||||
	o._putProp("has", r.newNativeFunc(r.mapProto_has, "has", 1), true, false, true)
 | 
			
		||||
	o._putProp("get", r.newNativeFunc(r.mapProto_get, "get", 1), true, false, true)
 | 
			
		||||
	o.setOwnStr("size", &valueProperty{
 | 
			
		||||
		getterFunc:   r.newNativeFunc(r.mapProto_getSize, "get size", 0),
 | 
			
		||||
		accessor:     true,
 | 
			
		||||
		writable:     true,
 | 
			
		||||
		configurable: true,
 | 
			
		||||
	}, true)
 | 
			
		||||
	o._putProp("keys", r.newNativeFunc(r.mapProto_keys, "keys", 0), true, false, true)
 | 
			
		||||
	o._putProp("values", r.newNativeFunc(r.mapProto_values, "values", 0), true, false, true)
 | 
			
		||||
 | 
			
		||||
	entriesFunc := r.newNativeFunc(r.mapProto_entries, "entries", 0)
 | 
			
		||||
	o._putProp("entries", entriesFunc, true, false, true)
 | 
			
		||||
	o._putSym(SymIterator, valueProp(entriesFunc, true, false, true))
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classMap), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createMap(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeConstructOnly(val, r.builtin_newMap, r.getMapPrototype(), "Map", 0)
 | 
			
		||||
	r.putSpeciesReturnThis(o)
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createMapIterProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("next", r.newNativeFunc(r.mapIterProto_next, "next", 0), true, false, true)
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classMapIterator), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getMapIteratorPrototype() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.MapIteratorPrototype; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.MapIteratorPrototype = o
 | 
			
		||||
		o.self = r.createMapIterProto(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getMapPrototype() *Object {
 | 
			
		||||
	ret := r.global.MapPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.MapPrototype = ret
 | 
			
		||||
		ret.self = r.createMapProto(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getMap() *Object {
 | 
			
		||||
	ret := r.global.Map
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Map = ret
 | 
			
		||||
		ret.self = r.createMap(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										244
									
								
								goja/builtin_map_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								goja/builtin_map_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,244 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/maphash"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMapEvilIterator(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	'use strict';
 | 
			
		||||
	var o = {};
 | 
			
		||||
 | 
			
		||||
	function Iter(value) {
 | 
			
		||||
		this.value = value;
 | 
			
		||||
		this.idx = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Iter.prototype.next = function() {
 | 
			
		||||
		var idx = this.idx;
 | 
			
		||||
		if (idx === 0) {
 | 
			
		||||
			this.idx++;
 | 
			
		||||
			return this.value;
 | 
			
		||||
		}
 | 
			
		||||
		return {done: true};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o[Symbol.iterator] = function() {
 | 
			
		||||
		return new Iter({});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		new Map(o);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	o[Symbol.iterator] = function() {
 | 
			
		||||
		return new Iter({value: []});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function t(prefix) {
 | 
			
		||||
		var m = new Map(o);
 | 
			
		||||
		assert.sameValue(1, m.size, prefix+": m.size");
 | 
			
		||||
		assert.sameValue(true, m.has(undefined), prefix+": m.has(undefined)");
 | 
			
		||||
		assert.sameValue(undefined, m.get(undefined), prefix+": m.get(undefined)");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t("standard adder");
 | 
			
		||||
 | 
			
		||||
	var count = 0;
 | 
			
		||||
	var origSet = Map.prototype.set;
 | 
			
		||||
 | 
			
		||||
	Map.prototype.set = function() {
 | 
			
		||||
		count++;
 | 
			
		||||
		origSet.apply(this, arguments);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t("custom adder");
 | 
			
		||||
	assert.sameValue(1, count, "count");
 | 
			
		||||
 | 
			
		||||
	undefined;
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMapExportToNilMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	var m map[int]interface{}
 | 
			
		||||
	res, err := vm.RunString("new Map([[1, true]])")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = vm.ExportTo(res, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(m) != 1 {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
	if _, exists := m[1]; !exists {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMapExportToNonNilMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[int]interface{}{
 | 
			
		||||
		2: true,
 | 
			
		||||
	}
 | 
			
		||||
	res, err := vm.RunString("new Map([[1, true]])")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = vm.ExportTo(res, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(m) != 1 {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
	if _, exists := m[1]; !exists {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMapGetAdderGetIteratorOrder(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	let getterCalled = 0;
 | 
			
		||||
 | 
			
		||||
	class M extends Map {
 | 
			
		||||
	    get set() {
 | 
			
		||||
	        getterCalled++;
 | 
			
		||||
	        return null;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let getIteratorCalled = 0;
 | 
			
		||||
 | 
			
		||||
	let iterable = {};
 | 
			
		||||
	iterable[Symbol.iterator] = () => {
 | 
			
		||||
	    getIteratorCalled++
 | 
			
		||||
	    return {
 | 
			
		||||
	        next: 1
 | 
			
		||||
	    };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let thrown = false;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
	    new M(iterable);
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
	    if (e instanceof TypeError) {
 | 
			
		||||
	        thrown = true;
 | 
			
		||||
	    } else {
 | 
			
		||||
	        throw e;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	thrown && getterCalled === 1 && getIteratorCalled === 0;
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleObject_Export_map() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m, err := vm.RunString(`
 | 
			
		||||
	new Map([[1, true], [2, false]]);
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	exp := m.Export()
 | 
			
		||||
	fmt.Printf("%T, %v\n", exp, exp)
 | 
			
		||||
	// Output: [][2]interface {}, [[1 true] [2 false]]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_mapToMap() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m, err := vm.RunString(`
 | 
			
		||||
	new Map([[1, true], [2, false]]);
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	exp := make(map[int]bool)
 | 
			
		||||
	err = vm.ExportTo(m, &exp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(exp)
 | 
			
		||||
	// Output: map[1:true 2:false]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_mapToSlice() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m, err := vm.RunString(`
 | 
			
		||||
	new Map([[1, true], [2, false]]);
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	exp := make([][]interface{}, 0)
 | 
			
		||||
	err = vm.ExportTo(m, &exp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(exp)
 | 
			
		||||
	// Output: [[1 true] [2 false]]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_mapToTypedSlice() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m, err := vm.RunString(`
 | 
			
		||||
	new Map([[1, true], [2, false]]);
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	exp := make([][2]interface{}, 0)
 | 
			
		||||
	err = vm.ExportTo(m, &exp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(exp)
 | 
			
		||||
	// Output: [[1 true] [2 false]]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkMapDelete(b *testing.B) {
 | 
			
		||||
	var key1 Value = asciiString("a")
 | 
			
		||||
	var key2 Value = asciiString("b")
 | 
			
		||||
	one := intToValue(1)
 | 
			
		||||
	two := intToValue(2)
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
		m.set(key1, one)
 | 
			
		||||
		m.set(key2, two)
 | 
			
		||||
		if !m.remove(key1) {
 | 
			
		||||
			b.Fatal("remove() returned false")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkMapDeleteJS(b *testing.B) {
 | 
			
		||||
	prg, err := Compile("test.js", `
 | 
			
		||||
	var m = new Map([['a',1], ['b', 2]]);
 | 
			
		||||
	
 | 
			
		||||
	var result = m.delete('a');
 | 
			
		||||
 | 
			
		||||
	if (!result || m.size !== 1) {
 | 
			
		||||
		throw new Error("Fail!");
 | 
			
		||||
	}
 | 
			
		||||
	`,
 | 
			
		||||
		false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		vm := New()
 | 
			
		||||
		_, err := vm.RunProgram(prg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			b.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										358
									
								
								goja/builtin_math.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								goja/builtin_math.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,358 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/bits"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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_acosh(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Acosh(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_asin(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Asin(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_asinh(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Asinh(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_atan(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Atan(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_atanh(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Atanh(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_cbrt(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Cbrt(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_ceil(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Ceil(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_clz32(call FunctionCall) Value {
 | 
			
		||||
	return intToValue(int64(bits.LeadingZeros32(toUint32(call.Argument(0)))))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_cos(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Cos(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_cosh(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Cosh(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_exp(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Exp(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_expm1(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Expm1(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_floor(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Floor(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_fround(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(float64(float32(call.Argument(0).ToFloat())))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_hypot(call FunctionCall) Value {
 | 
			
		||||
	var max float64
 | 
			
		||||
	var hasNaN bool
 | 
			
		||||
	absValues := make([]float64, 0, len(call.Arguments))
 | 
			
		||||
	for _, v := range call.Arguments {
 | 
			
		||||
		arg := nilSafe(v).ToFloat()
 | 
			
		||||
		if math.IsNaN(arg) {
 | 
			
		||||
			hasNaN = true
 | 
			
		||||
		} else {
 | 
			
		||||
			abs := math.Abs(arg)
 | 
			
		||||
			if abs > max {
 | 
			
		||||
				max = abs
 | 
			
		||||
			}
 | 
			
		||||
			absValues = append(absValues, abs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsInf(max, 1) {
 | 
			
		||||
		return _positiveInf
 | 
			
		||||
	}
 | 
			
		||||
	if hasNaN {
 | 
			
		||||
		return _NaN
 | 
			
		||||
	}
 | 
			
		||||
	if max == 0 {
 | 
			
		||||
		return _positiveZero
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Kahan summation to avoid rounding errors.
 | 
			
		||||
	// Normalize the numbers to the largest one to avoid overflow.
 | 
			
		||||
	var sum, compensation float64
 | 
			
		||||
	for _, n := range absValues {
 | 
			
		||||
		n /= max
 | 
			
		||||
		summand := n*n - compensation
 | 
			
		||||
		preliminary := sum + summand
 | 
			
		||||
		compensation = (preliminary - sum) - summand
 | 
			
		||||
		sum = preliminary
 | 
			
		||||
	}
 | 
			
		||||
	return floatToValue(math.Sqrt(sum) * max)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_imul(call FunctionCall) Value {
 | 
			
		||||
	x := toUint32(call.Argument(0))
 | 
			
		||||
	y := toUint32(call.Argument(1))
 | 
			
		||||
	return intToValue(int64(int32(x * y)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_log(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Log(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_log1p(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Log1p(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_log10(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Log10(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_log2(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Log2(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_max(call FunctionCall) Value {
 | 
			
		||||
	result := math.Inf(-1)
 | 
			
		||||
	args := call.Arguments
 | 
			
		||||
	for i, arg := range args {
 | 
			
		||||
		n := nilSafe(arg).ToFloat()
 | 
			
		||||
		if math.IsNaN(n) {
 | 
			
		||||
			args = args[i+1:]
 | 
			
		||||
			goto NaNLoop
 | 
			
		||||
		}
 | 
			
		||||
		result = math.Max(result, n)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return floatToValue(result)
 | 
			
		||||
 | 
			
		||||
NaNLoop:
 | 
			
		||||
	// All arguments still need to be coerced to number according to the specs.
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		nilSafe(arg).ToFloat()
 | 
			
		||||
	}
 | 
			
		||||
	return _NaN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_min(call FunctionCall) Value {
 | 
			
		||||
	result := math.Inf(1)
 | 
			
		||||
	args := call.Arguments
 | 
			
		||||
	for i, arg := range args {
 | 
			
		||||
		n := nilSafe(arg).ToFloat()
 | 
			
		||||
		if math.IsNaN(n) {
 | 
			
		||||
			args = args[i+1:]
 | 
			
		||||
			goto NaNLoop
 | 
			
		||||
		}
 | 
			
		||||
		result = math.Min(result, n)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return floatToValue(result)
 | 
			
		||||
 | 
			
		||||
NaNLoop:
 | 
			
		||||
	// All arguments still need to be coerced to number according to the specs.
 | 
			
		||||
	for _, arg := range args {
 | 
			
		||||
		nilSafe(arg).ToFloat()
 | 
			
		||||
	}
 | 
			
		||||
	return _NaN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func pow(x, y Value) Value {
 | 
			
		||||
	if x, ok := x.(valueInt); ok {
 | 
			
		||||
		if y, ok := y.(valueInt); ok && y >= 0 {
 | 
			
		||||
			if y == 0 {
 | 
			
		||||
				return intToValue(1)
 | 
			
		||||
			}
 | 
			
		||||
			if x == 0 {
 | 
			
		||||
				return intToValue(0)
 | 
			
		||||
			}
 | 
			
		||||
			ip := ipow(int64(x), int64(y))
 | 
			
		||||
			if ip != 0 {
 | 
			
		||||
				return intToValue(ip)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	xf := x.ToFloat()
 | 
			
		||||
	yf := y.ToFloat()
 | 
			
		||||
	if math.Abs(xf) == 1 && math.IsInf(yf, 0) {
 | 
			
		||||
		return _NaN
 | 
			
		||||
	}
 | 
			
		||||
	if xf == 1 && math.IsNaN(yf) {
 | 
			
		||||
		return _NaN
 | 
			
		||||
	}
 | 
			
		||||
	return floatToValue(math.Pow(xf, yf))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_pow(call FunctionCall) Value {
 | 
			
		||||
	return pow(call.Argument(0), call.Argument(1))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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_sign(call FunctionCall) Value {
 | 
			
		||||
	arg := call.Argument(0)
 | 
			
		||||
	num := arg.ToFloat()
 | 
			
		||||
	if math.IsNaN(num) || num == 0 { // this will match -0 too
 | 
			
		||||
		return arg
 | 
			
		||||
	}
 | 
			
		||||
	if num > 0 {
 | 
			
		||||
		return intToValue(1)
 | 
			
		||||
	}
 | 
			
		||||
	return intToValue(-1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_sin(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Sin(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_sinh(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Sinh(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) math_tanh(call FunctionCall) Value {
 | 
			
		||||
	return floatToValue(math.Tanh(call.Argument(0).ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) math_trunc(call FunctionCall) Value {
 | 
			
		||||
	arg := call.Argument(0)
 | 
			
		||||
	if i, ok := arg.(valueInt); ok {
 | 
			
		||||
		return i
 | 
			
		||||
	}
 | 
			
		||||
	return floatToValue(math.Trunc(arg.ToFloat()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createMathTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.global.ObjectPrototype
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("E", func(r *Runtime) Value { return valueProp(valueFloat(math.E), false, false, false) })
 | 
			
		||||
	t.putStr("LN10", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln10), false, false, false) })
 | 
			
		||||
	t.putStr("LN2", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln2), false, false, false) })
 | 
			
		||||
	t.putStr("LOG10E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log10E), false, false, false) })
 | 
			
		||||
	t.putStr("LOG2E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log2E), false, false, false) })
 | 
			
		||||
	t.putStr("PI", func(r *Runtime) Value { return valueProp(valueFloat(math.Pi), false, false, false) })
 | 
			
		||||
	t.putStr("SQRT1_2", func(r *Runtime) Value { return valueProp(valueFloat(sqrt1_2), false, false, false) })
 | 
			
		||||
	t.putStr("SQRT2", func(r *Runtime) Value { return valueProp(valueFloat(math.Sqrt2), false, false, false) })
 | 
			
		||||
 | 
			
		||||
	t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classMath), false, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("abs", func(r *Runtime) Value { return r.methodProp(r.math_abs, "abs", 1) })
 | 
			
		||||
	t.putStr("acos", func(r *Runtime) Value { return r.methodProp(r.math_acos, "acos", 1) })
 | 
			
		||||
	t.putStr("acosh", func(r *Runtime) Value { return r.methodProp(r.math_acosh, "acosh", 1) })
 | 
			
		||||
	t.putStr("asin", func(r *Runtime) Value { return r.methodProp(r.math_asin, "asin", 1) })
 | 
			
		||||
	t.putStr("asinh", func(r *Runtime) Value { return r.methodProp(r.math_asinh, "asinh", 1) })
 | 
			
		||||
	t.putStr("atan", func(r *Runtime) Value { return r.methodProp(r.math_atan, "atan", 1) })
 | 
			
		||||
	t.putStr("atanh", func(r *Runtime) Value { return r.methodProp(r.math_atanh, "atanh", 1) })
 | 
			
		||||
	t.putStr("atan2", func(r *Runtime) Value { return r.methodProp(r.math_atan2, "atan2", 2) })
 | 
			
		||||
	t.putStr("cbrt", func(r *Runtime) Value { return r.methodProp(r.math_cbrt, "cbrt", 1) })
 | 
			
		||||
	t.putStr("ceil", func(r *Runtime) Value { return r.methodProp(r.math_ceil, "ceil", 1) })
 | 
			
		||||
	t.putStr("clz32", func(r *Runtime) Value { return r.methodProp(r.math_clz32, "clz32", 1) })
 | 
			
		||||
	t.putStr("cos", func(r *Runtime) Value { return r.methodProp(r.math_cos, "cos", 1) })
 | 
			
		||||
	t.putStr("cosh", func(r *Runtime) Value { return r.methodProp(r.math_cosh, "cosh", 1) })
 | 
			
		||||
	t.putStr("exp", func(r *Runtime) Value { return r.methodProp(r.math_exp, "exp", 1) })
 | 
			
		||||
	t.putStr("expm1", func(r *Runtime) Value { return r.methodProp(r.math_expm1, "expm1", 1) })
 | 
			
		||||
	t.putStr("floor", func(r *Runtime) Value { return r.methodProp(r.math_floor, "floor", 1) })
 | 
			
		||||
	t.putStr("fround", func(r *Runtime) Value { return r.methodProp(r.math_fround, "fround", 1) })
 | 
			
		||||
	t.putStr("hypot", func(r *Runtime) Value { return r.methodProp(r.math_hypot, "hypot", 2) })
 | 
			
		||||
	t.putStr("imul", func(r *Runtime) Value { return r.methodProp(r.math_imul, "imul", 2) })
 | 
			
		||||
	t.putStr("log", func(r *Runtime) Value { return r.methodProp(r.math_log, "log", 1) })
 | 
			
		||||
	t.putStr("log1p", func(r *Runtime) Value { return r.methodProp(r.math_log1p, "log1p", 1) })
 | 
			
		||||
	t.putStr("log10", func(r *Runtime) Value { return r.methodProp(r.math_log10, "log10", 1) })
 | 
			
		||||
	t.putStr("log2", func(r *Runtime) Value { return r.methodProp(r.math_log2, "log2", 1) })
 | 
			
		||||
	t.putStr("max", func(r *Runtime) Value { return r.methodProp(r.math_max, "max", 2) })
 | 
			
		||||
	t.putStr("min", func(r *Runtime) Value { return r.methodProp(r.math_min, "min", 2) })
 | 
			
		||||
	t.putStr("pow", func(r *Runtime) Value { return r.methodProp(r.math_pow, "pow", 2) })
 | 
			
		||||
	t.putStr("random", func(r *Runtime) Value { return r.methodProp(r.math_random, "random", 0) })
 | 
			
		||||
	t.putStr("round", func(r *Runtime) Value { return r.methodProp(r.math_round, "round", 1) })
 | 
			
		||||
	t.putStr("sign", func(r *Runtime) Value { return r.methodProp(r.math_sign, "sign", 1) })
 | 
			
		||||
	t.putStr("sin", func(r *Runtime) Value { return r.methodProp(r.math_sin, "sin", 1) })
 | 
			
		||||
	t.putStr("sinh", func(r *Runtime) Value { return r.methodProp(r.math_sinh, "sinh", 1) })
 | 
			
		||||
	t.putStr("sqrt", func(r *Runtime) Value { return r.methodProp(r.math_sqrt, "sqrt", 1) })
 | 
			
		||||
	t.putStr("tan", func(r *Runtime) Value { return r.methodProp(r.math_tan, "tan", 1) })
 | 
			
		||||
	t.putStr("tanh", func(r *Runtime) Value { return r.methodProp(r.math_tanh, "tanh", 1) })
 | 
			
		||||
	t.putStr("trunc", func(r *Runtime) Value { return r.methodProp(r.math_trunc, "trunc", 1) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var mathTemplate *objectTemplate
 | 
			
		||||
var mathTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getMathTemplate() *objectTemplate {
 | 
			
		||||
	mathTemplateOnce.Do(func() {
 | 
			
		||||
		mathTemplate = createMathTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return mathTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getMath() *Object {
 | 
			
		||||
	ret := r.global.Math
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Math = ret
 | 
			
		||||
		r.newTemplatedObject(getMathTemplate(), ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										303
									
								
								goja/builtin_number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								goja/builtin_number.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,303 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/ftoa"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) toNumber(v Value) Value {
 | 
			
		||||
	switch t := v.(type) {
 | 
			
		||||
	case valueFloat, valueInt:
 | 
			
		||||
		return v
 | 
			
		||||
	case *Object:
 | 
			
		||||
		switch t := t.self.(type) {
 | 
			
		||||
		case *primitiveValueObject:
 | 
			
		||||
			return r.toNumber(t.pValue)
 | 
			
		||||
		case *objectGoReflect:
 | 
			
		||||
			if t.class == classNumber && t.valueOf != nil {
 | 
			
		||||
				return t.valueOf()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if t == r.global.NumberPrototype {
 | 
			
		||||
			return _positiveZero
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Value is not a number: %s", v))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) numberproto_valueOf(call FunctionCall) Value {
 | 
			
		||||
	return r.toNumber(call.This)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) numberproto_toString(call FunctionCall) Value {
 | 
			
		||||
	var numVal Value
 | 
			
		||||
	switch t := call.This.(type) {
 | 
			
		||||
	case valueFloat, valueInt:
 | 
			
		||||
		numVal = t
 | 
			
		||||
	case *Object:
 | 
			
		||||
		switch t := t.self.(type) {
 | 
			
		||||
		case *primitiveValueObject:
 | 
			
		||||
			numVal = r.toNumber(t.pValue)
 | 
			
		||||
		case *objectGoReflect:
 | 
			
		||||
			if t.class == classNumber {
 | 
			
		||||
				if t.toString != nil {
 | 
			
		||||
					return t.toString()
 | 
			
		||||
				}
 | 
			
		||||
				if t.valueOf != nil {
 | 
			
		||||
					numVal = t.valueOf()
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if t == r.global.NumberPrototype {
 | 
			
		||||
			return asciiString("0")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if numVal == nil {
 | 
			
		||||
		panic(r.NewTypeError("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.getRangeError(), "toString() radix argument must be between 2 and 36"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	num := numVal.ToFloat()
 | 
			
		||||
 | 
			
		||||
	if math.IsNaN(num) {
 | 
			
		||||
		return stringNaN
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if math.IsInf(num, 1) {
 | 
			
		||||
		return stringInfinity
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if math.IsInf(num, -1) {
 | 
			
		||||
		return stringNegInfinity
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if radix == 10 {
 | 
			
		||||
		return asciiString(fToStr(num, ftoa.ModeStandard, 0))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return asciiString(ftoa.FToBaseStr(num, radix))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) numberproto_toFixed(call FunctionCall) Value {
 | 
			
		||||
	num := r.toNumber(call.This).ToFloat()
 | 
			
		||||
	prec := call.Argument(0).ToInteger()
 | 
			
		||||
 | 
			
		||||
	if prec < 0 || prec > 100 {
 | 
			
		||||
		panic(r.newError(r.getRangeError(), "toFixed() precision must be between 0 and 100"))
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsNaN(num) {
 | 
			
		||||
		return stringNaN
 | 
			
		||||
	}
 | 
			
		||||
	return asciiString(fToStr(num, ftoa.ModeFixed, int(prec)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) numberproto_toExponential(call FunctionCall) Value {
 | 
			
		||||
	num := r.toNumber(call.This).ToFloat()
 | 
			
		||||
	precVal := call.Argument(0)
 | 
			
		||||
	var prec int64
 | 
			
		||||
	if precVal == _undefined {
 | 
			
		||||
		return asciiString(fToStr(num, ftoa.ModeStandardExponential, 0))
 | 
			
		||||
	} else {
 | 
			
		||||
		prec = precVal.ToInteger()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if math.IsNaN(num) {
 | 
			
		||||
		return stringNaN
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsInf(num, 1) {
 | 
			
		||||
		return stringInfinity
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsInf(num, -1) {
 | 
			
		||||
		return stringNegInfinity
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if prec < 0 || prec > 100 {
 | 
			
		||||
		panic(r.newError(r.getRangeError(), "toExponential() precision must be between 0 and 100"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return asciiString(fToStr(num, ftoa.ModeExponential, int(prec+1)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) numberproto_toPrecision(call FunctionCall) Value {
 | 
			
		||||
	numVal := r.toNumber(call.This)
 | 
			
		||||
	precVal := call.Argument(0)
 | 
			
		||||
	if precVal == _undefined {
 | 
			
		||||
		return numVal.toString()
 | 
			
		||||
	}
 | 
			
		||||
	num := numVal.ToFloat()
 | 
			
		||||
	prec := precVal.ToInteger()
 | 
			
		||||
 | 
			
		||||
	if math.IsNaN(num) {
 | 
			
		||||
		return stringNaN
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsInf(num, 1) {
 | 
			
		||||
		return stringInfinity
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsInf(num, -1) {
 | 
			
		||||
		return stringNegInfinity
 | 
			
		||||
	}
 | 
			
		||||
	if prec < 1 || prec > 100 {
 | 
			
		||||
		panic(r.newError(r.getRangeError(), "toPrecision() precision must be between 1 and 100"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return asciiString(fToStr(num, ftoa.ModePrecision, int(prec)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) number_isFinite(call FunctionCall) Value {
 | 
			
		||||
	switch arg := call.Argument(0).(type) {
 | 
			
		||||
	case valueInt:
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	case valueFloat:
 | 
			
		||||
		f := float64(arg)
 | 
			
		||||
		return r.toBoolean(!math.IsInf(f, 0) && !math.IsNaN(f))
 | 
			
		||||
	default:
 | 
			
		||||
		return valueFalse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) number_isInteger(call FunctionCall) Value {
 | 
			
		||||
	switch arg := call.Argument(0).(type) {
 | 
			
		||||
	case valueInt:
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	case valueFloat:
 | 
			
		||||
		f := float64(arg)
 | 
			
		||||
		return r.toBoolean(!math.IsNaN(f) && !math.IsInf(f, 0) && math.Floor(f) == f)
 | 
			
		||||
	default:
 | 
			
		||||
		return valueFalse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) number_isNaN(call FunctionCall) Value {
 | 
			
		||||
	if f, ok := call.Argument(0).(valueFloat); ok && math.IsNaN(float64(f)) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) number_isSafeInteger(call FunctionCall) Value {
 | 
			
		||||
	arg := call.Argument(0)
 | 
			
		||||
	if i, ok := arg.(valueInt); ok && i >= -(maxInt-1) && i <= maxInt-1 {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	if arg == _negativeZero {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createNumberProtoTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.global.ObjectPrototype
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("toExponential", func(r *Runtime) Value { return r.methodProp(r.numberproto_toExponential, "toExponential", 1) })
 | 
			
		||||
	t.putStr("toFixed", func(r *Runtime) Value { return r.methodProp(r.numberproto_toFixed, "toFixed", 1) })
 | 
			
		||||
	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toLocaleString", 0) })
 | 
			
		||||
	t.putStr("toPrecision", func(r *Runtime) Value { return r.methodProp(r.numberproto_toPrecision, "toPrecision", 1) })
 | 
			
		||||
	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toString", 1) })
 | 
			
		||||
	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.numberproto_valueOf, "valueOf", 0) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var numberProtoTemplate *objectTemplate
 | 
			
		||||
var numberProtoTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getNumberProtoTemplate() *objectTemplate {
 | 
			
		||||
	numberProtoTemplateOnce.Do(func() {
 | 
			
		||||
		numberProtoTemplate = createNumberProtoTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return numberProtoTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getNumberPrototype() *Object {
 | 
			
		||||
	ret := r.global.NumberPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.NumberPrototype = ret
 | 
			
		||||
		o := r.newTemplatedObject(getNumberProtoTemplate(), ret)
 | 
			
		||||
		o.class = classNumber
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getParseFloat() *Object {
 | 
			
		||||
	ret := r.global.parseFloat
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = r.newNativeFunc(r.builtin_parseFloat, "parseFloat", 1)
 | 
			
		||||
		r.global.parseFloat = ret
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getParseInt() *Object {
 | 
			
		||||
	ret := r.global.parseInt
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = r.newNativeFunc(r.builtin_parseInt, "parseInt", 2)
 | 
			
		||||
		r.global.parseInt = ret
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createNumberTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.getFunctionPrototype()
 | 
			
		||||
	}
 | 
			
		||||
	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
 | 
			
		||||
	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Number"), false, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getNumberPrototype(), false, false, false) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("EPSILON", func(r *Runtime) Value { return valueProp(_epsilon, false, false, false) })
 | 
			
		||||
	t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.number_isFinite, "isFinite", 1) })
 | 
			
		||||
	t.putStr("isInteger", func(r *Runtime) Value { return r.methodProp(r.number_isInteger, "isInteger", 1) })
 | 
			
		||||
	t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.number_isNaN, "isNaN", 1) })
 | 
			
		||||
	t.putStr("isSafeInteger", func(r *Runtime) Value { return r.methodProp(r.number_isSafeInteger, "isSafeInteger", 1) })
 | 
			
		||||
	t.putStr("MAX_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(maxInt-1), false, false, false) })
 | 
			
		||||
	t.putStr("MIN_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(-(maxInt - 1)), false, false, false) })
 | 
			
		||||
	t.putStr("MIN_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.SmallestNonzeroFloat64), false, false, false) })
 | 
			
		||||
	t.putStr("MAX_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.MaxFloat64), false, false, false) })
 | 
			
		||||
	t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) })
 | 
			
		||||
	t.putStr("NEGATIVE_INFINITY", func(r *Runtime) Value { return valueProp(_negativeInf, false, false, false) })
 | 
			
		||||
	t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) })
 | 
			
		||||
	t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) })
 | 
			
		||||
	t.putStr("POSITIVE_INFINITY", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var numberTemplate *objectTemplate
 | 
			
		||||
var numberTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getNumberTemplate() *objectTemplate {
 | 
			
		||||
	numberTemplateOnce.Do(func() {
 | 
			
		||||
		numberTemplate = createNumberTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return numberTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getNumber() *Object {
 | 
			
		||||
	ret := r.global.Number
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Number = ret
 | 
			
		||||
		r.newTemplatedFuncObject(getNumberTemplate(), ret, r.builtin_Number,
 | 
			
		||||
			r.wrapNativeConstruct(r.builtin_newNumber, ret, r.getNumberPrototype()))
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										711
									
								
								goja/builtin_object.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										711
									
								
								goja/builtin_object.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,711 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_Object(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget != nil && newTarget != r.getObject() {
 | 
			
		||||
		proto := r.getPrototypeFromCtor(newTarget, nil, r.global.ObjectPrototype)
 | 
			
		||||
		return r.newBaseObject(proto, classObject).val
 | 
			
		||||
	}
 | 
			
		||||
	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) valuePropToDescriptorObject(desc Value) Value {
 | 
			
		||||
	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()
 | 
			
		||||
	obj := ret.self
 | 
			
		||||
	if !accessor {
 | 
			
		||||
		obj.setOwnStr("value", value, false)
 | 
			
		||||
		obj.setOwnStr("writable", r.toBoolean(writable), false)
 | 
			
		||||
	} else {
 | 
			
		||||
		if get != nil {
 | 
			
		||||
			obj.setOwnStr("get", get, false)
 | 
			
		||||
		} else {
 | 
			
		||||
			obj.setOwnStr("get", _undefined, false)
 | 
			
		||||
		}
 | 
			
		||||
		if set != nil {
 | 
			
		||||
			obj.setOwnStr("set", set, false)
 | 
			
		||||
		} else {
 | 
			
		||||
			obj.setOwnStr("set", _undefined, false)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	obj.setOwnStr("enumerable", r.toBoolean(enumerable), false)
 | 
			
		||||
	obj.setOwnStr("configurable", r.toBoolean(configurable), false)
 | 
			
		||||
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
 | 
			
		||||
	o := call.Argument(0).ToObject(r)
 | 
			
		||||
	propName := toPropertyKey(call.Argument(1))
 | 
			
		||||
	return r.valuePropToDescriptorObject(o.getOwnProp(propName))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_getOwnPropertyDescriptors(call FunctionCall) Value {
 | 
			
		||||
	o := call.Argument(0).ToObject(r)
 | 
			
		||||
	result := r.newBaseObject(r.global.ObjectPrototype, classObject).val
 | 
			
		||||
	for item, next := o.self.iterateKeys()(); next != nil; item, next = next() {
 | 
			
		||||
		var prop Value
 | 
			
		||||
		if item.value == nil {
 | 
			
		||||
			prop = o.getOwnProp(item.name)
 | 
			
		||||
			if prop == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			prop = item.value
 | 
			
		||||
		}
 | 
			
		||||
		descriptor := r.valuePropToDescriptorObject(prop)
 | 
			
		||||
		if descriptor != _undefined {
 | 
			
		||||
			createDataPropertyOrThrow(result, item.name, descriptor)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
 | 
			
		||||
	obj := call.Argument(0).ToObject(r)
 | 
			
		||||
 | 
			
		||||
	return r.newArrayValues(obj.self.stringKeys(true, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value {
 | 
			
		||||
	obj := call.Argument(0).ToObject(r)
 | 
			
		||||
	return r.newArrayValues(obj.self.symbols(true, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) toValueProp(v Value) *valueProperty {
 | 
			
		||||
	if v == nil || v == _undefined {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	obj := r.toObject(v)
 | 
			
		||||
	getter := obj.self.getStr("get", nil)
 | 
			
		||||
	setter := obj.self.getStr("set", nil)
 | 
			
		||||
	writable := obj.self.getStr("writable", nil)
 | 
			
		||||
	value := obj.self.getStr("value", nil)
 | 
			
		||||
	if (getter != nil || setter != nil) && (value != nil || writable != nil) {
 | 
			
		||||
		r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret := &valueProperty{}
 | 
			
		||||
	if writable != nil && writable.ToBoolean() {
 | 
			
		||||
		ret.writable = true
 | 
			
		||||
	}
 | 
			
		||||
	if e := obj.self.getStr("enumerable", nil); e != nil && e.ToBoolean() {
 | 
			
		||||
		ret.enumerable = true
 | 
			
		||||
	}
 | 
			
		||||
	if c := obj.self.getStr("configurable", nil); c != nil && c.ToBoolean() {
 | 
			
		||||
		ret.configurable = true
 | 
			
		||||
	}
 | 
			
		||||
	ret.value = value
 | 
			
		||||
 | 
			
		||||
	if getter != nil && getter != _undefined {
 | 
			
		||||
		o := r.toObject(getter)
 | 
			
		||||
		if _, ok := o.self.assertCallable(); !ok {
 | 
			
		||||
			r.typeErrorResult(true, "getter must be a function")
 | 
			
		||||
		}
 | 
			
		||||
		ret.getterFunc = o
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setter != nil && setter != _undefined {
 | 
			
		||||
		o := r.toObject(setter)
 | 
			
		||||
		if _, ok := o.self.assertCallable(); !ok {
 | 
			
		||||
			r.typeErrorResult(true, "setter must be a function")
 | 
			
		||||
		}
 | 
			
		||||
		ret.setterFunc = o
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ret.getterFunc != nil || ret.setterFunc != nil {
 | 
			
		||||
		ret.accessor = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) toPropertyDescriptor(v Value) (ret PropertyDescriptor) {
 | 
			
		||||
	if o, ok := v.(*Object); ok {
 | 
			
		||||
		descr := o.self
 | 
			
		||||
 | 
			
		||||
		// Save the original descriptor for reference
 | 
			
		||||
		ret.jsDescriptor = o
 | 
			
		||||
 | 
			
		||||
		ret.Value = descr.getStr("value", nil)
 | 
			
		||||
 | 
			
		||||
		if p := descr.getStr("writable", nil); p != nil {
 | 
			
		||||
			ret.Writable = ToFlag(p.ToBoolean())
 | 
			
		||||
		}
 | 
			
		||||
		if p := descr.getStr("enumerable", nil); p != nil {
 | 
			
		||||
			ret.Enumerable = ToFlag(p.ToBoolean())
 | 
			
		||||
		}
 | 
			
		||||
		if p := descr.getStr("configurable", nil); p != nil {
 | 
			
		||||
			ret.Configurable = ToFlag(p.ToBoolean())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ret.Getter = descr.getStr("get", nil)
 | 
			
		||||
		ret.Setter = descr.getStr("set", nil)
 | 
			
		||||
 | 
			
		||||
		if ret.Getter != nil && ret.Getter != _undefined {
 | 
			
		||||
			if _, ok := r.toObject(ret.Getter).self.assertCallable(); !ok {
 | 
			
		||||
				r.typeErrorResult(true, "getter must be a function")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ret.Setter != nil && ret.Setter != _undefined {
 | 
			
		||||
			if _, ok := r.toObject(ret.Setter).self.assertCallable(); !ok {
 | 
			
		||||
				r.typeErrorResult(true, "setter must be a function")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ret.Getter != nil || ret.Setter != nil) && (ret.Value != nil || ret.Writable != FLAG_NOT_SET) {
 | 
			
		||||
			r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		r.typeErrorResult(true, "Property description must be an object: %s", v.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) _defineProperties(o *Object, p Value) {
 | 
			
		||||
	type propItem struct {
 | 
			
		||||
		name Value
 | 
			
		||||
		prop PropertyDescriptor
 | 
			
		||||
	}
 | 
			
		||||
	props := p.ToObject(r)
 | 
			
		||||
	var list []propItem
 | 
			
		||||
	for item, next := iterateEnumerableProperties(props)(); next != nil; item, next = next() {
 | 
			
		||||
		list = append(list, propItem{
 | 
			
		||||
			name: item.name,
 | 
			
		||||
			prop: r.toPropertyDescriptor(item.value),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	for _, prop := range list {
 | 
			
		||||
		o.defineOwnProperty(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 {
 | 
			
		||||
		descr := r.toPropertyDescriptor(call.Argument(2))
 | 
			
		||||
		obj.defineOwnProperty(toPropertyKey(call.Argument(1)), descr, true)
 | 
			
		||||
		ret = call.Argument(0)
 | 
			
		||||
	} 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 {
 | 
			
		||||
		obj.self.preventExtensions(true)
 | 
			
		||||
		descr := PropertyDescriptor{
 | 
			
		||||
			Configurable: FLAG_FALSE,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
 | 
			
		||||
			if prop, ok := item.value.(*valueProperty); ok {
 | 
			
		||||
				prop.configurable = false
 | 
			
		||||
			} else {
 | 
			
		||||
				obj.defineOwnProperty(item.name, descr, true)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return obj
 | 
			
		||||
	}
 | 
			
		||||
	return arg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_freeze(call FunctionCall) Value {
 | 
			
		||||
	arg := call.Argument(0)
 | 
			
		||||
	if obj, ok := arg.(*Object); ok {
 | 
			
		||||
		obj.self.preventExtensions(true)
 | 
			
		||||
 | 
			
		||||
		for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
 | 
			
		||||
			if prop, ok := item.value.(*valueProperty); ok {
 | 
			
		||||
				prop.configurable = false
 | 
			
		||||
				if !prop.accessor {
 | 
			
		||||
					prop.writable = false
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				prop := obj.getOwnProp(item.name)
 | 
			
		||||
				descr := PropertyDescriptor{
 | 
			
		||||
					Configurable: FLAG_FALSE,
 | 
			
		||||
				}
 | 
			
		||||
				if prop, ok := prop.(*valueProperty); ok && prop.accessor {
 | 
			
		||||
					// no-op
 | 
			
		||||
				} else {
 | 
			
		||||
					descr.Writable = FLAG_FALSE
 | 
			
		||||
				}
 | 
			
		||||
				obj.defineOwnProperty(item.name, descr, true)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		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(true)
 | 
			
		||||
	}
 | 
			
		||||
	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, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
 | 
			
		||||
			var prop Value
 | 
			
		||||
			if item.value == nil {
 | 
			
		||||
				prop = obj.getOwnProp(item.name)
 | 
			
		||||
				if prop == nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				prop = item.value
 | 
			
		||||
			}
 | 
			
		||||
			if prop, ok := prop.(*valueProperty); ok {
 | 
			
		||||
				if prop.configurable {
 | 
			
		||||
					return valueFalse
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return valueFalse
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
 | 
			
		||||
			var prop Value
 | 
			
		||||
			if item.value == nil {
 | 
			
		||||
				prop = obj.getOwnProp(item.name)
 | 
			
		||||
				if prop == nil {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				prop = item.value
 | 
			
		||||
			}
 | 
			
		||||
			if prop, ok := prop.(*valueProperty); ok {
 | 
			
		||||
				if prop.configurable || prop.value != nil && prop.writable {
 | 
			
		||||
					return valueFalse
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return valueFalse
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
	obj := call.Argument(0).ToObject(r)
 | 
			
		||||
 | 
			
		||||
	return r.newArrayValues(obj.self.stringKeys(false, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_entries(call FunctionCall) Value {
 | 
			
		||||
	obj := call.Argument(0).ToObject(r)
 | 
			
		||||
 | 
			
		||||
	var values []Value
 | 
			
		||||
 | 
			
		||||
	for item, next := iterateEnumerableStringProperties(obj)(); next != nil; item, next = next() {
 | 
			
		||||
		values = append(values, r.newArrayValues([]Value{item.name, item.value}))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.newArrayValues(values)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_values(call FunctionCall) Value {
 | 
			
		||||
	obj := call.Argument(0).ToObject(r)
 | 
			
		||||
 | 
			
		||||
	var values []Value
 | 
			
		||||
 | 
			
		||||
	for item, next := iterateEnumerableStringProperties(obj)(); next != nil; item, next = next() {
 | 
			
		||||
		values = append(values, item.value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.newArrayValues(values)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
 | 
			
		||||
	p := toPropertyKey(call.Argument(0))
 | 
			
		||||
	o := call.This.ToObject(r)
 | 
			
		||||
	if o.hasOwnProperty(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 := toPropertyKey(call.Argument(0))
 | 
			
		||||
	o := call.This.ToObject(r)
 | 
			
		||||
	pv := o.getOwnProp(p)
 | 
			
		||||
	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
 | 
			
		||||
	default:
 | 
			
		||||
		obj := o.ToObject(r)
 | 
			
		||||
		if o, ok := obj.self.(*objectGoReflect); ok {
 | 
			
		||||
			if toString := o.toString; toString != nil {
 | 
			
		||||
				return toString()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		var clsName string
 | 
			
		||||
		if isArray(obj) {
 | 
			
		||||
			clsName = classArray
 | 
			
		||||
		} else {
 | 
			
		||||
			clsName = obj.self.className()
 | 
			
		||||
		}
 | 
			
		||||
		if tag := obj.self.getSym(SymToStringTag, nil); tag != nil {
 | 
			
		||||
			if str, ok := tag.(String); ok {
 | 
			
		||||
				clsName = str.String()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return newStringValue(fmt.Sprintf("[object %s]", clsName))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) objectproto_toLocaleString(call FunctionCall) Value {
 | 
			
		||||
	toString := toMethod(r.getVStr(call.This, "toString"))
 | 
			
		||||
	return toString(FunctionCall{This: call.This})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) objectproto_getProto(call FunctionCall) Value {
 | 
			
		||||
	proto := call.This.ToObject(r).self.proto()
 | 
			
		||||
	if proto != nil {
 | 
			
		||||
		return proto
 | 
			
		||||
	}
 | 
			
		||||
	return _null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setObjectProto(o, arg Value) {
 | 
			
		||||
	r.checkObjectCoercible(o)
 | 
			
		||||
	var proto *Object
 | 
			
		||||
	if arg != _null {
 | 
			
		||||
		if obj, ok := arg.(*Object); ok {
 | 
			
		||||
			proto = obj
 | 
			
		||||
		} else {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if o, ok := o.(*Object); ok {
 | 
			
		||||
		o.self.setProto(proto, true)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) objectproto_setProto(call FunctionCall) Value {
 | 
			
		||||
	r.setObjectProto(call.This, call.Argument(0))
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) objectproto_valueOf(call FunctionCall) Value {
 | 
			
		||||
	return call.This.ToObject(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_assign(call FunctionCall) Value {
 | 
			
		||||
	to := call.Argument(0).ToObject(r)
 | 
			
		||||
	if len(call.Arguments) > 1 {
 | 
			
		||||
		for _, arg := range call.Arguments[1:] {
 | 
			
		||||
			if arg != _undefined && arg != _null {
 | 
			
		||||
				source := arg.ToObject(r)
 | 
			
		||||
				for item, next := iterateEnumerableProperties(source)(); next != nil; item, next = next() {
 | 
			
		||||
					to.setOwn(item.name, item.value, true)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_is(call FunctionCall) Value {
 | 
			
		||||
	return r.toBoolean(call.Argument(0).SameAs(call.Argument(1)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) toProto(proto Value) *Object {
 | 
			
		||||
	if proto != _null {
 | 
			
		||||
		if obj, ok := proto.(*Object); ok {
 | 
			
		||||
			return obj
 | 
			
		||||
		} else {
 | 
			
		||||
			panic(r.NewTypeError("Object prototype may only be an Object or null: %s", proto))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_setPrototypeOf(call FunctionCall) Value {
 | 
			
		||||
	o := call.Argument(0)
 | 
			
		||||
	r.checkObjectCoercible(o)
 | 
			
		||||
	proto := r.toProto(call.Argument(1))
 | 
			
		||||
	if o, ok := o.(*Object); ok {
 | 
			
		||||
		o.self.setProto(proto, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_fromEntries(call FunctionCall) Value {
 | 
			
		||||
	o := call.Argument(0)
 | 
			
		||||
	r.checkObjectCoercible(o)
 | 
			
		||||
 | 
			
		||||
	result := r.newBaseObject(r.global.ObjectPrototype, classObject).val
 | 
			
		||||
 | 
			
		||||
	iter := r.getIterator(o, nil)
 | 
			
		||||
	iter.iterate(func(nextValue Value) {
 | 
			
		||||
		i0 := valueInt(0)
 | 
			
		||||
		i1 := valueInt(1)
 | 
			
		||||
 | 
			
		||||
		itemObj := r.toObject(nextValue)
 | 
			
		||||
		k := itemObj.self.getIdx(i0, nil)
 | 
			
		||||
		v := itemObj.self.getIdx(i1, nil)
 | 
			
		||||
		key := toPropertyKey(k)
 | 
			
		||||
 | 
			
		||||
		createDataPropertyOrThrow(result, key, v)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) object_hasOwn(call FunctionCall) Value {
 | 
			
		||||
	o := call.Argument(0)
 | 
			
		||||
	obj := o.ToObject(r)
 | 
			
		||||
	p := toPropertyKey(call.Argument(1))
 | 
			
		||||
 | 
			
		||||
	if obj.hasOwnProperty(p) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	} else {
 | 
			
		||||
		return valueFalse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createObjectTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
	t.protoFactory = func(r *Runtime) *Object {
 | 
			
		||||
		return r.getFunctionPrototype()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
 | 
			
		||||
	t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Object"), false, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.global.ObjectPrototype, false, false, false) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("assign", func(r *Runtime) Value { return r.methodProp(r.object_assign, "assign", 2) })
 | 
			
		||||
	t.putStr("defineProperty", func(r *Runtime) Value { return r.methodProp(r.object_defineProperty, "defineProperty", 3) })
 | 
			
		||||
	t.putStr("defineProperties", func(r *Runtime) Value { return r.methodProp(r.object_defineProperties, "defineProperties", 2) })
 | 
			
		||||
	t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.object_entries, "entries", 1) })
 | 
			
		||||
	t.putStr("getOwnPropertyDescriptor", func(r *Runtime) Value {
 | 
			
		||||
		return r.methodProp(r.object_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2)
 | 
			
		||||
	})
 | 
			
		||||
	t.putStr("getOwnPropertyDescriptors", func(r *Runtime) Value {
 | 
			
		||||
		return r.methodProp(r.object_getOwnPropertyDescriptors, "getOwnPropertyDescriptors", 1)
 | 
			
		||||
	})
 | 
			
		||||
	t.putStr("getPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_getPrototypeOf, "getPrototypeOf", 1) })
 | 
			
		||||
	t.putStr("is", func(r *Runtime) Value { return r.methodProp(r.object_is, "is", 2) })
 | 
			
		||||
	t.putStr("getOwnPropertyNames", func(r *Runtime) Value { return r.methodProp(r.object_getOwnPropertyNames, "getOwnPropertyNames", 1) })
 | 
			
		||||
	t.putStr("getOwnPropertySymbols", func(r *Runtime) Value {
 | 
			
		||||
		return r.methodProp(r.object_getOwnPropertySymbols, "getOwnPropertySymbols", 1)
 | 
			
		||||
	})
 | 
			
		||||
	t.putStr("create", func(r *Runtime) Value { return r.methodProp(r.object_create, "create", 2) })
 | 
			
		||||
	t.putStr("seal", func(r *Runtime) Value { return r.methodProp(r.object_seal, "seal", 1) })
 | 
			
		||||
	t.putStr("freeze", func(r *Runtime) Value { return r.methodProp(r.object_freeze, "freeze", 1) })
 | 
			
		||||
	t.putStr("preventExtensions", func(r *Runtime) Value { return r.methodProp(r.object_preventExtensions, "preventExtensions", 1) })
 | 
			
		||||
	t.putStr("isSealed", func(r *Runtime) Value { return r.methodProp(r.object_isSealed, "isSealed", 1) })
 | 
			
		||||
	t.putStr("isFrozen", func(r *Runtime) Value { return r.methodProp(r.object_isFrozen, "isFrozen", 1) })
 | 
			
		||||
	t.putStr("isExtensible", func(r *Runtime) Value { return r.methodProp(r.object_isExtensible, "isExtensible", 1) })
 | 
			
		||||
	t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.object_keys, "keys", 1) })
 | 
			
		||||
	t.putStr("setPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_setPrototypeOf, "setPrototypeOf", 2) })
 | 
			
		||||
	t.putStr("values", func(r *Runtime) Value { return r.methodProp(r.object_values, "values", 1) })
 | 
			
		||||
	t.putStr("fromEntries", func(r *Runtime) Value { return r.methodProp(r.object_fromEntries, "fromEntries", 1) })
 | 
			
		||||
	t.putStr("hasOwn", func(r *Runtime) Value { return r.methodProp(r.object_hasOwn, "hasOwn", 2) })
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _objectTemplate *objectTemplate
 | 
			
		||||
var objectTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getObjectTemplate() *objectTemplate {
 | 
			
		||||
	objectTemplateOnce.Do(func() {
 | 
			
		||||
		_objectTemplate = createObjectTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return _objectTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getObject() *Object {
 | 
			
		||||
	ret := r.global.Object
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Object = ret
 | 
			
		||||
		r.newTemplatedFuncObject(getObjectTemplate(), ret, func(call FunctionCall) Value {
 | 
			
		||||
			return r.builtin_Object(call.Arguments, nil)
 | 
			
		||||
		}, r.builtin_Object)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func (r *Runtime) getObjectPrototype() *Object {
 | 
			
		||||
	ret := r.global.ObjectPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.ObjectPrototype = ret
 | 
			
		||||
		r.newTemplatedObject(getObjectProtoTemplate(), ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
var objectProtoTemplate *objectTemplate
 | 
			
		||||
var objectProtoTemplateOnce sync.Once
 | 
			
		||||
 | 
			
		||||
func getObjectProtoTemplate() *objectTemplate {
 | 
			
		||||
	objectProtoTemplateOnce.Do(func() {
 | 
			
		||||
		objectProtoTemplate = createObjectProtoTemplate()
 | 
			
		||||
	})
 | 
			
		||||
	return objectProtoTemplate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createObjectProtoTemplate() *objectTemplate {
 | 
			
		||||
	t := newObjectTemplate()
 | 
			
		||||
 | 
			
		||||
	// null prototype
 | 
			
		||||
 | 
			
		||||
	t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) })
 | 
			
		||||
 | 
			
		||||
	t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toString, "toString", 0) })
 | 
			
		||||
	t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toLocaleString, "toLocaleString", 0) })
 | 
			
		||||
	t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_valueOf, "valueOf", 0) })
 | 
			
		||||
	t.putStr("hasOwnProperty", func(r *Runtime) Value { return r.methodProp(r.objectproto_hasOwnProperty, "hasOwnProperty", 1) })
 | 
			
		||||
	t.putStr("isPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_isPrototypeOf, "isPrototypeOf", 1) })
 | 
			
		||||
	t.putStr("propertyIsEnumerable", func(r *Runtime) Value {
 | 
			
		||||
		return r.methodProp(r.objectproto_propertyIsEnumerable, "propertyIsEnumerable", 1)
 | 
			
		||||
	})
 | 
			
		||||
	t.putStr(__proto__, func(r *Runtime) Value {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			accessor:     true,
 | 
			
		||||
			getterFunc:   r.newNativeFunc(r.objectproto_getProto, "get __proto__", 0),
 | 
			
		||||
			setterFunc:   r.newNativeFunc(r.objectproto_setProto, "set __proto__", 1),
 | 
			
		||||
			configurable: true,
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										646
									
								
								goja/builtin_promise.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										646
									
								
								goja/builtin_promise.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,646 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PromiseState int
 | 
			
		||||
type PromiseRejectionOperation int
 | 
			
		||||
 | 
			
		||||
type promiseReactionType int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PromiseStatePending PromiseState = iota
 | 
			
		||||
	PromiseStateFulfilled
 | 
			
		||||
	PromiseStateRejected
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PromiseRejectionReject PromiseRejectionOperation = iota
 | 
			
		||||
	PromiseRejectionHandle
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	promiseReactionFulfill promiseReactionType = iota
 | 
			
		||||
	promiseReactionReject
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PromiseRejectionTracker func(p *Promise, operation PromiseRejectionOperation)
 | 
			
		||||
 | 
			
		||||
type jobCallback struct {
 | 
			
		||||
	callback func(FunctionCall) Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type promiseCapability struct {
 | 
			
		||||
	promise               *Object
 | 
			
		||||
	resolveObj, rejectObj *Object
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type promiseReaction struct {
 | 
			
		||||
	capability  *promiseCapability
 | 
			
		||||
	typ         promiseReactionType
 | 
			
		||||
	handler     *jobCallback
 | 
			
		||||
	asyncRunner *asyncRunner
 | 
			
		||||
	asyncCtx    interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var typePromise = reflect.TypeOf((*Promise)(nil))
 | 
			
		||||
 | 
			
		||||
// Promise is a Go wrapper around ECMAScript Promise. Calling Runtime.ToValue() on it
 | 
			
		||||
// returns the underlying Object. Calling Export() on a Promise Object returns a Promise.
 | 
			
		||||
//
 | 
			
		||||
// Use Runtime.NewPromise() to create one. Calling Runtime.ToValue() on a zero object or nil returns null Value.
 | 
			
		||||
//
 | 
			
		||||
// WARNING: Instances of Promise are not goroutine-safe. See Runtime.NewPromise() for more details.
 | 
			
		||||
type Promise struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	state            PromiseState
 | 
			
		||||
	result           Value
 | 
			
		||||
	fulfillReactions []*promiseReaction
 | 
			
		||||
	rejectReactions  []*promiseReaction
 | 
			
		||||
	handled          bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) State() PromiseState {
 | 
			
		||||
	return p.state
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) Result() Value {
 | 
			
		||||
	return p.result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) toValue(r *Runtime) Value {
 | 
			
		||||
	if p == nil || p.val == nil {
 | 
			
		||||
		return _null
 | 
			
		||||
	}
 | 
			
		||||
	promise := p.val
 | 
			
		||||
	if promise.runtime != r {
 | 
			
		||||
		panic(r.NewTypeError("Illegal runtime transition of a Promise"))
 | 
			
		||||
	}
 | 
			
		||||
	return promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) createResolvingFunctions() (resolve, reject *Object) {
 | 
			
		||||
	r := p.val.runtime
 | 
			
		||||
	alreadyResolved := false
 | 
			
		||||
	return p.val.runtime.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
			if alreadyResolved {
 | 
			
		||||
				return _undefined
 | 
			
		||||
			}
 | 
			
		||||
			alreadyResolved = true
 | 
			
		||||
			resolution := call.Argument(0)
 | 
			
		||||
			if resolution.SameAs(p.val) {
 | 
			
		||||
				return p.reject(r.NewTypeError("Promise self-resolution"))
 | 
			
		||||
			}
 | 
			
		||||
			if obj, ok := resolution.(*Object); ok {
 | 
			
		||||
				var thenAction Value
 | 
			
		||||
				ex := r.vm.try(func() {
 | 
			
		||||
					thenAction = obj.self.getStr("then", nil)
 | 
			
		||||
				})
 | 
			
		||||
				if ex != nil {
 | 
			
		||||
					return p.reject(ex.val)
 | 
			
		||||
				}
 | 
			
		||||
				if call, ok := assertCallable(thenAction); ok {
 | 
			
		||||
					job := r.newPromiseResolveThenableJob(p, resolution, &jobCallback{callback: call})
 | 
			
		||||
					r.enqueuePromiseJob(job)
 | 
			
		||||
					return _undefined
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return p.fulfill(resolution)
 | 
			
		||||
		}, "", 1),
 | 
			
		||||
		p.val.runtime.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
			if alreadyResolved {
 | 
			
		||||
				return _undefined
 | 
			
		||||
			}
 | 
			
		||||
			alreadyResolved = true
 | 
			
		||||
			reason := call.Argument(0)
 | 
			
		||||
			return p.reject(reason)
 | 
			
		||||
		}, "", 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) reject(reason Value) Value {
 | 
			
		||||
	reactions := p.rejectReactions
 | 
			
		||||
	p.result = reason
 | 
			
		||||
	p.fulfillReactions, p.rejectReactions = nil, nil
 | 
			
		||||
	p.state = PromiseStateRejected
 | 
			
		||||
	r := p.val.runtime
 | 
			
		||||
	if !p.handled {
 | 
			
		||||
		r.trackPromiseRejection(p, PromiseRejectionReject)
 | 
			
		||||
	}
 | 
			
		||||
	r.triggerPromiseReactions(reactions, reason)
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) fulfill(value Value) Value {
 | 
			
		||||
	reactions := p.fulfillReactions
 | 
			
		||||
	p.result = value
 | 
			
		||||
	p.fulfillReactions, p.rejectReactions = nil, nil
 | 
			
		||||
	p.state = PromiseStateFulfilled
 | 
			
		||||
	p.val.runtime.triggerPromiseReactions(reactions, value)
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) exportType() reflect.Type {
 | 
			
		||||
	return typePromise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) export(*objectExportCtx) interface{} {
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Promise) addReactions(fulfillReaction *promiseReaction, rejectReaction *promiseReaction) {
 | 
			
		||||
	r := p.val.runtime
 | 
			
		||||
	if tracker := r.asyncContextTracker; tracker != nil {
 | 
			
		||||
		ctx := tracker.Grab()
 | 
			
		||||
		fulfillReaction.asyncCtx = ctx
 | 
			
		||||
		rejectReaction.asyncCtx = ctx
 | 
			
		||||
	}
 | 
			
		||||
	switch p.state {
 | 
			
		||||
	case PromiseStatePending:
 | 
			
		||||
		p.fulfillReactions = append(p.fulfillReactions, fulfillReaction)
 | 
			
		||||
		p.rejectReactions = append(p.rejectReactions, rejectReaction)
 | 
			
		||||
	case PromiseStateFulfilled:
 | 
			
		||||
		r.enqueuePromiseJob(r.newPromiseReactionJob(fulfillReaction, p.result))
 | 
			
		||||
	default:
 | 
			
		||||
		reason := p.result
 | 
			
		||||
		if !p.handled {
 | 
			
		||||
			r.trackPromiseRejection(p, PromiseRejectionHandle)
 | 
			
		||||
		}
 | 
			
		||||
		r.enqueuePromiseJob(r.newPromiseReactionJob(rejectReaction, reason))
 | 
			
		||||
	}
 | 
			
		||||
	p.handled = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newPromiseResolveThenableJob(p *Promise, thenable Value, then *jobCallback) func() {
 | 
			
		||||
	return func() {
 | 
			
		||||
		resolve, reject := p.createResolvingFunctions()
 | 
			
		||||
		ex := r.vm.try(func() {
 | 
			
		||||
			r.callJobCallback(then, thenable, resolve, reject)
 | 
			
		||||
		})
 | 
			
		||||
		if ex != nil {
 | 
			
		||||
			if fn, ok := reject.self.assertCallable(); ok {
 | 
			
		||||
				fn(FunctionCall{Arguments: []Value{ex.val}})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) enqueuePromiseJob(job func()) {
 | 
			
		||||
	r.jobQueue = append(r.jobQueue, job)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) triggerPromiseReactions(reactions []*promiseReaction, argument Value) {
 | 
			
		||||
	for _, reaction := range reactions {
 | 
			
		||||
		r.enqueuePromiseJob(r.newPromiseReactionJob(reaction, argument))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newPromiseReactionJob(reaction *promiseReaction, argument Value) func() {
 | 
			
		||||
	return func() {
 | 
			
		||||
		var handlerResult Value
 | 
			
		||||
		fulfill := false
 | 
			
		||||
		if reaction.handler == nil {
 | 
			
		||||
			handlerResult = argument
 | 
			
		||||
			if reaction.typ == promiseReactionFulfill {
 | 
			
		||||
				fulfill = true
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if tracker := r.asyncContextTracker; tracker != nil {
 | 
			
		||||
				tracker.Resumed(reaction.asyncCtx)
 | 
			
		||||
			}
 | 
			
		||||
			ex := r.vm.try(func() {
 | 
			
		||||
				handlerResult = r.callJobCallback(reaction.handler, _undefined, argument)
 | 
			
		||||
				fulfill = true
 | 
			
		||||
			})
 | 
			
		||||
			if ex != nil {
 | 
			
		||||
				handlerResult = ex.val
 | 
			
		||||
			}
 | 
			
		||||
			if tracker := r.asyncContextTracker; tracker != nil {
 | 
			
		||||
				tracker.Exited()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if reaction.capability != nil {
 | 
			
		||||
			if fulfill {
 | 
			
		||||
				reaction.capability.resolve(handlerResult)
 | 
			
		||||
			} else {
 | 
			
		||||
				reaction.capability.reject(handlerResult)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newPromise(proto *Object) *Promise {
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	po := &Promise{}
 | 
			
		||||
	po.class = classObject
 | 
			
		||||
	po.val = o
 | 
			
		||||
	po.extensible = true
 | 
			
		||||
	o.self = po
 | 
			
		||||
	po.prototype = proto
 | 
			
		||||
	po.init()
 | 
			
		||||
	return po
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newPromise(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget == nil {
 | 
			
		||||
		panic(r.needNew("Promise"))
 | 
			
		||||
	}
 | 
			
		||||
	var arg0 Value
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		arg0 = args[0]
 | 
			
		||||
	}
 | 
			
		||||
	executor := r.toCallable(arg0)
 | 
			
		||||
 | 
			
		||||
	proto := r.getPrototypeFromCtor(newTarget, r.global.Promise, r.getPromisePrototype())
 | 
			
		||||
	po := r.newPromise(proto)
 | 
			
		||||
 | 
			
		||||
	resolve, reject := po.createResolvingFunctions()
 | 
			
		||||
	ex := r.vm.try(func() {
 | 
			
		||||
		executor(FunctionCall{Arguments: []Value{resolve, reject}})
 | 
			
		||||
	})
 | 
			
		||||
	if ex != nil {
 | 
			
		||||
		if fn, ok := reject.self.assertCallable(); ok {
 | 
			
		||||
			fn(FunctionCall{Arguments: []Value{ex.val}})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return po.val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promiseProto_then(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	if p, ok := thisObj.self.(*Promise); ok {
 | 
			
		||||
		c := r.speciesConstructorObj(thisObj, r.getPromise())
 | 
			
		||||
		resultCapability := r.newPromiseCapability(c)
 | 
			
		||||
		return r.performPromiseThen(p, call.Argument(0), call.Argument(1), resultCapability)
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Method Promise.prototype.then called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newPromiseCapability(c *Object) *promiseCapability {
 | 
			
		||||
	pcap := new(promiseCapability)
 | 
			
		||||
	if c == r.getPromise() {
 | 
			
		||||
		p := r.newPromise(r.getPromisePrototype())
 | 
			
		||||
		pcap.resolveObj, pcap.rejectObj = p.createResolvingFunctions()
 | 
			
		||||
		pcap.promise = p.val
 | 
			
		||||
	} else {
 | 
			
		||||
		var resolve, reject Value
 | 
			
		||||
		executor := r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
			if resolve != nil {
 | 
			
		||||
				panic(r.NewTypeError("resolve is already set"))
 | 
			
		||||
			}
 | 
			
		||||
			if reject != nil {
 | 
			
		||||
				panic(r.NewTypeError("reject is already set"))
 | 
			
		||||
			}
 | 
			
		||||
			if arg := call.Argument(0); arg != _undefined {
 | 
			
		||||
				resolve = arg
 | 
			
		||||
			}
 | 
			
		||||
			if arg := call.Argument(1); arg != _undefined {
 | 
			
		||||
				reject = arg
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}, "", 2)
 | 
			
		||||
		pcap.promise = r.toConstructor(c)([]Value{executor}, c)
 | 
			
		||||
		pcap.resolveObj = r.toObject(resolve)
 | 
			
		||||
		r.toCallable(pcap.resolveObj) // make sure it's callable
 | 
			
		||||
		pcap.rejectObj = r.toObject(reject)
 | 
			
		||||
		r.toCallable(pcap.rejectObj)
 | 
			
		||||
	}
 | 
			
		||||
	return pcap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) performPromiseThen(p *Promise, onFulfilled, onRejected Value, resultCapability *promiseCapability) Value {
 | 
			
		||||
	var onFulfilledJobCallback, onRejectedJobCallback *jobCallback
 | 
			
		||||
	if f, ok := assertCallable(onFulfilled); ok {
 | 
			
		||||
		onFulfilledJobCallback = &jobCallback{callback: f}
 | 
			
		||||
	}
 | 
			
		||||
	if f, ok := assertCallable(onRejected); ok {
 | 
			
		||||
		onRejectedJobCallback = &jobCallback{callback: f}
 | 
			
		||||
	}
 | 
			
		||||
	fulfillReaction := &promiseReaction{
 | 
			
		||||
		capability: resultCapability,
 | 
			
		||||
		typ:        promiseReactionFulfill,
 | 
			
		||||
		handler:    onFulfilledJobCallback,
 | 
			
		||||
	}
 | 
			
		||||
	rejectReaction := &promiseReaction{
 | 
			
		||||
		capability: resultCapability,
 | 
			
		||||
		typ:        promiseReactionReject,
 | 
			
		||||
		handler:    onRejectedJobCallback,
 | 
			
		||||
	}
 | 
			
		||||
	p.addReactions(fulfillReaction, rejectReaction)
 | 
			
		||||
	if resultCapability == nil {
 | 
			
		||||
		return _undefined
 | 
			
		||||
	}
 | 
			
		||||
	return resultCapability.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promiseProto_catch(call FunctionCall) Value {
 | 
			
		||||
	return r.invoke(call.This, "then", _undefined, call.Argument(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promiseResolve(c *Object, x Value) *Object {
 | 
			
		||||
	if obj, ok := x.(*Object); ok {
 | 
			
		||||
		xConstructor := nilSafe(obj.self.getStr("constructor", nil))
 | 
			
		||||
		if xConstructor.SameAs(c) {
 | 
			
		||||
			return obj
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	pcap := r.newPromiseCapability(c)
 | 
			
		||||
	pcap.resolve(x)
 | 
			
		||||
	return pcap.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promiseProto_finally(call FunctionCall) Value {
 | 
			
		||||
	promise := r.toObject(call.This)
 | 
			
		||||
	c := r.speciesConstructorObj(promise, r.getPromise())
 | 
			
		||||
	onFinally := call.Argument(0)
 | 
			
		||||
	var thenFinally, catchFinally Value
 | 
			
		||||
	if onFinallyFn, ok := assertCallable(onFinally); !ok {
 | 
			
		||||
		thenFinally, catchFinally = onFinally, onFinally
 | 
			
		||||
	} else {
 | 
			
		||||
		thenFinally = r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
			value := call.Argument(0)
 | 
			
		||||
			result := onFinallyFn(FunctionCall{})
 | 
			
		||||
			promise := r.promiseResolve(c, result)
 | 
			
		||||
			valueThunk := r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
				return value
 | 
			
		||||
			}, "", 0)
 | 
			
		||||
			return r.invoke(promise, "then", valueThunk)
 | 
			
		||||
		}, "", 1)
 | 
			
		||||
 | 
			
		||||
		catchFinally = r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
			reason := call.Argument(0)
 | 
			
		||||
			result := onFinallyFn(FunctionCall{})
 | 
			
		||||
			promise := r.promiseResolve(c, result)
 | 
			
		||||
			thrower := r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
				panic(reason)
 | 
			
		||||
			}, "", 0)
 | 
			
		||||
			return r.invoke(promise, "then", thrower)
 | 
			
		||||
		}, "", 1)
 | 
			
		||||
	}
 | 
			
		||||
	return r.invoke(promise, "then", thenFinally, catchFinally)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pcap *promiseCapability) resolve(result Value) {
 | 
			
		||||
	pcap.promise.runtime.toCallable(pcap.resolveObj)(FunctionCall{Arguments: []Value{result}})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pcap *promiseCapability) reject(reason Value) {
 | 
			
		||||
	pcap.promise.runtime.toCallable(pcap.rejectObj)(FunctionCall{Arguments: []Value{reason}})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pcap *promiseCapability) try(f func()) bool {
 | 
			
		||||
	ex := pcap.promise.runtime.vm.try(f)
 | 
			
		||||
	if ex != nil {
 | 
			
		||||
		pcap.reject(ex.val)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promise_all(call FunctionCall) Value {
 | 
			
		||||
	c := r.toObject(call.This)
 | 
			
		||||
	pcap := r.newPromiseCapability(c)
 | 
			
		||||
 | 
			
		||||
	pcap.try(func() {
 | 
			
		||||
		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
 | 
			
		||||
		iter := r.getIterator(call.Argument(0), nil)
 | 
			
		||||
		var values []Value
 | 
			
		||||
		remainingElementsCount := 1
 | 
			
		||||
		iter.iterate(func(nextValue Value) {
 | 
			
		||||
			index := len(values)
 | 
			
		||||
			values = append(values, _undefined)
 | 
			
		||||
			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
 | 
			
		||||
			alreadyCalled := false
 | 
			
		||||
			onFulfilled := r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
				if alreadyCalled {
 | 
			
		||||
					return _undefined
 | 
			
		||||
				}
 | 
			
		||||
				alreadyCalled = true
 | 
			
		||||
				values[index] = call.Argument(0)
 | 
			
		||||
				remainingElementsCount--
 | 
			
		||||
				if remainingElementsCount == 0 {
 | 
			
		||||
					pcap.resolve(r.newArrayValues(values))
 | 
			
		||||
				}
 | 
			
		||||
				return _undefined
 | 
			
		||||
			}, "", 1)
 | 
			
		||||
			remainingElementsCount++
 | 
			
		||||
			r.invoke(nextPromise, "then", onFulfilled, pcap.rejectObj)
 | 
			
		||||
		})
 | 
			
		||||
		remainingElementsCount--
 | 
			
		||||
		if remainingElementsCount == 0 {
 | 
			
		||||
			pcap.resolve(r.newArrayValues(values))
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	return pcap.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promise_allSettled(call FunctionCall) Value {
 | 
			
		||||
	c := r.toObject(call.This)
 | 
			
		||||
	pcap := r.newPromiseCapability(c)
 | 
			
		||||
 | 
			
		||||
	pcap.try(func() {
 | 
			
		||||
		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
 | 
			
		||||
		iter := r.getIterator(call.Argument(0), nil)
 | 
			
		||||
		var values []Value
 | 
			
		||||
		remainingElementsCount := 1
 | 
			
		||||
		iter.iterate(func(nextValue Value) {
 | 
			
		||||
			index := len(values)
 | 
			
		||||
			values = append(values, _undefined)
 | 
			
		||||
			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
 | 
			
		||||
			alreadyCalled := false
 | 
			
		||||
			reaction := func(status Value, valueKey unistring.String) *Object {
 | 
			
		||||
				return r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
					if alreadyCalled {
 | 
			
		||||
						return _undefined
 | 
			
		||||
					}
 | 
			
		||||
					alreadyCalled = true
 | 
			
		||||
					obj := r.NewObject()
 | 
			
		||||
					obj.self._putProp("status", status, true, true, true)
 | 
			
		||||
					obj.self._putProp(valueKey, call.Argument(0), true, true, true)
 | 
			
		||||
					values[index] = obj
 | 
			
		||||
					remainingElementsCount--
 | 
			
		||||
					if remainingElementsCount == 0 {
 | 
			
		||||
						pcap.resolve(r.newArrayValues(values))
 | 
			
		||||
					}
 | 
			
		||||
					return _undefined
 | 
			
		||||
				}, "", 1)
 | 
			
		||||
			}
 | 
			
		||||
			onFulfilled := reaction(asciiString("fulfilled"), "value")
 | 
			
		||||
			onRejected := reaction(asciiString("rejected"), "reason")
 | 
			
		||||
			remainingElementsCount++
 | 
			
		||||
			r.invoke(nextPromise, "then", onFulfilled, onRejected)
 | 
			
		||||
		})
 | 
			
		||||
		remainingElementsCount--
 | 
			
		||||
		if remainingElementsCount == 0 {
 | 
			
		||||
			pcap.resolve(r.newArrayValues(values))
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	return pcap.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promise_any(call FunctionCall) Value {
 | 
			
		||||
	c := r.toObject(call.This)
 | 
			
		||||
	pcap := r.newPromiseCapability(c)
 | 
			
		||||
 | 
			
		||||
	pcap.try(func() {
 | 
			
		||||
		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
 | 
			
		||||
		iter := r.getIterator(call.Argument(0), nil)
 | 
			
		||||
		var errors []Value
 | 
			
		||||
		remainingElementsCount := 1
 | 
			
		||||
		iter.iterate(func(nextValue Value) {
 | 
			
		||||
			index := len(errors)
 | 
			
		||||
			errors = append(errors, _undefined)
 | 
			
		||||
			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
 | 
			
		||||
			alreadyCalled := false
 | 
			
		||||
			onRejected := r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
				if alreadyCalled {
 | 
			
		||||
					return _undefined
 | 
			
		||||
				}
 | 
			
		||||
				alreadyCalled = true
 | 
			
		||||
				errors[index] = call.Argument(0)
 | 
			
		||||
				remainingElementsCount--
 | 
			
		||||
				if remainingElementsCount == 0 {
 | 
			
		||||
					_error := r.builtin_new(r.getAggregateError(), nil)
 | 
			
		||||
					_error.self._putProp("errors", r.newArrayValues(errors), true, false, true)
 | 
			
		||||
					pcap.reject(_error)
 | 
			
		||||
				}
 | 
			
		||||
				return _undefined
 | 
			
		||||
			}, "", 1)
 | 
			
		||||
 | 
			
		||||
			remainingElementsCount++
 | 
			
		||||
			r.invoke(nextPromise, "then", pcap.resolveObj, onRejected)
 | 
			
		||||
		})
 | 
			
		||||
		remainingElementsCount--
 | 
			
		||||
		if remainingElementsCount == 0 {
 | 
			
		||||
			_error := r.builtin_new(r.getAggregateError(), nil)
 | 
			
		||||
			_error.self._putProp("errors", r.newArrayValues(errors), true, false, true)
 | 
			
		||||
			pcap.reject(_error)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	return pcap.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promise_race(call FunctionCall) Value {
 | 
			
		||||
	c := r.toObject(call.This)
 | 
			
		||||
	pcap := r.newPromiseCapability(c)
 | 
			
		||||
 | 
			
		||||
	pcap.try(func() {
 | 
			
		||||
		promiseResolve := r.toCallable(c.self.getStr("resolve", nil))
 | 
			
		||||
		iter := r.getIterator(call.Argument(0), nil)
 | 
			
		||||
		iter.iterate(func(nextValue Value) {
 | 
			
		||||
			nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}})
 | 
			
		||||
			r.invoke(nextPromise, "then", pcap.resolveObj, pcap.rejectObj)
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
	return pcap.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promise_reject(call FunctionCall) Value {
 | 
			
		||||
	pcap := r.newPromiseCapability(r.toObject(call.This))
 | 
			
		||||
	pcap.reject(call.Argument(0))
 | 
			
		||||
	return pcap.promise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) promise_resolve(call FunctionCall) Value {
 | 
			
		||||
	return r.promiseResolve(r.toObject(call.This), call.Argument(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createPromiseProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 | 
			
		||||
	o._putProp("constructor", r.getPromise(), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putProp("catch", r.newNativeFunc(r.promiseProto_catch, "catch", 1), true, false, true)
 | 
			
		||||
	o._putProp("finally", r.newNativeFunc(r.promiseProto_finally, "finally", 1), true, false, true)
 | 
			
		||||
	o._putProp("then", r.newNativeFunc(r.promiseProto_then, "then", 2), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classPromise), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createPromise(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeConstructOnly(val, r.builtin_newPromise, r.getPromisePrototype(), "Promise", 1)
 | 
			
		||||
 | 
			
		||||
	o._putProp("all", r.newNativeFunc(r.promise_all, "all", 1), true, false, true)
 | 
			
		||||
	o._putProp("allSettled", r.newNativeFunc(r.promise_allSettled, "allSettled", 1), true, false, true)
 | 
			
		||||
	o._putProp("any", r.newNativeFunc(r.promise_any, "any", 1), true, false, true)
 | 
			
		||||
	o._putProp("race", r.newNativeFunc(r.promise_race, "race", 1), true, false, true)
 | 
			
		||||
	o._putProp("reject", r.newNativeFunc(r.promise_reject, "reject", 1), true, false, true)
 | 
			
		||||
	o._putProp("resolve", r.newNativeFunc(r.promise_resolve, "resolve", 1), true, false, true)
 | 
			
		||||
 | 
			
		||||
	r.putSpeciesReturnThis(o)
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getPromisePrototype() *Object {
 | 
			
		||||
	ret := r.global.PromisePrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.PromisePrototype = ret
 | 
			
		||||
		ret.self = r.createPromiseProto(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getPromise() *Object {
 | 
			
		||||
	ret := r.global.Promise
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Promise = ret
 | 
			
		||||
		ret.self = r.createPromise(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
 | 
			
		||||
	f, _ := AssertFunction(fObj)
 | 
			
		||||
	return func(x interface{}) {
 | 
			
		||||
		_, _ = f(nil, r.ToValue(x))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPromise creates and returns a Promise and resolving functions for it.
 | 
			
		||||
//
 | 
			
		||||
// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
 | 
			
		||||
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://github.com/dop251/goja_nodejs)
 | 
			
		||||
// where it can be used like this:
 | 
			
		||||
//
 | 
			
		||||
//	loop := NewEventLoop()
 | 
			
		||||
//	loop.Start()
 | 
			
		||||
//	defer loop.Stop()
 | 
			
		||||
//	loop.RunOnLoop(func(vm *goja.Runtime) {
 | 
			
		||||
//	    p, resolve, _ := vm.NewPromise()
 | 
			
		||||
//	    vm.Set("p", p)
 | 
			
		||||
//	    go func() {
 | 
			
		||||
//	        time.Sleep(500 * time.Millisecond)   // or perform any other blocking operation
 | 
			
		||||
//	        loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
 | 
			
		||||
//	            resolve(result)
 | 
			
		||||
//	        })
 | 
			
		||||
//	    }()
 | 
			
		||||
//	}
 | 
			
		||||
func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) {
 | 
			
		||||
	p := r.newPromise(r.getPromisePrototype())
 | 
			
		||||
	resolveF, rejectF := p.createResolvingFunctions()
 | 
			
		||||
	return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetPromiseRejectionTracker registers a function that will be called in two scenarios: when a promise is rejected
 | 
			
		||||
// without any handlers (with operation argument set to PromiseRejectionReject), and when a handler is added to a
 | 
			
		||||
// rejected promise for the first time (with operation argument set to PromiseRejectionHandle).
 | 
			
		||||
//
 | 
			
		||||
// Setting a tracker replaces any existing one. Setting it to nil disables the functionality.
 | 
			
		||||
//
 | 
			
		||||
// See https://tc39.es/ecma262/#sec-host-promise-rejection-tracker for more details.
 | 
			
		||||
func (r *Runtime) SetPromiseRejectionTracker(tracker PromiseRejectionTracker) {
 | 
			
		||||
	r.promiseRejectionTracker = tracker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetAsyncContextTracker registers a handler that allows to track async execution contexts. See AsyncContextTracker
 | 
			
		||||
// documentation for more details. Setting it to nil disables the functionality.
 | 
			
		||||
// This method (as Runtime in general) is not goroutine-safe.
 | 
			
		||||
func (r *Runtime) SetAsyncContextTracker(tracker AsyncContextTracker) {
 | 
			
		||||
	r.asyncContextTracker = tracker
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										396
									
								
								goja/builtin_proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								goja/builtin_proxy.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,396 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type nativeProxyHandler struct {
 | 
			
		||||
	handler *ProxyTrapConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getPrototypeOf(target *Object) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetPrototypeOf; trap != nil {
 | 
			
		||||
		return trap(target), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) setPrototypeOf(target *Object, proto *Object) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.SetPrototypeOf; trap != nil {
 | 
			
		||||
		return trap(target, proto), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) isExtensible(target *Object) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.IsExtensible; trap != nil {
 | 
			
		||||
		return trap(target), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) preventExtensions(target *Object) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.PreventExtensions; trap != nil {
 | 
			
		||||
		return trap(target), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil {
 | 
			
		||||
		if idx, ok := strToInt(prop); ok {
 | 
			
		||||
			desc := trap(target, idx)
 | 
			
		||||
			return desc.toValue(target.runtime), true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.GetOwnPropertyDescriptor; trap != nil {
 | 
			
		||||
		desc := trap(target, prop.String())
 | 
			
		||||
		return desc.toValue(target.runtime), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getOwnPropertyDescriptorIdx(target *Object, prop valueInt) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil {
 | 
			
		||||
		desc := trap(target, toIntStrict(int64(prop)))
 | 
			
		||||
		return desc.toValue(target.runtime), true
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.GetOwnPropertyDescriptor; trap != nil {
 | 
			
		||||
		desc := trap(target, prop.String())
 | 
			
		||||
		return desc.toValue(target.runtime), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getOwnPropertyDescriptorSym(target *Object, prop *Symbol) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetOwnPropertyDescriptorSym; trap != nil {
 | 
			
		||||
		desc := trap(target, prop)
 | 
			
		||||
		return desc.toValue(target.runtime), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.DefinePropertyIdx; trap != nil {
 | 
			
		||||
		if idx, ok := strToInt(prop); ok {
 | 
			
		||||
			return trap(target, idx, desc), true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.DefineProperty; trap != nil {
 | 
			
		||||
		return trap(target, prop.String(), desc), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) definePropertyIdx(target *Object, prop valueInt, desc PropertyDescriptor) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.DefinePropertyIdx; trap != nil {
 | 
			
		||||
		return trap(target, toIntStrict(int64(prop)), desc), true
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.DefineProperty; trap != nil {
 | 
			
		||||
		return trap(target, prop.String(), desc), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) definePropertySym(target *Object, prop *Symbol, desc PropertyDescriptor) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.DefinePropertySym; trap != nil {
 | 
			
		||||
		return trap(target, prop, desc), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) hasStr(target *Object, prop unistring.String) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.HasIdx; trap != nil {
 | 
			
		||||
		if idx, ok := strToInt(prop); ok {
 | 
			
		||||
			return trap(target, idx), true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.Has; trap != nil {
 | 
			
		||||
		return trap(target, prop.String()), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) hasIdx(target *Object, prop valueInt) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.HasIdx; trap != nil {
 | 
			
		||||
		return trap(target, toIntStrict(int64(prop))), true
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.Has; trap != nil {
 | 
			
		||||
		return trap(target, prop.String()), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) hasSym(target *Object, prop *Symbol) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.HasSym; trap != nil {
 | 
			
		||||
		return trap(target, prop), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetIdx; trap != nil {
 | 
			
		||||
		if idx, ok := strToInt(prop); ok {
 | 
			
		||||
			return trap(target, idx, receiver), true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.Get; trap != nil {
 | 
			
		||||
		return trap(target, prop.String(), receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getIdx(target *Object, prop valueInt, receiver Value) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetIdx; trap != nil {
 | 
			
		||||
		return trap(target, toIntStrict(int64(prop)), receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.Get; trap != nil {
 | 
			
		||||
		return trap(target, prop.String(), receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) getSym(target *Object, prop *Symbol, receiver Value) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.GetSym; trap != nil {
 | 
			
		||||
		return trap(target, prop, receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.SetIdx; trap != nil {
 | 
			
		||||
		if idx, ok := strToInt(prop); ok {
 | 
			
		||||
			return trap(target, idx, value, receiver), true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.Set; trap != nil {
 | 
			
		||||
		return trap(target, prop.String(), value, receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) setIdx(target *Object, prop valueInt, value Value, receiver Value) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.SetIdx; trap != nil {
 | 
			
		||||
		return trap(target, toIntStrict(int64(prop)), value, receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.Set; trap != nil {
 | 
			
		||||
		return trap(target, prop.String(), value, receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) setSym(target *Object, prop *Symbol, value Value, receiver Value) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.SetSym; trap != nil {
 | 
			
		||||
		return trap(target, prop, value, receiver), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) deleteStr(target *Object, prop unistring.String) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.DeletePropertyIdx; trap != nil {
 | 
			
		||||
		if idx, ok := strToInt(prop); ok {
 | 
			
		||||
			return trap(target, idx), true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.DeleteProperty; trap != nil {
 | 
			
		||||
		return trap(target, prop.String()), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) deleteIdx(target *Object, prop valueInt) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.DeletePropertyIdx; trap != nil {
 | 
			
		||||
		return trap(target, toIntStrict(int64(prop))), true
 | 
			
		||||
	}
 | 
			
		||||
	if trap := h.handler.DeleteProperty; trap != nil {
 | 
			
		||||
		return trap(target, prop.String()), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) deleteSym(target *Object, prop *Symbol) (bool, bool) {
 | 
			
		||||
	if trap := h.handler.DeletePropertySym; trap != nil {
 | 
			
		||||
		return trap(target, prop), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) ownKeys(target *Object) (*Object, bool) {
 | 
			
		||||
	if trap := h.handler.OwnKeys; trap != nil {
 | 
			
		||||
		return trap(target), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) apply(target *Object, this Value, args []Value) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.Apply; trap != nil {
 | 
			
		||||
		return trap(target, this, args), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) construct(target *Object, args []Value, newTarget *Object) (Value, bool) {
 | 
			
		||||
	if trap := h.handler.Construct; trap != nil {
 | 
			
		||||
		return trap(target, args, newTarget), true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *nativeProxyHandler) toObject(runtime *Runtime) *Object {
 | 
			
		||||
	return runtime.ToValue(h.handler).ToObject(runtime)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) proxyHandler {
 | 
			
		||||
	return &nativeProxyHandler{handler: nativeHandler}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProxyTrapConfig provides a simplified Go-friendly API for implementing Proxy traps.
 | 
			
		||||
// If an *Idx trap is defined it gets called for integer property keys, including negative ones. Note that
 | 
			
		||||
// this only includes string property keys that represent a canonical integer
 | 
			
		||||
// (i.e. "0", "123", but not "00", "01", " 1" or "-0").
 | 
			
		||||
// For efficiency strings representing integers exceeding 2^53 are not checked to see if they are canonical,
 | 
			
		||||
// i.e. the *Idx traps will receive "9007199254740993" as well as "9007199254740994", even though the former is not
 | 
			
		||||
// a canonical representation in ECMAScript (Number("9007199254740993") === 9007199254740992).
 | 
			
		||||
// See https://262.ecma-international.org/#sec-canonicalnumericindexstring
 | 
			
		||||
// If an *Idx trap is not set, the corresponding string one is used.
 | 
			
		||||
type ProxyTrapConfig struct {
 | 
			
		||||
	// A trap for Object.getPrototypeOf, Reflect.getPrototypeOf, __proto__, Object.prototype.isPrototypeOf, instanceof
 | 
			
		||||
	GetPrototypeOf func(target *Object) (prototype *Object)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.setPrototypeOf, Reflect.setPrototypeOf
 | 
			
		||||
	SetPrototypeOf func(target *Object, prototype *Object) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.isExtensible, Reflect.isExtensible
 | 
			
		||||
	IsExtensible func(target *Object) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.preventExtensions, Reflect.preventExtensions
 | 
			
		||||
	PreventExtensions func(target *Object) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (string properties)
 | 
			
		||||
	GetOwnPropertyDescriptor func(target *Object, prop string) (propertyDescriptor PropertyDescriptor)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (integer properties)
 | 
			
		||||
	GetOwnPropertyDescriptorIdx func(target *Object, prop int) (propertyDescriptor PropertyDescriptor)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (Symbol properties)
 | 
			
		||||
	GetOwnPropertyDescriptorSym func(target *Object, prop *Symbol) (propertyDescriptor PropertyDescriptor)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.defineProperty, Reflect.defineProperty (string properties)
 | 
			
		||||
	DefineProperty func(target *Object, key string, propertyDescriptor PropertyDescriptor) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.defineProperty, Reflect.defineProperty (integer properties)
 | 
			
		||||
	DefinePropertyIdx func(target *Object, key int, propertyDescriptor PropertyDescriptor) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.defineProperty, Reflect.defineProperty (Symbol properties)
 | 
			
		||||
	DefinePropertySym func(target *Object, key *Symbol, propertyDescriptor PropertyDescriptor) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for the in operator, with operator, Reflect.has (string properties)
 | 
			
		||||
	Has func(target *Object, property string) (available bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for the in operator, with operator, Reflect.has (integer properties)
 | 
			
		||||
	HasIdx func(target *Object, property int) (available bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for the in operator, with operator, Reflect.has (Symbol properties)
 | 
			
		||||
	HasSym func(target *Object, property *Symbol) (available bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for getting property values, Reflect.get (string properties)
 | 
			
		||||
	Get func(target *Object, property string, receiver Value) (value Value)
 | 
			
		||||
 | 
			
		||||
	// A trap for getting property values, Reflect.get (integer properties)
 | 
			
		||||
	GetIdx func(target *Object, property int, receiver Value) (value Value)
 | 
			
		||||
 | 
			
		||||
	// A trap for getting property values, Reflect.get (Symbol properties)
 | 
			
		||||
	GetSym func(target *Object, property *Symbol, receiver Value) (value Value)
 | 
			
		||||
 | 
			
		||||
	// A trap for setting property values, Reflect.set (string properties)
 | 
			
		||||
	Set func(target *Object, property string, value Value, receiver Value) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for setting property values, Reflect.set (integer properties)
 | 
			
		||||
	SetIdx func(target *Object, property int, value Value, receiver Value) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for setting property values, Reflect.set (Symbol properties)
 | 
			
		||||
	SetSym func(target *Object, property *Symbol, value Value, receiver Value) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for the delete operator, Reflect.deleteProperty (string properties)
 | 
			
		||||
	DeleteProperty func(target *Object, property string) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for the delete operator, Reflect.deleteProperty (integer properties)
 | 
			
		||||
	DeletePropertyIdx func(target *Object, property int) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for the delete operator, Reflect.deleteProperty (Symbol properties)
 | 
			
		||||
	DeletePropertySym func(target *Object, property *Symbol) (success bool)
 | 
			
		||||
 | 
			
		||||
	// A trap for Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.keys, Reflect.ownKeys
 | 
			
		||||
	OwnKeys func(target *Object) (object *Object)
 | 
			
		||||
 | 
			
		||||
	// A trap for a function call, Function.prototype.apply, Function.prototype.call, Reflect.apply
 | 
			
		||||
	Apply func(target *Object, this Value, argumentsList []Value) (value Value)
 | 
			
		||||
 | 
			
		||||
	// A trap for the new operator, Reflect.construct
 | 
			
		||||
	Construct func(target *Object, argumentsList []Value, newTarget *Object) (value *Object)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newProxy(args []Value, proto *Object) *Object {
 | 
			
		||||
	if len(args) >= 2 {
 | 
			
		||||
		if target, ok := args[0].(*Object); ok {
 | 
			
		||||
			if proxyHandler, ok := args[1].(*Object); ok {
 | 
			
		||||
				return r.newProxyObject(target, proxyHandler, proto).val
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newProxy(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget == nil {
 | 
			
		||||
		panic(r.needNew("Proxy"))
 | 
			
		||||
	}
 | 
			
		||||
	return r.newProxy(args, r.getPrototypeFromCtor(newTarget, r.getProxy(), r.global.ObjectPrototype))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) NewProxy(target *Object, nativeHandler *ProxyTrapConfig) Proxy {
 | 
			
		||||
	if p, ok := target.self.(*proxyObject); ok {
 | 
			
		||||
		if p.handler == nil {
 | 
			
		||||
			panic(r.NewTypeError("Cannot create proxy with a revoked proxy as target"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	handler := r.newNativeProxyHandler(nativeHandler)
 | 
			
		||||
	proxy := r._newProxyObject(target, handler, nil)
 | 
			
		||||
	return Proxy{proxy: proxy}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_proxy_revocable(call FunctionCall) Value {
 | 
			
		||||
	if len(call.Arguments) >= 2 {
 | 
			
		||||
		if target, ok := call.Argument(0).(*Object); ok {
 | 
			
		||||
			if proxyHandler, ok := call.Argument(1).(*Object); ok {
 | 
			
		||||
				proxy := r.newProxyObject(target, proxyHandler, nil)
 | 
			
		||||
				revoke := r.newNativeFunc(func(FunctionCall) Value {
 | 
			
		||||
					proxy.revoke()
 | 
			
		||||
					return _undefined
 | 
			
		||||
				}, "", 0)
 | 
			
		||||
				ret := r.NewObject()
 | 
			
		||||
				ret.self._putProp("proxy", proxy.val, true, true, true)
 | 
			
		||||
				ret.self._putProp("revoke", revoke, true, true, true)
 | 
			
		||||
				return ret
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createProxy(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeConstructOnly(val, r.builtin_newProxy, nil, "Proxy", 2)
 | 
			
		||||
 | 
			
		||||
	o._putProp("revocable", r.newNativeFunc(r.builtin_proxy_revocable, "revocable", 2), true, false, true)
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getProxy() *Object {
 | 
			
		||||
	ret := r.global.Proxy
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Proxy = ret
 | 
			
		||||
		r.createProxy(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1275
									
								
								goja/builtin_proxy_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1275
									
								
								goja/builtin_proxy_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										140
									
								
								goja/builtin_reflect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								goja/builtin_reflect.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_apply(call FunctionCall) Value {
 | 
			
		||||
	return r.toCallable(call.Argument(0))(FunctionCall{
 | 
			
		||||
		This:      call.Argument(1),
 | 
			
		||||
		Arguments: r.createListFromArrayLike(call.Argument(2))})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) toConstructor(v Value) func(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if ctor := r.toObject(v).self.assertConstructor(); ctor != nil {
 | 
			
		||||
		return ctor
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Value is not a constructor"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_construct(call FunctionCall) Value {
 | 
			
		||||
	target := call.Argument(0)
 | 
			
		||||
	ctor := r.toConstructor(target)
 | 
			
		||||
	var newTarget Value
 | 
			
		||||
	if len(call.Arguments) > 2 {
 | 
			
		||||
		newTarget = call.Argument(2)
 | 
			
		||||
		r.toConstructor(newTarget)
 | 
			
		||||
	} else {
 | 
			
		||||
		newTarget = target
 | 
			
		||||
	}
 | 
			
		||||
	return ctor(r.createListFromArrayLike(call.Argument(1)), r.toObject(newTarget))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_defineProperty(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	key := toPropertyKey(call.Argument(1))
 | 
			
		||||
	desc := r.toPropertyDescriptor(call.Argument(2))
 | 
			
		||||
 | 
			
		||||
	return r.toBoolean(target.defineOwnProperty(key, desc, false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_deleteProperty(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	key := toPropertyKey(call.Argument(1))
 | 
			
		||||
 | 
			
		||||
	return r.toBoolean(target.delete(key, false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_get(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	key := toPropertyKey(call.Argument(1))
 | 
			
		||||
	var receiver Value
 | 
			
		||||
	if len(call.Arguments) > 2 {
 | 
			
		||||
		receiver = call.Arguments[2]
 | 
			
		||||
	}
 | 
			
		||||
	return target.get(key, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_getOwnPropertyDescriptor(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	key := toPropertyKey(call.Argument(1))
 | 
			
		||||
	return r.valuePropToDescriptorObject(target.getOwnProp(key))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_getPrototypeOf(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	if proto := target.self.proto(); proto != nil {
 | 
			
		||||
		return proto
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return _null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_has(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	key := toPropertyKey(call.Argument(1))
 | 
			
		||||
	return r.toBoolean(target.hasProperty(key))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_isExtensible(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	return r.toBoolean(target.self.isExtensible())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_ownKeys(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	return r.newArrayValues(target.self.keys(true, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_preventExtensions(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	return r.toBoolean(target.self.preventExtensions(false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_set(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	var receiver Value
 | 
			
		||||
	if len(call.Arguments) >= 4 {
 | 
			
		||||
		receiver = call.Argument(3)
 | 
			
		||||
	} else {
 | 
			
		||||
		receiver = target
 | 
			
		||||
	}
 | 
			
		||||
	return r.toBoolean(target.set(call.Argument(1), call.Argument(2), receiver, false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_reflect_setPrototypeOf(call FunctionCall) Value {
 | 
			
		||||
	target := r.toObject(call.Argument(0))
 | 
			
		||||
	var proto *Object
 | 
			
		||||
	if arg := call.Argument(1); arg != _null {
 | 
			
		||||
		proto = r.toObject(arg)
 | 
			
		||||
	}
 | 
			
		||||
	return r.toBoolean(target.self.setProto(proto, false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createReflect(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("apply", r.newNativeFunc(r.builtin_reflect_apply, "apply", 3), true, false, true)
 | 
			
		||||
	o._putProp("construct", r.newNativeFunc(r.builtin_reflect_construct, "construct", 2), true, false, true)
 | 
			
		||||
	o._putProp("defineProperty", r.newNativeFunc(r.builtin_reflect_defineProperty, "defineProperty", 3), true, false, true)
 | 
			
		||||
	o._putProp("deleteProperty", r.newNativeFunc(r.builtin_reflect_deleteProperty, "deleteProperty", 2), true, false, true)
 | 
			
		||||
	o._putProp("get", r.newNativeFunc(r.builtin_reflect_get, "get", 2), true, false, true)
 | 
			
		||||
	o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.builtin_reflect_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2), true, false, true)
 | 
			
		||||
	o._putProp("getPrototypeOf", r.newNativeFunc(r.builtin_reflect_getPrototypeOf, "getPrototypeOf", 1), true, false, true)
 | 
			
		||||
	o._putProp("has", r.newNativeFunc(r.builtin_reflect_has, "has", 2), true, false, true)
 | 
			
		||||
	o._putProp("isExtensible", r.newNativeFunc(r.builtin_reflect_isExtensible, "isExtensible", 1), true, false, true)
 | 
			
		||||
	o._putProp("ownKeys", r.newNativeFunc(r.builtin_reflect_ownKeys, "ownKeys", 1), true, false, true)
 | 
			
		||||
	o._putProp("preventExtensions", r.newNativeFunc(r.builtin_reflect_preventExtensions, "preventExtensions", 1), true, false, true)
 | 
			
		||||
	o._putProp("set", r.newNativeFunc(r.builtin_reflect_set, "set", 3), true, false, true)
 | 
			
		||||
	o._putProp("setPrototypeOf", r.newNativeFunc(r.builtin_reflect_setPrototypeOf, "setPrototypeOf", 2), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString("Reflect"), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getReflect() *Object {
 | 
			
		||||
	ret := r.global.Reflect
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Reflect = ret
 | 
			
		||||
		ret.self = r.createReflect(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1348
									
								
								goja/builtin_regexp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1348
									
								
								goja/builtin_regexp.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										346
									
								
								goja/builtin_set.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								goja/builtin_set.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,346 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var setExportType = reflectTypeArray
 | 
			
		||||
 | 
			
		||||
type setObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	m *orderedMap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type setIterObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	iter *orderedMapIter
 | 
			
		||||
	kind iterationKind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *setIterObject) next() Value {
 | 
			
		||||
	if o.iter == nil {
 | 
			
		||||
		return o.val.runtime.createIterResultObject(_undefined, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry := o.iter.next()
 | 
			
		||||
	if entry == nil {
 | 
			
		||||
		o.iter = nil
 | 
			
		||||
		return o.val.runtime.createIterResultObject(_undefined, true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var result Value
 | 
			
		||||
	switch o.kind {
 | 
			
		||||
	case iterationKindValue:
 | 
			
		||||
		result = entry.key
 | 
			
		||||
	default:
 | 
			
		||||
		result = o.val.runtime.newArrayValues([]Value{entry.key, entry.key})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.val.runtime.createIterResultObject(result, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (so *setObject) init() {
 | 
			
		||||
	so.baseObject.init()
 | 
			
		||||
	so.m = newOrderedMap(so.val.runtime.getHash())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (so *setObject) exportType() reflect.Type {
 | 
			
		||||
	return setExportType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (so *setObject) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	a := make([]interface{}, so.m.size)
 | 
			
		||||
	ctx.put(so.val, a)
 | 
			
		||||
	iter := so.m.newIter()
 | 
			
		||||
	for i := 0; i < len(a); i++ {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		a[i] = exportValue(entry.key, ctx)
 | 
			
		||||
	}
 | 
			
		||||
	return a
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (so *setObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	l := so.m.size
 | 
			
		||||
	if typ.Kind() == reflect.Array {
 | 
			
		||||
		if dst.Len() != l {
 | 
			
		||||
			return fmt.Errorf("cannot convert a Set into an array, lengths mismatch: have %d, need %d)", l, dst.Len())
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		dst.Set(reflect.MakeSlice(typ, l, l))
 | 
			
		||||
	}
 | 
			
		||||
	ctx.putTyped(so.val, typ, dst.Interface())
 | 
			
		||||
	iter := so.m.newIter()
 | 
			
		||||
	r := so.val.runtime
 | 
			
		||||
	for i := 0; i < l; i++ {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		err := r.toReflectValue(entry.key, dst.Index(i), ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (so *setObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	dst.Set(reflect.MakeMap(typ))
 | 
			
		||||
	keyTyp := typ.Key()
 | 
			
		||||
	elemTyp := typ.Elem()
 | 
			
		||||
	iter := so.m.newIter()
 | 
			
		||||
	r := so.val.runtime
 | 
			
		||||
	for {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		keyVal := reflect.New(keyTyp).Elem()
 | 
			
		||||
		err := r.toReflectValue(entry.key, keyVal, ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		dst.SetMapIndex(keyVal, reflect.Zero(elemTyp))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_add(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	so, ok := thisObj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Set.prototype.add called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	so.m.set(call.Argument(0), nil)
 | 
			
		||||
	return call.This
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_clear(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	so, ok := thisObj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Set.prototype.clear called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	so.m.clear()
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_delete(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	so, ok := thisObj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Set.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.toBoolean(so.m.remove(call.Argument(0)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_entries(call FunctionCall) Value {
 | 
			
		||||
	return r.createSetIterator(call.This, iterationKindKeyValue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_forEach(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	so, ok := thisObj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Set.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable()
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("object is not a function %s"))
 | 
			
		||||
	}
 | 
			
		||||
	t := call.Argument(1)
 | 
			
		||||
	iter := so.m.newIter()
 | 
			
		||||
	for {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		callbackFn(FunctionCall{This: t, Arguments: []Value{entry.key, entry.key, thisObj}})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_has(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	so, ok := thisObj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method Set.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.toBoolean(so.m.has(call.Argument(0)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_getSize(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	so, ok := thisObj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method get Set.prototype.size called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return intToValue(int64(so.m.size))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setProto_values(call FunctionCall) Value {
 | 
			
		||||
	return r.createSetIterator(call.This, iterationKindValue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newSet(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget == nil {
 | 
			
		||||
		panic(r.needNew("Set"))
 | 
			
		||||
	}
 | 
			
		||||
	proto := r.getPrototypeFromCtor(newTarget, r.global.Set, r.global.SetPrototype)
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	so := &setObject{}
 | 
			
		||||
	so.class = classObject
 | 
			
		||||
	so.val = o
 | 
			
		||||
	so.extensible = true
 | 
			
		||||
	o.self = so
 | 
			
		||||
	so.prototype = proto
 | 
			
		||||
	so.init()
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
 | 
			
		||||
			adder := so.getStr("add", nil)
 | 
			
		||||
			stdArr := r.checkStdArrayIter(arg)
 | 
			
		||||
			if adder == r.global.setAdder {
 | 
			
		||||
				if stdArr != nil {
 | 
			
		||||
					for _, v := range stdArr.values {
 | 
			
		||||
						so.m.set(v, nil)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					r.getIterator(arg, nil).iterate(func(item Value) {
 | 
			
		||||
						so.m.set(item, nil)
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				adderFn := toMethod(adder)
 | 
			
		||||
				if adderFn == nil {
 | 
			
		||||
					panic(r.NewTypeError("Set.add in missing"))
 | 
			
		||||
				}
 | 
			
		||||
				if stdArr != nil {
 | 
			
		||||
					for _, item := range stdArr.values {
 | 
			
		||||
						adderFn(FunctionCall{This: o, Arguments: []Value{item}})
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					r.getIterator(arg, nil).iterate(func(item Value) {
 | 
			
		||||
						adderFn(FunctionCall{This: o, Arguments: []Value{item}})
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createSetIterator(setValue Value, kind iterationKind) Value {
 | 
			
		||||
	obj := r.toObject(setValue)
 | 
			
		||||
	setObj, ok := obj.self.(*setObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Object is not a Set"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	si := &setIterObject{
 | 
			
		||||
		iter: setObj.m.newIter(),
 | 
			
		||||
		kind: kind,
 | 
			
		||||
	}
 | 
			
		||||
	si.class = classObject
 | 
			
		||||
	si.val = o
 | 
			
		||||
	si.extensible = true
 | 
			
		||||
	o.self = si
 | 
			
		||||
	si.prototype = r.getSetIteratorPrototype()
 | 
			
		||||
	si.init()
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) setIterProto_next(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	if iter, ok := thisObj.self.(*setIterObject); ok {
 | 
			
		||||
		return iter.next()
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Method Set Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createSetProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getSet(), true, false, true)
 | 
			
		||||
	r.global.setAdder = r.newNativeFunc(r.setProto_add, "add", 1)
 | 
			
		||||
	o._putProp("add", r.global.setAdder, true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putProp("clear", r.newNativeFunc(r.setProto_clear, "clear", 0), true, false, true)
 | 
			
		||||
	o._putProp("delete", r.newNativeFunc(r.setProto_delete, "delete", 1), true, false, true)
 | 
			
		||||
	o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, "forEach", 1), true, false, true)
 | 
			
		||||
	o._putProp("has", r.newNativeFunc(r.setProto_has, "has", 1), true, false, true)
 | 
			
		||||
	o.setOwnStr("size", &valueProperty{
 | 
			
		||||
		getterFunc:   r.newNativeFunc(r.setProto_getSize, "get size", 0),
 | 
			
		||||
		accessor:     true,
 | 
			
		||||
		writable:     true,
 | 
			
		||||
		configurable: true,
 | 
			
		||||
	}, true)
 | 
			
		||||
 | 
			
		||||
	valuesFunc := r.newNativeFunc(r.setProto_values, "values", 0)
 | 
			
		||||
	o._putProp("values", valuesFunc, true, false, true)
 | 
			
		||||
	o._putProp("keys", valuesFunc, true, false, true)
 | 
			
		||||
	o._putProp("entries", r.newNativeFunc(r.setProto_entries, "entries", 0), true, false, true)
 | 
			
		||||
	o._putSym(SymIterator, valueProp(valuesFunc, true, false, true))
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classSet), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createSet(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeConstructOnly(val, r.builtin_newSet, r.getSetPrototype(), "Set", 0)
 | 
			
		||||
	r.putSpeciesReturnThis(o)
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createSetIterProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("next", r.newNativeFunc(r.setIterProto_next, "next", 0), true, false, true)
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classSetIterator), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getSetIteratorPrototype() *Object {
 | 
			
		||||
	var o *Object
 | 
			
		||||
	if o = r.global.SetIteratorPrototype; o == nil {
 | 
			
		||||
		o = &Object{runtime: r}
 | 
			
		||||
		r.global.SetIteratorPrototype = o
 | 
			
		||||
		o.self = r.createSetIterProto(o)
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getSetPrototype() *Object {
 | 
			
		||||
	ret := r.global.SetPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.SetPrototype = ret
 | 
			
		||||
		ret.self = r.createSetProto(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getSet() *Object {
 | 
			
		||||
	ret := r.global.Set
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Set = ret
 | 
			
		||||
		ret.self = r.createSet(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										180
									
								
								goja/builtin_set_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								goja/builtin_set_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,180 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSetEvilIterator(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var o = {};
 | 
			
		||||
	o[Symbol.iterator] = function() {
 | 
			
		||||
		return {
 | 
			
		||||
			next: function() {
 | 
			
		||||
				if (!this.flag) {
 | 
			
		||||
					this.flag = true;
 | 
			
		||||
					return {};
 | 
			
		||||
				}
 | 
			
		||||
				return {done: true};
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	new Set(o);
 | 
			
		||||
	undefined;
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_setToMap() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	s, err := vm.RunString(`
 | 
			
		||||
	new Set([1, 2, 3])
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	m := make(map[int]struct{})
 | 
			
		||||
	err = vm.ExportTo(s, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(m)
 | 
			
		||||
	// Output: map[1:{} 2:{} 3:{}]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_setToSlice() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	s, err := vm.RunString(`
 | 
			
		||||
	new Set([1, 2, 3])
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	var a []int
 | 
			
		||||
	err = vm.ExportTo(s, &a)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(a)
 | 
			
		||||
	// Output: [1 2 3]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetExportToSliceCircular(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	s, err := vm.RunString(`
 | 
			
		||||
	let s = new Set();
 | 
			
		||||
	s.add(s);
 | 
			
		||||
	s;
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	var a []Value
 | 
			
		||||
	err = vm.ExportTo(s, &a)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(a) != 1 {
 | 
			
		||||
		t.Fatalf("len: %d", len(a))
 | 
			
		||||
	}
 | 
			
		||||
	if a[0] != s {
 | 
			
		||||
		t.Fatalf("a: %v", a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetExportToArrayMismatchedLengths(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	s, err := vm.RunString(`
 | 
			
		||||
	new Set([1, 2])
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	var s1 [3]int
 | 
			
		||||
	err = vm.ExportTo(s, &s1)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("expected error")
 | 
			
		||||
	}
 | 
			
		||||
	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetExportToNilMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	var m map[int]interface{}
 | 
			
		||||
	res, err := vm.RunString("new Set([1])")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = vm.ExportTo(res, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(m) != 1 {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
	if _, exists := m[1]; !exists {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetExportToNonNilMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[int]interface{}{
 | 
			
		||||
		2: true,
 | 
			
		||||
	}
 | 
			
		||||
	res, err := vm.RunString("new Set([1])")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = vm.ExportTo(res, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(m) != 1 {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
	if _, exists := m[1]; !exists {
 | 
			
		||||
		t.Fatal(m)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetGetAdderGetIteratorOrder(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	let getterCalled = 0;
 | 
			
		||||
 | 
			
		||||
	class S extends Set {
 | 
			
		||||
	    get add() {
 | 
			
		||||
	        getterCalled++;
 | 
			
		||||
	        return null;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let getIteratorCalled = 0;
 | 
			
		||||
 | 
			
		||||
	let iterable = {};
 | 
			
		||||
	iterable[Symbol.iterator] = () => {
 | 
			
		||||
	    getIteratorCalled++
 | 
			
		||||
	    return {
 | 
			
		||||
	        next: 1
 | 
			
		||||
	    };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let thrown = false;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
	    new S(iterable);
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
	    if (e instanceof TypeError) {
 | 
			
		||||
	        thrown = true;
 | 
			
		||||
	    } else {
 | 
			
		||||
	        throw e;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	thrown && getterCalled === 1 && getIteratorCalled === 0;
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1112
									
								
								goja/builtin_string.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1112
									
								
								goja/builtin_string.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										288
									
								
								goja/builtin_string_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								goja/builtin_string_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,288 @@
 | 
			
		||||
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'
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
assert.sameValue('A—', String.fromCharCode(65, 0x2014));
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStringMatchSym(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
function Prefix(p) {
 | 
			
		||||
	this.p = p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Prefix.prototype[Symbol.match] = function(s) {
 | 
			
		||||
	return s.substring(0, this.p.length) === this.p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var prefix1 = new Prefix("abc");
 | 
			
		||||
var prefix2 = new Prefix("def");
 | 
			
		||||
 | 
			
		||||
"abc123".match(prefix1) === true && "abc123".match(prefix2) === false &&
 | 
			
		||||
"def123".match(prefix1) === false && "def123".match(prefix2) === true;
 | 
			
		||||
`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStringMatchAllSym(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
function Prefix(p) {
 | 
			
		||||
	this.p = p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Prefix.prototype[Symbol.matchAll] = function(s) {
 | 
			
		||||
	return s.substring(0, this.p.length) === this.p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var prefix1 = new Prefix("abc");
 | 
			
		||||
var prefix2 = new Prefix("def");
 | 
			
		||||
 | 
			
		||||
"abc123".matchAll(prefix1) === true && "abc123".matchAll(prefix2) === false &&
 | 
			
		||||
"def123".matchAll(prefix1) === false && "def123".matchAll(prefix2) === true;
 | 
			
		||||
`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGenericSplitter(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
function MyRegexp(pattern, flags) {
 | 
			
		||||
	if (pattern instanceof MyRegexp) {
 | 
			
		||||
		pattern = pattern.wrapped;
 | 
			
		||||
	}
 | 
			
		||||
	this.wrapped = new RegExp(pattern, flags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MyRegexp.prototype.exec = function() {
 | 
			
		||||
	return this.wrapped.exec.apply(this.wrapped, arguments);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Object.defineProperty(MyRegexp.prototype, "lastIndex", {
 | 
			
		||||
	get: function() {
 | 
			
		||||
		return this.wrapped.lastIndex;
 | 
			
		||||
	},
 | 
			
		||||
	set: function(v) {
 | 
			
		||||
		this.wrapped.lastIndex = v;
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Object.defineProperty(MyRegexp.prototype, "flags", {
 | 
			
		||||
	get: function() {
 | 
			
		||||
		return this.wrapped.flags;
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
MyRegexp[Symbol.species] = MyRegexp;
 | 
			
		||||
MyRegexp.prototype[Symbol.split] = RegExp.prototype[Symbol.split];
 | 
			
		||||
 | 
			
		||||
var r = new MyRegexp(/ /);
 | 
			
		||||
var res = "a b c".split(r);
 | 
			
		||||
res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c";
 | 
			
		||||
`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStringIterSurrPair(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
var lo = '\uD834';
 | 
			
		||||
var hi = '\uDF06';
 | 
			
		||||
var pair = lo + hi;
 | 
			
		||||
var string = 'a' + pair + 'b' + lo + pair + hi + lo;
 | 
			
		||||
var iterator = string[Symbol.iterator]();
 | 
			
		||||
var result;
 | 
			
		||||
 | 
			
		||||
result = iterator.next();
 | 
			
		||||
if (result.value !== 'a') {
 | 
			
		||||
	throw new Error("at 0: " + result.value);
 | 
			
		||||
}
 | 
			
		||||
result = iterator.next();
 | 
			
		||||
if (result.value !== pair) {
 | 
			
		||||
	throw new Error("at 1: " + result.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValueStringBuilder(t *testing.T) {
 | 
			
		||||
	t.Run("substringASCII", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		str := newStringValue("a\U00010000b")
 | 
			
		||||
		sb.WriteSubstring(str, 0, 1)
 | 
			
		||||
		res := sb.String()
 | 
			
		||||
		if res != asciiString("a") {
 | 
			
		||||
			t.Fatal(res)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("substringASCIIPure", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		str := newStringValue("ab")
 | 
			
		||||
		sb.WriteSubstring(str, 0, 1)
 | 
			
		||||
		res := sb.String()
 | 
			
		||||
		if res != asciiString("a") {
 | 
			
		||||
			t.Fatal(res)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("substringUnicode", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		str := newStringValue("a\U00010000b")
 | 
			
		||||
		sb.WriteSubstring(str, 1, 3)
 | 
			
		||||
		res := sb.String()
 | 
			
		||||
		if !res.SameAs(unicodeStringFromRunes([]rune{0x10000})) {
 | 
			
		||||
			t.Fatal(res)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("substringASCIIUnicode", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		str := newStringValue("a\U00010000b")
 | 
			
		||||
		sb.WriteSubstring(str, 0, 2)
 | 
			
		||||
		res := sb.String()
 | 
			
		||||
		if !res.SameAs(unicodeStringFromRunes([]rune{'a', 0xD800})) {
 | 
			
		||||
			t.Fatal(res)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("substringUnicodeASCII", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		str := newStringValue("a\U00010000b")
 | 
			
		||||
		sb.WriteSubstring(str, 2, 4)
 | 
			
		||||
		res := sb.String()
 | 
			
		||||
		if !res.SameAs(unicodeStringFromRunes([]rune{0xDC00, 'b'})) {
 | 
			
		||||
			t.Fatal(res)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("concatSubstringUnicodeASCII", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		sb.WriteString(newStringValue("юникод"))
 | 
			
		||||
		sb.WriteSubstring(asciiString(" ascii"), 0, 6)
 | 
			
		||||
		if res := sb.String(); !res.SameAs(newStringValue("юникод ascii")) {
 | 
			
		||||
			t.Fatal(res)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("concat_ASCII_importedASCII", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		sb.WriteString(asciiString("ascii"))
 | 
			
		||||
		sb.WriteString(&importedString{s: " imported_ascii1234567890"})
 | 
			
		||||
		s := sb.String()
 | 
			
		||||
		if res, ok := s.(asciiString); !ok || res != "ascii imported_ascii1234567890" {
 | 
			
		||||
			t.Fatal(s)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("concat_ASCII_importedUnicode", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		var sb StringBuilder
 | 
			
		||||
		sb.WriteString(asciiString("ascii"))
 | 
			
		||||
		sb.WriteString(&importedString{s: " imported_юникод"})
 | 
			
		||||
		s := sb.String()
 | 
			
		||||
		if res, ok := s.(unicodeString); !ok || !res.SameAs(newStringValue("ascii imported_юникод")) {
 | 
			
		||||
			t.Fatal(s)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStringSplit(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert(compareArray("".split("#",2), [""]));
 | 
			
		||||
	assert(compareArray("".split("#"), [""]));
 | 
			
		||||
	assert(compareArray("".split("",2), []));
 | 
			
		||||
	assert(compareArray("".split(""), []));
 | 
			
		||||
`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										177
									
								
								goja/builtin_symbol.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								goja/builtin_symbol.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import "github.com/dop251/goja/unistring"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	SymHasInstance        = newSymbol(asciiString("Symbol.hasInstance"))
 | 
			
		||||
	SymIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable"))
 | 
			
		||||
	SymIterator           = newSymbol(asciiString("Symbol.iterator"))
 | 
			
		||||
	SymMatch              = newSymbol(asciiString("Symbol.match"))
 | 
			
		||||
	SymMatchAll           = newSymbol(asciiString("Symbol.matchAll"))
 | 
			
		||||
	SymReplace            = newSymbol(asciiString("Symbol.replace"))
 | 
			
		||||
	SymSearch             = newSymbol(asciiString("Symbol.search"))
 | 
			
		||||
	SymSpecies            = newSymbol(asciiString("Symbol.species"))
 | 
			
		||||
	SymSplit              = newSymbol(asciiString("Symbol.split"))
 | 
			
		||||
	SymToPrimitive        = newSymbol(asciiString("Symbol.toPrimitive"))
 | 
			
		||||
	SymToStringTag        = newSymbol(asciiString("Symbol.toStringTag"))
 | 
			
		||||
	SymUnscopables        = newSymbol(asciiString("Symbol.unscopables"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_symbol(call FunctionCall) Value {
 | 
			
		||||
	var desc String
 | 
			
		||||
	if arg := call.Argument(0); !IsUndefined(arg) {
 | 
			
		||||
		desc = arg.toString()
 | 
			
		||||
	}
 | 
			
		||||
	return newSymbol(desc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
 | 
			
		||||
	sym, ok := call.This.(*Symbol)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		if obj, ok := call.This.(*Object); ok {
 | 
			
		||||
			if v, ok := obj.self.(*primitiveValueObject); ok {
 | 
			
		||||
				if sym1, ok := v.pValue.(*Symbol); ok {
 | 
			
		||||
					sym = sym1
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if sym == nil {
 | 
			
		||||
		panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver"))
 | 
			
		||||
	}
 | 
			
		||||
	return sym.descriptiveString()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value {
 | 
			
		||||
	_, ok := call.This.(*Symbol)
 | 
			
		||||
	if ok {
 | 
			
		||||
		return call.This
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if obj, ok := call.This.(*Object); ok {
 | 
			
		||||
		if v, ok := obj.self.(*primitiveValueObject); ok {
 | 
			
		||||
			if sym, ok := v.pValue.(*Symbol); ok {
 | 
			
		||||
				return sym
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	panic(r.NewTypeError("Symbol.prototype.valueOf requires that 'this' be a Symbol"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) symbol_for(call FunctionCall) Value {
 | 
			
		||||
	key := call.Argument(0).toString()
 | 
			
		||||
	keyStr := key.string()
 | 
			
		||||
	if v := r.symbolRegistry[keyStr]; v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	if r.symbolRegistry == nil {
 | 
			
		||||
		r.symbolRegistry = make(map[unistring.String]*Symbol)
 | 
			
		||||
	}
 | 
			
		||||
	v := newSymbol(key)
 | 
			
		||||
	r.symbolRegistry[keyStr] = v
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) symbol_keyfor(call FunctionCall) Value {
 | 
			
		||||
	arg := call.Argument(0)
 | 
			
		||||
	sym, ok := arg.(*Symbol)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("%s is not a symbol", arg.String()))
 | 
			
		||||
	}
 | 
			
		||||
	for key, s := range r.symbolRegistry {
 | 
			
		||||
		if s == sym {
 | 
			
		||||
			return stringValueFromRaw(key)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return _undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) thisSymbolValue(v Value) *Symbol {
 | 
			
		||||
	if sym, ok := v.(*Symbol); ok {
 | 
			
		||||
		return sym
 | 
			
		||||
	}
 | 
			
		||||
	if obj, ok := v.(*Object); ok {
 | 
			
		||||
		if pVal, ok := obj.self.(*primitiveValueObject); ok {
 | 
			
		||||
			if sym, ok := pVal.pValue.(*Symbol); ok {
 | 
			
		||||
				return sym
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	panic(r.NewTypeError("Value is not a Symbol"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createSymbolProto(val *Object) objectImpl {
 | 
			
		||||
	o := &baseObject{
 | 
			
		||||
		class:      classObject,
 | 
			
		||||
		val:        val,
 | 
			
		||||
		extensible: true,
 | 
			
		||||
		prototype:  r.global.ObjectPrototype,
 | 
			
		||||
	}
 | 
			
		||||
	o.init()
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getSymbol(), true, false, true)
 | 
			
		||||
	o.setOwnStr("description", &valueProperty{
 | 
			
		||||
		configurable: true,
 | 
			
		||||
		getterFunc: r.newNativeFunc(func(call FunctionCall) Value {
 | 
			
		||||
			return r.thisSymbolValue(call.This).desc
 | 
			
		||||
		}, "get description", 0),
 | 
			
		||||
		accessor: true,
 | 
			
		||||
	}, false)
 | 
			
		||||
	o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, "toString", 0), true, false, true)
 | 
			
		||||
	o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, "valueOf", 0), true, false, true)
 | 
			
		||||
	o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, "[Symbol.toPrimitive]", 1), false, false, true))
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(newStringValue("Symbol"), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createSymbol(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeFuncAndConstruct(val, r.builtin_symbol, func(args []Value, newTarget *Object) *Object {
 | 
			
		||||
		panic(r.NewTypeError("Symbol is not a constructor"))
 | 
			
		||||
	}, r.getSymbolPrototype(), "Symbol", _positiveZero)
 | 
			
		||||
 | 
			
		||||
	o._putProp("for", r.newNativeFunc(r.symbol_for, "for", 1), true, false, true)
 | 
			
		||||
	o._putProp("keyFor", r.newNativeFunc(r.symbol_keyfor, "keyFor", 1), true, false, true)
 | 
			
		||||
 | 
			
		||||
	for _, s := range []*Symbol{
 | 
			
		||||
		SymHasInstance,
 | 
			
		||||
		SymIsConcatSpreadable,
 | 
			
		||||
		SymIterator,
 | 
			
		||||
		SymMatch,
 | 
			
		||||
		SymMatchAll,
 | 
			
		||||
		SymReplace,
 | 
			
		||||
		SymSearch,
 | 
			
		||||
		SymSpecies,
 | 
			
		||||
		SymSplit,
 | 
			
		||||
		SymToPrimitive,
 | 
			
		||||
		SymToStringTag,
 | 
			
		||||
		SymUnscopables,
 | 
			
		||||
	} {
 | 
			
		||||
		n := s.desc.(asciiString)
 | 
			
		||||
		n = n[len("Symbol."):]
 | 
			
		||||
		o._putProp(unistring.String(n), s, false, false, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getSymbolPrototype() *Object {
 | 
			
		||||
	ret := r.global.SymbolPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.SymbolPrototype = ret
 | 
			
		||||
		ret.self = r.createSymbolProto(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getSymbol() *Object {
 | 
			
		||||
	ret := r.global.Symbol
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.Symbol = ret
 | 
			
		||||
		ret.self = r.createSymbol(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1973
									
								
								goja/builtin_typedarrays.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1973
									
								
								goja/builtin_typedarrays.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										326
									
								
								goja/builtin_typedarrays_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								goja/builtin_typedarrays_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,326 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
func TestArrayBufferNew(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var b = new ArrayBuffer(16);
 | 
			
		||||
	b.byteLength;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, intToValue(16), t)
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
func TestArrayBufferSetUint32(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
 | 
			
		||||
	b.data = make([]byte, 4)
 | 
			
		||||
	b.setUint32(0, 0xCAFEBABE, bigEndian)
 | 
			
		||||
 | 
			
		||||
	i := b.getUint32(0, bigEndian)
 | 
			
		||||
	if i != 0xCAFEBABE {
 | 
			
		||||
		t.Fatal(i)
 | 
			
		||||
	}
 | 
			
		||||
	i = b.getUint32(0, littleEndian)
 | 
			
		||||
	if i != 0xBEBAFECA {
 | 
			
		||||
		t.Fatal(i)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.setUint32(0, 0xBEBAFECA, littleEndian)
 | 
			
		||||
	i = b.getUint32(0, bigEndian)
 | 
			
		||||
	if i != 0xCAFEBABE {
 | 
			
		||||
		t.Fatal(i)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestArrayBufferSetInt32(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
 | 
			
		||||
	b.data = make([]byte, 4)
 | 
			
		||||
	b.setInt32(0, -42, littleEndian)
 | 
			
		||||
	if v := b.getInt32(0, littleEndian); v != -42 {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.setInt32(0, -42, bigEndian)
 | 
			
		||||
	if v := b.getInt32(0, bigEndian); v != -42 {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewUint8Array(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a = new Uint8Array(1);
 | 
			
		||||
	a[0] = 42;
 | 
			
		||||
	a.byteLength === 1 && a.length === 1 && a[0] === 42;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewUint16Array(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a = new Uint16Array(1);
 | 
			
		||||
	a[0] = 42;
 | 
			
		||||
	a.byteLength === 2 && a.length === 1 && a[0] === 42;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraysSpeciesConstructor(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
    'use strict';
 | 
			
		||||
    function MyArray() {
 | 
			
		||||
        var NewTarget = this.__proto__.constructor;
 | 
			
		||||
        return Reflect.construct(Uint16Array, arguments, NewTarget);
 | 
			
		||||
    }
 | 
			
		||||
    MyArray.prototype = Object.create(Uint16Array.prototype, {
 | 
			
		||||
        constructor: {
 | 
			
		||||
            value: MyArray,
 | 
			
		||||
            writable: true,
 | 
			
		||||
            configurable: true
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    var a = new MyArray(1);
 | 
			
		||||
    Object.defineProperty(MyArray, Symbol.species, {value: Uint8Array, configurable: true});
 | 
			
		||||
    a[0] = 32767;
 | 
			
		||||
    var b = a.filter(function() {
 | 
			
		||||
        return true;
 | 
			
		||||
    });
 | 
			
		||||
	if (a[0] !== 32767) {
 | 
			
		||||
		throw new Error("a[0]=" + a[0]); 
 | 
			
		||||
	}
 | 
			
		||||
	if (!(b instanceof Uint8Array)) {
 | 
			
		||||
		throw new Error("b instanceof Uint8Array");
 | 
			
		||||
	}
 | 
			
		||||
	if (b[0] != 255) {
 | 
			
		||||
		throw new Error("b[0]=" + b[0]);
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArrayFromArrayBuffer(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(2);
 | 
			
		||||
	var a16 = new Uint16Array(buf);
 | 
			
		||||
	if (!(a16 instanceof Uint16Array)) {
 | 
			
		||||
		throw new Error("a16 is not an instance");
 | 
			
		||||
	}
 | 
			
		||||
	if (a16.buffer !== buf) {
 | 
			
		||||
		throw new Error("a16.buffer !== buf");
 | 
			
		||||
	}
 | 
			
		||||
	if (a16.length !== 1) {
 | 
			
		||||
		throw new Error("a16.length=" + a16.length);
 | 
			
		||||
	}
 | 
			
		||||
	var a8 = new Uint8Array(buf);
 | 
			
		||||
	a8.fill(0xAA);
 | 
			
		||||
	if (a16[0] !== 0xAAAA) {
 | 
			
		||||
		throw new Error("a16[0]=" + a16[0]);
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetOverlapDifSize(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(4);
 | 
			
		||||
	var src = new Uint8Array(buf, 1, 2);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	var dst = new Uint16Array(buf);
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetOverlapDifSize2(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(4);
 | 
			
		||||
	var src = new Uint8Array(buf, 0, 2);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	var dst = new Uint16Array(buf);
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetOverlapDifSize3(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(8);
 | 
			
		||||
	var src = new Uint8Array(buf, 2, 4);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	src[2] = 3;
 | 
			
		||||
	src[3] = 4;
 | 
			
		||||
	var dst = new Uint16Array(buf);
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetOverlapDifSize4(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(10);
 | 
			
		||||
	var dst = new Uint8Array(buf, 2, 5);
 | 
			
		||||
	var src = new Uint16Array(buf);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	src[2] = 3;
 | 
			
		||||
	src[3] = 4;
 | 
			
		||||
	src[4] = 5;
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4 || dst[4] !== 5) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetNoOverlapDifSizeForward(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(10);
 | 
			
		||||
	var dst = new Uint8Array(buf, 7, 2);
 | 
			
		||||
	var src = new Uint16Array(buf, 0, 2);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetNoOverlapDifSizeBackward(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var buf = new ArrayBuffer(10);
 | 
			
		||||
	var dst = new Uint8Array(buf, 0, 2);
 | 
			
		||||
	var src = new Uint16Array(buf, 6, 2);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySetNoOverlapDifSizeDifBuffers(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var dstBuf = new ArrayBuffer(1024);
 | 
			
		||||
	var dst = new Uint8Array(dstBuf, 0, 2);
 | 
			
		||||
	var src = new Uint16Array(2);
 | 
			
		||||
	src[0] = 1;
 | 
			
		||||
	src[1] = 2;
 | 
			
		||||
	dst.set(src);
 | 
			
		||||
	if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySliceSameType(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var src = Uint8Array.of(1,2,3,4);
 | 
			
		||||
	var dst = src.slice(1, 3);
 | 
			
		||||
	if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySliceDifType(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var src = Uint8Array.of(1,2,3,4);
 | 
			
		||||
	Object.defineProperty(Uint8Array, Symbol.species, {value: Uint16Array, configurable: true});
 | 
			
		||||
	var dst = src.slice(1, 3);
 | 
			
		||||
	if (!(dst instanceof Uint16Array)) {
 | 
			
		||||
		throw new Error("wrong dst type: " + dst);
 | 
			
		||||
	}
 | 
			
		||||
	if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) {
 | 
			
		||||
		throw new Error("dst: " + dst.join(","));
 | 
			
		||||
	}	
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySortComparatorReturnValueFloats(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a = Float64Array.of(
 | 
			
		||||
		5.97,
 | 
			
		||||
		9.91,
 | 
			
		||||
		4.13,
 | 
			
		||||
		9.28,
 | 
			
		||||
		3.29
 | 
			
		||||
	);
 | 
			
		||||
	a.sort( function(a, b) { return a - b; } );
 | 
			
		||||
	for (var i = 1; i < a.length; i++) {
 | 
			
		||||
		if (a[i] < a[i-1]) {
 | 
			
		||||
			throw new Error("Array is not sorted: " + a);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArraySortComparatorReturnValueNegZero(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var a = new Uint8Array([2, 1]);
 | 
			
		||||
	a.sort( function(a, b) { return a > b ? 0 : -0; } );
 | 
			
		||||
	for (var i = 1; i < a.length; i++) {
 | 
			
		||||
		if (a[i] < a[i-1]) {
 | 
			
		||||
			throw new Error("Array is not sorted: " + a);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInt32ArrayNegativeIndex(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	new Int32Array()[-1] === undefined;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTypedArrayDeleteUnconfigurable(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	try {
 | 
			
		||||
		(function() {
 | 
			
		||||
			'use strict';
 | 
			
		||||
			delete Uint8Array.prototype.BYTES_PER_ELEMENT;
 | 
			
		||||
		})();
 | 
			
		||||
	} catch(e) {
 | 
			
		||||
		if (!(e instanceof TypeError)) {
 | 
			
		||||
			throw e;
 | 
			
		||||
		}
 | 
			
		||||
		if (!e.message.startsWith("Cannot delete property")) {
 | 
			
		||||
			throw e;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								goja/builtin_weakmap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								goja/builtin_weakmap.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
type weakMap uint64
 | 
			
		||||
 | 
			
		||||
type weakMapObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	m weakMap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wmo *weakMapObject) init() {
 | 
			
		||||
	wmo.baseObject.init()
 | 
			
		||||
	wmo.m = weakMap(wmo.val.runtime.genId())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wm weakMap) set(key *Object, value Value) {
 | 
			
		||||
	key.getWeakRefs()[wm] = value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wm weakMap) get(key *Object) Value {
 | 
			
		||||
	return key.weakRefs[wm]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wm weakMap) remove(key *Object) bool {
 | 
			
		||||
	if _, exists := key.weakRefs[wm]; exists {
 | 
			
		||||
		delete(key.weakRefs, wm)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wm weakMap) has(key *Object) bool {
 | 
			
		||||
	_, exists := key.weakRefs[wm]
 | 
			
		||||
	return exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakMapProto_delete(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wmo, ok := thisObj.self.(*weakMapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakMap.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	key, ok := call.Argument(0).(*Object)
 | 
			
		||||
	if ok && wmo.m.remove(key) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakMapProto_get(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wmo, ok := thisObj.self.(*weakMapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakMap.prototype.get called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	var res Value
 | 
			
		||||
	if key, ok := call.Argument(0).(*Object); ok {
 | 
			
		||||
		res = wmo.m.get(key)
 | 
			
		||||
	}
 | 
			
		||||
	if res == nil {
 | 
			
		||||
		return _undefined
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakMapProto_has(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wmo, ok := thisObj.self.(*weakMapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakMap.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	key, ok := call.Argument(0).(*Object)
 | 
			
		||||
	if ok && wmo.m.has(key) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakMapProto_set(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wmo, ok := thisObj.self.(*weakMapObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakMap.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	key := r.toObject(call.Argument(0))
 | 
			
		||||
	wmo.m.set(key, call.Argument(1))
 | 
			
		||||
	return call.This
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) needNew(name string) *Object {
 | 
			
		||||
	return r.NewTypeError("Constructor %s requires 'new'", name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newWeakMap(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget == nil {
 | 
			
		||||
		panic(r.needNew("WeakMap"))
 | 
			
		||||
	}
 | 
			
		||||
	proto := r.getPrototypeFromCtor(newTarget, r.global.WeakMap, r.global.WeakMapPrototype)
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	wmo := &weakMapObject{}
 | 
			
		||||
	wmo.class = classObject
 | 
			
		||||
	wmo.val = o
 | 
			
		||||
	wmo.extensible = true
 | 
			
		||||
	o.self = wmo
 | 
			
		||||
	wmo.prototype = proto
 | 
			
		||||
	wmo.init()
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
 | 
			
		||||
			adder := wmo.getStr("set", nil)
 | 
			
		||||
			adderFn := toMethod(adder)
 | 
			
		||||
			if adderFn == nil {
 | 
			
		||||
				panic(r.NewTypeError("WeakMap.set in missing"))
 | 
			
		||||
			}
 | 
			
		||||
			iter := r.getIterator(arg, nil)
 | 
			
		||||
			i0 := valueInt(0)
 | 
			
		||||
			i1 := valueInt(1)
 | 
			
		||||
			if adder == r.global.weakMapAdder {
 | 
			
		||||
				iter.iterate(func(item Value) {
 | 
			
		||||
					itemObj := r.toObject(item)
 | 
			
		||||
					k := itemObj.self.getIdx(i0, nil)
 | 
			
		||||
					v := nilSafe(itemObj.self.getIdx(i1, nil))
 | 
			
		||||
					wmo.m.set(r.toObject(k), v)
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				iter.iterate(func(item Value) {
 | 
			
		||||
					itemObj := r.toObject(item)
 | 
			
		||||
					k := itemObj.self.getIdx(i0, nil)
 | 
			
		||||
					v := itemObj.self.getIdx(i1, nil)
 | 
			
		||||
					adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createWeakMapProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.getWeakMap(), true, false, true)
 | 
			
		||||
	r.global.weakMapAdder = r.newNativeFunc(r.weakMapProto_set, "set", 2)
 | 
			
		||||
	o._putProp("set", r.global.weakMapAdder, true, false, true)
 | 
			
		||||
	o._putProp("delete", r.newNativeFunc(r.weakMapProto_delete, "delete", 1), true, false, true)
 | 
			
		||||
	o._putProp("has", r.newNativeFunc(r.weakMapProto_has, "has", 1), true, false, true)
 | 
			
		||||
	o._putProp("get", r.newNativeFunc(r.weakMapProto_get, "get", 1), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classWeakMap), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createWeakMap(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeConstructOnly(val, r.builtin_newWeakMap, r.getWeakMapPrototype(), "WeakMap", 0)
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getWeakMapPrototype() *Object {
 | 
			
		||||
	ret := r.global.WeakMapPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.WeakMapPrototype = ret
 | 
			
		||||
		ret.self = r.createWeakMapProto(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getWeakMap() *Object {
 | 
			
		||||
	ret := r.global.WeakMap
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.WeakMap = ret
 | 
			
		||||
		ret.self = r.createWeakMap(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								goja/builtin_weakmap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								goja/builtin_weakmap_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestWeakMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
	var m = new WeakMap();
 | 
			
		||||
	var m1 = new WeakMap();
 | 
			
		||||
	var key = {};
 | 
			
		||||
	m.set(key, true);
 | 
			
		||||
	m1.set(key, false);
 | 
			
		||||
	if (!m.has(key)) {
 | 
			
		||||
		throw new Error("has");
 | 
			
		||||
	}
 | 
			
		||||
	if (m.get(key) !== true) {
 | 
			
		||||
		throw new Error("value does not match");
 | 
			
		||||
	}
 | 
			
		||||
	if (!m1.has(key)) {
 | 
			
		||||
		throw new Error("has (m1)");
 | 
			
		||||
	}
 | 
			
		||||
	if (m1.get(key) !== false) {
 | 
			
		||||
		throw new Error("m1 value does not match");
 | 
			
		||||
	}
 | 
			
		||||
	m.delete(key);
 | 
			
		||||
	if (m.has(key)) {
 | 
			
		||||
		throw new Error("m still has after delete");
 | 
			
		||||
	}
 | 
			
		||||
	if (!m1.has(key)) {
 | 
			
		||||
		throw new Error("m1 does not have after delete from m");
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWeakMapGetAdderGetIteratorOrder(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	let getterCalled = 0;
 | 
			
		||||
 | 
			
		||||
	class M extends WeakMap {
 | 
			
		||||
	    get set() {
 | 
			
		||||
	        getterCalled++;
 | 
			
		||||
	        return null;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let getIteratorCalled = 0;
 | 
			
		||||
 | 
			
		||||
	let iterable = {};
 | 
			
		||||
	iterable[Symbol.iterator] = () => {
 | 
			
		||||
	    getIteratorCalled++
 | 
			
		||||
	    return {
 | 
			
		||||
	        next: 1
 | 
			
		||||
	    };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let thrown = false;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
	    new M(iterable);
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
	    if (e instanceof TypeError) {
 | 
			
		||||
	        thrown = true;
 | 
			
		||||
	    } else {
 | 
			
		||||
	        throw e;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	thrown && getterCalled === 1 && getIteratorCalled === 0;
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								goja/builtin_weakset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								goja/builtin_weakset.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,135 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
type weakSetObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	s weakMap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ws *weakSetObject) init() {
 | 
			
		||||
	ws.baseObject.init()
 | 
			
		||||
	ws.s = weakMap(ws.val.runtime.genId())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakSetProto_add(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wso, ok := thisObj.self.(*weakSetObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakSet.prototype.add called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	wso.s.set(r.toObject(call.Argument(0)), nil)
 | 
			
		||||
	return call.This
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakSetProto_delete(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wso, ok := thisObj.self.(*weakSetObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakSet.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	obj, ok := call.Argument(0).(*Object)
 | 
			
		||||
	if ok && wso.s.remove(obj) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) weakSetProto_has(call FunctionCall) Value {
 | 
			
		||||
	thisObj := r.toObject(call.This)
 | 
			
		||||
	wso, ok := thisObj.self.(*weakSetObject)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic(r.NewTypeError("Method WeakSet.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
 | 
			
		||||
	}
 | 
			
		||||
	obj, ok := call.Argument(0).(*Object)
 | 
			
		||||
	if ok && wso.s.has(obj) {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return valueFalse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) builtin_newWeakSet(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	if newTarget == nil {
 | 
			
		||||
		panic(r.needNew("WeakSet"))
 | 
			
		||||
	}
 | 
			
		||||
	proto := r.getPrototypeFromCtor(newTarget, r.global.WeakSet, r.global.WeakSetPrototype)
 | 
			
		||||
	o := &Object{runtime: r}
 | 
			
		||||
 | 
			
		||||
	wso := &weakSetObject{}
 | 
			
		||||
	wso.class = classObject
 | 
			
		||||
	wso.val = o
 | 
			
		||||
	wso.extensible = true
 | 
			
		||||
	o.self = wso
 | 
			
		||||
	wso.prototype = proto
 | 
			
		||||
	wso.init()
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
 | 
			
		||||
			adder := wso.getStr("add", nil)
 | 
			
		||||
			stdArr := r.checkStdArrayIter(arg)
 | 
			
		||||
			if adder == r.global.weakSetAdder {
 | 
			
		||||
				if stdArr != nil {
 | 
			
		||||
					for _, v := range stdArr.values {
 | 
			
		||||
						wso.s.set(r.toObject(v), nil)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					r.getIterator(arg, nil).iterate(func(item Value) {
 | 
			
		||||
						wso.s.set(r.toObject(item), nil)
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				adderFn := toMethod(adder)
 | 
			
		||||
				if adderFn == nil {
 | 
			
		||||
					panic(r.NewTypeError("WeakSet.add in missing"))
 | 
			
		||||
				}
 | 
			
		||||
				if stdArr != nil {
 | 
			
		||||
					for _, item := range stdArr.values {
 | 
			
		||||
						adderFn(FunctionCall{This: o, Arguments: []Value{item}})
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					r.getIterator(arg, nil).iterate(func(item Value) {
 | 
			
		||||
						adderFn(FunctionCall{This: o, Arguments: []Value{item}})
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createWeakSetProto(val *Object) objectImpl {
 | 
			
		||||
	o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
 | 
			
		||||
 | 
			
		||||
	o._putProp("constructor", r.global.WeakSet, true, false, true)
 | 
			
		||||
	r.global.weakSetAdder = r.newNativeFunc(r.weakSetProto_add, "add", 1)
 | 
			
		||||
	o._putProp("add", r.global.weakSetAdder, true, false, true)
 | 
			
		||||
	o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, "delete", 1), true, false, true)
 | 
			
		||||
	o._putProp("has", r.newNativeFunc(r.weakSetProto_has, "has", 1), true, false, true)
 | 
			
		||||
 | 
			
		||||
	o._putSym(SymToStringTag, valueProp(asciiString(classWeakSet), false, false, true))
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) createWeakSet(val *Object) objectImpl {
 | 
			
		||||
	o := r.newNativeConstructOnly(val, r.builtin_newWeakSet, r.getWeakSetPrototype(), "WeakSet", 0)
 | 
			
		||||
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getWeakSetPrototype() *Object {
 | 
			
		||||
	ret := r.global.WeakSetPrototype
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.WeakSetPrototype = ret
 | 
			
		||||
		ret.self = r.createWeakSetProto(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) getWeakSet() *Object {
 | 
			
		||||
	ret := r.global.WeakSet
 | 
			
		||||
	if ret == nil {
 | 
			
		||||
		ret = &Object{runtime: r}
 | 
			
		||||
		r.global.WeakSet = ret
 | 
			
		||||
		ret.self = r.createWeakSet(ret)
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								goja/builtin_weakset_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								goja/builtin_weakset_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestWeakSetBasic(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var s = new WeakSet();
 | 
			
		||||
	var o = {};
 | 
			
		||||
	s.add(o);
 | 
			
		||||
	if (!s.has(o)) {
 | 
			
		||||
		throw new Error("has");
 | 
			
		||||
	}
 | 
			
		||||
	s.delete(o);
 | 
			
		||||
	if (s.has(o)) {
 | 
			
		||||
		throw new Error("still has");
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWeakSetArraySimple(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var o1 = {}, o2 = {}, o3 = {};
 | 
			
		||||
	
 | 
			
		||||
	var s = new WeakSet([o1, o2, o3]);
 | 
			
		||||
	s.has(o1) && s.has(o2) && s.has(o3);
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWeakSetArrayGeneric(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var o1 = {}, o2 = {}, o3 = {};
 | 
			
		||||
	var a = new Array();
 | 
			
		||||
	var s;
 | 
			
		||||
	var thrown = false;
 | 
			
		||||
	a[1] = o2;
 | 
			
		||||
	
 | 
			
		||||
	try {
 | 
			
		||||
		s = new WeakSet(a);
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		if (e instanceof TypeError) {
 | 
			
		||||
			thrown = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (!thrown) {
 | 
			
		||||
		throw new Error("Case 1 does not throw");
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
	Object.defineProperty(a.__proto__, "0", {value: o1, writable: true, enumerable: true, configurable: true});
 | 
			
		||||
	s = new WeakSet(a);
 | 
			
		||||
	if (!(s.has(o1) && s.has(o2) && !s.has(o3))) {
 | 
			
		||||
		throw new Error("Case 2 failed");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Object.defineProperty(a, "2", {value: o3, configurable: true});	
 | 
			
		||||
	s = new WeakSet(a);
 | 
			
		||||
	s.has(o1) && s.has(o2) && s.has(o3);
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWeakSetGetAdderGetIteratorOrder(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	let getterCalled = 0;
 | 
			
		||||
 | 
			
		||||
	class S extends WeakSet {
 | 
			
		||||
	    get add() {
 | 
			
		||||
	        getterCalled++;
 | 
			
		||||
	        return null;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let getIteratorCalled = 0;
 | 
			
		||||
 | 
			
		||||
	let iterable = {};
 | 
			
		||||
	iterable[Symbol.iterator] = () => {
 | 
			
		||||
	    getIteratorCalled++
 | 
			
		||||
	    return {
 | 
			
		||||
	        next: 1
 | 
			
		||||
	    };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let thrown = false;
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
	    new S(iterable);
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
	    if (e instanceof TypeError) {
 | 
			
		||||
	        thrown = true;
 | 
			
		||||
	    } else {
 | 
			
		||||
	        throw e;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	thrown && getterCalled === 1 && getIteratorCalled === 0;
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1481
									
								
								goja/compiler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1481
									
								
								goja/compiler.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										3613
									
								
								goja/compiler_expr.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3613
									
								
								goja/compiler_expr.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1127
									
								
								goja/compiler_stmt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1127
									
								
								goja/compiler_stmt.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5900
									
								
								goja/compiler_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5900
									
								
								goja/compiler_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										170
									
								
								goja/date.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								goja/date.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,170 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	dateTimeLayout       = "Mon Jan 02 2006 15:04:05 GMT-0700 (MST)"
 | 
			
		||||
	utcDateTimeLayout    = "Mon, 02 Jan 2006 15:04:05 GMT"
 | 
			
		||||
	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"
 | 
			
		||||
 | 
			
		||||
	maxTime   = 8.64e15
 | 
			
		||||
	timeUnset = math.MinInt64
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type dateObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	msec int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dateLayoutDesc struct {
 | 
			
		||||
	layout   string
 | 
			
		||||
	dateOnly bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	dateLayoutsNumeric = []dateLayoutDesc{
 | 
			
		||||
		{layout: "2006-01-02T15:04:05Z0700"},
 | 
			
		||||
		{layout: "2006-01-02T15:04:05"},
 | 
			
		||||
		{layout: "2006-01-02", dateOnly: true},
 | 
			
		||||
		{layout: "2006-01-02 15:04:05"},
 | 
			
		||||
 | 
			
		||||
		{layout: "2006", dateOnly: true},
 | 
			
		||||
		{layout: "2006-01", dateOnly: true},
 | 
			
		||||
 | 
			
		||||
		{layout: "2006T15:04"},
 | 
			
		||||
		{layout: "2006-01T15:04"},
 | 
			
		||||
		{layout: "2006-01-02T15:04"},
 | 
			
		||||
 | 
			
		||||
		{layout: "2006T15:04:05"},
 | 
			
		||||
		{layout: "2006-01T15:04:05"},
 | 
			
		||||
 | 
			
		||||
		{layout: "2006T15:04Z0700"},
 | 
			
		||||
		{layout: "2006-01T15:04Z0700"},
 | 
			
		||||
		{layout: "2006-01-02T15:04Z0700"},
 | 
			
		||||
 | 
			
		||||
		{layout: "2006T15:04:05Z0700"},
 | 
			
		||||
		{layout: "2006-01T15:04:05Z0700"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dateLayoutsAlpha = []dateLayoutDesc{
 | 
			
		||||
		{layout: time.RFC1123},
 | 
			
		||||
		{layout: time.RFC1123Z},
 | 
			
		||||
		{layout: dateTimeLayout},
 | 
			
		||||
		{layout: time.UnixDate},
 | 
			
		||||
		{layout: time.ANSIC},
 | 
			
		||||
		{layout: time.RubyDate},
 | 
			
		||||
		{layout: "Mon, _2 Jan 2006 15:04:05 GMT-0700 (MST)"},
 | 
			
		||||
		{layout: "Mon, _2 Jan 2006 15:04:05 -0700 (MST)"},
 | 
			
		||||
		{layout: "Jan _2, 2006", dateOnly: true},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func dateParse(date string) (time.Time, bool) {
 | 
			
		||||
	var t time.Time
 | 
			
		||||
	var err error
 | 
			
		||||
	var layouts []dateLayoutDesc
 | 
			
		||||
	if len(date) > 0 {
 | 
			
		||||
		first := date[0]
 | 
			
		||||
		if first <= '9' && (first >= '0' || first == '-' || first == '+') {
 | 
			
		||||
			layouts = dateLayoutsNumeric
 | 
			
		||||
		} else {
 | 
			
		||||
			layouts = dateLayoutsAlpha
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return time.Time{}, false
 | 
			
		||||
	}
 | 
			
		||||
	for _, desc := range layouts {
 | 
			
		||||
		var defLoc *time.Location
 | 
			
		||||
		if desc.dateOnly {
 | 
			
		||||
			defLoc = time.UTC
 | 
			
		||||
		} else {
 | 
			
		||||
			defLoc = time.Local
 | 
			
		||||
		}
 | 
			
		||||
		t, err = parseDate(desc.layout, date, defLoc)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return time.Time{}, false
 | 
			
		||||
	}
 | 
			
		||||
	unix := timeToMsec(t)
 | 
			
		||||
	return t, unix >= -maxTime && unix <= maxTime
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object {
 | 
			
		||||
	v := &Object{runtime: r}
 | 
			
		||||
	d := &dateObject{}
 | 
			
		||||
	v.self = d
 | 
			
		||||
	d.val = v
 | 
			
		||||
	d.class = classDate
 | 
			
		||||
	d.prototype = proto
 | 
			
		||||
	d.extensible = true
 | 
			
		||||
	d.init()
 | 
			
		||||
	if isSet {
 | 
			
		||||
		d.msec = timeToMsec(t)
 | 
			
		||||
	} else {
 | 
			
		||||
		d.msec = timeUnset
 | 
			
		||||
	}
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dateFormat(t time.Time) string {
 | 
			
		||||
	return t.Local().Format(dateTimeLayout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func timeFromMsec(msec int64) time.Time {
 | 
			
		||||
	sec := msec / 1000
 | 
			
		||||
	nsec := (msec % 1000) * 1e6
 | 
			
		||||
	return time.Unix(sec, nsec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func timeToMsec(t time.Time) int64 {
 | 
			
		||||
	return t.Unix()*1000 + int64(t.Nanosecond())/1e6
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) exportType() reflect.Type {
 | 
			
		||||
	return typeTime
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) export(*objectExportCtx) interface{} {
 | 
			
		||||
	if d.isSet() {
 | 
			
		||||
		return d.time()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) setTimeMs(ms int64) Value {
 | 
			
		||||
	if ms >= 0 && ms <= maxTime || ms < 0 && ms >= -maxTime {
 | 
			
		||||
		d.msec = ms
 | 
			
		||||
		return intToValue(ms)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.unset()
 | 
			
		||||
	return _NaN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) isSet() bool {
 | 
			
		||||
	return d.msec != timeUnset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) unset() {
 | 
			
		||||
	d.msec = timeUnset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) time() time.Time {
 | 
			
		||||
	return timeFromMsec(d.msec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dateObject) timeUTC() time.Time {
 | 
			
		||||
	return timeFromMsec(d.msec).In(time.UTC)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										869
									
								
								goja/date_parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										869
									
								
								goja/date_parser.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,869 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
// This is a slightly modified version of the standard Go parser to make it more compatible with ECMAScript 5.1
 | 
			
		||||
// Changes:
 | 
			
		||||
// - 6-digit extended years are supported in place of long year (2006) in the form of +123456
 | 
			
		||||
// - Timezone formats tolerate colons, e.g. -0700 will parse -07:00
 | 
			
		||||
// - Short week day will also parse long week day
 | 
			
		||||
// - Short month ("Jan") will also parse long month ("January")
 | 
			
		||||
// - Long day ("02") will also parse short day ("2").
 | 
			
		||||
// - Timezone in brackets, "(MST)", will match any string in brackets (e.g. "(GMT Standard Time)")
 | 
			
		||||
// - If offset is not set and timezone name is unknown, an error is returned
 | 
			
		||||
// - If offset and timezone name are both set the offset takes precedence and the resulting Location will be FixedZone("", offset)
 | 
			
		||||
 | 
			
		||||
// Original copyright message:
 | 
			
		||||
 | 
			
		||||
// Copyright 2010 The Go Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a BSD-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	_                        = iota
 | 
			
		||||
	stdLongMonth             = iota + stdNeedDate  // "January"
 | 
			
		||||
	stdMonth                                       // "Jan"
 | 
			
		||||
	stdNumMonth                                    // "1"
 | 
			
		||||
	stdZeroMonth                                   // "01"
 | 
			
		||||
	stdLongWeekDay                                 // "Monday"
 | 
			
		||||
	stdWeekDay                                     // "Mon"
 | 
			
		||||
	stdDay                                         // "2"
 | 
			
		||||
	stdUnderDay                                    // "_2"
 | 
			
		||||
	stdZeroDay                                     // "02"
 | 
			
		||||
	stdHour                  = iota + stdNeedClock // "15"
 | 
			
		||||
	stdHour12                                      // "3"
 | 
			
		||||
	stdZeroHour12                                  // "03"
 | 
			
		||||
	stdMinute                                      // "4"
 | 
			
		||||
	stdZeroMinute                                  // "04"
 | 
			
		||||
	stdSecond                                      // "5"
 | 
			
		||||
	stdZeroSecond                                  // "05"
 | 
			
		||||
	stdLongYear              = iota + stdNeedDate  // "2006"
 | 
			
		||||
	stdYear                                        // "06"
 | 
			
		||||
	stdPM                    = iota + stdNeedClock // "PM"
 | 
			
		||||
	stdpm                                          // "pm"
 | 
			
		||||
	stdTZ                    = iota                // "MST"
 | 
			
		||||
	stdBracketTZ                                   // "(MST)"
 | 
			
		||||
	stdISO8601TZ                                   // "Z0700"  // prints Z for UTC
 | 
			
		||||
	stdISO8601SecondsTZ                            // "Z070000"
 | 
			
		||||
	stdISO8601ShortTZ                              // "Z07"
 | 
			
		||||
	stdISO8601ColonTZ                              // "Z07:00" // prints Z for UTC
 | 
			
		||||
	stdISO8601ColonSecondsTZ                       // "Z07:00:00"
 | 
			
		||||
	stdNumTZ                                       // "-0700"  // always numeric
 | 
			
		||||
	stdNumSecondsTz                                // "-070000"
 | 
			
		||||
	stdNumShortTZ                                  // "-07"    // always numeric
 | 
			
		||||
	stdNumColonTZ                                  // "-07:00" // always numeric
 | 
			
		||||
	stdNumColonSecondsTZ                           // "-07:00:00"
 | 
			
		||||
	stdFracSecond0                                 // ".0", ".00", ... , trailing zeros included
 | 
			
		||||
	stdFracSecond9                                 // ".9", ".99", ..., trailing zeros omitted
 | 
			
		||||
 | 
			
		||||
	stdNeedDate  = 1 << 8             // need month, day, year
 | 
			
		||||
	stdNeedClock = 2 << 8             // need hour, minute, second
 | 
			
		||||
	stdArgShift  = 16                 // extra argument in high bits, above low stdArgShift
 | 
			
		||||
	stdMask      = 1<<stdArgShift - 1 // mask out argument
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var errBad = errors.New("bad value for field") // placeholder not passed to user
 | 
			
		||||
 | 
			
		||||
func parseDate(layout, value string, defaultLocation *time.Location) (time.Time, error) {
 | 
			
		||||
	alayout, avalue := layout, value
 | 
			
		||||
	rangeErrString := "" // set if a value is out of range
 | 
			
		||||
	amSet := false       // do we need to subtract 12 from the hour for midnight?
 | 
			
		||||
	pmSet := false       // do we need to add 12 to the hour?
 | 
			
		||||
 | 
			
		||||
	// Time being constructed.
 | 
			
		||||
	var (
 | 
			
		||||
		year       int
 | 
			
		||||
		month      int = 1 // January
 | 
			
		||||
		day        int = 1
 | 
			
		||||
		hour       int
 | 
			
		||||
		min        int
 | 
			
		||||
		sec        int
 | 
			
		||||
		nsec       int
 | 
			
		||||
		z          *time.Location
 | 
			
		||||
		zoneOffset int = -1
 | 
			
		||||
		zoneName   string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Each iteration processes one std value.
 | 
			
		||||
	for {
 | 
			
		||||
		var err error
 | 
			
		||||
		prefix, std, suffix := nextStdChunk(layout)
 | 
			
		||||
		stdstr := layout[len(prefix) : len(layout)-len(suffix)]
 | 
			
		||||
		value, err = skip(value, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, LayoutElem: prefix, ValueElem: value}
 | 
			
		||||
		}
 | 
			
		||||
		if std == 0 {
 | 
			
		||||
			if len(value) != 0 {
 | 
			
		||||
				return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, ValueElem: value, Message: ": extra text: " + value}
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		layout = suffix
 | 
			
		||||
		var p string
 | 
			
		||||
		switch std & stdMask {
 | 
			
		||||
		case stdYear:
 | 
			
		||||
			if len(value) < 2 {
 | 
			
		||||
				err = errBad
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			p, value = value[0:2], value[2:]
 | 
			
		||||
			year, err = atoi(p)
 | 
			
		||||
			if year >= 69 { // Unix time starts Dec 31 1969 in some time zones
 | 
			
		||||
				year += 1900
 | 
			
		||||
			} else {
 | 
			
		||||
				year += 2000
 | 
			
		||||
			}
 | 
			
		||||
		case stdLongYear:
 | 
			
		||||
			if len(value) >= 7 && (value[0] == '-' || value[0] == '+') { // extended year
 | 
			
		||||
				neg := value[0] == '-'
 | 
			
		||||
				p, value = value[1:7], value[7:]
 | 
			
		||||
				year, err = atoi(p)
 | 
			
		||||
				if neg {
 | 
			
		||||
					if year == 0 {
 | 
			
		||||
						err = errBad
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					year = -year
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if len(value) < 4 || !isDigit(value, 0) {
 | 
			
		||||
					err = errBad
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				p, value = value[0:4], value[4:]
 | 
			
		||||
				year, err = atoi(p)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case stdMonth:
 | 
			
		||||
			month, value, err = lookup(longMonthNames, value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				month, value, err = lookup(shortMonthNames, value)
 | 
			
		||||
			}
 | 
			
		||||
			month++
 | 
			
		||||
		case stdLongMonth:
 | 
			
		||||
			month, value, err = lookup(longMonthNames, value)
 | 
			
		||||
			month++
 | 
			
		||||
		case stdNumMonth, stdZeroMonth:
 | 
			
		||||
			month, value, err = getnum(value, std == stdZeroMonth)
 | 
			
		||||
			if month <= 0 || 12 < month {
 | 
			
		||||
				rangeErrString = "month"
 | 
			
		||||
			}
 | 
			
		||||
		case stdWeekDay:
 | 
			
		||||
			// Ignore weekday except for error checking.
 | 
			
		||||
			_, value, err = lookup(longDayNames, value)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				_, value, err = lookup(shortDayNames, value)
 | 
			
		||||
			}
 | 
			
		||||
		case stdLongWeekDay:
 | 
			
		||||
			_, value, err = lookup(longDayNames, value)
 | 
			
		||||
		case stdDay, stdUnderDay, stdZeroDay:
 | 
			
		||||
			if std == stdUnderDay && len(value) > 0 && value[0] == ' ' {
 | 
			
		||||
				value = value[1:]
 | 
			
		||||
			}
 | 
			
		||||
			day, value, err = getnum(value, false)
 | 
			
		||||
			if day < 0 {
 | 
			
		||||
				// Note that we allow any one- or two-digit day here.
 | 
			
		||||
				rangeErrString = "day"
 | 
			
		||||
			}
 | 
			
		||||
		case stdHour:
 | 
			
		||||
			hour, value, err = getnum(value, false)
 | 
			
		||||
			if hour < 0 || 24 <= hour {
 | 
			
		||||
				rangeErrString = "hour"
 | 
			
		||||
			}
 | 
			
		||||
		case stdHour12, stdZeroHour12:
 | 
			
		||||
			hour, value, err = getnum(value, std == stdZeroHour12)
 | 
			
		||||
			if hour < 0 || 12 < hour {
 | 
			
		||||
				rangeErrString = "hour"
 | 
			
		||||
			}
 | 
			
		||||
		case stdMinute, stdZeroMinute:
 | 
			
		||||
			min, value, err = getnum(value, std == stdZeroMinute)
 | 
			
		||||
			if min < 0 || 60 <= min {
 | 
			
		||||
				rangeErrString = "minute"
 | 
			
		||||
			}
 | 
			
		||||
		case stdSecond, stdZeroSecond:
 | 
			
		||||
			sec, value, err = getnum(value, std == stdZeroSecond)
 | 
			
		||||
			if sec < 0 || 60 <= sec {
 | 
			
		||||
				rangeErrString = "second"
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			// Special case: do we have a fractional second but no
 | 
			
		||||
			// fractional second in the format?
 | 
			
		||||
			if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) {
 | 
			
		||||
				_, std, _ = nextStdChunk(layout)
 | 
			
		||||
				std &= stdMask
 | 
			
		||||
				if std == stdFracSecond0 || std == stdFracSecond9 {
 | 
			
		||||
					// Fractional second in the layout; proceed normally
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				// No fractional second in the layout but we have one in the input.
 | 
			
		||||
				n := 2
 | 
			
		||||
				for ; n < len(value) && isDigit(value, n); n++ {
 | 
			
		||||
				}
 | 
			
		||||
				nsec, rangeErrString, err = parseNanoseconds(value, n)
 | 
			
		||||
				value = value[n:]
 | 
			
		||||
			}
 | 
			
		||||
		case stdPM:
 | 
			
		||||
			if len(value) < 2 {
 | 
			
		||||
				err = errBad
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			p, value = value[0:2], value[2:]
 | 
			
		||||
			switch p {
 | 
			
		||||
			case "PM":
 | 
			
		||||
				pmSet = true
 | 
			
		||||
			case "AM":
 | 
			
		||||
				amSet = true
 | 
			
		||||
			default:
 | 
			
		||||
				err = errBad
 | 
			
		||||
			}
 | 
			
		||||
		case stdpm:
 | 
			
		||||
			if len(value) < 2 {
 | 
			
		||||
				err = errBad
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			p, value = value[0:2], value[2:]
 | 
			
		||||
			switch p {
 | 
			
		||||
			case "pm":
 | 
			
		||||
				pmSet = true
 | 
			
		||||
			case "am":
 | 
			
		||||
				amSet = true
 | 
			
		||||
			default:
 | 
			
		||||
				err = errBad
 | 
			
		||||
			}
 | 
			
		||||
		case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
 | 
			
		||||
			if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ ||
 | 
			
		||||
				std == stdISO8601SecondsTZ || std == stdISO8601ColonSecondsTZ) && len(value) >= 1 && value[0] == 'Z' {
 | 
			
		||||
 | 
			
		||||
				value = value[1:]
 | 
			
		||||
				z = time.UTC
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			var sign, hour, min, seconds string
 | 
			
		||||
			if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdNumTZ || std == stdISO8601TZ {
 | 
			
		||||
				if len(value) < 4 {
 | 
			
		||||
					err = errBad
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				if value[3] != ':' {
 | 
			
		||||
					if std == stdNumColonTZ || std == stdISO8601ColonTZ || len(value) < 5 {
 | 
			
		||||
						err = errBad
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:]
 | 
			
		||||
				} else {
 | 
			
		||||
					if len(value) < 6 {
 | 
			
		||||
						err = errBad
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:]
 | 
			
		||||
				}
 | 
			
		||||
			} else if std == stdNumShortTZ || std == stdISO8601ShortTZ {
 | 
			
		||||
				if len(value) < 3 {
 | 
			
		||||
					err = errBad
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:]
 | 
			
		||||
			} else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ || std == stdISO8601SecondsTZ || std == stdNumSecondsTz {
 | 
			
		||||
				if len(value) < 7 {
 | 
			
		||||
					err = errBad
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				if value[3] != ':' || value[6] != ':' {
 | 
			
		||||
					if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ || len(value) < 7 {
 | 
			
		||||
						err = errBad
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:]
 | 
			
		||||
				} else {
 | 
			
		||||
					if len(value) < 9 {
 | 
			
		||||
						err = errBad
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			var hr, mm, ss int
 | 
			
		||||
			hr, err = atoi(hour)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				mm, err = atoi(min)
 | 
			
		||||
			}
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				ss, err = atoi(seconds)
 | 
			
		||||
			}
 | 
			
		||||
			zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds
 | 
			
		||||
			switch sign[0] {
 | 
			
		||||
			case '+':
 | 
			
		||||
			case '-':
 | 
			
		||||
				zoneOffset = -zoneOffset
 | 
			
		||||
			default:
 | 
			
		||||
				err = errBad
 | 
			
		||||
			}
 | 
			
		||||
		case stdTZ:
 | 
			
		||||
			// Does it look like a time zone?
 | 
			
		||||
			if len(value) >= 3 && value[0:3] == "UTC" {
 | 
			
		||||
				z = time.UTC
 | 
			
		||||
				value = value[3:]
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			n, ok := parseTimeZone(value)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				err = errBad
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			zoneName, value = value[:n], value[n:]
 | 
			
		||||
		case stdBracketTZ:
 | 
			
		||||
			if len(value) < 3 || value[0] != '(' {
 | 
			
		||||
				err = errBad
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			i := 1
 | 
			
		||||
			for ; ; i++ {
 | 
			
		||||
				if i >= len(value) {
 | 
			
		||||
					err = errBad
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				if value[i] == ')' {
 | 
			
		||||
					zoneName, value = value[1:i], value[i+1:]
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case stdFracSecond0:
 | 
			
		||||
			// stdFracSecond0 requires the exact number of digits as specified in
 | 
			
		||||
			// the layout.
 | 
			
		||||
			ndigit := 1 + (std >> stdArgShift)
 | 
			
		||||
			if len(value) < ndigit {
 | 
			
		||||
				err = errBad
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			nsec, rangeErrString, err = parseNanoseconds(value, ndigit)
 | 
			
		||||
			value = value[ndigit:]
 | 
			
		||||
 | 
			
		||||
		case stdFracSecond9:
 | 
			
		||||
			if len(value) < 2 || value[0] != '.' || value[1] < '0' || '9' < value[1] {
 | 
			
		||||
				// Fractional second omitted.
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			// Take any number of digits, even more than asked for,
 | 
			
		||||
			// because it is what the stdSecond case would do.
 | 
			
		||||
			i := 0
 | 
			
		||||
			for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' {
 | 
			
		||||
				i++
 | 
			
		||||
			}
 | 
			
		||||
			nsec, rangeErrString, err = parseNanoseconds(value, 1+i)
 | 
			
		||||
			value = value[1+i:]
 | 
			
		||||
		}
 | 
			
		||||
		if rangeErrString != "" {
 | 
			
		||||
			return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, LayoutElem: stdstr, ValueElem: value, Message: ": " + rangeErrString + " out of range"}
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, LayoutElem: stdstr, ValueElem: value}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if pmSet && hour < 12 {
 | 
			
		||||
		hour += 12
 | 
			
		||||
	} else if amSet && hour == 12 {
 | 
			
		||||
		hour = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Validate the day of the month.
 | 
			
		||||
	if day < 1 || day > daysIn(time.Month(month), year) {
 | 
			
		||||
		return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, ValueElem: value, Message: ": day out of range"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if z == nil {
 | 
			
		||||
		if zoneOffset == -1 {
 | 
			
		||||
			if zoneName != "" {
 | 
			
		||||
				if z1, err := time.LoadLocation(zoneName); err == nil {
 | 
			
		||||
					z = z1
 | 
			
		||||
				} else {
 | 
			
		||||
					return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, ValueElem: value, Message: ": unknown timezone"}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				z = defaultLocation
 | 
			
		||||
			}
 | 
			
		||||
		} else if zoneOffset == 0 {
 | 
			
		||||
			z = time.UTC
 | 
			
		||||
		} else {
 | 
			
		||||
			z = time.FixedZone("", zoneOffset)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return time.Date(year, time.Month(month), day, hour, min, sec, nsec, z), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
 | 
			
		||||
 | 
			
		||||
func signedLeadingInt(s string) (x int64, rem string, err error) {
 | 
			
		||||
	neg := false
 | 
			
		||||
	if s != "" && (s[0] == '-' || s[0] == '+') {
 | 
			
		||||
		neg = s[0] == '-'
 | 
			
		||||
		s = s[1:]
 | 
			
		||||
	}
 | 
			
		||||
	x, rem, err = leadingInt(s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if neg {
 | 
			
		||||
		x = -x
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// leadingInt consumes the leading [0-9]* from s.
 | 
			
		||||
func leadingInt(s string) (x int64, rem string, err error) {
 | 
			
		||||
	i := 0
 | 
			
		||||
	for ; i < len(s); i++ {
 | 
			
		||||
		c := s[i]
 | 
			
		||||
		if c < '0' || c > '9' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if x > (1<<63-1)/10 {
 | 
			
		||||
			// overflow
 | 
			
		||||
			return 0, "", errLeadingInt
 | 
			
		||||
		}
 | 
			
		||||
		x = x*10 + int64(c) - '0'
 | 
			
		||||
		if x < 0 {
 | 
			
		||||
			// overflow
 | 
			
		||||
			return 0, "", errLeadingInt
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return x, s[i:], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// nextStdChunk finds the first occurrence of a std string in
 | 
			
		||||
// layout and returns the text before, the std string, and the text after.
 | 
			
		||||
func nextStdChunk(layout string) (prefix string, std int, suffix string) {
 | 
			
		||||
	for i := 0; i < len(layout); i++ {
 | 
			
		||||
		switch c := int(layout[i]); c {
 | 
			
		||||
		case 'J': // January, Jan
 | 
			
		||||
			if len(layout) >= i+3 && layout[i:i+3] == "Jan" {
 | 
			
		||||
				if len(layout) >= i+7 && layout[i:i+7] == "January" {
 | 
			
		||||
					return layout[0:i], stdLongMonth, layout[i+7:]
 | 
			
		||||
				}
 | 
			
		||||
				if !startsWithLowerCase(layout[i+3:]) {
 | 
			
		||||
					return layout[0:i], stdMonth, layout[i+3:]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case 'M': // Monday, Mon, MST
 | 
			
		||||
			if len(layout) >= i+3 {
 | 
			
		||||
				if layout[i:i+3] == "Mon" {
 | 
			
		||||
					if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
 | 
			
		||||
						return layout[0:i], stdLongWeekDay, layout[i+6:]
 | 
			
		||||
					}
 | 
			
		||||
					if !startsWithLowerCase(layout[i+3:]) {
 | 
			
		||||
						return layout[0:i], stdWeekDay, layout[i+3:]
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if layout[i:i+3] == "MST" {
 | 
			
		||||
					return layout[0:i], stdTZ, layout[i+3:]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case '0': // 01, 02, 03, 04, 05, 06
 | 
			
		||||
			if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' {
 | 
			
		||||
				return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case '1': // 15, 1
 | 
			
		||||
			if len(layout) >= i+2 && layout[i+1] == '5' {
 | 
			
		||||
				return layout[0:i], stdHour, layout[i+2:]
 | 
			
		||||
			}
 | 
			
		||||
			return layout[0:i], stdNumMonth, layout[i+1:]
 | 
			
		||||
 | 
			
		||||
		case '2': // 2006, 2
 | 
			
		||||
			if len(layout) >= i+4 && layout[i:i+4] == "2006" {
 | 
			
		||||
				return layout[0:i], stdLongYear, layout[i+4:]
 | 
			
		||||
			}
 | 
			
		||||
			return layout[0:i], stdDay, layout[i+1:]
 | 
			
		||||
 | 
			
		||||
		case '_': // _2, _2006
 | 
			
		||||
			if len(layout) >= i+2 && layout[i+1] == '2' {
 | 
			
		||||
				//_2006 is really a literal _, followed by stdLongYear
 | 
			
		||||
				if len(layout) >= i+5 && layout[i+1:i+5] == "2006" {
 | 
			
		||||
					return layout[0 : i+1], stdLongYear, layout[i+5:]
 | 
			
		||||
				}
 | 
			
		||||
				return layout[0:i], stdUnderDay, layout[i+2:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case '3':
 | 
			
		||||
			return layout[0:i], stdHour12, layout[i+1:]
 | 
			
		||||
 | 
			
		||||
		case '4':
 | 
			
		||||
			return layout[0:i], stdMinute, layout[i+1:]
 | 
			
		||||
 | 
			
		||||
		case '5':
 | 
			
		||||
			return layout[0:i], stdSecond, layout[i+1:]
 | 
			
		||||
 | 
			
		||||
		case 'P': // PM
 | 
			
		||||
			if len(layout) >= i+2 && layout[i+1] == 'M' {
 | 
			
		||||
				return layout[0:i], stdPM, layout[i+2:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case 'p': // pm
 | 
			
		||||
			if len(layout) >= i+2 && layout[i+1] == 'm' {
 | 
			
		||||
				return layout[0:i], stdpm, layout[i+2:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case '-': // -070000, -07:00:00, -0700, -07:00, -07
 | 
			
		||||
			if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
 | 
			
		||||
				return layout[0:i], stdNumSecondsTz, layout[i+7:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
 | 
			
		||||
				return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
 | 
			
		||||
				return layout[0:i], stdNumTZ, layout[i+5:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+6 && layout[i:i+6] == "-07:00" {
 | 
			
		||||
				return layout[0:i], stdNumColonTZ, layout[i+6:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+3 && layout[i:i+3] == "-07" {
 | 
			
		||||
				return layout[0:i], stdNumShortTZ, layout[i+3:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
 | 
			
		||||
			if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
 | 
			
		||||
				return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
 | 
			
		||||
				return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
 | 
			
		||||
				return layout[0:i], stdISO8601TZ, layout[i+5:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
 | 
			
		||||
				return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
 | 
			
		||||
			}
 | 
			
		||||
			if len(layout) >= i+3 && layout[i:i+3] == "Z07" {
 | 
			
		||||
				return layout[0:i], stdISO8601ShortTZ, layout[i+3:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case '.': // .000 or .999 - repeated digits for fractional seconds.
 | 
			
		||||
			if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
 | 
			
		||||
				ch := layout[i+1]
 | 
			
		||||
				j := i + 1
 | 
			
		||||
				for j < len(layout) && layout[j] == ch {
 | 
			
		||||
					j++
 | 
			
		||||
				}
 | 
			
		||||
				// String of digits must end here - only fractional second is all digits.
 | 
			
		||||
				if !isDigit(layout, j) {
 | 
			
		||||
					std := stdFracSecond0
 | 
			
		||||
					if layout[i+1] == '9' {
 | 
			
		||||
						std = stdFracSecond9
 | 
			
		||||
					}
 | 
			
		||||
					std |= (j - (i + 1)) << stdArgShift
 | 
			
		||||
					return layout[0:i], std, layout[j:]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case '(':
 | 
			
		||||
			if len(layout) >= i+5 && layout[i:i+5] == "(MST)" {
 | 
			
		||||
				return layout[0:i], stdBracketTZ, layout[i+5:]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return layout, 0, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var longDayNames = []string{
 | 
			
		||||
	"Sunday",
 | 
			
		||||
	"Monday",
 | 
			
		||||
	"Tuesday",
 | 
			
		||||
	"Wednesday",
 | 
			
		||||
	"Thursday",
 | 
			
		||||
	"Friday",
 | 
			
		||||
	"Saturday",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var shortDayNames = []string{
 | 
			
		||||
	"Sun",
 | 
			
		||||
	"Mon",
 | 
			
		||||
	"Tue",
 | 
			
		||||
	"Wed",
 | 
			
		||||
	"Thu",
 | 
			
		||||
	"Fri",
 | 
			
		||||
	"Sat",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var shortMonthNames = []string{
 | 
			
		||||
	"Jan",
 | 
			
		||||
	"Feb",
 | 
			
		||||
	"Mar",
 | 
			
		||||
	"Apr",
 | 
			
		||||
	"May",
 | 
			
		||||
	"Jun",
 | 
			
		||||
	"Jul",
 | 
			
		||||
	"Aug",
 | 
			
		||||
	"Sep",
 | 
			
		||||
	"Oct",
 | 
			
		||||
	"Nov",
 | 
			
		||||
	"Dec",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var longMonthNames = []string{
 | 
			
		||||
	"January",
 | 
			
		||||
	"February",
 | 
			
		||||
	"March",
 | 
			
		||||
	"April",
 | 
			
		||||
	"May",
 | 
			
		||||
	"June",
 | 
			
		||||
	"July",
 | 
			
		||||
	"August",
 | 
			
		||||
	"September",
 | 
			
		||||
	"October",
 | 
			
		||||
	"November",
 | 
			
		||||
	"December",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isDigit reports whether s[i] is in range and is a decimal digit.
 | 
			
		||||
func isDigit(s string, i int) bool {
 | 
			
		||||
	if len(s) <= i {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	c := s[i]
 | 
			
		||||
	return '0' <= c && c <= '9'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getnum parses s[0:1] or s[0:2] (fixed forces the latter)
 | 
			
		||||
// as a decimal integer and returns the integer and the
 | 
			
		||||
// remainder of the string.
 | 
			
		||||
func getnum(s string, fixed bool) (int, string, error) {
 | 
			
		||||
	if !isDigit(s, 0) {
 | 
			
		||||
		return 0, s, errBad
 | 
			
		||||
	}
 | 
			
		||||
	if !isDigit(s, 1) {
 | 
			
		||||
		if fixed {
 | 
			
		||||
			return 0, s, errBad
 | 
			
		||||
		}
 | 
			
		||||
		return int(s[0] - '0'), s[1:], nil
 | 
			
		||||
	}
 | 
			
		||||
	return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cutspace(s string) string {
 | 
			
		||||
	for len(s) > 0 && s[0] == ' ' {
 | 
			
		||||
		s = s[1:]
 | 
			
		||||
	}
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// skip removes the given prefix from value,
 | 
			
		||||
// treating runs of space characters as equivalent.
 | 
			
		||||
func skip(value, prefix string) (string, error) {
 | 
			
		||||
	for len(prefix) > 0 {
 | 
			
		||||
		if prefix[0] == ' ' {
 | 
			
		||||
			if len(value) > 0 && value[0] != ' ' {
 | 
			
		||||
				return value, errBad
 | 
			
		||||
			}
 | 
			
		||||
			prefix = cutspace(prefix)
 | 
			
		||||
			value = cutspace(value)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if len(value) == 0 || value[0] != prefix[0] {
 | 
			
		||||
			return value, errBad
 | 
			
		||||
		}
 | 
			
		||||
		prefix = prefix[1:]
 | 
			
		||||
		value = value[1:]
 | 
			
		||||
	}
 | 
			
		||||
	return value, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Never printed, just needs to be non-nil for return by atoi.
 | 
			
		||||
var atoiError = errors.New("time: invalid number")
 | 
			
		||||
 | 
			
		||||
// Duplicates functionality in strconv, but avoids dependency.
 | 
			
		||||
func atoi(s string) (x int, err error) {
 | 
			
		||||
	q, rem, err := signedLeadingInt(s)
 | 
			
		||||
	x = int(q)
 | 
			
		||||
	if err != nil || rem != "" {
 | 
			
		||||
		return 0, atoiError
 | 
			
		||||
	}
 | 
			
		||||
	return x, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// match reports whether s1 and s2 match ignoring case.
 | 
			
		||||
// It is assumed s1 and s2 are the same length.
 | 
			
		||||
func match(s1, s2 string) bool {
 | 
			
		||||
	for i := 0; i < len(s1); i++ {
 | 
			
		||||
		c1 := s1[i]
 | 
			
		||||
		c2 := s2[i]
 | 
			
		||||
		if c1 != c2 {
 | 
			
		||||
			// Switch to lower-case; 'a'-'A' is known to be a single bit.
 | 
			
		||||
			c1 |= 'a' - 'A'
 | 
			
		||||
			c2 |= 'a' - 'A'
 | 
			
		||||
			if c1 != c2 || c1 < 'a' || c1 > 'z' {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lookup(tab []string, val string) (int, string, error) {
 | 
			
		||||
	for i, v := range tab {
 | 
			
		||||
		if len(val) >= len(v) && match(val[0:len(v)], v) {
 | 
			
		||||
			return i, val[len(v):], nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return -1, val, errBad
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// daysBefore[m] counts the number of days in a non-leap year
 | 
			
		||||
// before month m begins. There is an entry for m=12, counting
 | 
			
		||||
// the number of days before January of next year (365).
 | 
			
		||||
var daysBefore = [...]int32{
 | 
			
		||||
	0,
 | 
			
		||||
	31,
 | 
			
		||||
	31 + 28,
 | 
			
		||||
	31 + 28 + 31,
 | 
			
		||||
	31 + 28 + 31 + 30,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30 + 31,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
 | 
			
		||||
	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isLeap(year int) bool {
 | 
			
		||||
	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func daysIn(m time.Month, year int) int {
 | 
			
		||||
	if m == time.February && isLeap(year) {
 | 
			
		||||
		return 29
 | 
			
		||||
	}
 | 
			
		||||
	return int(daysBefore[m] - daysBefore[m-1])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseTimeZone parses a time zone string and returns its length. Time zones
 | 
			
		||||
// are human-generated and unpredictable. We can't do precise error checking.
 | 
			
		||||
// On the other hand, for a correct parse there must be a time zone at the
 | 
			
		||||
// beginning of the string, so it's almost always true that there's one
 | 
			
		||||
// there. We look at the beginning of the string for a run of upper-case letters.
 | 
			
		||||
// If there are more than 5, it's an error.
 | 
			
		||||
// If there are 4 or 5 and the last is a T, it's a time zone.
 | 
			
		||||
// If there are 3, it's a time zone.
 | 
			
		||||
// Otherwise, other than special cases, it's not a time zone.
 | 
			
		||||
// GMT is special because it can have an hour offset.
 | 
			
		||||
func parseTimeZone(value string) (length int, ok bool) {
 | 
			
		||||
	if len(value) < 3 {
 | 
			
		||||
		return 0, false
 | 
			
		||||
	}
 | 
			
		||||
	// Special case 1: ChST and MeST are the only zones with a lower-case letter.
 | 
			
		||||
	if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") {
 | 
			
		||||
		return 4, true
 | 
			
		||||
	}
 | 
			
		||||
	// Special case 2: GMT may have an hour offset; treat it specially.
 | 
			
		||||
	if value[:3] == "GMT" {
 | 
			
		||||
		length = parseGMT(value)
 | 
			
		||||
		return length, true
 | 
			
		||||
	}
 | 
			
		||||
	// Special Case 3: Some time zones are not named, but have +/-00 format
 | 
			
		||||
	if value[0] == '+' || value[0] == '-' {
 | 
			
		||||
		length = parseSignedOffset(value)
 | 
			
		||||
		return length, true
 | 
			
		||||
	}
 | 
			
		||||
	// How many upper-case letters are there? Need at least three, at most five.
 | 
			
		||||
	var nUpper int
 | 
			
		||||
	for nUpper = 0; nUpper < 6; nUpper++ {
 | 
			
		||||
		if nUpper >= len(value) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if c := value[nUpper]; c < 'A' || 'Z' < c {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	switch nUpper {
 | 
			
		||||
	case 0, 1, 2, 6:
 | 
			
		||||
		return 0, false
 | 
			
		||||
	case 5: // Must end in T to match.
 | 
			
		||||
		if value[4] == 'T' {
 | 
			
		||||
			return 5, true
 | 
			
		||||
		}
 | 
			
		||||
	case 4:
 | 
			
		||||
		// Must end in T, except one special case.
 | 
			
		||||
		if value[3] == 'T' || value[:4] == "WITA" {
 | 
			
		||||
			return 4, true
 | 
			
		||||
		}
 | 
			
		||||
	case 3:
 | 
			
		||||
		return 3, true
 | 
			
		||||
	}
 | 
			
		||||
	return 0, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseGMT parses a GMT time zone. The input string is known to start "GMT".
 | 
			
		||||
// The function checks whether that is followed by a sign and a number in the
 | 
			
		||||
// range -14 through 12 excluding zero.
 | 
			
		||||
func parseGMT(value string) int {
 | 
			
		||||
	value = value[3:]
 | 
			
		||||
	if len(value) == 0 {
 | 
			
		||||
		return 3
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 3 + parseSignedOffset(value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04").
 | 
			
		||||
// The function checks for a signed number in the range -14 through +12 excluding zero.
 | 
			
		||||
// Returns length of the found offset string or 0 otherwise
 | 
			
		||||
func parseSignedOffset(value string) int {
 | 
			
		||||
	sign := value[0]
 | 
			
		||||
	if sign != '-' && sign != '+' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	x, rem, err := leadingInt(value[1:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	if sign == '-' {
 | 
			
		||||
		x = -x
 | 
			
		||||
	}
 | 
			
		||||
	if x == 0 || x < -14 || 12 < x {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return len(value) - len(rem)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) {
 | 
			
		||||
	if value[0] != '.' {
 | 
			
		||||
		err = errBad
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ns, err = atoi(value[1:nbytes]); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if ns < 0 || 1e9 <= ns {
 | 
			
		||||
		rangeErrString = "fractional second"
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// We need nanoseconds, which means scaling by the number
 | 
			
		||||
	// of missing digits in the format, maximum length 10. If it's
 | 
			
		||||
	// longer than 10, we won't scale.
 | 
			
		||||
	scaleDigits := 10 - nbytes
 | 
			
		||||
	for i := 0; i < scaleDigits; i++ {
 | 
			
		||||
		ns *= 10
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// std0x records the std values for "01", "02", ..., "06".
 | 
			
		||||
var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear}
 | 
			
		||||
 | 
			
		||||
// startsWithLowerCase reports whether the string has a lower-case letter at the beginning.
 | 
			
		||||
// Its purpose is to prevent matching strings like "Month" when looking for "Mon".
 | 
			
		||||
func startsWithLowerCase(str string) bool {
 | 
			
		||||
	if len(str) == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	c := str[0]
 | 
			
		||||
	return 'a' <= c && c <= 'z'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								goja/date_parser_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								goja/date_parser_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseDate(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	tst := func(layout, value string, expectedTs int64) func(t *testing.T) {
 | 
			
		||||
		return func(t *testing.T) {
 | 
			
		||||
			t.Parallel()
 | 
			
		||||
			tm, err := parseDate(layout, value, time.UTC)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			if tm.Unix() != expectedTs {
 | 
			
		||||
				t.Fatal(tm)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("1", tst("2006-01-02T15:04:05.000Z070000", "2006-01-02T15:04:05.000+07:00:00", 1136189045))
 | 
			
		||||
	t.Run("2", tst("2006-01-02T15:04:05.000Z07:00:00", "2006-01-02T15:04:05.000+07:00:00", 1136189045))
 | 
			
		||||
	t.Run("3", tst("2006-01-02T15:04:05.000Z07:00", "2006-01-02T15:04:05.000+07:00", 1136189045))
 | 
			
		||||
	t.Run("4", tst("2006-01-02T15:04:05.000Z070000", "2006-01-02T15:04:05.000+070000", 1136189045))
 | 
			
		||||
	t.Run("5", tst("2006-01-02T15:04:05.000Z070000", "2006-01-02T15:04:05.000Z", 1136214245))
 | 
			
		||||
	t.Run("6", tst("2006-01-02T15:04:05.000Z0700", "2006-01-02T15:04:05.000Z", 1136214245))
 | 
			
		||||
	t.Run("7", tst("2006-01-02T15:04:05.000Z07", "2006-01-02T15:04:05.000Z", 1136214245))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										309
									
								
								goja/date_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								goja/date_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,309 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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'
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewDate(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var d1 = new Date("2016-09-01T12:34:56Z");
 | 
			
		||||
	d1.getUTCHours() === 12;
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewDate0(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	(new Date(0)).toUTCString();
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, asciiString("Thu, 01 Jan 1970 00:00:00 GMT"), 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);
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(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);
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, intToValue(-60), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateValueOf(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var d9 = new Date(1.23e15);
 | 
			
		||||
	d9.valueOf();
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, intToValue(1.23e15), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateSetters(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert.sameValue((new Date(0)).setMilliseconds(2345), 2345, "setMilliseconds(2345)");
 | 
			
		||||
	assert.sameValue(new Date(1000).setMilliseconds(23450000000000), 23450000001000, "setMilliseconds(23450000000000)");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCMilliseconds(2345), 2345, "setUTCMilliseconds()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setSeconds(12), 12000, "setSeconds()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCSeconds(12), 12000, "setUTCSeconds()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setMinutes(12), 12 * 60 * 1000, "setMinutes()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCMinutes(12), 12 * 60 * 1000, "setUTCMinutes()");
 | 
			
		||||
	assert.sameValue((new Date("2016-06-01")).setHours(1), 1464739200000, "setHours()");
 | 
			
		||||
	assert.sameValue((new Date("2016-06-01")).setUTCHours(1), 1464742800000, "setUTCHours()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setDate(2), 86400000, "setDate()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCDate(2), 86400000, "setUTCDate()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setMonth(2), 5097600000, "setMonth()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCMonth(2), 5097600000, "setUTCMonth()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setFullYear(1971), 31536000000, "setFullYear()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setFullYear(1971, 2, 3), 36806400000, "setFullYear(Y,M,D)");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCFullYear(1971), 31536000000, "setUTCFullYear()");
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCFullYear(1971, 2, 3), 36806400000, "setUTCFullYear(Y,M,D)");
 | 
			
		||||
 | 
			
		||||
	var d = new Date();
 | 
			
		||||
	d.setTime(1151877845000);
 | 
			
		||||
	assert.sameValue(d.getHours(), 23, "d.getHours()");
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	l := time.Local
 | 
			
		||||
	defer func() {
 | 
			
		||||
		time.Local = l
 | 
			
		||||
	}()
 | 
			
		||||
	var err error
 | 
			
		||||
	time.Local, err = time.LoadLocation("Europe/London")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateParse(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var zero = new Date(0);
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(zero.valueOf(), Date.parse(zero.toString()),
 | 
			
		||||
					 "Date.parse(zeroDate.toString())");
 | 
			
		||||
	assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()),
 | 
			
		||||
					 "Date.parse(zeroDate.toUTCString())");
 | 
			
		||||
	assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()),
 | 
			
		||||
					 "Date.parse(zeroDate.toISOString())");
 | 
			
		||||
 | 
			
		||||
	function testParse(str, expected) {
 | 
			
		||||
		assert.sameValue(Date.parse(str), expected, str);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testParse("Mon, 02 Jan 2006 15:04:05 MST",							1136239445000);
 | 
			
		||||
	testParse("Tue, 22 Jun 2021 13:54:40 GMT",							1624370080000);
 | 
			
		||||
	testParse("Tuesday, 22 Jun 2021 13:54:40 GMT",						1624370080000);
 | 
			
		||||
	testParse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)",				1136239445000);
 | 
			
		||||
	testParse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)",					1136239445000);
 | 
			
		||||
	testParse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)",				1136239445000);
 | 
			
		||||
	testParse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)",	1136239445000);
 | 
			
		||||
	testParse("Mon Jan 2 15:04:05 MST 2006",							1136239445000);
 | 
			
		||||
	testParse("Mon Jan 02 15:04:05 MST 2006",							1136239445000);
 | 
			
		||||
	testParse("Mon Jan 02 15:04:05 -0700 2006",							1136239445000);
 | 
			
		||||
 | 
			
		||||
	testParse("December 04, 1986",	534038400000);
 | 
			
		||||
	testParse("Dec 04, 1986",		534038400000);
 | 
			
		||||
	testParse("Dec 4, 1986",		534038400000);
 | 
			
		||||
 | 
			
		||||
	testParse("2006-01-02T15:04:05.000Z",	1136214245000);
 | 
			
		||||
	testParse("2006-06-02T15:04:05.000",	1149275045000);
 | 
			
		||||
	testParse("2006-01-02T15:04:05",		1136232245000);
 | 
			
		||||
	testParse("2006-01-02",					1136160000000);
 | 
			
		||||
	testParse("2006T15:04-0700",			1136153040000);
 | 
			
		||||
	testParse("2006T15:04Z",				1136127840000);
 | 
			
		||||
	testParse("2019-01-01T12:00:00.52Z",	1546344000520);
 | 
			
		||||
 | 
			
		||||
	var d = new Date("Mon, 02 Jan 2006 15:04:05 MST");
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(d.getUTCHours(), 22,
 | 
			
		||||
					"new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()");
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(d.getHours(), 17,
 | 
			
		||||
					"new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()");
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN,
 | 
			
		||||
					 "Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")");
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN,
 | 
			
		||||
					 "Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")");
 | 
			
		||||
 | 
			
		||||
	var minDateStr = "-271821-04-20T00:00:00.000Z";
 | 
			
		||||
	var minDate = new Date(-8640000000000000);
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr");
 | 
			
		||||
	assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr");
 | 
			
		||||
 | 
			
		||||
	var maxDateStr = "+275760-09-13T00:00:00.000Z";
 | 
			
		||||
	var maxDate = new Date(8640000000000000);
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr");
 | 
			
		||||
	assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr");
 | 
			
		||||
 | 
			
		||||
	var belowRange = "-271821-04-19T23:59:59.999Z";
 | 
			
		||||
	var aboveRange = "+275760-09-13T00:00:00.001Z";
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value");
 | 
			
		||||
	assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value");
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateMaxValues(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCMilliseconds(8.64e15), 8.64e15);
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCSeconds(8640000000000), 8.64e15);
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCMilliseconds(-8.64e15), -8.64e15);
 | 
			
		||||
	assert.sameValue((new Date(0)).setUTCSeconds(-8640000000000), -8.64e15);
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateExport(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	res, err := vm.RunString(`new Date(1000)`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	exp := res.Export()
 | 
			
		||||
	if d, ok := exp.(time.Time); ok {
 | 
			
		||||
		if d.UnixNano()/1e6 != 1000 {
 | 
			
		||||
			t.Fatalf("Invalid exported date: %v", d)
 | 
			
		||||
		}
 | 
			
		||||
		if loc := d.Location(); loc != time.Local {
 | 
			
		||||
			t.Fatalf("Invalid timezone: %v", loc)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Invalid export type: %T", exp)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateToJSON(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	Date.prototype.toJSON.call({ toISOString: function () { return 1; } })
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, intToValue(1), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDateExportType(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v, err := vm.RunString(`new Date()`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if typ := v.ExportType(); typ != typeTime {
 | 
			
		||||
		t.Fatal(typ)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										300
									
								
								goja/destruct.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								goja/destruct.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,300 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type destructKeyedSource struct {
 | 
			
		||||
	r        *Runtime
 | 
			
		||||
	wrapped  Value
 | 
			
		||||
	usedKeys map[Value]struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDestructKeyedSource(r *Runtime, wrapped Value) *destructKeyedSource {
 | 
			
		||||
	return &destructKeyedSource{
 | 
			
		||||
		r:       r,
 | 
			
		||||
		wrapped: wrapped,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newDestructKeyedSource(wrapped Value) *Object {
 | 
			
		||||
	return &Object{
 | 
			
		||||
		runtime: r,
 | 
			
		||||
		self:    newDestructKeyedSource(r, wrapped),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) w() objectImpl {
 | 
			
		||||
	return d.wrapped.ToObject(d.r).self
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) recordKey(key Value) {
 | 
			
		||||
	if d.usedKeys == nil {
 | 
			
		||||
		d.usedKeys = make(map[Value]struct{})
 | 
			
		||||
	}
 | 
			
		||||
	d.usedKeys[key] = struct{}{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) sortLen() int {
 | 
			
		||||
	return d.w().sortLen()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) sortGet(i int) Value {
 | 
			
		||||
	return d.w().sortGet(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) swap(i int, i2 int) {
 | 
			
		||||
	d.w().swap(i, i2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) className() string {
 | 
			
		||||
	return d.w().className()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) typeOf() String {
 | 
			
		||||
	return d.w().typeOf()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getStr(p unistring.String, receiver Value) Value {
 | 
			
		||||
	d.recordKey(stringValueFromRaw(p))
 | 
			
		||||
	return d.w().getStr(p, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getIdx(p valueInt, receiver Value) Value {
 | 
			
		||||
	d.recordKey(p.toString())
 | 
			
		||||
	return d.w().getIdx(p, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getSym(p *Symbol, receiver Value) Value {
 | 
			
		||||
	d.recordKey(p)
 | 
			
		||||
	return d.w().getSym(p, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getOwnPropStr(u unistring.String) Value {
 | 
			
		||||
	d.recordKey(stringValueFromRaw(u))
 | 
			
		||||
	return d.w().getOwnPropStr(u)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getOwnPropIdx(v valueInt) Value {
 | 
			
		||||
	d.recordKey(v.toString())
 | 
			
		||||
	return d.w().getOwnPropIdx(v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getOwnPropSym(symbol *Symbol) Value {
 | 
			
		||||
	d.recordKey(symbol)
 | 
			
		||||
	return d.w().getOwnPropSym(symbol)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setOwnStr(p unistring.String, v Value, throw bool) bool {
 | 
			
		||||
	return d.w().setOwnStr(p, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setOwnIdx(p valueInt, v Value, throw bool) bool {
 | 
			
		||||
	return d.w().setOwnIdx(p, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setOwnSym(p *Symbol, v Value, throw bool) bool {
 | 
			
		||||
	return d.w().setOwnSym(p, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	return d.w().setForeignStr(p, v, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	return d.w().setForeignIdx(p, v, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	return d.w().setForeignSym(p, v, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasPropertyStr(u unistring.String) bool {
 | 
			
		||||
	return d.w().hasPropertyStr(u)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	return d.w().hasPropertyIdx(idx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasPropertySym(s *Symbol) bool {
 | 
			
		||||
	return d.w().hasPropertySym(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasOwnPropertyStr(u unistring.String) bool {
 | 
			
		||||
	return d.w().hasOwnPropertyStr(u)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasOwnPropertyIdx(v valueInt) bool {
 | 
			
		||||
	return d.w().hasOwnPropertyIdx(v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasOwnPropertySym(s *Symbol) bool {
 | 
			
		||||
	return d.w().hasOwnPropertySym(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	return d.w().defineOwnPropertyStr(name, desc, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	return d.w().defineOwnPropertyIdx(name, desc, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	return d.w().defineOwnPropertySym(name, desc, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	return d.w().deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) deleteIdx(idx valueInt, throw bool) bool {
 | 
			
		||||
	return d.w().deleteIdx(idx, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) deleteSym(s *Symbol, throw bool) bool {
 | 
			
		||||
	return d.w().deleteSym(s, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) assertCallable() (call func(FunctionCall) Value, ok bool) {
 | 
			
		||||
	return d.w().assertCallable()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) vmCall(vm *vm, n int) {
 | 
			
		||||
	d.w().vmCall(vm, n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) assertConstructor() func(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	return d.w().assertConstructor()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) proto() *Object {
 | 
			
		||||
	return d.w().proto()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) setProto(proto *Object, throw bool) bool {
 | 
			
		||||
	return d.w().setProto(proto, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) hasInstance(v Value) bool {
 | 
			
		||||
	return d.w().hasInstance(v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) isExtensible() bool {
 | 
			
		||||
	return d.w().isExtensible()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) preventExtensions(throw bool) bool {
 | 
			
		||||
	return d.w().preventExtensions(throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type destructKeyedSourceIter struct {
 | 
			
		||||
	d       *destructKeyedSource
 | 
			
		||||
	wrapped iterNextFunc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *destructKeyedSourceIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	for {
 | 
			
		||||
		item, next := i.wrapped()
 | 
			
		||||
		if next == nil {
 | 
			
		||||
			return item, nil
 | 
			
		||||
		}
 | 
			
		||||
		i.wrapped = next
 | 
			
		||||
		if _, exists := i.d.usedKeys[item.name]; !exists {
 | 
			
		||||
			return item, i.next
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&destructKeyedSourceIter{
 | 
			
		||||
		d:       d,
 | 
			
		||||
		wrapped: d.w().iterateStringKeys(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) iterateSymbols() iterNextFunc {
 | 
			
		||||
	return (&destructKeyedSourceIter{
 | 
			
		||||
		d:       d,
 | 
			
		||||
		wrapped: d.w().iterateSymbols(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) iterateKeys() iterNextFunc {
 | 
			
		||||
	return (&destructKeyedSourceIter{
 | 
			
		||||
		d:       d,
 | 
			
		||||
		wrapped: d.w().iterateKeys(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	return d.w().export(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) exportType() reflect.Type {
 | 
			
		||||
	return d.w().exportType()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	return d.w().exportToMap(dst, typ, ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	return d.w().exportToArrayOrSlice(dst, typ, ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) equal(impl objectImpl) bool {
 | 
			
		||||
	return d.w().equal(impl)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	var next iterNextFunc
 | 
			
		||||
	if all {
 | 
			
		||||
		next = d.iterateStringKeys()
 | 
			
		||||
	} else {
 | 
			
		||||
		next = (&enumerableIter{
 | 
			
		||||
			o:       d.wrapped.ToObject(d.r),
 | 
			
		||||
			wrapped: d.iterateStringKeys(),
 | 
			
		||||
		}).next
 | 
			
		||||
	}
 | 
			
		||||
	for item, next := next(); next != nil; item, next = next() {
 | 
			
		||||
		accum = append(accum, item.name)
 | 
			
		||||
	}
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) filterUsedKeys(keys []Value) []Value {
 | 
			
		||||
	k := 0
 | 
			
		||||
	for i, key := range keys {
 | 
			
		||||
		if _, exists := d.usedKeys[key]; exists {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if k != i {
 | 
			
		||||
			keys[k] = key
 | 
			
		||||
		}
 | 
			
		||||
		k++
 | 
			
		||||
	}
 | 
			
		||||
	return keys[:k]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) symbols(all bool, accum []Value) []Value {
 | 
			
		||||
	return d.filterUsedKeys(d.w().symbols(all, accum))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) keys(all bool, accum []Value) []Value {
 | 
			
		||||
	return d.filterUsedKeys(d.w().keys(all, accum))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
 | 
			
		||||
	return d.w()._putProp(name, value, writable, enumerable, configurable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) _putSym(s *Symbol, prop Value) {
 | 
			
		||||
	d.w()._putSym(s, prop)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *destructKeyedSource) getPrivateEnv(typ *privateEnvType, create bool) *privateElements {
 | 
			
		||||
	return d.w().getPrivateEnv(typ, create)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								goja/extract_failed_tests.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								goja/extract_failed_tests.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
sed -En 's/^.*FAIL: TestTC39\/tc39\/(test\/.*.js).*$/"\1": true,/p'
 | 
			
		||||
							
								
								
									
										110
									
								
								goja/file/README.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								goja/file/README.markdown
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
			
		||||
# file
 | 
			
		||||
--
 | 
			
		||||
    import "github.com/dop251/goja/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
 | 
			
		||||
							
								
								
									
										234
									
								
								goja/file/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								goja/file/file.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,234 @@
 | 
			
		||||
// Package file encapsulates the file abstractions used by the ast & parser.
 | 
			
		||||
package file
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-sourcemap/sourcemap"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	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 {
 | 
			
		||||
	for _, file := range self.files {
 | 
			
		||||
		if idx <= Idx(file.base+len(file.src)) {
 | 
			
		||||
			return file.Position(int(idx) - file.base)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return Position{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type File struct {
 | 
			
		||||
	mu                sync.Mutex
 | 
			
		||||
	name              string
 | 
			
		||||
	src               string
 | 
			
		||||
	base              int // This will always be 1 or greater
 | 
			
		||||
	sourceMap         *sourcemap.Consumer
 | 
			
		||||
	lineOffsets       []int
 | 
			
		||||
	lastScannedOffset int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fl *File) SetSourceMap(m *sourcemap.Consumer) {
 | 
			
		||||
	fl.sourceMap = m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fl *File) Position(offset int) Position {
 | 
			
		||||
	var line int
 | 
			
		||||
	var lineOffsets []int
 | 
			
		||||
	fl.mu.Lock()
 | 
			
		||||
	if offset > fl.lastScannedOffset {
 | 
			
		||||
		line = fl.scanTo(offset)
 | 
			
		||||
		lineOffsets = fl.lineOffsets
 | 
			
		||||
		fl.mu.Unlock()
 | 
			
		||||
	} else {
 | 
			
		||||
		lineOffsets = fl.lineOffsets
 | 
			
		||||
		fl.mu.Unlock()
 | 
			
		||||
		line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var lineStart int
 | 
			
		||||
	if line >= 0 {
 | 
			
		||||
		lineStart = lineOffsets[line]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	row := line + 2
 | 
			
		||||
	col := offset - lineStart + 1
 | 
			
		||||
 | 
			
		||||
	if fl.sourceMap != nil {
 | 
			
		||||
		if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok {
 | 
			
		||||
			sourceUrlStr := source
 | 
			
		||||
			sourceURL := ResolveSourcemapURL(fl.Name(), source)
 | 
			
		||||
			if sourceURL != nil {
 | 
			
		||||
				sourceUrlStr = sourceURL.String()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Position{
 | 
			
		||||
				Filename: sourceUrlStr,
 | 
			
		||||
				Line:     row,
 | 
			
		||||
				Column:   col,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return Position{
 | 
			
		||||
		Filename: fl.name,
 | 
			
		||||
		Line:     row,
 | 
			
		||||
		Column:   col,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ResolveSourcemapURL(basename, source string) *url.URL {
 | 
			
		||||
	// if the url is absolute(has scheme) there is nothing to do
 | 
			
		||||
	smURL, err := url.Parse(strings.TrimSpace(source))
 | 
			
		||||
	if err == nil && !smURL.IsAbs() {
 | 
			
		||||
		baseURL, err1 := url.Parse(strings.TrimSpace(basename))
 | 
			
		||||
		if err1 == nil && path.IsAbs(baseURL.Path) {
 | 
			
		||||
			smURL = baseURL.ResolveReference(smURL)
 | 
			
		||||
		} else {
 | 
			
		||||
			// pathological case where both are not absolute paths and using Resolve
 | 
			
		||||
			// as above will produce an absolute one
 | 
			
		||||
			smURL, _ = url.Parse(path.Join(path.Dir(basename), smURL.Path))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return smURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findNextLineStart(s string) int {
 | 
			
		||||
	for pos, ch := range s {
 | 
			
		||||
		switch ch {
 | 
			
		||||
		case '\r':
 | 
			
		||||
			if pos < len(s)-1 && s[pos+1] == '\n' {
 | 
			
		||||
				return pos + 2
 | 
			
		||||
			}
 | 
			
		||||
			return pos + 1
 | 
			
		||||
		case '\n':
 | 
			
		||||
			return pos + 1
 | 
			
		||||
		case '\u2028', '\u2029':
 | 
			
		||||
			return pos + 3
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fl *File) scanTo(offset int) int {
 | 
			
		||||
	o := fl.lastScannedOffset
 | 
			
		||||
	for o < offset {
 | 
			
		||||
		p := findNextLineStart(fl.src[o:])
 | 
			
		||||
		if p == -1 {
 | 
			
		||||
			fl.lastScannedOffset = len(fl.src)
 | 
			
		||||
			return len(fl.lineOffsets) - 1
 | 
			
		||||
		}
 | 
			
		||||
		o = o + p
 | 
			
		||||
		fl.lineOffsets = append(fl.lineOffsets, o)
 | 
			
		||||
	}
 | 
			
		||||
	fl.lastScannedOffset = o
 | 
			
		||||
 | 
			
		||||
	if o == offset {
 | 
			
		||||
		return len(fl.lineOffsets) - 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(fl.lineOffsets) - 2
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								goja/file/file_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								goja/file/file_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
package file
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPosition(t *testing.T) {
 | 
			
		||||
	const SRC = `line1
 | 
			
		||||
line2
 | 
			
		||||
line3`
 | 
			
		||||
	f := NewFile("", SRC, 0)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		offset int
 | 
			
		||||
		line   int
 | 
			
		||||
		col    int
 | 
			
		||||
	}{
 | 
			
		||||
		{0, 1, 1},
 | 
			
		||||
		{2, 1, 3},
 | 
			
		||||
		{2, 1, 3},
 | 
			
		||||
		{6, 2, 1},
 | 
			
		||||
		{7, 2, 2},
 | 
			
		||||
		{12, 3, 1},
 | 
			
		||||
		{12, 3, 1},
 | 
			
		||||
		{13, 3, 2},
 | 
			
		||||
		{13, 3, 2},
 | 
			
		||||
		{16, 3, 5},
 | 
			
		||||
		{17, 3, 6},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, test := range tests {
 | 
			
		||||
		if p := f.Position(test.offset); p.Line != test.line || p.Column != test.col {
 | 
			
		||||
			t.Fatalf("%d. Line: %d, col: %d", i, p.Line, p.Column)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileConcurrency(t *testing.T) {
 | 
			
		||||
	const SRC = `line1
 | 
			
		||||
line2
 | 
			
		||||
line3`
 | 
			
		||||
	f := NewFile("", SRC, 0)
 | 
			
		||||
	go func() {
 | 
			
		||||
		f.Position(12)
 | 
			
		||||
	}()
 | 
			
		||||
	f.Position(2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSourceFilename(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		source, basename, result string
 | 
			
		||||
	}{
 | 
			
		||||
		{"test.js", "base.js", "test.js"},
 | 
			
		||||
		{"test.js", "../base.js", "../test.js"},
 | 
			
		||||
		{"test.js", "/somewhere/base.js", "/somewhere/test.js"},
 | 
			
		||||
		{"/test.js", "/somewhere/base.js", "/test.js"},
 | 
			
		||||
		{"/test.js", "file:///somewhere/base.js", "file:///test.js"},
 | 
			
		||||
		{"file:///test.js", "base.js", "file:///test.js"},
 | 
			
		||||
		{"file:///test.js", "/somwehere/base.js", "file:///test.js"},
 | 
			
		||||
		{"file:///test.js", "file:///somewhere/base.js", "file:///test.js"},
 | 
			
		||||
		{"../test.js", "/somewhere/else/base.js", "/somewhere/test.js"},
 | 
			
		||||
		{"../test.js", "file:///somewhere/else/base.js", "file:///somewhere/test.js"},
 | 
			
		||||
		{"../test.js", "https://example.com/somewhere/else/base.js", "https://example.com/somewhere/test.js"},
 | 
			
		||||
		{"\ntest.js", "base123.js", "test.js"},
 | 
			
		||||
		{"\rtest2.js\t\n  ", "base123.js", "test2.js"},
 | 
			
		||||
		// TODO find something that won't parse
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		resultURL := ResolveSourcemapURL(test.basename, test.source)
 | 
			
		||||
		result := resultURL.String()
 | 
			
		||||
		if result != test.result {
 | 
			
		||||
			t.Fatalf("source: %q, basename %q produced %q instead of %q", test.source, test.basename, result, test.result)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								goja/ftoa/LICENSE_LUCENE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								goja/ftoa/LICENSE_LUCENE
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
Copyright (C) 1998, 1999 by Lucent Technologies
 | 
			
		||||
All Rights Reserved
 | 
			
		||||
 | 
			
		||||
Permission to use, copy, modify, and distribute this software and
 | 
			
		||||
its documentation for any purpose and without fee is hereby
 | 
			
		||||
granted, provided that the above copyright notice appear in all
 | 
			
		||||
copies and that both that the copyright notice and this
 | 
			
		||||
permission notice and warranty disclaimer appear in supporting
 | 
			
		||||
documentation, and that the name of Lucent or any of its entities
 | 
			
		||||
not be used in advertising or publicity pertaining to
 | 
			
		||||
distribution of the software without specific, written prior
 | 
			
		||||
permission.
 | 
			
		||||
 | 
			
		||||
LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 | 
			
		||||
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
 | 
			
		||||
IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
 | 
			
		||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 | 
			
		||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 | 
			
		||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 | 
			
		||||
THIS SOFTWARE.
 | 
			
		||||
							
								
								
									
										150
									
								
								goja/ftoa/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								goja/ftoa/common.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,150 @@
 | 
			
		||||
/*
 | 
			
		||||
Package ftoa provides ECMAScript-compliant floating point number conversion to string.
 | 
			
		||||
 | 
			
		||||
It contains code ported from Rhino (https://github.com/mozilla/rhino/blob/master/src/org/mozilla/javascript/DToA.java)
 | 
			
		||||
as well as from the original code by David M. Gay.
 | 
			
		||||
 | 
			
		||||
See LICENSE_LUCENE for the original copyright message and disclaimer.
 | 
			
		||||
*/
 | 
			
		||||
package ftoa
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func lo0bits(x uint32) (k int) {
 | 
			
		||||
 | 
			
		||||
	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 int) {
 | 
			
		||||
 | 
			
		||||
	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 []byte) (e, bits int, dblBits []byte) {
 | 
			
		||||
	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 int
 | 
			
		||||
	if de = int(d0 >> exp_shift); de != 0 {
 | 
			
		||||
		z |= exp_msk1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	y := d1
 | 
			
		||||
	if y != 0 {
 | 
			
		||||
		dblBits = b[:8]
 | 
			
		||||
		k = lo0bits(y)
 | 
			
		||||
		y >>= k
 | 
			
		||||
		if k != 0 {
 | 
			
		||||
			stuffBits(dblBits, 4, y|z<<(32-k))
 | 
			
		||||
			z >>= k
 | 
			
		||||
		} else {
 | 
			
		||||
			stuffBits(dblBits, 4, y)
 | 
			
		||||
		}
 | 
			
		||||
		stuffBits(dblBits, 0, z)
 | 
			
		||||
		if z != 0 {
 | 
			
		||||
			i = 2
 | 
			
		||||
		} else {
 | 
			
		||||
			i = 1
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		dblBits = b[:4]
 | 
			
		||||
		k = lo0bits(z)
 | 
			
		||||
		z >>= k
 | 
			
		||||
		stuffBits(dblBits, 0, z)
 | 
			
		||||
		k += 32
 | 
			
		||||
		i = 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if de != 0 {
 | 
			
		||||
		e = de - bias - (p - 1) + k
 | 
			
		||||
		bits = p - k
 | 
			
		||||
	} else {
 | 
			
		||||
		e = de - bias - (p - 1) + 1 + k
 | 
			
		||||
		bits = 32*i - hi0bits(z)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										699
									
								
								goja/ftoa/ftoa.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										699
									
								
								goja/ftoa/ftoa.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,699 @@
 | 
			
		||||
package ftoa
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/big"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	exp_11     = 0x3ff00000
 | 
			
		||||
	frac_mask1 = 0xfffff
 | 
			
		||||
	bletch     = 0x10
 | 
			
		||||
	quick_max  = 14
 | 
			
		||||
	int_max    = 14
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	tens = [...]float64{
 | 
			
		||||
		1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
 | 
			
		||||
		1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 | 
			
		||||
		1e20, 1e21, 1e22,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bigtens = [...]float64{1e16, 1e32, 1e64, 1e128, 1e256}
 | 
			
		||||
 | 
			
		||||
	big5  = big.NewInt(5)
 | 
			
		||||
	big10 = big.NewInt(10)
 | 
			
		||||
 | 
			
		||||
	p05       = []*big.Int{big5, big.NewInt(25), big.NewInt(125)}
 | 
			
		||||
	pow5Cache [7]*big.Int
 | 
			
		||||
 | 
			
		||||
	dtoaModes = []int{
 | 
			
		||||
		ModeStandard:            0,
 | 
			
		||||
		ModeStandardExponential: 0,
 | 
			
		||||
		ModeFixed:               3,
 | 
			
		||||
		ModeExponential:         2,
 | 
			
		||||
		ModePrecision:           2,
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
d must be > 0 and must not be Inf
 | 
			
		||||
 | 
			
		||||
mode:
 | 
			
		||||
 | 
			
		||||
	0 ==> shortest string that yields d when read in
 | 
			
		||||
		and rounded to nearest.
 | 
			
		||||
	1 ==> like 0, but with Steele & White stopping rule;
 | 
			
		||||
		e.g. with IEEE P754 arithmetic , mode 0 gives
 | 
			
		||||
		1e23 whereas mode 1 gives 9.999999999999999e22.
 | 
			
		||||
	2 ==> max(1,ndigits) significant digits.  This gives a
 | 
			
		||||
		return value similar to that of ecvt, except
 | 
			
		||||
		that trailing zeros are suppressed.
 | 
			
		||||
	3 ==> through ndigits past the decimal point.  This
 | 
			
		||||
		gives a return value similar to that from fcvt,
 | 
			
		||||
		except that trailing zeros are suppressed, and
 | 
			
		||||
		ndigits can be negative.
 | 
			
		||||
	4,5 ==> similar to 2 and 3, respectively, but (in
 | 
			
		||||
		round-nearest mode) with the tests of mode 0 to
 | 
			
		||||
		possibly return a shorter string that rounds to d.
 | 
			
		||||
		With IEEE arithmetic and compilation with
 | 
			
		||||
		-DHonor_FLT_ROUNDS, modes 4 and 5 behave the same
 | 
			
		||||
		as modes 2 and 3 when FLT_ROUNDS != 1.
 | 
			
		||||
	6-9 ==> Debugging modes similar to mode - 4:  don't try
 | 
			
		||||
		fast floating-point estimate (if applicable).
 | 
			
		||||
 | 
			
		||||
	Values of mode other than 0-9 are treated as mode 0.
 | 
			
		||||
*/
 | 
			
		||||
func ftoa(d float64, mode int, biasUp bool, ndigits int, buf []byte) ([]byte, int) {
 | 
			
		||||
	startPos := len(buf)
 | 
			
		||||
	dblBits := make([]byte, 0, 8)
 | 
			
		||||
	be, bbits, dblBits := d2b(d, dblBits)
 | 
			
		||||
 | 
			
		||||
	dBits := math.Float64bits(d)
 | 
			
		||||
	word0 := uint32(dBits >> 32)
 | 
			
		||||
	word1 := uint32(dBits)
 | 
			
		||||
 | 
			
		||||
	i := int((word0 >> exp_shift1) & (exp_mask >> exp_shift1))
 | 
			
		||||
	var d2 float64
 | 
			
		||||
	var denorm bool
 | 
			
		||||
	if i != 0 {
 | 
			
		||||
		d2 = setWord0(d, (word0&frac_mask1)|exp_11)
 | 
			
		||||
		i -= bias
 | 
			
		||||
		denorm = false
 | 
			
		||||
	} else {
 | 
			
		||||
		/* d is denormalized */
 | 
			
		||||
		i = bbits + be + (bias + (p - 1) - 1)
 | 
			
		||||
		var x uint64
 | 
			
		||||
		if i > 32 {
 | 
			
		||||
			x = uint64(word0)<<(64-i) | uint64(word1)>>(i-32)
 | 
			
		||||
		} else {
 | 
			
		||||
			x = uint64(word1) << (32 - i)
 | 
			
		||||
		}
 | 
			
		||||
		d2 = setWord0(float64(x), uint32((x>>32)-31*exp_mask))
 | 
			
		||||
		i -= (bias + (p - 1) - 1) + 1
 | 
			
		||||
		denorm = true
 | 
			
		||||
	}
 | 
			
		||||
	/* At this point d = f*2^i, where 1 <= f < 2.  d2 is an approximation of f. */
 | 
			
		||||
	ds := (d2-1.5)*0.289529654602168 + 0.1760912590558 + float64(i)*0.301029995663981
 | 
			
		||||
	k := int(ds)
 | 
			
		||||
	if ds < 0.0 && ds != float64(k) {
 | 
			
		||||
		k-- /* want k = floor(ds) */
 | 
			
		||||
	}
 | 
			
		||||
	k_check := true
 | 
			
		||||
	if k >= 0 && k < len(tens) {
 | 
			
		||||
		if d < tens[k] {
 | 
			
		||||
			k--
 | 
			
		||||
		}
 | 
			
		||||
		k_check = false
 | 
			
		||||
	}
 | 
			
		||||
	/* At this point floor(log10(d)) <= k <= floor(log10(d))+1.
 | 
			
		||||
	   If k_check is zero, we're guaranteed that k = floor(log10(d)). */
 | 
			
		||||
	j := bbits - i - 1
 | 
			
		||||
	var b2, s2, b5, s5 int
 | 
			
		||||
	/* At this point d = b/2^j, where b is an odd integer. */
 | 
			
		||||
	if j >= 0 {
 | 
			
		||||
		b2 = 0
 | 
			
		||||
		s2 = j
 | 
			
		||||
	} else {
 | 
			
		||||
		b2 = -j
 | 
			
		||||
		s2 = 0
 | 
			
		||||
	}
 | 
			
		||||
	if k >= 0 {
 | 
			
		||||
		b5 = 0
 | 
			
		||||
		s5 = k
 | 
			
		||||
		s2 += k
 | 
			
		||||
	} else {
 | 
			
		||||
		b2 -= k
 | 
			
		||||
		b5 = -k
 | 
			
		||||
		s5 = 0
 | 
			
		||||
	}
 | 
			
		||||
	/* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer,
 | 
			
		||||
	   b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */
 | 
			
		||||
	if mode < 0 || mode > 9 {
 | 
			
		||||
		mode = 0
 | 
			
		||||
	}
 | 
			
		||||
	try_quick := true
 | 
			
		||||
	if mode > 5 {
 | 
			
		||||
		mode -= 4
 | 
			
		||||
		try_quick = false
 | 
			
		||||
	}
 | 
			
		||||
	leftright := true
 | 
			
		||||
	var ilim, ilim1 int
 | 
			
		||||
	switch mode {
 | 
			
		||||
	case 0, 1:
 | 
			
		||||
		ilim, ilim1 = -1, -1
 | 
			
		||||
		ndigits = 0
 | 
			
		||||
	case 2:
 | 
			
		||||
		leftright = false
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 4:
 | 
			
		||||
		if ndigits <= 0 {
 | 
			
		||||
			ndigits = 1
 | 
			
		||||
		}
 | 
			
		||||
		ilim, ilim1 = ndigits, ndigits
 | 
			
		||||
	case 3:
 | 
			
		||||
		leftright = false
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 5:
 | 
			
		||||
		i = ndigits + k + 1
 | 
			
		||||
		ilim = i
 | 
			
		||||
		ilim1 = i - 1
 | 
			
		||||
	}
 | 
			
		||||
	/* ilim is the maximum number of significant digits we want, based on k and ndigits. */
 | 
			
		||||
	/* ilim1 is the maximum number of significant digits we want, based on k and ndigits,
 | 
			
		||||
	   when it turns out that k was computed too high by one. */
 | 
			
		||||
	fast_failed := false
 | 
			
		||||
	if ilim >= 0 && ilim <= quick_max && try_quick {
 | 
			
		||||
 | 
			
		||||
		/* Try to get by with floating-point arithmetic. */
 | 
			
		||||
 | 
			
		||||
		i = 0
 | 
			
		||||
		d2 = d
 | 
			
		||||
		k0 := k
 | 
			
		||||
		ilim0 := ilim
 | 
			
		||||
		ieps := 2 /* conservative */
 | 
			
		||||
		/* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */
 | 
			
		||||
		if k > 0 {
 | 
			
		||||
			ds = tens[k&0xf]
 | 
			
		||||
			j = k >> 4
 | 
			
		||||
			if (j & bletch) != 0 {
 | 
			
		||||
				/* prevent overflows */
 | 
			
		||||
				j &= bletch - 1
 | 
			
		||||
				d /= bigtens[len(bigtens)-1]
 | 
			
		||||
				ieps++
 | 
			
		||||
			}
 | 
			
		||||
			for ; j != 0; i++ {
 | 
			
		||||
				if (j & 1) != 0 {
 | 
			
		||||
					ieps++
 | 
			
		||||
					ds *= bigtens[i]
 | 
			
		||||
				}
 | 
			
		||||
				j >>= 1
 | 
			
		||||
			}
 | 
			
		||||
			d /= ds
 | 
			
		||||
		} else if j1 := -k; j1 != 0 {
 | 
			
		||||
			d *= tens[j1&0xf]
 | 
			
		||||
			for j = j1 >> 4; j != 0; i++ {
 | 
			
		||||
				if (j & 1) != 0 {
 | 
			
		||||
					ieps++
 | 
			
		||||
					d *= bigtens[i]
 | 
			
		||||
				}
 | 
			
		||||
				j >>= 1
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		/* Check that k was computed correctly. */
 | 
			
		||||
		if k_check && d < 1.0 && ilim > 0 {
 | 
			
		||||
			if ilim1 <= 0 {
 | 
			
		||||
				fast_failed = true
 | 
			
		||||
			} else {
 | 
			
		||||
				ilim = ilim1
 | 
			
		||||
				k--
 | 
			
		||||
				d *= 10.
 | 
			
		||||
				ieps++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		/* eps bounds the cumulative error. */
 | 
			
		||||
		eps := float64(ieps)*d + 7.0
 | 
			
		||||
		eps = setWord0(eps, _word0(eps)-(p-1)*exp_msk1)
 | 
			
		||||
		if ilim == 0 {
 | 
			
		||||
			d -= 5.0
 | 
			
		||||
			if d > eps {
 | 
			
		||||
				buf = append(buf, '1')
 | 
			
		||||
				k++
 | 
			
		||||
				return buf, k + 1
 | 
			
		||||
			}
 | 
			
		||||
			if d < -eps {
 | 
			
		||||
				buf = append(buf, '0')
 | 
			
		||||
				return buf, 1
 | 
			
		||||
			}
 | 
			
		||||
			fast_failed = true
 | 
			
		||||
		}
 | 
			
		||||
		if !fast_failed {
 | 
			
		||||
			fast_failed = true
 | 
			
		||||
			if leftright {
 | 
			
		||||
				/* Use Steele & White method of only
 | 
			
		||||
				 * generating digits needed.
 | 
			
		||||
				 */
 | 
			
		||||
				eps = 0.5/tens[ilim-1] - eps
 | 
			
		||||
				for i = 0; ; {
 | 
			
		||||
					l := int64(d)
 | 
			
		||||
					d -= float64(l)
 | 
			
		||||
					buf = append(buf, byte('0'+l))
 | 
			
		||||
					if d < eps {
 | 
			
		||||
						return buf, k + 1
 | 
			
		||||
					}
 | 
			
		||||
					if 1.0-d < eps {
 | 
			
		||||
						buf, k = bumpUp(buf, k)
 | 
			
		||||
						return buf, k + 1
 | 
			
		||||
					}
 | 
			
		||||
					i++
 | 
			
		||||
					if i >= ilim {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					eps *= 10.0
 | 
			
		||||
					d *= 10.0
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				/* Generate ilim digits, then fix them up. */
 | 
			
		||||
				eps *= tens[ilim-1]
 | 
			
		||||
				for i = 1; ; i++ {
 | 
			
		||||
					l := int64(d)
 | 
			
		||||
					d -= float64(l)
 | 
			
		||||
					buf = append(buf, byte('0'+l))
 | 
			
		||||
					if i == ilim {
 | 
			
		||||
						if d > 0.5+eps {
 | 
			
		||||
							buf, k = bumpUp(buf, k)
 | 
			
		||||
							return buf, k + 1
 | 
			
		||||
						} else if d < 0.5-eps {
 | 
			
		||||
							buf = stripTrailingZeroes(buf, startPos)
 | 
			
		||||
							return buf, k + 1
 | 
			
		||||
						}
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					d *= 10.0
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if fast_failed {
 | 
			
		||||
			buf = buf[:startPos]
 | 
			
		||||
			d = d2
 | 
			
		||||
			k = k0
 | 
			
		||||
			ilim = ilim0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Do we have a "small" integer? */
 | 
			
		||||
	if be >= 0 && k <= int_max {
 | 
			
		||||
		/* Yes. */
 | 
			
		||||
		ds = tens[k]
 | 
			
		||||
		if ndigits < 0 && ilim <= 0 {
 | 
			
		||||
			if ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds) {
 | 
			
		||||
				buf = buf[:startPos]
 | 
			
		||||
				buf = append(buf, '0')
 | 
			
		||||
				return buf, 1
 | 
			
		||||
			}
 | 
			
		||||
			buf = append(buf, '1')
 | 
			
		||||
			k++
 | 
			
		||||
			return buf, k + 1
 | 
			
		||||
		}
 | 
			
		||||
		for i = 1; ; i++ {
 | 
			
		||||
			l := int64(d / ds)
 | 
			
		||||
			d -= float64(l) * ds
 | 
			
		||||
			buf = append(buf, byte('0'+l))
 | 
			
		||||
			if i == ilim {
 | 
			
		||||
				d += d
 | 
			
		||||
				if (d > ds) || (d == ds && (((l & 1) != 0) || biasUp)) {
 | 
			
		||||
					buf, k = bumpUp(buf, k)
 | 
			
		||||
				}
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			d *= 10.0
 | 
			
		||||
			if d == 0 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return buf, k + 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m2 := b2
 | 
			
		||||
	m5 := b5
 | 
			
		||||
	var mhi, mlo *big.Int
 | 
			
		||||
	if leftright {
 | 
			
		||||
		if mode < 2 {
 | 
			
		||||
			if denorm {
 | 
			
		||||
				i = be + (bias + (p - 1) - 1 + 1)
 | 
			
		||||
			} else {
 | 
			
		||||
				i = 1 + p - bbits
 | 
			
		||||
			}
 | 
			
		||||
			/* i is 1 plus the number of trailing zero bits in d's significand. Thus,
 | 
			
		||||
			   (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */
 | 
			
		||||
		} else {
 | 
			
		||||
			j = ilim - 1
 | 
			
		||||
			if m5 >= j {
 | 
			
		||||
				m5 -= j
 | 
			
		||||
			} else {
 | 
			
		||||
				j -= m5
 | 
			
		||||
				s5 += j
 | 
			
		||||
				b5 += j
 | 
			
		||||
				m5 = 0
 | 
			
		||||
			}
 | 
			
		||||
			i = ilim
 | 
			
		||||
			if i < 0 {
 | 
			
		||||
				m2 -= i
 | 
			
		||||
				i = 0
 | 
			
		||||
			}
 | 
			
		||||
			/* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */
 | 
			
		||||
		}
 | 
			
		||||
		b2 += i
 | 
			
		||||
		s2 += i
 | 
			
		||||
		mhi = big.NewInt(1)
 | 
			
		||||
		/* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or
 | 
			
		||||
		   input (when mode < 2) significant digit, divided by 10^k. */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5).  Reduce common factors in
 | 
			
		||||
	   b2, m2, and s2 without changing the equalities. */
 | 
			
		||||
	if m2 > 0 && s2 > 0 {
 | 
			
		||||
		if m2 < s2 {
 | 
			
		||||
			i = m2
 | 
			
		||||
		} else {
 | 
			
		||||
			i = s2
 | 
			
		||||
		}
 | 
			
		||||
		b2 -= i
 | 
			
		||||
		m2 -= i
 | 
			
		||||
		s2 -= i
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b := new(big.Int).SetBytes(dblBits)
 | 
			
		||||
	/* Fold b5 into b and m5 into mhi. */
 | 
			
		||||
	if b5 > 0 {
 | 
			
		||||
		if leftright {
 | 
			
		||||
			if m5 > 0 {
 | 
			
		||||
				pow5mult(mhi, m5)
 | 
			
		||||
				b.Mul(mhi, b)
 | 
			
		||||
			}
 | 
			
		||||
			j = b5 - m5
 | 
			
		||||
			if j != 0 {
 | 
			
		||||
				pow5mult(b, j)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			pow5mult(b, b5)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	/* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and
 | 
			
		||||
	   (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */
 | 
			
		||||
 | 
			
		||||
	S := big.NewInt(1)
 | 
			
		||||
	if s5 > 0 {
 | 
			
		||||
		pow5mult(S, s5)
 | 
			
		||||
	}
 | 
			
		||||
	/* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and
 | 
			
		||||
	   (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */
 | 
			
		||||
 | 
			
		||||
	/* Check for special case that d is a normalized power of 2. */
 | 
			
		||||
	spec_case := false
 | 
			
		||||
	if mode < 2 {
 | 
			
		||||
		if (_word1(d) == 0) && ((_word0(d) & bndry_mask) == 0) &&
 | 
			
		||||
			((_word0(d) & (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 decimal output string's value is less than d.  */
 | 
			
		||||
			b2 += log2P
 | 
			
		||||
			s2 += log2P
 | 
			
		||||
			spec_case = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Arrange for convenient computation of quotients:
 | 
			
		||||
	 * shift left if necessary so divisor has 4 leading 0 bits.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Perhaps we should just compute leading 28 bits of S once
 | 
			
		||||
	 * and for all and pass them and a shift to quorem, so it
 | 
			
		||||
	 * can do shifts and ors to compute the numerator for q.
 | 
			
		||||
	 */
 | 
			
		||||
	var zz int
 | 
			
		||||
	if s5 != 0 {
 | 
			
		||||
		S_bytes := S.Bytes()
 | 
			
		||||
		var S_hiWord uint32
 | 
			
		||||
		for idx := 0; idx < 4; idx++ {
 | 
			
		||||
			S_hiWord = S_hiWord << 8
 | 
			
		||||
			if idx < len(S_bytes) {
 | 
			
		||||
				S_hiWord |= uint32(S_bytes[idx])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		zz = 32 - hi0bits(S_hiWord)
 | 
			
		||||
	} else {
 | 
			
		||||
		zz = 1
 | 
			
		||||
	}
 | 
			
		||||
	i = (zz + s2) & 0x1f
 | 
			
		||||
	if i != 0 {
 | 
			
		||||
		i = 32 - i
 | 
			
		||||
	}
 | 
			
		||||
	/* i is the number of leading zero bits in the most significant word of S*2^s2. */
 | 
			
		||||
	if i > 4 {
 | 
			
		||||
		i -= 4
 | 
			
		||||
		b2 += i
 | 
			
		||||
		m2 += i
 | 
			
		||||
		s2 += i
 | 
			
		||||
	} else if i < 4 {
 | 
			
		||||
		i += 28
 | 
			
		||||
		b2 += i
 | 
			
		||||
		m2 += i
 | 
			
		||||
		s2 += i
 | 
			
		||||
	}
 | 
			
		||||
	/* Now S*2^s2 has exactly four leading zero bits in its most significant word. */
 | 
			
		||||
	if b2 > 0 {
 | 
			
		||||
		b = b.Lsh(b, uint(b2))
 | 
			
		||||
	}
 | 
			
		||||
	if s2 > 0 {
 | 
			
		||||
		S.Lsh(S, uint(s2))
 | 
			
		||||
	}
 | 
			
		||||
	/* Now we have d/10^k = b/S and
 | 
			
		||||
	   (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */
 | 
			
		||||
	if k_check {
 | 
			
		||||
		if b.Cmp(S) < 0 {
 | 
			
		||||
			k--
 | 
			
		||||
			b.Mul(b, big10) /* we botched the k estimate */
 | 
			
		||||
			if leftright {
 | 
			
		||||
				mhi.Mul(mhi, big10)
 | 
			
		||||
			}
 | 
			
		||||
			ilim = ilim1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	/* At this point 1 <= d/10^k = b/S < 10. */
 | 
			
		||||
 | 
			
		||||
	if ilim <= 0 && mode > 2 {
 | 
			
		||||
		/* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode.
 | 
			
		||||
		   Output either zero or the minimum nonzero output depending on which is closer to d. */
 | 
			
		||||
		if ilim >= 0 {
 | 
			
		||||
			i = b.Cmp(S.Mul(S, big5))
 | 
			
		||||
		}
 | 
			
		||||
		if ilim < 0 || i < 0 || i == 0 && !biasUp {
 | 
			
		||||
			/* Always emit at least one digit.  If the number appears to be zero
 | 
			
		||||
			   using the current mode, then emit one '0' digit and set decpt to 1. */
 | 
			
		||||
			buf = buf[:startPos]
 | 
			
		||||
			buf = append(buf, '0')
 | 
			
		||||
			return buf, 1
 | 
			
		||||
		}
 | 
			
		||||
		buf = append(buf, '1')
 | 
			
		||||
		k++
 | 
			
		||||
		return buf, k + 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var dig byte
 | 
			
		||||
	if leftright {
 | 
			
		||||
		if m2 > 0 {
 | 
			
		||||
			mhi.Lsh(mhi, uint(m2))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Compute mlo -- check for special case
 | 
			
		||||
		 * that d is a normalized power of 2.
 | 
			
		||||
		 */
 | 
			
		||||
 | 
			
		||||
		mlo = mhi
 | 
			
		||||
		if spec_case {
 | 
			
		||||
			mhi = mlo
 | 
			
		||||
			mhi = new(big.Int).Lsh(mhi, log2P)
 | 
			
		||||
		}
 | 
			
		||||
		/* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */
 | 
			
		||||
		/* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */
 | 
			
		||||
		var z, delta big.Int
 | 
			
		||||
		for i = 1; ; i++ {
 | 
			
		||||
			z.DivMod(b, S, b)
 | 
			
		||||
			dig = byte(z.Int64() + '0')
 | 
			
		||||
			/* Do we yet have the shortest decimal string
 | 
			
		||||
			 * that will round to d?
 | 
			
		||||
			 */
 | 
			
		||||
			j = b.Cmp(mlo)
 | 
			
		||||
			/* j is b/S compared with mlo/S. */
 | 
			
		||||
			delta.Sub(S, mhi)
 | 
			
		||||
			var j1 int
 | 
			
		||||
			if delta.Sign() <= 0 {
 | 
			
		||||
				j1 = 1
 | 
			
		||||
			} else {
 | 
			
		||||
				j1 = b.Cmp(&delta)
 | 
			
		||||
			}
 | 
			
		||||
			/* j1 is b/S compared with 1 - mhi/S. */
 | 
			
		||||
			if (j1 == 0) && (mode == 0) && ((_word1(d) & 1) == 0) {
 | 
			
		||||
				if dig == '9' {
 | 
			
		||||
					var flag bool
 | 
			
		||||
					buf = append(buf, '9')
 | 
			
		||||
					if buf, flag = roundOff(buf, startPos); flag {
 | 
			
		||||
						k++
 | 
			
		||||
						buf = append(buf, '1')
 | 
			
		||||
					}
 | 
			
		||||
					return buf, k + 1
 | 
			
		||||
				}
 | 
			
		||||
				if j > 0 {
 | 
			
		||||
					dig++
 | 
			
		||||
				}
 | 
			
		||||
				buf = append(buf, dig)
 | 
			
		||||
				return buf, k + 1
 | 
			
		||||
			}
 | 
			
		||||
			if (j < 0) || ((j == 0) && (mode == 0) && ((_word1(d) & 1) == 0)) {
 | 
			
		||||
				if j1 > 0 {
 | 
			
		||||
					/* Either dig or dig+1 would work here as the least significant decimal digit.
 | 
			
		||||
					   Use whichever would produce a decimal value closer to d. */
 | 
			
		||||
					b.Lsh(b, 1)
 | 
			
		||||
					j1 = b.Cmp(S)
 | 
			
		||||
					if (j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp)) {
 | 
			
		||||
						dig++
 | 
			
		||||
						if dig == '9' {
 | 
			
		||||
							buf = append(buf, '9')
 | 
			
		||||
							buf, flag := roundOff(buf, startPos)
 | 
			
		||||
							if flag {
 | 
			
		||||
								k++
 | 
			
		||||
								buf = append(buf, '1')
 | 
			
		||||
							}
 | 
			
		||||
							return buf, k + 1
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				buf = append(buf, dig)
 | 
			
		||||
				return buf, k + 1
 | 
			
		||||
			}
 | 
			
		||||
			if j1 > 0 {
 | 
			
		||||
				if dig == '9' { /* possible if i == 1 */
 | 
			
		||||
					buf = append(buf, '9')
 | 
			
		||||
					buf, flag := roundOff(buf, startPos)
 | 
			
		||||
					if flag {
 | 
			
		||||
						k++
 | 
			
		||||
						buf = append(buf, '1')
 | 
			
		||||
					}
 | 
			
		||||
					return buf, k + 1
 | 
			
		||||
				}
 | 
			
		||||
				buf = append(buf, dig+1)
 | 
			
		||||
				return buf, k + 1
 | 
			
		||||
			}
 | 
			
		||||
			buf = append(buf, dig)
 | 
			
		||||
			if i == ilim {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			b.Mul(b, big10)
 | 
			
		||||
			if mlo == mhi {
 | 
			
		||||
				mhi.Mul(mhi, big10)
 | 
			
		||||
			} else {
 | 
			
		||||
				mlo.Mul(mlo, big10)
 | 
			
		||||
				mhi.Mul(mhi, big10)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		var z big.Int
 | 
			
		||||
		for i = 1; ; i++ {
 | 
			
		||||
			z.DivMod(b, S, b)
 | 
			
		||||
			dig = byte(z.Int64() + '0')
 | 
			
		||||
			buf = append(buf, dig)
 | 
			
		||||
			if i >= ilim {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			b.Mul(b, big10)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	/* Round off last digit */
 | 
			
		||||
 | 
			
		||||
	b.Lsh(b, 1)
 | 
			
		||||
	j = b.Cmp(S)
 | 
			
		||||
	if (j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp)) {
 | 
			
		||||
		var flag bool
 | 
			
		||||
		buf, flag = roundOff(buf, startPos)
 | 
			
		||||
		if flag {
 | 
			
		||||
			k++
 | 
			
		||||
			buf = append(buf, '1')
 | 
			
		||||
			return buf, k + 1
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		buf = stripTrailingZeroes(buf, startPos)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return buf, k + 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func bumpUp(buf []byte, k int) ([]byte, int) {
 | 
			
		||||
	var lastCh byte
 | 
			
		||||
	stop := 0
 | 
			
		||||
	if len(buf) > 0 && buf[0] == '-' {
 | 
			
		||||
		stop = 1
 | 
			
		||||
	}
 | 
			
		||||
	for {
 | 
			
		||||
		lastCh = buf[len(buf)-1]
 | 
			
		||||
		buf = buf[:len(buf)-1]
 | 
			
		||||
		if lastCh != '9' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if len(buf) == stop {
 | 
			
		||||
			k++
 | 
			
		||||
			lastCh = '0'
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, lastCh+1)
 | 
			
		||||
	return buf, k
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setWord0(d float64, w uint32) float64 {
 | 
			
		||||
	dBits := math.Float64bits(d)
 | 
			
		||||
	return math.Float64frombits(uint64(w)<<32 | dBits&0xffffffff)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func _word0(d float64) uint32 {
 | 
			
		||||
	dBits := math.Float64bits(d)
 | 
			
		||||
	return uint32(dBits >> 32)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func _word1(d float64) uint32 {
 | 
			
		||||
	dBits := math.Float64bits(d)
 | 
			
		||||
	return uint32(dBits)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func stripTrailingZeroes(buf []byte, startPos int) []byte {
 | 
			
		||||
	bl := len(buf) - 1
 | 
			
		||||
	for bl >= startPos && buf[bl] == '0' {
 | 
			
		||||
		bl--
 | 
			
		||||
	}
 | 
			
		||||
	return buf[:bl+1]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Set b = b * 5^k.  k must be nonnegative. */
 | 
			
		||||
func pow5mult(b *big.Int, k int) *big.Int {
 | 
			
		||||
	if k < (1 << (len(pow5Cache) + 2)) {
 | 
			
		||||
		i := k & 3
 | 
			
		||||
		if i != 0 {
 | 
			
		||||
			b.Mul(b, p05[i-1])
 | 
			
		||||
		}
 | 
			
		||||
		k >>= 2
 | 
			
		||||
		i = 0
 | 
			
		||||
		for {
 | 
			
		||||
			if k&1 != 0 {
 | 
			
		||||
				b.Mul(b, pow5Cache[i])
 | 
			
		||||
			}
 | 
			
		||||
			k >>= 1
 | 
			
		||||
			if k == 0 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
		return b
 | 
			
		||||
	}
 | 
			
		||||
	return b.Mul(b, new(big.Int).Exp(big5, big.NewInt(int64(k)), nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func roundOff(buf []byte, startPos int) ([]byte, bool) {
 | 
			
		||||
	i := len(buf)
 | 
			
		||||
	for i != startPos {
 | 
			
		||||
		i--
 | 
			
		||||
		if buf[i] != '9' {
 | 
			
		||||
			buf[i]++
 | 
			
		||||
			return buf[:i+1], false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return buf[:startPos], true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	p := big.NewInt(625)
 | 
			
		||||
	pow5Cache[0] = p
 | 
			
		||||
	for i := 1; i < len(pow5Cache); i++ {
 | 
			
		||||
		p = new(big.Int).Mul(p, p)
 | 
			
		||||
		pow5Cache[i] = p
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								goja/ftoa/ftobasestr.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								goja/ftoa/ftobasestr.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
			
		||||
package ftoa
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	digits = "0123456789abcdefghijklmnopqrstuvwxyz"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func FToBaseStr(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 strings.Builder
 | 
			
		||||
		buffer.WriteString(intDigits)
 | 
			
		||||
		buffer.WriteByte('.')
 | 
			
		||||
		df := num - dfloor
 | 
			
		||||
 | 
			
		||||
		dBits := math.Float64bits(num)
 | 
			
		||||
		word0 := uint32(dBits >> 32)
 | 
			
		||||
		word1 := uint32(dBits)
 | 
			
		||||
 | 
			
		||||
		dblBits := make([]byte, 0, 8)
 | 
			
		||||
		e, _, dblBits := d2b(df, dblBits)
 | 
			
		||||
		//            JS_ASSERT(e < 0);
 | 
			
		||||
		/* At this point df = b * 2^e.  e must be less than zero because 0 < df < 1. */
 | 
			
		||||
 | 
			
		||||
		s2 := -int((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 := new(big.Int).SetBytes(dblBits)
 | 
			
		||||
		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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								goja/ftoa/ftobasestr_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								goja/ftoa/ftobasestr_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
package ftoa
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestFToBaseStr(t *testing.T) {
 | 
			
		||||
	if s := FToBaseStr(0.8466400793967279, 36); s != "0.uh8u81s3fz" {
 | 
			
		||||
		t.Fatal(s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										147
									
								
								goja/ftoa/ftostr.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								goja/ftoa/ftostr.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
			
		||||
package ftoa
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/ftoa/internal/fast"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type FToStrMode int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// Either fixed or exponential format; round-trip
 | 
			
		||||
	ModeStandard FToStrMode = iota
 | 
			
		||||
	// Always exponential format; round-trip
 | 
			
		||||
	ModeStandardExponential
 | 
			
		||||
	// Round to <precision> digits after the decimal point; exponential if number is large
 | 
			
		||||
	ModeFixed
 | 
			
		||||
	// Always exponential format; <precision> significant digits
 | 
			
		||||
	ModeExponential
 | 
			
		||||
	// Either fixed or exponential format; <precision> significant digits
 | 
			
		||||
	ModePrecision
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func insert(b []byte, p int, c byte) []byte {
 | 
			
		||||
	b = append(b, 0)
 | 
			
		||||
	copy(b[p+1:], b[p:])
 | 
			
		||||
	b[p] = c
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func expand(b []byte, delta int) []byte {
 | 
			
		||||
	newLen := len(b) + delta
 | 
			
		||||
	if newLen <= cap(b) {
 | 
			
		||||
		return b[:newLen]
 | 
			
		||||
	}
 | 
			
		||||
	b1 := make([]byte, newLen)
 | 
			
		||||
	copy(b1, b)
 | 
			
		||||
	return b1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FToStr(d float64, mode FToStrMode, precision int, buffer []byte) []byte {
 | 
			
		||||
	if math.IsNaN(d) {
 | 
			
		||||
		buffer = append(buffer, "NaN"...)
 | 
			
		||||
		return buffer
 | 
			
		||||
	}
 | 
			
		||||
	if math.IsInf(d, 0) {
 | 
			
		||||
		if math.Signbit(d) {
 | 
			
		||||
			buffer = append(buffer, '-')
 | 
			
		||||
		}
 | 
			
		||||
		buffer = append(buffer, "Infinity"...)
 | 
			
		||||
		return buffer
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mode == ModeFixed && (d >= 1e21 || d <= -1e21) {
 | 
			
		||||
		mode = ModeStandard
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var decPt int
 | 
			
		||||
	var ok bool
 | 
			
		||||
	startPos := len(buffer)
 | 
			
		||||
 | 
			
		||||
	if d != 0 { // also matches -0
 | 
			
		||||
		if d < 0 {
 | 
			
		||||
			buffer = append(buffer, '-')
 | 
			
		||||
			d = -d
 | 
			
		||||
			startPos++
 | 
			
		||||
		}
 | 
			
		||||
		switch mode {
 | 
			
		||||
		case ModeStandard, ModeStandardExponential:
 | 
			
		||||
			buffer, decPt, ok = fast.Dtoa(d, fast.ModeShortest, 0, buffer)
 | 
			
		||||
		case ModeExponential, ModePrecision:
 | 
			
		||||
			buffer, decPt, ok = fast.Dtoa(d, fast.ModePrecision, precision, buffer)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		buffer = append(buffer, '0')
 | 
			
		||||
		decPt, ok = 1, true
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		buffer, decPt = ftoa(d, dtoaModes[mode], mode >= ModeFixed, precision, buffer)
 | 
			
		||||
	}
 | 
			
		||||
	exponentialNotation := false
 | 
			
		||||
	minNDigits := 0 /* Minimum number of significand digits required by mode and precision */
 | 
			
		||||
	nDigits := len(buffer) - startPos
 | 
			
		||||
 | 
			
		||||
	switch mode {
 | 
			
		||||
	case ModeStandard:
 | 
			
		||||
		if decPt < -5 || decPt > 21 {
 | 
			
		||||
			exponentialNotation = true
 | 
			
		||||
		} else {
 | 
			
		||||
			minNDigits = decPt
 | 
			
		||||
		}
 | 
			
		||||
	case ModeFixed:
 | 
			
		||||
		if precision >= 0 {
 | 
			
		||||
			minNDigits = decPt + precision
 | 
			
		||||
		} else {
 | 
			
		||||
			minNDigits = decPt
 | 
			
		||||
		}
 | 
			
		||||
	case ModeExponential:
 | 
			
		||||
		//                    JS_ASSERT(precision > 0);
 | 
			
		||||
		minNDigits = precision
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case ModeStandardExponential:
 | 
			
		||||
		exponentialNotation = true
 | 
			
		||||
	case ModePrecision:
 | 
			
		||||
		//                    JS_ASSERT(precision > 0);
 | 
			
		||||
		minNDigits = precision
 | 
			
		||||
		if decPt < -5 || decPt > precision {
 | 
			
		||||
			exponentialNotation = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for nDigits < minNDigits {
 | 
			
		||||
		buffer = append(buffer, '0')
 | 
			
		||||
		nDigits++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if exponentialNotation {
 | 
			
		||||
		/* Insert a decimal point if more than one significand digit */
 | 
			
		||||
		if nDigits != 1 {
 | 
			
		||||
			buffer = insert(buffer, startPos+1, '.')
 | 
			
		||||
		}
 | 
			
		||||
		buffer = append(buffer, 'e')
 | 
			
		||||
		if decPt-1 >= 0 {
 | 
			
		||||
			buffer = append(buffer, '+')
 | 
			
		||||
		}
 | 
			
		||||
		buffer = strconv.AppendInt(buffer, int64(decPt-1), 10)
 | 
			
		||||
	} else if decPt != nDigits {
 | 
			
		||||
		/* Some kind of a fraction in fixed notation */
 | 
			
		||||
		//                JS_ASSERT(decPt <= nDigits);
 | 
			
		||||
		if decPt > 0 {
 | 
			
		||||
			/* dd...dd . dd...dd */
 | 
			
		||||
			buffer = insert(buffer, startPos+decPt, '.')
 | 
			
		||||
		} else {
 | 
			
		||||
			/* 0 . 00...00dd...dd */
 | 
			
		||||
			buffer = expand(buffer, 2-decPt)
 | 
			
		||||
			copy(buffer[startPos+2-decPt:], buffer[startPos:])
 | 
			
		||||
			buffer[startPos] = '0'
 | 
			
		||||
			buffer[startPos+1] = '.'
 | 
			
		||||
			for i := startPos + 2; i < startPos+2-decPt; i++ {
 | 
			
		||||
				buffer[i] = '0'
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return buffer
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										92
									
								
								goja/ftoa/ftostr_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								goja/ftoa/ftostr_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
			
		||||
package ftoa
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func _testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) {
 | 
			
		||||
	buf := FToStr(num, mode, precision, nil)
 | 
			
		||||
	if s := string(buf); s != expected {
 | 
			
		||||
		t.Fatalf("expected: '%s', actual: '%s", expected, s)
 | 
			
		||||
	}
 | 
			
		||||
	if !math.IsNaN(num) && num != 0 && !math.Signbit(num) {
 | 
			
		||||
		_testFToStr(-num, mode, precision, "-"+expected, t)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) {
 | 
			
		||||
	t.Run("", func(t *testing.T) {
 | 
			
		||||
		t.Parallel()
 | 
			
		||||
		_testFToStr(num, mode, precision, expected, t)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDtostr(t *testing.T) {
 | 
			
		||||
	testFToStr(0, ModeStandard, 0, "0", t)
 | 
			
		||||
	testFToStr(1, ModeStandard, 0, "1", t)
 | 
			
		||||
	testFToStr(9007199254740991, ModeStandard, 0, "9007199254740991", t)
 | 
			
		||||
	testFToStr(math.MaxInt64, ModeStandardExponential, 0, "9.223372036854776e+18", t)
 | 
			
		||||
	testFToStr(1e-5, ModeFixed, 1, "0.0", t)
 | 
			
		||||
	testFToStr(8.85, ModeExponential, 2, "8.8e+0", t)
 | 
			
		||||
	testFToStr(885, ModeExponential, 2, "8.9e+2", t)
 | 
			
		||||
	testFToStr(25, ModeExponential, 1, "3e+1", t)
 | 
			
		||||
	testFToStr(1e-6, ModeFixed, 7, "0.0000010", t)
 | 
			
		||||
	testFToStr(math.Pi, ModeStandardExponential, 0, "3.141592653589793e+0", t)
 | 
			
		||||
	testFToStr(math.Inf(1), ModeStandard, 0, "Infinity", t)
 | 
			
		||||
	testFToStr(math.NaN(), ModeStandard, 0, "NaN", t)
 | 
			
		||||
	testFToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, "4.940656458412465441765687928682213723651e-324", t)
 | 
			
		||||
	testFToStr(3.5844466002796428e+298, ModeStandard, 0, "3.5844466002796428e+298", t)
 | 
			
		||||
	testFToStr(math.Float64frombits(0x0010000000000000), ModeStandard, 0, "2.2250738585072014e-308", t) // smallest normal
 | 
			
		||||
	testFToStr(math.Float64frombits(0x000FFFFFFFFFFFFF), ModeStandard, 0, "2.225073858507201e-308", t)  // largest denormal
 | 
			
		||||
	testFToStr(4294967272.0, ModePrecision, 14, "4294967272.0000", t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkDtostrSmall(b *testing.B) {
 | 
			
		||||
	var buf [128]byte
 | 
			
		||||
	b.ReportAllocs()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		FToStr(math.Pi, ModeStandardExponential, 0, buf[:0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkDtostrShort(b *testing.B) {
 | 
			
		||||
	var buf [128]byte
 | 
			
		||||
	b.ReportAllocs()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		FToStr(3.1415, ModeStandard, 0, buf[:0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkDtostrFixed(b *testing.B) {
 | 
			
		||||
	var buf [128]byte
 | 
			
		||||
	b.ReportAllocs()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		FToStr(math.Pi, ModeFixed, 4, buf[:0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkDtostrBig(b *testing.B) {
 | 
			
		||||
	var buf [128]byte
 | 
			
		||||
	b.ReportAllocs()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		FToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, buf[:0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkAppendFloatBig(b *testing.B) {
 | 
			
		||||
	var buf [128]byte
 | 
			
		||||
	b.ReportAllocs()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		strconv.AppendFloat(buf[:0], math.SmallestNonzeroFloat64, 'e', 40, 64)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkAppendFloatSmall(b *testing.B) {
 | 
			
		||||
	var buf [128]byte
 | 
			
		||||
	b.ReportAllocs()
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		strconv.AppendFloat(buf[:0], math.Pi, 'e', -1, 64)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								goja/ftoa/internal/fast/LICENSE_V8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								goja/ftoa/internal/fast/LICENSE_V8
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
Copyright 2014, the V8 project authors. All rights reserved.
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are
 | 
			
		||||
met:
 | 
			
		||||
 | 
			
		||||
    * Redistributions of source code must retain the above copyright
 | 
			
		||||
      notice, this list of conditions and the following disclaimer.
 | 
			
		||||
    * Redistributions in binary form must reproduce the above
 | 
			
		||||
      copyright notice, this list of conditions and the following
 | 
			
		||||
      disclaimer in the documentation and/or other materials provided
 | 
			
		||||
      with the distribution.
 | 
			
		||||
    * Neither the name of Google Inc. nor the names of its
 | 
			
		||||
      contributors may be used to endorse or promote products derived
 | 
			
		||||
      from this software without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
			
		||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
			
		||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
			
		||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
			
		||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										120
									
								
								goja/ftoa/internal/fast/cachedpower.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								goja/ftoa/internal/fast/cachedpower.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
			
		||||
package fast
 | 
			
		||||
 | 
			
		||||
import "math"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	kCachedPowersOffset      = 348                 // -1 * the first decimal_exponent.
 | 
			
		||||
	kD_1_LOG2_10             = 0.30102999566398114 //  1 / lg(10)
 | 
			
		||||
	kDecimalExponentDistance = 8
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type cachedPower struct {
 | 
			
		||||
	significand      uint64
 | 
			
		||||
	binary_exponent  int16
 | 
			
		||||
	decimal_exponent int16
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	cachedPowers = [...]cachedPower{
 | 
			
		||||
		{0xFA8FD5A0081C0288, -1220, -348},
 | 
			
		||||
		{0xBAAEE17FA23EBF76, -1193, -340},
 | 
			
		||||
		{0x8B16FB203055AC76, -1166, -332},
 | 
			
		||||
		{0xCF42894A5DCE35EA, -1140, -324},
 | 
			
		||||
		{0x9A6BB0AA55653B2D, -1113, -316},
 | 
			
		||||
		{0xE61ACF033D1A45DF, -1087, -308},
 | 
			
		||||
		{0xAB70FE17C79AC6CA, -1060, -300},
 | 
			
		||||
		{0xFF77B1FCBEBCDC4F, -1034, -292},
 | 
			
		||||
		{0xBE5691EF416BD60C, -1007, -284},
 | 
			
		||||
		{0x8DD01FAD907FFC3C, -980, -276},
 | 
			
		||||
		{0xD3515C2831559A83, -954, -268},
 | 
			
		||||
		{0x9D71AC8FADA6C9B5, -927, -260},
 | 
			
		||||
		{0xEA9C227723EE8BCB, -901, -252},
 | 
			
		||||
		{0xAECC49914078536D, -874, -244},
 | 
			
		||||
		{0x823C12795DB6CE57, -847, -236},
 | 
			
		||||
		{0xC21094364DFB5637, -821, -228},
 | 
			
		||||
		{0x9096EA6F3848984F, -794, -220},
 | 
			
		||||
		{0xD77485CB25823AC7, -768, -212},
 | 
			
		||||
		{0xA086CFCD97BF97F4, -741, -204},
 | 
			
		||||
		{0xEF340A98172AACE5, -715, -196},
 | 
			
		||||
		{0xB23867FB2A35B28E, -688, -188},
 | 
			
		||||
		{0x84C8D4DFD2C63F3B, -661, -180},
 | 
			
		||||
		{0xC5DD44271AD3CDBA, -635, -172},
 | 
			
		||||
		{0x936B9FCEBB25C996, -608, -164},
 | 
			
		||||
		{0xDBAC6C247D62A584, -582, -156},
 | 
			
		||||
		{0xA3AB66580D5FDAF6, -555, -148},
 | 
			
		||||
		{0xF3E2F893DEC3F126, -529, -140},
 | 
			
		||||
		{0xB5B5ADA8AAFF80B8, -502, -132},
 | 
			
		||||
		{0x87625F056C7C4A8B, -475, -124},
 | 
			
		||||
		{0xC9BCFF6034C13053, -449, -116},
 | 
			
		||||
		{0x964E858C91BA2655, -422, -108},
 | 
			
		||||
		{0xDFF9772470297EBD, -396, -100},
 | 
			
		||||
		{0xA6DFBD9FB8E5B88F, -369, -92},
 | 
			
		||||
		{0xF8A95FCF88747D94, -343, -84},
 | 
			
		||||
		{0xB94470938FA89BCF, -316, -76},
 | 
			
		||||
		{0x8A08F0F8BF0F156B, -289, -68},
 | 
			
		||||
		{0xCDB02555653131B6, -263, -60},
 | 
			
		||||
		{0x993FE2C6D07B7FAC, -236, -52},
 | 
			
		||||
		{0xE45C10C42A2B3B06, -210, -44},
 | 
			
		||||
		{0xAA242499697392D3, -183, -36},
 | 
			
		||||
		{0xFD87B5F28300CA0E, -157, -28},
 | 
			
		||||
		{0xBCE5086492111AEB, -130, -20},
 | 
			
		||||
		{0x8CBCCC096F5088CC, -103, -12},
 | 
			
		||||
		{0xD1B71758E219652C, -77, -4},
 | 
			
		||||
		{0x9C40000000000000, -50, 4},
 | 
			
		||||
		{0xE8D4A51000000000, -24, 12},
 | 
			
		||||
		{0xAD78EBC5AC620000, 3, 20},
 | 
			
		||||
		{0x813F3978F8940984, 30, 28},
 | 
			
		||||
		{0xC097CE7BC90715B3, 56, 36},
 | 
			
		||||
		{0x8F7E32CE7BEA5C70, 83, 44},
 | 
			
		||||
		{0xD5D238A4ABE98068, 109, 52},
 | 
			
		||||
		{0x9F4F2726179A2245, 136, 60},
 | 
			
		||||
		{0xED63A231D4C4FB27, 162, 68},
 | 
			
		||||
		{0xB0DE65388CC8ADA8, 189, 76},
 | 
			
		||||
		{0x83C7088E1AAB65DB, 216, 84},
 | 
			
		||||
		{0xC45D1DF942711D9A, 242, 92},
 | 
			
		||||
		{0x924D692CA61BE758, 269, 100},
 | 
			
		||||
		{0xDA01EE641A708DEA, 295, 108},
 | 
			
		||||
		{0xA26DA3999AEF774A, 322, 116},
 | 
			
		||||
		{0xF209787BB47D6B85, 348, 124},
 | 
			
		||||
		{0xB454E4A179DD1877, 375, 132},
 | 
			
		||||
		{0x865B86925B9BC5C2, 402, 140},
 | 
			
		||||
		{0xC83553C5C8965D3D, 428, 148},
 | 
			
		||||
		{0x952AB45CFA97A0B3, 455, 156},
 | 
			
		||||
		{0xDE469FBD99A05FE3, 481, 164},
 | 
			
		||||
		{0xA59BC234DB398C25, 508, 172},
 | 
			
		||||
		{0xF6C69A72A3989F5C, 534, 180},
 | 
			
		||||
		{0xB7DCBF5354E9BECE, 561, 188},
 | 
			
		||||
		{0x88FCF317F22241E2, 588, 196},
 | 
			
		||||
		{0xCC20CE9BD35C78A5, 614, 204},
 | 
			
		||||
		{0x98165AF37B2153DF, 641, 212},
 | 
			
		||||
		{0xE2A0B5DC971F303A, 667, 220},
 | 
			
		||||
		{0xA8D9D1535CE3B396, 694, 228},
 | 
			
		||||
		{0xFB9B7CD9A4A7443C, 720, 236},
 | 
			
		||||
		{0xBB764C4CA7A44410, 747, 244},
 | 
			
		||||
		{0x8BAB8EEFB6409C1A, 774, 252},
 | 
			
		||||
		{0xD01FEF10A657842C, 800, 260},
 | 
			
		||||
		{0x9B10A4E5E9913129, 827, 268},
 | 
			
		||||
		{0xE7109BFBA19C0C9D, 853, 276},
 | 
			
		||||
		{0xAC2820D9623BF429, 880, 284},
 | 
			
		||||
		{0x80444B5E7AA7CF85, 907, 292},
 | 
			
		||||
		{0xBF21E44003ACDD2D, 933, 300},
 | 
			
		||||
		{0x8E679C2F5E44FF8F, 960, 308},
 | 
			
		||||
		{0xD433179D9C8CB841, 986, 316},
 | 
			
		||||
		{0x9E19DB92B4E31BA9, 1013, 324},
 | 
			
		||||
		{0xEB96BF6EBADF77D9, 1039, 332},
 | 
			
		||||
		{0xAF87023B9BF0EE6B, 1066, 340},
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getCachedPowerForBinaryExponentRange(min_exponent, max_exponent int) (power diyfp, decimal_exponent int) {
 | 
			
		||||
	kQ := diyFpKSignificandSize
 | 
			
		||||
	k := int(math.Ceil(float64(min_exponent+kQ-1) * kD_1_LOG2_10))
 | 
			
		||||
	index := (kCachedPowersOffset+k-1)/kDecimalExponentDistance + 1
 | 
			
		||||
	cached_power := cachedPowers[index]
 | 
			
		||||
	_DCHECK(min_exponent <= int(cached_power.binary_exponent))
 | 
			
		||||
	_DCHECK(int(cached_power.binary_exponent) <= max_exponent)
 | 
			
		||||
	decimal_exponent = int(cached_power.decimal_exponent)
 | 
			
		||||
	power = diyfp{f: cached_power.significand, e: int(cached_power.binary_exponent)}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								goja/ftoa/internal/fast/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								goja/ftoa/internal/fast/common.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
/*
 | 
			
		||||
Package fast contains code ported from V8 (https://github.com/v8/v8/blob/master/src/numbers/fast-dtoa.cc)
 | 
			
		||||
 | 
			
		||||
See LICENSE_V8 for the original copyright message and disclaimer.
 | 
			
		||||
*/
 | 
			
		||||
package fast
 | 
			
		||||
 | 
			
		||||
import "errors"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	dcheckFailure = errors.New("DCHECK assertion failed")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func _DCHECK(f bool) {
 | 
			
		||||
	if !f {
 | 
			
		||||
		panic(dcheckFailure)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										152
									
								
								goja/ftoa/internal/fast/diyfp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								goja/ftoa/internal/fast/diyfp.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,152 @@
 | 
			
		||||
package fast
 | 
			
		||||
 | 
			
		||||
import "math"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	diyFpKSignificandSize        = 64
 | 
			
		||||
	kSignificandSize             = 53
 | 
			
		||||
	kUint64MSB            uint64 = 1 << 63
 | 
			
		||||
 | 
			
		||||
	kSignificandMask = 0x000FFFFFFFFFFFFF
 | 
			
		||||
	kHiddenBit       = 0x0010000000000000
 | 
			
		||||
	kExponentMask    = 0x7FF0000000000000
 | 
			
		||||
 | 
			
		||||
	kPhysicalSignificandSize = 52 // Excludes the hidden bit.
 | 
			
		||||
	kExponentBias            = 0x3FF + kPhysicalSignificandSize
 | 
			
		||||
	kDenormalExponent        = -kExponentBias + 1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type double float64
 | 
			
		||||
 | 
			
		||||
type diyfp struct {
 | 
			
		||||
	f uint64
 | 
			
		||||
	e int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// f =- o.
 | 
			
		||||
// The exponents of both numbers must be the same and the significand of this
 | 
			
		||||
// must be bigger than the significand of other.
 | 
			
		||||
// The result will not be normalized.
 | 
			
		||||
func (f *diyfp) subtract(o diyfp) {
 | 
			
		||||
	_DCHECK(f.e == o.e)
 | 
			
		||||
	_DCHECK(f.f >= o.f)
 | 
			
		||||
	f.f -= o.f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns f - o
 | 
			
		||||
// The exponents of both numbers must be the same and this must be bigger
 | 
			
		||||
// than other. The result will not be normalized.
 | 
			
		||||
func (f diyfp) minus(o diyfp) diyfp {
 | 
			
		||||
	res := f
 | 
			
		||||
	res.subtract(o)
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// f *= o
 | 
			
		||||
func (f *diyfp) mul(o diyfp) {
 | 
			
		||||
	// Simply "emulates" a 128 bit multiplication.
 | 
			
		||||
	// However: the resulting number only contains 64 bits. The least
 | 
			
		||||
	// significant 64 bits are only used for rounding the most significant 64
 | 
			
		||||
	// bits.
 | 
			
		||||
	const kM32 uint64 = 0xFFFFFFFF
 | 
			
		||||
	a := f.f >> 32
 | 
			
		||||
	b := f.f & kM32
 | 
			
		||||
	c := o.f >> 32
 | 
			
		||||
	d := o.f & kM32
 | 
			
		||||
	ac := a * c
 | 
			
		||||
	bc := b * c
 | 
			
		||||
	ad := a * d
 | 
			
		||||
	bd := b * d
 | 
			
		||||
	tmp := (bd >> 32) + (ad & kM32) + (bc & kM32)
 | 
			
		||||
	// By adding 1U << 31 to tmp we round the final result.
 | 
			
		||||
	// Halfway cases will be round up.
 | 
			
		||||
	tmp += 1 << 31
 | 
			
		||||
	result_f := ac + (ad >> 32) + (bc >> 32) + (tmp >> 32)
 | 
			
		||||
	f.e += o.e + 64
 | 
			
		||||
	f.f = result_f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns f * o
 | 
			
		||||
func (f diyfp) times(o diyfp) diyfp {
 | 
			
		||||
	res := f
 | 
			
		||||
	res.mul(o)
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *diyfp) _normalize() {
 | 
			
		||||
	f_, e := f.f, f.e
 | 
			
		||||
	// This method is mainly called for normalizing boundaries. In general
 | 
			
		||||
	// boundaries need to be shifted by 10 bits. We thus optimize for this case.
 | 
			
		||||
	const k10MSBits uint64 = 0x3FF << 54
 | 
			
		||||
	for f_&k10MSBits == 0 {
 | 
			
		||||
		f_ <<= 10
 | 
			
		||||
		e -= 10
 | 
			
		||||
	}
 | 
			
		||||
	for f_&kUint64MSB == 0 {
 | 
			
		||||
		f_ <<= 1
 | 
			
		||||
		e--
 | 
			
		||||
	}
 | 
			
		||||
	f.f, f.e = f_, e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func normalizeDiyfp(f diyfp) diyfp {
 | 
			
		||||
	res := f
 | 
			
		||||
	res._normalize()
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// f must be strictly greater than 0.
 | 
			
		||||
func (d double) toNormalizedDiyfp() diyfp {
 | 
			
		||||
	f, e := d.sigExp()
 | 
			
		||||
 | 
			
		||||
	// The current float could be a denormal.
 | 
			
		||||
	for (f & kHiddenBit) == 0 {
 | 
			
		||||
		f <<= 1
 | 
			
		||||
		e--
 | 
			
		||||
	}
 | 
			
		||||
	// Do the final shifts in one go.
 | 
			
		||||
	f <<= diyFpKSignificandSize - kSignificandSize
 | 
			
		||||
	e -= diyFpKSignificandSize - kSignificandSize
 | 
			
		||||
	return diyfp{f, e}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the two boundaries of this.
 | 
			
		||||
// The bigger boundary (m_plus) is normalized. The lower boundary has the same
 | 
			
		||||
// exponent as m_plus.
 | 
			
		||||
// Precondition: the value encoded by this Double must be greater than 0.
 | 
			
		||||
func (d double) normalizedBoundaries() (m_minus, m_plus diyfp) {
 | 
			
		||||
	v := d.toDiyFp()
 | 
			
		||||
	significand_is_zero := v.f == kHiddenBit
 | 
			
		||||
	m_plus = normalizeDiyfp(diyfp{f: (v.f << 1) + 1, e: v.e - 1})
 | 
			
		||||
	if significand_is_zero && v.e != kDenormalExponent {
 | 
			
		||||
		// The boundary is closer. Think of v = 1000e10 and v- = 9999e9.
 | 
			
		||||
		// Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
 | 
			
		||||
		// at a distance of 1e8.
 | 
			
		||||
		// The only exception is for the smallest normal: the largest denormal is
 | 
			
		||||
		// at the same distance as its successor.
 | 
			
		||||
		// Note: denormals have the same exponent as the smallest normals.
 | 
			
		||||
		m_minus = diyfp{f: (v.f << 2) - 1, e: v.e - 2}
 | 
			
		||||
	} else {
 | 
			
		||||
		m_minus = diyfp{f: (v.f << 1) - 1, e: v.e - 1}
 | 
			
		||||
	}
 | 
			
		||||
	m_minus.f <<= m_minus.e - m_plus.e
 | 
			
		||||
	m_minus.e = m_plus.e
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d double) toDiyFp() diyfp {
 | 
			
		||||
	f, e := d.sigExp()
 | 
			
		||||
	return diyfp{f: f, e: e}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d double) sigExp() (significand uint64, exponent int) {
 | 
			
		||||
	d64 := math.Float64bits(float64(d))
 | 
			
		||||
	significand = d64 & kSignificandMask
 | 
			
		||||
	if d64&kExponentMask != 0 { // not denormal
 | 
			
		||||
		significand += kHiddenBit
 | 
			
		||||
		exponent = int((d64&kExponentMask)>>kPhysicalSignificandSize) - kExponentBias
 | 
			
		||||
	} else {
 | 
			
		||||
		exponent = kDenormalExponent
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										642
									
								
								goja/ftoa/internal/fast/dtoa.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										642
									
								
								goja/ftoa/internal/fast/dtoa.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,642 @@
 | 
			
		||||
package fast
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	kMinimalTargetExponent = -60
 | 
			
		||||
	kMaximalTargetExponent = -32
 | 
			
		||||
 | 
			
		||||
	kTen4 = 10000
 | 
			
		||||
	kTen5 = 100000
 | 
			
		||||
	kTen6 = 1000000
 | 
			
		||||
	kTen7 = 10000000
 | 
			
		||||
	kTen8 = 100000000
 | 
			
		||||
	kTen9 = 1000000000
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Mode int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ModeShortest Mode = iota
 | 
			
		||||
	ModePrecision
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Adjusts the last digit of the generated number, and screens out generated
 | 
			
		||||
// solutions that may be inaccurate. A solution may be inaccurate if it is
 | 
			
		||||
// outside the safe interval, or if we cannot prove that it is closer to the
 | 
			
		||||
// input than a neighboring representation of the same length.
 | 
			
		||||
//
 | 
			
		||||
// Input: * buffer containing the digits of too_high / 10^kappa
 | 
			
		||||
//   - distance_too_high_w == (too_high - w).f() * unit
 | 
			
		||||
//   - unsafe_interval == (too_high - too_low).f() * unit
 | 
			
		||||
//   - rest = (too_high - buffer * 10^kappa).f() * unit
 | 
			
		||||
//   - ten_kappa = 10^kappa * unit
 | 
			
		||||
//   - unit = the common multiplier
 | 
			
		||||
//
 | 
			
		||||
// Output: returns true if the buffer is guaranteed to contain the closest
 | 
			
		||||
//
 | 
			
		||||
//	  representable number to the input.
 | 
			
		||||
//	Modifies the generated digits in the buffer to approach (round towards) w.
 | 
			
		||||
func roundWeed(buffer []byte, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit uint64) bool {
 | 
			
		||||
	small_distance := distance_too_high_w - unit
 | 
			
		||||
	big_distance := distance_too_high_w + unit
 | 
			
		||||
 | 
			
		||||
	// Let w_low  = too_high - big_distance, and
 | 
			
		||||
	//     w_high = too_high - small_distance.
 | 
			
		||||
	// Note: w_low < w < w_high
 | 
			
		||||
	//
 | 
			
		||||
	// The real w (* unit) must lie somewhere inside the interval
 | 
			
		||||
	// ]w_low; w_high[ (often written as "(w_low; w_high)")
 | 
			
		||||
 | 
			
		||||
	// Basically the buffer currently contains a number in the unsafe interval
 | 
			
		||||
	// ]too_low; too_high[ with too_low < w < too_high
 | 
			
		||||
	//
 | 
			
		||||
	//  too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
	//                     ^v 1 unit            ^      ^                 ^      ^
 | 
			
		||||
	//  boundary_high ---------------------     .      .                 .      .
 | 
			
		||||
	//                     ^v 1 unit            .      .                 .      .
 | 
			
		||||
	//   - - - - - - - - - - - - - - - - - - -  +  - - + - - - - - -     .      .
 | 
			
		||||
	//                                          .      .         ^       .      .
 | 
			
		||||
	//                                          .  big_distance  .       .      .
 | 
			
		||||
	//                                          .      .         .       .    rest
 | 
			
		||||
	//                              small_distance     .         .       .      .
 | 
			
		||||
	//                                          v      .         .       .      .
 | 
			
		||||
	//  w_high - - - - - - - - - - - - - - - - - -     .         .       .      .
 | 
			
		||||
	//                     ^v 1 unit                   .         .       .      .
 | 
			
		||||
	//  w ----------------------------------------     .         .       .      .
 | 
			
		||||
	//                     ^v 1 unit                   v         .       .      .
 | 
			
		||||
	//  w_low  - - - - - - - - - - - - - - - - - - - - -         .       .      .
 | 
			
		||||
	//                                                           .       .      v
 | 
			
		||||
	//  buffer --------------------------------------------------+-------+--------
 | 
			
		||||
	//                                                           .       .
 | 
			
		||||
	//                                                  safe_interval    .
 | 
			
		||||
	//                                                           v       .
 | 
			
		||||
	//   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -     .
 | 
			
		||||
	//                     ^v 1 unit                                     .
 | 
			
		||||
	//  boundary_low -------------------------                     unsafe_interval
 | 
			
		||||
	//                     ^v 1 unit                                     v
 | 
			
		||||
	//  too_low  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
	//
 | 
			
		||||
	//
 | 
			
		||||
	// Note that the value of buffer could lie anywhere inside the range too_low
 | 
			
		||||
	// to too_high.
 | 
			
		||||
	//
 | 
			
		||||
	// boundary_low, boundary_high and w are approximations of the real boundaries
 | 
			
		||||
	// and v (the input number). They are guaranteed to be precise up to one unit.
 | 
			
		||||
	// In fact the error is guaranteed to be strictly less than one unit.
 | 
			
		||||
	//
 | 
			
		||||
	// Anything that lies outside the unsafe interval is guaranteed not to round
 | 
			
		||||
	// to v when read again.
 | 
			
		||||
	// Anything that lies inside the safe interval is guaranteed to round to v
 | 
			
		||||
	// when read again.
 | 
			
		||||
	// If the number inside the buffer lies inside the unsafe interval but not
 | 
			
		||||
	// inside the safe interval then we simply do not know and bail out (returning
 | 
			
		||||
	// false).
 | 
			
		||||
	//
 | 
			
		||||
	// Similarly we have to take into account the imprecision of 'w' when finding
 | 
			
		||||
	// the closest representation of 'w'. If we have two potential
 | 
			
		||||
	// representations, and one is closer to both w_low and w_high, then we know
 | 
			
		||||
	// it is closer to the actual value v.
 | 
			
		||||
	//
 | 
			
		||||
	// By generating the digits of too_high we got the largest (closest to
 | 
			
		||||
	// too_high) buffer that is still in the unsafe interval. In the case where
 | 
			
		||||
	// w_high < buffer < too_high we try to decrement the buffer.
 | 
			
		||||
	// This way the buffer approaches (rounds towards) w.
 | 
			
		||||
	// There are 3 conditions that stop the decrementation process:
 | 
			
		||||
	//   1) the buffer is already below w_high
 | 
			
		||||
	//   2) decrementing the buffer would make it leave the unsafe interval
 | 
			
		||||
	//   3) decrementing the buffer would yield a number below w_high and farther
 | 
			
		||||
	//      away than the current number. In other words:
 | 
			
		||||
	//              (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high
 | 
			
		||||
	// Instead of using the buffer directly we use its distance to too_high.
 | 
			
		||||
	// Conceptually rest ~= too_high - buffer
 | 
			
		||||
	// We need to do the following tests in this order to avoid over- and
 | 
			
		||||
	// underflows.
 | 
			
		||||
	_DCHECK(rest <= unsafe_interval)
 | 
			
		||||
	for rest < small_distance && // Negated condition 1
 | 
			
		||||
		unsafe_interval-rest >= ten_kappa && // Negated condition 2
 | 
			
		||||
		(rest+ten_kappa < small_distance || // buffer{-1} > w_high
 | 
			
		||||
			small_distance-rest >= rest+ten_kappa-small_distance) {
 | 
			
		||||
		buffer[len(buffer)-1]--
 | 
			
		||||
		rest += ten_kappa
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We have approached w+ as much as possible. We now test if approaching w-
 | 
			
		||||
	// would require changing the buffer. If yes, then we have two possible
 | 
			
		||||
	// representations close to w, but we cannot decide which one is closer.
 | 
			
		||||
	if rest < big_distance && unsafe_interval-rest >= ten_kappa &&
 | 
			
		||||
		(rest+ten_kappa < big_distance ||
 | 
			
		||||
			big_distance-rest > rest+ten_kappa-big_distance) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Weeding test.
 | 
			
		||||
	//   The safe interval is [too_low + 2 ulp; too_high - 2 ulp]
 | 
			
		||||
	//   Since too_low = too_high - unsafe_interval this is equivalent to
 | 
			
		||||
	//      [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp]
 | 
			
		||||
	//   Conceptually we have: rest ~= too_high - buffer
 | 
			
		||||
	return (2*unit <= rest) && (rest <= unsafe_interval-4*unit)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Rounds the buffer upwards if the result is closer to v by possibly adding
 | 
			
		||||
// 1 to the buffer. If the precision of the calculation is not sufficient to
 | 
			
		||||
// round correctly, return false.
 | 
			
		||||
// The rounding might shift the whole buffer in which case the kappa is
 | 
			
		||||
// adjusted. For example "99", kappa = 3 might become "10", kappa = 4.
 | 
			
		||||
//
 | 
			
		||||
// If 2*rest > ten_kappa then the buffer needs to be round up.
 | 
			
		||||
// rest can have an error of +/- 1 unit. This function accounts for the
 | 
			
		||||
// imprecision and returns false, if the rounding direction cannot be
 | 
			
		||||
// unambiguously determined.
 | 
			
		||||
//
 | 
			
		||||
// Precondition: rest < ten_kappa.
 | 
			
		||||
func roundWeedCounted(buffer []byte, rest, ten_kappa, unit uint64, kappa *int) bool {
 | 
			
		||||
	_DCHECK(rest < ten_kappa)
 | 
			
		||||
	// The following tests are done in a specific order to avoid overflows. They
 | 
			
		||||
	// will work correctly with any uint64 values of rest < ten_kappa and unit.
 | 
			
		||||
	//
 | 
			
		||||
	// If the unit is too big, then we don't know which way to round. For example
 | 
			
		||||
	// a unit of 50 means that the real number lies within rest +/- 50. If
 | 
			
		||||
	// 10^kappa == 40 then there is no way to tell which way to round.
 | 
			
		||||
	if unit >= ten_kappa {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	// Even if unit is just half the size of 10^kappa we are already completely
 | 
			
		||||
	// lost. (And after the previous test we know that the expression will not
 | 
			
		||||
	// over/underflow.)
 | 
			
		||||
	if ten_kappa-unit <= unit {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	// If 2 * (rest + unit) <= 10^kappa we can safely round down.
 | 
			
		||||
	if (ten_kappa-rest > rest) && (ten_kappa-2*rest >= 2*unit) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If 2 * (rest - unit) >= 10^kappa, then we can safely round up.
 | 
			
		||||
	if (rest > unit) && (ten_kappa-(rest-unit) <= (rest - unit)) {
 | 
			
		||||
		// Increment the last digit recursively until we find a non '9' digit.
 | 
			
		||||
		buffer[len(buffer)-1]++
 | 
			
		||||
		for i := len(buffer) - 1; i > 0; i-- {
 | 
			
		||||
			if buffer[i] != '0'+10 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			buffer[i] = '0'
 | 
			
		||||
			buffer[i-1]++
 | 
			
		||||
		}
 | 
			
		||||
		// If the first digit is now '0'+ 10 we had a buffer with all '9's. With the
 | 
			
		||||
		// exception of the first digit all digits are now '0'. Simply switch the
 | 
			
		||||
		// first digit to '1' and adjust the kappa. Example: "99" becomes "10" and
 | 
			
		||||
		// the power (the kappa) is increased.
 | 
			
		||||
		if buffer[0] == '0'+10 {
 | 
			
		||||
			buffer[0] = '1'
 | 
			
		||||
			*kappa += 1
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the biggest power of ten that is less than or equal than the given
 | 
			
		||||
// number. We furthermore receive the maximum number of bits 'number' has.
 | 
			
		||||
// If number_bits == 0 then 0^-1 is returned
 | 
			
		||||
// The number of bits must be <= 32.
 | 
			
		||||
// Precondition: number < (1 << (number_bits + 1)).
 | 
			
		||||
func biggestPowerTen(number uint32, number_bits int) (power uint32, exponent int) {
 | 
			
		||||
	switch number_bits {
 | 
			
		||||
	case 32, 31, 30:
 | 
			
		||||
		if kTen9 <= number {
 | 
			
		||||
			power = kTen9
 | 
			
		||||
			exponent = 9
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 29, 28, 27:
 | 
			
		||||
		if kTen8 <= number {
 | 
			
		||||
			power = kTen8
 | 
			
		||||
			exponent = 8
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 26, 25, 24:
 | 
			
		||||
		if kTen7 <= number {
 | 
			
		||||
			power = kTen7
 | 
			
		||||
			exponent = 7
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 23, 22, 21, 20:
 | 
			
		||||
		if kTen6 <= number {
 | 
			
		||||
			power = kTen6
 | 
			
		||||
			exponent = 6
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 19, 18, 17:
 | 
			
		||||
		if kTen5 <= number {
 | 
			
		||||
			power = kTen5
 | 
			
		||||
			exponent = 5
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 16, 15, 14:
 | 
			
		||||
		if kTen4 <= number {
 | 
			
		||||
			power = kTen4
 | 
			
		||||
			exponent = 4
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 13, 12, 11, 10:
 | 
			
		||||
		if 1000 <= number {
 | 
			
		||||
			power = 1000
 | 
			
		||||
			exponent = 3
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 9, 8, 7:
 | 
			
		||||
		if 100 <= number {
 | 
			
		||||
			power = 100
 | 
			
		||||
			exponent = 2
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 6, 5, 4:
 | 
			
		||||
		if 10 <= number {
 | 
			
		||||
			power = 10
 | 
			
		||||
			exponent = 1
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 3, 2, 1:
 | 
			
		||||
		if 1 <= number {
 | 
			
		||||
			power = 1
 | 
			
		||||
			exponent = 0
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case 0:
 | 
			
		||||
		power = 0
 | 
			
		||||
		exponent = -1
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generates the digits of input number w.
 | 
			
		||||
// w is a floating-point number (DiyFp), consisting of a significand and an
 | 
			
		||||
// exponent. Its exponent is bounded by kMinimalTargetExponent and
 | 
			
		||||
// kMaximalTargetExponent.
 | 
			
		||||
//
 | 
			
		||||
//	Hence -60 <= w.e() <= -32.
 | 
			
		||||
//
 | 
			
		||||
// Returns false if it fails, in which case the generated digits in the buffer
 | 
			
		||||
// should not be used.
 | 
			
		||||
// Preconditions:
 | 
			
		||||
//   - low, w and high are correct up to 1 ulp (unit in the last place). That
 | 
			
		||||
//     is, their error must be less than a unit of their last digits.
 | 
			
		||||
//   - low.e() == w.e() == high.e()
 | 
			
		||||
//   - low < w < high, and taking into account their error: low~ <= high~
 | 
			
		||||
//   - kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
 | 
			
		||||
//
 | 
			
		||||
// Postconditions: returns false if procedure fails.
 | 
			
		||||
//
 | 
			
		||||
//	otherwise:
 | 
			
		||||
//	  * buffer is not null-terminated, but len contains the number of digits.
 | 
			
		||||
//	  * buffer contains the shortest possible decimal digit-sequence
 | 
			
		||||
//	    such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
 | 
			
		||||
//	    correct values of low and high (without their error).
 | 
			
		||||
//	  * if more than one decimal representation gives the minimal number of
 | 
			
		||||
//	    decimal digits then the one closest to W (where W is the correct value
 | 
			
		||||
//	    of w) is chosen.
 | 
			
		||||
//
 | 
			
		||||
// Remark: this procedure takes into account the imprecision of its input
 | 
			
		||||
//
 | 
			
		||||
//	numbers. If the precision is not enough to guarantee all the postconditions
 | 
			
		||||
//	then false is returned. This usually happens rarely (~0.5%).
 | 
			
		||||
//
 | 
			
		||||
// Say, for the sake of example, that
 | 
			
		||||
//
 | 
			
		||||
//	w.e() == -48, and w.f() == 0x1234567890ABCDEF
 | 
			
		||||
//
 | 
			
		||||
// w's value can be computed by w.f() * 2^w.e()
 | 
			
		||||
// We can obtain w's integral digits by simply shifting w.f() by -w.e().
 | 
			
		||||
//
 | 
			
		||||
//	-> w's integral part is 0x1234
 | 
			
		||||
//	w's fractional part is therefore 0x567890ABCDEF.
 | 
			
		||||
//
 | 
			
		||||
// Printing w's integral part is easy (simply print 0x1234 in decimal).
 | 
			
		||||
// In order to print its fraction we repeatedly multiply the fraction by 10 and
 | 
			
		||||
// get each digit. Example the first digit after the point would be computed by
 | 
			
		||||
//
 | 
			
		||||
//	(0x567890ABCDEF * 10) >> 48. -> 3
 | 
			
		||||
//
 | 
			
		||||
// The whole thing becomes slightly more complicated because we want to stop
 | 
			
		||||
// once we have enough digits. That is, once the digits inside the buffer
 | 
			
		||||
// represent 'w' we can stop. Everything inside the interval low - high
 | 
			
		||||
// represents w. However we have to pay attention to low, high and w's
 | 
			
		||||
// imprecision.
 | 
			
		||||
func digitGen(low, w, high diyfp, buffer []byte) (kappa int, buf []byte, res bool) {
 | 
			
		||||
	_DCHECK(low.e == w.e && w.e == high.e)
 | 
			
		||||
	_DCHECK(low.f+1 <= high.f-1)
 | 
			
		||||
	_DCHECK(kMinimalTargetExponent <= w.e && w.e <= kMaximalTargetExponent)
 | 
			
		||||
	// low, w and high are imprecise, but by less than one ulp (unit in the last
 | 
			
		||||
	// place).
 | 
			
		||||
	// If we remove (resp. add) 1 ulp from low (resp. high) we are certain that
 | 
			
		||||
	// the new numbers are outside of the interval we want the final
 | 
			
		||||
	// representation to lie in.
 | 
			
		||||
	// Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield
 | 
			
		||||
	// numbers that are certain to lie in the interval. We will use this fact
 | 
			
		||||
	// later on.
 | 
			
		||||
	// We will now start by generating the digits within the uncertain
 | 
			
		||||
	// interval. Later we will weed out representations that lie outside the safe
 | 
			
		||||
	// interval and thus _might_ lie outside the correct interval.
 | 
			
		||||
	unit := uint64(1)
 | 
			
		||||
	too_low := diyfp{f: low.f - unit, e: low.e}
 | 
			
		||||
	too_high := diyfp{f: high.f + unit, e: high.e}
 | 
			
		||||
	// too_low and too_high are guaranteed to lie outside the interval we want the
 | 
			
		||||
	// generated number in.
 | 
			
		||||
	unsafe_interval := too_high.minus(too_low)
 | 
			
		||||
	// We now cut the input number into two parts: the integral digits and the
 | 
			
		||||
	// fractionals. We will not write any decimal separator though, but adapt
 | 
			
		||||
	// kappa instead.
 | 
			
		||||
	// Reminder: we are currently computing the digits (stored inside the buffer)
 | 
			
		||||
	// such that:   too_low < buffer * 10^kappa < too_high
 | 
			
		||||
	// We use too_high for the digit_generation and stop as soon as possible.
 | 
			
		||||
	// If we stop early we effectively round down.
 | 
			
		||||
	one := diyfp{f: 1 << -w.e, e: w.e}
 | 
			
		||||
	// Division by one is a shift.
 | 
			
		||||
	integrals := uint32(too_high.f >> -one.e)
 | 
			
		||||
	// Modulo by one is an and.
 | 
			
		||||
	fractionals := too_high.f & (one.f - 1)
 | 
			
		||||
	divisor, divisor_exponent := biggestPowerTen(integrals, diyFpKSignificandSize-(-one.e))
 | 
			
		||||
	kappa = divisor_exponent + 1
 | 
			
		||||
	buf = buffer
 | 
			
		||||
	for kappa > 0 {
 | 
			
		||||
		digit := int(integrals / divisor)
 | 
			
		||||
		buf = append(buf, byte('0'+digit))
 | 
			
		||||
		integrals %= divisor
 | 
			
		||||
		kappa--
 | 
			
		||||
		// Note that kappa now equals the exponent of the divisor and that the
 | 
			
		||||
		// invariant thus holds again.
 | 
			
		||||
		rest := uint64(integrals)<<-one.e + fractionals
 | 
			
		||||
		// Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e)
 | 
			
		||||
		// Reminder: unsafe_interval.e == one.e
 | 
			
		||||
		if rest < unsafe_interval.f {
 | 
			
		||||
			// Rounding down (by not emitting the remaining digits) yields a number
 | 
			
		||||
			// that lies within the unsafe interval.
 | 
			
		||||
			res = roundWeed(buf, too_high.minus(w).f,
 | 
			
		||||
				unsafe_interval.f, rest,
 | 
			
		||||
				uint64(divisor)<<-one.e, unit)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		divisor /= 10
 | 
			
		||||
	}
 | 
			
		||||
	// The integrals have been generated. We are at the point of the decimal
 | 
			
		||||
	// separator. In the following loop we simply multiply the remaining digits by
 | 
			
		||||
	// 10 and divide by one. We just need to pay attention to multiply associated
 | 
			
		||||
	// data (like the interval or 'unit'), too.
 | 
			
		||||
	// Note that the multiplication by 10 does not overflow, because w.e >= -60
 | 
			
		||||
	// and thus one.e >= -60.
 | 
			
		||||
	_DCHECK(one.e >= -60)
 | 
			
		||||
	_DCHECK(fractionals < one.f)
 | 
			
		||||
	_DCHECK(0xFFFFFFFFFFFFFFFF/10 >= one.f)
 | 
			
		||||
	for {
 | 
			
		||||
		fractionals *= 10
 | 
			
		||||
		unit *= 10
 | 
			
		||||
		unsafe_interval.f *= 10
 | 
			
		||||
		// Integer division by one.
 | 
			
		||||
		digit := byte(fractionals >> -one.e)
 | 
			
		||||
		buf = append(buf, '0'+digit)
 | 
			
		||||
		fractionals &= one.f - 1 // Modulo by one.
 | 
			
		||||
		kappa--
 | 
			
		||||
		if fractionals < unsafe_interval.f {
 | 
			
		||||
			res = roundWeed(buf, too_high.minus(w).f*unit, unsafe_interval.f, fractionals, one.f, unit)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generates (at most) requested_digits of input number w.
 | 
			
		||||
// w is a floating-point number (DiyFp), consisting of a significand and an
 | 
			
		||||
// exponent. Its exponent is bounded by kMinimalTargetExponent and
 | 
			
		||||
// kMaximalTargetExponent.
 | 
			
		||||
//
 | 
			
		||||
//	Hence -60 <= w.e() <= -32.
 | 
			
		||||
//
 | 
			
		||||
// Returns false if it fails, in which case the generated digits in the buffer
 | 
			
		||||
// should not be used.
 | 
			
		||||
// Preconditions:
 | 
			
		||||
//   - w is correct up to 1 ulp (unit in the last place). That
 | 
			
		||||
//     is, its error must be strictly less than a unit of its last digit.
 | 
			
		||||
//   - kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
 | 
			
		||||
//
 | 
			
		||||
// Postconditions: returns false if procedure fails.
 | 
			
		||||
//
 | 
			
		||||
//	otherwise:
 | 
			
		||||
//	  * buffer is not null-terminated, but length contains the number of
 | 
			
		||||
//	    digits.
 | 
			
		||||
//	  * the representation in buffer is the most precise representation of
 | 
			
		||||
//	    requested_digits digits.
 | 
			
		||||
//	  * buffer contains at most requested_digits digits of w. If there are less
 | 
			
		||||
//	    than requested_digits digits then some trailing '0's have been removed.
 | 
			
		||||
//	  * kappa is such that
 | 
			
		||||
//	         w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2.
 | 
			
		||||
//
 | 
			
		||||
// Remark: This procedure takes into account the imprecision of its input
 | 
			
		||||
//
 | 
			
		||||
//	numbers. If the precision is not enough to guarantee all the postconditions
 | 
			
		||||
//	then false is returned. This usually happens rarely, but the failure-rate
 | 
			
		||||
//	increases with higher requested_digits.
 | 
			
		||||
func digitGenCounted(w diyfp, requested_digits int, buffer []byte) (kappa int, buf []byte, res bool) {
 | 
			
		||||
	_DCHECK(kMinimalTargetExponent <= w.e && w.e <= kMaximalTargetExponent)
 | 
			
		||||
 | 
			
		||||
	// w is assumed to have an error less than 1 unit. Whenever w is scaled we
 | 
			
		||||
	// also scale its error.
 | 
			
		||||
	w_error := uint64(1)
 | 
			
		||||
	// We cut the input number into two parts: the integral digits and the
 | 
			
		||||
	// fractional digits. We don't emit any decimal separator, but adapt kappa
 | 
			
		||||
	// instead. Example: instead of writing "1.2" we put "12" into the buffer and
 | 
			
		||||
	// increase kappa by 1.
 | 
			
		||||
	one := diyfp{f: 1 << -w.e, e: w.e}
 | 
			
		||||
	// Division by one is a shift.
 | 
			
		||||
	integrals := uint32(w.f >> -one.e)
 | 
			
		||||
	// Modulo by one is an and.
 | 
			
		||||
	fractionals := w.f & (one.f - 1)
 | 
			
		||||
	divisor, divisor_exponent := biggestPowerTen(integrals, diyFpKSignificandSize-(-one.e))
 | 
			
		||||
	kappa = divisor_exponent + 1
 | 
			
		||||
	buf = buffer
 | 
			
		||||
	// Loop invariant: buffer = w / 10^kappa  (integer division)
 | 
			
		||||
	// The invariant holds for the first iteration: kappa has been initialized
 | 
			
		||||
	// with the divisor exponent + 1. And the divisor is the biggest power of ten
 | 
			
		||||
	// that is smaller than 'integrals'.
 | 
			
		||||
	for kappa > 0 {
 | 
			
		||||
		digit := byte(integrals / divisor)
 | 
			
		||||
		buf = append(buf, '0'+digit)
 | 
			
		||||
		requested_digits--
 | 
			
		||||
		integrals %= divisor
 | 
			
		||||
		kappa--
 | 
			
		||||
		// Note that kappa now equals the exponent of the divisor and that the
 | 
			
		||||
		// invariant thus holds again.
 | 
			
		||||
		if requested_digits == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		divisor /= 10
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if requested_digits == 0 {
 | 
			
		||||
		rest := uint64(integrals)<<-one.e + fractionals
 | 
			
		||||
		res = roundWeedCounted(buf, rest, uint64(divisor)<<-one.e, w_error, &kappa)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The integrals have been generated. We are at the point of the decimal
 | 
			
		||||
	// separator. In the following loop we simply multiply the remaining digits by
 | 
			
		||||
	// 10 and divide by one. We just need to pay attention to multiply associated
 | 
			
		||||
	// data (the 'unit'), too.
 | 
			
		||||
	// Note that the multiplication by 10 does not overflow, because w.e >= -60
 | 
			
		||||
	// and thus one.e >= -60.
 | 
			
		||||
	_DCHECK(one.e >= -60)
 | 
			
		||||
	_DCHECK(fractionals < one.f)
 | 
			
		||||
	_DCHECK(0xFFFFFFFFFFFFFFFF/10 >= one.f)
 | 
			
		||||
	for requested_digits > 0 && fractionals > w_error {
 | 
			
		||||
		fractionals *= 10
 | 
			
		||||
		w_error *= 10
 | 
			
		||||
		// Integer division by one.
 | 
			
		||||
		digit := byte(fractionals >> -one.e)
 | 
			
		||||
		buf = append(buf, '0'+digit)
 | 
			
		||||
		requested_digits--
 | 
			
		||||
		fractionals &= one.f - 1 // Modulo by one.
 | 
			
		||||
		kappa--
 | 
			
		||||
	}
 | 
			
		||||
	if requested_digits != 0 {
 | 
			
		||||
		res = false
 | 
			
		||||
	} else {
 | 
			
		||||
		res = roundWeedCounted(buf, fractionals, one.f, w_error, &kappa)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Provides a decimal representation of v.
 | 
			
		||||
// Returns true if it succeeds, otherwise the result cannot be trusted.
 | 
			
		||||
// There will be *length digits inside the buffer (not null-terminated).
 | 
			
		||||
// If the function returns true then
 | 
			
		||||
//
 | 
			
		||||
//	v == (double) (buffer * 10^decimal_exponent).
 | 
			
		||||
//
 | 
			
		||||
// The digits in the buffer are the shortest representation possible: no
 | 
			
		||||
// 0.09999999999999999 instead of 0.1. The shorter representation will even be
 | 
			
		||||
// chosen even if the longer one would be closer to v.
 | 
			
		||||
// The last digit will be closest to the actual v. That is, even if several
 | 
			
		||||
// digits might correctly yield 'v' when read again, the closest will be
 | 
			
		||||
// computed.
 | 
			
		||||
func grisu3(f float64, buffer []byte) (digits []byte, decimal_exponent int, result bool) {
 | 
			
		||||
	v := double(f)
 | 
			
		||||
	w := v.toNormalizedDiyfp()
 | 
			
		||||
 | 
			
		||||
	// boundary_minus and boundary_plus are the boundaries between v and its
 | 
			
		||||
	// closest floating-point neighbors. Any number strictly between
 | 
			
		||||
	// boundary_minus and boundary_plus will round to v when convert to a double.
 | 
			
		||||
	// Grisu3 will never output representations that lie exactly on a boundary.
 | 
			
		||||
	boundary_minus, boundary_plus := v.normalizedBoundaries()
 | 
			
		||||
	ten_mk_minimal_binary_exponent := kMinimalTargetExponent - (w.e + diyFpKSignificandSize)
 | 
			
		||||
	ten_mk_maximal_binary_exponent := kMaximalTargetExponent - (w.e + diyFpKSignificandSize)
 | 
			
		||||
	ten_mk, mk := getCachedPowerForBinaryExponentRange(ten_mk_minimal_binary_exponent, ten_mk_maximal_binary_exponent)
 | 
			
		||||
 | 
			
		||||
	_DCHECK(
 | 
			
		||||
		(kMinimalTargetExponent <=
 | 
			
		||||
			w.e+ten_mk.e+diyFpKSignificandSize) &&
 | 
			
		||||
			(kMaximalTargetExponent >= w.e+ten_mk.e+diyFpKSignificandSize))
 | 
			
		||||
	// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
 | 
			
		||||
	// 64 bit significand and ten_mk is thus only precise up to 64 bits.
 | 
			
		||||
 | 
			
		||||
	// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
 | 
			
		||||
	// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
 | 
			
		||||
	// off by a small amount.
 | 
			
		||||
	// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
 | 
			
		||||
	// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
 | 
			
		||||
	//           (f-1) * 2^e < w*10^k < (f+1) * 2^e
 | 
			
		||||
	scaled_w := w.times(ten_mk)
 | 
			
		||||
	_DCHECK(scaled_w.e ==
 | 
			
		||||
		boundary_plus.e+ten_mk.e+diyFpKSignificandSize)
 | 
			
		||||
	// In theory it would be possible to avoid some recomputations by computing
 | 
			
		||||
	// the difference between w and boundary_minus/plus (a power of 2) and to
 | 
			
		||||
	// compute scaled_boundary_minus/plus by subtracting/adding from
 | 
			
		||||
	// scaled_w. However the code becomes much less readable and the speed
 | 
			
		||||
	// enhancements are not terrific.
 | 
			
		||||
	scaled_boundary_minus := boundary_minus.times(ten_mk)
 | 
			
		||||
	scaled_boundary_plus := boundary_plus.times(ten_mk)
 | 
			
		||||
	// DigitGen will generate the digits of scaled_w. Therefore we have
 | 
			
		||||
	// v == (double) (scaled_w * 10^-mk).
 | 
			
		||||
	// Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
 | 
			
		||||
	// integer than it will be updated. For instance if scaled_w == 1.23 then
 | 
			
		||||
	// the buffer will be filled with "123" und the decimal_exponent will be
 | 
			
		||||
	// decreased by 2.
 | 
			
		||||
	var kappa int
 | 
			
		||||
	kappa, digits, result = digitGen(scaled_boundary_minus, scaled_w, scaled_boundary_plus, buffer)
 | 
			
		||||
	decimal_exponent = -mk + kappa
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The "counted" version of grisu3 (see above) only generates requested_digits
 | 
			
		||||
// number of digits. This version does not generate the shortest representation,
 | 
			
		||||
// and with enough requested digits 0.1 will at some point print as 0.9999999...
 | 
			
		||||
// Grisu3 is too imprecise for real halfway cases (1.5 will not work) and
 | 
			
		||||
// therefore the rounding strategy for halfway cases is irrelevant.
 | 
			
		||||
func grisu3Counted(v float64, requested_digits int, buffer []byte) (digits []byte, decimal_exponent int, result bool) {
 | 
			
		||||
	w := double(v).toNormalizedDiyfp()
 | 
			
		||||
	ten_mk_minimal_binary_exponent := kMinimalTargetExponent - (w.e + diyFpKSignificandSize)
 | 
			
		||||
	ten_mk_maximal_binary_exponent := kMaximalTargetExponent - (w.e + diyFpKSignificandSize)
 | 
			
		||||
	ten_mk, mk := getCachedPowerForBinaryExponentRange(ten_mk_minimal_binary_exponent, ten_mk_maximal_binary_exponent)
 | 
			
		||||
 | 
			
		||||
	_DCHECK(
 | 
			
		||||
		(kMinimalTargetExponent <=
 | 
			
		||||
			w.e+ten_mk.e+diyFpKSignificandSize) &&
 | 
			
		||||
			(kMaximalTargetExponent >= w.e+ten_mk.e+diyFpKSignificandSize))
 | 
			
		||||
	// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
 | 
			
		||||
	// 64 bit significand and ten_mk is thus only precise up to 64 bits.
 | 
			
		||||
 | 
			
		||||
	// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
 | 
			
		||||
	// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
 | 
			
		||||
	// off by a small amount.
 | 
			
		||||
	// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
 | 
			
		||||
	// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
 | 
			
		||||
	//           (f-1) * 2^e < w*10^k < (f+1) * 2^e
 | 
			
		||||
	scaled_w := w.times(ten_mk)
 | 
			
		||||
	// We now have (double) (scaled_w * 10^-mk).
 | 
			
		||||
	// DigitGen will generate the first requested_digits digits of scaled_w and
 | 
			
		||||
	// return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
 | 
			
		||||
	// will not always be exactly the same since DigitGenCounted only produces a
 | 
			
		||||
	// limited number of digits.)
 | 
			
		||||
	var kappa int
 | 
			
		||||
	kappa, digits, result = digitGenCounted(scaled_w, requested_digits, buffer)
 | 
			
		||||
	decimal_exponent = -mk + kappa
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// v must be > 0 and must not be Inf or NaN
 | 
			
		||||
func Dtoa(v float64, mode Mode, requested_digits int, buffer []byte) (digits []byte, decimal_point int, result bool) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if x := recover(); x != nil {
 | 
			
		||||
			if x == dcheckFailure {
 | 
			
		||||
				panic(fmt.Errorf("DCHECK assertion failed while formatting %s in mode %d", strconv.FormatFloat(v, 'e', 50, 64), mode))
 | 
			
		||||
			}
 | 
			
		||||
			panic(x)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	var decimal_exponent int
 | 
			
		||||
	startPos := len(buffer)
 | 
			
		||||
	switch mode {
 | 
			
		||||
	case ModeShortest:
 | 
			
		||||
		digits, decimal_exponent, result = grisu3(v, buffer)
 | 
			
		||||
	case ModePrecision:
 | 
			
		||||
		digits, decimal_exponent, result = grisu3Counted(v, requested_digits, buffer)
 | 
			
		||||
	}
 | 
			
		||||
	if result {
 | 
			
		||||
		decimal_point = len(digits) - startPos + decimal_exponent
 | 
			
		||||
	} else {
 | 
			
		||||
		digits = digits[:startPos]
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1116
									
								
								goja/func.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1116
									
								
								goja/func.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										309
									
								
								goja/func_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								goja/func_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,309 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestFuncProto(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	"use strict";
 | 
			
		||||
	function A() {}
 | 
			
		||||
	A.__proto__ = Object;
 | 
			
		||||
	A.prototype = {};
 | 
			
		||||
 | 
			
		||||
	function B() {}
 | 
			
		||||
	B.__proto__ = Object.create(null);
 | 
			
		||||
	var thrown = false;
 | 
			
		||||
	try {
 | 
			
		||||
		delete B.prototype;
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		thrown = e instanceof TypeError;
 | 
			
		||||
	}
 | 
			
		||||
	thrown;
 | 
			
		||||
	`
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFuncPrototypeRedefine(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	let thrown = false;
 | 
			
		||||
	try {
 | 
			
		||||
		Object.defineProperty(function() {}, "prototype", {
 | 
			
		||||
			set: function(_value) {},
 | 
			
		||||
		});
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		if (e instanceof TypeError) {
 | 
			
		||||
			thrown = true;
 | 
			
		||||
		} else {
 | 
			
		||||
			throw e;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	thrown;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFuncExport(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	typ := reflect.TypeOf((func(FunctionCall) Value)(nil))
 | 
			
		||||
 | 
			
		||||
	f := func(expr string, t *testing.T) {
 | 
			
		||||
		v, err := vm.RunString(expr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if actualTyp := v.ExportType(); actualTyp != typ {
 | 
			
		||||
			t.Fatalf("Invalid export type: %v", actualTyp)
 | 
			
		||||
		}
 | 
			
		||||
		ev := v.Export()
 | 
			
		||||
		if actualTyp := reflect.TypeOf(ev); actualTyp != typ {
 | 
			
		||||
			t.Fatalf("Invalid export value: %v", ev)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("regular function", func(t *testing.T) {
 | 
			
		||||
		f("(function() {})", t)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("arrow function", func(t *testing.T) {
 | 
			
		||||
		f("(()=>{})", t)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("method", func(t *testing.T) {
 | 
			
		||||
		f("({m() {}}).m", t)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("class", func(t *testing.T) {
 | 
			
		||||
		f("(class {})", t)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFuncWrapUnwrap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	f := func(a int, b string) bool {
 | 
			
		||||
		return a > 0 && b != ""
 | 
			
		||||
	}
 | 
			
		||||
	var f1 func(int, string) bool
 | 
			
		||||
	v := vm.ToValue(f)
 | 
			
		||||
	if et := v.ExportType(); et != reflect.TypeOf(f1) {
 | 
			
		||||
		t.Fatal(et)
 | 
			
		||||
	}
 | 
			
		||||
	err := vm.ExportTo(v, &f1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !f1(1, "a") {
 | 
			
		||||
		t.Fatal("not true")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWrappedFunc(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	f := func(a int, b string) bool {
 | 
			
		||||
		return a > 0 && b != ""
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("f", f)
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert.sameValue(typeof f, "function");
 | 
			
		||||
	const s = f.toString()
 | 
			
		||||
	assert(s.endsWith("TestWrappedFunc.func1() { [native code] }"), s);
 | 
			
		||||
	assert(f(1, "a"));
 | 
			
		||||
	assert(!f(0, ""));
 | 
			
		||||
	`
 | 
			
		||||
	vm.testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWrappedFuncErrorPassthrough(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	e := errors.New("test")
 | 
			
		||||
	f := func(a int) error {
 | 
			
		||||
		if a > 0 {
 | 
			
		||||
			return e
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var f1 func(a int64) error
 | 
			
		||||
	err := vm.ExportTo(vm.ToValue(f), &f1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := f1(1); err != e {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleAssertConstructor() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	res, err := vm.RunString(`
 | 
			
		||||
		(class C {
 | 
			
		||||
			constructor(x) {
 | 
			
		||||
				this.x = x;
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	if ctor, ok := AssertConstructor(res); ok {
 | 
			
		||||
		obj, err := ctor(nil, vm.ToValue("Test"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Print(obj.Get("x"))
 | 
			
		||||
	} else {
 | 
			
		||||
		panic("Not a constructor")
 | 
			
		||||
	}
 | 
			
		||||
	// Output: Test
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testAsyncCtx struct {
 | 
			
		||||
	group    string
 | 
			
		||||
	refCount int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testAsyncContextTracker struct {
 | 
			
		||||
	ctx     *testAsyncCtx
 | 
			
		||||
	logFunc func(...interface{})
 | 
			
		||||
	resumed bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *testAsyncContextTracker) Grab() interface{} {
 | 
			
		||||
	ctx := s.ctx
 | 
			
		||||
	if ctx != nil {
 | 
			
		||||
		s.logFunc("Grab", ctx.group)
 | 
			
		||||
		ctx.refCount++
 | 
			
		||||
	}
 | 
			
		||||
	return ctx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *testAsyncContextTracker) Resumed(trackingObj interface{}) {
 | 
			
		||||
	s.logFunc("Resumed", trackingObj)
 | 
			
		||||
	if s.resumed {
 | 
			
		||||
		panic("Nested Resumed() calls")
 | 
			
		||||
	}
 | 
			
		||||
	s.ctx = trackingObj.(*testAsyncCtx)
 | 
			
		||||
	s.resumed = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *testAsyncContextTracker) releaseCtx() {
 | 
			
		||||
	s.ctx.refCount--
 | 
			
		||||
	if s.ctx.refCount < 0 {
 | 
			
		||||
		panic("refCount < 0")
 | 
			
		||||
	}
 | 
			
		||||
	if s.ctx.refCount == 0 {
 | 
			
		||||
		s.logFunc(s.ctx.group, "is finished")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *testAsyncContextTracker) Exited() {
 | 
			
		||||
	s.logFunc("Exited")
 | 
			
		||||
	if s.ctx != nil {
 | 
			
		||||
		s.releaseCtx()
 | 
			
		||||
		s.ctx = nil
 | 
			
		||||
	}
 | 
			
		||||
	s.resumed = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAsyncContextTracker(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	var tracker testAsyncContextTracker
 | 
			
		||||
	tracker.logFunc = t.Log
 | 
			
		||||
 | 
			
		||||
	group := func(name string, asyncFunc func(FunctionCall) Value) Value {
 | 
			
		||||
		prevCtx := tracker.ctx
 | 
			
		||||
		defer func() {
 | 
			
		||||
			t.Log("Returned", name)
 | 
			
		||||
			tracker.releaseCtx()
 | 
			
		||||
			tracker.ctx = prevCtx
 | 
			
		||||
		}()
 | 
			
		||||
		tracker.ctx = &testAsyncCtx{
 | 
			
		||||
			group:    name,
 | 
			
		||||
			refCount: 1,
 | 
			
		||||
		}
 | 
			
		||||
		t.Log("Set", name)
 | 
			
		||||
		return asyncFunc(FunctionCall{})
 | 
			
		||||
	}
 | 
			
		||||
	r.SetAsyncContextTracker(&tracker)
 | 
			
		||||
	r.Set("group", group)
 | 
			
		||||
	r.Set("check", func(expectedGroup, msg string) {
 | 
			
		||||
		var groupName string
 | 
			
		||||
		if tracker.ctx != nil {
 | 
			
		||||
			groupName = tracker.ctx.group
 | 
			
		||||
		}
 | 
			
		||||
		if groupName != expectedGroup {
 | 
			
		||||
			t.Fatalf("Unexpected group (%q), expected %q in %s", groupName, expectedGroup, msg)
 | 
			
		||||
		}
 | 
			
		||||
		t.Log("In", msg)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("", func(t *testing.T) {
 | 
			
		||||
		_, err := r.RunString(`
 | 
			
		||||
		group("1", async () => {
 | 
			
		||||
		  check("1", "line A");
 | 
			
		||||
		  await 3;
 | 
			
		||||
		  check("1", "line B");
 | 
			
		||||
		  group("2", async () => {
 | 
			
		||||
		     check("2", "line C");
 | 
			
		||||
		     await 4;
 | 
			
		||||
		     check("2", "line D");
 | 
			
		||||
		 })
 | 
			
		||||
		}).then(() => {
 | 
			
		||||
            check("", "line E");
 | 
			
		||||
		})
 | 
			
		||||
		`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("", func(t *testing.T) {
 | 
			
		||||
		_, err := r.RunString(`
 | 
			
		||||
		group("some", async () => {
 | 
			
		||||
			check("some", "line A");
 | 
			
		||||
		    (async () => {
 | 
			
		||||
				check("some", "line B");
 | 
			
		||||
		        await 1;
 | 
			
		||||
				check("some", "line C");
 | 
			
		||||
		        await 2;
 | 
			
		||||
				check("some", "line D");
 | 
			
		||||
		    })();
 | 
			
		||||
			check("some", "line E");
 | 
			
		||||
		});
 | 
			
		||||
	`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("", func(t *testing.T) {
 | 
			
		||||
		_, err := r.RunString(`
 | 
			
		||||
	group("Main", async () => {
 | 
			
		||||
		check("Main", "0.1");
 | 
			
		||||
		await Promise.all([
 | 
			
		||||
			group("A", async () => {
 | 
			
		||||
				check("A", "1.1");
 | 
			
		||||
				await 1;
 | 
			
		||||
				check("A", "1.2");
 | 
			
		||||
			}),
 | 
			
		||||
			(async () => {
 | 
			
		||||
				check("Main", "3.1");
 | 
			
		||||
			})(),
 | 
			
		||||
			group("B", async () => {
 | 
			
		||||
				check("B", "2.1");
 | 
			
		||||
				await 2;
 | 
			
		||||
				check("B", "2.2");
 | 
			
		||||
			})
 | 
			
		||||
		]);
 | 
			
		||||
		check("Main", "0.2");
 | 
			
		||||
	});
 | 
			
		||||
	`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								goja/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								goja/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
module github.com/dop251/goja
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Masterminds/semver/v3 v3.2.1
 | 
			
		||||
	github.com/dlclark/regexp2 v1.11.4
 | 
			
		||||
	github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
 | 
			
		||||
	github.com/go-sourcemap/sourcemap v2.1.3+incompatible
 | 
			
		||||
	github.com/google/pprof v0.0.0-20230207041349-798e818bf904
 | 
			
		||||
	golang.org/x/text v0.3.8
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require github.com/kr/pretty v0.3.0 // indirect
 | 
			
		||||
							
								
								
									
										127
									
								
								goja/goja/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								goja/goja/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	crand "crypto/rand"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"log"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	"runtime/pprof"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja"
 | 
			
		||||
	"github.com/dop251/goja_nodejs/console"
 | 
			
		||||
	"github.com/dop251/goja_nodejs/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 io.ReadAll(os.Stdin)
 | 
			
		||||
	}
 | 
			
		||||
	return os.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 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())
 | 
			
		||||
 | 
			
		||||
	new(require.Registry).Enable(vm)
 | 
			
		||||
	console.Enable(vm)
 | 
			
		||||
 | 
			
		||||
	vm.Set("load", func(call goja.FunctionCall) goja.Value {
 | 
			
		||||
		return load(vm, call)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	vm.Set("readFile", func(name string) (string, error) {
 | 
			
		||||
		b, err := os.ReadFile(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		return string(b), nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if *timelimit > 0 {
 | 
			
		||||
		time.AfterFunc(time.Duration(*timelimit)*time.Second, func() {
 | 
			
		||||
			vm.Interrupt("timeout")
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("Compiling...")
 | 
			
		||||
	prg, err := goja.Compile(filename, string(src), false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	//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.Println(err.String())
 | 
			
		||||
		case *goja.InterruptedError:
 | 
			
		||||
			fmt.Println(err.String())
 | 
			
		||||
		default:
 | 
			
		||||
			fmt.Println(err)
 | 
			
		||||
		}
 | 
			
		||||
		os.Exit(64)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								goja/ipow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								goja/ipow.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
// inspired by https://gist.github.com/orlp/3551590
 | 
			
		||||
 | 
			
		||||
var overflows = [64]int64{
 | 
			
		||||
	9223372036854775807, 9223372036854775807, 3037000499, 2097151,
 | 
			
		||||
	55108, 6208, 1448, 511,
 | 
			
		||||
	234, 127, 78, 52,
 | 
			
		||||
	38, 28, 22, 18,
 | 
			
		||||
	15, 13, 11, 9,
 | 
			
		||||
	8, 7, 7, 6,
 | 
			
		||||
	6, 5, 5, 5,
 | 
			
		||||
	4, 4, 4, 4,
 | 
			
		||||
	3, 3, 3, 3,
 | 
			
		||||
	3, 3, 3, 3,
 | 
			
		||||
	2, 2, 2, 2,
 | 
			
		||||
	2, 2, 2, 2,
 | 
			
		||||
	2, 2, 2, 2,
 | 
			
		||||
	2, 2, 2, 2,
 | 
			
		||||
	2, 2, 2, 2,
 | 
			
		||||
	2, 2, 2, 2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var highestBitSet = [63]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,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ipow(base, exp int64) (result int64) {
 | 
			
		||||
	if exp >= 63 {
 | 
			
		||||
		if base == 1 {
 | 
			
		||||
			return 1
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if base == -1 {
 | 
			
		||||
			return 1 - 2*(exp&1)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if base > overflows[exp] || -base > overflows[exp] {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result = 1
 | 
			
		||||
 | 
			
		||||
	switch highestBitSet[byte(exp)] {
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										169
									
								
								goja/map.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								goja/map.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,169 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"hash/maphash"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type mapEntry struct {
 | 
			
		||||
	key, value Value
 | 
			
		||||
 | 
			
		||||
	iterPrev, iterNext *mapEntry
 | 
			
		||||
	hNext              *mapEntry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type orderedMap struct {
 | 
			
		||||
	hash                *maphash.Hash
 | 
			
		||||
	hashTable           map[uint64]*mapEntry
 | 
			
		||||
	iterFirst, iterLast *mapEntry
 | 
			
		||||
	size                int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type orderedMapIter struct {
 | 
			
		||||
	m   *orderedMap
 | 
			
		||||
	cur *mapEntry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) lookup(key Value) (h uint64, entry, hPrev *mapEntry) {
 | 
			
		||||
	if key == _negativeZero {
 | 
			
		||||
		key = intToValue(0)
 | 
			
		||||
	}
 | 
			
		||||
	h = key.hash(m.hash)
 | 
			
		||||
	for entry = m.hashTable[h]; entry != nil && !entry.key.SameAs(key); hPrev, entry = entry, entry.hNext {
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) set(key, value Value) {
 | 
			
		||||
	h, entry, hPrev := m.lookup(key)
 | 
			
		||||
	if entry != nil {
 | 
			
		||||
		entry.value = value
 | 
			
		||||
	} else {
 | 
			
		||||
		if key == _negativeZero {
 | 
			
		||||
			key = intToValue(0)
 | 
			
		||||
		}
 | 
			
		||||
		entry = &mapEntry{key: key, value: value}
 | 
			
		||||
		if hPrev == nil {
 | 
			
		||||
			m.hashTable[h] = entry
 | 
			
		||||
		} else {
 | 
			
		||||
			hPrev.hNext = entry
 | 
			
		||||
		}
 | 
			
		||||
		if m.iterLast != nil {
 | 
			
		||||
			entry.iterPrev = m.iterLast
 | 
			
		||||
			m.iterLast.iterNext = entry
 | 
			
		||||
		} else {
 | 
			
		||||
			m.iterFirst = entry
 | 
			
		||||
		}
 | 
			
		||||
		m.iterLast = entry
 | 
			
		||||
		m.size++
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) get(key Value) Value {
 | 
			
		||||
	_, entry, _ := m.lookup(key)
 | 
			
		||||
	if entry != nil {
 | 
			
		||||
		return entry.value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) remove(key Value) bool {
 | 
			
		||||
	h, entry, hPrev := m.lookup(key)
 | 
			
		||||
	if entry != nil {
 | 
			
		||||
		entry.key = nil
 | 
			
		||||
		entry.value = nil
 | 
			
		||||
 | 
			
		||||
		// remove from the doubly-linked list
 | 
			
		||||
		if entry.iterPrev != nil {
 | 
			
		||||
			entry.iterPrev.iterNext = entry.iterNext
 | 
			
		||||
		} else {
 | 
			
		||||
			m.iterFirst = entry.iterNext
 | 
			
		||||
		}
 | 
			
		||||
		if entry.iterNext != nil {
 | 
			
		||||
			entry.iterNext.iterPrev = entry.iterPrev
 | 
			
		||||
		} else {
 | 
			
		||||
			m.iterLast = entry.iterPrev
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// remove from the hashTable
 | 
			
		||||
		if hPrev == nil {
 | 
			
		||||
			if entry.hNext == nil {
 | 
			
		||||
				delete(m.hashTable, h)
 | 
			
		||||
			} else {
 | 
			
		||||
				m.hashTable[h] = entry.hNext
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			hPrev.hNext = entry.hNext
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		m.size--
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) has(key Value) bool {
 | 
			
		||||
	_, entry, _ := m.lookup(key)
 | 
			
		||||
	return entry != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (iter *orderedMapIter) next() *mapEntry {
 | 
			
		||||
	if iter.m == nil {
 | 
			
		||||
		// closed iterator
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cur := iter.cur
 | 
			
		||||
	// if the current item was deleted, track back to find the latest that wasn't
 | 
			
		||||
	for cur != nil && cur.key == nil {
 | 
			
		||||
		cur = cur.iterPrev
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cur != nil {
 | 
			
		||||
		cur = cur.iterNext
 | 
			
		||||
	} else {
 | 
			
		||||
		cur = iter.m.iterFirst
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cur == nil {
 | 
			
		||||
		iter.close()
 | 
			
		||||
	} else {
 | 
			
		||||
		iter.cur = cur
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cur
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (iter *orderedMapIter) close() {
 | 
			
		||||
	iter.m = nil
 | 
			
		||||
	iter.cur = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newOrderedMap(h *maphash.Hash) *orderedMap {
 | 
			
		||||
	return &orderedMap{
 | 
			
		||||
		hash:      h,
 | 
			
		||||
		hashTable: make(map[uint64]*mapEntry),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) newIter() *orderedMapIter {
 | 
			
		||||
	iter := &orderedMapIter{
 | 
			
		||||
		m: m,
 | 
			
		||||
	}
 | 
			
		||||
	return iter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *orderedMap) clear() {
 | 
			
		||||
	for item := m.iterFirst; item != nil; item = item.iterNext {
 | 
			
		||||
		item.key = nil
 | 
			
		||||
		item.value = nil
 | 
			
		||||
		if item.iterPrev != nil {
 | 
			
		||||
			item.iterPrev.iterNext = nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	m.iterFirst = nil
 | 
			
		||||
	m.iterLast = nil
 | 
			
		||||
	m.hashTable = make(map[uint64]*mapEntry)
 | 
			
		||||
	m.size = 0
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										201
									
								
								goja/map_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								goja/map_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,201 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"hash/maphash"
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testMapHashVal(v1, v2 Value, expected bool, t *testing.T) {
 | 
			
		||||
	var h maphash.Hash
 | 
			
		||||
	actual := v1.hash(&h) == v2.hash(&h)
 | 
			
		||||
	if actual != expected {
 | 
			
		||||
		t.Fatalf("testMapHashVal failed for %v, %v", v1, v2)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMapHash(t *testing.T) {
 | 
			
		||||
	testMapHashVal(_NaN, _NaN, true, t)
 | 
			
		||||
	testMapHashVal(valueTrue, valueFalse, false, t)
 | 
			
		||||
	testMapHashVal(valueTrue, valueTrue, true, t)
 | 
			
		||||
	testMapHashVal(intToValue(0), _negativeZero, true, t)
 | 
			
		||||
	testMapHashVal(asciiString("Test"), asciiString("Test"), true, t)
 | 
			
		||||
	testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, t)
 | 
			
		||||
	testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t)
 | 
			
		||||
	testMapHashVal(SymIterator, SymToStringTag, false, t)
 | 
			
		||||
	testMapHashVal(SymIterator, SymIterator, true, t)
 | 
			
		||||
	testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(-1)), false, t)
 | 
			
		||||
	testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(1)), true, t)
 | 
			
		||||
 | 
			
		||||
	// The following tests introduce indeterministic behaviour
 | 
			
		||||
	//testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t)
 | 
			
		||||
	//testMapHashVal(newStringValue("Тест"), asciiString("Test"), false, t)
 | 
			
		||||
	//testMapHashVal(newStringValue("Тест"), newStringValue("Тест1"), false, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOrderedMap(t *testing.T) {
 | 
			
		||||
	m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
	for i := int64(0); i < 50; i++ {
 | 
			
		||||
		m.set(intToValue(i), asciiString(strconv.FormatInt(i, 10)))
 | 
			
		||||
	}
 | 
			
		||||
	if m.size != 50 {
 | 
			
		||||
		t.Fatalf("Unexpected size: %d", m.size)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := int64(0); i < 50; i++ {
 | 
			
		||||
		expected := asciiString(strconv.FormatInt(i, 10))
 | 
			
		||||
		actual := m.get(intToValue(i))
 | 
			
		||||
		if !expected.SameAs(actual) {
 | 
			
		||||
			t.Fatalf("Wrong value for %d", i)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := int64(0); i < 50; i += 2 {
 | 
			
		||||
		if !m.remove(intToValue(i)) {
 | 
			
		||||
			t.Fatalf("remove(%d) return false", i)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if m.size != 25 {
 | 
			
		||||
		t.Fatalf("Unexpected size: %d", m.size)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	iter := m.newIter()
 | 
			
		||||
	count := 0
 | 
			
		||||
	for {
 | 
			
		||||
		entry := iter.next()
 | 
			
		||||
		if entry == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		m.remove(entry.key)
 | 
			
		||||
		count++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if count != 25 {
 | 
			
		||||
		t.Fatalf("Unexpected iter count: %d", count)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.size != 0 {
 | 
			
		||||
		t.Fatalf("Unexpected size: %d", m.size)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOrderedMapCollision(t *testing.T) {
 | 
			
		||||
	m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
	n1 := uint64(123456789)
 | 
			
		||||
	n2 := math.Float64frombits(n1)
 | 
			
		||||
	n1Key := intToValue(int64(n1))
 | 
			
		||||
	n2Key := floatToValue(n2)
 | 
			
		||||
	m.set(n1Key, asciiString("n1"))
 | 
			
		||||
	m.set(n2Key, asciiString("n2"))
 | 
			
		||||
	if m.size == len(m.hashTable) {
 | 
			
		||||
		t.Fatal("Expected a collision but there wasn't one")
 | 
			
		||||
	}
 | 
			
		||||
	if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
 | 
			
		||||
		t.Fatalf("unexpected n2Val: %v", n2Val)
 | 
			
		||||
	}
 | 
			
		||||
	if n1Val := m.get(n1Key); !asciiString("n1").SameAs(n1Val) {
 | 
			
		||||
		t.Fatalf("unexpected nVal: %v", n1Val)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !m.remove(n1Key) {
 | 
			
		||||
		t.Fatal("removing n1Key returned false")
 | 
			
		||||
	}
 | 
			
		||||
	if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
 | 
			
		||||
		t.Fatalf("2: unexpected n2Val: %v", n2Val)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOrderedMapIter(t *testing.T) {
 | 
			
		||||
	m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
	iter := m.newIter()
 | 
			
		||||
	ent := iter.next()
 | 
			
		||||
	if ent != nil {
 | 
			
		||||
		t.Fatal("entry should be nil")
 | 
			
		||||
	}
 | 
			
		||||
	iter1 := m.newIter()
 | 
			
		||||
	m.set(intToValue(1), valueTrue)
 | 
			
		||||
	ent = iter.next()
 | 
			
		||||
	if ent != nil {
 | 
			
		||||
		t.Fatal("2: entry should be nil")
 | 
			
		||||
	}
 | 
			
		||||
	ent = iter1.next()
 | 
			
		||||
	if ent == nil {
 | 
			
		||||
		t.Fatal("entry is nil")
 | 
			
		||||
	}
 | 
			
		||||
	if !intToValue(1).SameAs(ent.key) {
 | 
			
		||||
		t.Fatal("unexpected key")
 | 
			
		||||
	}
 | 
			
		||||
	if !valueTrue.SameAs(ent.value) {
 | 
			
		||||
		t.Fatal("unexpected value")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOrderedMapIterVisitAfterReAdd(t *testing.T) {
 | 
			
		||||
	m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
	one := intToValue(1)
 | 
			
		||||
	two := intToValue(2)
 | 
			
		||||
 | 
			
		||||
	m.set(one, valueTrue)
 | 
			
		||||
	m.set(two, valueTrue)
 | 
			
		||||
	iter := m.newIter()
 | 
			
		||||
	entry := iter.next()
 | 
			
		||||
	if !one.SameAs(entry.key) {
 | 
			
		||||
		t.Fatalf("1: unexpected key: %v", entry.key)
 | 
			
		||||
	}
 | 
			
		||||
	if !m.remove(one) {
 | 
			
		||||
		t.Fatal("remove returned false")
 | 
			
		||||
	}
 | 
			
		||||
	entry = iter.next()
 | 
			
		||||
	if !two.SameAs(entry.key) {
 | 
			
		||||
		t.Fatalf("2: unexpected key: %v", entry.key)
 | 
			
		||||
	}
 | 
			
		||||
	m.set(one, valueTrue)
 | 
			
		||||
	entry = iter.next()
 | 
			
		||||
	if entry == nil {
 | 
			
		||||
		t.Fatal("entry is nil")
 | 
			
		||||
	}
 | 
			
		||||
	if !one.SameAs(entry.key) {
 | 
			
		||||
		t.Fatalf("3: unexpected key: %v", entry.key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOrderedMapIterAddAfterClear(t *testing.T) {
 | 
			
		||||
	m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
	one := intToValue(1)
 | 
			
		||||
	m.set(one, valueTrue)
 | 
			
		||||
	iter := m.newIter()
 | 
			
		||||
	iter.next()
 | 
			
		||||
	m.clear()
 | 
			
		||||
	m.set(one, valueTrue)
 | 
			
		||||
	entry := iter.next()
 | 
			
		||||
	if entry == nil {
 | 
			
		||||
		t.Fatal("entry is nil")
 | 
			
		||||
	}
 | 
			
		||||
	if entry.key != one {
 | 
			
		||||
		t.Fatalf("unexpected key: %v", entry.key)
 | 
			
		||||
	}
 | 
			
		||||
	entry = iter.next()
 | 
			
		||||
	if entry != nil {
 | 
			
		||||
		t.Fatalf("entry is not nil: %v", entry)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOrderedMapIterDeleteCurrent(t *testing.T) {
 | 
			
		||||
	m := newOrderedMap(&maphash.Hash{})
 | 
			
		||||
	one := intToValue(1)
 | 
			
		||||
	two := intToValue(2)
 | 
			
		||||
	iter := m.newIter()
 | 
			
		||||
	m.set(one, valueTrue)
 | 
			
		||||
	m.set(two, valueTrue)
 | 
			
		||||
	entry := iter.next()
 | 
			
		||||
	if entry.key != one {
 | 
			
		||||
		t.Fatalf("unexpected key: %v", entry.key)
 | 
			
		||||
	}
 | 
			
		||||
	m.remove(one)
 | 
			
		||||
	entry = iter.next()
 | 
			
		||||
	if entry.key != two {
 | 
			
		||||
		t.Fatalf("2: unexpected key: %v", entry.key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1824
									
								
								goja/object.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1824
									
								
								goja/object.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										139
									
								
								goja/object_args.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								goja/object_args.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import "github.com/dop251/goja/unistring"
 | 
			
		||||
 | 
			
		||||
type argumentsObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	length int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mappedProperty struct {
 | 
			
		||||
	valueProperty
 | 
			
		||||
	v *Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if mapped, ok := a.values[name].(*mappedProperty); ok {
 | 
			
		||||
		if mapped.writable && mapped.enumerable && mapped.configurable {
 | 
			
		||||
			return *mapped.v
 | 
			
		||||
		}
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:        *mapped.v,
 | 
			
		||||
			writable:     mapped.writable,
 | 
			
		||||
			configurable: mapped.configurable,
 | 
			
		||||
			enumerable:   mapped.enumerable,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return a.baseObject.getOwnPropStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) init() {
 | 
			
		||||
	a.baseObject.init()
 | 
			
		||||
	a._putProp("length", intToValue(int64(a.length)), true, false, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if prop, ok := a.values[name].(*mappedProperty); ok {
 | 
			
		||||
		if !prop.writable {
 | 
			
		||||
			a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		*prop.v = val
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return a.baseObject.setOwnStr(name, val, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) deleteStr(name unistring.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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&argumentsPropIter{
 | 
			
		||||
		wrapped: a.baseObject.iterateStringKeys(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if mapped, ok := a.values[name].(*mappedProperty); ok {
 | 
			
		||||
		existing := &valueProperty{
 | 
			
		||||
			configurable: mapped.configurable,
 | 
			
		||||
			writable:     true,
 | 
			
		||||
			enumerable:   mapped.enumerable,
 | 
			
		||||
			value:        *mapped.v,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		val, ok := a.baseObject._defineOwnProperty(name, 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.defineOwnPropertyStr(name, descr, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *argumentsObject) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	if v, exists := ctx.get(a.val); exists {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	arr := make([]interface{}, a.length)
 | 
			
		||||
	ctx.put(a.val, arr)
 | 
			
		||||
	for i := range arr {
 | 
			
		||||
		v := a.getIdx(valueInt(int64(i)), nil)
 | 
			
		||||
		if v != nil {
 | 
			
		||||
			arr[i] = exportValue(v, ctx)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return arr
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										794
									
								
								goja/object_dynamic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										794
									
								
								goja/object_dynamic.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,794 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
DynamicObject is an interface representing a handler for a dynamic Object. Such an object can be created
 | 
			
		||||
using the Runtime.NewDynamicObject() method.
 | 
			
		||||
 | 
			
		||||
Note that Runtime.ToValue() does not have any special treatment for DynamicObject. The only way to create
 | 
			
		||||
a dynamic object is by using the Runtime.NewDynamicObject() method. This is done deliberately to avoid
 | 
			
		||||
silent code breaks when this interface changes.
 | 
			
		||||
*/
 | 
			
		||||
type DynamicObject interface {
 | 
			
		||||
	// Get a property value for the key. May return nil if the property does not exist.
 | 
			
		||||
	Get(key string) Value
 | 
			
		||||
	// Set a property value for the key. Return true if success, false otherwise.
 | 
			
		||||
	Set(key string, val Value) bool
 | 
			
		||||
	// Has should return true if and only if the property exists.
 | 
			
		||||
	Has(key string) bool
 | 
			
		||||
	// Delete the property for the key. Returns true on success (note, that includes missing property).
 | 
			
		||||
	Delete(key string) bool
 | 
			
		||||
	// Keys returns a list of all existing property keys. There are no checks for duplicates or to make sure
 | 
			
		||||
	// that the order conforms to https://262.ecma-international.org/#sec-ordinaryownpropertykeys
 | 
			
		||||
	Keys() []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
DynamicArray is an interface representing a handler for a dynamic array Object. Such an object can be created
 | 
			
		||||
using the Runtime.NewDynamicArray() method.
 | 
			
		||||
 | 
			
		||||
Any integer property key or a string property key that can be parsed into an int value (including negative
 | 
			
		||||
ones) is treated as an index and passed to the trap methods of the DynamicArray. Note this is different from
 | 
			
		||||
the regular ECMAScript arrays which only support positive indexes up to 2^32-1.
 | 
			
		||||
 | 
			
		||||
DynamicArray cannot be sparse, i.e. hasOwnProperty(num) will return true for num >= 0 && num < Len(). Deleting
 | 
			
		||||
such a property is equivalent to setting it to undefined. Note that this creates a slight peculiarity because
 | 
			
		||||
hasOwnProperty() will still return true, even after deletion.
 | 
			
		||||
 | 
			
		||||
Note that Runtime.ToValue() does not have any special treatment for DynamicArray. The only way to create
 | 
			
		||||
a dynamic array is by using the Runtime.NewDynamicArray() method. This is done deliberately to avoid
 | 
			
		||||
silent code breaks when this interface changes.
 | 
			
		||||
*/
 | 
			
		||||
type DynamicArray interface {
 | 
			
		||||
	// Len returns the current array length.
 | 
			
		||||
	Len() int
 | 
			
		||||
	// Get an item at index idx. Note that idx may be any integer, negative or beyond the current length.
 | 
			
		||||
	Get(idx int) Value
 | 
			
		||||
	// Set an item at index idx. Note that idx may be any integer, negative or beyond the current length.
 | 
			
		||||
	// The expected behaviour when it's beyond length is that the array's length is increased to accommodate
 | 
			
		||||
	// the item. All elements in the 'new' section of the array should be zeroed.
 | 
			
		||||
	Set(idx int, val Value) bool
 | 
			
		||||
	// SetLen is called when the array's 'length' property is changed. If the length is increased all elements in the
 | 
			
		||||
	// 'new' section of the array should be zeroed.
 | 
			
		||||
	SetLen(int) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type baseDynamicObject struct {
 | 
			
		||||
	val       *Object
 | 
			
		||||
	prototype *Object
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dynamicObject struct {
 | 
			
		||||
	baseDynamicObject
 | 
			
		||||
	d DynamicObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dynamicArray struct {
 | 
			
		||||
	baseDynamicObject
 | 
			
		||||
	a DynamicArray
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
NewDynamicObject creates an Object backed by the provided DynamicObject handler.
 | 
			
		||||
 | 
			
		||||
All properties of this Object are Writable, Enumerable and Configurable data properties. Any attempt to define
 | 
			
		||||
a property that does not conform to this will fail.
 | 
			
		||||
 | 
			
		||||
The Object is always extensible and cannot be made non-extensible. Object.preventExtensions() will fail.
 | 
			
		||||
 | 
			
		||||
The Object's prototype is initially set to Object.prototype, but can be changed using regular mechanisms
 | 
			
		||||
(Object.SetPrototype() in Go or Object.setPrototypeOf() in JS).
 | 
			
		||||
 | 
			
		||||
The Object cannot have own Symbol properties, however its prototype can. If you need an iterator support for
 | 
			
		||||
example, you could create a regular object, set Symbol.iterator on that object and then use it as a
 | 
			
		||||
prototype. See TestDynamicObjectCustomProto for more details.
 | 
			
		||||
 | 
			
		||||
Export() returns the original DynamicObject.
 | 
			
		||||
 | 
			
		||||
This mechanism is similar to ECMAScript Proxy, however because all properties are enumerable and the object
 | 
			
		||||
is always extensible there is no need for invariant checks which removes the need to have a target object and
 | 
			
		||||
makes it a lot more efficient.
 | 
			
		||||
*/
 | 
			
		||||
func (r *Runtime) NewDynamicObject(d DynamicObject) *Object {
 | 
			
		||||
	v := &Object{runtime: r}
 | 
			
		||||
	o := &dynamicObject{
 | 
			
		||||
		d: d,
 | 
			
		||||
		baseDynamicObject: baseDynamicObject{
 | 
			
		||||
			val:       v,
 | 
			
		||||
			prototype: r.global.ObjectPrototype,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	v.self = o
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
NewSharedDynamicObject is similar to Runtime.NewDynamicObject but the resulting Object can be shared across multiple
 | 
			
		||||
Runtimes. The Object's prototype will be null. The provided DynamicObject must be goroutine-safe.
 | 
			
		||||
*/
 | 
			
		||||
func NewSharedDynamicObject(d DynamicObject) *Object {
 | 
			
		||||
	v := &Object{}
 | 
			
		||||
	o := &dynamicObject{
 | 
			
		||||
		d: d,
 | 
			
		||||
		baseDynamicObject: baseDynamicObject{
 | 
			
		||||
			val: v,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	v.self = o
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
NewDynamicArray creates an array Object backed by the provided DynamicArray handler.
 | 
			
		||||
It is similar to NewDynamicObject, the differences are:
 | 
			
		||||
 | 
			
		||||
- the Object is an array (i.e. Array.isArray() will return true and it will have the length property).
 | 
			
		||||
 | 
			
		||||
- the prototype will be initially set to Array.prototype.
 | 
			
		||||
 | 
			
		||||
- the Object cannot have any own string properties except for the 'length'.
 | 
			
		||||
*/
 | 
			
		||||
func (r *Runtime) NewDynamicArray(a DynamicArray) *Object {
 | 
			
		||||
	v := &Object{runtime: r}
 | 
			
		||||
	o := &dynamicArray{
 | 
			
		||||
		a: a,
 | 
			
		||||
		baseDynamicObject: baseDynamicObject{
 | 
			
		||||
			val:       v,
 | 
			
		||||
			prototype: r.getArrayPrototype(),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	v.self = o
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
NewSharedDynamicArray is similar to Runtime.NewDynamicArray but the resulting Object can be shared across multiple
 | 
			
		||||
Runtimes. The Object's prototype will be null. If you need to run Array's methods on it, use Array.prototype.[...].call(a, ...).
 | 
			
		||||
The provided DynamicArray must be goroutine-safe.
 | 
			
		||||
*/
 | 
			
		||||
func NewSharedDynamicArray(a DynamicArray) *Object {
 | 
			
		||||
	v := &Object{}
 | 
			
		||||
	o := &dynamicArray{
 | 
			
		||||
		a: a,
 | 
			
		||||
		baseDynamicObject: baseDynamicObject{
 | 
			
		||||
			val: v,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	v.self = o
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*dynamicObject) sortLen() int {
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*dynamicObject) sortGet(i int) Value {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*dynamicObject) swap(i int, i2 int) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*dynamicObject) className() string {
 | 
			
		||||
	return classObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) getParentStr(p unistring.String, receiver Value) Value {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		if receiver == nil {
 | 
			
		||||
			return proto.self.getStr(p, o.val)
 | 
			
		||||
		}
 | 
			
		||||
		return proto.self.getStr(p, receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) getStr(p unistring.String, receiver Value) Value {
 | 
			
		||||
	prop := o.d.Get(p.String())
 | 
			
		||||
	if prop == nil {
 | 
			
		||||
		return o.getParentStr(p, receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return prop
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) getParentIdx(p valueInt, receiver Value) Value {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		if receiver == nil {
 | 
			
		||||
			return proto.self.getIdx(p, o.val)
 | 
			
		||||
		}
 | 
			
		||||
		return proto.self.getIdx(p, receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) getIdx(p valueInt, receiver Value) Value {
 | 
			
		||||
	prop := o.d.Get(p.String())
 | 
			
		||||
	if prop == nil {
 | 
			
		||||
		return o.getParentIdx(p, receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return prop
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) getSym(p *Symbol, receiver Value) Value {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		if receiver == nil {
 | 
			
		||||
			return proto.self.getSym(p, o.val)
 | 
			
		||||
		}
 | 
			
		||||
		return proto.self.getSym(p, receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) getOwnPropStr(u unistring.String) Value {
 | 
			
		||||
	return o.d.Get(u.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) getOwnPropIdx(v valueInt) Value {
 | 
			
		||||
	return o.d.Get(v.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) getOwnPropSym(*Symbol) Value {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) _set(prop string, v Value, throw bool) bool {
 | 
			
		||||
	if o.d.Set(prop, v) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	typeErrorResult(throw, "'Set' on a dynamic object returned false")
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) _setSym(throw bool) {
 | 
			
		||||
	typeErrorResult(throw, "Dynamic objects do not support Symbol properties")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
 | 
			
		||||
	prop := p.String()
 | 
			
		||||
	if !o.d.Has(prop) {
 | 
			
		||||
		if proto := o.prototype; proto != nil {
 | 
			
		||||
			// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
			if res, handled := proto.self.setForeignStr(p, v, o.val, throw); handled {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o._set(prop, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
 | 
			
		||||
	prop := p.String()
 | 
			
		||||
	if !o.d.Has(prop) {
 | 
			
		||||
		if proto := o.prototype; proto != nil {
 | 
			
		||||
			// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
			if res, handled := proto.self.setForeignIdx(p, v, o.val, throw); handled {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o._set(prop, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) setOwnSym(s *Symbol, v Value, throw bool) bool {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
		if res, handled := proto.self.setForeignSym(s, v, o.val, throw); handled {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o._setSym(throw)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) setParentForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		if receiver != proto {
 | 
			
		||||
			return proto.self.setForeignStr(p, v, receiver, throw)
 | 
			
		||||
		}
 | 
			
		||||
		return proto.self.setOwnStr(p, v, throw), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	prop := p.String()
 | 
			
		||||
	if !o.d.Has(prop) {
 | 
			
		||||
		return o.setParentForeignStr(p, v, receiver, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) setParentForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		if receiver != proto {
 | 
			
		||||
			return proto.self.setForeignIdx(p, v, receiver, throw)
 | 
			
		||||
		}
 | 
			
		||||
		return proto.self.setOwnIdx(p, v, throw), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	prop := p.String()
 | 
			
		||||
	if !o.d.Has(prop) {
 | 
			
		||||
		return o.setParentForeignIdx(p, v, receiver, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		if receiver != proto {
 | 
			
		||||
			return proto.self.setForeignSym(p, v, receiver, throw)
 | 
			
		||||
		}
 | 
			
		||||
		return proto.self.setOwnSym(p, v, throw), true
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) hasPropertyStr(u unistring.String) bool {
 | 
			
		||||
	if o.hasOwnPropertyStr(u) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		return proto.self.hasPropertyStr(u)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) hasPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if o.hasOwnPropertyIdx(idx) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		return proto.self.hasPropertyIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) hasPropertySym(s *Symbol) bool {
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		return proto.self.hasPropertySym(s)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) hasOwnPropertyStr(u unistring.String) bool {
 | 
			
		||||
	return o.d.Has(u.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) hasOwnPropertyIdx(v valueInt) bool {
 | 
			
		||||
	return o.d.Has(v.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) hasOwnPropertySym(_ *Symbol) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) checkDynamicObjectPropertyDescr(name fmt.Stringer, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if descr.Getter != nil || descr.Setter != nil {
 | 
			
		||||
		typeErrorResult(throw, "Dynamic objects do not support accessor properties")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if descr.Writable == FLAG_FALSE {
 | 
			
		||||
		typeErrorResult(throw, "Dynamic object field %q cannot be made read-only", name.String())
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if descr.Enumerable == FLAG_FALSE {
 | 
			
		||||
		typeErrorResult(throw, "Dynamic object field %q cannot be made non-enumerable", name.String())
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if descr.Configurable == FLAG_FALSE {
 | 
			
		||||
		typeErrorResult(throw, "Dynamic object field %q cannot be made non-configurable", name.String())
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if o.checkDynamicObjectPropertyDescr(name, desc, throw) {
 | 
			
		||||
		return o._set(name.String(), desc.Value, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if o.checkDynamicObjectPropertyDescr(name, desc, throw) {
 | 
			
		||||
		return o._set(name.String(), desc.Value, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	o._setSym(throw)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) _delete(prop string, throw bool) bool {
 | 
			
		||||
	if o.d.Delete(prop) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	typeErrorResult(throw, "Could not delete property %q of a dynamic object", prop)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	return o._delete(name.String(), throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) deleteIdx(idx valueInt, throw bool) bool {
 | 
			
		||||
	return o._delete(idx.String(), throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) deleteSym(_ *Symbol, _ bool) bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) vmCall(vm *vm, n int) {
 | 
			
		||||
	panic(vm.r.NewTypeError("Dynamic object is not callable"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) proto() *Object {
 | 
			
		||||
	return o.prototype
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) setProto(proto *Object, throw bool) bool {
 | 
			
		||||
	o.prototype = proto
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) hasInstance(v Value) bool {
 | 
			
		||||
	panic(newTypeError("Expecting a function in instanceof check, but got a dynamic object"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) isExtensible() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) preventExtensions(throw bool) bool {
 | 
			
		||||
	typeErrorResult(throw, "Cannot make a dynamic object non-extensible")
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dynamicObjectPropIter struct {
 | 
			
		||||
	o         *dynamicObject
 | 
			
		||||
	propNames []string
 | 
			
		||||
	idx       int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *dynamicObjectPropIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	for i.idx < len(i.propNames) {
 | 
			
		||||
		name := i.propNames[i.idx]
 | 
			
		||||
		i.idx++
 | 
			
		||||
		if i.o.d.Has(name) {
 | 
			
		||||
			return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return propIterItem{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	keys := o.d.Keys()
 | 
			
		||||
	return (&dynamicObjectPropIter{
 | 
			
		||||
		o:         o,
 | 
			
		||||
		propNames: keys,
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) iterateSymbols() iterNextFunc {
 | 
			
		||||
	return func() (propIterItem, iterNextFunc) {
 | 
			
		||||
		return propIterItem{}, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) iterateKeys() iterNextFunc {
 | 
			
		||||
	return o.iterateStringKeys()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	return o.d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) exportType() reflect.Type {
 | 
			
		||||
	return reflect.TypeOf(o.d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	return genericExportToMap(o.val, dst, typ, ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
 | 
			
		||||
	return genericExportToArrayOrSlice(o.val, dst, typ, ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) equal(impl objectImpl) bool {
 | 
			
		||||
	if other, ok := impl.(*dynamicObject); ok {
 | 
			
		||||
		return o.d == other.d
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	keys := o.d.Keys()
 | 
			
		||||
	if l := len(accum) + len(keys); l > cap(accum) {
 | 
			
		||||
		oldAccum := accum
 | 
			
		||||
		accum = make([]Value, len(accum), l)
 | 
			
		||||
		copy(accum, oldAccum)
 | 
			
		||||
	}
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		accum = append(accum, newStringValue(key))
 | 
			
		||||
	}
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) symbols(all bool, accum []Value) []Value {
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *dynamicObject) keys(all bool, accum []Value) []Value {
 | 
			
		||||
	return o.stringKeys(all, accum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*baseDynamicObject) _putSym(s *Symbol, prop Value) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) getPrivateEnv(*privateEnvType, bool) *privateElements {
 | 
			
		||||
	panic(newTypeError("Dynamic objects cannot have private elements"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *baseDynamicObject) typeOf() String {
 | 
			
		||||
	return stringObjectC
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) sortLen() int {
 | 
			
		||||
	return a.a.Len()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) sortGet(i int) Value {
 | 
			
		||||
	return a.a.Get(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) swap(i int, j int) {
 | 
			
		||||
	x := a.sortGet(i)
 | 
			
		||||
	y := a.sortGet(j)
 | 
			
		||||
	a.a.Set(int(i), y)
 | 
			
		||||
	a.a.Set(int(j), x)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) className() string {
 | 
			
		||||
	return classArray
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) getStr(p unistring.String, receiver Value) Value {
 | 
			
		||||
	if p == "length" {
 | 
			
		||||
		return intToValue(int64(a.a.Len()))
 | 
			
		||||
	}
 | 
			
		||||
	if idx, ok := strToInt(p); ok {
 | 
			
		||||
		return a.a.Get(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return a.getParentStr(p, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) getIdx(p valueInt, receiver Value) Value {
 | 
			
		||||
	if val := a.getOwnPropIdx(p); val != nil {
 | 
			
		||||
		return val
 | 
			
		||||
	}
 | 
			
		||||
	return a.getParentIdx(p, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) getOwnPropStr(u unistring.String) Value {
 | 
			
		||||
	if u == "length" {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:    intToValue(int64(a.a.Len())),
 | 
			
		||||
			writable: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if idx, ok := strToInt(u); ok {
 | 
			
		||||
		return a.a.Get(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) getOwnPropIdx(v valueInt) Value {
 | 
			
		||||
	return a.a.Get(toIntStrict(int64(v)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) _setLen(v Value, throw bool) bool {
 | 
			
		||||
	if a.a.SetLen(toIntStrict(v.ToInteger())) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	typeErrorResult(throw, "'SetLen' on a dynamic array returned false")
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) setOwnStr(p unistring.String, v Value, throw bool) bool {
 | 
			
		||||
	if p == "length" {
 | 
			
		||||
		return a._setLen(v, throw)
 | 
			
		||||
	}
 | 
			
		||||
	if idx, ok := strToInt(p); ok {
 | 
			
		||||
		return a._setIdx(idx, v, throw)
 | 
			
		||||
	}
 | 
			
		||||
	typeErrorResult(throw, "Cannot set property %q on a dynamic array", p.String())
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) _setIdx(idx int, v Value, throw bool) bool {
 | 
			
		||||
	if a.a.Set(idx, v) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	typeErrorResult(throw, "'Set' on a dynamic array returned false")
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) setOwnIdx(p valueInt, v Value, throw bool) bool {
 | 
			
		||||
	return a._setIdx(toIntStrict(int64(p)), v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	return a.setParentForeignStr(p, v, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
 | 
			
		||||
	return a.setParentForeignIdx(p, v, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) hasPropertyStr(u unistring.String) bool {
 | 
			
		||||
	if a.hasOwnPropertyStr(u) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if proto := a.prototype; proto != nil {
 | 
			
		||||
		return proto.self.hasPropertyStr(u)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) hasPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if a.hasOwnPropertyIdx(idx) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if proto := a.prototype; proto != nil {
 | 
			
		||||
		return proto.self.hasPropertyIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) _has(idx int) bool {
 | 
			
		||||
	return idx >= 0 && idx < a.a.Len()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) hasOwnPropertyStr(u unistring.String) bool {
 | 
			
		||||
	if u == "length" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if idx, ok := strToInt(u); ok {
 | 
			
		||||
		return a._has(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) hasOwnPropertyIdx(v valueInt) bool {
 | 
			
		||||
	return a._has(toIntStrict(int64(v)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if a.checkDynamicObjectPropertyDescr(name, desc, throw) {
 | 
			
		||||
		if idx, ok := strToInt(name); ok {
 | 
			
		||||
			return a._setIdx(idx, desc.Value, throw)
 | 
			
		||||
		}
 | 
			
		||||
		typeErrorResult(throw, "Cannot define property %q on a dynamic array", name.String())
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if a.checkDynamicObjectPropertyDescr(name, desc, throw) {
 | 
			
		||||
		return a._setIdx(toIntStrict(int64(name)), desc.Value, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) _delete(idx int, throw bool) bool {
 | 
			
		||||
	if a._has(idx) {
 | 
			
		||||
		a._setIdx(idx, _undefined, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if idx, ok := strToInt(name); ok {
 | 
			
		||||
		return a._delete(idx, throw)
 | 
			
		||||
	}
 | 
			
		||||
	if a.hasOwnPropertyStr(name) {
 | 
			
		||||
		typeErrorResult(throw, "Cannot delete property %q on a dynamic array", name.String())
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) deleteIdx(idx valueInt, throw bool) bool {
 | 
			
		||||
	return a._delete(toIntStrict(int64(idx)), throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dynArrayPropIter struct {
 | 
			
		||||
	a          DynamicArray
 | 
			
		||||
	idx, limit int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *dynArrayPropIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	if i.idx < i.limit && i.idx < i.a.Len() {
 | 
			
		||||
		name := strconv.Itoa(i.idx)
 | 
			
		||||
		i.idx++
 | 
			
		||||
		return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return propIterItem{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&dynArrayPropIter{
 | 
			
		||||
		a:     a.a,
 | 
			
		||||
		limit: a.a.Len(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) iterateKeys() iterNextFunc {
 | 
			
		||||
	return a.iterateStringKeys()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) export(ctx *objectExportCtx) interface{} {
 | 
			
		||||
	return a.a
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) exportType() reflect.Type {
 | 
			
		||||
	return reflect.TypeOf(a.a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) equal(impl objectImpl) bool {
 | 
			
		||||
	if other, ok := impl.(*dynamicArray); ok {
 | 
			
		||||
		return a == other
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	al := a.a.Len()
 | 
			
		||||
	l := len(accum) + al
 | 
			
		||||
	if all {
 | 
			
		||||
		l++
 | 
			
		||||
	}
 | 
			
		||||
	if l > cap(accum) {
 | 
			
		||||
		oldAccum := accum
 | 
			
		||||
		accum = make([]Value, len(oldAccum), l)
 | 
			
		||||
		copy(accum, oldAccum)
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < al; i++ {
 | 
			
		||||
		accum = append(accum, asciiString(strconv.Itoa(i)))
 | 
			
		||||
	}
 | 
			
		||||
	if all {
 | 
			
		||||
		accum = append(accum, asciiString("length"))
 | 
			
		||||
	}
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dynamicArray) keys(all bool, accum []Value) []Value {
 | 
			
		||||
	return a.stringKeys(all, accum)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										420
									
								
								goja/object_dynamic_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								goja/object_dynamic_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,420 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type testDynObject struct {
 | 
			
		||||
	r *Runtime
 | 
			
		||||
	m map[string]Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynObject) Get(key string) Value {
 | 
			
		||||
	return t.m[key]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynObject) Set(key string, val Value) bool {
 | 
			
		||||
	t.m[key] = val
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynObject) Has(key string) bool {
 | 
			
		||||
	_, exists := t.m[key]
 | 
			
		||||
	return exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynObject) Delete(key string) bool {
 | 
			
		||||
	delete(t.m, key)
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynObject) Keys() []string {
 | 
			
		||||
	keys := make([]string, 0, len(t.m))
 | 
			
		||||
	for k := range t.m {
 | 
			
		||||
		keys = append(keys, k)
 | 
			
		||||
	}
 | 
			
		||||
	return keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testDynArray struct {
 | 
			
		||||
	r *Runtime
 | 
			
		||||
	a []Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynArray) Len() int {
 | 
			
		||||
	return len(t.a)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynArray) Get(idx int) Value {
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		idx += len(t.a)
 | 
			
		||||
	}
 | 
			
		||||
	if idx >= 0 && idx < len(t.a) {
 | 
			
		||||
		return t.a[idx]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynArray) expand(newLen int) {
 | 
			
		||||
	if newLen > cap(t.a) {
 | 
			
		||||
		a := make([]Value, newLen)
 | 
			
		||||
		copy(a, t.a)
 | 
			
		||||
		t.a = a
 | 
			
		||||
	} else {
 | 
			
		||||
		t.a = t.a[:newLen]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynArray) Set(idx int, val Value) bool {
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		idx += len(t.a)
 | 
			
		||||
	}
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if idx >= len(t.a) {
 | 
			
		||||
		t.expand(idx + 1)
 | 
			
		||||
	}
 | 
			
		||||
	t.a[idx] = val
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testDynArray) SetLen(i int) bool {
 | 
			
		||||
	if i > len(t.a) {
 | 
			
		||||
		t.expand(i)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if i < 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if i < len(t.a) {
 | 
			
		||||
		tail := t.a[i:len(t.a)]
 | 
			
		||||
		for j := range tail {
 | 
			
		||||
			tail[j] = nil
 | 
			
		||||
		}
 | 
			
		||||
		t.a = t.a[:i]
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDynamicObject(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	dynObj := &testDynObject{
 | 
			
		||||
		r: vm,
 | 
			
		||||
		m: make(map[string]Value),
 | 
			
		||||
	}
 | 
			
		||||
	o := vm.NewDynamicObject(dynObj)
 | 
			
		||||
	vm.Set("o", o)
 | 
			
		||||
	vm.testScriptWithTestLibX(`
 | 
			
		||||
	assert(o instanceof Object, "instanceof Object");
 | 
			
		||||
	assert(o === o, "self equality");
 | 
			
		||||
	assert(o !== {}, "non-equality");
 | 
			
		||||
 | 
			
		||||
	o.test = 42;
 | 
			
		||||
	assert("test" in o, "'test' in o");
 | 
			
		||||
	assert(deepEqual(Object.getOwnPropertyDescriptor(o, "test"), {value: 42, writable: true, enumerable: true, configurable: true}), "prop desc");
 | 
			
		||||
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		"use strict";
 | 
			
		||||
		Object.defineProperty(o, "test1", {value: 0, writable: false, enumerable: false, configurable: true});
 | 
			
		||||
	}, "define prop");
 | 
			
		||||
 | 
			
		||||
	var keys = [];
 | 
			
		||||
	for (var key in o) {
 | 
			
		||||
		keys.push(key);
 | 
			
		||||
	}
 | 
			
		||||
	assert(compareArray(keys, ["test"]), "for-in");
 | 
			
		||||
 | 
			
		||||
	assert(delete o.test, "delete");
 | 
			
		||||
	assert(!("test" in o), "'test' in o after delete");
 | 
			
		||||
 | 
			
		||||
	assert("__proto__" in o, "__proto__ in o");
 | 
			
		||||
	assert.sameValue(o.__proto__, Object.prototype, "__proto__");
 | 
			
		||||
	o.__proto__ = null;
 | 
			
		||||
	assert(!("__proto__" in o), "__proto__ in o after setting to null");
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDynamicObjectCustomProto(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := make(map[string]Value)
 | 
			
		||||
	dynObj := &testDynObject{
 | 
			
		||||
		r: vm,
 | 
			
		||||
		m: m,
 | 
			
		||||
	}
 | 
			
		||||
	o := vm.NewDynamicObject(dynObj)
 | 
			
		||||
	vm.Set("o", o)
 | 
			
		||||
	vm.testScriptWithTestLib(`
 | 
			
		||||
	var proto = {
 | 
			
		||||
		valueOf: function() {
 | 
			
		||||
			return this.num;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	proto[Symbol.toStringTag] = "GoObject";
 | 
			
		||||
	Object.setPrototypeOf(o, proto);
 | 
			
		||||
	o.num = 41;
 | 
			
		||||
	assert(o instanceof Object, "instanceof");
 | 
			
		||||
	assert.sameValue(o+1, 42);
 | 
			
		||||
	assert.sameValue(o.toString(), "[object GoObject]");
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
 | 
			
		||||
	if v := m["num"]; v.Export() != int64(41) {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDynamicArray(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	dynObj := &testDynArray{
 | 
			
		||||
		r: vm,
 | 
			
		||||
	}
 | 
			
		||||
	a := vm.NewDynamicArray(dynObj)
 | 
			
		||||
	vm.Set("a", a)
 | 
			
		||||
	vm.testScriptWithTestLibX(`
 | 
			
		||||
	assert(a instanceof Array, "instanceof Array");
 | 
			
		||||
	assert(a instanceof Object, "instanceof Object");
 | 
			
		||||
	assert(a === a, "self equality");
 | 
			
		||||
	assert(a !== [], "non-equality");
 | 
			
		||||
	assert(Array.isArray(a), "isArray()");
 | 
			
		||||
	assert("length" in a, "length in a");
 | 
			
		||||
	assert.sameValue(a.length, 0, "len == 0");
 | 
			
		||||
	assert.sameValue(a[0], undefined, "a[0] (1)");
 | 
			
		||||
 | 
			
		||||
	a[0] = 0;
 | 
			
		||||
	assert.sameValue(a[0], 0, "a[0] (2)");
 | 
			
		||||
	assert.sameValue(a.length, 1, "length");
 | 
			
		||||
	assert(deepEqual(Object.getOwnPropertyDescriptor(a, 0), {value: 0, writable: true, enumerable: true, configurable: true}), "prop desc");
 | 
			
		||||
	assert(deepEqual(Object.getOwnPropertyDescriptor(a, "length"), {value: 1, writable: true, enumerable: false, configurable: false}), "length prop desc");
 | 
			
		||||
 | 
			
		||||
	assert("__proto__" in a, "__proto__ in a");
 | 
			
		||||
	assert.sameValue(a.__proto__, Array.prototype, "__proto__");
 | 
			
		||||
 | 
			
		||||
	assert(compareArray(Object.keys(a), ["0"]), "Object.keys()");
 | 
			
		||||
	assert(compareArray(Reflect.ownKeys(a), ["0", "length"]), "Reflect.ownKeys()");
 | 
			
		||||
 | 
			
		||||
	a.length = 2;
 | 
			
		||||
	assert.sameValue(a.length, 2, "length after grow");
 | 
			
		||||
	assert.sameValue(a[1], undefined, "a[1]");
 | 
			
		||||
 | 
			
		||||
	a[1] = 1;
 | 
			
		||||
	assert.sameValue(a[1], 1, "a[1] after set");
 | 
			
		||||
	a.length = 1;
 | 
			
		||||
	assert.sameValue(a.length, 1, "length after shrink");
 | 
			
		||||
	assert.sameValue(a[1], undefined, "a[1] after shrink");
 | 
			
		||||
	a.length = 2;
 | 
			
		||||
	assert.sameValue(a.length, 2, "length after shrink and grow");
 | 
			
		||||
	assert.sameValue(a[1], undefined, "a[1] after grow");
 | 
			
		||||
 | 
			
		||||
	a[0] = 3; a[1] = 1; a[2] = 2;
 | 
			
		||||
	assert.sameValue(a.length, 3);
 | 
			
		||||
	var keys = [];
 | 
			
		||||
	for (var key in a) {
 | 
			
		||||
		keys.push(key);
 | 
			
		||||
	}
 | 
			
		||||
	assert(compareArray(keys, ["0","1","2"]), "for-in");
 | 
			
		||||
 | 
			
		||||
	var vals = [];
 | 
			
		||||
	for (var val of a) {
 | 
			
		||||
		vals.push(val);
 | 
			
		||||
	}
 | 
			
		||||
	assert(compareArray(vals, [3,1,2]), "for-of");
 | 
			
		||||
 | 
			
		||||
	a.sort();
 | 
			
		||||
	assert(compareArray(a, [1,2,3]), "sort: "+a);
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(a[-1], 3);
 | 
			
		||||
	assert.sameValue(a[-4], undefined);
 | 
			
		||||
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		"use strict";
 | 
			
		||||
		delete a.length;
 | 
			
		||||
	}, "delete length");
 | 
			
		||||
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		"use strict";
 | 
			
		||||
		a.test = true;
 | 
			
		||||
	}, "set string prop");
 | 
			
		||||
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		"use strict";
 | 
			
		||||
		Object.defineProperty(a, 0, {value: 0, writable: false, enumerable: false, configurable: true});
 | 
			
		||||
	}, "define prop");
 | 
			
		||||
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testSharedDynObject struct {
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	m map[string]Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynObject) Get(key string) Value {
 | 
			
		||||
	t.RLock()
 | 
			
		||||
	val := t.m[key]
 | 
			
		||||
	t.RUnlock()
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynObject) Set(key string, val Value) bool {
 | 
			
		||||
	t.Lock()
 | 
			
		||||
	t.m[key] = val
 | 
			
		||||
	t.Unlock()
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynObject) Has(key string) bool {
 | 
			
		||||
	t.RLock()
 | 
			
		||||
	_, exists := t.m[key]
 | 
			
		||||
	t.RUnlock()
 | 
			
		||||
	return exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynObject) Delete(key string) bool {
 | 
			
		||||
	t.Lock()
 | 
			
		||||
	delete(t.m, key)
 | 
			
		||||
	t.Unlock()
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynObject) Keys() []string {
 | 
			
		||||
	t.RLock()
 | 
			
		||||
	keys := make([]string, 0, len(t.m))
 | 
			
		||||
	for k := range t.m {
 | 
			
		||||
		keys = append(keys, k)
 | 
			
		||||
	}
 | 
			
		||||
	t.RUnlock()
 | 
			
		||||
	return keys
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSharedDynamicObject(t *testing.T) {
 | 
			
		||||
	dynObj := &testSharedDynObject{m: make(map[string]Value, 10000)}
 | 
			
		||||
	o := NewSharedDynamicObject(dynObj)
 | 
			
		||||
	ch := make(chan error, 1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		vm := New()
 | 
			
		||||
		vm.Set("o", o)
 | 
			
		||||
		_, err := vm.RunString(`
 | 
			
		||||
			for (let i = 0; i < 10000; i++) {
 | 
			
		||||
				o[i] = i;
 | 
			
		||||
			}
 | 
			
		||||
		`)
 | 
			
		||||
		ch <- err
 | 
			
		||||
	}()
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("o", o)
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
			for (let i = 0; i < 10000; i++) {
 | 
			
		||||
				o[i] = i+1;
 | 
			
		||||
			}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = <-ch
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testSharedDynArray struct {
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	a []Value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynArray) Len() int {
 | 
			
		||||
	t.RLock()
 | 
			
		||||
	l := len(t.a)
 | 
			
		||||
	t.RUnlock()
 | 
			
		||||
	return l
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynArray) Get(idx int) Value {
 | 
			
		||||
	t.RLock()
 | 
			
		||||
	defer t.RUnlock()
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		idx += len(t.a)
 | 
			
		||||
	}
 | 
			
		||||
	if idx >= 0 && idx < len(t.a) {
 | 
			
		||||
		return t.a[idx]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynArray) expand(newLen int) {
 | 
			
		||||
	if newLen > cap(t.a) {
 | 
			
		||||
		a := make([]Value, newLen)
 | 
			
		||||
		copy(a, t.a)
 | 
			
		||||
		t.a = a
 | 
			
		||||
	} else {
 | 
			
		||||
		t.a = t.a[:newLen]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynArray) Set(idx int, val Value) bool {
 | 
			
		||||
	t.Lock()
 | 
			
		||||
	defer t.Unlock()
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		idx += len(t.a)
 | 
			
		||||
	}
 | 
			
		||||
	if idx < 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if idx >= len(t.a) {
 | 
			
		||||
		t.expand(idx + 1)
 | 
			
		||||
	}
 | 
			
		||||
	t.a[idx] = val
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *testSharedDynArray) SetLen(i int) bool {
 | 
			
		||||
	t.Lock()
 | 
			
		||||
	defer t.Unlock()
 | 
			
		||||
	if i > len(t.a) {
 | 
			
		||||
		t.expand(i)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if i < 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if i < len(t.a) {
 | 
			
		||||
		tail := t.a[i:len(t.a)]
 | 
			
		||||
		for j := range tail {
 | 
			
		||||
			tail[j] = nil
 | 
			
		||||
		}
 | 
			
		||||
		t.a = t.a[:i]
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSharedDynamicArray(t *testing.T) {
 | 
			
		||||
	dynObj := &testSharedDynArray{a: make([]Value, 10000)}
 | 
			
		||||
	o := NewSharedDynamicArray(dynObj)
 | 
			
		||||
	ch := make(chan error, 1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		vm := New()
 | 
			
		||||
		vm.Set("o", o)
 | 
			
		||||
		_, err := vm.RunString(`
 | 
			
		||||
			for (let i = 0; i < 10000; i++) {
 | 
			
		||||
				o[i] = i;
 | 
			
		||||
			}
 | 
			
		||||
		`)
 | 
			
		||||
		ch <- err
 | 
			
		||||
	}()
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("o", o)
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
			for (let i = 0; i < 10000; i++) {
 | 
			
		||||
				o[i] = i+1;
 | 
			
		||||
			}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = <-ch
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										358
									
								
								goja/object_goarray_reflect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								goja/object_goarray_reflect.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,358 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type objectGoArrayReflect struct {
 | 
			
		||||
	objectGoReflect
 | 
			
		||||
	lengthProp valueProperty
 | 
			
		||||
 | 
			
		||||
	valueCache valueArrayCache
 | 
			
		||||
 | 
			
		||||
	putIdx func(idx int, v Value, throw bool) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type valueArrayCache []reflectValueWrapper
 | 
			
		||||
 | 
			
		||||
func (c *valueArrayCache) get(idx int) reflectValueWrapper {
 | 
			
		||||
	if idx < len(*c) {
 | 
			
		||||
		return (*c)[idx]
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *valueArrayCache) grow(newlen int) {
 | 
			
		||||
	oldcap := cap(*c)
 | 
			
		||||
	if oldcap < newlen {
 | 
			
		||||
		a := make([]reflectValueWrapper, newlen, growCap(newlen, len(*c), oldcap))
 | 
			
		||||
		copy(a, *c)
 | 
			
		||||
		*c = a
 | 
			
		||||
	} else {
 | 
			
		||||
		*c = (*c)[:newlen]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *valueArrayCache) put(idx int, w reflectValueWrapper) {
 | 
			
		||||
	if len(*c) <= idx {
 | 
			
		||||
		c.grow(idx + 1)
 | 
			
		||||
	}
 | 
			
		||||
	(*c)[idx] = w
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *valueArrayCache) shrink(newlen int) {
 | 
			
		||||
	if len(*c) > newlen {
 | 
			
		||||
		tail := (*c)[newlen:]
 | 
			
		||||
		for i, item := range tail {
 | 
			
		||||
			if item != nil {
 | 
			
		||||
				copyReflectValueWrapper(item)
 | 
			
		||||
				tail[i] = nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		*c = (*c)[:newlen]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) _init() {
 | 
			
		||||
	o.objectGoReflect.init()
 | 
			
		||||
	o.class = classArray
 | 
			
		||||
	o.prototype = o.val.runtime.getArrayPrototype()
 | 
			
		||||
	o.baseObject._put("length", &o.lengthProp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) init() {
 | 
			
		||||
	o._init()
 | 
			
		||||
	o.updateLen()
 | 
			
		||||
	o.putIdx = o._putIdx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) updateLen() {
 | 
			
		||||
	o.lengthProp.value = intToValue(int64(o.fieldsValue.Len()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) _hasIdx(idx valueInt) bool {
 | 
			
		||||
	if idx := int64(idx); idx >= 0 && idx < int64(o.fieldsValue.Len()) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) _hasStr(name unistring.String) bool {
 | 
			
		||||
	if idx := strToIdx64(name); idx >= 0 && idx < int64(o.fieldsValue.Len()) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) _getIdx(idx int) Value {
 | 
			
		||||
	if v := o.valueCache.get(idx); v != nil {
 | 
			
		||||
		return v.esValue()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v := o.fieldsValue.Index(idx)
 | 
			
		||||
 | 
			
		||||
	res, w := o.elemToValue(v)
 | 
			
		||||
	if w != nil {
 | 
			
		||||
		o.valueCache.put(idx, w)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) getIdx(idx valueInt, receiver Value) Value {
 | 
			
		||||
	if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.fieldsValue.Len() {
 | 
			
		||||
		return o._getIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.getStr(idx.string(), receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	var ownProp Value
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 && idx < o.fieldsValue.Len() {
 | 
			
		||||
		ownProp = o._getIdx(idx)
 | 
			
		||||
	} else if name == "length" {
 | 
			
		||||
		if o.fieldsValue.Kind() == reflect.Slice {
 | 
			
		||||
			o.updateLen()
 | 
			
		||||
		}
 | 
			
		||||
		ownProp = &o.lengthProp
 | 
			
		||||
	} else {
 | 
			
		||||
		ownProp = o.objectGoReflect.getOwnPropStr(name)
 | 
			
		||||
	}
 | 
			
		||||
	return o.getStrWithOwnProp(ownProp, name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		if idx < o.fieldsValue.Len() {
 | 
			
		||||
			return &valueProperty{
 | 
			
		||||
				value:      o._getIdx(idx),
 | 
			
		||||
				writable:   true,
 | 
			
		||||
				enumerable: true,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		if o.fieldsValue.Kind() == reflect.Slice {
 | 
			
		||||
			o.updateLen()
 | 
			
		||||
		}
 | 
			
		||||
		return &o.lengthProp
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.getOwnPropStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) getOwnPropIdx(idx valueInt) Value {
 | 
			
		||||
	if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.fieldsValue.Len() {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:      o._getIdx(idx),
 | 
			
		||||
			writable:   true,
 | 
			
		||||
			enumerable: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) _putIdx(idx int, v Value, throw bool) bool {
 | 
			
		||||
	cached := o.valueCache.get(idx)
 | 
			
		||||
	if cached != nil {
 | 
			
		||||
		copyReflectValueWrapper(cached)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rv := o.fieldsValue.Index(idx)
 | 
			
		||||
	err := o.val.runtime.toReflectValue(v, rv, &objectExportCtx{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if cached != nil {
 | 
			
		||||
			cached.setReflectValue(rv)
 | 
			
		||||
		}
 | 
			
		||||
		o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if cached != nil {
 | 
			
		||||
		o.valueCache[idx] = nil
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 | 
			
		||||
	if i := toIntStrict(int64(idx)); i >= 0 {
 | 
			
		||||
		if i >= o.fieldsValue.Len() {
 | 
			
		||||
			if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return o.putIdx(i, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		name := idx.string()
 | 
			
		||||
		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		if idx >= o.fieldsValue.Len() {
 | 
			
		||||
			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return o.putIdx(idx, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) hasOwnPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	return o._hasIdx(idx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if o._hasStr(name) || name == "length" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.hasOwnPropertyStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if i := toIntStrict(int64(idx)); i >= 0 {
 | 
			
		||||
		if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		val := descr.Value
 | 
			
		||||
		if val == nil {
 | 
			
		||||
			val = _undefined
 | 
			
		||||
		}
 | 
			
		||||
		return o.putIdx(i, val, throw)
 | 
			
		||||
	}
 | 
			
		||||
	o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		val := descr.Value
 | 
			
		||||
		if val == nil {
 | 
			
		||||
			val = _undefined
 | 
			
		||||
		}
 | 
			
		||||
		return o.putIdx(idx, val, throw)
 | 
			
		||||
	}
 | 
			
		||||
	o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) _deleteIdx(idx int) {
 | 
			
		||||
	if idx < o.fieldsValue.Len() {
 | 
			
		||||
		if cv := o.valueCache.get(idx); cv != nil {
 | 
			
		||||
			copyReflectValueWrapper(cv)
 | 
			
		||||
			o.valueCache[idx] = nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		o.fieldsValue.Index(idx).Set(reflect.Zero(o.fieldsValue.Type().Elem()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		o._deleteIdx(idx)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.objectGoReflect.deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) deleteIdx(i valueInt, throw bool) bool {
 | 
			
		||||
	idx := toIntStrict(int64(i))
 | 
			
		||||
	if idx >= 0 {
 | 
			
		||||
		o._deleteIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type goArrayReflectPropIter struct {
 | 
			
		||||
	o          *objectGoArrayReflect
 | 
			
		||||
	idx, limit int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *goArrayReflectPropIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	if i.idx < i.limit && i.idx < i.o.fieldsValue.Len() {
 | 
			
		||||
		name := strconv.Itoa(i.idx)
 | 
			
		||||
		i.idx++
 | 
			
		||||
		return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return i.o.objectGoReflect.iterateStringKeys()()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) stringKeys(all bool, accum []Value) []Value {
 | 
			
		||||
	for i := 0; i < o.fieldsValue.Len(); i++ {
 | 
			
		||||
		accum = append(accum, asciiString(strconv.Itoa(i)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.objectGoReflect.stringKeys(all, accum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&goArrayReflectPropIter{
 | 
			
		||||
		o:     o,
 | 
			
		||||
		limit: o.fieldsValue.Len(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) sortLen() int {
 | 
			
		||||
	return o.fieldsValue.Len()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) sortGet(i int) Value {
 | 
			
		||||
	return o.getIdx(valueInt(i), nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoArrayReflect) swap(i int, j int) {
 | 
			
		||||
	vi := o.fieldsValue.Index(i)
 | 
			
		||||
	vj := o.fieldsValue.Index(j)
 | 
			
		||||
	tmp := reflect.New(o.fieldsValue.Type().Elem()).Elem()
 | 
			
		||||
	tmp.Set(vi)
 | 
			
		||||
	vi.Set(vj)
 | 
			
		||||
	vj.Set(tmp)
 | 
			
		||||
 | 
			
		||||
	cachedI := o.valueCache.get(i)
 | 
			
		||||
	cachedJ := o.valueCache.get(j)
 | 
			
		||||
	if cachedI != nil {
 | 
			
		||||
		cachedI.setReflectValue(vj)
 | 
			
		||||
		o.valueCache.put(j, cachedI)
 | 
			
		||||
	} else {
 | 
			
		||||
		if j < len(o.valueCache) {
 | 
			
		||||
			o.valueCache[j] = nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cachedJ != nil {
 | 
			
		||||
		cachedJ.setReflectValue(vi)
 | 
			
		||||
		o.valueCache.put(i, cachedJ)
 | 
			
		||||
	} else {
 | 
			
		||||
		if i < len(o.valueCache) {
 | 
			
		||||
			o.valueCache[i] = nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										304
									
								
								goja/object_goarray_reflect_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								goja/object_goarray_reflect_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,304 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGoReflectArray(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("a", [...]int{1, 2, 3})
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
	if (!Array.isArray(a)) {
 | 
			
		||||
		throw new Error("isArray() returned false");
 | 
			
		||||
	}
 | 
			
		||||
	if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
 | 
			
		||||
		throw new Error("Array contents is incorrect");
 | 
			
		||||
	}
 | 
			
		||||
	if (!a.hasOwnProperty("length")) {
 | 
			
		||||
		throw new Error("hasOwnProperty() returned false");
 | 
			
		||||
	}
 | 
			
		||||
	let desc = Object.getOwnPropertyDescriptor(a, "length");
 | 
			
		||||
	if (desc.value !== 3 || desc.writable || desc.enumerable || desc.configurable) {
 | 
			
		||||
		throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoReflectArraySort(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("a", [...]int{3, 1, 2})
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
		a.sort();
 | 
			
		||||
		if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
 | 
			
		||||
			throw new Error(a.toString());
 | 
			
		||||
		}
 | 
			
		||||
		a;
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	res := v.Export()
 | 
			
		||||
	if a, ok := res.([3]int); ok {
 | 
			
		||||
		if a[0] != 1 || a[1] != 2 || a[2] != 3 {
 | 
			
		||||
			t.Fatal(a)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Wrong type: %T", res)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoReflectArrayCopyOnChange(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
	a => {
 | 
			
		||||
		let tmp = a[0];
 | 
			
		||||
		if (tmp !== a[0]) {
 | 
			
		||||
			throw new Error("tmp !== a[0]");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a[0] = a[1];
 | 
			
		||||
		if (tmp === a[0]) {
 | 
			
		||||
			throw new Error("tmp === a[0]");
 | 
			
		||||
		}
 | 
			
		||||
		if (tmp.Test !== "1") {
 | 
			
		||||
			throw new Error("tmp.Test: " + tmp.Test + " (" + typeof tmp.Test + ")");
 | 
			
		||||
		}
 | 
			
		||||
		if (a[0].Test !== "2") {
 | 
			
		||||
			throw new Error("a[0].Test: " + a[0].Test);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a[0].Test = "3";
 | 
			
		||||
		if (a[0].Test !== "3") {
 | 
			
		||||
			throw new Error("a[0].Test (1): " + a[0].Test);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tmp = a[0];
 | 
			
		||||
		tmp.Test = "4";
 | 
			
		||||
		if (a[0].Test !== "4") {
 | 
			
		||||
			throw new Error("a[0].Test (2): " + a[0].Test);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		delete a[0];
 | 
			
		||||
		if (a[0] && a[0].Test !== "") {
 | 
			
		||||
			throw new Error("a[0].Test (3): " + a[0].Test);
 | 
			
		||||
		}
 | 
			
		||||
		if (tmp.Test !== "4") {
 | 
			
		||||
			throw new Error("tmp.Test (1): " + tmp.Test);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a[1] = tmp;
 | 
			
		||||
		if (a[1].Test !== "4") {
 | 
			
		||||
			throw new Error("a[1].Test: " + a[1].Test);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        // grow
 | 
			
		||||
		tmp = a[1];
 | 
			
		||||
		a.push(null);
 | 
			
		||||
		if (a.length !== 3) {
 | 
			
		||||
			throw new Error("a.length after push: " + a.length);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tmp.Test = "5";
 | 
			
		||||
		if (a[1].Test !== "5") {
 | 
			
		||||
			throw new Error("a[1].Test (1): " + a[1].Test);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// shrink
 | 
			
		||||
		a.length = 1;
 | 
			
		||||
		if (a.length !== 1) {
 | 
			
		||||
			throw new Error("a.length after shrink: " + a.length);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (tmp.Test !== "5") {
 | 
			
		||||
			throw new Error("tmp.Test (shrink): " + tmp.Test);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn, ok := AssertFunction(v)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Fatal("Not a function")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("[]struct", func(t *testing.T) {
 | 
			
		||||
		a := []struct {
 | 
			
		||||
			Test string
 | 
			
		||||
		}{{"1"}, {"2"}}
 | 
			
		||||
		_, err := fn(nil, vm.ToValue(a))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if a[0].Test != "" {
 | 
			
		||||
			t.Fatalf("a[0]: %#v", a[0])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if a[1].Test != "4" {
 | 
			
		||||
			t.Fatalf("a0[1]: %#v", a[1])
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// The copy-on-change mechanism doesn't apply to the types below because the contained values are references.
 | 
			
		||||
	// These tests are here for completeness and to prove that the behaviour is consistent.
 | 
			
		||||
 | 
			
		||||
	t.Run("[]I", func(t *testing.T) {
 | 
			
		||||
		type I interface {
 | 
			
		||||
			Get() string
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := []I{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}}
 | 
			
		||||
 | 
			
		||||
		_, err = fn(nil, vm.ToValue(a))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("[]interface{}", func(t *testing.T) {
 | 
			
		||||
		a := []interface{}{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}}
 | 
			
		||||
 | 
			
		||||
		_, err = fn(nil, vm.ToValue(a))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCopyOnChangeReflectSlice(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
	s => {
 | 
			
		||||
		s.A.push(1);
 | 
			
		||||
		if (s.A.length !== 1) {
 | 
			
		||||
			throw new Error("s.A.length: " + s.A.length);
 | 
			
		||||
		}
 | 
			
		||||
		if (s.A[0] !== 1) {
 | 
			
		||||
			throw new Error("s.A[0]: " + s.A[0]);
 | 
			
		||||
		}
 | 
			
		||||
		let tmp = s.A;
 | 
			
		||||
		if (tmp !== s.A) {
 | 
			
		||||
			throw new Error("tmp !== s.A");
 | 
			
		||||
		}
 | 
			
		||||
		s.A = [2];
 | 
			
		||||
		if (tmp === s.A) {
 | 
			
		||||
			throw new Error("tmp === s.A");
 | 
			
		||||
		}
 | 
			
		||||
		if (tmp[0] !== 1) {
 | 
			
		||||
			throw new Error("tmp[0]: " + tmp[0]);
 | 
			
		||||
		}
 | 
			
		||||
		if (s.A[0] !== 2) {
 | 
			
		||||
			throw new Error("s.A[0] (1): " + s.A[0]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	fn, ok := AssertFunction(v)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Fatal("Not a function")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("[]int", func(t *testing.T) {
 | 
			
		||||
		type S struct {
 | 
			
		||||
			A []int
 | 
			
		||||
		}
 | 
			
		||||
		var s S
 | 
			
		||||
		_, err := fn(nil, vm.ToValue(&s))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if len(s.A) != 1 {
 | 
			
		||||
			t.Fatal(s)
 | 
			
		||||
		}
 | 
			
		||||
		if s.A[0] != 2 {
 | 
			
		||||
			t.Fatal(s.A)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("[]interface{}", func(t *testing.T) {
 | 
			
		||||
		type S struct {
 | 
			
		||||
			A []interface{}
 | 
			
		||||
		}
 | 
			
		||||
		var s S
 | 
			
		||||
		_, err := fn(nil, vm.ToValue(&s))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if len(s.A) != 1 {
 | 
			
		||||
			t.Fatal(s)
 | 
			
		||||
		}
 | 
			
		||||
		if s.A[0] != int64(2) {
 | 
			
		||||
			t.Fatal(s.A)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCopyOnChangeSort(t *testing.T) {
 | 
			
		||||
	a := []struct {
 | 
			
		||||
		Test string
 | 
			
		||||
	}{{"2"}, {"1"}}
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("a", &a)
 | 
			
		||||
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
		let a0 = a[0];
 | 
			
		||||
		let a1 = a[1];
 | 
			
		||||
		a.sort((a, b) => a.Test.localeCompare(b.Test));
 | 
			
		||||
		if (a[0].Test !== "1") {
 | 
			
		||||
			throw new Error("a[0]: " + a[0]);
 | 
			
		||||
		}
 | 
			
		||||
		if (a[1].Test !== "2") {
 | 
			
		||||
			throw new Error("a[1]: " + a[1]);
 | 
			
		||||
		}
 | 
			
		||||
		if (a0 !== a[1]) {
 | 
			
		||||
			throw new Error("a0 !== a[1]");
 | 
			
		||||
		}
 | 
			
		||||
		if (a1 !== a[0]) {
 | 
			
		||||
			throw new Error("a1 !== a[0]");
 | 
			
		||||
		}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if a[0].Test != "1" || a[1].Test != "2" {
 | 
			
		||||
		t.Fatal(a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testStringerArray [8]byte
 | 
			
		||||
 | 
			
		||||
func (a testStringerArray) String() string {
 | 
			
		||||
	return "X"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestReflectArrayToString(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	var a testStringerArray
 | 
			
		||||
	vm.Set("a", &a)
 | 
			
		||||
	res, err := vm.RunString("`${a}`")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if exp := res.Export(); exp != "X" {
 | 
			
		||||
		t.Fatal(exp)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var a1 [2]byte
 | 
			
		||||
	vm.Set("a", &a1)
 | 
			
		||||
	res, err = vm.RunString("`${a}`")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if exp := res.Export(); exp != "0,0" {
 | 
			
		||||
		t.Fatal(exp)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										158
									
								
								goja/object_gomap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								goja/object_gomap.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	o.extensible = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	if v := o._getStr(name.String()); v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject.getStr(name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if v := o._getStr(name.String()); v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	n := name.String()
 | 
			
		||||
	if _, exists := o.data[n]; exists {
 | 
			
		||||
		o.data[n] = val.Export()
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if proto := o.prototype; proto != nil {
 | 
			
		||||
		// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
		if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// new property
 | 
			
		||||
	if !o.extensible {
 | 
			
		||||
		o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
 | 
			
		||||
		return false
 | 
			
		||||
	} else {
 | 
			
		||||
		o.data[n] = val.Export()
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trueValIfPresent(present bool) Value {
 | 
			
		||||
	if present {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignStr(name, trueValIfPresent(o._hasStr(name.String())), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) _hasStr(name string) bool {
 | 
			
		||||
	_, exists := o.data[name]
 | 
			
		||||
	return exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	return o._hasStr(name.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	n := name.String()
 | 
			
		||||
	if o.extensible || o._hasStr(n) {
 | 
			
		||||
		o.data[n] = descr.Value.Export()
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", n)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) deleteStr(name unistring.String, _ bool) bool {
 | 
			
		||||
	delete(o.data, name.String())
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type gomapPropIter struct {
 | 
			
		||||
	o         *objectGoMapSimple
 | 
			
		||||
	propNames []string
 | 
			
		||||
	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: newStringValue(name), enumerable: _ENUM_TRUE}, i.next
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return propIterItem{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	propNames := make([]string, len(o.data))
 | 
			
		||||
	i := 0
 | 
			
		||||
	for key := range o.data {
 | 
			
		||||
		propNames[i] = key
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (&gomapPropIter{
 | 
			
		||||
		o:         o,
 | 
			
		||||
		propNames: propNames,
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) stringKeys(_ bool, accum []Value) []Value {
 | 
			
		||||
	// all own keys are enumerable
 | 
			
		||||
	for key := range o.data {
 | 
			
		||||
		accum = append(accum, newStringValue(key))
 | 
			
		||||
	}
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapSimple) export(*objectExportCtx) 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										294
									
								
								goja/object_gomap_reflect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								goja/object_gomap_reflect.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,294 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type objectGoMapReflect struct {
 | 
			
		||||
	objectGoReflect
 | 
			
		||||
 | 
			
		||||
	keyType, valueType reflect.Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) init() {
 | 
			
		||||
	o.objectGoReflect.init()
 | 
			
		||||
	o.keyType = o.fieldsValue.Type().Key()
 | 
			
		||||
	o.valueType = o.fieldsValue.Type().Elem()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) toKey(n Value, throw bool) reflect.Value {
 | 
			
		||||
	key := reflect.New(o.keyType).Elem()
 | 
			
		||||
	err := o.val.runtime.toReflectValue(n, key, &objectExportCtx{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.val.runtime.typeErrorResult(throw, "map key conversion error: %v", err)
 | 
			
		||||
		return reflect.Value{}
 | 
			
		||||
	}
 | 
			
		||||
	return key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) strToKey(name string, throw bool) reflect.Value {
 | 
			
		||||
	if o.keyType.Kind() == reflect.String {
 | 
			
		||||
		return reflect.ValueOf(name).Convert(o.keyType)
 | 
			
		||||
	}
 | 
			
		||||
	return o.toKey(newStringValue(name), throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) _getKey(key reflect.Value) Value {
 | 
			
		||||
	if !key.IsValid() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if v := o.fieldsValue.MapIndex(key); v.IsValid() {
 | 
			
		||||
		rv := v
 | 
			
		||||
		if rv.Kind() == reflect.Interface {
 | 
			
		||||
			rv = rv.Elem()
 | 
			
		||||
		}
 | 
			
		||||
		return o.val.runtime.toValue(v.Interface(), rv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) _get(n Value) Value {
 | 
			
		||||
	return o._getKey(o.toKey(n, false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) _getStr(name string) Value {
 | 
			
		||||
	return o._getKey(o.strToKey(name, false))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	if v := o._getStr(name.String()); v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.getStr(name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) getIdx(idx valueInt, receiver Value) Value {
 | 
			
		||||
	if v := o._get(idx); v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.getIdx(idx, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if v := o._getStr(name.String()); v != nil {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:      v,
 | 
			
		||||
			writable:   true,
 | 
			
		||||
			enumerable: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.getOwnPropStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value {
 | 
			
		||||
	if v := o._get(idx); v != nil {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:      v,
 | 
			
		||||
			writable:   true,
 | 
			
		||||
			enumerable: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoReflect.getOwnPropStr(idx.string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
 | 
			
		||||
	v := reflect.New(o.valueType).Elem()
 | 
			
		||||
	err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.val.runtime.typeErrorResult(throw, "map value conversion error: %v", err)
 | 
			
		||||
		return reflect.Value{}, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return v, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) _put(key reflect.Value, val Value, throw bool) bool {
 | 
			
		||||
	if key.IsValid() {
 | 
			
		||||
		if o.extensible || o.fieldsValue.MapIndex(key).IsValid() {
 | 
			
		||||
			v, ok := o.toValue(val, throw)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
			o.fieldsValue.SetMapIndex(key, v)
 | 
			
		||||
		} else {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Cannot set property %v, object is not extensible", key)
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	n := name.String()
 | 
			
		||||
	key := o.strToKey(n, false)
 | 
			
		||||
	if !key.IsValid() || !o.fieldsValue.MapIndex(key).IsValid() {
 | 
			
		||||
		if proto := o.prototype; proto != nil {
 | 
			
		||||
			// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
			if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// new property
 | 
			
		||||
		if !o.extensible {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", n)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			if throw && !key.IsValid() {
 | 
			
		||||
				o.strToKey(n, true)
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o._put(key, val, throw)
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 | 
			
		||||
	key := o.toKey(idx, false)
 | 
			
		||||
	if !key.IsValid() || !o.fieldsValue.MapIndex(key).IsValid() {
 | 
			
		||||
		if proto := o.prototype; proto != nil {
 | 
			
		||||
			// we know it's foreign because prototype loops are not allowed
 | 
			
		||||
			if res, ok := proto.self.setForeignIdx(idx, val, o.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// new property
 | 
			
		||||
		if !o.extensible {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			if throw && !key.IsValid() {
 | 
			
		||||
				o.toKey(idx, true)
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o._put(key, val, throw)
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o._put(o.strToKey(name.String(), throw), descr.Value, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o._put(o.toKey(idx, throw), descr.Value, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	key := o.strToKey(name.String(), false)
 | 
			
		||||
	if key.IsValid() && o.fieldsValue.MapIndex(key).IsValid() {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	key := o.toKey(idx, false)
 | 
			
		||||
	if key.IsValid() && o.fieldsValue.MapIndex(key).IsValid() {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	key := o.strToKey(name.String(), throw)
 | 
			
		||||
	if !key.IsValid() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	o.fieldsValue.SetMapIndex(key, reflect.Value{})
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) deleteIdx(idx valueInt, throw bool) bool {
 | 
			
		||||
	key := o.toKey(idx, throw)
 | 
			
		||||
	if !key.IsValid() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	o.fieldsValue.SetMapIndex(key, reflect.Value{})
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type gomapReflectPropIter struct {
 | 
			
		||||
	o    *objectGoMapReflect
 | 
			
		||||
	keys []reflect.Value
 | 
			
		||||
	idx  int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
 | 
			
		||||
	for i.idx < len(i.keys) {
 | 
			
		||||
		key := i.keys[i.idx]
 | 
			
		||||
		v := i.o.fieldsValue.MapIndex(key)
 | 
			
		||||
		i.idx++
 | 
			
		||||
		if v.IsValid() {
 | 
			
		||||
			return propIterItem{name: i.o.keyToString(key), enumerable: _ENUM_TRUE}, i.next
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return propIterItem{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&gomapReflectPropIter{
 | 
			
		||||
		o:    o,
 | 
			
		||||
		keys: o.fieldsValue.MapKeys(),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoMapReflect) stringKeys(_ bool, accum []Value) []Value {
 | 
			
		||||
	// all own keys are enumerable
 | 
			
		||||
	for _, key := range o.fieldsValue.MapKeys() {
 | 
			
		||||
		accum = append(accum, o.keyToString(key))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*objectGoMapReflect) keyToString(key reflect.Value) String {
 | 
			
		||||
	kind := key.Kind()
 | 
			
		||||
 | 
			
		||||
	if kind == reflect.String {
 | 
			
		||||
		return newStringValue(key.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	str := fmt.Sprintf("%v", key)
 | 
			
		||||
 | 
			
		||||
	switch kind {
 | 
			
		||||
	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:
 | 
			
		||||
		return asciiString(str)
 | 
			
		||||
	default:
 | 
			
		||||
		return newStringValue(str)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										350
									
								
								goja/object_gomap_reflect_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								goja/object_gomap_reflect_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,350 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectGetSet(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	m.c = m.a + m.b;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]string{
 | 
			
		||||
		"a": "4",
 | 
			
		||||
		"b": "2",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
 | 
			
		||||
	_, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c := m["c"]; c != "42" {
 | 
			
		||||
		t.Fatalf("Unexpected value: '%s'", c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectIntKey(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	m[2] = m[0] + m[1];
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[int]int{
 | 
			
		||||
		0: 40,
 | 
			
		||||
		1: 2,
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
 | 
			
		||||
	_, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c := m[2]; c != 42 {
 | 
			
		||||
		t.Fatalf("Unexpected value: '%d'", c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectDelete(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	delete m.a;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]string{
 | 
			
		||||
		"a": "4",
 | 
			
		||||
		"b": "2",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
 | 
			
		||||
	_, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, exists := m["a"]; exists {
 | 
			
		||||
		t.Fatal("a still exists")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if b := m["b"]; b != "2" {
 | 
			
		||||
		t.Fatalf("Unexpected b: '%s'", b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectJSON(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	function f(m) {
 | 
			
		||||
		return JSON.stringify(m);
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]string{
 | 
			
		||||
		"t": "42",
 | 
			
		||||
	}
 | 
			
		||||
	_, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	f := vm.Get("f")
 | 
			
		||||
	if call, ok := AssertFunction(f); ok {
 | 
			
		||||
		v, err := call(nil, ([]Value{vm.ToValue(m)})...)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if !v.StrictEquals(asciiString(`{"t":"42"}`)) {
 | 
			
		||||
			t.Fatalf("Unexpected value: %v", v)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Not a function: %v", f)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectProto(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	m.hasOwnProperty("t");
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]string{
 | 
			
		||||
		"t": "42",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	v, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.StrictEquals(valueTrue) {
 | 
			
		||||
		t.Fatalf("Expected true, got %v", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type gomapReflect_noMethods map[string]interface{}
 | 
			
		||||
type gomapReflect_withMethods map[string]interface{}
 | 
			
		||||
 | 
			
		||||
func (m gomapReflect_withMethods) Method() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectNoMethods(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	typeof m === "object" && m.hasOwnProperty("t") && m.t === 42;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := make(gomapReflect_noMethods)
 | 
			
		||||
	m["t"] = 42
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	v, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.StrictEquals(valueTrue) {
 | 
			
		||||
		t.Fatalf("Expected true, got %v", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectWithMethods(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	typeof m === "object" && !m.hasOwnProperty("t") && m.hasOwnProperty("Method") && m.Method();
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := make(gomapReflect_withMethods)
 | 
			
		||||
	m["t"] = 42
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	v, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.StrictEquals(valueTrue) {
 | 
			
		||||
		t.Fatalf("Expected true, got %v", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectWithProto(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]string{
 | 
			
		||||
		"t": "42",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	vm.testScriptWithTestLib(`
 | 
			
		||||
	(function() {
 | 
			
		||||
	'use strict';
 | 
			
		||||
	var proto = {};
 | 
			
		||||
	var getterAllowed = false;
 | 
			
		||||
	var setterAllowed = false;
 | 
			
		||||
	var tHolder = "proto t";
 | 
			
		||||
	Object.defineProperty(proto, "t", {
 | 
			
		||||
		get: function() {
 | 
			
		||||
			if (!getterAllowed) throw new Error("getter is called");
 | 
			
		||||
			return tHolder;
 | 
			
		||||
		},
 | 
			
		||||
		set: function(v) {
 | 
			
		||||
			if (!setterAllowed) throw new Error("setter is called");
 | 
			
		||||
			tHolder = v;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	var t1Holder;
 | 
			
		||||
	Object.defineProperty(proto, "t1", {
 | 
			
		||||
		get: function() {
 | 
			
		||||
			return t1Holder;
 | 
			
		||||
		},
 | 
			
		||||
		set: function(v) {
 | 
			
		||||
			t1Holder = v;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	Object.setPrototypeOf(m, proto);
 | 
			
		||||
	assert.sameValue(m.t, "42");
 | 
			
		||||
	m.t = 43;
 | 
			
		||||
	assert.sameValue(m.t, "43");
 | 
			
		||||
	t1Holder = "test";
 | 
			
		||||
	assert.sameValue(m.t1, "test");
 | 
			
		||||
	m.t1 = "test1";
 | 
			
		||||
	assert.sameValue(m.t1, "test1");
 | 
			
		||||
	delete m.t;
 | 
			
		||||
	getterAllowed = true;
 | 
			
		||||
	assert.sameValue(m.t, "proto t", "after delete");
 | 
			
		||||
	setterAllowed = true;
 | 
			
		||||
	m.t = true;
 | 
			
		||||
	assert.sameValue(m.t, true, "m.t === true");
 | 
			
		||||
	assert.sameValue(tHolder, true, "tHolder === true");
 | 
			
		||||
	Object.preventExtensions(m);
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		m.t2 = 1;
 | 
			
		||||
	});
 | 
			
		||||
	m.t1 = "test2";
 | 
			
		||||
	assert.sameValue(m.t1, "test2");
 | 
			
		||||
	})();
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectProtoProp(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	(function() {
 | 
			
		||||
	"use strict";
 | 
			
		||||
	var proto = {};
 | 
			
		||||
	Object.defineProperty(proto, "ro", {value: 42});
 | 
			
		||||
	Object.setPrototypeOf(m, proto);
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		m.ro = 43;
 | 
			
		||||
	});
 | 
			
		||||
	Object.defineProperty(m, "ro", {value: 43});
 | 
			
		||||
	assert.sameValue(m.ro, "43");
 | 
			
		||||
	})();
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	r := New()
 | 
			
		||||
	r.Set("m", map[string]string{})
 | 
			
		||||
	r.testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectUnicode(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	Object.setPrototypeOf(m, s);
 | 
			
		||||
	if (m.Тест !== "passed") {
 | 
			
		||||
		throw new Error("m.Тест: " + m.Тест);
 | 
			
		||||
	}
 | 
			
		||||
	m["é"];
 | 
			
		||||
	`
 | 
			
		||||
	type S struct {
 | 
			
		||||
		Тест string
 | 
			
		||||
	}
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]int{
 | 
			
		||||
		"é": 42,
 | 
			
		||||
	}
 | 
			
		||||
	s := S{
 | 
			
		||||
		Тест: "passed",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	vm.Set("s", &s)
 | 
			
		||||
	res, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if res == nil || !res.StrictEquals(valueInt(42)) {
 | 
			
		||||
		t.Fatalf("Unexpected value: %v", res)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectStruct(t *testing.T) {
 | 
			
		||||
	type S struct {
 | 
			
		||||
		Test int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := map[string]S{
 | 
			
		||||
		"1": {Test: 1},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	res, err := vm.RunString("m[1].Test = 2; m[1].Test")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if res.Export() != int64(1) {
 | 
			
		||||
		t.Fatal(res)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectElt(t *testing.T) {
 | 
			
		||||
	type mapping map[string]interface{}
 | 
			
		||||
 | 
			
		||||
	const SCRIPT = `a.s() && a.t === null && a.t1 === undefined;`
 | 
			
		||||
 | 
			
		||||
	r := New()
 | 
			
		||||
 | 
			
		||||
	r.Set("a", mapping{
 | 
			
		||||
		"s": func() bool { return true },
 | 
			
		||||
		"t": nil,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	r.testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapReflectKeyToString(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
 | 
			
		||||
	test := func(v any, t *testing.T) {
 | 
			
		||||
		o1 := vm.ToValue(v).ToObject(vm)
 | 
			
		||||
		keys := o1.Keys()
 | 
			
		||||
		sort.Strings(keys)
 | 
			
		||||
		if len(keys) != 2 || keys[0] != "1" || keys[1] != "2" {
 | 
			
		||||
			t.Fatal(keys)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		keys1 := o1.self.stringKeys(true, nil)
 | 
			
		||||
		sort.Slice(keys1, func(a, b int) bool {
 | 
			
		||||
			return strings.Compare(keys1[a].String(), keys1[b].String()) < 0
 | 
			
		||||
		})
 | 
			
		||||
		if len(keys1) != 2 || keys1[0] != asciiString("1") || keys1[1] != asciiString("2") {
 | 
			
		||||
			t.Fatal(keys1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("int", func(t *testing.T) {
 | 
			
		||||
		m1 := map[int]any{
 | 
			
		||||
			1: 2,
 | 
			
		||||
			2: 3,
 | 
			
		||||
		}
 | 
			
		||||
		test(m1, t)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("CustomString", func(t *testing.T) {
 | 
			
		||||
		type CustomString string
 | 
			
		||||
		m2 := map[CustomString]any{
 | 
			
		||||
			"1": 2,
 | 
			
		||||
			"2": 3,
 | 
			
		||||
		}
 | 
			
		||||
		test(m2, t)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										328
									
								
								goja/object_gomap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								goja/object_gomap_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,328 @@
 | 
			
		||||
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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapExtensibility(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	"use strict";
 | 
			
		||||
	o.test = 42;
 | 
			
		||||
	Object.preventExtensions(o);
 | 
			
		||||
	o.test = 43;
 | 
			
		||||
	try {
 | 
			
		||||
		o.test1 = 42;
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		if (!(e instanceof TypeError)) {
 | 
			
		||||
			throw e;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.test === 43 && o.test1 === undefined;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	r := New()
 | 
			
		||||
	r.Set("o", map[string]interface{}{})
 | 
			
		||||
	v, err := r.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if ex, ok := err.(*Exception); ok {
 | 
			
		||||
			t.Fatal(ex.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !v.StrictEquals(valueTrue) {
 | 
			
		||||
		t.Fatalf("Expected true, got %v", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapWithProto(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]interface{}{
 | 
			
		||||
		"t": "42",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	vm.testScriptWithTestLib(`
 | 
			
		||||
	(function() {
 | 
			
		||||
	'use strict';
 | 
			
		||||
	var proto = {};
 | 
			
		||||
	var getterAllowed = false;
 | 
			
		||||
	var setterAllowed = false;
 | 
			
		||||
	var tHolder = "proto t";
 | 
			
		||||
	Object.defineProperty(proto, "t", {
 | 
			
		||||
		get: function() {
 | 
			
		||||
			if (!getterAllowed) throw new Error("getter is called");
 | 
			
		||||
			return tHolder;
 | 
			
		||||
		},
 | 
			
		||||
		set: function(v) {
 | 
			
		||||
			if (!setterAllowed) throw new Error("setter is called");
 | 
			
		||||
			tHolder = v;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	var t1Holder;
 | 
			
		||||
	Object.defineProperty(proto, "t1", {
 | 
			
		||||
		get: function() {
 | 
			
		||||
			return t1Holder;
 | 
			
		||||
		},
 | 
			
		||||
		set: function(v) {
 | 
			
		||||
			t1Holder = v;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	Object.setPrototypeOf(m, proto);
 | 
			
		||||
	assert.sameValue(m.t, "42");
 | 
			
		||||
	m.t = 43;
 | 
			
		||||
	assert.sameValue(m.t, 43);
 | 
			
		||||
	t1Holder = "test";
 | 
			
		||||
	assert.sameValue(m.t1, "test");
 | 
			
		||||
	m.t1 = "test1";
 | 
			
		||||
	assert.sameValue(m.t1, "test1");
 | 
			
		||||
	delete m.t;
 | 
			
		||||
	getterAllowed = true;
 | 
			
		||||
	assert.sameValue(m.t, "proto t", "after delete");
 | 
			
		||||
	setterAllowed = true;
 | 
			
		||||
	m.t = true;
 | 
			
		||||
	assert.sameValue(m.t, true);
 | 
			
		||||
	assert.sameValue(tHolder, true);
 | 
			
		||||
	Object.preventExtensions(m);
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		m.t2 = 1;
 | 
			
		||||
	});
 | 
			
		||||
	m.t1 = "test2";
 | 
			
		||||
	assert.sameValue(m.t1, "test2");
 | 
			
		||||
	})();
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapProtoProp(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	(function() {
 | 
			
		||||
	"use strict";
 | 
			
		||||
	var proto = {};
 | 
			
		||||
	Object.defineProperty(proto, "ro", {value: 42});
 | 
			
		||||
	Object.setPrototypeOf(m, proto);
 | 
			
		||||
	assert.throws(TypeError, function() {
 | 
			
		||||
		m.ro = 43;
 | 
			
		||||
	});
 | 
			
		||||
	Object.defineProperty(m, "ro", {value: 43});
 | 
			
		||||
	assert.sameValue(m.ro, 43);
 | 
			
		||||
	})();
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	r := New()
 | 
			
		||||
	r.Set("m", map[string]interface{}{})
 | 
			
		||||
	r.testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapProtoPropChain(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	(function() {
 | 
			
		||||
	"use strict";
 | 
			
		||||
	var p1 = Object.create(null);
 | 
			
		||||
	m.__proto__ = p1;
 | 
			
		||||
	
 | 
			
		||||
	Object.defineProperty(p1, "test", {
 | 
			
		||||
		value: 42
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	Object.defineProperty(m, "test", {
 | 
			
		||||
		value: 43,
 | 
			
		||||
		writable: true,
 | 
			
		||||
	});
 | 
			
		||||
	var o = Object.create(m);
 | 
			
		||||
	o.test = 44;
 | 
			
		||||
	assert.sameValue(o.test, 44);
 | 
			
		||||
 | 
			
		||||
	var sym = Symbol(true);
 | 
			
		||||
	Object.defineProperty(p1, sym, {
 | 
			
		||||
		value: 42
 | 
			
		||||
	});
 | 
			
		||||
	
 | 
			
		||||
	Object.defineProperty(m, sym, {
 | 
			
		||||
		value: 43,
 | 
			
		||||
		writable: true,
 | 
			
		||||
	});
 | 
			
		||||
	o[sym] = 44;
 | 
			
		||||
	assert.sameValue(o[sym], 44);
 | 
			
		||||
	})();
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	r := New()
 | 
			
		||||
	r.Set("m", map[string]interface{}{})
 | 
			
		||||
	r.testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoMapUnicode(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	Object.setPrototypeOf(m, s);
 | 
			
		||||
	if (m.Тест !== "passed") {
 | 
			
		||||
		throw new Error("m.Тест: " + m.Тест);
 | 
			
		||||
	}
 | 
			
		||||
	m["é"];
 | 
			
		||||
	`
 | 
			
		||||
	type S struct {
 | 
			
		||||
		Тест string
 | 
			
		||||
	}
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]interface{}{
 | 
			
		||||
		"é": 42,
 | 
			
		||||
	}
 | 
			
		||||
	s := S{
 | 
			
		||||
		Тест: "passed",
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("m", m)
 | 
			
		||||
	vm.Set("s", &s)
 | 
			
		||||
	res, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if res == nil || !res.StrictEquals(valueInt(42)) {
 | 
			
		||||
		t.Fatalf("Unexpected value: %v", res)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										674
									
								
								goja/object_goreflect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										674
									
								
								goja/object_goreflect.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,674 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"go/ast"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/parser"
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JsonEncodable allows custom JSON encoding by JSON.stringify()
 | 
			
		||||
// Note that if the returned value itself also implements JsonEncodable, it won't have any effect.
 | 
			
		||||
type JsonEncodable interface {
 | 
			
		||||
	JsonEncodable() interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FieldNameMapper provides custom mapping between Go and JavaScript property names.
 | 
			
		||||
type FieldNameMapper interface {
 | 
			
		||||
	// FieldName returns a JavaScript name for the given struct field in the given type.
 | 
			
		||||
	// If this method returns "" the field becomes hidden.
 | 
			
		||||
	FieldName(t reflect.Type, f reflect.StructField) string
 | 
			
		||||
 | 
			
		||||
	// MethodName returns a JavaScript name for the given method in the given type.
 | 
			
		||||
	// If this method returns "" the method becomes hidden.
 | 
			
		||||
	MethodName(t reflect.Type, m reflect.Method) string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tagFieldNameMapper struct {
 | 
			
		||||
	tagName      string
 | 
			
		||||
	uncapMethods bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tfm tagFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
 | 
			
		||||
	tag := f.Tag.Get(tfm.tagName)
 | 
			
		||||
	if idx := strings.IndexByte(tag, ','); idx != -1 {
 | 
			
		||||
		tag = tag[:idx]
 | 
			
		||||
	}
 | 
			
		||||
	if parser.IsIdentifier(tag) {
 | 
			
		||||
		return tag
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func uncapitalize(s string) string {
 | 
			
		||||
	return strings.ToLower(s[0:1]) + s[1:]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tfm tagFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string {
 | 
			
		||||
	if tfm.uncapMethods {
 | 
			
		||||
		return uncapitalize(m.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return m.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type uncapFieldNameMapper struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u uncapFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
 | 
			
		||||
	return uncapitalize(f.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u uncapFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string {
 | 
			
		||||
	return uncapitalize(m.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reflectFieldInfo struct {
 | 
			
		||||
	Index     []int
 | 
			
		||||
	Anonymous bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reflectFieldsInfo struct {
 | 
			
		||||
	Fields map[string]reflectFieldInfo
 | 
			
		||||
	Names  []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reflectMethodsInfo struct {
 | 
			
		||||
	Methods map[string]int
 | 
			
		||||
	Names   []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reflectValueWrapper interface {
 | 
			
		||||
	esValue() Value
 | 
			
		||||
	reflectValue() reflect.Value
 | 
			
		||||
	setReflectValue(reflect.Value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isContainer(k reflect.Kind) bool {
 | 
			
		||||
	switch k {
 | 
			
		||||
	case reflect.Struct, reflect.Slice, reflect.Array:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyReflectValueWrapper(w reflectValueWrapper) {
 | 
			
		||||
	v := w.reflectValue()
 | 
			
		||||
	c := reflect.New(v.Type()).Elem()
 | 
			
		||||
	c.Set(v)
 | 
			
		||||
	w.setReflectValue(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type objectGoReflect struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	origValue, fieldsValue reflect.Value
 | 
			
		||||
 | 
			
		||||
	fieldsInfo  *reflectFieldsInfo
 | 
			
		||||
	methodsInfo *reflectMethodsInfo
 | 
			
		||||
 | 
			
		||||
	methodsValue reflect.Value
 | 
			
		||||
 | 
			
		||||
	valueCache map[string]reflectValueWrapper
 | 
			
		||||
 | 
			
		||||
	toString, valueOf func() Value
 | 
			
		||||
 | 
			
		||||
	toJson func() interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) init() {
 | 
			
		||||
	o.baseObject.init()
 | 
			
		||||
	switch o.fieldsValue.Kind() {
 | 
			
		||||
	case reflect.Bool:
 | 
			
		||||
		o.class = classBoolean
 | 
			
		||||
		o.prototype = o.val.runtime.getBooleanPrototype()
 | 
			
		||||
		o.toString = o._toStringBool
 | 
			
		||||
		o.valueOf = o._valueOfBool
 | 
			
		||||
	case reflect.String:
 | 
			
		||||
		o.class = classString
 | 
			
		||||
		o.prototype = o.val.runtime.getStringPrototype()
 | 
			
		||||
		o.toString = o._toStringString
 | 
			
		||||
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
			
		||||
		o.class = classNumber
 | 
			
		||||
		o.prototype = o.val.runtime.getNumberPrototype()
 | 
			
		||||
		o.valueOf = o._valueOfInt
 | 
			
		||||
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | 
			
		||||
		o.class = classNumber
 | 
			
		||||
		o.prototype = o.val.runtime.getNumberPrototype()
 | 
			
		||||
		o.valueOf = o._valueOfUint
 | 
			
		||||
	case reflect.Float32, reflect.Float64:
 | 
			
		||||
		o.class = classNumber
 | 
			
		||||
		o.prototype = o.val.runtime.getNumberPrototype()
 | 
			
		||||
		o.valueOf = o._valueOfFloat
 | 
			
		||||
	default:
 | 
			
		||||
		o.class = classObject
 | 
			
		||||
		o.prototype = o.val.runtime.global.ObjectPrototype
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.fieldsValue.Kind() == reflect.Struct {
 | 
			
		||||
		o.fieldsInfo = o.val.runtime.fieldsInfo(o.fieldsValue.Type())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var methodsType reflect.Type
 | 
			
		||||
	// Always use pointer type for non-interface values to be able to access both methods defined on
 | 
			
		||||
	// the literal type and on the pointer.
 | 
			
		||||
	if o.fieldsValue.Kind() != reflect.Interface {
 | 
			
		||||
		methodsType = reflect.PtrTo(o.fieldsValue.Type())
 | 
			
		||||
	} else {
 | 
			
		||||
		methodsType = o.fieldsValue.Type()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.methodsInfo = o.val.runtime.methodsInfo(methodsType)
 | 
			
		||||
 | 
			
		||||
	// Container values and values that have at least one method defined on the pointer type
 | 
			
		||||
	// need to be addressable.
 | 
			
		||||
	if !o.origValue.CanAddr() && (isContainer(o.origValue.Kind()) || len(o.methodsInfo.Names) > 0) {
 | 
			
		||||
		value := reflect.New(o.origValue.Type()).Elem()
 | 
			
		||||
		value.Set(o.origValue)
 | 
			
		||||
		o.origValue = value
 | 
			
		||||
		if value.Kind() != reflect.Ptr {
 | 
			
		||||
			o.fieldsValue = value
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.extensible = true
 | 
			
		||||
 | 
			
		||||
	switch o.origValue.Interface().(type) {
 | 
			
		||||
	case fmt.Stringer:
 | 
			
		||||
		o.toString = o._toStringStringer
 | 
			
		||||
	case error:
 | 
			
		||||
		o.toString = o._toStringError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.methodsInfo.Names) > 0 && o.fieldsValue.Kind() != reflect.Interface {
 | 
			
		||||
		o.methodsValue = o.fieldsValue.Addr()
 | 
			
		||||
	} else {
 | 
			
		||||
		o.methodsValue = o.fieldsValue
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if j, ok := o.origValue.Interface().(JsonEncodable); ok {
 | 
			
		||||
		o.toJson = j.JsonEncodable
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	if v := o._get(name.String()); v != nil {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject.getStr(name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _getField(jsName string) reflect.Value {
 | 
			
		||||
	if o.fieldsInfo != nil {
 | 
			
		||||
		if info, exists := o.fieldsInfo.Fields[jsName]; exists {
 | 
			
		||||
			return o.fieldsValue.FieldByIndex(info.Index)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return reflect.Value{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _getMethod(jsName string) reflect.Value {
 | 
			
		||||
	if o.methodsInfo != nil {
 | 
			
		||||
		if idx, exists := o.methodsInfo.Methods[jsName]; exists {
 | 
			
		||||
			return o.methodsValue.Method(idx)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return reflect.Value{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) elemToValue(ev reflect.Value) (Value, reflectValueWrapper) {
 | 
			
		||||
	if isContainer(ev.Kind()) {
 | 
			
		||||
		ret := o.val.runtime.toValue(ev.Interface(), ev)
 | 
			
		||||
		if obj, ok := ret.(*Object); ok {
 | 
			
		||||
			if w, ok := obj.self.(reflectValueWrapper); ok {
 | 
			
		||||
				return ret, w
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return ret, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ev.Kind() == reflect.Interface {
 | 
			
		||||
		ev = ev.Elem()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ev.Kind() == reflect.Invalid {
 | 
			
		||||
		return _null, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.val.runtime.toValue(ev.Interface(), ev), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _getFieldValue(name string) Value {
 | 
			
		||||
	if v := o.valueCache[name]; v != nil {
 | 
			
		||||
		return v.esValue()
 | 
			
		||||
	}
 | 
			
		||||
	if v := o._getField(name); v.IsValid() {
 | 
			
		||||
		res, w := o.elemToValue(v)
 | 
			
		||||
		if w != nil {
 | 
			
		||||
			if o.valueCache == nil {
 | 
			
		||||
				o.valueCache = make(map[string]reflectValueWrapper)
 | 
			
		||||
			}
 | 
			
		||||
			o.valueCache[name] = w
 | 
			
		||||
		}
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _get(name string) Value {
 | 
			
		||||
	if o.fieldsValue.Kind() == reflect.Struct {
 | 
			
		||||
		if ret := o._getFieldValue(name); ret != nil {
 | 
			
		||||
			return ret
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v := o._getMethod(name); v.IsValid() {
 | 
			
		||||
		return o.val.runtime.toValue(v.Interface(), v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	n := name.String()
 | 
			
		||||
	if o.fieldsValue.Kind() == reflect.Struct {
 | 
			
		||||
		if v := o._getFieldValue(n); v != nil {
 | 
			
		||||
			return &valueProperty{
 | 
			
		||||
				value:      v,
 | 
			
		||||
				writable:   true,
 | 
			
		||||
				enumerable: true,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v := o._getMethod(n); v.IsValid() {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:      o.val.runtime.toValue(v.Interface(), v),
 | 
			
		||||
			enumerable: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.baseObject.getOwnPropStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	has, ok := o._put(name.String(), val, throw)
 | 
			
		||||
	if !has {
 | 
			
		||||
		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignStr(name, trueValIfPresent(o._has(name.String())), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignIdx(idx, nil, val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) {
 | 
			
		||||
	if o.fieldsValue.Kind() == reflect.Struct {
 | 
			
		||||
		if v := o._getField(name); v.IsValid() {
 | 
			
		||||
			cached := o.valueCache[name]
 | 
			
		||||
			if cached != nil {
 | 
			
		||||
				copyReflectValueWrapper(cached)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if cached != nil {
 | 
			
		||||
					cached.setReflectValue(v)
 | 
			
		||||
				}
 | 
			
		||||
				o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
 | 
			
		||||
				return true, false
 | 
			
		||||
			}
 | 
			
		||||
			if cached != nil {
 | 
			
		||||
				delete(o.valueCache, name)
 | 
			
		||||
			}
 | 
			
		||||
			return true, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
 | 
			
		||||
	if _, ok := o._put(name.String(), value, false); ok {
 | 
			
		||||
		return value
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if descr.Getter != nil || descr.Setter != nil {
 | 
			
		||||
		r.typeErrorResult(throw, "Host objects do not support accessor properties")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if descr.Writable == FLAG_FALSE {
 | 
			
		||||
		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if descr.Configurable == FLAG_TRUE {
 | 
			
		||||
		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 | 
			
		||||
		n := name.String()
 | 
			
		||||
		if has, ok := o._put(n, descr.Value, throw); !has {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", n)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			return ok
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _has(name string) bool {
 | 
			
		||||
	if o.fieldsValue.Kind() == reflect.Struct {
 | 
			
		||||
		if v := o._getField(name); v.IsValid() {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if v := o._getMethod(name); v.IsValid() {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	return o._has(name.String()) || o.baseObject.hasOwnPropertyStr(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _valueOfInt() Value {
 | 
			
		||||
	return intToValue(o.fieldsValue.Int())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _valueOfUint() Value {
 | 
			
		||||
	return intToValue(int64(o.fieldsValue.Uint()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _valueOfBool() Value {
 | 
			
		||||
	if o.fieldsValue.Bool() {
 | 
			
		||||
		return valueTrue
 | 
			
		||||
	} else {
 | 
			
		||||
		return valueFalse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _valueOfFloat() Value {
 | 
			
		||||
	return floatToValue(o.fieldsValue.Float())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _toStringStringer() Value {
 | 
			
		||||
	return newStringValue(o.origValue.Interface().(fmt.Stringer).String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _toStringString() Value {
 | 
			
		||||
	return newStringValue(o.fieldsValue.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _toStringBool() Value {
 | 
			
		||||
	if o.fieldsValue.Bool() {
 | 
			
		||||
		return stringTrue
 | 
			
		||||
	} else {
 | 
			
		||||
		return stringFalse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) _toStringError() Value {
 | 
			
		||||
	return newStringValue(o.origValue.Interface().(error).Error())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	n := name.String()
 | 
			
		||||
	if o._has(n) {
 | 
			
		||||
		o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type", n)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject.deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type goreflectPropIter struct {
 | 
			
		||||
	o   *objectGoReflect
 | 
			
		||||
	idx int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
 | 
			
		||||
	names := i.o.fieldsInfo.Names
 | 
			
		||||
	if i.idx < len(names) {
 | 
			
		||||
		name := names[i.idx]
 | 
			
		||||
		i.idx++
 | 
			
		||||
		return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.nextField
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i.idx = 0
 | 
			
		||||
	return i.nextMethod()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
 | 
			
		||||
	names := i.o.methodsInfo.Names
 | 
			
		||||
	if i.idx < len(names) {
 | 
			
		||||
		name := names[i.idx]
 | 
			
		||||
		i.idx++
 | 
			
		||||
		return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.nextMethod
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return propIterItem{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	r := &goreflectPropIter{
 | 
			
		||||
		o: o,
 | 
			
		||||
	}
 | 
			
		||||
	if o.fieldsInfo != nil {
 | 
			
		||||
		return r.nextField
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return r.nextMethod
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) stringKeys(_ bool, accum []Value) []Value {
 | 
			
		||||
	// all own keys are enumerable
 | 
			
		||||
	if o.fieldsInfo != nil {
 | 
			
		||||
		for _, name := range o.fieldsInfo.Names {
 | 
			
		||||
			accum = append(accum, newStringValue(name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, name := range o.methodsInfo.Names {
 | 
			
		||||
		accum = append(accum, newStringValue(name))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) export(*objectExportCtx) 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 {
 | 
			
		||||
		k1, k2 := o.fieldsValue.Kind(), other.fieldsValue.Kind()
 | 
			
		||||
		if k1 == k2 {
 | 
			
		||||
			if isContainer(k1) {
 | 
			
		||||
				return o.fieldsValue == other.fieldsValue
 | 
			
		||||
			}
 | 
			
		||||
			return o.fieldsValue.Interface() == other.fieldsValue.Interface()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) reflectValue() reflect.Value {
 | 
			
		||||
	return o.fieldsValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) setReflectValue(v reflect.Value) {
 | 
			
		||||
	o.fieldsValue = v
 | 
			
		||||
	o.origValue = v
 | 
			
		||||
	o.methodsValue = v.Addr()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoReflect) esValue() Value {
 | 
			
		||||
	return o.val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) buildFieldInfo(t reflect.Type, index []int, info *reflectFieldsInfo) {
 | 
			
		||||
	n := t.NumField()
 | 
			
		||||
	for i := 0; i < n; i++ {
 | 
			
		||||
		field := t.Field(i)
 | 
			
		||||
		name := field.Name
 | 
			
		||||
		isExported := ast.IsExported(name)
 | 
			
		||||
 | 
			
		||||
		if !isExported && !field.Anonymous {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.fieldNameMapper != nil {
 | 
			
		||||
			name = r.fieldNameMapper.FieldName(t, field)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if name != "" && isExported {
 | 
			
		||||
			if inf, exists := info.Fields[name]; !exists {
 | 
			
		||||
				info.Names = append(info.Names, name)
 | 
			
		||||
			} else {
 | 
			
		||||
				if len(inf.Index) <= len(index) {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if name != "" || field.Anonymous {
 | 
			
		||||
			idx := make([]int, len(index)+1)
 | 
			
		||||
			copy(idx, index)
 | 
			
		||||
			idx[len(idx)-1] = i
 | 
			
		||||
 | 
			
		||||
			if name != "" && isExported {
 | 
			
		||||
				info.Fields[name] = reflectFieldInfo{
 | 
			
		||||
					Index:     idx,
 | 
			
		||||
					Anonymous: field.Anonymous,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if field.Anonymous {
 | 
			
		||||
				typ := field.Type
 | 
			
		||||
				for typ.Kind() == reflect.Ptr {
 | 
			
		||||
					typ = typ.Elem()
 | 
			
		||||
				}
 | 
			
		||||
				if typ.Kind() == reflect.Struct {
 | 
			
		||||
					r.buildFieldInfo(typ, idx, info)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var emptyMethodsInfo = reflectMethodsInfo{}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) buildMethodsInfo(t reflect.Type) (info *reflectMethodsInfo) {
 | 
			
		||||
	n := t.NumMethod()
 | 
			
		||||
	if n == 0 {
 | 
			
		||||
		return &emptyMethodsInfo
 | 
			
		||||
	}
 | 
			
		||||
	info = new(reflectMethodsInfo)
 | 
			
		||||
	info.Methods = make(map[string]int, n)
 | 
			
		||||
	info.Names = make([]string, 0, n)
 | 
			
		||||
	for i := 0; i < n; i++ {
 | 
			
		||||
		method := t.Method(i)
 | 
			
		||||
		name := method.Name
 | 
			
		||||
		if !ast.IsExported(name) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if r.fieldNameMapper != nil {
 | 
			
		||||
			name = r.fieldNameMapper.MethodName(t, method)
 | 
			
		||||
			if name == "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, exists := info.Methods[name]; !exists {
 | 
			
		||||
			info.Names = append(info.Names, name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		info.Methods[name] = i
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) buildFieldsInfo(t reflect.Type) (info *reflectFieldsInfo) {
 | 
			
		||||
	info = new(reflectFieldsInfo)
 | 
			
		||||
	n := t.NumField()
 | 
			
		||||
	info.Fields = make(map[string]reflectFieldInfo, n)
 | 
			
		||||
	info.Names = make([]string, 0, n)
 | 
			
		||||
	r.buildFieldInfo(t, nil, info)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) fieldsInfo(t reflect.Type) (info *reflectFieldsInfo) {
 | 
			
		||||
	var exists bool
 | 
			
		||||
	if info, exists = r.fieldsInfoCache[t]; !exists {
 | 
			
		||||
		info = r.buildFieldsInfo(t)
 | 
			
		||||
		if r.fieldsInfoCache == nil {
 | 
			
		||||
			r.fieldsInfoCache = make(map[reflect.Type]*reflectFieldsInfo)
 | 
			
		||||
		}
 | 
			
		||||
		r.fieldsInfoCache[t] = info
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) methodsInfo(t reflect.Type) (info *reflectMethodsInfo) {
 | 
			
		||||
	var exists bool
 | 
			
		||||
	if info, exists = r.methodsInfoCache[t]; !exists {
 | 
			
		||||
		info = r.buildMethodsInfo(t)
 | 
			
		||||
		if r.methodsInfoCache == nil {
 | 
			
		||||
			r.methodsInfoCache = make(map[reflect.Type]*reflectMethodsInfo)
 | 
			
		||||
		}
 | 
			
		||||
		r.methodsInfoCache[t] = info
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFieldNameMapper sets a custom field name mapper for Go types. It can be called at any time, however
 | 
			
		||||
// the mapping for any given value is fixed at the point of creation.
 | 
			
		||||
// Setting this to nil restores the default behaviour which is all exported fields and methods are mapped to their
 | 
			
		||||
// original unchanged names.
 | 
			
		||||
func (r *Runtime) SetFieldNameMapper(mapper FieldNameMapper) {
 | 
			
		||||
	r.fieldNameMapper = mapper
 | 
			
		||||
	r.fieldsInfoCache = nil
 | 
			
		||||
	r.methodsInfoCache = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TagFieldNameMapper returns a FieldNameMapper that uses the given tagName for struct fields and optionally
 | 
			
		||||
// uncapitalises (making the first letter lower case) method names.
 | 
			
		||||
// The common tag value syntax is supported (name[,options]), however options are ignored.
 | 
			
		||||
// Setting name to anything other than a valid ECMAScript identifier makes the field hidden.
 | 
			
		||||
func TagFieldNameMapper(tagName string, uncapMethods bool) FieldNameMapper {
 | 
			
		||||
	return tagFieldNameMapper{
 | 
			
		||||
		tagName:      tagName,
 | 
			
		||||
		uncapMethods: uncapMethods,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UncapFieldNameMapper returns a FieldNameMapper that uncapitalises struct field and method names
 | 
			
		||||
// making the first letter lower case.
 | 
			
		||||
func UncapFieldNameMapper() FieldNameMapper {
 | 
			
		||||
	return uncapFieldNameMapper{}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1667
									
								
								goja/object_goreflect_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1667
									
								
								goja/object_goreflect_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										343
									
								
								goja/object_goslice.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								goja/object_goslice.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,343 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/bits"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type objectGoSlice struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	data       *[]interface{}
 | 
			
		||||
	lengthProp valueProperty
 | 
			
		||||
	origIsPtr  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newObjectGoSlice(data *[]interface{}, isPtr bool) *objectGoSlice {
 | 
			
		||||
	obj := &Object{runtime: r}
 | 
			
		||||
	a := &objectGoSlice{
 | 
			
		||||
		baseObject: baseObject{
 | 
			
		||||
			val: obj,
 | 
			
		||||
		},
 | 
			
		||||
		data:      data,
 | 
			
		||||
		origIsPtr: isPtr,
 | 
			
		||||
	}
 | 
			
		||||
	obj.self = a
 | 
			
		||||
	a.init()
 | 
			
		||||
 | 
			
		||||
	return a
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) init() {
 | 
			
		||||
	o.baseObject.init()
 | 
			
		||||
	o.class = classArray
 | 
			
		||||
	o.prototype = o.val.runtime.getArrayPrototype()
 | 
			
		||||
	o.lengthProp.writable = true
 | 
			
		||||
	o.extensible = true
 | 
			
		||||
	o.baseObject._put("length", &o.lengthProp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) updateLen() {
 | 
			
		||||
	o.lengthProp.value = intToValue(int64(len(*o.data)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) _getIdx(idx int) Value {
 | 
			
		||||
	return o.val.runtime.ToValue((*o.data)[idx])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	var ownProp Value
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
 | 
			
		||||
		ownProp = o._getIdx(idx)
 | 
			
		||||
	} else if name == "length" {
 | 
			
		||||
		o.updateLen()
 | 
			
		||||
		ownProp = &o.lengthProp
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return o.getStrWithOwnProp(ownProp, name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value {
 | 
			
		||||
	if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) {
 | 
			
		||||
		return o._getIdx(int(idx))
 | 
			
		||||
	}
 | 
			
		||||
	if o.prototype != nil {
 | 
			
		||||
		if receiver == nil {
 | 
			
		||||
			return o.prototype.self.getIdx(idx, o.val)
 | 
			
		||||
		}
 | 
			
		||||
		return o.prototype.self.getIdx(idx, receiver)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) getOwnPropStr(name unistring.String) Value {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		if idx < len(*o.data) {
 | 
			
		||||
			return &valueProperty{
 | 
			
		||||
				value:      o._getIdx(idx),
 | 
			
		||||
				writable:   true,
 | 
			
		||||
				enumerable: true,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		o.updateLen()
 | 
			
		||||
		return &o.lengthProp
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) getOwnPropIdx(idx valueInt) Value {
 | 
			
		||||
	if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) {
 | 
			
		||||
		return &valueProperty{
 | 
			
		||||
			value:      o._getIdx(int(idx)),
 | 
			
		||||
			writable:   true,
 | 
			
		||||
			enumerable: true,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) grow(size int) {
 | 
			
		||||
	oldcap := cap(*o.data)
 | 
			
		||||
	if oldcap < size {
 | 
			
		||||
		n := make([]interface{}, size, growCap(size, len(*o.data), oldcap))
 | 
			
		||||
		copy(n, *o.data)
 | 
			
		||||
		*o.data = n
 | 
			
		||||
	} else {
 | 
			
		||||
		tail := (*o.data)[len(*o.data):size]
 | 
			
		||||
		for k := range tail {
 | 
			
		||||
			tail[k] = nil
 | 
			
		||||
		}
 | 
			
		||||
		*o.data = (*o.data)[:size]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) shrink(size int) {
 | 
			
		||||
	tail := (*o.data)[size:]
 | 
			
		||||
	for k := range tail {
 | 
			
		||||
		tail[k] = nil
 | 
			
		||||
	}
 | 
			
		||||
	*o.data = (*o.data)[:size]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
 | 
			
		||||
	if idx >= len(*o.data) {
 | 
			
		||||
		o.grow(idx + 1)
 | 
			
		||||
	}
 | 
			
		||||
	(*o.data)[idx] = v.Export()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) putLength(v uint32, throw bool) bool {
 | 
			
		||||
	if bits.UintSize == 32 && v > math.MaxInt32 {
 | 
			
		||||
		panic(rangeError("Integer value overflows 32-bit int"))
 | 
			
		||||
	}
 | 
			
		||||
	newLen := int(v)
 | 
			
		||||
	curLen := len(*o.data)
 | 
			
		||||
	if newLen > curLen {
 | 
			
		||||
		o.grow(newLen)
 | 
			
		||||
	} else if newLen < curLen {
 | 
			
		||||
		o.shrink(newLen)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
 | 
			
		||||
	if i := toIntStrict(int64(idx)); i >= 0 {
 | 
			
		||||
		if i >= len(*o.data) {
 | 
			
		||||
			if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		o.putIdx(i, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		name := idx.string()
 | 
			
		||||
		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		if idx >= len(*o.data) {
 | 
			
		||||
			if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
 | 
			
		||||
				return res
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		o.putIdx(idx, val, throw)
 | 
			
		||||
	} else {
 | 
			
		||||
		if name == "length" {
 | 
			
		||||
			return o.putLength(o.val.runtime.toLengthUint32(val), throw)
 | 
			
		||||
		}
 | 
			
		||||
		if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
 | 
			
		||||
			o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
 | 
			
		||||
			return false
 | 
			
		||||
		} else {
 | 
			
		||||
			return res
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool {
 | 
			
		||||
	if idx := int64(idx); idx >= 0 {
 | 
			
		||||
		return idx < int64(len(*o.data))
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if idx := strToIdx64(name); idx >= 0 {
 | 
			
		||||
		return idx < int64(len(*o.data))
 | 
			
		||||
	}
 | 
			
		||||
	return name == "length"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if i := toIntStrict(int64(idx)); i >= 0 {
 | 
			
		||||
		if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		val := descr.Value
 | 
			
		||||
		if val == nil {
 | 
			
		||||
			val = _undefined
 | 
			
		||||
		}
 | 
			
		||||
		o.putIdx(i, val, throw)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if idx := strToGoIdx(name); idx >= 0 {
 | 
			
		||||
		if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		val := descr.Value
 | 
			
		||||
		if val == nil {
 | 
			
		||||
			val = _undefined
 | 
			
		||||
		}
 | 
			
		||||
		o.putIdx(idx, val, throw)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
 | 
			
		||||
	}
 | 
			
		||||
	o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) _deleteIdx(idx int64) {
 | 
			
		||||
	if idx < int64(len(*o.data)) {
 | 
			
		||||
		(*o.data)[idx] = nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if idx := strToIdx64(name); idx >= 0 {
 | 
			
		||||
		o._deleteIdx(idx)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject.deleteStr(name, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) deleteIdx(i valueInt, throw bool) bool {
 | 
			
		||||
	idx := int64(i)
 | 
			
		||||
	if idx >= 0 {
 | 
			
		||||
		o._deleteIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type goslicePropIter struct {
 | 
			
		||||
	o          *objectGoSlice
 | 
			
		||||
	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: newStringValue(name), enumerable: _ENUM_TRUE}, i.next
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return propIterItem{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	return (&goslicePropIter{
 | 
			
		||||
		o:     o,
 | 
			
		||||
		limit: len(*o.data),
 | 
			
		||||
	}).next
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) stringKeys(_ bool, accum []Value) []Value {
 | 
			
		||||
	for i := range *o.data {
 | 
			
		||||
		accum = append(accum, asciiString(strconv.Itoa(i)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return accum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) export(*objectExportCtx) interface{} {
 | 
			
		||||
	if o.origIsPtr {
 | 
			
		||||
		return o.data
 | 
			
		||||
	}
 | 
			
		||||
	return *o.data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) exportType() reflect.Type {
 | 
			
		||||
	if o.origIsPtr {
 | 
			
		||||
		return reflectTypeArrayPtr
 | 
			
		||||
	}
 | 
			
		||||
	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) esValue() Value {
 | 
			
		||||
	return o.val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) reflectValue() reflect.Value {
 | 
			
		||||
	return reflect.ValueOf(o.data).Elem()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) setReflectValue(value reflect.Value) {
 | 
			
		||||
	o.data = value.Addr().Interface().(*[]interface{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) sortLen() int {
 | 
			
		||||
	return len(*o.data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) sortGet(i int) Value {
 | 
			
		||||
	return o.getIdx(valueInt(i), nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSlice) swap(i int, j int) {
 | 
			
		||||
	(*o.data)[i], (*o.data)[j] = (*o.data)[j], (*o.data)[i]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								goja/object_goslice_reflect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								goja/object_goslice_reflect.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"math/bits"
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type objectGoSliceReflect struct {
 | 
			
		||||
	objectGoArrayReflect
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) init() {
 | 
			
		||||
	o.objectGoArrayReflect._init()
 | 
			
		||||
	o.lengthProp.writable = true
 | 
			
		||||
	o.putIdx = o._putIdx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) _putIdx(idx int, v Value, throw bool) bool {
 | 
			
		||||
	if idx >= o.fieldsValue.Len() {
 | 
			
		||||
		o.grow(idx + 1)
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoArrayReflect._putIdx(idx, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) grow(size int) {
 | 
			
		||||
	oldcap := o.fieldsValue.Cap()
 | 
			
		||||
	if oldcap < size {
 | 
			
		||||
		n := reflect.MakeSlice(o.fieldsValue.Type(), size, growCap(size, o.fieldsValue.Len(), oldcap))
 | 
			
		||||
		reflect.Copy(n, o.fieldsValue)
 | 
			
		||||
		o.fieldsValue.Set(n)
 | 
			
		||||
		l := len(o.valueCache)
 | 
			
		||||
		if l > size {
 | 
			
		||||
			l = size
 | 
			
		||||
		}
 | 
			
		||||
		for i, w := range o.valueCache[:l] {
 | 
			
		||||
			if w != nil {
 | 
			
		||||
				w.setReflectValue(o.fieldsValue.Index(i))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		tail := o.fieldsValue.Slice(o.fieldsValue.Len(), size)
 | 
			
		||||
		zero := reflect.Zero(o.fieldsValue.Type().Elem())
 | 
			
		||||
		for i := 0; i < tail.Len(); i++ {
 | 
			
		||||
			tail.Index(i).Set(zero)
 | 
			
		||||
		}
 | 
			
		||||
		o.fieldsValue.SetLen(size)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) shrink(size int) {
 | 
			
		||||
	o.valueCache.shrink(size)
 | 
			
		||||
	tail := o.fieldsValue.Slice(size, o.fieldsValue.Len())
 | 
			
		||||
	zero := reflect.Zero(o.fieldsValue.Type().Elem())
 | 
			
		||||
	for i := 0; i < tail.Len(); i++ {
 | 
			
		||||
		tail.Index(i).Set(zero)
 | 
			
		||||
	}
 | 
			
		||||
	o.fieldsValue.SetLen(size)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) putLength(v uint32, throw bool) bool {
 | 
			
		||||
	if bits.UintSize == 32 && v > math.MaxInt32 {
 | 
			
		||||
		panic(rangeError("Integer value overflows 32-bit int"))
 | 
			
		||||
	}
 | 
			
		||||
	newLen := int(v)
 | 
			
		||||
	curLen := o.fieldsValue.Len()
 | 
			
		||||
	if newLen > curLen {
 | 
			
		||||
		o.grow(newLen)
 | 
			
		||||
	} else if newLen < curLen {
 | 
			
		||||
		o.shrink(newLen)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return o.putLength(o.val.runtime.toLengthUint32(val), throw)
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoArrayReflect.setOwnStr(name, val, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
 | 
			
		||||
	}
 | 
			
		||||
	return o.objectGoArrayReflect.defineOwnPropertyStr(name, descr, throw)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										520
									
								
								goja/object_goslice_reflect_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										520
									
								
								goja/object_goslice_reflect_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,520 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"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[1])
 | 
			
		||||
	}
 | 
			
		||||
	if a[2] != 42 {
 | 
			
		||||
		t.Fatalf("a[2] = %d, expected 42", a[2])
 | 
			
		||||
	}
 | 
			
		||||
	if a[3] != 0 {
 | 
			
		||||
		t.Fatalf("a[3] = %d, expected 0", a[3])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectPush(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := New()
 | 
			
		||||
 | 
			
		||||
	t.Run("Can push to array by array ptr", func(t *testing.T) {
 | 
			
		||||
		a := []int8{1}
 | 
			
		||||
		r.Set("a", &a)
 | 
			
		||||
		_, err := r.RunString(`a.push (10)`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if a[1] != 10 {
 | 
			
		||||
			t.Fatalf("a[1] = %d, expected 10", a[1])
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Can push to array by struct ptr", func(t *testing.T) {
 | 
			
		||||
		type testStr struct {
 | 
			
		||||
			A []int
 | 
			
		||||
		}
 | 
			
		||||
		a := testStr{
 | 
			
		||||
			A: []int{2},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.Set("a", &a)
 | 
			
		||||
		_, err := r.RunString(`a.A.push (10)`)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if a.A[1] != 10 {
 | 
			
		||||
			t.Fatalf("a[1] = %v, expected 10", a)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectStructField(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	var s struct {
 | 
			
		||||
		A []int
 | 
			
		||||
		B *[]int
 | 
			
		||||
	}
 | 
			
		||||
	vm.Set("s", &s)
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
		'use strict';
 | 
			
		||||
		s.A.push(1);
 | 
			
		||||
		if (s.B !== null) {
 | 
			
		||||
			throw new Error("s.B is not null: " + s.B);
 | 
			
		||||
		}
 | 
			
		||||
		s.B = [2];
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(s.A) != 1 || s.A[0] != 1 {
 | 
			
		||||
		t.Fatalf("s.A: %v", s.A)
 | 
			
		||||
	}
 | 
			
		||||
	if len(*s.B) != 1 || (*s.B)[0] != 2 {
 | 
			
		||||
		t.Fatalf("s.B: %v", *s.B)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectExportToStructField(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v, err := vm.RunString(`({A: [1], B: [2]})`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	var s struct {
 | 
			
		||||
		A []int
 | 
			
		||||
		B *[]int
 | 
			
		||||
	}
 | 
			
		||||
	err = vm.ExportTo(v, &s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(s.A) != 1 || s.A[0] != 1 {
 | 
			
		||||
		t.Fatalf("s.A: %v", s.A)
 | 
			
		||||
	}
 | 
			
		||||
	if len(*s.B) != 1 || (*s.B)[0] != 2 {
 | 
			
		||||
		t.Fatalf("s.B: %v", *s.B)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectProtoMethod(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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type gosliceReflect_withMethods []interface{}
 | 
			
		||||
 | 
			
		||||
func (s gosliceReflect_withMethods) Method() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectMethod(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	typeof a === "object" && a[0] === 42 && a.Method() === true;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	a := make(gosliceReflect_withMethods, 1)
 | 
			
		||||
	a[0] = 42
 | 
			
		||||
	vm.Set("a", a)
 | 
			
		||||
	v, err := vm.RunString(SCRIPT)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.StrictEquals(valueTrue) {
 | 
			
		||||
		t.Fatalf("Expected true, got %v", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectGetStr(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	v := r.ToValue([]string{"test"})
 | 
			
		||||
	if o, ok := v.(*Object); ok {
 | 
			
		||||
		if e := o.Get("0").Export(); e != "test" {
 | 
			
		||||
			t.Fatalf("Unexpected o.Get(\"0\"): %v", e)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectNilObjectIfaceVal(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []Value{(*Object)(nil)}
 | 
			
		||||
	r.Set("a", a)
 | 
			
		||||
	ret, err := r.RunString(`
 | 
			
		||||
	""+a[0];
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !asciiString("null").SameAs(ret) {
 | 
			
		||||
		t.Fatalf("ret: %v", ret)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectSetLength(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []int{1, 2, 3, 4}
 | 
			
		||||
	b := []testing.TB{&testing.T{}, &testing.T{}, (*testing.T)(nil)}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	r.Set("b", &b)
 | 
			
		||||
	_, err := r.RunString(`
 | 
			
		||||
	'use strict';
 | 
			
		||||
	a.length = 3;
 | 
			
		||||
	if (a.length !== 3) {
 | 
			
		||||
		throw new Error("length="+a.length);
 | 
			
		||||
	}
 | 
			
		||||
	if (a[3] !== undefined) {
 | 
			
		||||
		throw new Error("a[3]="+a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	a.length = 5;
 | 
			
		||||
	if (a.length !== 5) {
 | 
			
		||||
		throw new Error("a.length="+a.length);
 | 
			
		||||
	}
 | 
			
		||||
	if (a[3] !== 0) {
 | 
			
		||||
		throw new Error("a[3]="+a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	if (a[4] !== 0) {
 | 
			
		||||
		throw new Error("a[4]="+a[4]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.length = 3;
 | 
			
		||||
	if (b.length !== 3) {
 | 
			
		||||
		throw new Error("b.length="+b.length);
 | 
			
		||||
	}
 | 
			
		||||
	if (b[3] !== undefined) {
 | 
			
		||||
		throw new Error("b[3]="+b[3]);
 | 
			
		||||
	}
 | 
			
		||||
	b.length = 5;
 | 
			
		||||
	if (b.length !== 5) {
 | 
			
		||||
		throw new Error("length="+b.length);
 | 
			
		||||
	}
 | 
			
		||||
	if (b[3] !== null) {
 | 
			
		||||
		throw new Error("b[3]="+b[3]);
 | 
			
		||||
	}
 | 
			
		||||
	if (b[4] !== null) {
 | 
			
		||||
		throw new Error("b[4]="+b[4]);
 | 
			
		||||
	}
 | 
			
		||||
	if (b[2] !== null) {
 | 
			
		||||
		throw new Error("b[2]="+b[2]);
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectProto(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []*Object{{}, nil, {}}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	r.testScriptWithTestLib(`
 | 
			
		||||
	var proto = [,2,,4];
 | 
			
		||||
	Object.setPrototypeOf(a, proto);
 | 
			
		||||
	assert.sameValue(a[1], null, "a[1]");
 | 
			
		||||
	assert.sameValue(a[3], 4, "a[3]");
 | 
			
		||||
	var desc = Object.getOwnPropertyDescriptor(a, "1");
 | 
			
		||||
	assert.sameValue(desc.value, null, "desc.value");
 | 
			
		||||
	assert(desc.writable, "writable");
 | 
			
		||||
	assert(desc.enumerable, "enumerable");
 | 
			
		||||
	assert(!desc.configurable, "configurable");
 | 
			
		||||
	var v5;
 | 
			
		||||
	Object.defineProperty(proto, "5", {
 | 
			
		||||
		set: function(v) {
 | 
			
		||||
			v5 = v;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	a[5] = "test";
 | 
			
		||||
	assert.sameValue(v5, "test", "v5");
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectProtoProto(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []*Object{{}, nil, {}}
 | 
			
		||||
	proto := []*Object{{}, {}, {}, {}}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	r.Set("proto", proto)
 | 
			
		||||
	_, err := r.RunString(`
 | 
			
		||||
	"use strict";
 | 
			
		||||
	var protoproto = {};
 | 
			
		||||
	Object.defineProperty(protoproto, "3", {
 | 
			
		||||
		value: 42
 | 
			
		||||
	});
 | 
			
		||||
	Object.setPrototypeOf(proto, protoproto);
 | 
			
		||||
	Object.setPrototypeOf(a, proto);
 | 
			
		||||
	if (a.hasOwnProperty("3")) {
 | 
			
		||||
		throw new Error("a.hasOwnProperty(\"3\")");
 | 
			
		||||
	}
 | 
			
		||||
	if (a[3] !== null) {
 | 
			
		||||
		throw new Error("a[3]="+a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	a[3] = null;
 | 
			
		||||
	if (a[3] !== null) {
 | 
			
		||||
		throw new Error("a[3]=" + a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectDelete(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []*Object{{}, nil, {}}
 | 
			
		||||
	r.Set("a", a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	delete a[0] && delete a[1] && delete a[3];
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if v != valueTrue {
 | 
			
		||||
		t.Fatalf("not true: %v", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectPop(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []string{"1", "", "3"}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	a.pop()
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.SameAs(asciiString("3")) {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectPopNoPtr(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []string{"1", "", "3"}
 | 
			
		||||
	r.Set("a", a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	a.pop()
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.SameAs(asciiString("3")) {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectLengthProperty(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("s", []int{2, 3, 4})
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
	if (!s.hasOwnProperty("length")) {
 | 
			
		||||
		throw new Error("hasOwnProperty() returned false");
 | 
			
		||||
	}
 | 
			
		||||
	let desc = Object.getOwnPropertyDescriptor(s, "length");
 | 
			
		||||
	if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) {
 | 
			
		||||
		throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type testCustomSliceWithMethods []int
 | 
			
		||||
 | 
			
		||||
func (a testCustomSliceWithMethods) Method() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectMethods(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("s", testCustomSliceWithMethods{1, 2, 3})
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
	if (!s.hasOwnProperty("Method")) {
 | 
			
		||||
		throw new Error("hasOwnProperty() returned false");
 | 
			
		||||
	}
 | 
			
		||||
	let desc = Object.getOwnPropertyDescriptor(s, "Method");
 | 
			
		||||
	if (desc.value() !== true || desc.writable || !desc.enumerable || desc.configurable) {
 | 
			
		||||
		throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectExportAfterGrow(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("a", []int{1})
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
		a.push(2);
 | 
			
		||||
		a;
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	exp := v.Export()
 | 
			
		||||
	if a, ok := exp.([]int); ok {
 | 
			
		||||
		if len(a) != 2 || a[0] != 1 || a[1] != 2 {
 | 
			
		||||
			t.Fatal(a)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Wrong type: %T", exp)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectSort(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	type Thing struct{ Name string }
 | 
			
		||||
	vm.Set("v", []*Thing{
 | 
			
		||||
		{Name: "log"},
 | 
			
		||||
		{Name: "etc"},
 | 
			
		||||
		{Name: "test"},
 | 
			
		||||
		{Name: "bin"},
 | 
			
		||||
	})
 | 
			
		||||
	ret, err := vm.RunString(`
 | 
			
		||||
//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name);
 | 
			
		||||
	const tmp = v[0];
 | 
			
		||||
	v[0] = v[1];
 | 
			
		||||
	v[1] = tmp;
 | 
			
		||||
	v[0].Name + v[1].Name;
 | 
			
		||||
`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log(ret.Export())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflect111(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("v", []int32{
 | 
			
		||||
		1, 2,
 | 
			
		||||
	})
 | 
			
		||||
	ret, err := vm.RunString(`
 | 
			
		||||
//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name);
 | 
			
		||||
	const tmp = v[0];
 | 
			
		||||
	v[0] = v[1];
 | 
			
		||||
	v[1] = tmp;
 | 
			
		||||
	"" + v[0] + v[1];
 | 
			
		||||
`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log(ret.Export())
 | 
			
		||||
	a := []int{1, 2}
 | 
			
		||||
	a0 := reflect.ValueOf(a).Index(0)
 | 
			
		||||
	a0.Set(reflect.ValueOf(0))
 | 
			
		||||
	t.Log(a[0])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceReflectExternalLenUpdate(t *testing.T) {
 | 
			
		||||
	data := &[]int{1}
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("data", data)
 | 
			
		||||
	vm.Set("append", func(a *[]int, v int) {
 | 
			
		||||
		if a != data {
 | 
			
		||||
			panic(vm.NewTypeError("a != data"))
 | 
			
		||||
		}
 | 
			
		||||
		*a = append(*a, v)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	vm.testScriptWithTestLib(`
 | 
			
		||||
		assert.sameValue(data.length, 1);
 | 
			
		||||
 | 
			
		||||
        // modify with js
 | 
			
		||||
        data.push(1);
 | 
			
		||||
		assert.sameValue(data.length, 2);
 | 
			
		||||
 | 
			
		||||
        // modify with go
 | 
			
		||||
        append(data, 2);
 | 
			
		||||
		assert.sameValue(data.length, 3);
 | 
			
		||||
    `, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkGoSliceReflectSet(b *testing.B) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	a := vm.ToValue([]int{1}).(*Object)
 | 
			
		||||
	b.ResetTimer()
 | 
			
		||||
	v := intToValue(0)
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		a.Set("0", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										298
									
								
								goja/object_goslice_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								goja/object_goslice_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,298 @@
 | 
			
		||||
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 TestGoSliceProtoMethod(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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceSetLength(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, 2, 3, 4}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	_, err := r.RunString(`
 | 
			
		||||
	'use strict';
 | 
			
		||||
	a.length = 3;
 | 
			
		||||
	if (a.length !== 3) {
 | 
			
		||||
		throw new Error("length="+a.length);
 | 
			
		||||
	}
 | 
			
		||||
	if (a[3] !== undefined) {
 | 
			
		||||
		throw new Error("a[3](1)="+a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	a.length = 5;
 | 
			
		||||
	if (a.length !== 5) {
 | 
			
		||||
		throw new Error("length="+a.length);
 | 
			
		||||
	}
 | 
			
		||||
	if (a[3] !== null) {
 | 
			
		||||
		throw new Error("a[3](2)="+a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	if (a[4] !== null) {
 | 
			
		||||
		throw new Error("a[4]="+a[4]);
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceProto(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, nil, 3}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	r.testScriptWithTestLib(`
 | 
			
		||||
	var proto = [,2,,4];
 | 
			
		||||
	Object.setPrototypeOf(a, proto);
 | 
			
		||||
	assert.sameValue(a[1], null, "a[1]");
 | 
			
		||||
	assert.sameValue(a[3], 4, "a[3]");
 | 
			
		||||
	var desc = Object.getOwnPropertyDescriptor(a, "1");
 | 
			
		||||
	assert.sameValue(desc.value, null, "desc.value");
 | 
			
		||||
	assert(desc.writable, "writable");
 | 
			
		||||
	assert(desc.enumerable, "enumerable");
 | 
			
		||||
	assert(!desc.configurable, "configurable");
 | 
			
		||||
	var v5;
 | 
			
		||||
	Object.defineProperty(proto, "5", {
 | 
			
		||||
		set: function(v) {
 | 
			
		||||
			v5 = v;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	a[5] = "test";
 | 
			
		||||
	assert.sameValue(v5, "test", "v5");
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceProtoProto(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, nil, 3}
 | 
			
		||||
	proto := []interface{}{1, 2, 3, 4}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	r.Set("proto", proto)
 | 
			
		||||
	_, err := r.RunString(`
 | 
			
		||||
	"use strict";
 | 
			
		||||
	var protoproto = Object.create(null);
 | 
			
		||||
	Object.defineProperty(protoproto, "3", {
 | 
			
		||||
		value: 42
 | 
			
		||||
	});
 | 
			
		||||
	Object.setPrototypeOf(proto, protoproto);
 | 
			
		||||
	Object.setPrototypeOf(a, proto);
 | 
			
		||||
	a[3] = 11;
 | 
			
		||||
	if (a[3] !== 11) {
 | 
			
		||||
		throw new Error("a[3]=" + a[3]);
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceDelete(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, nil, 3}
 | 
			
		||||
	r.Set("a", a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	delete a[0] && delete a[1] && delete a[3];
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if v != valueTrue {
 | 
			
		||||
		t.Fatalf("not true: %v", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSlicePop(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, nil, 3}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	a.pop()
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.SameAs(intToValue(3)) {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSlicePopNoPtr(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, nil, 3}
 | 
			
		||||
	r.Set("a", a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	a.pop()
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.SameAs(intToValue(3)) {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceShift(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	a := []interface{}{1, nil, 3}
 | 
			
		||||
	r.Set("a", &a)
 | 
			
		||||
	v, err := r.RunString(`
 | 
			
		||||
	a.shift()
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if !v.SameAs(intToValue(1)) {
 | 
			
		||||
		t.Fatal(v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceLengthProperty(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("s", []interface{}{2, 3, 4})
 | 
			
		||||
	_, err := vm.RunString(`
 | 
			
		||||
	if (!s.hasOwnProperty("length")) {
 | 
			
		||||
		throw new Error("hasOwnProperty() returned false");
 | 
			
		||||
	}
 | 
			
		||||
	let desc = Object.getOwnPropertyDescriptor(s, "length");
 | 
			
		||||
	if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) {
 | 
			
		||||
		throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
 | 
			
		||||
	}
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceSort(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	s := []interface{}{4, 2, 3}
 | 
			
		||||
	vm.Set("s", &s)
 | 
			
		||||
	_, err := vm.RunString(`s.sort()`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(s) != 3 {
 | 
			
		||||
		t.Fatalf("len: %d", len(s))
 | 
			
		||||
	}
 | 
			
		||||
	if s[0] != 2 || s[1] != 3 || s[2] != 4 {
 | 
			
		||||
		t.Fatalf("val: %v", s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceToString(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	s := []interface{}{4, 2, 3}
 | 
			
		||||
	vm.Set("s", &s)
 | 
			
		||||
	res, err := vm.RunString("`${s}`")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if exp := res.Export(); exp != "4,2,3" {
 | 
			
		||||
		t.Fatal(exp)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGoSliceExternalLenUpdate(t *testing.T) {
 | 
			
		||||
	data := &[]interface{}{1}
 | 
			
		||||
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("data", data)
 | 
			
		||||
	vm.Set("append", func(a *[]interface{}, v int) {
 | 
			
		||||
		if a != data {
 | 
			
		||||
			panic(vm.NewTypeError("a != data"))
 | 
			
		||||
		}
 | 
			
		||||
		*a = append(*a, v)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	vm.testScriptWithTestLib(`
 | 
			
		||||
		assert.sameValue(data.length, 1);
 | 
			
		||||
 | 
			
		||||
        // modify with js
 | 
			
		||||
        data.push(1);
 | 
			
		||||
		assert.sameValue(data.length, 2);
 | 
			
		||||
 | 
			
		||||
        // modify with go
 | 
			
		||||
        append(data, 2);
 | 
			
		||||
		assert.sameValue(data.length, 3);
 | 
			
		||||
    `, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										469
									
								
								goja/object_template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										469
									
								
								goja/object_template.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,469 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
	"math"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sort"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type templatePropFactory func(*Runtime) Value
 | 
			
		||||
 | 
			
		||||
type objectTemplate struct {
 | 
			
		||||
	propNames []unistring.String
 | 
			
		||||
	props     map[unistring.String]templatePropFactory
 | 
			
		||||
 | 
			
		||||
	symProps     map[*Symbol]templatePropFactory
 | 
			
		||||
	symPropNames []*Symbol
 | 
			
		||||
 | 
			
		||||
	protoFactory func(*Runtime) *Object
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type templatedObject struct {
 | 
			
		||||
	baseObject
 | 
			
		||||
	tmpl *objectTemplate
 | 
			
		||||
 | 
			
		||||
	protoMaterialised bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type templatedFuncObject struct {
 | 
			
		||||
	templatedObject
 | 
			
		||||
 | 
			
		||||
	f         func(FunctionCall) Value
 | 
			
		||||
	construct func(args []Value, newTarget *Object) *Object
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This type exists because Array.prototype is supposed to be an array itself and I could not find
 | 
			
		||||
// a different way of implementing it without either introducing another layer of interfaces or hoisting
 | 
			
		||||
// the templates to baseObject both of which would have had a negative effect on the performance.
 | 
			
		||||
// The implementation is as simple as possible and is not optimised in any way, but I very much doubt anybody
 | 
			
		||||
// uses Array.prototype as an actual array.
 | 
			
		||||
type templatedArrayObject struct {
 | 
			
		||||
	templatedObject
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newObjectTemplate() *objectTemplate {
 | 
			
		||||
	return &objectTemplate{
 | 
			
		||||
		props: make(map[unistring.String]templatePropFactory),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *objectTemplate) putStr(name unistring.String, f templatePropFactory) {
 | 
			
		||||
	t.props[name] = f
 | 
			
		||||
	t.propNames = append(t.propNames, name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *objectTemplate) putSym(s *Symbol, f templatePropFactory) {
 | 
			
		||||
	if t.symProps == nil {
 | 
			
		||||
		t.symProps = make(map[*Symbol]templatePropFactory)
 | 
			
		||||
	}
 | 
			
		||||
	t.symProps[s] = f
 | 
			
		||||
	t.symPropNames = append(t.symPropNames, s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newTemplatedObject(tmpl *objectTemplate, obj *Object) *templatedObject {
 | 
			
		||||
	if obj == nil {
 | 
			
		||||
		obj = &Object{runtime: r}
 | 
			
		||||
	}
 | 
			
		||||
	o := &templatedObject{
 | 
			
		||||
		baseObject: baseObject{
 | 
			
		||||
			class:      classObject,
 | 
			
		||||
			val:        obj,
 | 
			
		||||
			extensible: true,
 | 
			
		||||
		},
 | 
			
		||||
		tmpl: tmpl,
 | 
			
		||||
	}
 | 
			
		||||
	obj.self = o
 | 
			
		||||
	o.init()
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) materialiseProto() {
 | 
			
		||||
	if !o.protoMaterialised {
 | 
			
		||||
		if o.tmpl.protoFactory != nil {
 | 
			
		||||
			o.prototype = o.tmpl.protoFactory(o.val.runtime)
 | 
			
		||||
		}
 | 
			
		||||
		o.protoMaterialised = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) getStr(name unistring.String, receiver Value) Value {
 | 
			
		||||
	ownProp := o.getOwnPropStr(name)
 | 
			
		||||
	if ownProp == nil {
 | 
			
		||||
		o.materialiseProto()
 | 
			
		||||
	}
 | 
			
		||||
	return o.getStrWithOwnProp(ownProp, name, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) getSym(s *Symbol, receiver Value) Value {
 | 
			
		||||
	ownProp := o.getOwnPropSym(s)
 | 
			
		||||
	if ownProp == nil {
 | 
			
		||||
		o.materialiseProto()
 | 
			
		||||
	}
 | 
			
		||||
	return o.getWithOwnProp(ownProp, s, receiver)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) getOwnPropStr(p unistring.String) Value {
 | 
			
		||||
	if v, exists := o.values[p]; exists {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	if f := o.tmpl.props[p]; f != nil {
 | 
			
		||||
		v := f(o.val.runtime)
 | 
			
		||||
		o.values[p] = v
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) materialiseSymbols() {
 | 
			
		||||
	if o.symValues == nil {
 | 
			
		||||
		o.symValues = newOrderedMap(nil)
 | 
			
		||||
		for _, p := range o.tmpl.symPropNames {
 | 
			
		||||
			o.symValues.set(p, o.tmpl.symProps[p](o.val.runtime))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) getOwnPropSym(s *Symbol) Value {
 | 
			
		||||
	if o.symValues == nil && o.tmpl.symProps[s] == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	return o.baseObject.getOwnPropSym(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) materialisePropNames() {
 | 
			
		||||
	if o.propNames == nil {
 | 
			
		||||
		o.propNames = append(([]unistring.String)(nil), o.tmpl.propNames...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
 | 
			
		||||
	existing := o.getOwnPropStr(p) // materialise property (in case it's an accessor)
 | 
			
		||||
	if existing == nil {
 | 
			
		||||
		o.materialiseProto()
 | 
			
		||||
		o.materialisePropNames()
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject.setOwnStr(p, v, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) setOwnSym(name *Symbol, val Value, throw bool) bool {
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	o.materialiseProto()
 | 
			
		||||
	return o.baseObject.setOwnSym(name, val, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	ownProp := o.getOwnPropStr(name)
 | 
			
		||||
	if ownProp == nil {
 | 
			
		||||
		o.materialiseProto()
 | 
			
		||||
	}
 | 
			
		||||
	return o._setForeignStr(name, ownProp, val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) proto() *Object {
 | 
			
		||||
	o.materialiseProto()
 | 
			
		||||
	return o.prototype
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) setProto(proto *Object, throw bool) bool {
 | 
			
		||||
	o.protoMaterialised = true
 | 
			
		||||
	ret := o.baseObject.setProto(proto, throw)
 | 
			
		||||
	if ret {
 | 
			
		||||
		o.protoMaterialised = true
 | 
			
		||||
	}
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	return o.setForeignStr(name.string(), val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) {
 | 
			
		||||
	o.materialiseProto()
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	return o.baseObject.setForeignSym(name, val, receiver, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) hasPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if o.val.self.hasOwnPropertyStr(name) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	o.materialiseProto()
 | 
			
		||||
	if o.prototype != nil {
 | 
			
		||||
		return o.prototype.self.hasPropertyStr(name)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) hasPropertySym(s *Symbol) bool {
 | 
			
		||||
	if o.hasOwnPropertySym(s) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	o.materialiseProto()
 | 
			
		||||
	if o.prototype != nil {
 | 
			
		||||
		return o.prototype.self.hasPropertySym(s)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) hasOwnPropertyStr(name unistring.String) bool {
 | 
			
		||||
	if v, exists := o.values[name]; exists {
 | 
			
		||||
		return v != nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, exists := o.tmpl.props[name]
 | 
			
		||||
	return exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) hasOwnPropertySym(s *Symbol) bool {
 | 
			
		||||
	if o.symValues != nil {
 | 
			
		||||
		return o.symValues.has(s)
 | 
			
		||||
	}
 | 
			
		||||
	_, exists := o.tmpl.symProps[s]
 | 
			
		||||
	return exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	existingVal := o.getOwnPropStr(name)
 | 
			
		||||
	if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
 | 
			
		||||
		o.values[name] = v
 | 
			
		||||
		if existingVal == nil {
 | 
			
		||||
			o.materialisePropNames()
 | 
			
		||||
			names := copyNamesIfNeeded(o.propNames, 1)
 | 
			
		||||
			o.propNames = append(names, name)
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	return o.baseObject.defineOwnPropertySym(s, descr, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) deleteStr(name unistring.String, throw bool) bool {
 | 
			
		||||
	if val := o.getOwnPropStr(name); val != nil {
 | 
			
		||||
		if !o.checkDelete(name, val, throw) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		o.materialisePropNames()
 | 
			
		||||
		o._delete(name)
 | 
			
		||||
		if _, exists := o.tmpl.props[name]; exists {
 | 
			
		||||
			o.values[name] = nil // white hole
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) deleteSym(s *Symbol, throw bool) bool {
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	return o.baseObject.deleteSym(s, throw)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) materialiseProps() {
 | 
			
		||||
	for name, f := range o.tmpl.props {
 | 
			
		||||
		if _, exists := o.values[name]; !exists {
 | 
			
		||||
			o.values[name] = f(o.val.runtime)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.materialisePropNames()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) iterateStringKeys() iterNextFunc {
 | 
			
		||||
	o.materialiseProps()
 | 
			
		||||
	return o.baseObject.iterateStringKeys()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) iterateSymbols() iterNextFunc {
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	return o.baseObject.iterateSymbols()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) stringKeys(all bool, keys []Value) []Value {
 | 
			
		||||
	if all {
 | 
			
		||||
		o.materialisePropNames()
 | 
			
		||||
	} else {
 | 
			
		||||
		o.materialiseProps()
 | 
			
		||||
	}
 | 
			
		||||
	return o.baseObject.stringKeys(all, keys)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) symbols(all bool, accum []Value) []Value {
 | 
			
		||||
	o.materialiseSymbols()
 | 
			
		||||
	return o.baseObject.symbols(all, accum)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *templatedObject) keys(all bool, accum []Value) []Value {
 | 
			
		||||
	return o.symbols(all, o.stringKeys(all, accum))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newTemplatedFuncObject(tmpl *objectTemplate, obj *Object, f func(FunctionCall) Value, ctor func([]Value, *Object) *Object) *templatedFuncObject {
 | 
			
		||||
	if obj == nil {
 | 
			
		||||
		obj = &Object{runtime: r}
 | 
			
		||||
	}
 | 
			
		||||
	o := &templatedFuncObject{
 | 
			
		||||
		templatedObject: templatedObject{
 | 
			
		||||
			baseObject: baseObject{
 | 
			
		||||
				class:      classFunction,
 | 
			
		||||
				val:        obj,
 | 
			
		||||
				extensible: true,
 | 
			
		||||
			},
 | 
			
		||||
			tmpl: tmpl,
 | 
			
		||||
		},
 | 
			
		||||
		f:         f,
 | 
			
		||||
		construct: ctor,
 | 
			
		||||
	}
 | 
			
		||||
	obj.self = o
 | 
			
		||||
	o.init()
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) source() String {
 | 
			
		||||
	return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) export(*objectExportCtx) interface{} {
 | 
			
		||||
	return f.f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
 | 
			
		||||
	if f.f != nil {
 | 
			
		||||
		return f.f, true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) vmCall(vm *vm, n int) {
 | 
			
		||||
	var nf nativeFuncObject
 | 
			
		||||
	nf.f = f.f
 | 
			
		||||
	nf.vmCall(vm, n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
 | 
			
		||||
	return f.construct
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) exportType() reflect.Type {
 | 
			
		||||
	return reflectTypeFunc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) typeOf() String {
 | 
			
		||||
	return stringFunction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *templatedFuncObject) hasInstance(v Value) bool {
 | 
			
		||||
	return hasInstance(f.val, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Runtime) newTemplatedArrayObject(tmpl *objectTemplate, obj *Object) *templatedArrayObject {
 | 
			
		||||
	if obj == nil {
 | 
			
		||||
		obj = &Object{runtime: r}
 | 
			
		||||
	}
 | 
			
		||||
	o := &templatedArrayObject{
 | 
			
		||||
		templatedObject: templatedObject{
 | 
			
		||||
			baseObject: baseObject{
 | 
			
		||||
				class:      classArray,
 | 
			
		||||
				val:        obj,
 | 
			
		||||
				extensible: true,
 | 
			
		||||
			},
 | 
			
		||||
			tmpl: tmpl,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	obj.self = o
 | 
			
		||||
	o.init()
 | 
			
		||||
	return o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) getLenProp() *valueProperty {
 | 
			
		||||
	lenProp, _ := a.getOwnPropStr("length").(*valueProperty)
 | 
			
		||||
	if lenProp == nil {
 | 
			
		||||
		panic(a.val.runtime.NewTypeError("missing length property"))
 | 
			
		||||
	}
 | 
			
		||||
	return lenProp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) _setOwnIdx(idx uint32) {
 | 
			
		||||
	lenProp := a.getLenProp()
 | 
			
		||||
	l := uint32(lenProp.value.ToInteger())
 | 
			
		||||
	if idx >= l {
 | 
			
		||||
		lenProp.value = intToValue(int64(idx) + 1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) setLength(l uint32, throw bool) bool {
 | 
			
		||||
	lenProp := a.getLenProp()
 | 
			
		||||
	oldLen := uint32(lenProp.value.ToInteger())
 | 
			
		||||
	if l == oldLen {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if !lenProp.writable {
 | 
			
		||||
		a.val.runtime.typeErrorResult(throw, "length is not writable")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	ret := true
 | 
			
		||||
	if l < oldLen {
 | 
			
		||||
		a.materialisePropNames()
 | 
			
		||||
		a.fixPropOrder()
 | 
			
		||||
		i := sort.Search(a.idxPropCount, func(idx int) bool {
 | 
			
		||||
			return strToArrayIdx(a.propNames[idx]) >= l
 | 
			
		||||
		})
 | 
			
		||||
		for j := a.idxPropCount - 1; j >= i; j-- {
 | 
			
		||||
			if !a.deleteStr(a.propNames[j], false) {
 | 
			
		||||
				l = strToArrayIdx(a.propNames[j]) + 1
 | 
			
		||||
				ret = false
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	lenProp.value = intToValue(int64(l))
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) setOwnStr(name unistring.String, value Value, throw bool) bool {
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return a.setLength(a.val.runtime.toLengthUint32(value), throw)
 | 
			
		||||
	}
 | 
			
		||||
	if !a.templatedObject.setOwnStr(name, value, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		a._setOwnIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
 | 
			
		||||
	if !a.templatedObject.setOwnStr(p.string(), v, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if idx := toIdx(p); idx != math.MaxUint32 {
 | 
			
		||||
		a._setOwnIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if name == "length" {
 | 
			
		||||
		return a.val.runtime.defineArrayLength(a.getLenProp(), descr, a.setLength, throw)
 | 
			
		||||
	}
 | 
			
		||||
	if !a.templatedObject.defineOwnPropertyStr(name, descr, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if idx := strToArrayIdx(name); idx != math.MaxUint32 {
 | 
			
		||||
		a._setOwnIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *templatedArrayObject) defineOwnPropertyIdx(p valueInt, desc PropertyDescriptor, throw bool) bool {
 | 
			
		||||
	if !a.templatedObject.defineOwnPropertyStr(p.string(), desc, throw) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if idx := toIdx(p); idx != math.MaxUint32 {
 | 
			
		||||
		a._setOwnIdx(idx)
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										668
									
								
								goja/object_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										668
									
								
								goja/object_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,668 @@
 | 
			
		||||
package goja
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDefineProperty(t *testing.T) {
 | 
			
		||||
	r := New()
 | 
			
		||||
	o := r.NewObject()
 | 
			
		||||
 | 
			
		||||
	err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int {
 | 
			
		||||
		return 1
 | 
			
		||||
	}), nil, FLAG_TRUE, FLAG_TRUE)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = o.DefineAccessorProperty("accessor_rw",
 | 
			
		||||
		r.ToValue(func(call FunctionCall) Value {
 | 
			
		||||
			return o.Get("__hidden")
 | 
			
		||||
		}),
 | 
			
		||||
		r.ToValue(func(call FunctionCall) (ret Value) {
 | 
			
		||||
			o.Set("__hidden", call.Argument(0))
 | 
			
		||||
			return
 | 
			
		||||
		}),
 | 
			
		||||
		FLAG_TRUE, FLAG_TRUE)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v := o.Get("accessor_ro"); v.ToInteger() != 1 {
 | 
			
		||||
		t.Fatalf("Unexpected accessor value: %v", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = o.Set("accessor_ro", r.ToValue(2))
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected an error")
 | 
			
		||||
	}
 | 
			
		||||
	if ex, ok := err.(*Exception); ok {
 | 
			
		||||
		if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" {
 | 
			
		||||
			t.Fatalf("Unexpected error: '%s'", msg)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Unexected error type: %T", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = o.Set("accessor_rw", 42)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v := o.Get("accessor_rw"); v.ToInteger() != 42 {
 | 
			
		||||
		t.Fatalf("Unexpected value: %v", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPropertyOrder(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var o = {};
 | 
			
		||||
	var sym1 = Symbol(1);
 | 
			
		||||
	var sym2 = Symbol(2);
 | 
			
		||||
	o[sym2] = 1;
 | 
			
		||||
	o[4294967294] = 1;
 | 
			
		||||
	o[2] = 1;
 | 
			
		||||
	o[1] = 1;
 | 
			
		||||
	o[0] = 1;
 | 
			
		||||
	o["02"] = 1;
 | 
			
		||||
	o[4294967295] = 1;
 | 
			
		||||
	o["01"] = 1;
 | 
			
		||||
	o["00"] = 1;
 | 
			
		||||
	o[sym1] = 1;
 | 
			
		||||
	var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1];
 | 
			
		||||
	var actual = Reflect.ownKeys(o);
 | 
			
		||||
	if (actual.length !== expected.length) {
 | 
			
		||||
		throw new Error("Unexpected length: "+actual.length);
 | 
			
		||||
	}
 | 
			
		||||
	for (var i = 0; i < actual.length; i++) {
 | 
			
		||||
		if (actual[i] !== expected[i]) {
 | 
			
		||||
			throw new Error("Unexpected list: " + actual);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDefinePropertiesSymbol(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var desc = {};
 | 
			
		||||
	desc[Symbol.toStringTag] = {value: "Test"};
 | 
			
		||||
	var o = {};
 | 
			
		||||
	Object.defineProperties(o, desc);
 | 
			
		||||
	o[Symbol.toStringTag] === "Test";
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestObjectShorthandProperties(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var b = 1;
 | 
			
		||||
	var a = {b, get() {return "c"}};
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(a.b, b, "#1");
 | 
			
		||||
	assert.sameValue(a.get(), "c", "#2");
 | 
			
		||||
 | 
			
		||||
	var obj = {
 | 
			
		||||
		w\u0069th() { return 42; }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(obj['with'](), 42, 'property exists');
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestObjectAssign(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	assert.sameValue(Object.assign({ b: 1 }, { get a() {
 | 
			
		||||
          Object.defineProperty(this, "b", {
 | 
			
		||||
            value: 3,
 | 
			
		||||
            enumerable: false
 | 
			
		||||
          });
 | 
			
		||||
        }, b: 2 }).b, 1, "#1");
 | 
			
		||||
 | 
			
		||||
	assert.sameValue(Object.assign({ b: 1 }, { get a() {
 | 
			
		||||
          delete this.b;
 | 
			
		||||
        }, b: 2 }).b, 1, "#2");
 | 
			
		||||
	`
 | 
			
		||||
	testScriptWithTestLib(SCRIPT, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportCircular(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	o := vm.NewObject()
 | 
			
		||||
	o.Set("o", o)
 | 
			
		||||
	v := o.Export()
 | 
			
		||||
	if m, ok := v.(map[string]interface{}); ok {
 | 
			
		||||
		if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() {
 | 
			
		||||
			t.Fatal("Unexpected value")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatal("Unexpected type")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res, err := vm.RunString(`var a = []; a[0] = a;`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	v = res.Export()
 | 
			
		||||
	if a, ok := v.([]interface{}); ok {
 | 
			
		||||
		if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() {
 | 
			
		||||
			t.Fatal("Unexpected value")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatal("Unexpected type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type test_s struct {
 | 
			
		||||
	S *test_s1
 | 
			
		||||
}
 | 
			
		||||
type test_s1 struct {
 | 
			
		||||
	S *test_s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportToCircular(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	o := vm.NewObject()
 | 
			
		||||
	o.Set("o", o)
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	err := vm.ExportTo(o, &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type K string
 | 
			
		||||
	type T map[K]T
 | 
			
		||||
	var m1 T
 | 
			
		||||
	err = vm.ExportTo(o, &m1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type A []A
 | 
			
		||||
	var a A
 | 
			
		||||
	res, err := vm.RunString("var a = []; a[0] = a;")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = vm.ExportTo(res, &a)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if &a[0] != &a[0][0] {
 | 
			
		||||
		t.Fatal("values do not match")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o = vm.NewObject()
 | 
			
		||||
	o.Set("S", o)
 | 
			
		||||
	var s test_s
 | 
			
		||||
	err = vm.ExportTo(o, &s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if s.S.S != &s {
 | 
			
		||||
		t.Fatalf("values do not match: %v, %v", s.S.S, &s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type test_s2 struct {
 | 
			
		||||
		S  interface{}
 | 
			
		||||
		S1 *test_s2
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var s2 test_s2
 | 
			
		||||
	o.Set("S1", o)
 | 
			
		||||
 | 
			
		||||
	err = vm.ExportTo(o, &s2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m, ok := s2.S.(map[string]interface{}); ok {
 | 
			
		||||
		if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() {
 | 
			
		||||
			t.Fatal("Unexpected m.S")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Unexpected s2.S type: %T", s2.S)
 | 
			
		||||
	}
 | 
			
		||||
	if s2.S1 != &s2 {
 | 
			
		||||
		t.Fatal("Unexpected s2.S1")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o1 := vm.NewObject()
 | 
			
		||||
	o1.Set("S", o)
 | 
			
		||||
	o1.Set("S1", o)
 | 
			
		||||
	err = vm.ExportTo(o1, &s2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if s2.S1.S1 != s2.S1 {
 | 
			
		||||
		t.Fatal("Unexpected s2.S1.S1")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportWrappedMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]interface{}{
 | 
			
		||||
		"test": "failed",
 | 
			
		||||
	}
 | 
			
		||||
	exported := vm.ToValue(m).Export()
 | 
			
		||||
	if exportedMap, ok := exported.(map[string]interface{}); ok {
 | 
			
		||||
		exportedMap["test"] = "passed"
 | 
			
		||||
		if v := m["test"]; v != "passed" {
 | 
			
		||||
			t.Fatalf("Unexpected m[\"test\"]: %v", v)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("Unexpected export type: %T", exported)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportToWrappedMap(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := map[string]interface{}{
 | 
			
		||||
		"test": "failed",
 | 
			
		||||
	}
 | 
			
		||||
	var exported map[string]interface{}
 | 
			
		||||
	err := vm.ExportTo(vm.ToValue(m), &exported)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	exported["test"] = "passed"
 | 
			
		||||
	if v := m["test"]; v != "passed" {
 | 
			
		||||
		t.Fatalf("Unexpected m[\"test\"]: %v", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportToWrappedMapCustom(t *testing.T) {
 | 
			
		||||
	type CustomMap map[string]bool
 | 
			
		||||
	vm := New()
 | 
			
		||||
	m := CustomMap{}
 | 
			
		||||
	var exported CustomMap
 | 
			
		||||
	err := vm.ExportTo(vm.ToValue(m), &exported)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	exported["test"] = true
 | 
			
		||||
	if v := m["test"]; v != true {
 | 
			
		||||
		t.Fatalf("Unexpected m[\"test\"]: %v", v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportToSliceNonIterable(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	o := vm.NewObject()
 | 
			
		||||
	var a []interface{}
 | 
			
		||||
	err := vm.ExportTo(o, &a)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected an error")
 | 
			
		||||
	}
 | 
			
		||||
	if len(a) != 0 {
 | 
			
		||||
		t.Fatalf("a: %v", a)
 | 
			
		||||
	}
 | 
			
		||||
	if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" {
 | 
			
		||||
		t.Fatalf("Unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_iterableToSlice() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
	function reverseIterator() {
 | 
			
		||||
	    const arr = this;
 | 
			
		||||
	    let idx = arr.length;
 | 
			
		||||
	    return {
 | 
			
		||||
			next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
	const arr = [1,2,3];
 | 
			
		||||
	arr[Symbol.iterator] = reverseIterator;
 | 
			
		||||
	arr;
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var arr []int
 | 
			
		||||
	err = vm.ExportTo(v, &arr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println(arr)
 | 
			
		||||
	// Output: [3 2 1]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
	function reverseIterator() {
 | 
			
		||||
	    const arr = this;
 | 
			
		||||
	    let idx = arr.length;
 | 
			
		||||
	    return {
 | 
			
		||||
			next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
	const arr = [1,2,3];
 | 
			
		||||
	arr[Symbol.iterator] = reverseIterator;
 | 
			
		||||
	new Proxy(arr, {});
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var arr []int
 | 
			
		||||
	err = vm.ExportTo(v, &arr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if out := fmt.Sprint(arr); out != "[3 2 1]" {
 | 
			
		||||
		t.Fatal(out)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleRuntime_ExportTo_arrayLikeToSlice() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	v, err := vm.RunString(`
 | 
			
		||||
	({
 | 
			
		||||
		length: 3,
 | 
			
		||||
		0: 1,
 | 
			
		||||
		1: 2,
 | 
			
		||||
		2: 3
 | 
			
		||||
	});
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var arr []int
 | 
			
		||||
	err = vm.ExportTo(v, &arr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println(arr)
 | 
			
		||||
	// Output: [1 2 3]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportArrayToArrayMismatchedLengths(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	a := vm.NewArray(1, 2)
 | 
			
		||||
	var a1 [3]int
 | 
			
		||||
	err := vm.ExportTo(a, &a1)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("expected error")
 | 
			
		||||
	}
 | 
			
		||||
	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportIterableToArrayMismatchedLengths(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	a, err := vm.RunString(`
 | 
			
		||||
		new Map([[1, true], [2, true]]);
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var a1 [3]interface{}
 | 
			
		||||
	err = vm.ExportTo(a, &a1)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("expected error")
 | 
			
		||||
	}
 | 
			
		||||
	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	a, err := vm.RunString(`
 | 
			
		||||
		({
 | 
			
		||||
			length: 2,
 | 
			
		||||
			0: true,
 | 
			
		||||
			1: true
 | 
			
		||||
		});
 | 
			
		||||
	`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var a1 [3]interface{}
 | 
			
		||||
	err = vm.ExportTo(a, &a1)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("expected error")
 | 
			
		||||
	}
 | 
			
		||||
	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetForeignReturnValue(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
	var array = [1, 2, 3];
 | 
			
		||||
	var arrayTarget = new Proxy(array, {});
 | 
			
		||||
 | 
			
		||||
	Object.preventExtensions(array);
 | 
			
		||||
 | 
			
		||||
	!Reflect.set(arrayTarget, "foo", 2);
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDefinePropertiesUndefinedVal(t *testing.T) {
 | 
			
		||||
	const SCRIPT = `
 | 
			
		||||
var target = {};
 | 
			
		||||
var sym = Symbol();
 | 
			
		||||
target[sym] = 1;
 | 
			
		||||
target.foo = 2;
 | 
			
		||||
target[0] = 3;
 | 
			
		||||
 | 
			
		||||
var getOwnKeys = [];
 | 
			
		||||
var proxy = new Proxy(target, {
 | 
			
		||||
  getOwnPropertyDescriptor: function(_target, key) {
 | 
			
		||||
    getOwnKeys.push(key);
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Object.defineProperties({}, proxy);
 | 
			
		||||
	true;
 | 
			
		||||
	`
 | 
			
		||||
 | 
			
		||||
	testScript(SCRIPT, valueTrue, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleObject_Delete() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	obj := vm.NewObject()
 | 
			
		||||
	_ = obj.Set("test", true)
 | 
			
		||||
	before := obj.Get("test")
 | 
			
		||||
	_ = obj.Delete("test")
 | 
			
		||||
	after := obj.Get("test")
 | 
			
		||||
	fmt.Printf("before: %v, after: %v", before, after)
 | 
			
		||||
	// Output: before: true, after: <nil>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExampleObject_GetOwnPropertyNames() {
 | 
			
		||||
	vm := New()
 | 
			
		||||
	obj := vm.NewObject()
 | 
			
		||||
	obj.DefineDataProperty("test", vm.ToValue(true), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE) // define a non-enumerable property that Keys() would not return
 | 
			
		||||
	fmt.Print(obj.GetOwnPropertyNames())
 | 
			
		||||
	// Output: [test]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestObjectEquality(t *testing.T) {
 | 
			
		||||
	type CustomInt int
 | 
			
		||||
	type S struct {
 | 
			
		||||
		F CustomInt
 | 
			
		||||
	}
 | 
			
		||||
	vm := New()
 | 
			
		||||
	vm.Set("s", S{})
 | 
			
		||||
	// indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively),
 | 
			
		||||
	// but for objects they must behave the same. De-referencing s.F creates a new wrapper each time.
 | 
			
		||||
	vm.testScriptWithTestLib(`
 | 
			
		||||
	assert.sameValue([s.F].indexOf(s.F), 0, "indexOf");
 | 
			
		||||
	assert([s.F].includes(s.F));
 | 
			
		||||
	`, _undefined, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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++ {
 | 
			
		||||
		v.setOwn(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.setOwnStr("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++ {
 | 
			
		||||
		v.get(n, nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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", nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 BenchmarkToUTF8String(b *testing.B) {
 | 
			
		||||
	var s String = 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.(valueInt); ok {
 | 
			
		||||
			if yi, ok := y.(valueInt); ok {
 | 
			
		||||
				x = 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.(String); ok {
 | 
			
		||||
			if yi, ok := y.(String); ok {
 | 
			
		||||
				z = xi.Concat(yi)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !z.StrictEquals(tst) {
 | 
			
		||||
			b.Fatalf("Unexpected result %v", x)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										184
									
								
								goja/parser/README.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								goja/parser/README.markdown
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										176
									
								
								goja/parser/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								goja/parser/error.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
			
		||||
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.ESCAPED_RESERVED_WORD:
 | 
			
		||||
		return self.error(self.idx, "Keyword must not contain escaped characters")
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1660
									
								
								goja/parser/expression.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1660
									
								
								goja/parser/expression.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1205
									
								
								goja/parser/lexer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1205
									
								
								goja/parser/lexer.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										423
									
								
								goja/parser/lexer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										423
									
								
								goja/parser/lexer_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,423 @@
 | 
			
		||||
package parser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/dop251/goja/file"
 | 
			
		||||
	"github.com/dop251/goja/token"
 | 
			
		||||
	"github.com/dop251/goja/unistring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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, unistring.String(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("#!",
 | 
			
		||||
			token.EOF, "", 3,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test("#!\n1",
 | 
			
		||||
			token.NUMBER, "1", 4,
 | 
			
		||||
			token.EOF, "", 5,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		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, "\\u0024", 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.CLASS, "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("1_000 1_000_000",
 | 
			
		||||
			token.NUMBER, "1_000", 1,
 | 
			
		||||
			token.NUMBER, "1_000_000", 7,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test(`1n`,
 | 
			
		||||
			token.NUMBER, "1n", 1,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test(`1n 9007199254740991n`,
 | 
			
		||||
			token.NUMBER, "1n", 1,
 | 
			
		||||
			token.NUMBER, "9007199254740991n", 4,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test(`0xabn`,
 | 
			
		||||
			token.NUMBER, "0xabn", 1,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test(`0xabcdef0123456789abcdef0123n`,
 | 
			
		||||
			token.NUMBER, "0xabcdef0123456789abcdef0123n", 1,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test("x ?.30 : false",
 | 
			
		||||
			token.IDENTIFIER, "x", 1,
 | 
			
		||||
			token.QUESTION_MARK, "", 3,
 | 
			
		||||
			token.NUMBER, ".30", 4,
 | 
			
		||||
			token.COLON, "", 8,
 | 
			
		||||
			token.BOOLEAN, "false", 10,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		test("a\n?.b",
 | 
			
		||||
			token.IDENTIFIER, "a", 1,
 | 
			
		||||
			token.QUESTION_DOT, "", 3,
 | 
			
		||||
			token.IDENTIFIER, "b", 5,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		// 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.ILLEGAL, "\"\\x0G\"", 1,
 | 
			
		||||
			//token.STRING, "\"\\x0G\"", 1,
 | 
			
		||||
			token.EOF, "", 7,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user