add http
This commit is contained in:
parent
b7ae15cad0
commit
e690ae764b
99
ai/ai.go
99
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...)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
} else if result != nil {
|
||||
fmt.Println(u.JsonP(result))
|
||||
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(u.BRed(err.Error()))
|
||||
} else if result != nil {
|
||||
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…
Reference in New Issue
Block a user