mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-13 15:27:25 +02:00
@@ -0,0 +1,27 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.out
|
||||
|
||||
.DS_Store
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
language: go
|
||||
arch:
|
||||
- AMD64
|
||||
- ppc64le
|
||||
go:
|
||||
- 1.9
|
||||
- tip
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
============
|
||||
These pieces of code were ported from dotnet/corefx:
|
||||
|
||||
syntax/charclass.go (from RegexCharClass.cs): ported to use the built-in Go unicode classes. Canonicalize is
|
||||
a direct port, but most of the other code required large changes because the C# implementation
|
||||
used a string to represent the CharSet data structure and I cleaned that up in my implementation.
|
||||
|
||||
syntax/code.go (from RegexCode.cs): ported literally with various cleanups and layout to make it more Go-ish.
|
||||
|
||||
syntax/escape.go (from RegexParser.cs): ported Escape method and added some optimizations. Unescape is inspired by
|
||||
the C# implementation but couldn't be directly ported because of the lack of do-while syntax in Go.
|
||||
|
||||
syntax/parser.go (from RegexpParser.cs and RegexOptions.cs): ported parser struct and associated methods as
|
||||
literally as possible. Several language differences required changes. E.g. lack pre/post-fix increments as
|
||||
expressions, lack of do-while loops, lack of overloads, etc.
|
||||
|
||||
syntax/prefix.go (from RegexFCD.cs and RegexBoyerMoore.cs): ported as literally as possible and added support
|
||||
for unicode chars that are longer than the 16-bit char in C# for the 32-bit rune in Go.
|
||||
|
||||
syntax/replacerdata.go (from RegexReplacement.cs): conceptually ported and re-organized to handle differences
|
||||
in charclass implementation, and fix odd code layout between RegexParser.cs, Regex.cs, and RegexReplacement.cs.
|
||||
|
||||
syntax/tree.go (from RegexTree.cs and RegexNode.cs): ported literally as possible.
|
||||
|
||||
syntax/writer.go (from RegexWriter.cs): ported literally with minor changes to make it more Go-ish.
|
||||
|
||||
match.go (from RegexMatch.cs): ported, simplified, and changed to handle Go's lack of inheritence.
|
||||
|
||||
regexp.go (from Regex.cs and RegexOptions.cs): conceptually serves the same "starting point", but is simplified
|
||||
and changed to handle differences in C# strings and Go strings/runes.
|
||||
|
||||
replace.go (from RegexReplacement.cs): ported closely and then cleaned up to combine the MatchEvaluator and
|
||||
simple string replace implementations.
|
||||
|
||||
runner.go (from RegexRunner.cs): ported literally as possible.
|
||||
|
||||
regexp_test.go (from CaptureTests.cs and GroupNamesAndNumbers.cs): conceptually ported, but the code was
|
||||
manually structured like Go tests.
|
||||
|
||||
replace_test.go (from RegexReplaceStringTest0.cs): conceptually ported
|
||||
|
||||
rtl_test.go (from RightToLeft.cs): conceptually ported
|
||||
---
|
||||
dotnet/corefx was released under this license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
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.
|
||||
|
||||
============
|
||||
These pieces of code are copied from the Go framework:
|
||||
|
||||
- The overall directory structure of regexp2 was inspired by the Go runtime regexp package.
|
||||
- The optimization in the escape method of syntax/escape.go is from the Go runtime QuoteMeta() func in regexp/regexp.go
|
||||
- The method signatures in regexp.go are designed to match the Go framework regexp methods closely
|
||||
- func regexp2.MustCompile and func quote are almost identifical to the regexp package versions
|
||||
- BenchmarkMatch* and TestProgramTooLong* funcs in regexp_performance_test.go were copied from the framework
|
||||
regexp/exec_test.go
|
||||
---
|
||||
The Go framework was released under this license:
|
||||
|
||||
Copyright (c) 2012 The Go 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.
|
||||
|
||||
============
|
||||
Some test data were gathered from the Mono project.
|
||||
|
||||
regexp_mono_test.go: ported from https://github.com/mono/mono/blob/master/mcs/class/System/Test/System.Text.RegularExpressions/PerlTrials.cs
|
||||
---
|
||||
Mono tests released under this license:
|
||||
|
||||
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.
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Doug Clark
|
||||
|
||||
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.
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
# regexp2 - full featured regular expressions for Go
|
||||
Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time guarantees like the built-in `regexp` package, but it allows backtracking and is compatible with Perl5 and .NET. You'll likely be better off with the RE2 engine from the `regexp` package and should only use this if you need to write very complex patterns or require compatibility with .NET.
|
||||
|
||||
## Basis of the engine
|
||||
The engine is ported from the .NET framework's System.Text.RegularExpressions.Regex engine. That engine was open sourced in 2015 under the MIT license. There are some fundamental differences between .NET strings and Go strings that required a bit of borrowing from the Go framework regex engine as well. I cleaned up a couple of the dirtier bits during the port (regexcharclass.cs was terrible), but the parse tree, code emmitted, and therefore patterns matched should be identical.
|
||||
|
||||
## New Code Generation
|
||||
For extra performance use `regexp2` with [`regexp2cg`](https://github.com/dlclark/regexp2cg). It is a code generation utility for `regexp2` and you can likely improve your regexp runtime performance by 3-10x in hot code paths. As always you should benchmark your specifics to confirm the results. Give it a try!
|
||||
|
||||
## Installing
|
||||
This is a go-gettable library, so install is easy:
|
||||
|
||||
go get github.com/dlclark/regexp2
|
||||
|
||||
To use the new Code Generation (while it's in beta) you'll need to use the `code_gen` branch:
|
||||
|
||||
go get github.com/dlclark/regexp2@code_gen
|
||||
|
||||
## Usage
|
||||
Usage is similar to the Go `regexp` package. Just like in `regexp`, you start by converting a regex into a state machine via the `Compile` or `MustCompile` methods. They ultimately do the same thing, but `MustCompile` will panic if the regex is invalid. You can then use the provided `Regexp` struct to find matches repeatedly. A `Regexp` struct is safe to use across goroutines.
|
||||
|
||||
```go
|
||||
re := regexp2.MustCompile(`Your pattern`, 0)
|
||||
if isMatch, _ := re.MatchString(`Something to match`); isMatch {
|
||||
//do something
|
||||
}
|
||||
```
|
||||
|
||||
The only error that the `*Match*` methods *should* return is a Timeout if you set the `re.MatchTimeout` field. Any other error is a bug in the `regexp2` package. If you need more details about capture groups in a match then use the `FindStringMatch` method, like so:
|
||||
|
||||
```go
|
||||
if m, _ := re.FindStringMatch(`Something to match`); m != nil {
|
||||
// the whole match is always group 0
|
||||
fmt.Printf("Group 0: %v\n", m.String())
|
||||
|
||||
// you can get all the groups too
|
||||
gps := m.Groups()
|
||||
|
||||
// a group can be captured multiple times, so each cap is separately addressable
|
||||
fmt.Printf("Group 1, first capture", gps[1].Captures[0].String())
|
||||
fmt.Printf("Group 1, second capture", gps[1].Captures[1].String())
|
||||
}
|
||||
```
|
||||
|
||||
Group 0 is embedded in the Match. Group 0 is an automatically-assigned group that encompasses the whole pattern. This means that `m.String()` is the same as `m.Group.String()` and `m.Groups()[0].String()`
|
||||
|
||||
The __last__ capture is embedded in each group, so `g.String()` will return the same thing as `g.Capture.String()` and `g.Captures[len(g.Captures)-1].String()`.
|
||||
|
||||
If you want to find multiple matches from a single input string you should use the `FindNextMatch` method. For example, to implement a function similar to `regexp.FindAllString`:
|
||||
|
||||
```go
|
||||
func regexp2FindAllString(re *regexp2.Regexp, s string) []string {
|
||||
var matches []string
|
||||
m, _ := re.FindStringMatch(s)
|
||||
for m != nil {
|
||||
matches = append(matches, m.String())
|
||||
m, _ = re.FindNextMatch(m)
|
||||
}
|
||||
return matches
|
||||
}
|
||||
```
|
||||
|
||||
`FindNextMatch` is optmized so that it re-uses the underlying string/rune slice.
|
||||
|
||||
The internals of `regexp2` always operate on `[]rune` so `Index` and `Length` data in a `Match` always reference a position in `rune`s rather than `byte`s (even if the input was given as a string). This is a dramatic difference between `regexp` and `regexp2`. It's advisable to use the provided `String()` methods to avoid having to work with indices.
|
||||
|
||||
## Compare `regexp` and `regexp2`
|
||||
| Category | regexp | regexp2 |
|
||||
| --- | --- | --- |
|
||||
| Catastrophic backtracking possible | no, constant execution time guarantees | yes, if your pattern is at risk you can use the `re.MatchTimeout` field |
|
||||
| Python-style capture groups `(?P<name>re)` | yes | no (yes in RE2 compat mode) |
|
||||
| .NET-style capture groups `(?<name>re)` or `(?'name're)` | no | yes |
|
||||
| comments `(?#comment)` | no | yes |
|
||||
| branch numbering reset `(?\|a\|b)` | no | no |
|
||||
| possessive match `(?>re)` | no | yes |
|
||||
| positive lookahead `(?=re)` | no | yes |
|
||||
| negative lookahead `(?!re)` | no | yes |
|
||||
| positive lookbehind `(?<=re)` | no | yes |
|
||||
| negative lookbehind `(?<!re)` | no | yes |
|
||||
| back reference `\1` | no | yes |
|
||||
| named back reference `\k'name'` | no | yes |
|
||||
| named ascii character class `[[:foo:]]`| yes | no (yes in RE2 compat mode) |
|
||||
| conditionals `(?(expr)yes\|no)` | no | yes |
|
||||
|
||||
## RE2 compatibility mode
|
||||
The default behavior of `regexp2` is to match the .NET regexp engine, however the `RE2` option is provided to change the parsing to increase compatibility with RE2. Using the `RE2` option when compiling a regexp will not take away any features, but will change the following behaviors:
|
||||
* add support for named ascii character classes (e.g. `[[:foo:]]`)
|
||||
* add support for python-style capture groups (e.g. `(P<name>re)`)
|
||||
* change singleline behavior for `$` to only match end of string (like RE2) (see [#24](https://github.com/dlclark/regexp2/issues/24))
|
||||
* change the character classes `\d` `\s` and `\w` to match the same characters as RE2. NOTE: if you also use the `ECMAScript` option then this will change the `\s` character class to match ECMAScript instead of RE2. ECMAScript allows more whitespace characters in `\s` than RE2 (but still fewer than the the default behavior).
|
||||
* allow character escape sequences to have defaults. For example, by default `\_` isn't a known character escape and will fail to compile, but in RE2 mode it will match the literal character `_`
|
||||
|
||||
```go
|
||||
re := regexp2.MustCompile(`Your RE2-compatible pattern`, regexp2.RE2)
|
||||
if isMatch, _ := re.MatchString(`Something to match`); isMatch {
|
||||
//do something
|
||||
}
|
||||
```
|
||||
|
||||
This feature is a work in progress and I'm open to ideas for more things to put here (maybe more relaxed character escaping rules?).
|
||||
|
||||
## Catastrophic Backtracking and Timeouts
|
||||
|
||||
`regexp2` supports features that can lead to catastrophic backtracking.
|
||||
`Regexp.MatchTimeout` can be set to to limit the impact of such behavior; the
|
||||
match will fail with an error after approximately MatchTimeout. No timeout
|
||||
checks are done by default.
|
||||
|
||||
Timeout checking is not free. The current timeout checking implementation starts
|
||||
a background worker that updates a clock value approximately once every 100
|
||||
milliseconds. The matching code compares this value against the precomputed
|
||||
deadline for the match. The performance impact is as follows.
|
||||
|
||||
1. A match with a timeout runs almost as fast as a match without a timeout.
|
||||
2. If any live matches have a timeout, there will be a background CPU load
|
||||
(`~0.15%` currently on a modern machine). This load will remain constant
|
||||
regardless of the number of matches done including matches done in parallel.
|
||||
3. If no live matches are using a timeout, the background load will remain
|
||||
until the longest deadline (match timeout + the time when the match started)
|
||||
is reached. E.g., if you set a timeout of one minute the load will persist
|
||||
for approximately a minute even if the match finishes quickly.
|
||||
|
||||
See [PR #58](https://github.com/dlclark/regexp2/pull/58) for more details and
|
||||
alternatives considered.
|
||||
|
||||
## Goroutine leak error
|
||||
If you're using a library during unit tests (e.g. https://github.com/uber-go/goleak) that validates all goroutines are exited then you'll likely get an error if you or any of your dependencies use regex's with a MatchTimeout.
|
||||
To remedy the problem you'll need to tell the unit test to wait until the backgroup timeout goroutine is exited.
|
||||
|
||||
```go
|
||||
func TestSomething(t *testing.T) {
|
||||
defer goleak.VerifyNone(t)
|
||||
defer regexp2.StopTimeoutClock()
|
||||
|
||||
// ... test
|
||||
}
|
||||
|
||||
//or
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// setup
|
||||
// ...
|
||||
|
||||
// run
|
||||
m.Run()
|
||||
|
||||
//tear down
|
||||
regexp2.StopTimeoutClock()
|
||||
goleak.VerifyNone(t)
|
||||
}
|
||||
```
|
||||
|
||||
This will add ~100ms runtime to each test (or TestMain). If that's too much time you can set the clock cycle rate of the timeout goroutine in an init function in a test file. `regexp2.SetTimeoutCheckPeriod` isn't threadsafe so it must be setup before starting any regex's with Timeouts.
|
||||
|
||||
```go
|
||||
func init() {
|
||||
//speed up testing by making the timeout clock 1ms
|
||||
regexp2.SetTimeoutCheckPeriod(time.Millisecond)
|
||||
}
|
||||
```
|
||||
|
||||
## ECMAScript compatibility mode
|
||||
In this mode the engine provides compatibility with the [regex engine](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-regular-expression-objects) described in the ECMAScript specification.
|
||||
|
||||
Additionally a Unicode mode is provided which allows parsing of `\u{CodePoint}` syntax that is only when both are provided.
|
||||
|
||||
## Library features that I'm still working on
|
||||
- Regex split
|
||||
|
||||
## Potential bugs
|
||||
I've run a battery of tests against regexp2 from various sources and found the debug output matches the .NET engine, but .NET and Go handle strings very differently. I've attempted to handle these differences, but most of my testing deals with basic ASCII with a little bit of multi-byte Unicode. There's a chance that there are bugs in the string handling related to character sets with supplementary Unicode chars. Right-to-Left support is coded, but not well tested either.
|
||||
|
||||
## Find a bug?
|
||||
I'm open to new issues and pull requests with tests if you find something odd!
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package regexp2
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fasttime holds a time value (ticks since clock initialization)
|
||||
type fasttime int64
|
||||
|
||||
// fastclock provides a fast clock implementation.
|
||||
//
|
||||
// A background goroutine periodically stores the current time
|
||||
// into an atomic variable.
|
||||
//
|
||||
// A deadline can be quickly checked for expiration by comparing
|
||||
// its value to the clock stored in the atomic variable.
|
||||
//
|
||||
// The goroutine automatically stops once clockEnd is reached.
|
||||
// (clockEnd covers the largest deadline seen so far + some
|
||||
// extra time). This ensures that if regexp2 with timeouts
|
||||
// stops being used we will stop background work.
|
||||
type fastclock struct {
|
||||
// instances of atomicTime must be at the start of the struct (or at least 64-bit aligned)
|
||||
// otherwise 32-bit architectures will panic
|
||||
|
||||
current atomicTime // Current time (approximate)
|
||||
clockEnd atomicTime // When clock updater is supposed to stop (>= any existing deadline)
|
||||
|
||||
// current and clockEnd can be read via atomic loads.
|
||||
// Reads and writes of other fields require mu to be held.
|
||||
mu sync.Mutex
|
||||
start time.Time // Time corresponding to fasttime(0)
|
||||
running bool // Is a clock updater running?
|
||||
}
|
||||
|
||||
var fast fastclock
|
||||
|
||||
// reached returns true if current time is at or past t.
|
||||
func (t fasttime) reached() bool {
|
||||
return fast.current.read() >= t
|
||||
}
|
||||
|
||||
// makeDeadline returns a time that is approximately time.Now().Add(d)
|
||||
func makeDeadline(d time.Duration) fasttime {
|
||||
// Increase the deadline since the clock we are reading may be
|
||||
// just about to tick forwards.
|
||||
end := fast.current.read() + durationToTicks(d+clockPeriod)
|
||||
|
||||
// Start or extend clock if necessary.
|
||||
if end > fast.clockEnd.read() {
|
||||
extendClock(end)
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
// extendClock ensures that clock is live and will run until at least end.
|
||||
func extendClock(end fasttime) {
|
||||
fast.mu.Lock()
|
||||
defer fast.mu.Unlock()
|
||||
|
||||
if fast.start.IsZero() {
|
||||
fast.start = time.Now()
|
||||
}
|
||||
|
||||
// Extend the running time to cover end as well as a bit of slop.
|
||||
if shutdown := end + durationToTicks(time.Second); shutdown > fast.clockEnd.read() {
|
||||
fast.clockEnd.write(shutdown)
|
||||
}
|
||||
|
||||
// Start clock if necessary
|
||||
if !fast.running {
|
||||
fast.running = true
|
||||
go runClock()
|
||||
}
|
||||
}
|
||||
|
||||
// stop the timeout clock in the background
|
||||
// should only used for unit tests to abandon the background goroutine
|
||||
func stopClock() {
|
||||
fast.mu.Lock()
|
||||
if fast.running {
|
||||
fast.clockEnd.write(fasttime(0))
|
||||
}
|
||||
fast.mu.Unlock()
|
||||
|
||||
// pause until not running
|
||||
// get and release the lock
|
||||
isRunning := true
|
||||
for isRunning {
|
||||
time.Sleep(clockPeriod / 2)
|
||||
fast.mu.Lock()
|
||||
isRunning = fast.running
|
||||
fast.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func durationToTicks(d time.Duration) fasttime {
|
||||
// Downscale nanoseconds to approximately a millisecond so that we can avoid
|
||||
// overflow even if the caller passes in math.MaxInt64.
|
||||
return fasttime(d) >> 20
|
||||
}
|
||||
|
||||
const DefaultClockPeriod = 100 * time.Millisecond
|
||||
|
||||
// clockPeriod is the approximate interval between updates of approximateClock.
|
||||
var clockPeriod = DefaultClockPeriod
|
||||
|
||||
func runClock() {
|
||||
fast.mu.Lock()
|
||||
defer fast.mu.Unlock()
|
||||
|
||||
for fast.current.read() <= fast.clockEnd.read() {
|
||||
// Unlock while sleeping.
|
||||
fast.mu.Unlock()
|
||||
time.Sleep(clockPeriod)
|
||||
fast.mu.Lock()
|
||||
|
||||
newTime := durationToTicks(time.Since(fast.start))
|
||||
fast.current.write(newTime)
|
||||
}
|
||||
fast.running = false
|
||||
}
|
||||
|
||||
type atomicTime struct{ v int64 } // Should change to atomic.Int64 when we can use go 1.19
|
||||
|
||||
func (t *atomicTime) read() fasttime { return fasttime(atomic.LoadInt64(&t.v)) }
|
||||
func (t *atomicTime) write(v fasttime) { atomic.StoreInt64(&t.v, int64(v)) }
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
package regexp2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Match is a single regex result match that contains groups and repeated captures
|
||||
//
|
||||
// -Groups
|
||||
// -Capture
|
||||
type Match struct {
|
||||
Group //embeded group 0
|
||||
|
||||
regex *Regexp
|
||||
otherGroups []Group
|
||||
|
||||
// input to the match
|
||||
textpos int
|
||||
textstart int
|
||||
|
||||
capcount int
|
||||
caps []int
|
||||
sparseCaps map[int]int
|
||||
|
||||
// output from the match
|
||||
matches [][]int
|
||||
matchcount []int
|
||||
|
||||
// whether we've done any balancing with this match. If we
|
||||
// have done balancing, we'll need to do extra work in Tidy().
|
||||
balancing bool
|
||||
}
|
||||
|
||||
// Group is an explicit or implit (group 0) matched group within the pattern
|
||||
type Group struct {
|
||||
Capture // the last capture of this group is embeded for ease of use
|
||||
|
||||
Name string // group name
|
||||
Captures []Capture // captures of this group
|
||||
}
|
||||
|
||||
// Capture is a single capture of text within the larger original string
|
||||
type Capture struct {
|
||||
// the original string
|
||||
text []rune
|
||||
// Index is the position in the underlying rune slice where the first character of
|
||||
// captured substring was found. Even if you pass in a string this will be in Runes.
|
||||
Index int
|
||||
// Length is the number of runes in the captured substring.
|
||||
Length int
|
||||
}
|
||||
|
||||
// String returns the captured text as a String
|
||||
func (c *Capture) String() string {
|
||||
return string(c.text[c.Index : c.Index+c.Length])
|
||||
}
|
||||
|
||||
// Runes returns the captured text as a rune slice
|
||||
func (c *Capture) Runes() []rune {
|
||||
return c.text[c.Index : c.Index+c.Length]
|
||||
}
|
||||
|
||||
func newMatch(regex *Regexp, capcount int, text []rune, startpos int) *Match {
|
||||
m := Match{
|
||||
regex: regex,
|
||||
matchcount: make([]int, capcount),
|
||||
matches: make([][]int, capcount),
|
||||
textstart: startpos,
|
||||
balancing: false,
|
||||
}
|
||||
m.Name = "0"
|
||||
m.text = text
|
||||
m.matches[0] = make([]int, 2)
|
||||
return &m
|
||||
}
|
||||
|
||||
func newMatchSparse(regex *Regexp, caps map[int]int, capcount int, text []rune, startpos int) *Match {
|
||||
m := newMatch(regex, capcount, text, startpos)
|
||||
m.sparseCaps = caps
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Match) reset(text []rune, textstart int) {
|
||||
m.text = text
|
||||
m.textstart = textstart
|
||||
for i := 0; i < len(m.matchcount); i++ {
|
||||
m.matchcount[i] = 0
|
||||
}
|
||||
m.balancing = false
|
||||
}
|
||||
|
||||
func (m *Match) tidy(textpos int) {
|
||||
|
||||
interval := m.matches[0]
|
||||
m.Index = interval[0]
|
||||
m.Length = interval[1]
|
||||
m.textpos = textpos
|
||||
m.capcount = m.matchcount[0]
|
||||
//copy our root capture to the list
|
||||
m.Group.Captures = []Capture{m.Group.Capture}
|
||||
|
||||
if m.balancing {
|
||||
// The idea here is that we want to compact all of our unbalanced captures. To do that we
|
||||
// use j basically as a count of how many unbalanced captures we have at any given time
|
||||
// (really j is an index, but j/2 is the count). First we skip past all of the real captures
|
||||
// until we find a balance captures. Then we check each subsequent entry. If it's a balance
|
||||
// capture (it's negative), we decrement j. If it's a real capture, we increment j and copy
|
||||
// it down to the last free position.
|
||||
for cap := 0; cap < len(m.matchcount); cap++ {
|
||||
limit := m.matchcount[cap] * 2
|
||||
matcharray := m.matches[cap]
|
||||
|
||||
var i, j int
|
||||
|
||||
for i = 0; i < limit; i++ {
|
||||
if matcharray[i] < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for j = i; i < limit; i++ {
|
||||
if matcharray[i] < 0 {
|
||||
// skip negative values
|
||||
j--
|
||||
} else {
|
||||
// but if we find something positive (an actual capture), copy it back to the last
|
||||
// unbalanced position.
|
||||
if i != j {
|
||||
matcharray[j] = matcharray[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
m.matchcount[cap] = j / 2
|
||||
}
|
||||
|
||||
m.balancing = false
|
||||
}
|
||||
}
|
||||
|
||||
// isMatched tells if a group was matched by capnum
|
||||
func (m *Match) isMatched(cap int) bool {
|
||||
return cap < len(m.matchcount) && m.matchcount[cap] > 0 && m.matches[cap][m.matchcount[cap]*2-1] != (-3+1)
|
||||
}
|
||||
|
||||
// matchIndex returns the index of the last specified matched group by capnum
|
||||
func (m *Match) matchIndex(cap int) int {
|
||||
i := m.matches[cap][m.matchcount[cap]*2-2]
|
||||
if i >= 0 {
|
||||
return i
|
||||
}
|
||||
|
||||
return m.matches[cap][-3-i]
|
||||
}
|
||||
|
||||
// matchLength returns the length of the last specified matched group by capnum
|
||||
func (m *Match) matchLength(cap int) int {
|
||||
i := m.matches[cap][m.matchcount[cap]*2-1]
|
||||
if i >= 0 {
|
||||
return i
|
||||
}
|
||||
|
||||
return m.matches[cap][-3-i]
|
||||
}
|
||||
|
||||
// Nonpublic builder: add a capture to the group specified by "c"
|
||||
func (m *Match) addMatch(c, start, l int) {
|
||||
|
||||
if m.matches[c] == nil {
|
||||
m.matches[c] = make([]int, 2)
|
||||
}
|
||||
|
||||
capcount := m.matchcount[c]
|
||||
|
||||
if capcount*2+2 > len(m.matches[c]) {
|
||||
oldmatches := m.matches[c]
|
||||
newmatches := make([]int, capcount*8)
|
||||
copy(newmatches, oldmatches[:capcount*2])
|
||||
m.matches[c] = newmatches
|
||||
}
|
||||
|
||||
m.matches[c][capcount*2] = start
|
||||
m.matches[c][capcount*2+1] = l
|
||||
m.matchcount[c] = capcount + 1
|
||||
//log.Printf("addMatch: c=%v, i=%v, l=%v ... matches: %v", c, start, l, m.matches)
|
||||
}
|
||||
|
||||
// Nonpublic builder: Add a capture to balance the specified group. This is used by the
|
||||
//
|
||||
// balanced match construct. (?<foo-foo2>...)
|
||||
//
|
||||
// If there were no such thing as backtracking, this would be as simple as calling RemoveMatch(c).
|
||||
// However, since we have backtracking, we need to keep track of everything.
|
||||
func (m *Match) balanceMatch(c int) {
|
||||
m.balancing = true
|
||||
|
||||
// we'll look at the last capture first
|
||||
capcount := m.matchcount[c]
|
||||
target := capcount*2 - 2
|
||||
|
||||
// first see if it is negative, and therefore is a reference to the next available
|
||||
// capture group for balancing. If it is, we'll reset target to point to that capture.
|
||||
if m.matches[c][target] < 0 {
|
||||
target = -3 - m.matches[c][target]
|
||||
}
|
||||
|
||||
// move back to the previous capture
|
||||
target -= 2
|
||||
|
||||
// if the previous capture is a reference, just copy that reference to the end. Otherwise, point to it.
|
||||
if target >= 0 && m.matches[c][target] < 0 {
|
||||
m.addMatch(c, m.matches[c][target], m.matches[c][target+1])
|
||||
} else {
|
||||
m.addMatch(c, -3-target, -4-target /* == -3 - (target + 1) */)
|
||||
}
|
||||
}
|
||||
|
||||
// Nonpublic builder: removes a group match by capnum
|
||||
func (m *Match) removeMatch(c int) {
|
||||
m.matchcount[c]--
|
||||
}
|
||||
|
||||
// GroupCount returns the number of groups this match has matched
|
||||
func (m *Match) GroupCount() int {
|
||||
return len(m.matchcount)
|
||||
}
|
||||
|
||||
// GroupByName returns a group based on the name of the group, or nil if the group name does not exist
|
||||
func (m *Match) GroupByName(name string) *Group {
|
||||
num := m.regex.GroupNumberFromName(name)
|
||||
if num < 0 {
|
||||
return nil
|
||||
}
|
||||
return m.GroupByNumber(num)
|
||||
}
|
||||
|
||||
// GroupByNumber returns a group based on the number of the group, or nil if the group number does not exist
|
||||
func (m *Match) GroupByNumber(num int) *Group {
|
||||
// check our sparse map
|
||||
if m.sparseCaps != nil {
|
||||
if newNum, ok := m.sparseCaps[num]; ok {
|
||||
num = newNum
|
||||
}
|
||||
}
|
||||
if num >= len(m.matchcount) || num < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if num == 0 {
|
||||
return &m.Group
|
||||
}
|
||||
|
||||
m.populateOtherGroups()
|
||||
|
||||
return &m.otherGroups[num-1]
|
||||
}
|
||||
|
||||
// Groups returns all the capture groups, starting with group 0 (the full match)
|
||||
func (m *Match) Groups() []Group {
|
||||
m.populateOtherGroups()
|
||||
g := make([]Group, len(m.otherGroups)+1)
|
||||
g[0] = m.Group
|
||||
copy(g[1:], m.otherGroups)
|
||||
return g
|
||||
}
|
||||
|
||||
func (m *Match) populateOtherGroups() {
|
||||
// Construct all the Group objects first time called
|
||||
if m.otherGroups == nil {
|
||||
m.otherGroups = make([]Group, len(m.matchcount)-1)
|
||||
for i := 0; i < len(m.otherGroups); i++ {
|
||||
m.otherGroups[i] = newGroup(m.regex.GroupNameFromNumber(i+1), m.text, m.matches[i+1], m.matchcount[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Match) groupValueAppendToBuf(groupnum int, buf *bytes.Buffer) {
|
||||
c := m.matchcount[groupnum]
|
||||
if c == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
matches := m.matches[groupnum]
|
||||
|
||||
index := matches[(c-1)*2]
|
||||
last := index + matches[(c*2)-1]
|
||||
|
||||
for ; index < last; index++ {
|
||||
buf.WriteRune(m.text[index])
|
||||
}
|
||||
}
|
||||
|
||||
func newGroup(name string, text []rune, caps []int, capcount int) Group {
|
||||
g := Group{}
|
||||
g.text = text
|
||||
if capcount > 0 {
|
||||
g.Index = caps[(capcount-1)*2]
|
||||
g.Length = caps[(capcount*2)-1]
|
||||
}
|
||||
g.Name = name
|
||||
g.Captures = make([]Capture, capcount)
|
||||
for i := 0; i < capcount; i++ {
|
||||
g.Captures[i] = Capture{
|
||||
text: text,
|
||||
Index: caps[i*2],
|
||||
Length: caps[i*2+1],
|
||||
}
|
||||
}
|
||||
//log.Printf("newGroup! capcount %v, %+v", capcount, g)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (m *Match) dump() string {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteRune('\n')
|
||||
if len(m.sparseCaps) > 0 {
|
||||
for k, v := range m.sparseCaps {
|
||||
fmt.Fprintf(buf, "Slot %v -> %v\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
for i, g := range m.Groups() {
|
||||
fmt.Fprintf(buf, "Group %v (%v), %v caps:\n", i, g.Name, len(g.Captures))
|
||||
|
||||
for _, c := range g.Captures {
|
||||
fmt.Fprintf(buf, " (%v, %v) %v\n", c.Index, c.Length, c.String())
|
||||
}
|
||||
}
|
||||
/*
|
||||
for i := 0; i < len(m.matchcount); i++ {
|
||||
fmt.Fprintf(buf, "\nGroup %v (%v):\n", i, m.regex.GroupNameFromNumber(i))
|
||||
|
||||
for j := 0; j < m.matchcount[i]; j++ {
|
||||
text := ""
|
||||
|
||||
if m.matches[i][j*2] >= 0 {
|
||||
start := m.matches[i][j*2]
|
||||
text = m.text[start : start+m.matches[i][j*2+1]]
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, " (%v, %v) %v\n", m.matches[i][j*2], m.matches[i][j*2+1], text)
|
||||
}
|
||||
}
|
||||
*/
|
||||
return buf.String()
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
Package regexp2 is a regexp package that has an interface similar to Go's framework regexp engine but uses a
|
||||
more feature full regex engine behind the scenes.
|
||||
|
||||
It doesn't have constant time guarantees, but it allows backtracking and is compatible with Perl5 and .NET.
|
||||
You'll likely be better off with the RE2 engine from the regexp package and should only use this if you
|
||||
need to write very complex patterns or require compatibility with .NET.
|
||||
*/
|
||||
package regexp2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dlclark/regexp2/syntax"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultMatchTimeout used when running regexp matches -- "forever"
|
||||
DefaultMatchTimeout = time.Duration(math.MaxInt64)
|
||||
// DefaultUnmarshalOptions used when unmarshaling a regex from text
|
||||
DefaultUnmarshalOptions = None
|
||||
)
|
||||
|
||||
// Regexp is the representation of a compiled regular expression.
|
||||
// A Regexp is safe for concurrent use by multiple goroutines.
|
||||
type Regexp struct {
|
||||
// A match will time out if it takes (approximately) more than
|
||||
// MatchTimeout. This is a safety check in case the match
|
||||
// encounters catastrophic backtracking. The default value
|
||||
// (DefaultMatchTimeout) causes all time out checking to be
|
||||
// suppressed.
|
||||
MatchTimeout time.Duration
|
||||
|
||||
// read-only after Compile
|
||||
pattern string // as passed to Compile
|
||||
options RegexOptions // options
|
||||
|
||||
caps map[int]int // capnum->index
|
||||
capnames map[string]int //capture group name -> index
|
||||
capslist []string //sorted list of capture group names
|
||||
capsize int // size of the capture array
|
||||
|
||||
code *syntax.Code // compiled program
|
||||
|
||||
// cache of machines for running regexp
|
||||
muRun *sync.Mutex
|
||||
runner []*runner
|
||||
}
|
||||
|
||||
// Compile parses a regular expression and returns, if successful,
|
||||
// a Regexp object that can be used to match against text.
|
||||
func Compile(expr string, opt RegexOptions) (*Regexp, error) {
|
||||
// parse it
|
||||
tree, err := syntax.Parse(expr, syntax.RegexOptions(opt))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// translate it to code
|
||||
code, err := syntax.Write(tree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return it
|
||||
return &Regexp{
|
||||
pattern: expr,
|
||||
options: opt,
|
||||
caps: code.Caps,
|
||||
capnames: tree.Capnames,
|
||||
capslist: tree.Caplist,
|
||||
capsize: code.Capsize,
|
||||
code: code,
|
||||
MatchTimeout: DefaultMatchTimeout,
|
||||
muRun: &sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MustCompile is like Compile but panics if the expression cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled regular
|
||||
// expressions.
|
||||
func MustCompile(str string, opt RegexOptions) *Regexp {
|
||||
regexp, error := Compile(str, opt)
|
||||
if error != nil {
|
||||
panic(`regexp2: Compile(` + quote(str) + `): ` + error.Error())
|
||||
}
|
||||
return regexp
|
||||
}
|
||||
|
||||
// Escape adds backslashes to any special characters in the input string
|
||||
func Escape(input string) string {
|
||||
return syntax.Escape(input)
|
||||
}
|
||||
|
||||
// Unescape removes any backslashes from previously-escaped special characters in the input string
|
||||
func Unescape(input string) (string, error) {
|
||||
return syntax.Unescape(input)
|
||||
}
|
||||
|
||||
// SetTimeoutPeriod is a debug function that sets the frequency of the timeout goroutine's sleep cycle.
|
||||
// Defaults to 100ms. The only benefit of setting this lower is that the 1 background goroutine that manages
|
||||
// timeouts may exit slightly sooner after all the timeouts have expired. See Github issue #63
|
||||
func SetTimeoutCheckPeriod(d time.Duration) {
|
||||
clockPeriod = d
|
||||
}
|
||||
|
||||
// StopTimeoutClock should only be used in unit tests to prevent the timeout clock goroutine
|
||||
// from appearing like a leaking goroutine
|
||||
func StopTimeoutClock() {
|
||||
stopClock()
|
||||
}
|
||||
|
||||
// String returns the source text used to compile the regular expression.
|
||||
func (re *Regexp) String() string {
|
||||
return re.pattern
|
||||
}
|
||||
|
||||
func quote(s string) string {
|
||||
if strconv.CanBackquote(s) {
|
||||
return "`" + s + "`"
|
||||
}
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
|
||||
// RegexOptions impact the runtime and parsing behavior
|
||||
// for each specific regex. They are setable in code as well
|
||||
// as in the regex pattern itself.
|
||||
type RegexOptions int32
|
||||
|
||||
const (
|
||||
None RegexOptions = 0x0
|
||||
IgnoreCase = 0x0001 // "i"
|
||||
Multiline = 0x0002 // "m"
|
||||
ExplicitCapture = 0x0004 // "n"
|
||||
Compiled = 0x0008 // "c"
|
||||
Singleline = 0x0010 // "s"
|
||||
IgnorePatternWhitespace = 0x0020 // "x"
|
||||
RightToLeft = 0x0040 // "r"
|
||||
Debug = 0x0080 // "d"
|
||||
ECMAScript = 0x0100 // "e"
|
||||
RE2 = 0x0200 // RE2 (regexp package) compatibility mode
|
||||
Unicode = 0x0400 // "u"
|
||||
)
|
||||
|
||||
func (re *Regexp) RightToLeft() bool {
|
||||
return re.options&RightToLeft != 0
|
||||
}
|
||||
|
||||
func (re *Regexp) Debug() bool {
|
||||
return re.options&Debug != 0
|
||||
}
|
||||
|
||||
// Replace searches the input string and replaces each match found with the replacement text.
|
||||
// Count will limit the number of matches attempted and startAt will allow
|
||||
// us to skip past possible matches at the start of the input (left or right depending on RightToLeft option).
|
||||
// Set startAt and count to -1 to go through the whole string
|
||||
func (re *Regexp) Replace(input, replacement string, startAt, count int) (string, error) {
|
||||
data, err := syntax.NewReplacerData(replacement, re.caps, re.capsize, re.capnames, syntax.RegexOptions(re.options))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//TODO: cache ReplacerData
|
||||
|
||||
return replace(re, data, nil, input, startAt, count)
|
||||
}
|
||||
|
||||
// ReplaceFunc searches the input string and replaces each match found using the string from the evaluator
|
||||
// Count will limit the number of matches attempted and startAt will allow
|
||||
// us to skip past possible matches at the start of the input (left or right depending on RightToLeft option).
|
||||
// Set startAt and count to -1 to go through the whole string.
|
||||
func (re *Regexp) ReplaceFunc(input string, evaluator MatchEvaluator, startAt, count int) (string, error) {
|
||||
return replace(re, nil, evaluator, input, startAt, count)
|
||||
}
|
||||
|
||||
// FindStringMatch searches the input string for a Regexp match
|
||||
func (re *Regexp) FindStringMatch(s string) (*Match, error) {
|
||||
// convert string to runes
|
||||
return re.run(false, -1, getRunes(s))
|
||||
}
|
||||
|
||||
// FindRunesMatch searches the input rune slice for a Regexp match
|
||||
func (re *Regexp) FindRunesMatch(r []rune) (*Match, error) {
|
||||
return re.run(false, -1, r)
|
||||
}
|
||||
|
||||
// FindStringMatchStartingAt searches the input string for a Regexp match starting at the startAt index
|
||||
func (re *Regexp) FindStringMatchStartingAt(s string, startAt int) (*Match, error) {
|
||||
if startAt > len(s) {
|
||||
return nil, errors.New("startAt must be less than the length of the input string")
|
||||
}
|
||||
r, startAt := re.getRunesAndStart(s, startAt)
|
||||
if startAt == -1 {
|
||||
// we didn't find our start index in the string -- that's a problem
|
||||
return nil, errors.New("startAt must align to the start of a valid rune in the input string")
|
||||
}
|
||||
|
||||
return re.run(false, startAt, r)
|
||||
}
|
||||
|
||||
// FindRunesMatchStartingAt searches the input rune slice for a Regexp match starting at the startAt index
|
||||
func (re *Regexp) FindRunesMatchStartingAt(r []rune, startAt int) (*Match, error) {
|
||||
return re.run(false, startAt, r)
|
||||
}
|
||||
|
||||
// FindNextMatch returns the next match in the same input string as the match parameter.
|
||||
// Will return nil if there is no next match or if given a nil match.
|
||||
func (re *Regexp) FindNextMatch(m *Match) (*Match, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// If previous match was empty, advance by one before matching to prevent
|
||||
// infinite loop
|
||||
startAt := m.textpos
|
||||
if m.Length == 0 {
|
||||
if m.textpos == len(m.text) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if re.RightToLeft() {
|
||||
startAt--
|
||||
} else {
|
||||
startAt++
|
||||
}
|
||||
}
|
||||
return re.run(false, startAt, m.text)
|
||||
}
|
||||
|
||||
// MatchString return true if the string matches the regex
|
||||
// error will be set if a timeout occurs
|
||||
func (re *Regexp) MatchString(s string) (bool, error) {
|
||||
m, err := re.run(true, -1, getRunes(s))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return m != nil, nil
|
||||
}
|
||||
|
||||
func (re *Regexp) getRunesAndStart(s string, startAt int) ([]rune, int) {
|
||||
if startAt < 0 {
|
||||
if re.RightToLeft() {
|
||||
r := getRunes(s)
|
||||
return r, len(r)
|
||||
}
|
||||
return getRunes(s), 0
|
||||
}
|
||||
ret := make([]rune, len(s))
|
||||
i := 0
|
||||
runeIdx := -1
|
||||
for strIdx, r := range s {
|
||||
if strIdx == startAt {
|
||||
runeIdx = i
|
||||
}
|
||||
ret[i] = r
|
||||
i++
|
||||
}
|
||||
if startAt == len(s) {
|
||||
runeIdx = i
|
||||
}
|
||||
return ret[:i], runeIdx
|
||||
}
|
||||
|
||||
func getRunes(s string) []rune {
|
||||
return []rune(s)
|
||||
}
|
||||
|
||||
// MatchRunes return true if the runes matches the regex
|
||||
// error will be set if a timeout occurs
|
||||
func (re *Regexp) MatchRunes(r []rune) (bool, error) {
|
||||
m, err := re.run(true, -1, r)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return m != nil, nil
|
||||
}
|
||||
|
||||
// GetGroupNames Returns the set of strings used to name capturing groups in the expression.
|
||||
func (re *Regexp) GetGroupNames() []string {
|
||||
var result []string
|
||||
|
||||
if re.capslist == nil {
|
||||
result = make([]string, re.capsize)
|
||||
|
||||
for i := 0; i < len(result); i++ {
|
||||
result[i] = strconv.Itoa(i)
|
||||
}
|
||||
} else {
|
||||
result = make([]string, len(re.capslist))
|
||||
copy(result, re.capslist)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetGroupNumbers returns the integer group numbers corresponding to a group name.
|
||||
func (re *Regexp) GetGroupNumbers() []int {
|
||||
var result []int
|
||||
|
||||
if re.caps == nil {
|
||||
result = make([]int, re.capsize)
|
||||
|
||||
for i := 0; i < len(result); i++ {
|
||||
result[i] = i
|
||||
}
|
||||
} else {
|
||||
result = make([]int, len(re.caps))
|
||||
|
||||
for k, v := range re.caps {
|
||||
result[v] = k
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GroupNameFromNumber retrieves a group name that corresponds to a group number.
|
||||
// It will return "" for and unknown group number. Unnamed groups automatically
|
||||
// receive a name that is the decimal string equivalent of its number.
|
||||
func (re *Regexp) GroupNameFromNumber(i int) string {
|
||||
if re.capslist == nil {
|
||||
if i >= 0 && i < re.capsize {
|
||||
return strconv.Itoa(i)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
if re.caps != nil {
|
||||
var ok bool
|
||||
if i, ok = re.caps[i]; !ok {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
if i >= 0 && i < len(re.capslist) {
|
||||
return re.capslist[i]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GroupNumberFromName returns a group number that corresponds to a group name.
|
||||
// Returns -1 if the name is not a recognized group name. Numbered groups
|
||||
// automatically get a group name that is the decimal string equivalent of its number.
|
||||
func (re *Regexp) GroupNumberFromName(name string) int {
|
||||
// look up name if we have a hashtable of names
|
||||
if re.capnames != nil {
|
||||
if k, ok := re.capnames[name]; ok {
|
||||
return k
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// convert to an int if it looks like a number
|
||||
result := 0
|
||||
for i := 0; i < len(name); i++ {
|
||||
ch := name[i]
|
||||
|
||||
if ch > '9' || ch < '0' {
|
||||
return -1
|
||||
}
|
||||
|
||||
result *= 10
|
||||
result += int(ch - '0')
|
||||
}
|
||||
|
||||
// return int if it's in range
|
||||
if result >= 0 && result < re.capsize {
|
||||
return result
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler]. The output
|
||||
// matches that of calling the [Regexp.String] method.
|
||||
func (re *Regexp) MarshalText() ([]byte, error) {
|
||||
return []byte(re.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler] by calling
|
||||
// [Compile] on the encoded value.
|
||||
func (re *Regexp) UnmarshalText(text []byte) error {
|
||||
newRE, err := Compile(string(text), DefaultUnmarshalOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*re = *newRE
|
||||
return nil
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package regexp2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/dlclark/regexp2/syntax"
|
||||
)
|
||||
|
||||
const (
|
||||
replaceSpecials = 4
|
||||
replaceLeftPortion = -1
|
||||
replaceRightPortion = -2
|
||||
replaceLastGroup = -3
|
||||
replaceWholeString = -4
|
||||
)
|
||||
|
||||
// MatchEvaluator is a function that takes a match and returns a replacement string to be used
|
||||
type MatchEvaluator func(Match) string
|
||||
|
||||
// Three very similar algorithms appear below: replace (pattern),
|
||||
// replace (evaluator), and split.
|
||||
|
||||
// Replace Replaces all occurrences of the regex in the string with the
|
||||
// replacement pattern.
|
||||
//
|
||||
// Note that the special case of no matches is handled on its own:
|
||||
// with no matches, the input string is returned unchanged.
|
||||
// The right-to-left case is split out because StringBuilder
|
||||
// doesn't handle right-to-left string building directly very well.
|
||||
func replace(regex *Regexp, data *syntax.ReplacerData, evaluator MatchEvaluator, input string, startAt, count int) (string, error) {
|
||||
if count < -1 {
|
||||
return "", errors.New("Count too small")
|
||||
}
|
||||
if count == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
m, err := regex.FindStringMatchStartingAt(input, startAt)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if m == nil {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
text := m.text
|
||||
|
||||
if !regex.RightToLeft() {
|
||||
prevat := 0
|
||||
for m != nil {
|
||||
if m.Index != prevat {
|
||||
buf.WriteString(string(text[prevat:m.Index]))
|
||||
}
|
||||
prevat = m.Index + m.Length
|
||||
if evaluator == nil {
|
||||
replacementImpl(data, buf, m)
|
||||
} else {
|
||||
buf.WriteString(evaluator(*m))
|
||||
}
|
||||
|
||||
count--
|
||||
if count == 0 {
|
||||
break
|
||||
}
|
||||
m, err = regex.FindNextMatch(m)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if prevat < len(text) {
|
||||
buf.WriteString(string(text[prevat:]))
|
||||
}
|
||||
} else {
|
||||
prevat := len(text)
|
||||
var al []string
|
||||
|
||||
for m != nil {
|
||||
if m.Index+m.Length != prevat {
|
||||
al = append(al, string(text[m.Index+m.Length:prevat]))
|
||||
}
|
||||
prevat = m.Index
|
||||
if evaluator == nil {
|
||||
replacementImplRTL(data, &al, m)
|
||||
} else {
|
||||
al = append(al, evaluator(*m))
|
||||
}
|
||||
|
||||
count--
|
||||
if count == 0 {
|
||||
break
|
||||
}
|
||||
m, err = regex.FindNextMatch(m)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if prevat > 0 {
|
||||
buf.WriteString(string(text[:prevat]))
|
||||
}
|
||||
|
||||
for i := len(al) - 1; i >= 0; i-- {
|
||||
buf.WriteString(al[i])
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// Given a Match, emits into the StringBuilder the evaluated
|
||||
// substitution pattern.
|
||||
func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) {
|
||||
for _, r := range data.Rules {
|
||||
|
||||
if r >= 0 { // string lookup
|
||||
buf.WriteString(data.Strings[r])
|
||||
} else if r < -replaceSpecials { // group lookup
|
||||
m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
|
||||
} else {
|
||||
switch -replaceSpecials - 1 - r { // special insertion patterns
|
||||
case replaceLeftPortion:
|
||||
for i := 0; i < m.Index; i++ {
|
||||
buf.WriteRune(m.text[i])
|
||||
}
|
||||
case replaceRightPortion:
|
||||
for i := m.Index + m.Length; i < len(m.text); i++ {
|
||||
buf.WriteRune(m.text[i])
|
||||
}
|
||||
case replaceLastGroup:
|
||||
m.groupValueAppendToBuf(m.GroupCount()-1, buf)
|
||||
case replaceWholeString:
|
||||
for i := 0; i < len(m.text); i++ {
|
||||
buf.WriteRune(m.text[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replacementImplRTL(data *syntax.ReplacerData, al *[]string, m *Match) {
|
||||
l := *al
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for _, r := range data.Rules {
|
||||
buf.Reset()
|
||||
if r >= 0 { // string lookup
|
||||
l = append(l, data.Strings[r])
|
||||
} else if r < -replaceSpecials { // group lookup
|
||||
m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
|
||||
l = append(l, buf.String())
|
||||
} else {
|
||||
switch -replaceSpecials - 1 - r { // special insertion patterns
|
||||
case replaceLeftPortion:
|
||||
for i := 0; i < m.Index; i++ {
|
||||
buf.WriteRune(m.text[i])
|
||||
}
|
||||
case replaceRightPortion:
|
||||
for i := m.Index + m.Length; i < len(m.text); i++ {
|
||||
buf.WriteRune(m.text[i])
|
||||
}
|
||||
case replaceLastGroup:
|
||||
m.groupValueAppendToBuf(m.GroupCount()-1, buf)
|
||||
case replaceWholeString:
|
||||
for i := 0; i < len(m.text); i++ {
|
||||
buf.WriteRune(m.text[i])
|
||||
}
|
||||
}
|
||||
l = append(l, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
*al = l
|
||||
}
|
||||
+1613
File diff suppressed because it is too large
Load Diff
+865
@@ -0,0 +1,865 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// CharSet combines start-end rune ranges and unicode categories representing a set of characters
|
||||
type CharSet struct {
|
||||
ranges []singleRange
|
||||
categories []category
|
||||
sub *CharSet //optional subtractor
|
||||
negate bool
|
||||
anything bool
|
||||
}
|
||||
|
||||
type category struct {
|
||||
negate bool
|
||||
cat string
|
||||
}
|
||||
|
||||
type singleRange struct {
|
||||
first rune
|
||||
last rune
|
||||
}
|
||||
|
||||
const (
|
||||
spaceCategoryText = " "
|
||||
wordCategoryText = "W"
|
||||
)
|
||||
|
||||
var (
|
||||
ecmaSpace = []rune{0x0009, 0x000e, 0x0020, 0x0021, 0x00a0, 0x00a1, 0x1680, 0x1681, 0x2000, 0x200b, 0x2028, 0x202a, 0x202f, 0x2030, 0x205f, 0x2060, 0x3000, 0x3001, 0xfeff, 0xff00}
|
||||
ecmaWord = []rune{0x0030, 0x003a, 0x0041, 0x005b, 0x005f, 0x0060, 0x0061, 0x007b}
|
||||
ecmaDigit = []rune{0x0030, 0x003a}
|
||||
|
||||
re2Space = []rune{0x0009, 0x000b, 0x000c, 0x000e, 0x0020, 0x0021}
|
||||
)
|
||||
|
||||
var (
|
||||
AnyClass = getCharSetFromOldString([]rune{0}, false)
|
||||
ECMAAnyClass = getCharSetFromOldString([]rune{0, 0x000a, 0x000b, 0x000d, 0x000e}, false)
|
||||
NoneClass = getCharSetFromOldString(nil, false)
|
||||
ECMAWordClass = getCharSetFromOldString(ecmaWord, false)
|
||||
NotECMAWordClass = getCharSetFromOldString(ecmaWord, true)
|
||||
ECMASpaceClass = getCharSetFromOldString(ecmaSpace, false)
|
||||
NotECMASpaceClass = getCharSetFromOldString(ecmaSpace, true)
|
||||
ECMADigitClass = getCharSetFromOldString(ecmaDigit, false)
|
||||
NotECMADigitClass = getCharSetFromOldString(ecmaDigit, true)
|
||||
|
||||
WordClass = getCharSetFromCategoryString(false, false, wordCategoryText)
|
||||
NotWordClass = getCharSetFromCategoryString(true, false, wordCategoryText)
|
||||
SpaceClass = getCharSetFromCategoryString(false, false, spaceCategoryText)
|
||||
NotSpaceClass = getCharSetFromCategoryString(true, false, spaceCategoryText)
|
||||
DigitClass = getCharSetFromCategoryString(false, false, "Nd")
|
||||
NotDigitClass = getCharSetFromCategoryString(false, true, "Nd")
|
||||
|
||||
RE2SpaceClass = getCharSetFromOldString(re2Space, false)
|
||||
NotRE2SpaceClass = getCharSetFromOldString(re2Space, true)
|
||||
)
|
||||
|
||||
var unicodeCategories = func() map[string]*unicode.RangeTable {
|
||||
retVal := make(map[string]*unicode.RangeTable)
|
||||
for k, v := range unicode.Scripts {
|
||||
retVal[k] = v
|
||||
}
|
||||
for k, v := range unicode.Categories {
|
||||
retVal[k] = v
|
||||
}
|
||||
for k, v := range unicode.Properties {
|
||||
retVal[k] = v
|
||||
}
|
||||
return retVal
|
||||
}()
|
||||
|
||||
func getCharSetFromCategoryString(negateSet bool, negateCat bool, cats ...string) func() *CharSet {
|
||||
if negateCat && negateSet {
|
||||
panic("BUG! You should only negate the set OR the category in a constant setup, but not both")
|
||||
}
|
||||
|
||||
c := CharSet{negate: negateSet}
|
||||
|
||||
c.categories = make([]category, len(cats))
|
||||
for i, cat := range cats {
|
||||
c.categories[i] = category{cat: cat, negate: negateCat}
|
||||
}
|
||||
return func() *CharSet {
|
||||
//make a copy each time
|
||||
local := c
|
||||
//return that address
|
||||
return &local
|
||||
}
|
||||
}
|
||||
|
||||
func getCharSetFromOldString(setText []rune, negate bool) func() *CharSet {
|
||||
c := CharSet{}
|
||||
if len(setText) > 0 {
|
||||
fillFirst := false
|
||||
l := len(setText)
|
||||
if negate {
|
||||
if setText[0] == 0 {
|
||||
setText = setText[1:]
|
||||
} else {
|
||||
l++
|
||||
fillFirst = true
|
||||
}
|
||||
}
|
||||
|
||||
if l%2 == 0 {
|
||||
c.ranges = make([]singleRange, l/2)
|
||||
} else {
|
||||
c.ranges = make([]singleRange, l/2+1)
|
||||
}
|
||||
|
||||
first := true
|
||||
if fillFirst {
|
||||
c.ranges[0] = singleRange{first: 0}
|
||||
first = false
|
||||
}
|
||||
|
||||
i := 0
|
||||
for _, r := range setText {
|
||||
if first {
|
||||
// lower bound in a new range
|
||||
c.ranges[i] = singleRange{first: r}
|
||||
first = false
|
||||
} else {
|
||||
c.ranges[i].last = r - 1
|
||||
i++
|
||||
first = true
|
||||
}
|
||||
}
|
||||
if !first {
|
||||
c.ranges[i].last = utf8.MaxRune
|
||||
}
|
||||
}
|
||||
|
||||
return func() *CharSet {
|
||||
local := c
|
||||
return &local
|
||||
}
|
||||
}
|
||||
|
||||
// Copy makes a deep copy to prevent accidental mutation of a set
|
||||
func (c CharSet) Copy() CharSet {
|
||||
ret := CharSet{
|
||||
anything: c.anything,
|
||||
negate: c.negate,
|
||||
}
|
||||
|
||||
ret.ranges = append(ret.ranges, c.ranges...)
|
||||
ret.categories = append(ret.categories, c.categories...)
|
||||
|
||||
if c.sub != nil {
|
||||
sub := c.sub.Copy()
|
||||
ret.sub = &sub
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// gets a human-readable description for a set string
|
||||
func (c CharSet) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteRune('[')
|
||||
|
||||
if c.IsNegated() {
|
||||
buf.WriteRune('^')
|
||||
}
|
||||
|
||||
for _, r := range c.ranges {
|
||||
|
||||
buf.WriteString(CharDescription(r.first))
|
||||
if r.first != r.last {
|
||||
if r.last-r.first != 1 {
|
||||
//groups that are 1 char apart skip the dash
|
||||
buf.WriteRune('-')
|
||||
}
|
||||
buf.WriteString(CharDescription(r.last))
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range c.categories {
|
||||
buf.WriteString(c.String())
|
||||
}
|
||||
|
||||
if c.sub != nil {
|
||||
buf.WriteRune('-')
|
||||
buf.WriteString(c.sub.String())
|
||||
}
|
||||
|
||||
buf.WriteRune(']')
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// mapHashFill converts a charset into a buffer for use in maps
|
||||
func (c CharSet) mapHashFill(buf *bytes.Buffer) {
|
||||
if c.negate {
|
||||
buf.WriteByte(0)
|
||||
} else {
|
||||
buf.WriteByte(1)
|
||||
}
|
||||
|
||||
binary.Write(buf, binary.LittleEndian, len(c.ranges))
|
||||
binary.Write(buf, binary.LittleEndian, len(c.categories))
|
||||
for _, r := range c.ranges {
|
||||
buf.WriteRune(r.first)
|
||||
buf.WriteRune(r.last)
|
||||
}
|
||||
for _, ct := range c.categories {
|
||||
buf.WriteString(ct.cat)
|
||||
if ct.negate {
|
||||
buf.WriteByte(1)
|
||||
} else {
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
if c.sub != nil {
|
||||
c.sub.mapHashFill(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// CharIn returns true if the rune is in our character set (either ranges or categories).
|
||||
// It handles negations and subtracted sub-charsets.
|
||||
func (c CharSet) CharIn(ch rune) bool {
|
||||
val := false
|
||||
// in s && !s.subtracted
|
||||
|
||||
//check ranges
|
||||
for _, r := range c.ranges {
|
||||
if ch < r.first {
|
||||
continue
|
||||
}
|
||||
if ch <= r.last {
|
||||
val = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//check categories if we haven't already found a range
|
||||
if !val && len(c.categories) > 0 {
|
||||
for _, ct := range c.categories {
|
||||
// special categories...then unicode
|
||||
if ct.cat == spaceCategoryText {
|
||||
if unicode.IsSpace(ch) {
|
||||
// we found a space so we're done
|
||||
// negate means this is a "bad" thing
|
||||
val = !ct.negate
|
||||
break
|
||||
} else if ct.negate {
|
||||
val = true
|
||||
break
|
||||
}
|
||||
} else if ct.cat == wordCategoryText {
|
||||
if IsWordChar(ch) {
|
||||
val = !ct.negate
|
||||
break
|
||||
} else if ct.negate {
|
||||
val = true
|
||||
break
|
||||
}
|
||||
} else if unicode.Is(unicodeCategories[ct.cat], ch) {
|
||||
// if we're in this unicode category then we're done
|
||||
// if negate=true on this category then we "failed" our test
|
||||
// otherwise we're good that we found it
|
||||
val = !ct.negate
|
||||
break
|
||||
} else if ct.negate {
|
||||
val = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// negate the whole char set
|
||||
if c.negate {
|
||||
val = !val
|
||||
}
|
||||
|
||||
// get subtracted recurse
|
||||
if val && c.sub != nil {
|
||||
val = !c.sub.CharIn(ch)
|
||||
}
|
||||
|
||||
//log.Printf("Char '%v' in %v == %v", string(ch), c.String(), val)
|
||||
return val
|
||||
}
|
||||
|
||||
func (c category) String() string {
|
||||
switch c.cat {
|
||||
case spaceCategoryText:
|
||||
if c.negate {
|
||||
return "\\S"
|
||||
}
|
||||
return "\\s"
|
||||
case wordCategoryText:
|
||||
if c.negate {
|
||||
return "\\W"
|
||||
}
|
||||
return "\\w"
|
||||
}
|
||||
if _, ok := unicodeCategories[c.cat]; ok {
|
||||
|
||||
if c.negate {
|
||||
return "\\P{" + c.cat + "}"
|
||||
}
|
||||
return "\\p{" + c.cat + "}"
|
||||
}
|
||||
return "Unknown category: " + c.cat
|
||||
}
|
||||
|
||||
// CharDescription Produces a human-readable description for a single character.
|
||||
func CharDescription(ch rune) string {
|
||||
/*if ch == '\\' {
|
||||
return "\\\\"
|
||||
}
|
||||
|
||||
if ch > ' ' && ch <= '~' {
|
||||
return string(ch)
|
||||
} else if ch == '\n' {
|
||||
return "\\n"
|
||||
} else if ch == ' ' {
|
||||
return "\\ "
|
||||
}*/
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
escape(b, ch, false) //fmt.Sprintf("%U", ch)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// According to UTS#18 Unicode Regular Expressions (http://www.unicode.org/reports/tr18/)
|
||||
// RL 1.4 Simple Word Boundaries The class of <word_character> includes all Alphabetic
|
||||
// values from the Unicode character database, from UnicodeData.txt [UData], plus the U+200C
|
||||
// ZERO WIDTH NON-JOINER and U+200D ZERO WIDTH JOINER.
|
||||
func IsWordChar(r rune) bool {
|
||||
//"L", "Mn", "Nd", "Pc"
|
||||
return unicode.In(r,
|
||||
unicode.Categories["L"], unicode.Categories["Mn"],
|
||||
unicode.Categories["Nd"], unicode.Categories["Pc"]) || r == '\u200D' || r == '\u200C'
|
||||
//return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_'
|
||||
}
|
||||
|
||||
func IsECMAWordChar(r rune) bool {
|
||||
return unicode.In(r,
|
||||
unicode.Categories["L"], unicode.Categories["Mn"],
|
||||
unicode.Categories["Nd"], unicode.Categories["Pc"])
|
||||
|
||||
//return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' || '0' <= r && r <= '9' || r == '_'
|
||||
}
|
||||
|
||||
// SingletonChar will return the char from the first range without validation.
|
||||
// It assumes you have checked for IsSingleton or IsSingletonInverse and will panic given bad input
|
||||
func (c CharSet) SingletonChar() rune {
|
||||
return c.ranges[0].first
|
||||
}
|
||||
|
||||
func (c CharSet) IsSingleton() bool {
|
||||
return !c.negate && //negated is multiple chars
|
||||
len(c.categories) == 0 && len(c.ranges) == 1 && // multiple ranges and unicode classes represent multiple chars
|
||||
c.sub == nil && // subtraction means we've got multiple chars
|
||||
c.ranges[0].first == c.ranges[0].last // first and last equal means we're just 1 char
|
||||
}
|
||||
|
||||
func (c CharSet) IsSingletonInverse() bool {
|
||||
return c.negate && //same as above, but requires negated
|
||||
len(c.categories) == 0 && len(c.ranges) == 1 && // multiple ranges and unicode classes represent multiple chars
|
||||
c.sub == nil && // subtraction means we've got multiple chars
|
||||
c.ranges[0].first == c.ranges[0].last // first and last equal means we're just 1 char
|
||||
}
|
||||
|
||||
func (c CharSet) IsMergeable() bool {
|
||||
return !c.IsNegated() && !c.HasSubtraction()
|
||||
}
|
||||
|
||||
func (c CharSet) IsNegated() bool {
|
||||
return c.negate
|
||||
}
|
||||
|
||||
func (c CharSet) HasSubtraction() bool {
|
||||
return c.sub != nil
|
||||
}
|
||||
|
||||
func (c CharSet) IsEmpty() bool {
|
||||
return len(c.ranges) == 0 && len(c.categories) == 0 && c.sub == nil
|
||||
}
|
||||
|
||||
func (c *CharSet) addDigit(ecma, negate bool, pattern string) {
|
||||
if ecma {
|
||||
if negate {
|
||||
c.addRanges(NotECMADigitClass().ranges)
|
||||
} else {
|
||||
c.addRanges(ECMADigitClass().ranges)
|
||||
}
|
||||
} else {
|
||||
c.addCategories(category{cat: "Nd", negate: negate})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CharSet) addChar(ch rune) {
|
||||
c.addRange(ch, ch)
|
||||
}
|
||||
|
||||
func (c *CharSet) addSpace(ecma, re2, negate bool) {
|
||||
if ecma {
|
||||
if negate {
|
||||
c.addRanges(NotECMASpaceClass().ranges)
|
||||
} else {
|
||||
c.addRanges(ECMASpaceClass().ranges)
|
||||
}
|
||||
} else if re2 {
|
||||
if negate {
|
||||
c.addRanges(NotRE2SpaceClass().ranges)
|
||||
} else {
|
||||
c.addRanges(RE2SpaceClass().ranges)
|
||||
}
|
||||
} else {
|
||||
c.addCategories(category{cat: spaceCategoryText, negate: negate})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CharSet) addWord(ecma, negate bool) {
|
||||
if ecma {
|
||||
if negate {
|
||||
c.addRanges(NotECMAWordClass().ranges)
|
||||
} else {
|
||||
c.addRanges(ECMAWordClass().ranges)
|
||||
}
|
||||
} else {
|
||||
c.addCategories(category{cat: wordCategoryText, negate: negate})
|
||||
}
|
||||
}
|
||||
|
||||
// Add set ranges and categories into ours -- no deduping or anything
|
||||
func (c *CharSet) addSet(set CharSet) {
|
||||
if c.anything {
|
||||
return
|
||||
}
|
||||
if set.anything {
|
||||
c.makeAnything()
|
||||
return
|
||||
}
|
||||
// just append here to prevent double-canon
|
||||
c.ranges = append(c.ranges, set.ranges...)
|
||||
c.addCategories(set.categories...)
|
||||
c.canonicalize()
|
||||
}
|
||||
|
||||
func (c *CharSet) makeAnything() {
|
||||
c.anything = true
|
||||
c.categories = []category{}
|
||||
c.ranges = AnyClass().ranges
|
||||
}
|
||||
|
||||
func (c *CharSet) addCategories(cats ...category) {
|
||||
// don't add dupes and remove positive+negative
|
||||
if c.anything {
|
||||
// if we've had a previous positive+negative group then
|
||||
// just return, we're as broad as we can get
|
||||
return
|
||||
}
|
||||
|
||||
for _, ct := range cats {
|
||||
found := false
|
||||
for _, ct2 := range c.categories {
|
||||
if ct.cat == ct2.cat {
|
||||
if ct.negate != ct2.negate {
|
||||
// oposite negations...this mean we just
|
||||
// take us as anything and move on
|
||||
c.makeAnything()
|
||||
return
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
c.categories = append(c.categories, ct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merges new ranges to our own
|
||||
func (c *CharSet) addRanges(ranges []singleRange) {
|
||||
if c.anything {
|
||||
return
|
||||
}
|
||||
c.ranges = append(c.ranges, ranges...)
|
||||
c.canonicalize()
|
||||
}
|
||||
|
||||
// Merges everything but the new ranges into our own
|
||||
func (c *CharSet) addNegativeRanges(ranges []singleRange) {
|
||||
if c.anything {
|
||||
return
|
||||
}
|
||||
|
||||
var hi rune
|
||||
|
||||
// convert incoming ranges into opposites, assume they are in order
|
||||
for _, r := range ranges {
|
||||
if hi < r.first {
|
||||
c.ranges = append(c.ranges, singleRange{hi, r.first - 1})
|
||||
}
|
||||
hi = r.last + 1
|
||||
}
|
||||
|
||||
if hi < utf8.MaxRune {
|
||||
c.ranges = append(c.ranges, singleRange{hi, utf8.MaxRune})
|
||||
}
|
||||
|
||||
c.canonicalize()
|
||||
}
|
||||
|
||||
func isValidUnicodeCat(catName string) bool {
|
||||
_, ok := unicodeCategories[catName]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (c *CharSet) addCategory(categoryName string, negate, caseInsensitive bool, pattern string) {
|
||||
if !isValidUnicodeCat(categoryName) {
|
||||
// unknown unicode category, script, or property "blah"
|
||||
panic(fmt.Errorf("Unknown unicode category, script, or property '%v'", categoryName))
|
||||
|
||||
}
|
||||
|
||||
if caseInsensitive && (categoryName == "Ll" || categoryName == "Lu" || categoryName == "Lt") {
|
||||
// when RegexOptions.IgnoreCase is specified then {Ll} {Lu} and {Lt} cases should all match
|
||||
c.addCategories(
|
||||
category{cat: "Ll", negate: negate},
|
||||
category{cat: "Lu", negate: negate},
|
||||
category{cat: "Lt", negate: negate})
|
||||
}
|
||||
c.addCategories(category{cat: categoryName, negate: negate})
|
||||
}
|
||||
|
||||
func (c *CharSet) addSubtraction(sub *CharSet) {
|
||||
c.sub = sub
|
||||
}
|
||||
|
||||
func (c *CharSet) addRange(chMin, chMax rune) {
|
||||
c.ranges = append(c.ranges, singleRange{first: chMin, last: chMax})
|
||||
c.canonicalize()
|
||||
}
|
||||
|
||||
func (c *CharSet) addNamedASCII(name string, negate bool) bool {
|
||||
var rs []singleRange
|
||||
|
||||
switch name {
|
||||
case "alnum":
|
||||
rs = []singleRange{singleRange{'0', '9'}, singleRange{'A', 'Z'}, singleRange{'a', 'z'}}
|
||||
case "alpha":
|
||||
rs = []singleRange{singleRange{'A', 'Z'}, singleRange{'a', 'z'}}
|
||||
case "ascii":
|
||||
rs = []singleRange{singleRange{0, 0x7f}}
|
||||
case "blank":
|
||||
rs = []singleRange{singleRange{'\t', '\t'}, singleRange{' ', ' '}}
|
||||
case "cntrl":
|
||||
rs = []singleRange{singleRange{0, 0x1f}, singleRange{0x7f, 0x7f}}
|
||||
case "digit":
|
||||
c.addDigit(false, negate, "")
|
||||
case "graph":
|
||||
rs = []singleRange{singleRange{'!', '~'}}
|
||||
case "lower":
|
||||
rs = []singleRange{singleRange{'a', 'z'}}
|
||||
case "print":
|
||||
rs = []singleRange{singleRange{' ', '~'}}
|
||||
case "punct": //[!-/:-@[-`{-~]
|
||||
rs = []singleRange{singleRange{'!', '/'}, singleRange{':', '@'}, singleRange{'[', '`'}, singleRange{'{', '~'}}
|
||||
case "space":
|
||||
c.addSpace(true, false, negate)
|
||||
case "upper":
|
||||
rs = []singleRange{singleRange{'A', 'Z'}}
|
||||
case "word":
|
||||
c.addWord(true, negate)
|
||||
case "xdigit":
|
||||
rs = []singleRange{singleRange{'0', '9'}, singleRange{'A', 'F'}, singleRange{'a', 'f'}}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
if len(rs) > 0 {
|
||||
if negate {
|
||||
c.addNegativeRanges(rs)
|
||||
} else {
|
||||
c.addRanges(rs)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type singleRangeSorter []singleRange
|
||||
|
||||
func (p singleRangeSorter) Len() int { return len(p) }
|
||||
func (p singleRangeSorter) Less(i, j int) bool { return p[i].first < p[j].first }
|
||||
func (p singleRangeSorter) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// Logic to reduce a character class to a unique, sorted form.
|
||||
func (c *CharSet) canonicalize() {
|
||||
var i, j int
|
||||
var last rune
|
||||
|
||||
//
|
||||
// Find and eliminate overlapping or abutting ranges
|
||||
//
|
||||
|
||||
if len(c.ranges) > 1 {
|
||||
sort.Sort(singleRangeSorter(c.ranges))
|
||||
|
||||
done := false
|
||||
|
||||
for i, j = 1, 0; ; i++ {
|
||||
for last = c.ranges[j].last; ; i++ {
|
||||
if i == len(c.ranges) || last == utf8.MaxRune {
|
||||
done = true
|
||||
break
|
||||
}
|
||||
|
||||
CurrentRange := c.ranges[i]
|
||||
if CurrentRange.first > last+1 {
|
||||
break
|
||||
}
|
||||
|
||||
if last < CurrentRange.last {
|
||||
last = CurrentRange.last
|
||||
}
|
||||
}
|
||||
|
||||
c.ranges[j] = singleRange{first: c.ranges[j].first, last: last}
|
||||
|
||||
j++
|
||||
|
||||
if done {
|
||||
break
|
||||
}
|
||||
|
||||
if j < i {
|
||||
c.ranges[j] = c.ranges[i]
|
||||
}
|
||||
}
|
||||
|
||||
c.ranges = append(c.ranges[:j], c.ranges[len(c.ranges):]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds to the class any lowercase versions of characters already
|
||||
// in the class. Used for case-insensitivity.
|
||||
func (c *CharSet) addLowercase() {
|
||||
if c.anything {
|
||||
return
|
||||
}
|
||||
toAdd := []singleRange{}
|
||||
for i := 0; i < len(c.ranges); i++ {
|
||||
r := c.ranges[i]
|
||||
if r.first == r.last {
|
||||
lower := unicode.ToLower(r.first)
|
||||
c.ranges[i] = singleRange{first: lower, last: lower}
|
||||
} else {
|
||||
toAdd = append(toAdd, r)
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range toAdd {
|
||||
c.addLowercaseRange(r.first, r.last)
|
||||
}
|
||||
c.canonicalize()
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
Let U be the set of Unicode character values and let L be the lowercase
|
||||
function, mapping from U to U. To perform case insensitive matching of
|
||||
character sets, we need to be able to map an interval I in U, say
|
||||
|
||||
I = [chMin, chMax] = { ch : chMin <= ch <= chMax }
|
||||
|
||||
to a set A such that A contains L(I) and A is contained in the union of
|
||||
I and L(I).
|
||||
|
||||
The table below partitions U into intervals on which L is non-decreasing.
|
||||
Thus, for any interval J = [a, b] contained in one of these intervals,
|
||||
L(J) is contained in [L(a), L(b)].
|
||||
|
||||
It is also true that for any such J, [L(a), L(b)] is contained in the
|
||||
union of J and L(J). This does not follow from L being non-decreasing on
|
||||
these intervals. It follows from the nature of the L on each interval.
|
||||
On each interval, L has one of the following forms:
|
||||
|
||||
(1) L(ch) = constant (LowercaseSet)
|
||||
(2) L(ch) = ch + offset (LowercaseAdd)
|
||||
(3) L(ch) = ch | 1 (LowercaseBor)
|
||||
(4) L(ch) = ch + (ch & 1) (LowercaseBad)
|
||||
|
||||
It is easy to verify that for any of these forms [L(a), L(b)] is
|
||||
contained in the union of [a, b] and L([a, b]).
|
||||
***************************************************************************/
|
||||
|
||||
const (
|
||||
LowercaseSet = 0 // Set to arg.
|
||||
LowercaseAdd = 1 // Add arg.
|
||||
LowercaseBor = 2 // Bitwise or with 1.
|
||||
LowercaseBad = 3 // Bitwise and with 1 and add original.
|
||||
)
|
||||
|
||||
type lcMap struct {
|
||||
chMin, chMax rune
|
||||
op, data int32
|
||||
}
|
||||
|
||||
var lcTable = []lcMap{
|
||||
lcMap{'\u0041', '\u005A', LowercaseAdd, 32},
|
||||
lcMap{'\u00C0', '\u00DE', LowercaseAdd, 32},
|
||||
lcMap{'\u0100', '\u012E', LowercaseBor, 0},
|
||||
lcMap{'\u0130', '\u0130', LowercaseSet, 0x0069},
|
||||
lcMap{'\u0132', '\u0136', LowercaseBor, 0},
|
||||
lcMap{'\u0139', '\u0147', LowercaseBad, 0},
|
||||
lcMap{'\u014A', '\u0176', LowercaseBor, 0},
|
||||
lcMap{'\u0178', '\u0178', LowercaseSet, 0x00FF},
|
||||
lcMap{'\u0179', '\u017D', LowercaseBad, 0},
|
||||
lcMap{'\u0181', '\u0181', LowercaseSet, 0x0253},
|
||||
lcMap{'\u0182', '\u0184', LowercaseBor, 0},
|
||||
lcMap{'\u0186', '\u0186', LowercaseSet, 0x0254},
|
||||
lcMap{'\u0187', '\u0187', LowercaseSet, 0x0188},
|
||||
lcMap{'\u0189', '\u018A', LowercaseAdd, 205},
|
||||
lcMap{'\u018B', '\u018B', LowercaseSet, 0x018C},
|
||||
lcMap{'\u018E', '\u018E', LowercaseSet, 0x01DD},
|
||||
lcMap{'\u018F', '\u018F', LowercaseSet, 0x0259},
|
||||
lcMap{'\u0190', '\u0190', LowercaseSet, 0x025B},
|
||||
lcMap{'\u0191', '\u0191', LowercaseSet, 0x0192},
|
||||
lcMap{'\u0193', '\u0193', LowercaseSet, 0x0260},
|
||||
lcMap{'\u0194', '\u0194', LowercaseSet, 0x0263},
|
||||
lcMap{'\u0196', '\u0196', LowercaseSet, 0x0269},
|
||||
lcMap{'\u0197', '\u0197', LowercaseSet, 0x0268},
|
||||
lcMap{'\u0198', '\u0198', LowercaseSet, 0x0199},
|
||||
lcMap{'\u019C', '\u019C', LowercaseSet, 0x026F},
|
||||
lcMap{'\u019D', '\u019D', LowercaseSet, 0x0272},
|
||||
lcMap{'\u019F', '\u019F', LowercaseSet, 0x0275},
|
||||
lcMap{'\u01A0', '\u01A4', LowercaseBor, 0},
|
||||
lcMap{'\u01A7', '\u01A7', LowercaseSet, 0x01A8},
|
||||
lcMap{'\u01A9', '\u01A9', LowercaseSet, 0x0283},
|
||||
lcMap{'\u01AC', '\u01AC', LowercaseSet, 0x01AD},
|
||||
lcMap{'\u01AE', '\u01AE', LowercaseSet, 0x0288},
|
||||
lcMap{'\u01AF', '\u01AF', LowercaseSet, 0x01B0},
|
||||
lcMap{'\u01B1', '\u01B2', LowercaseAdd, 217},
|
||||
lcMap{'\u01B3', '\u01B5', LowercaseBad, 0},
|
||||
lcMap{'\u01B7', '\u01B7', LowercaseSet, 0x0292},
|
||||
lcMap{'\u01B8', '\u01B8', LowercaseSet, 0x01B9},
|
||||
lcMap{'\u01BC', '\u01BC', LowercaseSet, 0x01BD},
|
||||
lcMap{'\u01C4', '\u01C5', LowercaseSet, 0x01C6},
|
||||
lcMap{'\u01C7', '\u01C8', LowercaseSet, 0x01C9},
|
||||
lcMap{'\u01CA', '\u01CB', LowercaseSet, 0x01CC},
|
||||
lcMap{'\u01CD', '\u01DB', LowercaseBad, 0},
|
||||
lcMap{'\u01DE', '\u01EE', LowercaseBor, 0},
|
||||
lcMap{'\u01F1', '\u01F2', LowercaseSet, 0x01F3},
|
||||
lcMap{'\u01F4', '\u01F4', LowercaseSet, 0x01F5},
|
||||
lcMap{'\u01FA', '\u0216', LowercaseBor, 0},
|
||||
lcMap{'\u0386', '\u0386', LowercaseSet, 0x03AC},
|
||||
lcMap{'\u0388', '\u038A', LowercaseAdd, 37},
|
||||
lcMap{'\u038C', '\u038C', LowercaseSet, 0x03CC},
|
||||
lcMap{'\u038E', '\u038F', LowercaseAdd, 63},
|
||||
lcMap{'\u0391', '\u03AB', LowercaseAdd, 32},
|
||||
lcMap{'\u03E2', '\u03EE', LowercaseBor, 0},
|
||||
lcMap{'\u0401', '\u040F', LowercaseAdd, 80},
|
||||
lcMap{'\u0410', '\u042F', LowercaseAdd, 32},
|
||||
lcMap{'\u0460', '\u0480', LowercaseBor, 0},
|
||||
lcMap{'\u0490', '\u04BE', LowercaseBor, 0},
|
||||
lcMap{'\u04C1', '\u04C3', LowercaseBad, 0},
|
||||
lcMap{'\u04C7', '\u04C7', LowercaseSet, 0x04C8},
|
||||
lcMap{'\u04CB', '\u04CB', LowercaseSet, 0x04CC},
|
||||
lcMap{'\u04D0', '\u04EA', LowercaseBor, 0},
|
||||
lcMap{'\u04EE', '\u04F4', LowercaseBor, 0},
|
||||
lcMap{'\u04F8', '\u04F8', LowercaseSet, 0x04F9},
|
||||
lcMap{'\u0531', '\u0556', LowercaseAdd, 48},
|
||||
lcMap{'\u10A0', '\u10C5', LowercaseAdd, 48},
|
||||
lcMap{'\u1E00', '\u1EF8', LowercaseBor, 0},
|
||||
lcMap{'\u1F08', '\u1F0F', LowercaseAdd, -8},
|
||||
lcMap{'\u1F18', '\u1F1F', LowercaseAdd, -8},
|
||||
lcMap{'\u1F28', '\u1F2F', LowercaseAdd, -8},
|
||||
lcMap{'\u1F38', '\u1F3F', LowercaseAdd, -8},
|
||||
lcMap{'\u1F48', '\u1F4D', LowercaseAdd, -8},
|
||||
lcMap{'\u1F59', '\u1F59', LowercaseSet, 0x1F51},
|
||||
lcMap{'\u1F5B', '\u1F5B', LowercaseSet, 0x1F53},
|
||||
lcMap{'\u1F5D', '\u1F5D', LowercaseSet, 0x1F55},
|
||||
lcMap{'\u1F5F', '\u1F5F', LowercaseSet, 0x1F57},
|
||||
lcMap{'\u1F68', '\u1F6F', LowercaseAdd, -8},
|
||||
lcMap{'\u1F88', '\u1F8F', LowercaseAdd, -8},
|
||||
lcMap{'\u1F98', '\u1F9F', LowercaseAdd, -8},
|
||||
lcMap{'\u1FA8', '\u1FAF', LowercaseAdd, -8},
|
||||
lcMap{'\u1FB8', '\u1FB9', LowercaseAdd, -8},
|
||||
lcMap{'\u1FBA', '\u1FBB', LowercaseAdd, -74},
|
||||
lcMap{'\u1FBC', '\u1FBC', LowercaseSet, 0x1FB3},
|
||||
lcMap{'\u1FC8', '\u1FCB', LowercaseAdd, -86},
|
||||
lcMap{'\u1FCC', '\u1FCC', LowercaseSet, 0x1FC3},
|
||||
lcMap{'\u1FD8', '\u1FD9', LowercaseAdd, -8},
|
||||
lcMap{'\u1FDA', '\u1FDB', LowercaseAdd, -100},
|
||||
lcMap{'\u1FE8', '\u1FE9', LowercaseAdd, -8},
|
||||
lcMap{'\u1FEA', '\u1FEB', LowercaseAdd, -112},
|
||||
lcMap{'\u1FEC', '\u1FEC', LowercaseSet, 0x1FE5},
|
||||
lcMap{'\u1FF8', '\u1FF9', LowercaseAdd, -128},
|
||||
lcMap{'\u1FFA', '\u1FFB', LowercaseAdd, -126},
|
||||
lcMap{'\u1FFC', '\u1FFC', LowercaseSet, 0x1FF3},
|
||||
lcMap{'\u2160', '\u216F', LowercaseAdd, 16},
|
||||
lcMap{'\u24B6', '\u24D0', LowercaseAdd, 26},
|
||||
lcMap{'\uFF21', '\uFF3A', LowercaseAdd, 32},
|
||||
}
|
||||
|
||||
func (c *CharSet) addLowercaseRange(chMin, chMax rune) {
|
||||
var i, iMax, iMid int
|
||||
var chMinT, chMaxT rune
|
||||
var lc lcMap
|
||||
|
||||
for i, iMax = 0, len(lcTable); i < iMax; {
|
||||
iMid = (i + iMax) / 2
|
||||
if lcTable[iMid].chMax < chMin {
|
||||
i = iMid + 1
|
||||
} else {
|
||||
iMax = iMid
|
||||
}
|
||||
}
|
||||
|
||||
for ; i < len(lcTable); i++ {
|
||||
lc = lcTable[i]
|
||||
if lc.chMin > chMax {
|
||||
return
|
||||
}
|
||||
chMinT = lc.chMin
|
||||
if chMinT < chMin {
|
||||
chMinT = chMin
|
||||
}
|
||||
|
||||
chMaxT = lc.chMax
|
||||
if chMaxT > chMax {
|
||||
chMaxT = chMax
|
||||
}
|
||||
|
||||
switch lc.op {
|
||||
case LowercaseSet:
|
||||
chMinT = rune(lc.data)
|
||||
chMaxT = rune(lc.data)
|
||||
break
|
||||
case LowercaseAdd:
|
||||
chMinT += lc.data
|
||||
chMaxT += lc.data
|
||||
break
|
||||
case LowercaseBor:
|
||||
chMinT |= 1
|
||||
chMaxT |= 1
|
||||
break
|
||||
case LowercaseBad:
|
||||
chMinT += (chMinT & 1)
|
||||
chMaxT += (chMaxT & 1)
|
||||
break
|
||||
}
|
||||
|
||||
if chMinT < chMin || chMaxT > chMax {
|
||||
c.addRange(chMinT, chMaxT)
|
||||
}
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// similar to prog.go in the go regex package...also with comment 'may not belong in this package'
|
||||
|
||||
// File provides operator constants for use by the Builder and the Machine.
|
||||
|
||||
// Implementation notes:
|
||||
//
|
||||
// Regexps are built into RegexCodes, which contain an operation array,
|
||||
// a string table, and some constants.
|
||||
//
|
||||
// Each operation is one of the codes below, followed by the integer
|
||||
// operands specified for each op.
|
||||
//
|
||||
// Strings and sets are indices into a string table.
|
||||
|
||||
type InstOp int
|
||||
|
||||
const (
|
||||
// lef/back operands description
|
||||
|
||||
Onerep InstOp = 0 // lef,back char,min,max a {n}
|
||||
Notonerep = 1 // lef,back char,min,max .{n}
|
||||
Setrep = 2 // lef,back set,min,max [\d]{n}
|
||||
|
||||
Oneloop = 3 // lef,back char,min,max a {,n}
|
||||
Notoneloop = 4 // lef,back char,min,max .{,n}
|
||||
Setloop = 5 // lef,back set,min,max [\d]{,n}
|
||||
|
||||
Onelazy = 6 // lef,back char,min,max a {,n}?
|
||||
Notonelazy = 7 // lef,back char,min,max .{,n}?
|
||||
Setlazy = 8 // lef,back set,min,max [\d]{,n}?
|
||||
|
||||
One = 9 // lef char a
|
||||
Notone = 10 // lef char [^a]
|
||||
Set = 11 // lef set [a-z\s] \w \s \d
|
||||
|
||||
Multi = 12 // lef string abcd
|
||||
Ref = 13 // lef group \#
|
||||
|
||||
Bol = 14 // ^
|
||||
Eol = 15 // $
|
||||
Boundary = 16 // \b
|
||||
Nonboundary = 17 // \B
|
||||
Beginning = 18 // \A
|
||||
Start = 19 // \G
|
||||
EndZ = 20 // \Z
|
||||
End = 21 // \Z
|
||||
|
||||
Nothing = 22 // Reject!
|
||||
|
||||
// Primitive control structures
|
||||
|
||||
Lazybranch = 23 // back jump straight first
|
||||
Branchmark = 24 // back jump branch first for loop
|
||||
Lazybranchmark = 25 // back jump straight first for loop
|
||||
Nullcount = 26 // back val set counter, null mark
|
||||
Setcount = 27 // back val set counter, make mark
|
||||
Branchcount = 28 // back jump,limit branch++ if zero<=c<limit
|
||||
Lazybranchcount = 29 // back jump,limit same, but straight first
|
||||
Nullmark = 30 // back save position
|
||||
Setmark = 31 // back save position
|
||||
Capturemark = 32 // back group define group
|
||||
Getmark = 33 // back recall position
|
||||
Setjump = 34 // back save backtrack state
|
||||
Backjump = 35 // zap back to saved state
|
||||
Forejump = 36 // zap backtracking state
|
||||
Testref = 37 // backtrack if ref undefined
|
||||
Goto = 38 // jump just go
|
||||
|
||||
Prune = 39 // prune it baby
|
||||
Stop = 40 // done!
|
||||
|
||||
ECMABoundary = 41 // \b
|
||||
NonECMABoundary = 42 // \B
|
||||
|
||||
// Modifiers for alternate modes
|
||||
|
||||
Mask = 63 // Mask to get unmodified ordinary operator
|
||||
Rtl = 64 // bit to indicate that we're reverse scanning.
|
||||
Back = 128 // bit to indicate that we're backtracking.
|
||||
Back2 = 256 // bit to indicate that we're backtracking on a second branch.
|
||||
Ci = 512 // bit to indicate that we're case-insensitive.
|
||||
)
|
||||
|
||||
type Code struct {
|
||||
Codes []int // the code
|
||||
Strings [][]rune // string table
|
||||
Sets []*CharSet //character set table
|
||||
TrackCount int // how many instructions use backtracking
|
||||
Caps map[int]int // mapping of user group numbers -> impl group slots
|
||||
Capsize int // number of impl group slots
|
||||
FcPrefix *Prefix // the set of candidate first characters (may be null)
|
||||
BmPrefix *BmPrefix // the fixed prefix string as a Boyer-Moore machine (may be null)
|
||||
Anchors AnchorLoc // the set of zero-length start anchors (RegexFCD.Bol, etc)
|
||||
RightToLeft bool // true if right to left
|
||||
}
|
||||
|
||||
func opcodeBacktracks(op InstOp) bool {
|
||||
op &= Mask
|
||||
|
||||
switch op {
|
||||
case Oneloop, Notoneloop, Setloop, Onelazy, Notonelazy, Setlazy, Lazybranch, Branchmark, Lazybranchmark,
|
||||
Nullcount, Setcount, Branchcount, Lazybranchcount, Setmark, Capturemark, Getmark, Setjump, Backjump,
|
||||
Forejump, Goto:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func opcodeSize(op InstOp) int {
|
||||
op &= Mask
|
||||
|
||||
switch op {
|
||||
case Nothing, Bol, Eol, Boundary, Nonboundary, ECMABoundary, NonECMABoundary, Beginning, Start, EndZ,
|
||||
End, Nullmark, Setmark, Getmark, Setjump, Backjump, Forejump, Stop:
|
||||
return 1
|
||||
|
||||
case One, Notone, Multi, Ref, Testref, Goto, Nullcount, Setcount, Lazybranch, Branchmark, Lazybranchmark,
|
||||
Prune, Set:
|
||||
return 2
|
||||
|
||||
case Capturemark, Branchcount, Lazybranchcount, Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy,
|
||||
Setlazy, Setrep, Setloop:
|
||||
return 3
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("Unexpected op code: %v", op))
|
||||
}
|
||||
}
|
||||
|
||||
var codeStr = []string{
|
||||
"Onerep", "Notonerep", "Setrep",
|
||||
"Oneloop", "Notoneloop", "Setloop",
|
||||
"Onelazy", "Notonelazy", "Setlazy",
|
||||
"One", "Notone", "Set",
|
||||
"Multi", "Ref",
|
||||
"Bol", "Eol", "Boundary", "Nonboundary", "Beginning", "Start", "EndZ", "End",
|
||||
"Nothing",
|
||||
"Lazybranch", "Branchmark", "Lazybranchmark",
|
||||
"Nullcount", "Setcount", "Branchcount", "Lazybranchcount",
|
||||
"Nullmark", "Setmark", "Capturemark", "Getmark",
|
||||
"Setjump", "Backjump", "Forejump", "Testref", "Goto",
|
||||
"Prune", "Stop",
|
||||
"ECMABoundary", "NonECMABoundary",
|
||||
}
|
||||
|
||||
func operatorDescription(op InstOp) string {
|
||||
desc := codeStr[op&Mask]
|
||||
if (op & Ci) != 0 {
|
||||
desc += "-Ci"
|
||||
}
|
||||
if (op & Rtl) != 0 {
|
||||
desc += "-Rtl"
|
||||
}
|
||||
if (op & Back) != 0 {
|
||||
desc += "-Back"
|
||||
}
|
||||
if (op & Back2) != 0 {
|
||||
desc += "-Back2"
|
||||
}
|
||||
|
||||
return desc
|
||||
}
|
||||
|
||||
// OpcodeDescription is a humman readable string of the specific offset
|
||||
func (c *Code) OpcodeDescription(offset int) string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
op := InstOp(c.Codes[offset])
|
||||
fmt.Fprintf(buf, "%06d ", offset)
|
||||
|
||||
if opcodeBacktracks(op & Mask) {
|
||||
buf.WriteString("*")
|
||||
} else {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
buf.WriteString(operatorDescription(op))
|
||||
buf.WriteString("(")
|
||||
op &= Mask
|
||||
|
||||
switch op {
|
||||
case One, Notone, Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy:
|
||||
buf.WriteString("Ch = ")
|
||||
buf.WriteString(CharDescription(rune(c.Codes[offset+1])))
|
||||
|
||||
case Set, Setrep, Setloop, Setlazy:
|
||||
buf.WriteString("Set = ")
|
||||
buf.WriteString(c.Sets[c.Codes[offset+1]].String())
|
||||
|
||||
case Multi:
|
||||
fmt.Fprintf(buf, "String = %s", string(c.Strings[c.Codes[offset+1]]))
|
||||
|
||||
case Ref, Testref:
|
||||
fmt.Fprintf(buf, "Index = %d", c.Codes[offset+1])
|
||||
|
||||
case Capturemark:
|
||||
fmt.Fprintf(buf, "Index = %d", c.Codes[offset+1])
|
||||
if c.Codes[offset+2] != -1 {
|
||||
fmt.Fprintf(buf, ", Unindex = %d", c.Codes[offset+2])
|
||||
}
|
||||
|
||||
case Nullcount, Setcount:
|
||||
fmt.Fprintf(buf, "Value = %d", c.Codes[offset+1])
|
||||
|
||||
case Goto, Lazybranch, Branchmark, Lazybranchmark, Branchcount, Lazybranchcount:
|
||||
fmt.Fprintf(buf, "Addr = %d", c.Codes[offset+1])
|
||||
}
|
||||
|
||||
switch op {
|
||||
case Onerep, Notonerep, Oneloop, Notoneloop, Onelazy, Notonelazy, Setrep, Setloop, Setlazy:
|
||||
buf.WriteString(", Rep = ")
|
||||
if c.Codes[offset+2] == math.MaxInt32 {
|
||||
buf.WriteString("inf")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%d", c.Codes[offset+2])
|
||||
}
|
||||
|
||||
case Branchcount, Lazybranchcount:
|
||||
buf.WriteString(", Limit = ")
|
||||
if c.Codes[offset+2] == math.MaxInt32 {
|
||||
buf.WriteString("inf")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%d", c.Codes[offset+2])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
buf.WriteString(")")
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (c *Code) Dump() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if c.RightToLeft {
|
||||
fmt.Fprintln(buf, "Direction: right-to-left")
|
||||
} else {
|
||||
fmt.Fprintln(buf, "Direction: left-to-right")
|
||||
}
|
||||
if c.FcPrefix == nil {
|
||||
fmt.Fprintln(buf, "Firstchars: n/a")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "Firstchars: %v\n", c.FcPrefix.PrefixSet.String())
|
||||
}
|
||||
|
||||
if c.BmPrefix == nil {
|
||||
fmt.Fprintln(buf, "Prefix: n/a")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "Prefix: %v\n", Escape(c.BmPrefix.String()))
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "Anchors: %v\n", c.Anchors)
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
if c.BmPrefix != nil {
|
||||
fmt.Fprintln(buf, "BoyerMoore:")
|
||||
fmt.Fprintln(buf, c.BmPrefix.Dump(" "))
|
||||
}
|
||||
for i := 0; i < len(c.Codes); i += opcodeSize(InstOp(c.Codes[i])) {
|
||||
fmt.Fprintln(buf, c.OpcodeDescription(i))
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func Escape(input string) string {
|
||||
b := &bytes.Buffer{}
|
||||
for _, r := range input {
|
||||
escape(b, r, false)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
const meta = `\.+*?()|[]{}^$# `
|
||||
|
||||
func escape(b *bytes.Buffer, r rune, force bool) {
|
||||
if unicode.IsPrint(r) {
|
||||
if strings.IndexRune(meta, r) >= 0 || force {
|
||||
b.WriteRune('\\')
|
||||
}
|
||||
b.WriteRune(r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '\a':
|
||||
b.WriteString(`\a`)
|
||||
case '\f':
|
||||
b.WriteString(`\f`)
|
||||
case '\n':
|
||||
b.WriteString(`\n`)
|
||||
case '\r':
|
||||
b.WriteString(`\r`)
|
||||
case '\t':
|
||||
b.WriteString(`\t`)
|
||||
case '\v':
|
||||
b.WriteString(`\v`)
|
||||
default:
|
||||
if r < 0x100 {
|
||||
b.WriteString(`\x`)
|
||||
s := strconv.FormatInt(int64(r), 16)
|
||||
if len(s) == 1 {
|
||||
b.WriteRune('0')
|
||||
}
|
||||
b.WriteString(s)
|
||||
break
|
||||
}
|
||||
b.WriteString(`\u`)
|
||||
b.WriteString(strconv.FormatInt(int64(r), 16))
|
||||
}
|
||||
}
|
||||
|
||||
func Unescape(input string) (string, error) {
|
||||
idx := strings.IndexRune(input, '\\')
|
||||
// no slashes means no unescape needed
|
||||
if idx == -1 {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(input[:idx])
|
||||
// get the runes for the rest of the string -- we're going full parser scan on this
|
||||
|
||||
p := parser{}
|
||||
p.setPattern(input[idx+1:])
|
||||
for {
|
||||
if p.rightMost() {
|
||||
return "", p.getErr(ErrIllegalEndEscape)
|
||||
}
|
||||
r, err := p.scanCharEscape()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
// are we done?
|
||||
if p.rightMost() {
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
r = p.moveRightGetChar()
|
||||
for r != '\\' {
|
||||
buf.WriteRune(r)
|
||||
if p.rightMost() {
|
||||
// we're done, no more slashes
|
||||
return buf.String(), nil
|
||||
}
|
||||
// keep scanning until we get another slash
|
||||
r = p.moveRightGetChar()
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// +build gofuzz
|
||||
|
||||
package syntax
|
||||
|
||||
// Fuzz is the input point for go-fuzz
|
||||
func Fuzz(data []byte) int {
|
||||
sdata := string(data)
|
||||
tree, err := Parse(sdata, RegexOptions(0))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// translate it to code
|
||||
_, err = Write(tree)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
+2262
File diff suppressed because it is too large
Load Diff
+896
@@ -0,0 +1,896 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type Prefix struct {
|
||||
PrefixStr []rune
|
||||
PrefixSet CharSet
|
||||
CaseInsensitive bool
|
||||
}
|
||||
|
||||
// It takes a RegexTree and computes the set of chars that can start it.
|
||||
func getFirstCharsPrefix(tree *RegexTree) *Prefix {
|
||||
s := regexFcd{
|
||||
fcStack: make([]regexFc, 32),
|
||||
intStack: make([]int, 32),
|
||||
}
|
||||
fc := s.regexFCFromRegexTree(tree)
|
||||
|
||||
if fc == nil || fc.nullable || fc.cc.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
fcSet := fc.getFirstChars()
|
||||
return &Prefix{PrefixSet: fcSet, CaseInsensitive: fc.caseInsensitive}
|
||||
}
|
||||
|
||||
type regexFcd struct {
|
||||
intStack []int
|
||||
intDepth int
|
||||
fcStack []regexFc
|
||||
fcDepth int
|
||||
skipAllChildren bool // don't process any more children at the current level
|
||||
skipchild bool // don't process the current child.
|
||||
failed bool
|
||||
}
|
||||
|
||||
/*
|
||||
* The main FC computation. It does a shortcutted depth-first walk
|
||||
* through the tree and calls CalculateFC to emits code before
|
||||
* and after each child of an interior node, and at each leaf.
|
||||
*/
|
||||
func (s *regexFcd) regexFCFromRegexTree(tree *RegexTree) *regexFc {
|
||||
curNode := tree.root
|
||||
curChild := 0
|
||||
|
||||
for {
|
||||
if len(curNode.children) == 0 {
|
||||
// This is a leaf node
|
||||
s.calculateFC(curNode.t, curNode, 0)
|
||||
} else if curChild < len(curNode.children) && !s.skipAllChildren {
|
||||
// This is an interior node, and we have more children to analyze
|
||||
s.calculateFC(curNode.t|beforeChild, curNode, curChild)
|
||||
|
||||
if !s.skipchild {
|
||||
curNode = curNode.children[curChild]
|
||||
// this stack is how we get a depth first walk of the tree.
|
||||
s.pushInt(curChild)
|
||||
curChild = 0
|
||||
} else {
|
||||
curChild++
|
||||
s.skipchild = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// This is an interior node where we've finished analyzing all the children, or
|
||||
// the end of a leaf node.
|
||||
s.skipAllChildren = false
|
||||
|
||||
if s.intIsEmpty() {
|
||||
break
|
||||
}
|
||||
|
||||
curChild = s.popInt()
|
||||
curNode = curNode.next
|
||||
|
||||
s.calculateFC(curNode.t|afterChild, curNode, curChild)
|
||||
if s.failed {
|
||||
return nil
|
||||
}
|
||||
|
||||
curChild++
|
||||
}
|
||||
|
||||
if s.fcIsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.popFC()
|
||||
}
|
||||
|
||||
// To avoid recursion, we use a simple integer stack.
|
||||
// This is the push.
|
||||
func (s *regexFcd) pushInt(I int) {
|
||||
if s.intDepth >= len(s.intStack) {
|
||||
expanded := make([]int, s.intDepth*2)
|
||||
copy(expanded, s.intStack)
|
||||
s.intStack = expanded
|
||||
}
|
||||
|
||||
s.intStack[s.intDepth] = I
|
||||
s.intDepth++
|
||||
}
|
||||
|
||||
// True if the stack is empty.
|
||||
func (s *regexFcd) intIsEmpty() bool {
|
||||
return s.intDepth == 0
|
||||
}
|
||||
|
||||
// This is the pop.
|
||||
func (s *regexFcd) popInt() int {
|
||||
s.intDepth--
|
||||
return s.intStack[s.intDepth]
|
||||
}
|
||||
|
||||
// We also use a stack of RegexFC objects.
|
||||
// This is the push.
|
||||
func (s *regexFcd) pushFC(fc regexFc) {
|
||||
if s.fcDepth >= len(s.fcStack) {
|
||||
expanded := make([]regexFc, s.fcDepth*2)
|
||||
copy(expanded, s.fcStack)
|
||||
s.fcStack = expanded
|
||||
}
|
||||
|
||||
s.fcStack[s.fcDepth] = fc
|
||||
s.fcDepth++
|
||||
}
|
||||
|
||||
// True if the stack is empty.
|
||||
func (s *regexFcd) fcIsEmpty() bool {
|
||||
return s.fcDepth == 0
|
||||
}
|
||||
|
||||
// This is the pop.
|
||||
func (s *regexFcd) popFC() *regexFc {
|
||||
s.fcDepth--
|
||||
return &s.fcStack[s.fcDepth]
|
||||
}
|
||||
|
||||
// This is the top.
|
||||
func (s *regexFcd) topFC() *regexFc {
|
||||
return &s.fcStack[s.fcDepth-1]
|
||||
}
|
||||
|
||||
// Called in Beforechild to prevent further processing of the current child
|
||||
func (s *regexFcd) skipChild() {
|
||||
s.skipchild = true
|
||||
}
|
||||
|
||||
// FC computation and shortcut cases for each node type
|
||||
func (s *regexFcd) calculateFC(nt nodeType, node *regexNode, CurIndex int) {
|
||||
//fmt.Printf("NodeType: %v, CurIndex: %v, Desc: %v\n", nt, CurIndex, node.description())
|
||||
ci := false
|
||||
rtl := false
|
||||
|
||||
if nt <= ntRef {
|
||||
if (node.options & IgnoreCase) != 0 {
|
||||
ci = true
|
||||
}
|
||||
if (node.options & RightToLeft) != 0 {
|
||||
rtl = true
|
||||
}
|
||||
}
|
||||
|
||||
switch nt {
|
||||
case ntConcatenate | beforeChild, ntAlternate | beforeChild, ntTestref | beforeChild, ntLoop | beforeChild, ntLazyloop | beforeChild:
|
||||
break
|
||||
|
||||
case ntTestgroup | beforeChild:
|
||||
if CurIndex == 0 {
|
||||
s.skipChild()
|
||||
}
|
||||
break
|
||||
|
||||
case ntEmpty:
|
||||
s.pushFC(regexFc{nullable: true})
|
||||
break
|
||||
|
||||
case ntConcatenate | afterChild:
|
||||
if CurIndex != 0 {
|
||||
child := s.popFC()
|
||||
cumul := s.topFC()
|
||||
|
||||
s.failed = !cumul.addFC(*child, true)
|
||||
}
|
||||
|
||||
fc := s.topFC()
|
||||
if !fc.nullable {
|
||||
s.skipAllChildren = true
|
||||
}
|
||||
break
|
||||
|
||||
case ntTestgroup | afterChild:
|
||||
if CurIndex > 1 {
|
||||
child := s.popFC()
|
||||
cumul := s.topFC()
|
||||
|
||||
s.failed = !cumul.addFC(*child, false)
|
||||
}
|
||||
break
|
||||
|
||||
case ntAlternate | afterChild, ntTestref | afterChild:
|
||||
if CurIndex != 0 {
|
||||
child := s.popFC()
|
||||
cumul := s.topFC()
|
||||
|
||||
s.failed = !cumul.addFC(*child, false)
|
||||
}
|
||||
break
|
||||
|
||||
case ntLoop | afterChild, ntLazyloop | afterChild:
|
||||
if node.m == 0 {
|
||||
fc := s.topFC()
|
||||
fc.nullable = true
|
||||
}
|
||||
break
|
||||
|
||||
case ntGroup | beforeChild, ntGroup | afterChild, ntCapture | beforeChild, ntCapture | afterChild, ntGreedy | beforeChild, ntGreedy | afterChild:
|
||||
break
|
||||
|
||||
case ntRequire | beforeChild, ntPrevent | beforeChild:
|
||||
s.skipChild()
|
||||
s.pushFC(regexFc{nullable: true})
|
||||
break
|
||||
|
||||
case ntRequire | afterChild, ntPrevent | afterChild:
|
||||
break
|
||||
|
||||
case ntOne, ntNotone:
|
||||
s.pushFC(newRegexFc(node.ch, nt == ntNotone, false, ci))
|
||||
break
|
||||
|
||||
case ntOneloop, ntOnelazy:
|
||||
s.pushFC(newRegexFc(node.ch, false, node.m == 0, ci))
|
||||
break
|
||||
|
||||
case ntNotoneloop, ntNotonelazy:
|
||||
s.pushFC(newRegexFc(node.ch, true, node.m == 0, ci))
|
||||
break
|
||||
|
||||
case ntMulti:
|
||||
if len(node.str) == 0 {
|
||||
s.pushFC(regexFc{nullable: true})
|
||||
} else if !rtl {
|
||||
s.pushFC(newRegexFc(node.str[0], false, false, ci))
|
||||
} else {
|
||||
s.pushFC(newRegexFc(node.str[len(node.str)-1], false, false, ci))
|
||||
}
|
||||
break
|
||||
|
||||
case ntSet:
|
||||
s.pushFC(regexFc{cc: node.set.Copy(), nullable: false, caseInsensitive: ci})
|
||||
break
|
||||
|
||||
case ntSetloop, ntSetlazy:
|
||||
s.pushFC(regexFc{cc: node.set.Copy(), nullable: node.m == 0, caseInsensitive: ci})
|
||||
break
|
||||
|
||||
case ntRef:
|
||||
s.pushFC(regexFc{cc: *AnyClass(), nullable: true, caseInsensitive: false})
|
||||
break
|
||||
|
||||
case ntNothing, ntBol, ntEol, ntBoundary, ntNonboundary, ntECMABoundary, ntNonECMABoundary, ntBeginning, ntStart, ntEndZ, ntEnd:
|
||||
s.pushFC(regexFc{nullable: true})
|
||||
break
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected op code: %v", nt))
|
||||
}
|
||||
}
|
||||
|
||||
type regexFc struct {
|
||||
cc CharSet
|
||||
nullable bool
|
||||
caseInsensitive bool
|
||||
}
|
||||
|
||||
func newRegexFc(ch rune, not, nullable, caseInsensitive bool) regexFc {
|
||||
r := regexFc{
|
||||
caseInsensitive: caseInsensitive,
|
||||
nullable: nullable,
|
||||
}
|
||||
if not {
|
||||
if ch > 0 {
|
||||
r.cc.addRange('\x00', ch-1)
|
||||
}
|
||||
if ch < 0xFFFF {
|
||||
r.cc.addRange(ch+1, utf8.MaxRune)
|
||||
}
|
||||
} else {
|
||||
r.cc.addRange(ch, ch)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *regexFc) getFirstChars() CharSet {
|
||||
if r.caseInsensitive {
|
||||
r.cc.addLowercase()
|
||||
}
|
||||
|
||||
return r.cc
|
||||
}
|
||||
|
||||
func (r *regexFc) addFC(fc regexFc, concatenate bool) bool {
|
||||
if !r.cc.IsMergeable() || !fc.cc.IsMergeable() {
|
||||
return false
|
||||
}
|
||||
|
||||
if concatenate {
|
||||
if !r.nullable {
|
||||
return true
|
||||
}
|
||||
|
||||
if !fc.nullable {
|
||||
r.nullable = false
|
||||
}
|
||||
} else {
|
||||
if fc.nullable {
|
||||
r.nullable = true
|
||||
}
|
||||
}
|
||||
|
||||
r.caseInsensitive = r.caseInsensitive || fc.caseInsensitive
|
||||
r.cc.addSet(fc.cc)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// This is a related computation: it takes a RegexTree and computes the
|
||||
// leading substring if it sees one. It's quite trivial and gives up easily.
|
||||
func getPrefix(tree *RegexTree) *Prefix {
|
||||
var concatNode *regexNode
|
||||
nextChild := 0
|
||||
|
||||
curNode := tree.root
|
||||
|
||||
for {
|
||||
switch curNode.t {
|
||||
case ntConcatenate:
|
||||
if len(curNode.children) > 0 {
|
||||
concatNode = curNode
|
||||
nextChild = 0
|
||||
}
|
||||
|
||||
case ntGreedy, ntCapture:
|
||||
curNode = curNode.children[0]
|
||||
concatNode = nil
|
||||
continue
|
||||
|
||||
case ntOneloop, ntOnelazy:
|
||||
if curNode.m > 0 {
|
||||
return &Prefix{
|
||||
PrefixStr: repeat(curNode.ch, curNode.m),
|
||||
CaseInsensitive: (curNode.options & IgnoreCase) != 0,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case ntOne:
|
||||
return &Prefix{
|
||||
PrefixStr: []rune{curNode.ch},
|
||||
CaseInsensitive: (curNode.options & IgnoreCase) != 0,
|
||||
}
|
||||
|
||||
case ntMulti:
|
||||
return &Prefix{
|
||||
PrefixStr: curNode.str,
|
||||
CaseInsensitive: (curNode.options & IgnoreCase) != 0,
|
||||
}
|
||||
|
||||
case ntBol, ntEol, ntBoundary, ntECMABoundary, ntBeginning, ntStart,
|
||||
ntEndZ, ntEnd, ntEmpty, ntRequire, ntPrevent:
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if concatNode == nil || nextChild >= len(concatNode.children) {
|
||||
return nil
|
||||
}
|
||||
|
||||
curNode = concatNode.children[nextChild]
|
||||
nextChild++
|
||||
}
|
||||
}
|
||||
|
||||
// repeat the rune r, c times... up to the max of MaxPrefixSize
|
||||
func repeat(r rune, c int) []rune {
|
||||
if c > MaxPrefixSize {
|
||||
c = MaxPrefixSize
|
||||
}
|
||||
|
||||
ret := make([]rune, c)
|
||||
|
||||
// binary growth using copy for speed
|
||||
ret[0] = r
|
||||
bp := 1
|
||||
for bp < len(ret) {
|
||||
copy(ret[bp:], ret[:bp])
|
||||
bp *= 2
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// BmPrefix precomputes the Boyer-Moore
|
||||
// tables for fast string scanning. These tables allow
|
||||
// you to scan for the first occurrence of a string within
|
||||
// a large body of text without examining every character.
|
||||
// The performance of the heuristic depends on the actual
|
||||
// string and the text being searched, but usually, the longer
|
||||
// the string that is being searched for, the fewer characters
|
||||
// need to be examined.
|
||||
type BmPrefix struct {
|
||||
positive []int
|
||||
negativeASCII []int
|
||||
negativeUnicode [][]int
|
||||
pattern []rune
|
||||
lowASCII rune
|
||||
highASCII rune
|
||||
rightToLeft bool
|
||||
caseInsensitive bool
|
||||
}
|
||||
|
||||
func newBmPrefix(pattern []rune, caseInsensitive, rightToLeft bool) *BmPrefix {
|
||||
|
||||
b := &BmPrefix{
|
||||
rightToLeft: rightToLeft,
|
||||
caseInsensitive: caseInsensitive,
|
||||
pattern: pattern,
|
||||
}
|
||||
|
||||
if caseInsensitive {
|
||||
for i := 0; i < len(b.pattern); i++ {
|
||||
// We do the ToLower character by character for consistency. With surrogate chars, doing
|
||||
// a ToLower on the entire string could actually change the surrogate pair. This is more correct
|
||||
// linguistically, but since Regex doesn't support surrogates, it's more important to be
|
||||
// consistent.
|
||||
|
||||
b.pattern[i] = unicode.ToLower(b.pattern[i])
|
||||
}
|
||||
}
|
||||
|
||||
var beforefirst, last, bump int
|
||||
var scan, match int
|
||||
|
||||
if !rightToLeft {
|
||||
beforefirst = -1
|
||||
last = len(b.pattern) - 1
|
||||
bump = 1
|
||||
} else {
|
||||
beforefirst = len(b.pattern)
|
||||
last = 0
|
||||
bump = -1
|
||||
}
|
||||
|
||||
// PART I - the good-suffix shift table
|
||||
//
|
||||
// compute the positive requirement:
|
||||
// if char "i" is the first one from the right that doesn't match,
|
||||
// then we know the matcher can advance by _positive[i].
|
||||
//
|
||||
// This algorithm is a simplified variant of the standard
|
||||
// Boyer-Moore good suffix calculation.
|
||||
|
||||
b.positive = make([]int, len(b.pattern))
|
||||
|
||||
examine := last
|
||||
ch := b.pattern[examine]
|
||||
b.positive[examine] = bump
|
||||
examine -= bump
|
||||
|
||||
Outerloop:
|
||||
for {
|
||||
// find an internal char (examine) that matches the tail
|
||||
|
||||
for {
|
||||
if examine == beforefirst {
|
||||
break Outerloop
|
||||
}
|
||||
if b.pattern[examine] == ch {
|
||||
break
|
||||
}
|
||||
examine -= bump
|
||||
}
|
||||
|
||||
match = last
|
||||
scan = examine
|
||||
|
||||
// find the length of the match
|
||||
for {
|
||||
if scan == beforefirst || b.pattern[match] != b.pattern[scan] {
|
||||
// at the end of the match, note the difference in _positive
|
||||
// this is not the length of the match, but the distance from the internal match
|
||||
// to the tail suffix.
|
||||
if b.positive[match] == 0 {
|
||||
b.positive[match] = match - scan
|
||||
}
|
||||
|
||||
// System.Diagnostics.Debug.WriteLine("Set positive[" + match + "] to " + (match - scan));
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
scan -= bump
|
||||
match -= bump
|
||||
}
|
||||
|
||||
examine -= bump
|
||||
}
|
||||
|
||||
match = last - bump
|
||||
|
||||
// scan for the chars for which there are no shifts that yield a different candidate
|
||||
|
||||
// The inside of the if statement used to say
|
||||
// "_positive[match] = last - beforefirst;"
|
||||
// This is slightly less aggressive in how much we skip, but at worst it
|
||||
// should mean a little more work rather than skipping a potential match.
|
||||
for match != beforefirst {
|
||||
if b.positive[match] == 0 {
|
||||
b.positive[match] = bump
|
||||
}
|
||||
|
||||
match -= bump
|
||||
}
|
||||
|
||||
// PART II - the bad-character shift table
|
||||
//
|
||||
// compute the negative requirement:
|
||||
// if char "ch" is the reject character when testing position "i",
|
||||
// we can slide up by _negative[ch];
|
||||
// (_negative[ch] = str.Length - 1 - str.LastIndexOf(ch))
|
||||
//
|
||||
// the lookup table is divided into ASCII and Unicode portions;
|
||||
// only those parts of the Unicode 16-bit code set that actually
|
||||
// appear in the string are in the table. (Maximum size with
|
||||
// Unicode is 65K; ASCII only case is 512 bytes.)
|
||||
|
||||
b.negativeASCII = make([]int, 128)
|
||||
|
||||
for i := 0; i < len(b.negativeASCII); i++ {
|
||||
b.negativeASCII[i] = last - beforefirst
|
||||
}
|
||||
|
||||
b.lowASCII = 127
|
||||
b.highASCII = 0
|
||||
|
||||
for examine = last; examine != beforefirst; examine -= bump {
|
||||
ch = b.pattern[examine]
|
||||
|
||||
switch {
|
||||
case ch < 128:
|
||||
if b.lowASCII > ch {
|
||||
b.lowASCII = ch
|
||||
}
|
||||
|
||||
if b.highASCII < ch {
|
||||
b.highASCII = ch
|
||||
}
|
||||
|
||||
if b.negativeASCII[ch] == last-beforefirst {
|
||||
b.negativeASCII[ch] = last - examine
|
||||
}
|
||||
case ch <= 0xffff:
|
||||
i, j := ch>>8, ch&0xFF
|
||||
|
||||
if b.negativeUnicode == nil {
|
||||
b.negativeUnicode = make([][]int, 256)
|
||||
}
|
||||
|
||||
if b.negativeUnicode[i] == nil {
|
||||
newarray := make([]int, 256)
|
||||
|
||||
for k := 0; k < len(newarray); k++ {
|
||||
newarray[k] = last - beforefirst
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
copy(newarray, b.negativeASCII)
|
||||
//TODO: this line needed?
|
||||
b.negativeASCII = newarray
|
||||
}
|
||||
|
||||
b.negativeUnicode[i] = newarray
|
||||
}
|
||||
|
||||
if b.negativeUnicode[i][j] == last-beforefirst {
|
||||
b.negativeUnicode[i][j] = last - examine
|
||||
}
|
||||
default:
|
||||
// we can't do the filter because this algo doesn't support
|
||||
// unicode chars >0xffff
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BmPrefix) String() string {
|
||||
return string(b.pattern)
|
||||
}
|
||||
|
||||
// Dump returns the contents of the filter as a human readable string
|
||||
func (b *BmPrefix) Dump(indent string) string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
fmt.Fprintf(buf, "%sBM Pattern: %s\n%sPositive: ", indent, string(b.pattern), indent)
|
||||
for i := 0; i < len(b.positive); i++ {
|
||||
buf.WriteString(strconv.Itoa(b.positive[i]))
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
buf.WriteRune('\n')
|
||||
|
||||
if b.negativeASCII != nil {
|
||||
buf.WriteString(indent)
|
||||
buf.WriteString("Negative table\n")
|
||||
for i := 0; i < len(b.negativeASCII); i++ {
|
||||
if b.negativeASCII[i] != len(b.pattern) {
|
||||
fmt.Fprintf(buf, "%s %s %s\n", indent, Escape(string(rune(i))), strconv.Itoa(b.negativeASCII[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Scan uses the Boyer-Moore algorithm to find the first occurrence
|
||||
// of the specified string within text, beginning at index, and
|
||||
// constrained within beglimit and endlimit.
|
||||
//
|
||||
// The direction and case-sensitivity of the match is determined
|
||||
// by the arguments to the RegexBoyerMoore constructor.
|
||||
func (b *BmPrefix) Scan(text []rune, index, beglimit, endlimit int) int {
|
||||
var (
|
||||
defadv, test, test2 int
|
||||
match, startmatch, endmatch int
|
||||
bump, advance int
|
||||
chTest rune
|
||||
unicodeLookup []int
|
||||
)
|
||||
|
||||
if !b.rightToLeft {
|
||||
defadv = len(b.pattern)
|
||||
startmatch = len(b.pattern) - 1
|
||||
endmatch = 0
|
||||
test = index + defadv - 1
|
||||
bump = 1
|
||||
} else {
|
||||
defadv = -len(b.pattern)
|
||||
startmatch = 0
|
||||
endmatch = -defadv - 1
|
||||
test = index + defadv
|
||||
bump = -1
|
||||
}
|
||||
|
||||
chMatch := b.pattern[startmatch]
|
||||
|
||||
for {
|
||||
if test >= endlimit || test < beglimit {
|
||||
return -1
|
||||
}
|
||||
|
||||
chTest = text[test]
|
||||
|
||||
if b.caseInsensitive {
|
||||
chTest = unicode.ToLower(chTest)
|
||||
}
|
||||
|
||||
if chTest != chMatch {
|
||||
if chTest < 128 {
|
||||
advance = b.negativeASCII[chTest]
|
||||
} else if chTest < 0xffff && len(b.negativeUnicode) > 0 {
|
||||
unicodeLookup = b.negativeUnicode[chTest>>8]
|
||||
if len(unicodeLookup) > 0 {
|
||||
advance = unicodeLookup[chTest&0xFF]
|
||||
} else {
|
||||
advance = defadv
|
||||
}
|
||||
} else {
|
||||
advance = defadv
|
||||
}
|
||||
|
||||
test += advance
|
||||
} else { // if (chTest == chMatch)
|
||||
test2 = test
|
||||
match = startmatch
|
||||
|
||||
for {
|
||||
if match == endmatch {
|
||||
if b.rightToLeft {
|
||||
return test2 + 1
|
||||
} else {
|
||||
return test2
|
||||
}
|
||||
}
|
||||
|
||||
match -= bump
|
||||
test2 -= bump
|
||||
|
||||
chTest = text[test2]
|
||||
|
||||
if b.caseInsensitive {
|
||||
chTest = unicode.ToLower(chTest)
|
||||
}
|
||||
|
||||
if chTest != b.pattern[match] {
|
||||
advance = b.positive[match]
|
||||
if chTest < 128 {
|
||||
test2 = (match - startmatch) + b.negativeASCII[chTest]
|
||||
} else if chTest < 0xffff && len(b.negativeUnicode) > 0 {
|
||||
unicodeLookup = b.negativeUnicode[chTest>>8]
|
||||
if len(unicodeLookup) > 0 {
|
||||
test2 = (match - startmatch) + unicodeLookup[chTest&0xFF]
|
||||
} else {
|
||||
test += advance
|
||||
break
|
||||
}
|
||||
} else {
|
||||
test += advance
|
||||
break
|
||||
}
|
||||
|
||||
if b.rightToLeft {
|
||||
if test2 < advance {
|
||||
advance = test2
|
||||
}
|
||||
} else if test2 > advance {
|
||||
advance = test2
|
||||
}
|
||||
|
||||
test += advance
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When a regex is anchored, we can do a quick IsMatch test instead of a Scan
|
||||
func (b *BmPrefix) IsMatch(text []rune, index, beglimit, endlimit int) bool {
|
||||
if !b.rightToLeft {
|
||||
if index < beglimit || endlimit-index < len(b.pattern) {
|
||||
return false
|
||||
}
|
||||
|
||||
return b.matchPattern(text, index)
|
||||
} else {
|
||||
if index > endlimit || index-beglimit < len(b.pattern) {
|
||||
return false
|
||||
}
|
||||
|
||||
return b.matchPattern(text, index-len(b.pattern))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BmPrefix) matchPattern(text []rune, index int) bool {
|
||||
if len(text)-index < len(b.pattern) {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.caseInsensitive {
|
||||
for i := 0; i < len(b.pattern); i++ {
|
||||
//Debug.Assert(textinfo.ToLower(_pattern[i]) == _pattern[i], "pattern should be converted to lower case in constructor!");
|
||||
if unicode.ToLower(text[index+i]) != b.pattern[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
for i := 0; i < len(b.pattern); i++ {
|
||||
if text[index+i] != b.pattern[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
type AnchorLoc int16
|
||||
|
||||
// where the regex can be pegged
|
||||
const (
|
||||
AnchorBeginning AnchorLoc = 0x0001
|
||||
AnchorBol = 0x0002
|
||||
AnchorStart = 0x0004
|
||||
AnchorEol = 0x0008
|
||||
AnchorEndZ = 0x0010
|
||||
AnchorEnd = 0x0020
|
||||
AnchorBoundary = 0x0040
|
||||
AnchorECMABoundary = 0x0080
|
||||
)
|
||||
|
||||
func getAnchors(tree *RegexTree) AnchorLoc {
|
||||
|
||||
var concatNode *regexNode
|
||||
nextChild, result := 0, AnchorLoc(0)
|
||||
|
||||
curNode := tree.root
|
||||
|
||||
for {
|
||||
switch curNode.t {
|
||||
case ntConcatenate:
|
||||
if len(curNode.children) > 0 {
|
||||
concatNode = curNode
|
||||
nextChild = 0
|
||||
}
|
||||
|
||||
case ntGreedy, ntCapture:
|
||||
curNode = curNode.children[0]
|
||||
concatNode = nil
|
||||
continue
|
||||
|
||||
case ntBol, ntEol, ntBoundary, ntECMABoundary, ntBeginning,
|
||||
ntStart, ntEndZ, ntEnd:
|
||||
return result | anchorFromType(curNode.t)
|
||||
|
||||
case ntEmpty, ntRequire, ntPrevent:
|
||||
|
||||
default:
|
||||
return result
|
||||
}
|
||||
|
||||
if concatNode == nil || nextChild >= len(concatNode.children) {
|
||||
return result
|
||||
}
|
||||
|
||||
curNode = concatNode.children[nextChild]
|
||||
nextChild++
|
||||
}
|
||||
}
|
||||
|
||||
func anchorFromType(t nodeType) AnchorLoc {
|
||||
switch t {
|
||||
case ntBol:
|
||||
return AnchorBol
|
||||
case ntEol:
|
||||
return AnchorEol
|
||||
case ntBoundary:
|
||||
return AnchorBoundary
|
||||
case ntECMABoundary:
|
||||
return AnchorECMABoundary
|
||||
case ntBeginning:
|
||||
return AnchorBeginning
|
||||
case ntStart:
|
||||
return AnchorStart
|
||||
case ntEndZ:
|
||||
return AnchorEndZ
|
||||
case ntEnd:
|
||||
return AnchorEnd
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// anchorDescription returns a human-readable description of the anchors
|
||||
func (anchors AnchorLoc) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if 0 != (anchors & AnchorBeginning) {
|
||||
buf.WriteString(", Beginning")
|
||||
}
|
||||
if 0 != (anchors & AnchorStart) {
|
||||
buf.WriteString(", Start")
|
||||
}
|
||||
if 0 != (anchors & AnchorBol) {
|
||||
buf.WriteString(", Bol")
|
||||
}
|
||||
if 0 != (anchors & AnchorBoundary) {
|
||||
buf.WriteString(", Boundary")
|
||||
}
|
||||
if 0 != (anchors & AnchorECMABoundary) {
|
||||
buf.WriteString(", ECMABoundary")
|
||||
}
|
||||
if 0 != (anchors & AnchorEol) {
|
||||
buf.WriteString(", Eol")
|
||||
}
|
||||
if 0 != (anchors & AnchorEnd) {
|
||||
buf.WriteString(", End")
|
||||
}
|
||||
if 0 != (anchors & AnchorEndZ) {
|
||||
buf.WriteString(", EndZ")
|
||||
}
|
||||
|
||||
// trim off comma
|
||||
if buf.Len() >= 2 {
|
||||
return buf.String()[2:]
|
||||
}
|
||||
return "None"
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ReplacerData struct {
|
||||
Rep string
|
||||
Strings []string
|
||||
Rules []int
|
||||
}
|
||||
|
||||
const (
|
||||
replaceSpecials = 4
|
||||
replaceLeftPortion = -1
|
||||
replaceRightPortion = -2
|
||||
replaceLastGroup = -3
|
||||
replaceWholeString = -4
|
||||
)
|
||||
|
||||
//ErrReplacementError is a general error during parsing the replacement text
|
||||
var ErrReplacementError = errors.New("Replacement pattern error.")
|
||||
|
||||
// NewReplacerData will populate a reusable replacer data struct based on the given replacement string
|
||||
// and the capture group data from a regexp
|
||||
func NewReplacerData(rep string, caps map[int]int, capsize int, capnames map[string]int, op RegexOptions) (*ReplacerData, error) {
|
||||
p := parser{
|
||||
options: op,
|
||||
caps: caps,
|
||||
capsize: capsize,
|
||||
capnames: capnames,
|
||||
}
|
||||
p.setPattern(rep)
|
||||
concat, err := p.scanReplacement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if concat.t != ntConcatenate {
|
||||
panic(ErrReplacementError)
|
||||
}
|
||||
|
||||
sb := &bytes.Buffer{}
|
||||
var (
|
||||
strings []string
|
||||
rules []int
|
||||
)
|
||||
|
||||
for _, child := range concat.children {
|
||||
switch child.t {
|
||||
case ntMulti:
|
||||
child.writeStrToBuf(sb)
|
||||
|
||||
case ntOne:
|
||||
sb.WriteRune(child.ch)
|
||||
|
||||
case ntRef:
|
||||
if sb.Len() > 0 {
|
||||
rules = append(rules, len(strings))
|
||||
strings = append(strings, sb.String())
|
||||
sb.Reset()
|
||||
}
|
||||
slot := child.m
|
||||
|
||||
if len(caps) > 0 && slot >= 0 {
|
||||
slot = caps[slot]
|
||||
}
|
||||
|
||||
rules = append(rules, -replaceSpecials-1-slot)
|
||||
|
||||
default:
|
||||
panic(ErrReplacementError)
|
||||
}
|
||||
}
|
||||
|
||||
if sb.Len() > 0 {
|
||||
rules = append(rules, len(strings))
|
||||
strings = append(strings, sb.String())
|
||||
}
|
||||
|
||||
return &ReplacerData{
|
||||
Rep: rep,
|
||||
Strings: strings,
|
||||
Rules: rules,
|
||||
}, nil
|
||||
}
|
||||
+654
@@ -0,0 +1,654 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type RegexTree struct {
|
||||
root *regexNode
|
||||
caps map[int]int
|
||||
capnumlist []int
|
||||
captop int
|
||||
Capnames map[string]int
|
||||
Caplist []string
|
||||
options RegexOptions
|
||||
}
|
||||
|
||||
// It is built into a parsed tree for a regular expression.
|
||||
|
||||
// Implementation notes:
|
||||
//
|
||||
// Since the node tree is a temporary data structure only used
|
||||
// during compilation of the regexp to integer codes, it's
|
||||
// designed for clarity and convenience rather than
|
||||
// space efficiency.
|
||||
//
|
||||
// RegexNodes are built into a tree, linked by the n.children list.
|
||||
// Each node also has a n.parent and n.ichild member indicating
|
||||
// its parent and which child # it is in its parent's list.
|
||||
//
|
||||
// RegexNodes come in as many types as there are constructs in
|
||||
// a regular expression, for example, "concatenate", "alternate",
|
||||
// "one", "rept", "group". There are also node types for basic
|
||||
// peephole optimizations, e.g., "onerep", "notsetrep", etc.
|
||||
//
|
||||
// Because perl 5 allows "lookback" groups that scan backwards,
|
||||
// each node also gets a "direction". Normally the value of
|
||||
// boolean n.backward = false.
|
||||
//
|
||||
// During parsing, top-level nodes are also stacked onto a parse
|
||||
// stack (a stack of trees). For this purpose we have a n.next
|
||||
// pointer. [Note that to save a few bytes, we could overload the
|
||||
// n.parent pointer instead.]
|
||||
//
|
||||
// On the parse stack, each tree has a "role" - basically, the
|
||||
// nonterminal in the grammar that the parser has currently
|
||||
// assigned to the tree. That code is stored in n.role.
|
||||
//
|
||||
// Finally, some of the different kinds of nodes have data.
|
||||
// Two integers (for the looping constructs) are stored in
|
||||
// n.operands, an an object (either a string or a set)
|
||||
// is stored in n.data
|
||||
type regexNode struct {
|
||||
t nodeType
|
||||
children []*regexNode
|
||||
str []rune
|
||||
set *CharSet
|
||||
ch rune
|
||||
m int
|
||||
n int
|
||||
options RegexOptions
|
||||
next *regexNode
|
||||
}
|
||||
|
||||
type nodeType int32
|
||||
|
||||
const (
|
||||
// The following are leaves, and correspond to primitive operations
|
||||
|
||||
ntOnerep nodeType = 0 // lef,back char,min,max a {n}
|
||||
ntNotonerep = 1 // lef,back char,min,max .{n}
|
||||
ntSetrep = 2 // lef,back set,min,max [\d]{n}
|
||||
ntOneloop = 3 // lef,back char,min,max a {,n}
|
||||
ntNotoneloop = 4 // lef,back char,min,max .{,n}
|
||||
ntSetloop = 5 // lef,back set,min,max [\d]{,n}
|
||||
ntOnelazy = 6 // lef,back char,min,max a {,n}?
|
||||
ntNotonelazy = 7 // lef,back char,min,max .{,n}?
|
||||
ntSetlazy = 8 // lef,back set,min,max [\d]{,n}?
|
||||
ntOne = 9 // lef char a
|
||||
ntNotone = 10 // lef char [^a]
|
||||
ntSet = 11 // lef set [a-z\s] \w \s \d
|
||||
ntMulti = 12 // lef string abcd
|
||||
ntRef = 13 // lef group \#
|
||||
ntBol = 14 // ^
|
||||
ntEol = 15 // $
|
||||
ntBoundary = 16 // \b
|
||||
ntNonboundary = 17 // \B
|
||||
ntBeginning = 18 // \A
|
||||
ntStart = 19 // \G
|
||||
ntEndZ = 20 // \Z
|
||||
ntEnd = 21 // \Z
|
||||
|
||||
// Interior nodes do not correspond to primitive operations, but
|
||||
// control structures compositing other operations
|
||||
|
||||
// Concat and alternate take n children, and can run forward or backwards
|
||||
|
||||
ntNothing = 22 // []
|
||||
ntEmpty = 23 // ()
|
||||
ntAlternate = 24 // a|b
|
||||
ntConcatenate = 25 // ab
|
||||
ntLoop = 26 // m,x * + ? {,}
|
||||
ntLazyloop = 27 // m,x *? +? ?? {,}?
|
||||
ntCapture = 28 // n ()
|
||||
ntGroup = 29 // (?:)
|
||||
ntRequire = 30 // (?=) (?<=)
|
||||
ntPrevent = 31 // (?!) (?<!)
|
||||
ntGreedy = 32 // (?>) (?<)
|
||||
ntTestref = 33 // (?(n) | )
|
||||
ntTestgroup = 34 // (?(...) | )
|
||||
|
||||
ntECMABoundary = 41 // \b
|
||||
ntNonECMABoundary = 42 // \B
|
||||
)
|
||||
|
||||
func newRegexNode(t nodeType, opt RegexOptions) *regexNode {
|
||||
return ®exNode{
|
||||
t: t,
|
||||
options: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegexNodeCh(t nodeType, opt RegexOptions, ch rune) *regexNode {
|
||||
return ®exNode{
|
||||
t: t,
|
||||
options: opt,
|
||||
ch: ch,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegexNodeStr(t nodeType, opt RegexOptions, str []rune) *regexNode {
|
||||
return ®exNode{
|
||||
t: t,
|
||||
options: opt,
|
||||
str: str,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegexNodeSet(t nodeType, opt RegexOptions, set *CharSet) *regexNode {
|
||||
return ®exNode{
|
||||
t: t,
|
||||
options: opt,
|
||||
set: set,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegexNodeM(t nodeType, opt RegexOptions, m int) *regexNode {
|
||||
return ®exNode{
|
||||
t: t,
|
||||
options: opt,
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
func newRegexNodeMN(t nodeType, opt RegexOptions, m, n int) *regexNode {
|
||||
return ®exNode{
|
||||
t: t,
|
||||
options: opt,
|
||||
m: m,
|
||||
n: n,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *regexNode) writeStrToBuf(buf *bytes.Buffer) {
|
||||
for i := 0; i < len(n.str); i++ {
|
||||
buf.WriteRune(n.str[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (n *regexNode) addChild(child *regexNode) {
|
||||
reduced := child.reduce()
|
||||
n.children = append(n.children, reduced)
|
||||
reduced.next = n
|
||||
}
|
||||
|
||||
func (n *regexNode) insertChildren(afterIndex int, nodes []*regexNode) {
|
||||
newChildren := make([]*regexNode, 0, len(n.children)+len(nodes))
|
||||
n.children = append(append(append(newChildren, n.children[:afterIndex]...), nodes...), n.children[afterIndex:]...)
|
||||
}
|
||||
|
||||
// removes children including the start but not the end index
|
||||
func (n *regexNode) removeChildren(startIndex, endIndex int) {
|
||||
n.children = append(n.children[:startIndex], n.children[endIndex:]...)
|
||||
}
|
||||
|
||||
// Pass type as OneLazy or OneLoop
|
||||
func (n *regexNode) makeRep(t nodeType, min, max int) {
|
||||
n.t += (t - ntOne)
|
||||
n.m = min
|
||||
n.n = max
|
||||
}
|
||||
|
||||
func (n *regexNode) reduce() *regexNode {
|
||||
switch n.t {
|
||||
case ntAlternate:
|
||||
return n.reduceAlternation()
|
||||
|
||||
case ntConcatenate:
|
||||
return n.reduceConcatenation()
|
||||
|
||||
case ntLoop, ntLazyloop:
|
||||
return n.reduceRep()
|
||||
|
||||
case ntGroup:
|
||||
return n.reduceGroup()
|
||||
|
||||
case ntSet, ntSetloop:
|
||||
return n.reduceSet()
|
||||
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
// Basic optimization. Single-letter alternations can be replaced
|
||||
// by faster set specifications, and nested alternations with no
|
||||
// intervening operators can be flattened:
|
||||
//
|
||||
// a|b|c|def|g|h -> [a-c]|def|[gh]
|
||||
// apple|(?:orange|pear)|grape -> apple|orange|pear|grape
|
||||
func (n *regexNode) reduceAlternation() *regexNode {
|
||||
if len(n.children) == 0 {
|
||||
return newRegexNode(ntNothing, n.options)
|
||||
}
|
||||
|
||||
wasLastSet := false
|
||||
lastNodeCannotMerge := false
|
||||
var optionsLast RegexOptions
|
||||
var i, j int
|
||||
|
||||
for i, j = 0, 0; i < len(n.children); i, j = i+1, j+1 {
|
||||
at := n.children[i]
|
||||
|
||||
if j < i {
|
||||
n.children[j] = at
|
||||
}
|
||||
|
||||
for {
|
||||
if at.t == ntAlternate {
|
||||
for k := 0; k < len(at.children); k++ {
|
||||
at.children[k].next = n
|
||||
}
|
||||
n.insertChildren(i+1, at.children)
|
||||
|
||||
j--
|
||||
} else if at.t == ntSet || at.t == ntOne {
|
||||
// Cannot merge sets if L or I options differ, or if either are negated.
|
||||
optionsAt := at.options & (RightToLeft | IgnoreCase)
|
||||
|
||||
if at.t == ntSet {
|
||||
if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge || !at.set.IsMergeable() {
|
||||
wasLastSet = true
|
||||
lastNodeCannotMerge = !at.set.IsMergeable()
|
||||
optionsLast = optionsAt
|
||||
break
|
||||
}
|
||||
} else if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge {
|
||||
wasLastSet = true
|
||||
lastNodeCannotMerge = false
|
||||
optionsLast = optionsAt
|
||||
break
|
||||
}
|
||||
|
||||
// The last node was a Set or a One, we're a Set or One and our options are the same.
|
||||
// Merge the two nodes.
|
||||
j--
|
||||
prev := n.children[j]
|
||||
|
||||
var prevCharClass *CharSet
|
||||
if prev.t == ntOne {
|
||||
prevCharClass = &CharSet{}
|
||||
prevCharClass.addChar(prev.ch)
|
||||
} else {
|
||||
prevCharClass = prev.set
|
||||
}
|
||||
|
||||
if at.t == ntOne {
|
||||
prevCharClass.addChar(at.ch)
|
||||
} else {
|
||||
prevCharClass.addSet(*at.set)
|
||||
}
|
||||
|
||||
prev.t = ntSet
|
||||
prev.set = prevCharClass
|
||||
} else if at.t == ntNothing {
|
||||
j--
|
||||
} else {
|
||||
wasLastSet = false
|
||||
lastNodeCannotMerge = false
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if j < i {
|
||||
n.removeChildren(j, i)
|
||||
}
|
||||
|
||||
return n.stripEnation(ntNothing)
|
||||
}
|
||||
|
||||
// Basic optimization. Adjacent strings can be concatenated.
|
||||
//
|
||||
// (?:abc)(?:def) -> abcdef
|
||||
func (n *regexNode) reduceConcatenation() *regexNode {
|
||||
// Eliminate empties and concat adjacent strings/chars
|
||||
|
||||
var optionsLast RegexOptions
|
||||
var optionsAt RegexOptions
|
||||
var i, j int
|
||||
|
||||
if len(n.children) == 0 {
|
||||
return newRegexNode(ntEmpty, n.options)
|
||||
}
|
||||
|
||||
wasLastString := false
|
||||
|
||||
for i, j = 0, 0; i < len(n.children); i, j = i+1, j+1 {
|
||||
var at, prev *regexNode
|
||||
|
||||
at = n.children[i]
|
||||
|
||||
if j < i {
|
||||
n.children[j] = at
|
||||
}
|
||||
|
||||
if at.t == ntConcatenate &&
|
||||
((at.options & RightToLeft) == (n.options & RightToLeft)) {
|
||||
for k := 0; k < len(at.children); k++ {
|
||||
at.children[k].next = n
|
||||
}
|
||||
|
||||
//insert at.children at i+1 index in n.children
|
||||
n.insertChildren(i+1, at.children)
|
||||
|
||||
j--
|
||||
} else if at.t == ntMulti || at.t == ntOne {
|
||||
// Cannot merge strings if L or I options differ
|
||||
optionsAt = at.options & (RightToLeft | IgnoreCase)
|
||||
|
||||
if !wasLastString || optionsLast != optionsAt {
|
||||
wasLastString = true
|
||||
optionsLast = optionsAt
|
||||
continue
|
||||
}
|
||||
|
||||
j--
|
||||
prev = n.children[j]
|
||||
|
||||
if prev.t == ntOne {
|
||||
prev.t = ntMulti
|
||||
prev.str = []rune{prev.ch}
|
||||
}
|
||||
|
||||
if (optionsAt & RightToLeft) == 0 {
|
||||
if at.t == ntOne {
|
||||
prev.str = append(prev.str, at.ch)
|
||||
} else {
|
||||
prev.str = append(prev.str, at.str...)
|
||||
}
|
||||
} else {
|
||||
if at.t == ntOne {
|
||||
// insert at the front by expanding our slice, copying the data over, and then setting the value
|
||||
prev.str = append(prev.str, 0)
|
||||
copy(prev.str[1:], prev.str)
|
||||
prev.str[0] = at.ch
|
||||
} else {
|
||||
//insert at the front...this one we'll make a new slice and copy both into it
|
||||
merge := make([]rune, len(prev.str)+len(at.str))
|
||||
copy(merge, at.str)
|
||||
copy(merge[len(at.str):], prev.str)
|
||||
prev.str = merge
|
||||
}
|
||||
}
|
||||
} else if at.t == ntEmpty {
|
||||
j--
|
||||
} else {
|
||||
wasLastString = false
|
||||
}
|
||||
}
|
||||
|
||||
if j < i {
|
||||
// remove indices j through i from the children
|
||||
n.removeChildren(j, i)
|
||||
}
|
||||
|
||||
return n.stripEnation(ntEmpty)
|
||||
}
|
||||
|
||||
// Nested repeaters just get multiplied with each other if they're not
|
||||
// too lumpy
|
||||
func (n *regexNode) reduceRep() *regexNode {
|
||||
|
||||
u := n
|
||||
t := n.t
|
||||
min := n.m
|
||||
max := n.n
|
||||
|
||||
for {
|
||||
if len(u.children) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
child := u.children[0]
|
||||
|
||||
// multiply reps of the same type only
|
||||
if child.t != t {
|
||||
childType := child.t
|
||||
|
||||
if !(childType >= ntOneloop && childType <= ntSetloop && t == ntLoop ||
|
||||
childType >= ntOnelazy && childType <= ntSetlazy && t == ntLazyloop) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// child can be too lumpy to blur, e.g., (a {100,105}) {3} or (a {2,})?
|
||||
// [but things like (a {2,})+ are not too lumpy...]
|
||||
if u.m == 0 && child.m > 1 || child.n < child.m*2 {
|
||||
break
|
||||
}
|
||||
|
||||
u = child
|
||||
if u.m > 0 {
|
||||
if (math.MaxInt32-1)/u.m < min {
|
||||
u.m = math.MaxInt32
|
||||
} else {
|
||||
u.m = u.m * min
|
||||
}
|
||||
}
|
||||
if u.n > 0 {
|
||||
if (math.MaxInt32-1)/u.n < max {
|
||||
u.n = math.MaxInt32
|
||||
} else {
|
||||
u.n = u.n * max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if math.MaxInt32 == min {
|
||||
return newRegexNode(ntNothing, n.options)
|
||||
}
|
||||
return u
|
||||
|
||||
}
|
||||
|
||||
// Simple optimization. If a concatenation or alternation has only
|
||||
// one child strip out the intermediate node. If it has zero children,
|
||||
// turn it into an empty.
|
||||
func (n *regexNode) stripEnation(emptyType nodeType) *regexNode {
|
||||
switch len(n.children) {
|
||||
case 0:
|
||||
return newRegexNode(emptyType, n.options)
|
||||
case 1:
|
||||
return n.children[0]
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
func (n *regexNode) reduceGroup() *regexNode {
|
||||
u := n
|
||||
|
||||
for u.t == ntGroup {
|
||||
u = u.children[0]
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// Simple optimization. If a set is a singleton, an inverse singleton,
|
||||
// or empty, it's transformed accordingly.
|
||||
func (n *regexNode) reduceSet() *regexNode {
|
||||
// Extract empty-set, one and not-one case as special
|
||||
|
||||
if n.set == nil {
|
||||
n.t = ntNothing
|
||||
} else if n.set.IsSingleton() {
|
||||
n.ch = n.set.SingletonChar()
|
||||
n.set = nil
|
||||
n.t += (ntOne - ntSet)
|
||||
} else if n.set.IsSingletonInverse() {
|
||||
n.ch = n.set.SingletonChar()
|
||||
n.set = nil
|
||||
n.t += (ntNotone - ntSet)
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *regexNode) reverseLeft() *regexNode {
|
||||
if n.options&RightToLeft != 0 && n.t == ntConcatenate && len(n.children) > 0 {
|
||||
//reverse children order
|
||||
for left, right := 0, len(n.children)-1; left < right; left, right = left+1, right-1 {
|
||||
n.children[left], n.children[right] = n.children[right], n.children[left]
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *regexNode) makeQuantifier(lazy bool, min, max int) *regexNode {
|
||||
if min == 0 && max == 0 {
|
||||
return newRegexNode(ntEmpty, n.options)
|
||||
}
|
||||
|
||||
if min == 1 && max == 1 {
|
||||
return n
|
||||
}
|
||||
|
||||
switch n.t {
|
||||
case ntOne, ntNotone, ntSet:
|
||||
if lazy {
|
||||
n.makeRep(Onelazy, min, max)
|
||||
} else {
|
||||
n.makeRep(Oneloop, min, max)
|
||||
}
|
||||
return n
|
||||
|
||||
default:
|
||||
var t nodeType
|
||||
if lazy {
|
||||
t = ntLazyloop
|
||||
} else {
|
||||
t = ntLoop
|
||||
}
|
||||
result := newRegexNodeMN(t, n.options, min, max)
|
||||
result.addChild(n)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// debug functions
|
||||
|
||||
var typeStr = []string{
|
||||
"Onerep", "Notonerep", "Setrep",
|
||||
"Oneloop", "Notoneloop", "Setloop",
|
||||
"Onelazy", "Notonelazy", "Setlazy",
|
||||
"One", "Notone", "Set",
|
||||
"Multi", "Ref",
|
||||
"Bol", "Eol", "Boundary", "Nonboundary",
|
||||
"Beginning", "Start", "EndZ", "End",
|
||||
"Nothing", "Empty",
|
||||
"Alternate", "Concatenate",
|
||||
"Loop", "Lazyloop",
|
||||
"Capture", "Group", "Require", "Prevent", "Greedy",
|
||||
"Testref", "Testgroup",
|
||||
"Unknown", "Unknown", "Unknown",
|
||||
"Unknown", "Unknown", "Unknown",
|
||||
"ECMABoundary", "NonECMABoundary",
|
||||
}
|
||||
|
||||
func (n *regexNode) description() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
buf.WriteString(typeStr[n.t])
|
||||
|
||||
if (n.options & ExplicitCapture) != 0 {
|
||||
buf.WriteString("-C")
|
||||
}
|
||||
if (n.options & IgnoreCase) != 0 {
|
||||
buf.WriteString("-I")
|
||||
}
|
||||
if (n.options & RightToLeft) != 0 {
|
||||
buf.WriteString("-L")
|
||||
}
|
||||
if (n.options & Multiline) != 0 {
|
||||
buf.WriteString("-M")
|
||||
}
|
||||
if (n.options & Singleline) != 0 {
|
||||
buf.WriteString("-S")
|
||||
}
|
||||
if (n.options & IgnorePatternWhitespace) != 0 {
|
||||
buf.WriteString("-X")
|
||||
}
|
||||
if (n.options & ECMAScript) != 0 {
|
||||
buf.WriteString("-E")
|
||||
}
|
||||
|
||||
switch n.t {
|
||||
case ntOneloop, ntNotoneloop, ntOnelazy, ntNotonelazy, ntOne, ntNotone:
|
||||
buf.WriteString("(Ch = " + CharDescription(n.ch) + ")")
|
||||
break
|
||||
case ntCapture:
|
||||
buf.WriteString("(index = " + strconv.Itoa(n.m) + ", unindex = " + strconv.Itoa(n.n) + ")")
|
||||
break
|
||||
case ntRef, ntTestref:
|
||||
buf.WriteString("(index = " + strconv.Itoa(n.m) + ")")
|
||||
break
|
||||
case ntMulti:
|
||||
fmt.Fprintf(buf, "(String = %s)", string(n.str))
|
||||
break
|
||||
case ntSet, ntSetloop, ntSetlazy:
|
||||
buf.WriteString("(Set = " + n.set.String() + ")")
|
||||
break
|
||||
}
|
||||
|
||||
switch n.t {
|
||||
case ntOneloop, ntNotoneloop, ntOnelazy, ntNotonelazy, ntSetloop, ntSetlazy, ntLoop, ntLazyloop:
|
||||
buf.WriteString("(Min = ")
|
||||
buf.WriteString(strconv.Itoa(n.m))
|
||||
buf.WriteString(", Max = ")
|
||||
if n.n == math.MaxInt32 {
|
||||
buf.WriteString("inf")
|
||||
} else {
|
||||
buf.WriteString(strconv.Itoa(n.n))
|
||||
}
|
||||
buf.WriteString(")")
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
var padSpace = []byte(" ")
|
||||
|
||||
func (t *RegexTree) Dump() string {
|
||||
return t.root.dump()
|
||||
}
|
||||
|
||||
func (n *regexNode) dump() string {
|
||||
var stack []int
|
||||
CurNode := n
|
||||
CurChild := 0
|
||||
|
||||
buf := bytes.NewBufferString(CurNode.description())
|
||||
buf.WriteRune('\n')
|
||||
|
||||
for {
|
||||
if CurNode.children != nil && CurChild < len(CurNode.children) {
|
||||
stack = append(stack, CurChild+1)
|
||||
CurNode = CurNode.children[CurChild]
|
||||
CurChild = 0
|
||||
|
||||
Depth := len(stack)
|
||||
if Depth > 32 {
|
||||
Depth = 32
|
||||
}
|
||||
buf.Write(padSpace[:Depth])
|
||||
buf.WriteString(CurNode.description())
|
||||
buf.WriteRune('\n')
|
||||
} else {
|
||||
if len(stack) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
CurChild = stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
CurNode = CurNode.next
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
+500
@@ -0,0 +1,500 @@
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Write(tree *RegexTree) (*Code, error) {
|
||||
w := writer{
|
||||
intStack: make([]int, 0, 32),
|
||||
emitted: make([]int, 2),
|
||||
stringhash: make(map[string]int),
|
||||
sethash: make(map[string]int),
|
||||
}
|
||||
|
||||
code, err := w.codeFromTree(tree)
|
||||
|
||||
if tree.options&Debug > 0 && code != nil {
|
||||
os.Stdout.WriteString(code.Dump())
|
||||
os.Stdout.WriteString("\n")
|
||||
}
|
||||
|
||||
return code, err
|
||||
}
|
||||
|
||||
type writer struct {
|
||||
emitted []int
|
||||
|
||||
intStack []int
|
||||
curpos int
|
||||
stringhash map[string]int
|
||||
stringtable [][]rune
|
||||
sethash map[string]int
|
||||
settable []*CharSet
|
||||
counting bool
|
||||
count int
|
||||
trackcount int
|
||||
caps map[int]int
|
||||
}
|
||||
|
||||
const (
|
||||
beforeChild nodeType = 64
|
||||
afterChild = 128
|
||||
//MaxPrefixSize is the largest number of runes we'll use for a BoyerMoyer prefix
|
||||
MaxPrefixSize = 50
|
||||
)
|
||||
|
||||
// The top level RegexCode generator. It does a depth-first walk
|
||||
// through the tree and calls EmitFragment to emits code before
|
||||
// and after each child of an interior node, and at each leaf.
|
||||
//
|
||||
// It runs two passes, first to count the size of the generated
|
||||
// code, and second to generate the code.
|
||||
//
|
||||
// We should time it against the alternative, which is
|
||||
// to just generate the code and grow the array as we go.
|
||||
func (w *writer) codeFromTree(tree *RegexTree) (*Code, error) {
|
||||
var (
|
||||
curNode *regexNode
|
||||
curChild int
|
||||
capsize int
|
||||
)
|
||||
// construct sparse capnum mapping if some numbers are unused
|
||||
|
||||
if tree.capnumlist == nil || tree.captop == len(tree.capnumlist) {
|
||||
capsize = tree.captop
|
||||
w.caps = nil
|
||||
} else {
|
||||
capsize = len(tree.capnumlist)
|
||||
w.caps = tree.caps
|
||||
for i := 0; i < len(tree.capnumlist); i++ {
|
||||
w.caps[tree.capnumlist[i]] = i
|
||||
}
|
||||
}
|
||||
|
||||
w.counting = true
|
||||
|
||||
for {
|
||||
if !w.counting {
|
||||
w.emitted = make([]int, w.count)
|
||||
}
|
||||
|
||||
curNode = tree.root
|
||||
curChild = 0
|
||||
|
||||
w.emit1(Lazybranch, 0)
|
||||
|
||||
for {
|
||||
if len(curNode.children) == 0 {
|
||||
w.emitFragment(curNode.t, curNode, 0)
|
||||
} else if curChild < len(curNode.children) {
|
||||
w.emitFragment(curNode.t|beforeChild, curNode, curChild)
|
||||
|
||||
curNode = curNode.children[curChild]
|
||||
|
||||
w.pushInt(curChild)
|
||||
curChild = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if w.emptyStack() {
|
||||
break
|
||||
}
|
||||
|
||||
curChild = w.popInt()
|
||||
curNode = curNode.next
|
||||
|
||||
w.emitFragment(curNode.t|afterChild, curNode, curChild)
|
||||
curChild++
|
||||
}
|
||||
|
||||
w.patchJump(0, w.curPos())
|
||||
w.emit(Stop)
|
||||
|
||||
if !w.counting {
|
||||
break
|
||||
}
|
||||
|
||||
w.counting = false
|
||||
}
|
||||
|
||||
fcPrefix := getFirstCharsPrefix(tree)
|
||||
prefix := getPrefix(tree)
|
||||
rtl := (tree.options & RightToLeft) != 0
|
||||
|
||||
var bmPrefix *BmPrefix
|
||||
//TODO: benchmark string prefixes
|
||||
if prefix != nil && len(prefix.PrefixStr) > 0 && MaxPrefixSize > 0 {
|
||||
if len(prefix.PrefixStr) > MaxPrefixSize {
|
||||
// limit prefix changes to 10k
|
||||
prefix.PrefixStr = prefix.PrefixStr[:MaxPrefixSize]
|
||||
}
|
||||
bmPrefix = newBmPrefix(prefix.PrefixStr, prefix.CaseInsensitive, rtl)
|
||||
} else {
|
||||
bmPrefix = nil
|
||||
}
|
||||
|
||||
return &Code{
|
||||
Codes: w.emitted,
|
||||
Strings: w.stringtable,
|
||||
Sets: w.settable,
|
||||
TrackCount: w.trackcount,
|
||||
Caps: w.caps,
|
||||
Capsize: capsize,
|
||||
FcPrefix: fcPrefix,
|
||||
BmPrefix: bmPrefix,
|
||||
Anchors: getAnchors(tree),
|
||||
RightToLeft: rtl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// The main RegexCode generator. It does a depth-first walk
|
||||
// through the tree and calls EmitFragment to emits code before
|
||||
// and after each child of an interior node, and at each leaf.
|
||||
func (w *writer) emitFragment(nodetype nodeType, node *regexNode, curIndex int) error {
|
||||
bits := InstOp(0)
|
||||
|
||||
if nodetype <= ntRef {
|
||||
if (node.options & RightToLeft) != 0 {
|
||||
bits |= Rtl
|
||||
}
|
||||
if (node.options & IgnoreCase) != 0 {
|
||||
bits |= Ci
|
||||
}
|
||||
}
|
||||
ntBits := nodeType(bits)
|
||||
|
||||
switch nodetype {
|
||||
case ntConcatenate | beforeChild, ntConcatenate | afterChild, ntEmpty:
|
||||
break
|
||||
|
||||
case ntAlternate | beforeChild:
|
||||
if curIndex < len(node.children)-1 {
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Lazybranch, 0)
|
||||
}
|
||||
|
||||
case ntAlternate | afterChild:
|
||||
if curIndex < len(node.children)-1 {
|
||||
lbPos := w.popInt()
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Goto, 0)
|
||||
w.patchJump(lbPos, w.curPos())
|
||||
} else {
|
||||
for i := 0; i < curIndex; i++ {
|
||||
w.patchJump(w.popInt(), w.curPos())
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case ntTestref | beforeChild:
|
||||
if curIndex == 0 {
|
||||
w.emit(Setjump)
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Lazybranch, 0)
|
||||
w.emit1(Testref, w.mapCapnum(node.m))
|
||||
w.emit(Forejump)
|
||||
}
|
||||
|
||||
case ntTestref | afterChild:
|
||||
if curIndex == 0 {
|
||||
branchpos := w.popInt()
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Goto, 0)
|
||||
w.patchJump(branchpos, w.curPos())
|
||||
w.emit(Forejump)
|
||||
if len(node.children) <= 1 {
|
||||
w.patchJump(w.popInt(), w.curPos())
|
||||
}
|
||||
} else if curIndex == 1 {
|
||||
w.patchJump(w.popInt(), w.curPos())
|
||||
}
|
||||
|
||||
case ntTestgroup | beforeChild:
|
||||
if curIndex == 0 {
|
||||
w.emit(Setjump)
|
||||
w.emit(Setmark)
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Lazybranch, 0)
|
||||
}
|
||||
|
||||
case ntTestgroup | afterChild:
|
||||
if curIndex == 0 {
|
||||
w.emit(Getmark)
|
||||
w.emit(Forejump)
|
||||
} else if curIndex == 1 {
|
||||
Branchpos := w.popInt()
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Goto, 0)
|
||||
w.patchJump(Branchpos, w.curPos())
|
||||
w.emit(Getmark)
|
||||
w.emit(Forejump)
|
||||
if len(node.children) <= 2 {
|
||||
w.patchJump(w.popInt(), w.curPos())
|
||||
}
|
||||
} else if curIndex == 2 {
|
||||
w.patchJump(w.popInt(), w.curPos())
|
||||
}
|
||||
|
||||
case ntLoop | beforeChild, ntLazyloop | beforeChild:
|
||||
|
||||
if node.n < math.MaxInt32 || node.m > 1 {
|
||||
if node.m == 0 {
|
||||
w.emit1(Nullcount, 0)
|
||||
} else {
|
||||
w.emit1(Setcount, 1-node.m)
|
||||
}
|
||||
} else if node.m == 0 {
|
||||
w.emit(Nullmark)
|
||||
} else {
|
||||
w.emit(Setmark)
|
||||
}
|
||||
|
||||
if node.m == 0 {
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Goto, 0)
|
||||
}
|
||||
w.pushInt(w.curPos())
|
||||
|
||||
case ntLoop | afterChild, ntLazyloop | afterChild:
|
||||
|
||||
startJumpPos := w.curPos()
|
||||
lazy := (nodetype - (ntLoop | afterChild))
|
||||
|
||||
if node.n < math.MaxInt32 || node.m > 1 {
|
||||
if node.n == math.MaxInt32 {
|
||||
w.emit2(InstOp(Branchcount+lazy), w.popInt(), math.MaxInt32)
|
||||
} else {
|
||||
w.emit2(InstOp(Branchcount+lazy), w.popInt(), node.n-node.m)
|
||||
}
|
||||
} else {
|
||||
w.emit1(InstOp(Branchmark+lazy), w.popInt())
|
||||
}
|
||||
|
||||
if node.m == 0 {
|
||||
w.patchJump(w.popInt(), startJumpPos)
|
||||
}
|
||||
|
||||
case ntGroup | beforeChild, ntGroup | afterChild:
|
||||
|
||||
case ntCapture | beforeChild:
|
||||
w.emit(Setmark)
|
||||
|
||||
case ntCapture | afterChild:
|
||||
w.emit2(Capturemark, w.mapCapnum(node.m), w.mapCapnum(node.n))
|
||||
|
||||
case ntRequire | beforeChild:
|
||||
// NOTE: the following line causes lookahead/lookbehind to be
|
||||
// NON-BACKTRACKING. It can be commented out with (*)
|
||||
w.emit(Setjump)
|
||||
|
||||
w.emit(Setmark)
|
||||
|
||||
case ntRequire | afterChild:
|
||||
w.emit(Getmark)
|
||||
|
||||
// NOTE: the following line causes lookahead/lookbehind to be
|
||||
// NON-BACKTRACKING. It can be commented out with (*)
|
||||
w.emit(Forejump)
|
||||
|
||||
case ntPrevent | beforeChild:
|
||||
w.emit(Setjump)
|
||||
w.pushInt(w.curPos())
|
||||
w.emit1(Lazybranch, 0)
|
||||
|
||||
case ntPrevent | afterChild:
|
||||
w.emit(Backjump)
|
||||
w.patchJump(w.popInt(), w.curPos())
|
||||
w.emit(Forejump)
|
||||
|
||||
case ntGreedy | beforeChild:
|
||||
w.emit(Setjump)
|
||||
|
||||
case ntGreedy | afterChild:
|
||||
w.emit(Forejump)
|
||||
|
||||
case ntOne, ntNotone:
|
||||
w.emit1(InstOp(node.t|ntBits), int(node.ch))
|
||||
|
||||
case ntNotoneloop, ntNotonelazy, ntOneloop, ntOnelazy:
|
||||
if node.m > 0 {
|
||||
if node.t == ntOneloop || node.t == ntOnelazy {
|
||||
w.emit2(Onerep|bits, int(node.ch), node.m)
|
||||
} else {
|
||||
w.emit2(Notonerep|bits, int(node.ch), node.m)
|
||||
}
|
||||
}
|
||||
if node.n > node.m {
|
||||
if node.n == math.MaxInt32 {
|
||||
w.emit2(InstOp(node.t|ntBits), int(node.ch), math.MaxInt32)
|
||||
} else {
|
||||
w.emit2(InstOp(node.t|ntBits), int(node.ch), node.n-node.m)
|
||||
}
|
||||
}
|
||||
|
||||
case ntSetloop, ntSetlazy:
|
||||
if node.m > 0 {
|
||||
w.emit2(Setrep|bits, w.setCode(node.set), node.m)
|
||||
}
|
||||
if node.n > node.m {
|
||||
if node.n == math.MaxInt32 {
|
||||
w.emit2(InstOp(node.t|ntBits), w.setCode(node.set), math.MaxInt32)
|
||||
} else {
|
||||
w.emit2(InstOp(node.t|ntBits), w.setCode(node.set), node.n-node.m)
|
||||
}
|
||||
}
|
||||
|
||||
case ntMulti:
|
||||
w.emit1(InstOp(node.t|ntBits), w.stringCode(node.str))
|
||||
|
||||
case ntSet:
|
||||
w.emit1(InstOp(node.t|ntBits), w.setCode(node.set))
|
||||
|
||||
case ntRef:
|
||||
w.emit1(InstOp(node.t|ntBits), w.mapCapnum(node.m))
|
||||
|
||||
case ntNothing, ntBol, ntEol, ntBoundary, ntNonboundary, ntECMABoundary, ntNonECMABoundary, ntBeginning, ntStart, ntEndZ, ntEnd:
|
||||
w.emit(InstOp(node.t))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected opcode in regular expression generation: %v", nodetype)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// To avoid recursion, we use a simple integer stack.
|
||||
// This is the push.
|
||||
func (w *writer) pushInt(i int) {
|
||||
w.intStack = append(w.intStack, i)
|
||||
}
|
||||
|
||||
// Returns true if the stack is empty.
|
||||
func (w *writer) emptyStack() bool {
|
||||
return len(w.intStack) == 0
|
||||
}
|
||||
|
||||
// This is the pop.
|
||||
func (w *writer) popInt() int {
|
||||
//get our item
|
||||
idx := len(w.intStack) - 1
|
||||
i := w.intStack[idx]
|
||||
//trim our slice
|
||||
w.intStack = w.intStack[:idx]
|
||||
return i
|
||||
}
|
||||
|
||||
// Returns the current position in the emitted code.
|
||||
func (w *writer) curPos() int {
|
||||
return w.curpos
|
||||
}
|
||||
|
||||
// Fixes up a jump instruction at the specified offset
|
||||
// so that it jumps to the specified jumpDest.
|
||||
func (w *writer) patchJump(offset, jumpDest int) {
|
||||
w.emitted[offset+1] = jumpDest
|
||||
}
|
||||
|
||||
// Returns an index in the set table for a charset
|
||||
// uses a map to eliminate duplicates.
|
||||
func (w *writer) setCode(set *CharSet) int {
|
||||
if w.counting {
|
||||
return 0
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
set.mapHashFill(buf)
|
||||
hash := buf.String()
|
||||
i, ok := w.sethash[hash]
|
||||
if !ok {
|
||||
i = len(w.sethash)
|
||||
w.sethash[hash] = i
|
||||
w.settable = append(w.settable, set)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Returns an index in the string table for a string.
|
||||
// uses a map to eliminate duplicates.
|
||||
func (w *writer) stringCode(str []rune) int {
|
||||
if w.counting {
|
||||
return 0
|
||||
}
|
||||
|
||||
hash := string(str)
|
||||
i, ok := w.stringhash[hash]
|
||||
if !ok {
|
||||
i = len(w.stringhash)
|
||||
w.stringhash[hash] = i
|
||||
w.stringtable = append(w.stringtable, str)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// When generating code on a regex that uses a sparse set
|
||||
// of capture slots, we hash them to a dense set of indices
|
||||
// for an array of capture slots. Instead of doing the hash
|
||||
// at match time, it's done at compile time, here.
|
||||
func (w *writer) mapCapnum(capnum int) int {
|
||||
if capnum == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if w.caps != nil {
|
||||
return w.caps[capnum]
|
||||
}
|
||||
|
||||
return capnum
|
||||
}
|
||||
|
||||
// Emits a zero-argument operation. Note that the emit
|
||||
// functions all run in two modes: they can emit code, or
|
||||
// they can just count the size of the code.
|
||||
func (w *writer) emit(op InstOp) {
|
||||
if w.counting {
|
||||
w.count++
|
||||
if opcodeBacktracks(op) {
|
||||
w.trackcount++
|
||||
}
|
||||
return
|
||||
}
|
||||
w.emitted[w.curpos] = int(op)
|
||||
w.curpos++
|
||||
}
|
||||
|
||||
// Emits a one-argument operation.
|
||||
func (w *writer) emit1(op InstOp, opd1 int) {
|
||||
if w.counting {
|
||||
w.count += 2
|
||||
if opcodeBacktracks(op) {
|
||||
w.trackcount++
|
||||
}
|
||||
return
|
||||
}
|
||||
w.emitted[w.curpos] = int(op)
|
||||
w.curpos++
|
||||
w.emitted[w.curpos] = opd1
|
||||
w.curpos++
|
||||
}
|
||||
|
||||
// Emits a two-argument operation.
|
||||
func (w *writer) emit2(op InstOp, opd1, opd2 int) {
|
||||
if w.counting {
|
||||
w.count += 3
|
||||
if opcodeBacktracks(op) {
|
||||
w.trackcount++
|
||||
}
|
||||
return
|
||||
}
|
||||
w.emitted[w.curpos] = int(op)
|
||||
w.curpos++
|
||||
w.emitted[w.curpos] = opd1
|
||||
w.curpos++
|
||||
w.emitted[w.curpos] = opd2
|
||||
w.curpos++
|
||||
}
|
||||
+7061
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
*.iml
|
||||
testdata/test262
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh -e
|
||||
# this is just the commit it was last tested with
|
||||
sha=3af36bec45bd4f72d4b57366653578e1e4dafef7
|
||||
|
||||
mkdir -p testdata/test262
|
||||
cd testdata/test262
|
||||
git init
|
||||
git remote add origin https://github.com/tc39/test262.git
|
||||
git fetch origin --depth=1 "${sha}"
|
||||
git reset --hard "${sha}"
|
||||
cd -
|
||||
+15
@@ -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.
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
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()/setInterval()?
|
||||
|
||||
setTimeout() and setInterval() are common functions to provide concurrent execution in ECMAScript environments, but the two functions are not part of the ECMAScript standard.
|
||||
Browsers and NodeJS just happen to provide similar, but not identical, functions. The hosting application need to control the environment for concurrent execution, e.g. an event loop, and supply the functionality to script code.
|
||||
|
||||
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
@@ -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
@@ -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)
|
||||
}
|
||||
+1068
File diff suppressed because it is too large
Load Diff
+876
@@ -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() }
|
||||
+1833
File diff suppressed because it is too large
Load Diff
+369
@@ -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
|
||||
}
|
||||
+75
@@ -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
File diff suppressed because it is too large
Load Diff
+314
@@ -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
|
||||
}
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
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
|
||||
|
||||
argArray := call.Argument(1)
|
||||
if !IsUndefined(argArray) && !IsNull(argArray) {
|
||||
args = r.createListFromArrayLike(argArray)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+576
@@ -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
|
||||
}
|
||||
+542
@@ -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
|
||||
}
|
||||
+342
@@ -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
|
||||
}
|
||||
+358
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+650
@@ -0,0 +1,650 @@
|
||||
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{}) error {
|
||||
f, _ := AssertFunction(fObj)
|
||||
return func(x interface{}) error {
|
||||
_, err := f(nil, r.ToValue(x))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// NewPromise creates and returns a Promise and resolving functions for it.
|
||||
// The returned errors will be uncatchable errors, such as InterruptedError or StackOverflowError, which should be propagated upwards.
|
||||
// Exceptions are handled through [PromiseRejectionTracker].
|
||||
//
|
||||
// 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
|
||||
// err := resolve(result)
|
||||
// // Handle uncatchable errors (e.g. by stopping the loop, panicking or setting a flag)
|
||||
// })
|
||||
// }()
|
||||
// }
|
||||
func (r *Runtime) NewPromise() (promise *Promise, resolve, reject func(reason interface{}) error) {
|
||||
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
@@ -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
|
||||
}
|
||||
+140
@@ -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
|
||||
}
|
||||
+1356
File diff suppressed because it is too large
Load Diff
+346
@@ -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
|
||||
}
|
||||
+1108
File diff suppressed because it is too large
Load Diff
+177
@@ -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
File diff suppressed because it is too large
Load Diff
+176
@@ -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
|
||||
}
|
||||
+135
@@ -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
|
||||
}
|
||||
+1487
File diff suppressed because it is too large
Load Diff
+3599
File diff suppressed because it is too large
Load Diff
+1127
File diff suppressed because it is too large
Load Diff
+124
@@ -0,0 +1,124 @@
|
||||
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
|
||||
}
|
||||
|
||||
func dateParse(date string) (t time.Time, ok bool) {
|
||||
d, ok := parseDateISOString(date)
|
||||
if !ok {
|
||||
d, ok = parseDateOtherString(date)
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if d.month > 12 ||
|
||||
d.day > 31 ||
|
||||
d.hour > 24 ||
|
||||
d.min > 59 ||
|
||||
d.sec > 59 ||
|
||||
// special case 24:00:00.000
|
||||
(d.hour == 24 && (d.min != 0 || d.sec != 0 || d.msec != 0)) {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
var loc *time.Location
|
||||
if d.isLocal {
|
||||
loc = time.Local
|
||||
} else {
|
||||
loc = time.FixedZone("", d.timeZoneOffset*60)
|
||||
}
|
||||
t = time.Date(d.year, time.Month(d.month), d.day, d.hour, d.min, d.sec, d.msec*1e6, loc)
|
||||
unixMilli := t.UnixMilli()
|
||||
ok = unixMilli >= -maxTime && unixMilli <= maxTime
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type date struct {
|
||||
year, month, day int
|
||||
hour, min, sec, msec int
|
||||
timeZoneOffset int // time zone offset in minutes
|
||||
isLocal bool
|
||||
}
|
||||
|
||||
func skip(s string, c byte) (string, bool) {
|
||||
if len(s) > 0 && s[0] == c {
|
||||
return s[1:], true
|
||||
}
|
||||
return s, false
|
||||
}
|
||||
|
||||
func skipSpaces(s string) string {
|
||||
for len(s) > 0 && s[0] == ' ' {
|
||||
s = s[1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func skipUntil(s string, stopList string) string {
|
||||
for len(s) > 0 && !strings.ContainsRune(stopList, rune(s[0])) {
|
||||
s = s[1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func match(s string, lower string) (string, bool) {
|
||||
if len(s) < len(lower) {
|
||||
return s, false
|
||||
}
|
||||
for i := 0; i < len(lower); i++ {
|
||||
c1 := s[i]
|
||||
c2 := lower[i]
|
||||
if c1 != c2 {
|
||||
// switch to lower-case; 'a'-'A' is known to be a single bit
|
||||
c1 |= 'a' - 'A'
|
||||
if c1 != c2 || c1 < 'a' || c1 > 'z' {
|
||||
return s, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return s[len(lower):], true
|
||||
}
|
||||
|
||||
func getDigits(s string, minDigits, maxDigits int) (int, string, bool) {
|
||||
var i, v int
|
||||
for i < len(s) && i < maxDigits && s[i] >= '0' && s[i] <= '9' {
|
||||
v = v*10 + int(s[i]-'0')
|
||||
i++
|
||||
}
|
||||
if i < minDigits {
|
||||
return 0, s, false
|
||||
}
|
||||
return v, s[i:], true
|
||||
}
|
||||
|
||||
func getMilliseconds(s string) (int, string) {
|
||||
mul, v := 100, 0
|
||||
if len(s) > 0 && (s[0] == '.' || s[0] == ',') {
|
||||
const I_START = 1
|
||||
i := I_START
|
||||
for i < len(s) && i-I_START < 9 && s[i] >= '0' && s[i] <= '9' {
|
||||
v += int(s[i]-'0') * mul
|
||||
mul /= 10
|
||||
i++
|
||||
}
|
||||
if i > I_START {
|
||||
// only consume the separator if digits are present
|
||||
return v, s[i:]
|
||||
}
|
||||
}
|
||||
return 0, s
|
||||
}
|
||||
|
||||
// [+-]HH:mm or [+-]HHmm or Z
|
||||
func getTimeZoneOffset(s string, strict bool) (int, string, bool) {
|
||||
if len(s) == 0 {
|
||||
return 0, s, false
|
||||
}
|
||||
sign := s[0]
|
||||
if sign == '+' || sign == '-' {
|
||||
var hh, mm, v int
|
||||
var ok bool
|
||||
t := s[1:]
|
||||
n := len(t)
|
||||
if hh, t, ok = getDigits(t, 1, 9); !ok {
|
||||
return 0, s, false
|
||||
}
|
||||
n -= len(t)
|
||||
if strict && n != 2 && n != 4 {
|
||||
return 0, s, false
|
||||
}
|
||||
for n > 4 {
|
||||
n -= 2
|
||||
hh /= 100
|
||||
}
|
||||
if n > 2 {
|
||||
mm = hh % 100
|
||||
hh = hh / 100
|
||||
} else if t, ok = skip(t, ':'); ok {
|
||||
if mm, t, ok = getDigits(t, 2, 2); !ok {
|
||||
return 0, s, false
|
||||
}
|
||||
}
|
||||
if hh > 23 || mm > 59 {
|
||||
return 0, s, false
|
||||
}
|
||||
v = hh*60 + mm
|
||||
if sign == '-' {
|
||||
v = -v
|
||||
}
|
||||
return v, t, true
|
||||
} else if sign == 'Z' {
|
||||
return 0, s[1:], true
|
||||
}
|
||||
return 0, s, false
|
||||
}
|
||||
|
||||
var tzAbbrs = []struct {
|
||||
nameLower string
|
||||
offset int
|
||||
}{
|
||||
{"gmt", 0}, // Greenwich Mean Time
|
||||
{"utc", 0}, // Coordinated Universal Time
|
||||
{"ut", 0}, // Universal Time
|
||||
{"z", 0}, // Zulu Time
|
||||
{"edt", -4 * 60}, // Eastern Daylight Time
|
||||
{"est", -5 * 60}, // Eastern Standard Time
|
||||
{"cdt", -5 * 60}, // Central Daylight Time
|
||||
{"cst", -6 * 60}, // Central Standard Time
|
||||
{"mdt", -6 * 60}, // Mountain Daylight Time
|
||||
{"mst", -7 * 60}, // Mountain Standard Time
|
||||
{"pdt", -7 * 60}, // Pacific Daylight Time
|
||||
{"pst", -8 * 60}, // Pacific Standard Time
|
||||
{"wet", +0 * 60}, // Western European Time
|
||||
{"west", +1 * 60}, // Western European Summer Time
|
||||
{"cet", +1 * 60}, // Central European Time
|
||||
{"cest", +2 * 60}, // Central European Summer Time
|
||||
{"eet", +2 * 60}, // Eastern European Time
|
||||
{"eest", +3 * 60}, // Eastern European Summer Time
|
||||
}
|
||||
|
||||
func getTimeZoneAbbr(s string) (int, string, bool) {
|
||||
for _, tzAbbr := range tzAbbrs {
|
||||
if s, ok := match(s, tzAbbr.nameLower); ok {
|
||||
return tzAbbr.offset, s, true
|
||||
}
|
||||
}
|
||||
return 0, s, false
|
||||
}
|
||||
|
||||
var monthNamesLower = []string{
|
||||
"jan",
|
||||
"feb",
|
||||
"mar",
|
||||
"apr",
|
||||
"may",
|
||||
"jun",
|
||||
"jul",
|
||||
"aug",
|
||||
"sep",
|
||||
"oct",
|
||||
"nov",
|
||||
"dec",
|
||||
}
|
||||
|
||||
func getMonth(s string) (int, string, bool) {
|
||||
for i, monthNameLower := range monthNamesLower {
|
||||
if s, ok := match(s, monthNameLower); ok {
|
||||
return i + 1, s, true
|
||||
}
|
||||
}
|
||||
return 0, s, false
|
||||
}
|
||||
|
||||
func parseDateISOString(s string) (date, bool) {
|
||||
if len(s) == 0 {
|
||||
return date{}, false
|
||||
}
|
||||
var d = date{month: 1, day: 1}
|
||||
var ok bool
|
||||
|
||||
// year is either yyyy digits or [+-]yyyyyy
|
||||
sign := s[0]
|
||||
if sign == '-' || sign == '+' {
|
||||
s = s[1:]
|
||||
if d.year, s, ok = getDigits(s, 6, 6); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
if sign == '-' {
|
||||
if d.year == 0 {
|
||||
// reject -000000
|
||||
return date{}, false
|
||||
}
|
||||
d.year = -d.year
|
||||
}
|
||||
} else if d.year, s, ok = getDigits(s, 4, 4); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
if s, ok = skip(s, '-'); ok {
|
||||
if d.month, s, ok = getDigits(s, 2, 2); !ok || d.month < 1 {
|
||||
return date{}, false
|
||||
}
|
||||
if s, ok = skip(s, '-'); ok {
|
||||
if d.day, s, ok = getDigits(s, 2, 2); !ok || d.day < 1 {
|
||||
return date{}, false
|
||||
}
|
||||
}
|
||||
}
|
||||
if s, ok = skip(s, 'T'); ok {
|
||||
if d.hour, s, ok = getDigits(s, 2, 2); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
if s, ok = skip(s, ':'); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
if d.min, s, ok = getDigits(s, 2, 2); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
if s, ok = skip(s, ':'); ok {
|
||||
if d.sec, s, ok = getDigits(s, 2, 2); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
d.msec, s = getMilliseconds(s)
|
||||
}
|
||||
d.isLocal = true
|
||||
}
|
||||
// parse the time zone offset if present
|
||||
if len(s) > 0 {
|
||||
if d.timeZoneOffset, s, ok = getTimeZoneOffset(s, true); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
d.isLocal = false
|
||||
}
|
||||
// error if extraneous characters
|
||||
return d, len(s) == 0
|
||||
}
|
||||
|
||||
func parseDateOtherString(s string) (date, bool) {
|
||||
var d = date{
|
||||
year: 2001,
|
||||
month: 1,
|
||||
day: 1,
|
||||
isLocal: true,
|
||||
}
|
||||
var nums [3]int
|
||||
var numIndex int
|
||||
var hasYear, hasMon, hasTime, ok bool
|
||||
for {
|
||||
s = skipSpaces(s)
|
||||
if len(s) == 0 {
|
||||
break
|
||||
}
|
||||
c := s[0]
|
||||
n, val := len(s), 0
|
||||
if c == '+' || c == '-' {
|
||||
if hasTime {
|
||||
if val, s, ok = getTimeZoneOffset(s, false); ok {
|
||||
d.timeZoneOffset = val
|
||||
d.isLocal = false
|
||||
}
|
||||
}
|
||||
if !hasTime || !ok {
|
||||
s = s[1:]
|
||||
if val, s, ok = getDigits(s, 1, 9); ok {
|
||||
d.year = val
|
||||
if c == '-' {
|
||||
if d.year == 0 {
|
||||
return date{}, false
|
||||
}
|
||||
d.year = -d.year
|
||||
}
|
||||
hasYear = true
|
||||
}
|
||||
}
|
||||
} else if val, s, ok = getDigits(s, 1, 9); ok {
|
||||
if s, ok = skip(s, ':'); ok {
|
||||
// time part
|
||||
d.hour = val
|
||||
if d.min, s, ok = getDigits(s, 1, 2); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
if s, ok = skip(s, ':'); ok {
|
||||
if d.sec, s, ok = getDigits(s, 1, 2); !ok {
|
||||
return date{}, false
|
||||
}
|
||||
d.msec, s = getMilliseconds(s)
|
||||
}
|
||||
hasTime = true
|
||||
if t := skipSpaces(s); len(t) > 0 {
|
||||
if t, ok = match(t, "pm"); ok {
|
||||
if d.hour < 12 {
|
||||
d.hour += 12
|
||||
}
|
||||
s = t
|
||||
continue
|
||||
} else if t, ok = match(t, "am"); ok {
|
||||
if d.hour == 12 {
|
||||
d.hour = 0
|
||||
}
|
||||
s = t
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else if n-len(s) > 2 {
|
||||
d.year = val
|
||||
hasYear = true
|
||||
} else if val < 1 || val > 31 {
|
||||
d.year = val
|
||||
if val < 100 {
|
||||
d.year += 1900
|
||||
}
|
||||
if val < 50 {
|
||||
d.year += 100
|
||||
}
|
||||
hasYear = true
|
||||
} else {
|
||||
if numIndex == 3 {
|
||||
return date{}, false
|
||||
}
|
||||
nums[numIndex] = val
|
||||
numIndex++
|
||||
}
|
||||
} else if val, s, ok = getMonth(s); ok {
|
||||
d.month = val
|
||||
hasMon = true
|
||||
s = skipUntil(s, "0123456789 -/(")
|
||||
} else if val, s, ok = getTimeZoneAbbr(s); ok {
|
||||
d.timeZoneOffset = val
|
||||
if len(s) > 0 {
|
||||
if c := s[0]; (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
|
||||
return date{}, false
|
||||
}
|
||||
}
|
||||
d.isLocal = false
|
||||
continue
|
||||
} else if c == '(' {
|
||||
// skip parenthesized phrase
|
||||
level := 1
|
||||
s = s[1:]
|
||||
for len(s) > 0 && level != 0 {
|
||||
if s[0] == '(' {
|
||||
level++
|
||||
} else if s[0] == ')' {
|
||||
level--
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
if level > 0 {
|
||||
return date{}, false
|
||||
}
|
||||
} else if c == ')' {
|
||||
return date{}, false
|
||||
} else {
|
||||
if hasYear || hasMon || hasTime || numIndex > 0 {
|
||||
return date{}, false
|
||||
}
|
||||
// skip a word
|
||||
s = skipUntil(s, " -/(")
|
||||
}
|
||||
for len(s) > 0 && strings.ContainsRune("-/.,", rune(s[0])) {
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
n := numIndex
|
||||
if hasYear {
|
||||
n++
|
||||
}
|
||||
if hasMon {
|
||||
n++
|
||||
}
|
||||
if n > 3 {
|
||||
return date{}, false
|
||||
}
|
||||
|
||||
switch numIndex {
|
||||
case 0:
|
||||
if !hasYear {
|
||||
return date{}, false
|
||||
}
|
||||
case 1:
|
||||
if hasMon {
|
||||
d.day = nums[0]
|
||||
} else {
|
||||
d.month = nums[0]
|
||||
}
|
||||
case 2:
|
||||
if hasYear {
|
||||
d.month = nums[0]
|
||||
d.day = nums[1]
|
||||
} else if hasMon {
|
||||
d.year = nums[1]
|
||||
if nums[1] < 100 {
|
||||
d.year += 1900
|
||||
}
|
||||
if nums[1] < 50 {
|
||||
d.year += 100
|
||||
}
|
||||
d.day = nums[0]
|
||||
} else {
|
||||
d.month = nums[0]
|
||||
d.day = nums[1]
|
||||
}
|
||||
case 3:
|
||||
d.year = nums[2]
|
||||
if nums[2] < 100 {
|
||||
d.year += 1900
|
||||
}
|
||||
if nums[2] < 50 {
|
||||
d.year += 100
|
||||
}
|
||||
d.month = nums[0]
|
||||
d.day = nums[1]
|
||||
default:
|
||||
return date{}, false
|
||||
}
|
||||
return d, d.month > 0 && d.day > 0
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
type destructKeyedSource struct {
|
||||
r *Runtime
|
||||
wrapped Value
|
||||
usedKeys propNameSet
|
||||
}
|
||||
|
||||
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) {
|
||||
d.usedKeys.add(key)
|
||||
}
|
||||
|
||||
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 !i.d.usedKeys.has(item.name) {
|
||||
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 d.usedKeys.has(key) {
|
||||
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
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
sed -En 's/^.*FAIL: TestTC39\/tc39\/(test\/.*.js).*$/"\1": true,/p'
|
||||
+110
@@ -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
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
// Package file encapsulates the file abstractions used by the ast & parser.
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"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(filepath.ToSlash(strings.TrimSpace(source)))
|
||||
if err == nil && !smURL.IsAbs() {
|
||||
basename = filepath.ToSlash(strings.TrimSpace(basename))
|
||||
baseURL, err1 := url.Parse(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
|
||||
}
|
||||
+21
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
+147
@@ -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
|
||||
}
|
||||
+26
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+1113
File diff suppressed because it is too large
Load Diff
+98
@@ -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
@@ -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
|
||||
}
|
||||
+1864
File diff suppressed because it is too large
Load Diff
+139
@@ -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
@@ -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)
|
||||
}
|
||||
+358
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
+677
@@ -0,0 +1,677 @@
|
||||
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()) {
|
||||
if ev.CanAddr() {
|
||||
ev = ev.Addr()
|
||||
}
|
||||
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{}
|
||||
}
|
||||
+343
@@ -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
@@ -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)
|
||||
}
|
||||
+469
@@ -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
|
||||
}
|
||||
+184
@@ -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
@@ -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
|
||||
}
|
||||
+1666
File diff suppressed because it is too large
Load Diff
+1210
File diff suppressed because it is too large
Load Diff
+268
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
|
||||
filename := "" // A filename is optional
|
||||
src := `
|
||||
// Sample xyzzy example
|
||||
(function(){
|
||||
if (3.14159 > 0) {
|
||||
console.log("Hello, World.");
|
||||
return;
|
||||
}
|
||||
|
||||
var xyzzy = NaN;
|
||||
console.log("Nothing happens.");
|
||||
return xyzzy;
|
||||
})();
|
||||
`
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, filename, src, 0)
|
||||
|
||||
# Warning
|
||||
|
||||
The parser and AST interfaces are still works-in-progress (particularly where
|
||||
node types are concerned) and may change in the future.
|
||||
*/
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
// A Mode value is a set of flags (or 0). They control optional parser functionality.
|
||||
type Mode uint
|
||||
|
||||
const (
|
||||
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
|
||||
)
|
||||
|
||||
type options struct {
|
||||
disableSourceMaps bool
|
||||
sourceMapLoader func(path string) ([]byte, error)
|
||||
}
|
||||
|
||||
// Option represents one of the options for the parser to use in the Parse methods. Currently supported are:
|
||||
// WithDisableSourceMaps and WithSourceMapLoader.
|
||||
type Option func(*options)
|
||||
|
||||
// WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps
|
||||
// are not in use.
|
||||
func WithDisableSourceMaps(opts *options) {
|
||||
opts.disableSourceMaps = true
|
||||
}
|
||||
|
||||
// WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a
|
||||
// URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name
|
||||
// of the file being parsed. Any error returned by the loader will fail the parsing.
|
||||
// Note that setting this to nil does not disable source map support, there is a default loader which reads
|
||||
// from the filesystem. Use WithDisableSourceMaps to disable source map support.
|
||||
func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option {
|
||||
return func(opts *options) {
|
||||
opts.sourceMapLoader = loader
|
||||
}
|
||||
}
|
||||
|
||||
type _parser struct {
|
||||
str string
|
||||
length int
|
||||
base int
|
||||
|
||||
chr rune // The current character
|
||||
chrOffset int // The offset of current character
|
||||
offset int // The offset after current character (may be greater than 1)
|
||||
|
||||
idx file.Idx // The index of token
|
||||
token token.Token // The token
|
||||
literal string // The literal of the token, if any
|
||||
parsedLiteral unistring.String
|
||||
|
||||
scope *_scope
|
||||
insertSemicolon bool // If we see a newline, then insert an implicit semicolon
|
||||
implicitSemicolon bool // An implicit semicolon exists
|
||||
|
||||
errors ErrorList
|
||||
|
||||
recover struct {
|
||||
// Scratch when trying to seek to the next statement, etc.
|
||||
idx file.Idx
|
||||
count int
|
||||
}
|
||||
|
||||
mode Mode
|
||||
opts options
|
||||
|
||||
file *file.File
|
||||
}
|
||||
|
||||
func _newParser(filename, src string, base int, opts ...Option) *_parser {
|
||||
p := &_parser{
|
||||
chr: ' ', // This is set so we can start scanning by skipping whitespace
|
||||
str: src,
|
||||
length: len(src),
|
||||
base: base,
|
||||
file: file.NewFile(filename, src, base),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&p.opts)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func newParser(filename, src string) *_parser {
|
||||
return _newParser(filename, src, 1)
|
||||
}
|
||||
|
||||
func ReadSource(filename string, src interface{}) ([]byte, error) {
|
||||
if src != nil {
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return []byte(src), nil
|
||||
case []byte:
|
||||
return src, nil
|
||||
case *bytes.Buffer:
|
||||
if src != nil {
|
||||
return src.Bytes(), nil
|
||||
}
|
||||
case io.Reader:
|
||||
var bfr bytes.Buffer
|
||||
if _, err := io.Copy(&bfr, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bfr.Bytes(), nil
|
||||
}
|
||||
return nil, errors.New("invalid source")
|
||||
}
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
// ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
|
||||
// the corresponding ast.Program node.
|
||||
//
|
||||
// If fileSet == nil, ParseFile parses source without a FileSet.
|
||||
// If fileSet != nil, ParseFile first adds filename and src to fileSet.
|
||||
//
|
||||
// The filename argument is optional and is used for labelling errors, etc.
|
||||
//
|
||||
// src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
|
||||
//
|
||||
// // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
// program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
|
||||
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode, options ...Option) (*ast.Program, error) {
|
||||
str, err := ReadSource(filename, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
{
|
||||
str := string(str)
|
||||
|
||||
base := 1
|
||||
if fileSet != nil {
|
||||
base = fileSet.AddFile(filename, str)
|
||||
}
|
||||
|
||||
parser := _newParser(filename, str, base, options...)
|
||||
parser.mode = mode
|
||||
return parser.parse()
|
||||
}
|
||||
}
|
||||
|
||||
// ParseFunction parses a given parameter list and body as a function and returns the
|
||||
// corresponding ast.FunctionLiteral node.
|
||||
//
|
||||
// The parameter list, if any, should be a comma-separated list of identifiers.
|
||||
func ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) {
|
||||
|
||||
src := "(function(" + parameterList + ") {\n" + body + "\n})"
|
||||
|
||||
parser := _newParser("", src, 1, options...)
|
||||
program, err := parser.parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
|
||||
}
|
||||
|
||||
func (self *_parser) slice(idx0, idx1 file.Idx) string {
|
||||
from := int(idx0) - self.base
|
||||
to := int(idx1) - self.base
|
||||
if from >= 0 && to <= len(self.str) {
|
||||
return self.str[from:to]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *_parser) parse() (*ast.Program, error) {
|
||||
self.openScope()
|
||||
defer self.closeScope()
|
||||
self.next()
|
||||
program := self.parseProgram()
|
||||
if false {
|
||||
self.errors.Sort()
|
||||
}
|
||||
return program, self.errors.Err()
|
||||
}
|
||||
|
||||
func (self *_parser) next() {
|
||||
self.token, self.literal, self.parsedLiteral, self.idx = self.scan()
|
||||
}
|
||||
|
||||
func (self *_parser) optionalSemicolon() {
|
||||
if self.token == token.SEMICOLON {
|
||||
self.next()
|
||||
return
|
||||
}
|
||||
|
||||
if self.implicitSemicolon {
|
||||
self.implicitSemicolon = false
|
||||
return
|
||||
}
|
||||
|
||||
if self.token != token.EOF && self.token != token.RIGHT_BRACE {
|
||||
self.expect(token.SEMICOLON)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) semicolon() {
|
||||
if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
|
||||
if self.implicitSemicolon {
|
||||
self.implicitSemicolon = false
|
||||
return
|
||||
}
|
||||
|
||||
self.expect(token.SEMICOLON)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) idxOf(offset int) file.Idx {
|
||||
return file.Idx(self.base + offset)
|
||||
}
|
||||
|
||||
func (self *_parser) expect(value token.Token) file.Idx {
|
||||
idx := self.idx
|
||||
if self.token != value {
|
||||
self.errorUnexpectedToken(self.token)
|
||||
}
|
||||
self.next()
|
||||
return idx
|
||||
}
|
||||
|
||||
func (self *_parser) position(idx file.Idx) file.Position {
|
||||
return self.file.Position(int(idx) - self.base)
|
||||
}
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
WhitespaceChars = " \f\n\r\t\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff"
|
||||
Re2Dot = "[^\r\n\u2028\u2029]"
|
||||
)
|
||||
|
||||
type regexpParseError struct {
|
||||
offset int
|
||||
err string
|
||||
}
|
||||
|
||||
type RegexpErrorIncompatible struct {
|
||||
regexpParseError
|
||||
}
|
||||
type RegexpSyntaxError struct {
|
||||
regexpParseError
|
||||
}
|
||||
|
||||
func (s regexpParseError) Error() string {
|
||||
return s.err
|
||||
}
|
||||
|
||||
type _RegExp_parser struct {
|
||||
str string
|
||||
length int
|
||||
|
||||
chr rune // The current character
|
||||
chrOffset int // The offset of current character
|
||||
offset int // The offset after current character (may be greater than 1)
|
||||
|
||||
err error
|
||||
|
||||
goRegexp strings.Builder
|
||||
passOffset int
|
||||
|
||||
dotAll bool // Enable dotAll mode
|
||||
unicode bool
|
||||
}
|
||||
|
||||
// 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 valid, but incompatible (contains a lookahead or backreference),
|
||||
// then this function returns an empty string an error of type RegexpErrorIncompatible.
|
||||
//
|
||||
// If the pattern is invalid (not valid even in JavaScript), then this function
|
||||
// returns an empty string and a generic error.
|
||||
func TransformRegExp(pattern string, dotAll, unicode bool) (transformed string, err error) {
|
||||
|
||||
if pattern == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parser := _RegExp_parser{
|
||||
str: pattern,
|
||||
length: len(pattern),
|
||||
dotAll: dotAll,
|
||||
unicode: unicode,
|
||||
}
|
||||
err = parser.parse()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return parser.ResultString(), nil
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) ResultString() string {
|
||||
if self.passOffset != -1 {
|
||||
return self.str[:self.passOffset]
|
||||
}
|
||||
return self.goRegexp.String()
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) parse() (err error) {
|
||||
self.read() // Pull in the first character
|
||||
self.scan()
|
||||
return self.err
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) read() {
|
||||
if self.offset < self.length {
|
||||
self.chrOffset = self.offset
|
||||
chr, width := rune(self.str[self.offset]), 1
|
||||
if chr >= utf8.RuneSelf { // !ASCII
|
||||
chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
|
||||
if chr == utf8.RuneError && width == 1 {
|
||||
self.error(true, "Invalid UTF-8 character")
|
||||
return
|
||||
}
|
||||
}
|
||||
self.offset += width
|
||||
self.chr = chr
|
||||
} else {
|
||||
self.chrOffset = self.length
|
||||
self.chr = -1 // EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) stopPassing() {
|
||||
self.goRegexp.Grow(3 * len(self.str) / 2)
|
||||
self.goRegexp.WriteString(self.str[:self.passOffset])
|
||||
self.passOffset = -1
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) write(p []byte) {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.Write(p)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) writeByte(b byte) {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.WriteByte(b)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) writeString(s string) {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.WriteString(s)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) scan() {
|
||||
for self.chr != -1 {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.scanBracket()
|
||||
case ')':
|
||||
self.error(true, "Unmatched ')'")
|
||||
return
|
||||
case '.':
|
||||
if self.dotAll {
|
||||
self.pass()
|
||||
break
|
||||
}
|
||||
self.writeString(Re2Dot)
|
||||
self.read()
|
||||
default:
|
||||
self.pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (...)
|
||||
func (self *_RegExp_parser) scanGroup() {
|
||||
str := self.str[self.chrOffset:]
|
||||
if len(str) > 1 { // A possibility of (?= or (?!
|
||||
if str[0] == '?' {
|
||||
ch := str[1]
|
||||
switch {
|
||||
case ch == '=' || ch == '!':
|
||||
self.error(false, "re2: Invalid (%s) <lookahead>", self.str[self.chrOffset:self.chrOffset+2])
|
||||
return
|
||||
case ch == '<':
|
||||
self.error(false, "re2: Invalid (%s) <lookbehind>", self.str[self.chrOffset:self.chrOffset+2])
|
||||
return
|
||||
case ch != ':':
|
||||
self.error(true, "Invalid group")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for self.chr != -1 && self.chr != ')' {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.scanBracket()
|
||||
case '.':
|
||||
if self.dotAll {
|
||||
self.pass()
|
||||
break
|
||||
}
|
||||
self.writeString(Re2Dot)
|
||||
self.read()
|
||||
default:
|
||||
self.pass()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if self.chr != ')' {
|
||||
self.error(true, "Unterminated group")
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// [...]
|
||||
func (self *_RegExp_parser) scanBracket() {
|
||||
str := self.str[self.chrOffset:]
|
||||
if strings.HasPrefix(str, "[]") {
|
||||
// [] -- Empty character class
|
||||
self.writeString("[^\u0000-\U0001FFFF]")
|
||||
self.offset += 1
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(str, "[^]") {
|
||||
self.writeString("[\u0000-\U0001FFFF]")
|
||||
self.offset += 2
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
|
||||
self.pass()
|
||||
for self.chr != -1 {
|
||||
if self.chr == ']' {
|
||||
break
|
||||
} else if self.chr == '\\' {
|
||||
self.read()
|
||||
self.scanEscape(true)
|
||||
continue
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
if self.chr != ']' {
|
||||
self.error(true, "Unterminated character class")
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// \...
|
||||
func (self *_RegExp_parser) scanEscape(inClass bool) {
|
||||
offset := self.chrOffset
|
||||
|
||||
var length, base uint32
|
||||
switch self.chr {
|
||||
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
var value int64
|
||||
size := 0
|
||||
for {
|
||||
digit := int64(digitValue(self.chr))
|
||||
if digit >= 8 {
|
||||
// Not a valid digit
|
||||
break
|
||||
}
|
||||
value = value*8 + digit
|
||||
self.read()
|
||||
size += 1
|
||||
}
|
||||
if size == 1 { // The number of characters read
|
||||
if value != 0 {
|
||||
// An invalid backreference
|
||||
self.error(false, "re2: Invalid \\%d <backreference>", value)
|
||||
return
|
||||
}
|
||||
self.passString(offset-1, self.chrOffset)
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
self.write(tmp)
|
||||
return
|
||||
|
||||
case '8', '9':
|
||||
self.read()
|
||||
self.error(false, "re2: Invalid \\%s <backreference>", self.str[offset:self.chrOffset])
|
||||
return
|
||||
|
||||
case 'x':
|
||||
self.read()
|
||||
length, base = 2, 16
|
||||
|
||||
case 'u':
|
||||
self.read()
|
||||
if self.chr == '{' && self.unicode {
|
||||
self.read()
|
||||
length, base = 0, 16
|
||||
} else {
|
||||
length, base = 4, 16
|
||||
}
|
||||
|
||||
case 'b':
|
||||
if inClass {
|
||||
self.write([]byte{'\\', 'x', '0', '8'})
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 'B':
|
||||
fallthrough
|
||||
|
||||
case 'd', 'D', 'w', 'W':
|
||||
// This is slightly broken, because ECMAScript
|
||||
// includes \v in \s, \S, while re2 does not
|
||||
fallthrough
|
||||
|
||||
case '\\':
|
||||
fallthrough
|
||||
|
||||
case 'f', 'n', 'r', 't', 'v':
|
||||
self.passString(offset-1, self.offset)
|
||||
self.read()
|
||||
return
|
||||
|
||||
case 'c':
|
||||
self.read()
|
||||
var value int64
|
||||
if 'a' <= self.chr && self.chr <= 'z' {
|
||||
value = int64(self.chr - 'a' + 1)
|
||||
} else if 'A' <= self.chr && self.chr <= 'Z' {
|
||||
value = int64(self.chr - 'A' + 1)
|
||||
} else {
|
||||
self.writeByte('c')
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
self.write(tmp)
|
||||
self.read()
|
||||
return
|
||||
case 's':
|
||||
if inClass {
|
||||
self.writeString(WhitespaceChars)
|
||||
} else {
|
||||
self.writeString("[" + WhitespaceChars + "]")
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
case 'S':
|
||||
if inClass {
|
||||
self.error(false, "S in class")
|
||||
return
|
||||
} else {
|
||||
self.writeString("[^" + WhitespaceChars + "]")
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
default:
|
||||
// $ is an identifier character, so we have to have
|
||||
// a special case for it here
|
||||
if self.chr == '$' || self.chr < utf8.RuneSelf && !isIdentifierPart(self.chr) {
|
||||
// A non-identifier character needs escaping
|
||||
self.passString(offset-1, self.offset)
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
// Unescape the character for re2
|
||||
self.pass()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we're a \u.... or \x...
|
||||
valueOffset := self.chrOffset
|
||||
|
||||
if length > 0 {
|
||||
for length := length; length > 0; length-- {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
// Not a valid digit
|
||||
goto skip
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
} else {
|
||||
for self.chr != '}' && self.chr != -1 {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
// Not a valid digit
|
||||
self.error(true, "Invalid Unicode escape")
|
||||
return
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
if length == 4 || length == 0 {
|
||||
self.write([]byte{
|
||||
'\\',
|
||||
'x',
|
||||
'{',
|
||||
})
|
||||
self.passString(valueOffset, self.chrOffset)
|
||||
if length != 0 {
|
||||
self.writeByte('}')
|
||||
}
|
||||
} else if length == 2 {
|
||||
self.passString(offset-1, valueOffset+2)
|
||||
} else {
|
||||
// Should never, ever get here...
|
||||
self.error(true, "re2: Illegal branch in scanEscape")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
skip:
|
||||
self.passString(offset, self.chrOffset)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) pass() {
|
||||
if self.passOffset == self.chrOffset {
|
||||
self.passOffset = self.offset
|
||||
} else {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
if self.chr != -1 {
|
||||
self.goRegexp.WriteRune(self.chr)
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) passString(start, end int) {
|
||||
if self.passOffset == start {
|
||||
self.passOffset = end
|
||||
return
|
||||
}
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.WriteString(self.str[start:end])
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) error(fatal bool, msg string, msgValues ...interface{}) {
|
||||
if self.err != nil {
|
||||
return
|
||||
}
|
||||
e := regexpParseError{
|
||||
offset: self.offset,
|
||||
err: fmt.Sprintf(msg, msgValues...),
|
||||
}
|
||||
if fatal {
|
||||
self.err = RegexpSyntaxError{e}
|
||||
} else {
|
||||
self.err = RegexpErrorIncompatible{e}
|
||||
}
|
||||
self.offset = self.length
|
||||
self.chr = -1
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
type _scope struct {
|
||||
outer *_scope
|
||||
allowIn bool
|
||||
allowLet bool
|
||||
inIteration bool
|
||||
inSwitch bool
|
||||
inFuncParams bool
|
||||
inFunction bool
|
||||
inAsync bool
|
||||
allowAwait bool
|
||||
allowYield bool
|
||||
declarationList []*ast.VariableDeclaration
|
||||
|
||||
labels []unistring.String
|
||||
}
|
||||
|
||||
func (self *_parser) openScope() {
|
||||
self.scope = &_scope{
|
||||
outer: self.scope,
|
||||
allowIn: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) closeScope() {
|
||||
self.scope = self.scope.outer
|
||||
}
|
||||
|
||||
func (self *_scope) declare(declaration *ast.VariableDeclaration) {
|
||||
self.declarationList = append(self.declarationList, declaration)
|
||||
}
|
||||
|
||||
func (self *_scope) hasLabel(name unistring.String) bool {
|
||||
for _, label := range self.labels {
|
||||
if label == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if self.outer != nil && !self.inFunction {
|
||||
// Crossing a function boundary to look for a label is verboten
|
||||
return self.outer.hasLabel(name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
+1078
File diff suppressed because it is too large
Load Diff
+350
@@ -0,0 +1,350 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/pprof/profile"
|
||||
)
|
||||
|
||||
const profInterval = 10 * time.Millisecond
|
||||
const profMaxStackDepth = 64
|
||||
|
||||
const (
|
||||
profReqNone int32 = iota
|
||||
profReqDoSample
|
||||
profReqSampleReady
|
||||
profReqStop
|
||||
)
|
||||
|
||||
type _globalProfiler struct {
|
||||
p profiler
|
||||
w io.Writer
|
||||
|
||||
enabled int32
|
||||
}
|
||||
|
||||
var globalProfiler _globalProfiler
|
||||
|
||||
type profTracker struct {
|
||||
req, finished int32
|
||||
start, stop time.Time
|
||||
numFrames int
|
||||
frames [profMaxStackDepth]StackFrame
|
||||
}
|
||||
|
||||
type profiler struct {
|
||||
mu sync.Mutex
|
||||
trackers []*profTracker
|
||||
buf *profBuffer
|
||||
running bool
|
||||
}
|
||||
|
||||
type profFunc struct {
|
||||
f profile.Function
|
||||
locs map[int32]*profile.Location
|
||||
}
|
||||
|
||||
type profSampleNode struct {
|
||||
loc *profile.Location
|
||||
sample *profile.Sample
|
||||
parent *profSampleNode
|
||||
children map[*profile.Location]*profSampleNode
|
||||
}
|
||||
|
||||
type profBuffer struct {
|
||||
funcs map[*Program]*profFunc
|
||||
root profSampleNode
|
||||
}
|
||||
|
||||
func (pb *profBuffer) addSample(pt *profTracker) {
|
||||
sampleFrames := pt.frames[:pt.numFrames]
|
||||
n := &pb.root
|
||||
for j := len(sampleFrames) - 1; j >= 0; j-- {
|
||||
frame := sampleFrames[j]
|
||||
if frame.prg == nil {
|
||||
continue
|
||||
}
|
||||
var f *profFunc
|
||||
if f = pb.funcs[frame.prg]; f == nil {
|
||||
f = &profFunc{
|
||||
locs: make(map[int32]*profile.Location),
|
||||
}
|
||||
if pb.funcs == nil {
|
||||
pb.funcs = make(map[*Program]*profFunc)
|
||||
}
|
||||
pb.funcs[frame.prg] = f
|
||||
}
|
||||
var loc *profile.Location
|
||||
if loc = f.locs[int32(frame.pc)]; loc == nil {
|
||||
loc = &profile.Location{}
|
||||
f.locs[int32(frame.pc)] = loc
|
||||
}
|
||||
if nn := n.children[loc]; nn == nil {
|
||||
if n.children == nil {
|
||||
n.children = make(map[*profile.Location]*profSampleNode, 1)
|
||||
}
|
||||
nn = &profSampleNode{
|
||||
parent: n,
|
||||
loc: loc,
|
||||
}
|
||||
n.children[loc] = nn
|
||||
n = nn
|
||||
} else {
|
||||
n = nn
|
||||
}
|
||||
}
|
||||
smpl := n.sample
|
||||
if smpl == nil {
|
||||
locs := make([]*profile.Location, 0, len(sampleFrames))
|
||||
for n1 := n; n1.loc != nil; n1 = n1.parent {
|
||||
locs = append(locs, n1.loc)
|
||||
}
|
||||
smpl = &profile.Sample{
|
||||
Location: locs,
|
||||
Value: make([]int64, 2),
|
||||
}
|
||||
n.sample = smpl
|
||||
}
|
||||
smpl.Value[0]++
|
||||
smpl.Value[1] += int64(pt.stop.Sub(pt.start))
|
||||
}
|
||||
|
||||
func (pb *profBuffer) profile() *profile.Profile {
|
||||
pr := profile.Profile{}
|
||||
pr.SampleType = []*profile.ValueType{
|
||||
{Type: "samples", Unit: "count"},
|
||||
{Type: "cpu", Unit: "nanoseconds"},
|
||||
}
|
||||
pr.PeriodType = pr.SampleType[1]
|
||||
pr.Period = int64(profInterval)
|
||||
mapping := &profile.Mapping{
|
||||
ID: 1,
|
||||
File: "[ECMAScript code]",
|
||||
}
|
||||
pr.Mapping = make([]*profile.Mapping, 1, len(pb.funcs)+1)
|
||||
pr.Mapping[0] = mapping
|
||||
|
||||
pr.Function = make([]*profile.Function, 0, len(pb.funcs))
|
||||
funcNames := make(map[string]struct{})
|
||||
var funcId, locId uint64
|
||||
for prg, f := range pb.funcs {
|
||||
fileName := prg.src.Name()
|
||||
funcId++
|
||||
f.f.ID = funcId
|
||||
f.f.Filename = fileName
|
||||
var funcName string
|
||||
if prg.funcName != "" {
|
||||
funcName = prg.funcName.String()
|
||||
} else {
|
||||
funcName = "<anonymous>"
|
||||
}
|
||||
// Make sure the function name is unique, otherwise the graph display merges them into one node, even
|
||||
// if they are in different mappings.
|
||||
if _, exists := funcNames[funcName]; exists {
|
||||
funcName += "." + strconv.FormatUint(f.f.ID, 10)
|
||||
} else {
|
||||
funcNames[funcName] = struct{}{}
|
||||
}
|
||||
f.f.Name = funcName
|
||||
pr.Function = append(pr.Function, &f.f)
|
||||
for pc, loc := range f.locs {
|
||||
locId++
|
||||
loc.ID = locId
|
||||
pos := prg.src.Position(prg.sourceOffset(int(pc)))
|
||||
loc.Line = []profile.Line{
|
||||
{
|
||||
Function: &f.f,
|
||||
Line: int64(pos.Line),
|
||||
},
|
||||
}
|
||||
|
||||
loc.Mapping = mapping
|
||||
pr.Location = append(pr.Location, loc)
|
||||
}
|
||||
}
|
||||
pb.addSamples(&pr, &pb.root)
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pb *profBuffer) addSamples(p *profile.Profile, n *profSampleNode) {
|
||||
if n.sample != nil {
|
||||
p.Sample = append(p.Sample, n.sample)
|
||||
}
|
||||
for _, child := range n.children {
|
||||
pb.addSamples(p, child)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *profiler) run() {
|
||||
ticker := time.NewTicker(profInterval)
|
||||
counter := 0
|
||||
|
||||
for ts := range ticker.C {
|
||||
p.mu.Lock()
|
||||
left := len(p.trackers)
|
||||
if left == 0 {
|
||||
break
|
||||
}
|
||||
for {
|
||||
// This loop runs until either one of the VMs is signalled or all of the VMs are scanned and found
|
||||
// busy or deleted.
|
||||
if counter >= len(p.trackers) {
|
||||
counter = 0
|
||||
}
|
||||
tracker := p.trackers[counter]
|
||||
req := atomic.LoadInt32(&tracker.req)
|
||||
if req == profReqSampleReady {
|
||||
p.buf.addSample(tracker)
|
||||
}
|
||||
if atomic.LoadInt32(&tracker.finished) != 0 {
|
||||
p.trackers[counter] = p.trackers[len(p.trackers)-1]
|
||||
p.trackers[len(p.trackers)-1] = nil
|
||||
p.trackers = p.trackers[:len(p.trackers)-1]
|
||||
} else {
|
||||
counter++
|
||||
if req != profReqDoSample {
|
||||
// signal the VM to take a sample
|
||||
tracker.start = ts
|
||||
atomic.StoreInt32(&tracker.req, profReqDoSample)
|
||||
break
|
||||
}
|
||||
}
|
||||
left--
|
||||
if left <= 0 {
|
||||
// all VMs are busy
|
||||
break
|
||||
}
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
ticker.Stop()
|
||||
p.running = false
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
func (p *profiler) registerVm() *profTracker {
|
||||
pt := new(profTracker)
|
||||
p.mu.Lock()
|
||||
if p.buf != nil {
|
||||
p.trackers = append(p.trackers, pt)
|
||||
if !p.running {
|
||||
go p.run()
|
||||
p.running = true
|
||||
}
|
||||
} else {
|
||||
pt.req = profReqStop
|
||||
}
|
||||
p.mu.Unlock()
|
||||
return pt
|
||||
}
|
||||
|
||||
func (p *profiler) start() error {
|
||||
p.mu.Lock()
|
||||
if p.buf != nil {
|
||||
p.mu.Unlock()
|
||||
return errors.New("profiler is already active")
|
||||
}
|
||||
p.buf = new(profBuffer)
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *profiler) stop() *profile.Profile {
|
||||
p.mu.Lock()
|
||||
trackers, buf := p.trackers, p.buf
|
||||
p.trackers, p.buf = nil, nil
|
||||
p.mu.Unlock()
|
||||
if buf != nil {
|
||||
k := 0
|
||||
for i, tracker := range trackers {
|
||||
req := atomic.LoadInt32(&tracker.req)
|
||||
if req == profReqSampleReady {
|
||||
buf.addSample(tracker)
|
||||
} else if req == profReqDoSample {
|
||||
// In case the VM is requested to do a sample, there is a small chance of a race
|
||||
// where we set profReqStop in between the read and the write, so that the req
|
||||
// ends up being set to profReqSampleReady. It's no such a big deal if we do nothing,
|
||||
// it just means the VM remains in tracing mode until it finishes the current run,
|
||||
// but we do an extra cleanup step later just in case.
|
||||
if i != k {
|
||||
trackers[k] = trackers[i]
|
||||
}
|
||||
k++
|
||||
}
|
||||
atomic.StoreInt32(&tracker.req, profReqStop)
|
||||
}
|
||||
|
||||
if k > 0 {
|
||||
trackers = trackers[:k]
|
||||
go func() {
|
||||
// Make sure all VMs are requested to stop tracing.
|
||||
for {
|
||||
k := 0
|
||||
for i, tracker := range trackers {
|
||||
req := atomic.LoadInt32(&tracker.req)
|
||||
if req != profReqStop {
|
||||
atomic.StoreInt32(&tracker.req, profReqStop)
|
||||
if i != k {
|
||||
trackers[k] = trackers[i]
|
||||
}
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if k == 0 {
|
||||
return
|
||||
}
|
||||
trackers = trackers[:k]
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return buf.profile()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
StartProfile enables execution time profiling for all Runtimes within the current process.
|
||||
This works similar to pprof.StartCPUProfile and produces the same format which can be consumed by `go tool pprof`.
|
||||
There are, however, a few notable differences. Firstly, it's not a CPU profile, rather "execution time" profile.
|
||||
It measures the time the VM spends executing an instruction. If this instruction happens to be a call to a
|
||||
blocking Go function, the waiting time will be measured. Secondly, the 'cpu' sample isn't simply `count*period`,
|
||||
it's the time interval between when sampling was requested and when the instruction has finished. If a VM is still
|
||||
executing the same instruction when the time comes for the next sample, the sampling is skipped (i.e. `count` doesn't
|
||||
grow).
|
||||
|
||||
If there are multiple functions with the same name, their names get a '.N' suffix, where N is a unique number,
|
||||
because otherwise the graph view merges them together (even if they are in different mappings). This includes
|
||||
"<anonymous>" functions.
|
||||
|
||||
The sampling period is set to 10ms.
|
||||
|
||||
It returns an error if profiling is already active.
|
||||
*/
|
||||
func StartProfile(w io.Writer) error {
|
||||
err := globalProfiler.p.start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
globalProfiler.w = w
|
||||
atomic.StoreInt32(&globalProfiler.enabled, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
StopProfile stops the current profile initiated by StartProfile, if any.
|
||||
*/
|
||||
func StopProfile() {
|
||||
atomic.StoreInt32(&globalProfiler.enabled, 0)
|
||||
pr := globalProfiler.p.stop()
|
||||
if pr != nil {
|
||||
_ = pr.Write(globalProfiler.w)
|
||||
}
|
||||
globalProfiler.w = nil
|
||||
}
|
||||
+1074
File diff suppressed because it is too large
Load Diff
+639
@@ -0,0 +1,639 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
type regexp2MatchCache struct {
|
||||
target String
|
||||
runes []rune
|
||||
posMap []int
|
||||
}
|
||||
|
||||
// Not goroutine-safe. Use regexp2Wrapper.clone()
|
||||
type regexp2Wrapper struct {
|
||||
rx *regexp2.Regexp
|
||||
cache *regexp2MatchCache
|
||||
}
|
||||
|
||||
type regexpWrapper regexp.Regexp
|
||||
|
||||
type positionMapItem struct {
|
||||
src, dst int
|
||||
}
|
||||
type positionMap []positionMapItem
|
||||
|
||||
func (m positionMap) get(src int) int {
|
||||
if src <= 0 {
|
||||
return src
|
||||
}
|
||||
res := sort.Search(len(m), func(n int) bool { return m[n].src >= src })
|
||||
if res >= len(m) || m[res].src != src {
|
||||
panic("index not found")
|
||||
}
|
||||
return m[res].dst
|
||||
}
|
||||
|
||||
type arrayRuneReader struct {
|
||||
runes []rune
|
||||
pos int
|
||||
}
|
||||
|
||||
func (rd *arrayRuneReader) ReadRune() (r rune, size int, err error) {
|
||||
if rd.pos < len(rd.runes) {
|
||||
r = rd.runes[rd.pos]
|
||||
size = 1
|
||||
rd.pos++
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Not goroutine-safe. Use regexpPattern.clone()
|
||||
type regexpPattern struct {
|
||||
src string
|
||||
|
||||
global, ignoreCase, multiline, dotAll, sticky, unicode bool
|
||||
|
||||
regexpWrapper *regexpWrapper
|
||||
regexp2Wrapper *regexp2Wrapper
|
||||
}
|
||||
|
||||
func compileRegexp2(src string, multiline, dotAll, ignoreCase, unicode bool) (*regexp2Wrapper, error) {
|
||||
var opts regexp2.RegexOptions = regexp2.ECMAScript
|
||||
if multiline {
|
||||
opts |= regexp2.Multiline
|
||||
}
|
||||
if dotAll {
|
||||
opts |= regexp2.Singleline
|
||||
}
|
||||
if ignoreCase {
|
||||
opts |= regexp2.IgnoreCase
|
||||
}
|
||||
if unicode {
|
||||
opts |= regexp2.Unicode
|
||||
}
|
||||
regexp2Pattern, err1 := regexp2.Compile(src, opts)
|
||||
if err1 != nil {
|
||||
return nil, fmt.Errorf("Invalid regular expression (regexp2): %s (%v)", src, err1)
|
||||
}
|
||||
|
||||
return ®exp2Wrapper{rx: regexp2Pattern}, nil
|
||||
}
|
||||
|
||||
func (p *regexpPattern) createRegexp2() {
|
||||
if p.regexp2Wrapper != nil {
|
||||
return
|
||||
}
|
||||
rx, err := compileRegexp2(p.src, p.multiline, p.dotAll, p.ignoreCase, p.unicode)
|
||||
if err != nil {
|
||||
// At this point the regexp should have been successfully converted to re2, if it fails now, it's a bug.
|
||||
panic(err)
|
||||
}
|
||||
p.regexp2Wrapper = rx
|
||||
}
|
||||
|
||||
func buildUTF8PosMap(s unicodeString) (positionMap, string) {
|
||||
pm := make(positionMap, 0, s.Length())
|
||||
rd := s.Reader()
|
||||
sPos, utf8Pos := 0, 0
|
||||
var sb strings.Builder
|
||||
for {
|
||||
r, size, err := rd.ReadRune()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// the string contains invalid UTF-16, bailing out
|
||||
return nil, ""
|
||||
}
|
||||
utf8Size, _ := sb.WriteRune(r)
|
||||
sPos += size
|
||||
utf8Pos += utf8Size
|
||||
pm = append(pm, positionMapItem{src: utf8Pos, dst: sPos})
|
||||
}
|
||||
return pm, sb.String()
|
||||
}
|
||||
|
||||
func (p *regexpPattern) findSubmatchIndex(s String, start int) []int {
|
||||
if p.regexpWrapper == nil {
|
||||
return p.regexp2Wrapper.findSubmatchIndex(s, start, p.unicode, p.global || p.sticky)
|
||||
}
|
||||
if start != 0 {
|
||||
// Unfortunately Go's regexp library does not allow starting from an arbitrary position.
|
||||
// If we just drop the first _start_ characters of the string the assertions (^, $, \b and \B) will not
|
||||
// work correctly.
|
||||
p.createRegexp2()
|
||||
return p.regexp2Wrapper.findSubmatchIndex(s, start, p.unicode, p.global || p.sticky)
|
||||
}
|
||||
return p.regexpWrapper.findSubmatchIndex(s, p.unicode)
|
||||
}
|
||||
|
||||
func (p *regexpPattern) findAllSubmatchIndex(s String, start int, limit int, sticky bool) [][]int {
|
||||
if p.regexpWrapper == nil {
|
||||
return p.regexp2Wrapper.findAllSubmatchIndex(s, start, limit, sticky, p.unicode)
|
||||
}
|
||||
if start == 0 {
|
||||
a, u := devirtualizeString(s)
|
||||
if u == nil {
|
||||
return p.regexpWrapper.findAllSubmatchIndex(string(a), limit, sticky)
|
||||
}
|
||||
if limit == 1 {
|
||||
result := p.regexpWrapper.findSubmatchIndexUnicode(u, p.unicode)
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
return [][]int{result}
|
||||
}
|
||||
// Unfortunately Go's regexp library lacks FindAllReaderSubmatchIndex(), so we have to use a UTF-8 string as an
|
||||
// input.
|
||||
if p.unicode {
|
||||
// Try to convert s to UTF-8. If it does not contain any invalid UTF-16 we can do the matching in UTF-8.
|
||||
pm, str := buildUTF8PosMap(u)
|
||||
if pm != nil {
|
||||
res := p.regexpWrapper.findAllSubmatchIndex(str, limit, sticky)
|
||||
for _, result := range res {
|
||||
for i, idx := range result {
|
||||
result[i] = pm.get(idx)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.createRegexp2()
|
||||
return p.regexp2Wrapper.findAllSubmatchIndex(s, start, limit, sticky, p.unicode)
|
||||
}
|
||||
|
||||
// clone creates a copy of the regexpPattern which can be used concurrently.
|
||||
func (p *regexpPattern) clone() *regexpPattern {
|
||||
ret := ®expPattern{
|
||||
src: p.src,
|
||||
global: p.global,
|
||||
ignoreCase: p.ignoreCase,
|
||||
multiline: p.multiline,
|
||||
dotAll: p.dotAll,
|
||||
sticky: p.sticky,
|
||||
unicode: p.unicode,
|
||||
}
|
||||
if p.regexpWrapper != nil {
|
||||
ret.regexpWrapper = p.regexpWrapper.clone()
|
||||
}
|
||||
if p.regexp2Wrapper != nil {
|
||||
ret.regexp2Wrapper = p.regexp2Wrapper.clone()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type regexpObject struct {
|
||||
baseObject
|
||||
pattern *regexpPattern
|
||||
source String
|
||||
|
||||
standard bool
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findSubmatchIndex(s String, start int, fullUnicode, doCache bool) (result []int) {
|
||||
if fullUnicode {
|
||||
return r.findSubmatchIndexUnicode(s, start, doCache)
|
||||
}
|
||||
return r.findSubmatchIndexUTF16(s, start, doCache)
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findUTF16Cached(s String, start int, doCache bool) (match *regexp2.Match, runes []rune, err error) {
|
||||
wrapped := r.rx
|
||||
cache := r.cache
|
||||
if cache != nil && cache.posMap == nil && cache.target.SameAs(s) {
|
||||
runes = cache.runes
|
||||
} else {
|
||||
runes = s.utf16Runes()
|
||||
cache = nil
|
||||
}
|
||||
match, err = wrapped.FindRunesMatchStartingAt(runes, start)
|
||||
if doCache && match != nil && err == nil {
|
||||
if cache == nil {
|
||||
if r.cache == nil {
|
||||
r.cache = new(regexp2MatchCache)
|
||||
}
|
||||
*r.cache = regexp2MatchCache{
|
||||
target: s,
|
||||
runes: runes,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.cache = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findSubmatchIndexUTF16(s String, start int, doCache bool) (result []int) {
|
||||
match, _, err := r.findUTF16Cached(s, start, doCache)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if match == nil {
|
||||
return
|
||||
}
|
||||
groups := match.Groups()
|
||||
|
||||
result = make([]int, 0, len(groups)<<1)
|
||||
for _, group := range groups {
|
||||
if len(group.Captures) > 0 {
|
||||
result = append(result, group.Index, group.Index+group.Length)
|
||||
} else {
|
||||
result = append(result, -1, 0)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findUnicodeCached(s String, start int, doCache bool) (match *regexp2.Match, posMap []int, err error) {
|
||||
var (
|
||||
runes []rune
|
||||
mappedStart int
|
||||
splitPair bool
|
||||
savedRune rune
|
||||
)
|
||||
wrapped := r.rx
|
||||
cache := r.cache
|
||||
if cache != nil && cache.posMap != nil && cache.target.SameAs(s) {
|
||||
runes, posMap = cache.runes, cache.posMap
|
||||
mappedStart, splitPair = posMapReverseLookup(posMap, start)
|
||||
} else {
|
||||
posMap, runes, mappedStart, splitPair = buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.Length(), start)
|
||||
cache = nil
|
||||
}
|
||||
if splitPair {
|
||||
// temporarily set the rune at mappedStart to the second code point of the pair
|
||||
_, second := utf16.EncodeRune(runes[mappedStart])
|
||||
savedRune, runes[mappedStart] = runes[mappedStart], second
|
||||
}
|
||||
match, err = wrapped.FindRunesMatchStartingAt(runes, mappedStart)
|
||||
if doCache && match != nil && err == nil {
|
||||
if splitPair {
|
||||
runes[mappedStart] = savedRune
|
||||
}
|
||||
if cache == nil {
|
||||
if r.cache == nil {
|
||||
r.cache = new(regexp2MatchCache)
|
||||
}
|
||||
*r.cache = regexp2MatchCache{
|
||||
target: s,
|
||||
runes: runes,
|
||||
posMap: posMap,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.cache = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findSubmatchIndexUnicode(s String, start int, doCache bool) (result []int) {
|
||||
match, posMap, err := r.findUnicodeCached(s, start, doCache)
|
||||
if match == nil || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
groups := match.Groups()
|
||||
|
||||
result = make([]int, 0, len(groups)<<1)
|
||||
for _, group := range groups {
|
||||
if len(group.Captures) > 0 {
|
||||
result = append(result, posMap[group.Index], posMap[group.Index+group.Length])
|
||||
} else {
|
||||
result = append(result, -1, 0)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findAllSubmatchIndexUTF16(s String, start, limit int, sticky bool) [][]int {
|
||||
wrapped := r.rx
|
||||
match, runes, err := r.findUTF16Cached(s, start, false)
|
||||
if match == nil || err != nil {
|
||||
return nil
|
||||
}
|
||||
if limit < 0 {
|
||||
limit = len(runes) + 1
|
||||
}
|
||||
results := make([][]int, 0, limit)
|
||||
for match != nil {
|
||||
groups := match.Groups()
|
||||
|
||||
result := make([]int, 0, len(groups)<<1)
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group.Captures) > 0 {
|
||||
startPos := group.Index
|
||||
endPos := group.Index + group.Length
|
||||
result = append(result, startPos, endPos)
|
||||
} else {
|
||||
result = append(result, -1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if sticky && len(result) > 1 {
|
||||
if result[0] != start {
|
||||
break
|
||||
}
|
||||
start = result[1]
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
limit--
|
||||
if limit <= 0 {
|
||||
break
|
||||
}
|
||||
match, err = wrapped.FindNextMatch(match)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func buildPosMap(rd io.RuneReader, l, start int) (posMap []int, runes []rune, mappedStart int, splitPair bool) {
|
||||
posMap = make([]int, 0, l+1)
|
||||
curPos := 0
|
||||
runes = make([]rune, 0, l)
|
||||
startFound := false
|
||||
for {
|
||||
if !startFound {
|
||||
if curPos == start {
|
||||
mappedStart = len(runes)
|
||||
startFound = true
|
||||
}
|
||||
if curPos > start {
|
||||
// start position splits a surrogate pair
|
||||
mappedStart = len(runes) - 1
|
||||
splitPair = true
|
||||
startFound = true
|
||||
}
|
||||
}
|
||||
rn, size, err := rd.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
runes = append(runes, rn)
|
||||
posMap = append(posMap, curPos)
|
||||
curPos += size
|
||||
}
|
||||
posMap = append(posMap, curPos)
|
||||
return
|
||||
}
|
||||
|
||||
func posMapReverseLookup(posMap []int, pos int) (int, bool) {
|
||||
mapped := sort.SearchInts(posMap, pos)
|
||||
if mapped < len(posMap) && posMap[mapped] != pos {
|
||||
return mapped - 1, true
|
||||
}
|
||||
return mapped, false
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findAllSubmatchIndexUnicode(s unicodeString, start, limit int, sticky bool) [][]int {
|
||||
wrapped := r.rx
|
||||
if limit < 0 {
|
||||
limit = len(s) + 1
|
||||
}
|
||||
results := make([][]int, 0, limit)
|
||||
match, posMap, err := r.findUnicodeCached(s, start, false)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for match != nil {
|
||||
groups := match.Groups()
|
||||
|
||||
result := make([]int, 0, len(groups)<<1)
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group.Captures) > 0 {
|
||||
start := posMap[group.Index]
|
||||
end := posMap[group.Index+group.Length]
|
||||
result = append(result, start, end)
|
||||
} else {
|
||||
result = append(result, -1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if sticky && len(result) > 1 {
|
||||
if result[0] != start {
|
||||
break
|
||||
}
|
||||
start = result[1]
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
match, err = wrapped.FindNextMatch(match)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) findAllSubmatchIndex(s String, start, limit int, sticky, fullUnicode bool) [][]int {
|
||||
a, u := devirtualizeString(s)
|
||||
if u != nil {
|
||||
if fullUnicode {
|
||||
return r.findAllSubmatchIndexUnicode(u, start, limit, sticky)
|
||||
}
|
||||
return r.findAllSubmatchIndexUTF16(u, start, limit, sticky)
|
||||
}
|
||||
return r.findAllSubmatchIndexUTF16(a, start, limit, sticky)
|
||||
}
|
||||
|
||||
func (r *regexp2Wrapper) clone() *regexp2Wrapper {
|
||||
return ®exp2Wrapper{
|
||||
rx: r.rx,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *regexpWrapper) findAllSubmatchIndex(s string, limit int, sticky bool) (results [][]int) {
|
||||
wrapped := (*regexp.Regexp)(r)
|
||||
results = wrapped.FindAllStringSubmatchIndex(s, limit)
|
||||
pos := 0
|
||||
if sticky {
|
||||
for i, result := range results {
|
||||
if len(result) > 1 {
|
||||
if result[0] != pos {
|
||||
return results[:i]
|
||||
}
|
||||
pos = result[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *regexpWrapper) findSubmatchIndex(s String, fullUnicode bool) []int {
|
||||
a, u := devirtualizeString(s)
|
||||
if u != nil {
|
||||
return r.findSubmatchIndexUnicode(u, fullUnicode)
|
||||
}
|
||||
return r.findSubmatchIndexASCII(string(a))
|
||||
}
|
||||
|
||||
func (r *regexpWrapper) findSubmatchIndexASCII(s string) []int {
|
||||
wrapped := (*regexp.Regexp)(r)
|
||||
return wrapped.FindStringSubmatchIndex(s)
|
||||
}
|
||||
|
||||
func (r *regexpWrapper) findSubmatchIndexUnicode(s unicodeString, fullUnicode bool) (result []int) {
|
||||
wrapped := (*regexp.Regexp)(r)
|
||||
if fullUnicode {
|
||||
posMap, runes, _, _ := buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.Length(), 0)
|
||||
res := wrapped.FindReaderSubmatchIndex(&arrayRuneReader{runes: runes})
|
||||
for i, item := range res {
|
||||
if item >= 0 {
|
||||
res[i] = posMap[item]
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
return wrapped.FindReaderSubmatchIndex(s.utf16RuneReader())
|
||||
}
|
||||
|
||||
func (r *regexpWrapper) clone() *regexpWrapper {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *regexpObject) execResultToArray(target String, result []int) Value {
|
||||
captureCount := len(result) >> 1
|
||||
valueArray := make([]Value, captureCount)
|
||||
matchIndex := result[0]
|
||||
valueArray[0] = target.Substring(result[0], result[1])
|
||||
lowerBound := 0
|
||||
for index := 1; index < captureCount; index++ {
|
||||
offset := index << 1
|
||||
if result[offset] >= 0 && result[offset+1] >= lowerBound {
|
||||
valueArray[index] = target.Substring(result[offset], result[offset+1])
|
||||
lowerBound = result[offset]
|
||||
} else {
|
||||
valueArray[index] = _undefined
|
||||
}
|
||||
}
|
||||
match := r.val.runtime.newArrayValues(valueArray)
|
||||
match.self.setOwnStr("input", target, false)
|
||||
match.self.setOwnStr("index", intToValue(int64(matchIndex)), false)
|
||||
return match
|
||||
}
|
||||
|
||||
func (r *regexpObject) getLastIndex() int64 {
|
||||
lastIndex := toLength(r.getStr("lastIndex", nil))
|
||||
if !r.pattern.global && !r.pattern.sticky {
|
||||
return 0
|
||||
}
|
||||
return lastIndex
|
||||
}
|
||||
|
||||
func (r *regexpObject) execRegexp(target String) (match bool, result []int) {
|
||||
index := r.getLastIndex()
|
||||
if index >= 0 && index <= int64(target.Length()) {
|
||||
result = r.pattern.findSubmatchIndex(target, int(index))
|
||||
}
|
||||
match = len(result) > 0 && (!r.pattern.sticky || int64(result[0]) == index)
|
||||
|
||||
if r.pattern.global || r.pattern.sticky {
|
||||
var newLastIndex int64
|
||||
if match {
|
||||
newLastIndex = int64(result[1])
|
||||
}
|
||||
r.setOwnStr("lastIndex", intToValue(newLastIndex), true)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *regexpObject) exec(target String) Value {
|
||||
match, result := r.execRegexp(target)
|
||||
if match {
|
||||
return r.execResultToArray(target, result)
|
||||
}
|
||||
return _null
|
||||
}
|
||||
|
||||
func (r *regexpObject) test(target String) bool {
|
||||
match, _ := r.execRegexp(target)
|
||||
return match
|
||||
}
|
||||
|
||||
func (r *regexpObject) clone() *regexpObject {
|
||||
r1 := r.val.runtime.newRegexpObject(r.prototype)
|
||||
r1.source = r.source
|
||||
r1.pattern = r.pattern
|
||||
|
||||
return r1
|
||||
}
|
||||
|
||||
func (r *regexpObject) init() {
|
||||
r.baseObject.init()
|
||||
r.standard = true
|
||||
r._putProp("lastIndex", intToValue(0), true, false, false)
|
||||
}
|
||||
|
||||
func (r *regexpObject) setProto(proto *Object, throw bool) bool {
|
||||
res := r.baseObject.setProto(proto, throw)
|
||||
if res {
|
||||
r.standard = false
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *regexpObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
|
||||
res := r.baseObject.defineOwnPropertyStr(name, desc, throw)
|
||||
if res {
|
||||
r.standard = false
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *regexpObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool {
|
||||
res := r.baseObject.defineOwnPropertySym(name, desc, throw)
|
||||
if res && r.standard {
|
||||
switch name {
|
||||
case SymMatch, SymMatchAll, SymSearch, SymSplit, SymReplace:
|
||||
r.standard = false
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *regexpObject) deleteStr(name unistring.String, throw bool) bool {
|
||||
res := r.baseObject.deleteStr(name, throw)
|
||||
if res {
|
||||
r.standard = false
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *regexpObject) setOwnStr(name unistring.String, value Value, throw bool) bool {
|
||||
res := r.baseObject.setOwnStr(name, value, throw)
|
||||
if res && r.standard && name == "exec" {
|
||||
r.standard = false
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *regexpObject) setOwnSym(name *Symbol, value Value, throw bool) bool {
|
||||
res := r.baseObject.setOwnSym(name, value, throw)
|
||||
if res && r.standard {
|
||||
switch name {
|
||||
case SymMatch, SymMatchAll, SymSearch, SymSplit, SymReplace:
|
||||
r.standard = false
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
+3234
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
checks = ["all", "-ST1000", "-ST1003", "-ST1005", "-ST1006", "-ST1012", "-ST1021", "-ST1020", "-ST1008"]
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
const (
|
||||
__proto__ = "__proto__"
|
||||
)
|
||||
|
||||
var (
|
||||
stringTrue String = asciiString("true")
|
||||
stringFalse String = asciiString("false")
|
||||
stringNull String = asciiString("null")
|
||||
stringUndefined String = asciiString("undefined")
|
||||
stringObjectC String = asciiString("object")
|
||||
stringFunction String = asciiString("function")
|
||||
stringBoolean String = asciiString("boolean")
|
||||
stringString String = asciiString("string")
|
||||
stringSymbol String = asciiString("symbol")
|
||||
stringNumber String = asciiString("number")
|
||||
stringBigInt String = asciiString("bigint")
|
||||
stringNaN String = asciiString("NaN")
|
||||
stringInfinity = asciiString("Infinity")
|
||||
stringNegInfinity = asciiString("-Infinity")
|
||||
stringBound_ String = asciiString("bound ")
|
||||
stringEmpty String = asciiString("")
|
||||
|
||||
stringError String = asciiString("Error")
|
||||
stringAggregateError String = asciiString("AggregateError")
|
||||
stringTypeError String = asciiString("TypeError")
|
||||
stringReferenceError String = asciiString("ReferenceError")
|
||||
stringSyntaxError String = asciiString("SyntaxError")
|
||||
stringRangeError String = asciiString("RangeError")
|
||||
stringEvalError String = asciiString("EvalError")
|
||||
stringURIError String = asciiString("URIError")
|
||||
stringGoError String = asciiString("GoError")
|
||||
|
||||
stringObjectNull String = asciiString("[object Null]")
|
||||
stringObjectUndefined String = asciiString("[object Undefined]")
|
||||
stringInvalidDate String = asciiString("Invalid Date")
|
||||
)
|
||||
|
||||
type utf16Reader interface {
|
||||
readChar() (c uint16, err error)
|
||||
}
|
||||
|
||||
// String represents an ECMAScript string Value. Its internal representation depends on the contents of the
|
||||
// string, but in any case it is capable of holding any UTF-16 string, either valid or invalid.
|
||||
// Instances of this type, as any other primitive values, are goroutine-safe and can be passed between runtimes.
|
||||
// Strings can be created using Runtime.ToValue(goString) or StringFromUTF16.
|
||||
type String interface {
|
||||
Value
|
||||
CharAt(int) uint16
|
||||
Length() int
|
||||
Concat(String) String
|
||||
Substring(start, end int) String
|
||||
CompareTo(String) int
|
||||
Reader() io.RuneReader
|
||||
utf16Reader() utf16Reader
|
||||
utf16RuneReader() io.RuneReader
|
||||
utf16Runes() []rune
|
||||
index(String, int) int
|
||||
lastIndex(String, int) int
|
||||
toLower() String
|
||||
toUpper() String
|
||||
toTrimmedUTF8() string
|
||||
}
|
||||
|
||||
type stringIterObject struct {
|
||||
baseObject
|
||||
reader io.RuneReader
|
||||
}
|
||||
|
||||
func isUTF16FirstSurrogate(c uint16) bool {
|
||||
return c >= 0xD800 && c <= 0xDBFF
|
||||
}
|
||||
|
||||
func isUTF16SecondSurrogate(c uint16) bool {
|
||||
return c >= 0xDC00 && c <= 0xDFFF
|
||||
}
|
||||
|
||||
func (si *stringIterObject) next() Value {
|
||||
if si.reader == nil {
|
||||
return si.val.runtime.createIterResultObject(_undefined, true)
|
||||
}
|
||||
r, _, err := si.reader.ReadRune()
|
||||
if err == io.EOF {
|
||||
si.reader = nil
|
||||
return si.val.runtime.createIterResultObject(_undefined, true)
|
||||
}
|
||||
return si.val.runtime.createIterResultObject(stringFromRune(r), false)
|
||||
}
|
||||
|
||||
func stringFromRune(r rune) String {
|
||||
if r < utf8.RuneSelf {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte(byte(r))
|
||||
return asciiString(sb.String())
|
||||
}
|
||||
var sb unicodeStringBuilder
|
||||
sb.WriteRune(r)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (r *Runtime) createStringIterator(s String) Value {
|
||||
o := &Object{runtime: r}
|
||||
|
||||
si := &stringIterObject{
|
||||
reader: &lenientUtf16Decoder{utf16Reader: s.utf16Reader()},
|
||||
}
|
||||
si.class = classObject
|
||||
si.val = o
|
||||
si.extensible = true
|
||||
o.self = si
|
||||
si.prototype = r.getStringIteratorPrototype()
|
||||
si.init()
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
type stringObject struct {
|
||||
baseObject
|
||||
value String
|
||||
length int
|
||||
lengthProp valueProperty
|
||||
}
|
||||
|
||||
func newStringValue(s string) String {
|
||||
if u := unistring.Scan(s); u != nil {
|
||||
return unicodeString(u)
|
||||
}
|
||||
return asciiString(s)
|
||||
}
|
||||
|
||||
func stringValueFromRaw(raw unistring.String) String {
|
||||
if b := raw.AsUtf16(); b != nil {
|
||||
return unicodeString(b)
|
||||
}
|
||||
return asciiString(raw)
|
||||
}
|
||||
|
||||
func (s *stringObject) init() {
|
||||
s.baseObject.init()
|
||||
s.setLength()
|
||||
}
|
||||
|
||||
func (s *stringObject) setLength() {
|
||||
if s.value != nil {
|
||||
s.length = s.value.Length()
|
||||
}
|
||||
s.lengthProp.value = intToValue(int64(s.length))
|
||||
s._put("length", &s.lengthProp)
|
||||
}
|
||||
|
||||
func (s *stringObject) getStr(name unistring.String, receiver Value) Value {
|
||||
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||||
return s._getIdx(i)
|
||||
}
|
||||
return s.baseObject.getStr(name, receiver)
|
||||
}
|
||||
|
||||
func (s *stringObject) getIdx(idx valueInt, receiver Value) Value {
|
||||
i := int(idx)
|
||||
if i >= 0 && i < s.length {
|
||||
return s._getIdx(i)
|
||||
}
|
||||
return s.baseObject.getStr(idx.string(), receiver)
|
||||
}
|
||||
|
||||
func (s *stringObject) getOwnPropStr(name unistring.String) Value {
|
||||
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||||
val := s._getIdx(i)
|
||||
return &valueProperty{
|
||||
value: val,
|
||||
enumerable: true,
|
||||
}
|
||||
}
|
||||
|
||||
return s.baseObject.getOwnPropStr(name)
|
||||
}
|
||||
|
||||
func (s *stringObject) getOwnPropIdx(idx valueInt) Value {
|
||||
i := int64(idx)
|
||||
if i >= 0 {
|
||||
if i < int64(s.length) {
|
||||
val := s._getIdx(int(i))
|
||||
return &valueProperty{
|
||||
value: val,
|
||||
enumerable: true,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.baseObject.getOwnPropStr(idx.string())
|
||||
}
|
||||
|
||||
func (s *stringObject) _getIdx(idx int) Value {
|
||||
return s.value.Substring(idx, idx+1)
|
||||
}
|
||||
|
||||
func (s *stringObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
|
||||
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||||
s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
|
||||
return false
|
||||
}
|
||||
|
||||
return s.baseObject.setOwnStr(name, val, throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
|
||||
i := int64(idx)
|
||||
if i >= 0 && i < int64(s.length) {
|
||||
s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i)
|
||||
return false
|
||||
}
|
||||
|
||||
return s.baseObject.setOwnStr(idx.string(), val, throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
|
||||
return s._setForeignStr(name, s.getOwnPropStr(name), val, receiver, throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
|
||||
return s._setForeignIdx(idx, s.getOwnPropIdx(idx), val, receiver, throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
|
||||
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||||
_, ok := s._defineOwnProperty(name, &valueProperty{enumerable: true}, descr, throw)
|
||||
return ok
|
||||
}
|
||||
|
||||
return s.baseObject.defineOwnPropertyStr(name, descr, throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
|
||||
i := int64(idx)
|
||||
if i >= 0 && i < int64(s.length) {
|
||||
s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i)
|
||||
return false
|
||||
}
|
||||
|
||||
return s.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
|
||||
}
|
||||
|
||||
type stringPropIter struct {
|
||||
str String // separate, because obj can be the singleton
|
||||
obj *stringObject
|
||||
idx, length int
|
||||
}
|
||||
|
||||
func (i *stringPropIter) next() (propIterItem, iterNextFunc) {
|
||||
if i.idx < i.length {
|
||||
name := strconv.Itoa(i.idx)
|
||||
i.idx++
|
||||
return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
|
||||
}
|
||||
|
||||
return i.obj.baseObject.iterateStringKeys()()
|
||||
}
|
||||
|
||||
func (s *stringObject) iterateStringKeys() iterNextFunc {
|
||||
return (&stringPropIter{
|
||||
str: s.value,
|
||||
obj: s,
|
||||
length: s.length,
|
||||
}).next
|
||||
}
|
||||
|
||||
func (s *stringObject) stringKeys(all bool, accum []Value) []Value {
|
||||
for i := 0; i < s.length; i++ {
|
||||
accum = append(accum, asciiString(strconv.Itoa(i)))
|
||||
}
|
||||
|
||||
return s.baseObject.stringKeys(all, accum)
|
||||
}
|
||||
|
||||
func (s *stringObject) deleteStr(name unistring.String, throw bool) bool {
|
||||
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||||
s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
|
||||
return false
|
||||
}
|
||||
|
||||
return s.baseObject.deleteStr(name, throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool {
|
||||
i := int64(idx)
|
||||
if i >= 0 && i < int64(s.length) {
|
||||
s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i)
|
||||
return false
|
||||
}
|
||||
|
||||
return s.baseObject.deleteStr(idx.string(), throw)
|
||||
}
|
||||
|
||||
func (s *stringObject) hasOwnPropertyStr(name unistring.String) bool {
|
||||
if i := strToGoIdx(name); i >= 0 && i < s.length {
|
||||
return true
|
||||
}
|
||||
return s.baseObject.hasOwnPropertyStr(name)
|
||||
}
|
||||
|
||||
func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool {
|
||||
i := int64(idx)
|
||||
if i >= 0 && i < int64(s.length) {
|
||||
return true
|
||||
}
|
||||
return s.baseObject.hasOwnPropertyStr(idx.string())
|
||||
}
|
||||
|
||||
func devirtualizeString(s String) (asciiString, unicodeString) {
|
||||
switch s := s.(type) {
|
||||
case asciiString:
|
||||
return s, nil
|
||||
case unicodeString:
|
||||
return "", s
|
||||
case *importedString:
|
||||
s.ensureScanned()
|
||||
if s.u != nil {
|
||||
return "", s.u
|
||||
}
|
||||
return asciiString(s.s), nil
|
||||
default:
|
||||
panic(unknownStringTypeErr(s))
|
||||
}
|
||||
}
|
||||
|
||||
func unknownStringTypeErr(v Value) interface{} {
|
||||
return newTypeError("Internal bug: unknown string type: %T", v)
|
||||
}
|
||||
|
||||
// StringFromUTF16 creates a string value from an array of UTF-16 code units. The result is a copy, so the initial
|
||||
// slice can be modified after calling this function (but it must not be modified while the function is running).
|
||||
// No validation of any kind is performed.
|
||||
func StringFromUTF16(chars []uint16) String {
|
||||
isAscii := true
|
||||
for _, c := range chars {
|
||||
if c >= utf8.RuneSelf {
|
||||
isAscii = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isAscii {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(chars))
|
||||
for _, c := range chars {
|
||||
sb.WriteByte(byte(c))
|
||||
}
|
||||
return asciiString(sb.String())
|
||||
}
|
||||
buf := make([]uint16, len(chars)+1)
|
||||
buf[0] = unistring.BOM
|
||||
copy(buf[1:], chars)
|
||||
return unicodeString(buf)
|
||||
}
|
||||
+401
@@ -0,0 +1,401 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
type asciiString string
|
||||
|
||||
type asciiRuneReader struct {
|
||||
s asciiString
|
||||
pos int
|
||||
}
|
||||
|
||||
func (rr *asciiRuneReader) ReadRune() (r rune, size int, err error) {
|
||||
if rr.pos < len(rr.s) {
|
||||
r = rune(rr.s[rr.pos])
|
||||
size = 1
|
||||
rr.pos++
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type asciiUtf16Reader struct {
|
||||
s asciiString
|
||||
pos int
|
||||
}
|
||||
|
||||
func (rr *asciiUtf16Reader) readChar() (c uint16, err error) {
|
||||
if rr.pos < len(rr.s) {
|
||||
c = uint16(rr.s[rr.pos])
|
||||
rr.pos++
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rr *asciiUtf16Reader) ReadRune() (r rune, size int, err error) {
|
||||
if rr.pos < len(rr.s) {
|
||||
r = rune(rr.s[rr.pos])
|
||||
rr.pos++
|
||||
size = 1
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s asciiString) Reader() io.RuneReader {
|
||||
return &asciiRuneReader{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s asciiString) utf16Reader() utf16Reader {
|
||||
return &asciiUtf16Reader{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s asciiString) utf16RuneReader() io.RuneReader {
|
||||
return &asciiUtf16Reader{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s asciiString) utf16Runes() []rune {
|
||||
runes := make([]rune, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
runes[i] = rune(s[i])
|
||||
}
|
||||
return runes
|
||||
}
|
||||
|
||||
// ss must be trimmed
|
||||
func stringToInt(ss string) (int64, error) {
|
||||
if ss == "" {
|
||||
return 0, nil
|
||||
}
|
||||
if ss == "-0" {
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
if len(ss) > 2 {
|
||||
switch ss[:2] {
|
||||
case "0x", "0X":
|
||||
return strconv.ParseInt(ss[2:], 16, 64)
|
||||
case "0b", "0B":
|
||||
return strconv.ParseInt(ss[2:], 2, 64)
|
||||
case "0o", "0O":
|
||||
return strconv.ParseInt(ss[2:], 8, 64)
|
||||
}
|
||||
}
|
||||
return strconv.ParseInt(ss, 10, 64)
|
||||
}
|
||||
|
||||
func (s asciiString) _toInt(trimmed string) (int64, error) {
|
||||
return stringToInt(trimmed)
|
||||
}
|
||||
|
||||
func isRangeErr(err error) bool {
|
||||
if err, ok := err.(*strconv.NumError); ok {
|
||||
return err.Err == strconv.ErrRange
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s asciiString) _toFloat(trimmed string) (float64, error) {
|
||||
if trimmed == "" {
|
||||
return 0, nil
|
||||
}
|
||||
if trimmed == "-0" {
|
||||
var f float64
|
||||
return -f, nil
|
||||
}
|
||||
|
||||
// Go allows underscores in numbers, when parsed as floats, but ECMAScript expect them to be interpreted as NaN.
|
||||
if strings.ContainsRune(trimmed, '_') {
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
|
||||
// Hexadecimal floats are not supported by ECMAScript.
|
||||
if len(trimmed) >= 2 {
|
||||
var prefix string
|
||||
if trimmed[0] == '-' || trimmed[0] == '+' {
|
||||
prefix = trimmed[1:]
|
||||
} else {
|
||||
prefix = trimmed
|
||||
}
|
||||
if len(prefix) >= 2 && prefix[0] == '0' && (prefix[1] == 'x' || prefix[1] == 'X') {
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(trimmed, 64)
|
||||
if err == nil && math.IsInf(f, 0) {
|
||||
ss := strings.ToLower(trimmed)
|
||||
if strings.HasPrefix(ss, "inf") || strings.HasPrefix(ss, "-inf") || strings.HasPrefix(ss, "+inf") {
|
||||
// We handle "Infinity" separately, prevent from being parsed as Infinity due to strconv.ParseFloat() permissive syntax
|
||||
return 0, strconv.ErrSyntax
|
||||
}
|
||||
}
|
||||
if isRangeErr(err) {
|
||||
err = nil
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func (s asciiString) ToInteger() int64 {
|
||||
ss := strings.TrimSpace(string(s))
|
||||
if ss == "" {
|
||||
return 0
|
||||
}
|
||||
if ss == "Infinity" || ss == "+Infinity" {
|
||||
return math.MaxInt64
|
||||
}
|
||||
if ss == "-Infinity" {
|
||||
return math.MinInt64
|
||||
}
|
||||
i, err := s._toInt(ss)
|
||||
if err != nil {
|
||||
f, err := s._toFloat(ss)
|
||||
if err == nil {
|
||||
return int64(f)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (s asciiString) toString() String {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s asciiString) ToString() Value {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s asciiString) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s asciiString) ToFloat() float64 {
|
||||
ss := strings.TrimSpace(string(s))
|
||||
if ss == "" {
|
||||
return 0
|
||||
}
|
||||
if ss == "Infinity" || ss == "+Infinity" {
|
||||
return math.Inf(1)
|
||||
}
|
||||
if ss == "-Infinity" {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
f, err := s._toFloat(ss)
|
||||
if err != nil {
|
||||
i, err := s._toInt(ss)
|
||||
if err == nil {
|
||||
return float64(i)
|
||||
}
|
||||
f = math.NaN()
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (s asciiString) ToBoolean() bool {
|
||||
return s != ""
|
||||
}
|
||||
|
||||
func (s asciiString) ToNumber() Value {
|
||||
ss := strings.TrimSpace(string(s))
|
||||
if ss == "" {
|
||||
return intToValue(0)
|
||||
}
|
||||
if ss == "Infinity" || ss == "+Infinity" {
|
||||
return _positiveInf
|
||||
}
|
||||
if ss == "-Infinity" {
|
||||
return _negativeInf
|
||||
}
|
||||
|
||||
if i, err := s._toInt(ss); err == nil {
|
||||
return intToValue(i)
|
||||
}
|
||||
|
||||
if f, err := s._toFloat(ss); err == nil {
|
||||
return floatToValue(f)
|
||||
}
|
||||
|
||||
return _NaN
|
||||
}
|
||||
|
||||
func (s asciiString) ToObject(r *Runtime) *Object {
|
||||
return r._newString(s, r.getStringPrototype())
|
||||
}
|
||||
|
||||
func (s asciiString) SameAs(other Value) bool {
|
||||
return s.StrictEquals(other)
|
||||
}
|
||||
|
||||
func (s asciiString) Equals(other Value) bool {
|
||||
if s.StrictEquals(other) {
|
||||
return true
|
||||
}
|
||||
|
||||
if o, ok := other.(valueInt); ok {
|
||||
if o1, e := s._toInt(strings.TrimSpace(string(s))); e == nil {
|
||||
return o1 == int64(o)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if o, ok := other.(valueFloat); ok {
|
||||
return s.ToFloat() == float64(o)
|
||||
}
|
||||
|
||||
if o, ok := other.(valueBool); ok {
|
||||
if o1, e := s._toFloat(strings.TrimSpace(string(s))); e == nil {
|
||||
return o1 == o.ToFloat()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if o, ok := other.(*valueBigInt); ok {
|
||||
bigInt, err := stringToBigInt(s.toTrimmedUTF8())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return bigInt.Cmp((*big.Int)(o)) == 0
|
||||
}
|
||||
|
||||
if o, ok := other.(*Object); ok {
|
||||
return s.Equals(o.toPrimitive())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s asciiString) StrictEquals(other Value) bool {
|
||||
if otherStr, ok := other.(asciiString); ok {
|
||||
return s == otherStr
|
||||
}
|
||||
if otherStr, ok := other.(*importedString); ok {
|
||||
if otherStr.u == nil {
|
||||
return string(s) == otherStr.s
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s asciiString) baseObject(r *Runtime) *Object {
|
||||
ss := r.getStringSingleton()
|
||||
ss.value = s
|
||||
ss.setLength()
|
||||
return ss.val
|
||||
}
|
||||
|
||||
func (s asciiString) hash(hash *maphash.Hash) uint64 {
|
||||
_, _ = hash.WriteString(string(s))
|
||||
h := hash.Sum64()
|
||||
hash.Reset()
|
||||
return h
|
||||
}
|
||||
|
||||
func (s asciiString) CharAt(idx int) uint16 {
|
||||
return uint16(s[idx])
|
||||
}
|
||||
|
||||
func (s asciiString) Length() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s asciiString) Concat(other String) String {
|
||||
a, u := devirtualizeString(other)
|
||||
if u != nil {
|
||||
b := make([]uint16, len(s)+len(u))
|
||||
b[0] = unistring.BOM
|
||||
for i := 0; i < len(s); i++ {
|
||||
b[i+1] = uint16(s[i])
|
||||
}
|
||||
copy(b[len(s)+1:], u[1:])
|
||||
return unicodeString(b)
|
||||
}
|
||||
return s + a
|
||||
}
|
||||
|
||||
func (s asciiString) Substring(start, end int) String {
|
||||
return s[start:end]
|
||||
}
|
||||
|
||||
func (s asciiString) CompareTo(other String) int {
|
||||
switch other := other.(type) {
|
||||
case asciiString:
|
||||
return strings.Compare(string(s), string(other))
|
||||
case unicodeString:
|
||||
return strings.Compare(string(s), other.String())
|
||||
case *importedString:
|
||||
return strings.Compare(string(s), other.s)
|
||||
default:
|
||||
panic(newTypeError("Internal bug: unknown string type: %T", other))
|
||||
}
|
||||
}
|
||||
|
||||
func (s asciiString) index(substr String, start int) int {
|
||||
a, u := devirtualizeString(substr)
|
||||
if u == nil {
|
||||
if start > len(s) {
|
||||
return -1
|
||||
}
|
||||
p := strings.Index(string(s[start:]), string(a))
|
||||
if p >= 0 {
|
||||
return p + start
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s asciiString) lastIndex(substr String, pos int) int {
|
||||
a, u := devirtualizeString(substr)
|
||||
if u == nil {
|
||||
end := pos + len(a)
|
||||
var ss string
|
||||
if end > len(s) {
|
||||
ss = string(s)
|
||||
} else {
|
||||
ss = string(s[:end])
|
||||
}
|
||||
return strings.LastIndex(ss, string(a))
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s asciiString) toLower() String {
|
||||
return asciiString(strings.ToLower(string(s)))
|
||||
}
|
||||
|
||||
func (s asciiString) toUpper() String {
|
||||
return asciiString(strings.ToUpper(string(s)))
|
||||
}
|
||||
|
||||
func (s asciiString) toTrimmedUTF8() string {
|
||||
return strings.TrimSpace(string(s))
|
||||
}
|
||||
|
||||
func (s asciiString) string() unistring.String {
|
||||
return unistring.String(s)
|
||||
}
|
||||
|
||||
func (s asciiString) Export() interface{} {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s asciiString) ExportType() reflect.Type {
|
||||
return reflectTypeString
|
||||
}
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Represents a string imported from Go. The idea is to delay the scanning for unicode characters and converting
|
||||
// to unicodeString until necessary. This way strings that are merely passed through never get scanned which
|
||||
// saves CPU and memory.
|
||||
// Currently, importedString is created in 2 cases: Runtime.ToValue() for strings longer than 16 bytes and as a result
|
||||
// of JSON.stringify() if it may contain unicode characters. More cases could be added in the future.
|
||||
type importedString struct {
|
||||
s string
|
||||
u unicodeString
|
||||
|
||||
scanned bool
|
||||
}
|
||||
|
||||
func (i *importedString) scan() {
|
||||
i.u = unistring.Scan(i.s)
|
||||
i.scanned = true
|
||||
}
|
||||
|
||||
func (i *importedString) ensureScanned() {
|
||||
if !i.scanned {
|
||||
i.scan()
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importedString) ToInteger() int64 {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return 0
|
||||
}
|
||||
return asciiString(i.s).ToInteger()
|
||||
}
|
||||
|
||||
func (i *importedString) toString() String {
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *importedString) string() unistring.String {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return unistring.FromUtf16(i.u)
|
||||
}
|
||||
return unistring.String(i.s)
|
||||
}
|
||||
|
||||
func (i *importedString) ToString() Value {
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *importedString) String() string {
|
||||
return i.s
|
||||
}
|
||||
|
||||
func (i *importedString) ToFloat() float64 {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return math.NaN()
|
||||
}
|
||||
return asciiString(i.s).ToFloat()
|
||||
}
|
||||
|
||||
func (i *importedString) ToNumber() Value {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.ToNumber()
|
||||
}
|
||||
return asciiString(i.s).ToNumber()
|
||||
}
|
||||
|
||||
func (i *importedString) ToBoolean() bool {
|
||||
return len(i.s) != 0
|
||||
}
|
||||
|
||||
func (i *importedString) ToObject(r *Runtime) *Object {
|
||||
return r._newString(i, r.getStringPrototype())
|
||||
}
|
||||
|
||||
func (i *importedString) SameAs(other Value) bool {
|
||||
return i.StrictEquals(other)
|
||||
}
|
||||
|
||||
func (i *importedString) Equals(other Value) bool {
|
||||
if i.StrictEquals(other) {
|
||||
return true
|
||||
}
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.Equals(other)
|
||||
}
|
||||
return asciiString(i.s).Equals(other)
|
||||
}
|
||||
|
||||
func (i *importedString) StrictEquals(other Value) bool {
|
||||
switch otherStr := other.(type) {
|
||||
case asciiString:
|
||||
if i.u != nil {
|
||||
return false
|
||||
}
|
||||
return i.s == string(otherStr)
|
||||
case unicodeString:
|
||||
i.ensureScanned()
|
||||
if i.u != nil && i.u.equals(otherStr) {
|
||||
return true
|
||||
}
|
||||
case *importedString:
|
||||
return i.s == otherStr.s
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *importedString) Export() interface{} {
|
||||
return i.s
|
||||
}
|
||||
|
||||
func (i *importedString) ExportType() reflect.Type {
|
||||
return reflectTypeString
|
||||
}
|
||||
|
||||
func (i *importedString) baseObject(r *Runtime) *Object {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.baseObject(r)
|
||||
}
|
||||
return asciiString(i.s).baseObject(r)
|
||||
}
|
||||
|
||||
func (i *importedString) hash(hasher *maphash.Hash) uint64 {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.hash(hasher)
|
||||
}
|
||||
return asciiString(i.s).hash(hasher)
|
||||
}
|
||||
|
||||
func (i *importedString) CharAt(idx int) uint16 {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.CharAt(idx)
|
||||
}
|
||||
return asciiString(i.s).CharAt(idx)
|
||||
}
|
||||
|
||||
func (i *importedString) Length() int {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.Length()
|
||||
}
|
||||
return asciiString(i.s).Length()
|
||||
}
|
||||
|
||||
func (i *importedString) Concat(v String) String {
|
||||
if !i.scanned {
|
||||
if v, ok := v.(*importedString); ok {
|
||||
if !v.scanned {
|
||||
return &importedString{s: i.s + v.s}
|
||||
}
|
||||
}
|
||||
i.ensureScanned()
|
||||
}
|
||||
if i.u != nil {
|
||||
return i.u.Concat(v)
|
||||
}
|
||||
return asciiString(i.s).Concat(v)
|
||||
}
|
||||
|
||||
func (i *importedString) Substring(start, end int) String {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.Substring(start, end)
|
||||
}
|
||||
return asciiString(i.s).Substring(start, end)
|
||||
}
|
||||
|
||||
func (i *importedString) CompareTo(v String) int {
|
||||
return strings.Compare(i.s, v.String())
|
||||
}
|
||||
|
||||
func (i *importedString) Reader() io.RuneReader {
|
||||
if i.scanned {
|
||||
if i.u != nil {
|
||||
return i.u.Reader()
|
||||
}
|
||||
return asciiString(i.s).Reader()
|
||||
}
|
||||
return strings.NewReader(i.s)
|
||||
}
|
||||
|
||||
type stringUtf16Reader struct {
|
||||
s string
|
||||
pos int
|
||||
second uint16
|
||||
}
|
||||
|
||||
func (s *stringUtf16Reader) readChar() (c uint16, err error) {
|
||||
if s.second != 0 {
|
||||
c, s.second = s.second, 0
|
||||
return
|
||||
}
|
||||
if s.pos < len(s.s) {
|
||||
r1, size1 := utf8.DecodeRuneInString(s.s[s.pos:])
|
||||
s.pos += size1
|
||||
if r1 <= 0xFFFF {
|
||||
c = uint16(r1)
|
||||
} else {
|
||||
first, second := utf16.EncodeRune(r1)
|
||||
c, s.second = uint16(first), uint16(second)
|
||||
}
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stringUtf16Reader) ReadRune() (r rune, size int, err error) {
|
||||
c, err := s.readChar()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = rune(c)
|
||||
size = 1
|
||||
return
|
||||
}
|
||||
|
||||
func (i *importedString) utf16Reader() utf16Reader {
|
||||
if i.scanned {
|
||||
if i.u != nil {
|
||||
return i.u.utf16Reader()
|
||||
}
|
||||
return asciiString(i.s).utf16Reader()
|
||||
}
|
||||
return &stringUtf16Reader{
|
||||
s: i.s,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importedString) utf16RuneReader() io.RuneReader {
|
||||
if i.scanned {
|
||||
if i.u != nil {
|
||||
return i.u.utf16RuneReader()
|
||||
}
|
||||
return asciiString(i.s).utf16RuneReader()
|
||||
}
|
||||
return &stringUtf16Reader{
|
||||
s: i.s,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importedString) utf16Runes() []rune {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.utf16Runes()
|
||||
}
|
||||
return asciiString(i.s).utf16Runes()
|
||||
}
|
||||
|
||||
func (i *importedString) index(v String, start int) int {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.index(v, start)
|
||||
}
|
||||
return asciiString(i.s).index(v, start)
|
||||
}
|
||||
|
||||
func (i *importedString) lastIndex(v String, pos int) int {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return i.u.lastIndex(v, pos)
|
||||
}
|
||||
return asciiString(i.s).lastIndex(v, pos)
|
||||
}
|
||||
|
||||
func (i *importedString) toLower() String {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
return toLower(i.s)
|
||||
}
|
||||
return asciiString(i.s).toLower()
|
||||
}
|
||||
|
||||
func (i *importedString) toUpper() String {
|
||||
i.ensureScanned()
|
||||
if i.u != nil {
|
||||
caser := cases.Upper(language.Und)
|
||||
return newStringValue(caser.String(i.s))
|
||||
}
|
||||
return asciiString(i.s).toUpper()
|
||||
}
|
||||
|
||||
func (i *importedString) toTrimmedUTF8() string {
|
||||
return strings.Trim(i.s, parser.WhitespaceChars)
|
||||
}
|
||||
+615
@@ -0,0 +1,615 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"hash/maphash"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dop251/goja/parser"
|
||||
"github.com/dop251/goja/unistring"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type unicodeString []uint16
|
||||
|
||||
type unicodeRuneReader struct {
|
||||
s unicodeString
|
||||
pos int
|
||||
}
|
||||
|
||||
type utf16RuneReader struct {
|
||||
s unicodeString
|
||||
pos int
|
||||
}
|
||||
|
||||
// passes through invalid surrogate pairs
|
||||
type lenientUtf16Decoder struct {
|
||||
utf16Reader utf16Reader
|
||||
prev uint16
|
||||
prevSet bool
|
||||
}
|
||||
|
||||
// StringBuilder serves similar purpose to strings.Builder, except it works with ECMAScript String.
|
||||
// Use it to efficiently build 'native' ECMAScript values that either contain invalid UTF-16 surrogate pairs
|
||||
// (and therefore cannot be represented as UTF-8) or never expected to be exported to Go. See also
|
||||
// StringFromUTF16.
|
||||
type StringBuilder struct {
|
||||
asciiBuilder strings.Builder
|
||||
unicodeBuilder unicodeStringBuilder
|
||||
}
|
||||
|
||||
type unicodeStringBuilder struct {
|
||||
buf []uint16
|
||||
unicode bool
|
||||
}
|
||||
|
||||
var (
|
||||
InvalidRuneError = errors.New("invalid rune")
|
||||
)
|
||||
|
||||
func (rr *utf16RuneReader) readChar() (c uint16, err error) {
|
||||
if rr.pos < len(rr.s) {
|
||||
c = rr.s[rr.pos]
|
||||
rr.pos++
|
||||
return
|
||||
}
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
func (rr *utf16RuneReader) ReadRune() (r rune, size int, err error) {
|
||||
if rr.pos < len(rr.s) {
|
||||
r = rune(rr.s[rr.pos])
|
||||
rr.pos++
|
||||
size = 1
|
||||
return
|
||||
}
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
func (rr *lenientUtf16Decoder) ReadRune() (r rune, size int, err error) {
|
||||
var c uint16
|
||||
if rr.prevSet {
|
||||
c = rr.prev
|
||||
rr.prevSet = false
|
||||
} else {
|
||||
c, err = rr.utf16Reader.readChar()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
size = 1
|
||||
if isUTF16FirstSurrogate(c) {
|
||||
second, err1 := rr.utf16Reader.readChar()
|
||||
if err1 != nil {
|
||||
if err1 != io.EOF {
|
||||
err = err1
|
||||
} else {
|
||||
r = rune(c)
|
||||
}
|
||||
return
|
||||
}
|
||||
if isUTF16SecondSurrogate(second) {
|
||||
r = utf16.DecodeRune(rune(c), rune(second))
|
||||
size++
|
||||
return
|
||||
} else {
|
||||
rr.prev = second
|
||||
rr.prevSet = true
|
||||
}
|
||||
}
|
||||
r = rune(c)
|
||||
return
|
||||
}
|
||||
|
||||
func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) {
|
||||
if rr.pos < len(rr.s) {
|
||||
c := rr.s[rr.pos]
|
||||
size++
|
||||
rr.pos++
|
||||
if isUTF16FirstSurrogate(c) {
|
||||
if rr.pos < len(rr.s) {
|
||||
second := rr.s[rr.pos]
|
||||
if isUTF16SecondSurrogate(second) {
|
||||
r = utf16.DecodeRune(rune(c), rune(second))
|
||||
size++
|
||||
rr.pos++
|
||||
return
|
||||
}
|
||||
}
|
||||
err = InvalidRuneError
|
||||
} else if isUTF16SecondSurrogate(c) {
|
||||
err = InvalidRuneError
|
||||
}
|
||||
r = rune(c)
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *unicodeStringBuilder) Grow(n int) {
|
||||
if len(b.buf) == 0 {
|
||||
n++
|
||||
}
|
||||
if cap(b.buf)-len(b.buf) < n {
|
||||
buf := make([]uint16, len(b.buf), 2*cap(b.buf)+n)
|
||||
copy(buf, b.buf)
|
||||
b.buf = buf
|
||||
}
|
||||
if len(b.buf) == 0 {
|
||||
b.buf = append(b.buf, unistring.BOM)
|
||||
}
|
||||
}
|
||||
|
||||
// assumes already started
|
||||
func (b *unicodeStringBuilder) writeString(s String) {
|
||||
a, u := devirtualizeString(s)
|
||||
if u != nil {
|
||||
b.buf = append(b.buf, u[1:]...)
|
||||
b.unicode = true
|
||||
} else {
|
||||
for i := 0; i < len(a); i++ {
|
||||
b.buf = append(b.buf, uint16(a[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *unicodeStringBuilder) String() String {
|
||||
if b.unicode {
|
||||
return unicodeString(b.buf)
|
||||
}
|
||||
if len(b.buf) < 2 {
|
||||
return stringEmpty
|
||||
}
|
||||
buf := make([]byte, 0, len(b.buf)-1)
|
||||
for _, c := range b.buf[1:] {
|
||||
buf = append(buf, byte(c))
|
||||
}
|
||||
return asciiString(buf)
|
||||
}
|
||||
|
||||
func (b *unicodeStringBuilder) WriteRune(r rune) {
|
||||
b.Grow(2)
|
||||
b.writeRuneFast(r)
|
||||
}
|
||||
|
||||
// assumes already started
|
||||
func (b *unicodeStringBuilder) writeRuneFast(r rune) {
|
||||
if r <= 0xFFFF {
|
||||
b.buf = append(b.buf, uint16(r))
|
||||
if !b.unicode && r >= utf8.RuneSelf {
|
||||
b.unicode = true
|
||||
}
|
||||
} else {
|
||||
first, second := utf16.EncodeRune(r)
|
||||
b.buf = append(b.buf, uint16(first), uint16(second))
|
||||
b.unicode = true
|
||||
}
|
||||
}
|
||||
|
||||
func (b *unicodeStringBuilder) writeASCIIString(bytes string) {
|
||||
for _, c := range bytes {
|
||||
b.buf = append(b.buf, uint16(c))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *unicodeStringBuilder) writeUnicodeString(str unicodeString) {
|
||||
b.buf = append(b.buf, str[1:]...)
|
||||
b.unicode = true
|
||||
}
|
||||
|
||||
func (b *StringBuilder) ascii() bool {
|
||||
return len(b.unicodeBuilder.buf) == 0
|
||||
}
|
||||
|
||||
func (b *StringBuilder) WriteString(s String) {
|
||||
a, u := devirtualizeString(s)
|
||||
if u != nil {
|
||||
b.switchToUnicode(u.Length())
|
||||
b.unicodeBuilder.writeUnicodeString(u)
|
||||
} else {
|
||||
if b.ascii() {
|
||||
b.asciiBuilder.WriteString(string(a))
|
||||
} else {
|
||||
b.unicodeBuilder.writeASCIIString(string(a))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *StringBuilder) WriteUTF8String(s string) {
|
||||
firstUnicodeIdx := 0
|
||||
if b.ascii() {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
b.switchToUnicode(len(s))
|
||||
b.unicodeBuilder.writeASCIIString(s[:i])
|
||||
firstUnicodeIdx = i
|
||||
goto unicode
|
||||
}
|
||||
}
|
||||
b.asciiBuilder.WriteString(s)
|
||||
return
|
||||
}
|
||||
unicode:
|
||||
for _, r := range s[firstUnicodeIdx:] {
|
||||
b.unicodeBuilder.writeRuneFast(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *StringBuilder) writeASCII(s string) {
|
||||
if b.ascii() {
|
||||
b.asciiBuilder.WriteString(s)
|
||||
} else {
|
||||
b.unicodeBuilder.writeASCIIString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *StringBuilder) WriteRune(r rune) {
|
||||
if r < utf8.RuneSelf {
|
||||
if b.ascii() {
|
||||
b.asciiBuilder.WriteByte(byte(r))
|
||||
} else {
|
||||
b.unicodeBuilder.writeRuneFast(r)
|
||||
}
|
||||
} else {
|
||||
var extraLen int
|
||||
if r <= 0xFFFF {
|
||||
extraLen = 1
|
||||
} else {
|
||||
extraLen = 2
|
||||
}
|
||||
b.switchToUnicode(extraLen)
|
||||
b.unicodeBuilder.writeRuneFast(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *StringBuilder) String() String {
|
||||
if b.ascii() {
|
||||
return asciiString(b.asciiBuilder.String())
|
||||
}
|
||||
return b.unicodeBuilder.String()
|
||||
}
|
||||
|
||||
func (b *StringBuilder) Grow(n int) {
|
||||
if b.ascii() {
|
||||
b.asciiBuilder.Grow(n)
|
||||
} else {
|
||||
b.unicodeBuilder.Grow(n)
|
||||
}
|
||||
}
|
||||
|
||||
// LikelyUnicode hints to the builder that the resulting string is likely to contain Unicode (non-ASCII) characters.
|
||||
// The argument is an extra capacity (in characters) to reserve on top of the current length (it's like calling
|
||||
// Grow() afterwards).
|
||||
// This method may be called at any point (not just when the buffer is empty), although for efficiency it should
|
||||
// be called as early as possible.
|
||||
func (b *StringBuilder) LikelyUnicode(extraLen int) {
|
||||
b.switchToUnicode(extraLen)
|
||||
}
|
||||
|
||||
func (b *StringBuilder) switchToUnicode(extraLen int) {
|
||||
if b.ascii() {
|
||||
c := b.asciiBuilder.Cap()
|
||||
newCap := b.asciiBuilder.Len() + extraLen
|
||||
if newCap < c {
|
||||
newCap = c
|
||||
}
|
||||
b.unicodeBuilder.Grow(newCap)
|
||||
b.unicodeBuilder.writeASCIIString(b.asciiBuilder.String())
|
||||
b.asciiBuilder.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *StringBuilder) WriteSubstring(source String, start int, end int) {
|
||||
a, us := devirtualizeString(source)
|
||||
if us == nil {
|
||||
if b.ascii() {
|
||||
b.asciiBuilder.WriteString(string(a[start:end]))
|
||||
} else {
|
||||
b.unicodeBuilder.writeASCIIString(string(a[start:end]))
|
||||
}
|
||||
return
|
||||
}
|
||||
if b.ascii() {
|
||||
uc := false
|
||||
for i := start; i < end; i++ {
|
||||
if us.CharAt(i) >= utf8.RuneSelf {
|
||||
uc = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if uc {
|
||||
b.switchToUnicode(end - start + 1)
|
||||
} else {
|
||||
b.asciiBuilder.Grow(end - start + 1)
|
||||
for i := start; i < end; i++ {
|
||||
b.asciiBuilder.WriteByte(byte(us.CharAt(i)))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
b.unicodeBuilder.buf = append(b.unicodeBuilder.buf, us[start+1:end+1]...)
|
||||
b.unicodeBuilder.unicode = true
|
||||
}
|
||||
|
||||
func (s unicodeString) Reader() io.RuneReader {
|
||||
return &unicodeRuneReader{
|
||||
s: s[1:],
|
||||
}
|
||||
}
|
||||
|
||||
func (s unicodeString) utf16Reader() utf16Reader {
|
||||
return &utf16RuneReader{
|
||||
s: s[1:],
|
||||
}
|
||||
}
|
||||
|
||||
func (s unicodeString) utf16RuneReader() io.RuneReader {
|
||||
return &utf16RuneReader{
|
||||
s: s[1:],
|
||||
}
|
||||
}
|
||||
|
||||
func (s unicodeString) utf16Runes() []rune {
|
||||
runes := make([]rune, len(s)-1)
|
||||
for i, ch := range s[1:] {
|
||||
runes[i] = rune(ch)
|
||||
}
|
||||
return runes
|
||||
}
|
||||
|
||||
func (s unicodeString) ToInteger() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s unicodeString) toString() String {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s unicodeString) ToString() Value {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s unicodeString) ToFloat() float64 {
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
func (s unicodeString) ToBoolean() bool {
|
||||
return len(s) > 0
|
||||
}
|
||||
|
||||
func (s unicodeString) toTrimmedUTF8() string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Trim(s.String(), parser.WhitespaceChars)
|
||||
}
|
||||
|
||||
func (s unicodeString) ToNumber() Value {
|
||||
return asciiString(s.toTrimmedUTF8()).ToNumber()
|
||||
}
|
||||
|
||||
func (s unicodeString) ToObject(r *Runtime) *Object {
|
||||
return r._newString(s, r.getStringPrototype())
|
||||
}
|
||||
|
||||
func (s unicodeString) equals(other unicodeString) bool {
|
||||
if len(s) != len(other) {
|
||||
return false
|
||||
}
|
||||
for i, r := range s {
|
||||
if r != other[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s unicodeString) SameAs(other Value) bool {
|
||||
return s.StrictEquals(other)
|
||||
}
|
||||
|
||||
func (s unicodeString) Equals(other Value) bool {
|
||||
if s.StrictEquals(other) {
|
||||
return true
|
||||
}
|
||||
|
||||
if o, ok := other.(*Object); ok {
|
||||
return s.Equals(o.toPrimitive())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s unicodeString) StrictEquals(other Value) bool {
|
||||
if otherStr, ok := other.(unicodeString); ok {
|
||||
return s.equals(otherStr)
|
||||
}
|
||||
if otherStr, ok := other.(*importedString); ok {
|
||||
otherStr.ensureScanned()
|
||||
if otherStr.u != nil {
|
||||
return s.equals(otherStr.u)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s unicodeString) baseObject(r *Runtime) *Object {
|
||||
ss := r.getStringSingleton()
|
||||
ss.value = s
|
||||
ss.setLength()
|
||||
return ss.val
|
||||
}
|
||||
|
||||
func (s unicodeString) CharAt(idx int) uint16 {
|
||||
return s[idx+1]
|
||||
}
|
||||
|
||||
func (s unicodeString) Length() int {
|
||||
return len(s) - 1
|
||||
}
|
||||
|
||||
func (s unicodeString) Concat(other String) String {
|
||||
a, u := devirtualizeString(other)
|
||||
if u != nil {
|
||||
b := make(unicodeString, len(s)+len(u)-1)
|
||||
copy(b, s)
|
||||
copy(b[len(s):], u[1:])
|
||||
return b
|
||||
}
|
||||
b := make([]uint16, len(s)+len(a))
|
||||
copy(b, s)
|
||||
b1 := b[len(s):]
|
||||
for i := 0; i < len(a); i++ {
|
||||
b1[i] = uint16(a[i])
|
||||
}
|
||||
return unicodeString(b)
|
||||
}
|
||||
|
||||
func (s unicodeString) Substring(start, end int) String {
|
||||
ss := s[start+1 : end+1]
|
||||
for _, c := range ss {
|
||||
if c >= utf8.RuneSelf {
|
||||
b := make(unicodeString, end-start+1)
|
||||
b[0] = unistring.BOM
|
||||
copy(b[1:], ss)
|
||||
return b
|
||||
}
|
||||
}
|
||||
as := make([]byte, end-start)
|
||||
for i, c := range ss {
|
||||
as[i] = byte(c)
|
||||
}
|
||||
return asciiString(as)
|
||||
}
|
||||
|
||||
func (s unicodeString) String() string {
|
||||
return string(utf16.Decode(s[1:]))
|
||||
}
|
||||
|
||||
func (s unicodeString) CompareTo(other String) int {
|
||||
// TODO handle invalid UTF-16
|
||||
return strings.Compare(s.String(), other.String())
|
||||
}
|
||||
|
||||
func (s unicodeString) index(substr String, start int) int {
|
||||
var ss []uint16
|
||||
a, u := devirtualizeString(substr)
|
||||
if u != nil {
|
||||
ss = u[1:]
|
||||
} else {
|
||||
ss = make([]uint16, len(a))
|
||||
for i := 0; i < len(a); i++ {
|
||||
ss[i] = uint16(a[i])
|
||||
}
|
||||
}
|
||||
s1 := s[1:]
|
||||
// TODO: optimise
|
||||
end := len(s1) - len(ss)
|
||||
for start <= end {
|
||||
for i := 0; i < len(ss); i++ {
|
||||
if s1[start+i] != ss[i] {
|
||||
goto nomatch
|
||||
}
|
||||
}
|
||||
|
||||
return start
|
||||
nomatch:
|
||||
start++
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s unicodeString) lastIndex(substr String, start int) int {
|
||||
var ss []uint16
|
||||
a, u := devirtualizeString(substr)
|
||||
if u != nil {
|
||||
ss = u[1:]
|
||||
} else {
|
||||
ss = make([]uint16, len(a))
|
||||
for i := 0; i < len(a); i++ {
|
||||
ss[i] = uint16(a[i])
|
||||
}
|
||||
}
|
||||
|
||||
s1 := s[1:]
|
||||
if maxStart := len(s1) - len(ss); start > maxStart {
|
||||
start = maxStart
|
||||
}
|
||||
// TODO: optimise
|
||||
for start >= 0 {
|
||||
for i := 0; i < len(ss); i++ {
|
||||
if s1[start+i] != ss[i] {
|
||||
goto nomatch
|
||||
}
|
||||
}
|
||||
|
||||
return start
|
||||
nomatch:
|
||||
start--
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func unicodeStringFromRunes(r []rune) unicodeString {
|
||||
return unistring.NewFromRunes(r).AsUtf16()
|
||||
}
|
||||
|
||||
func toLower(s string) String {
|
||||
caser := cases.Lower(language.Und)
|
||||
r := []rune(caser.String(s))
|
||||
// Workaround
|
||||
ascii := true
|
||||
for i := 0; i < len(r)-1; i++ {
|
||||
if (i == 0 || r[i-1] != 0x3b1) && r[i] == 0x345 && r[i+1] == 0x3c2 {
|
||||
i++
|
||||
r[i] = 0x3c3
|
||||
}
|
||||
if r[i] >= utf8.RuneSelf {
|
||||
ascii = false
|
||||
}
|
||||
}
|
||||
if ascii {
|
||||
ascii = r[len(r)-1] < utf8.RuneSelf
|
||||
}
|
||||
if ascii {
|
||||
return asciiString(r)
|
||||
}
|
||||
return unicodeStringFromRunes(r)
|
||||
}
|
||||
|
||||
func (s unicodeString) toLower() String {
|
||||
return toLower(s.String())
|
||||
}
|
||||
|
||||
func (s unicodeString) toUpper() String {
|
||||
caser := cases.Upper(language.Und)
|
||||
return newStringValue(caser.String(s.String()))
|
||||
}
|
||||
|
||||
func (s unicodeString) Export() interface{} {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s unicodeString) ExportType() reflect.Type {
|
||||
return reflectTypeString
|
||||
}
|
||||
|
||||
func (s unicodeString) hash(hash *maphash.Hash) uint64 {
|
||||
_, _ = hash.WriteString(string(unistring.FromUtf16(s)))
|
||||
h := hash.Sum64()
|
||||
hash.Reset()
|
||||
return h
|
||||
}
|
||||
|
||||
func (s unicodeString) string() unistring.String {
|
||||
return unistring.FromUtf16(s)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
token_const.go: tokenfmt
|
||||
./$^ | gofmt > $@
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user