update vendor

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2026-05-24 09:50:14 +02:00
parent 9bd048e4bf
commit f5e6f53d75
307 changed files with 238920 additions and 636 deletions
+27
View File
@@ -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
View File
@@ -0,0 +1,7 @@
language: go
arch:
- AMD64
- ppc64le
go:
- 1.9
- tip
+133
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+865
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+896
View File
@@ -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
View File
@@ -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
View File
@@ -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 &regexNode{
t: t,
options: opt,
}
}
func newRegexNodeCh(t nodeType, opt RegexOptions, ch rune) *regexNode {
return &regexNode{
t: t,
options: opt,
ch: ch,
}
}
func newRegexNodeStr(t nodeType, opt RegexOptions, str []rune) *regexNode {
return &regexNode{
t: t,
options: opt,
str: str,
}
}
func newRegexNodeSet(t nodeType, opt RegexOptions, set *CharSet) *regexNode {
return &regexNode{
t: t,
options: opt,
set: set,
}
}
func newRegexNodeM(t nodeType, opt RegexOptions, m int) *regexNode {
return &regexNode{
t: t,
options: opt,
m: m,
}
}
func newRegexNodeMN(t nodeType, opt RegexOptions, m, n int) *regexNode {
return &regexNode{
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
View File
@@ -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++
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
.idea
*.iml
testdata/test262
+11
View File
@@ -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
View File
@@ -0,0 +1,15 @@
Copyright (c) 2016 Dmitry Panov
Copyright (c) 2012 Robert Krimen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+335
View File
@@ -0,0 +1,335 @@
goja
====
ECMAScript 5.1(+) implementation in Go.
[![Go Reference](https://pkg.go.dev/badge/github.com/dop251/goja.svg)](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
View File
@@ -0,0 +1,565 @@
package goja
import (
"fmt"
"math"
"math/bits"
"reflect"
"strconv"
"github.com/dop251/goja/unistring"
)
type arrayIterObject struct {
baseObject
obj *Object
nextIdx int64
kind iterationKind
}
func (ai *arrayIterObject) next() Value {
if ai.obj == nil {
return ai.val.runtime.createIterResultObject(_undefined, true)
}
if ta, ok := ai.obj.self.(*typedArrayObject); ok {
ta.viewedArrayBuf.ensureNotDetached(true)
}
l := toLength(ai.obj.self.getStr("length", nil))
index := ai.nextIdx
if index >= l {
ai.obj = nil
return ai.val.runtime.createIterResultObject(_undefined, true)
}
ai.nextIdx++
idxVal := valueInt(index)
if ai.kind == iterationKindKey {
return ai.val.runtime.createIterResultObject(idxVal, false)
}
elementValue := nilSafe(ai.obj.self.getIdx(idxVal, nil))
var result Value
if ai.kind == iterationKindValue {
result = elementValue
} else {
result = ai.val.runtime.newArrayValues([]Value{idxVal, elementValue})
}
return ai.val.runtime.createIterResultObject(result, false)
}
func (r *Runtime) createArrayIterator(iterObj *Object, kind iterationKind) Value {
o := &Object{runtime: r}
ai := &arrayIterObject{
obj: iterObj,
kind: kind,
}
ai.class = classObject
ai.val = o
ai.extensible = true
o.self = ai
ai.prototype = r.getArrayIteratorPrototype()
ai.init()
return o
}
type arrayObject struct {
baseObject
values []Value
length uint32
objCount int
propValueCount int
lengthProp valueProperty
}
func (a *arrayObject) init() {
a.baseObject.init()
a.lengthProp.writable = true
a._put("length", &a.lengthProp)
}
func (a *arrayObject) _setLengthInt(l uint32, throw bool) bool {
ret := true
if l <= a.length {
if a.propValueCount > 0 {
// Slow path
for i := len(a.values) - 1; i >= int(l); i-- {
if prop, ok := a.values[i].(*valueProperty); ok {
if !prop.configurable {
l = uint32(i) + 1
ret = false
break
}
a.propValueCount--
}
}
}
}
if l <= uint32(len(a.values)) {
if l >= 16 && l < uint32(cap(a.values))>>2 {
ar := make([]Value, l)
copy(ar, a.values)
a.values = ar
} else {
ar := a.values[l:len(a.values)]
for i := range ar {
ar[i] = nil
}
a.values = a.values[:l]
}
}
a.length = l
if !ret {
a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length")
}
return ret
}
func (a *arrayObject) setLengthInt(l uint32, throw bool) bool {
if l == a.length {
return true
}
if !a.lengthProp.writable {
a.val.runtime.typeErrorResult(throw, "length is not writable")
return false
}
return a._setLengthInt(l, throw)
}
func (a *arrayObject) setLength(v uint32, throw bool) bool {
if !a.lengthProp.writable {
a.val.runtime.typeErrorResult(throw, "length is not writable")
return false
}
return a._setLengthInt(v, throw)
}
func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value {
prop := a.getOwnPropIdx(idx)
if prop == nil {
if a.prototype != nil {
if receiver == nil {
return a.prototype.self.getIdx(idx, a.val)
}
return a.prototype.self.getIdx(idx, receiver)
}
}
if prop, ok := prop.(*valueProperty); ok {
if receiver == nil {
return prop.get(a.val)
}
return prop.get(receiver)
}
return prop
}
func (a *arrayObject) getOwnPropStr(name unistring.String) Value {
if len(a.values) > 0 {
if i := strToArrayIdx(name); i != math.MaxUint32 {
if i < uint32(len(a.values)) {
return a.values[i]
}
}
}
if name == "length" {
return a.getLengthProp()
}
return a.baseObject.getOwnPropStr(name)
}
func (a *arrayObject) getOwnPropIdx(idx valueInt) Value {
if i := toIdx(idx); i != math.MaxUint32 {
if i < uint32(len(a.values)) {
return a.values[i]
}
return nil
}
return a.baseObject.getOwnPropStr(idx.string())
}
func (a *arrayObject) sortLen() int {
return len(a.values)
}
func (a *arrayObject) sortGet(i int) Value {
v := a.values[i]
if p, ok := v.(*valueProperty); ok {
v = p.get(a.val)
}
return v
}
func (a *arrayObject) swap(i int, j int) {
a.values[i], a.values[j] = a.values[j], a.values[i]
}
func (a *arrayObject) getStr(name unistring.String, receiver Value) Value {
return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
}
func (a *arrayObject) getLengthProp() *valueProperty {
a.lengthProp.value = intToValue(int64(a.length))
return &a.lengthProp
}
func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
if i := toIdx(idx); i != math.MaxUint32 {
return a._setOwnIdx(i, val, throw)
} else {
return a.baseObject.setOwnStr(idx.string(), val, throw)
}
}
func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
var prop Value
if idx < uint32(len(a.values)) {
prop = a.values[idx]
}
if prop == nil {
if proto := a.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
return res
}
}
// new property
if !a.extensible {
a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
return false
} else {
if idx >= a.length {
if !a.setLengthInt(idx+1, throw) {
return false
}
}
if idx >= uint32(len(a.values)) {
if !a.expand(idx) {
a.val.self.(*sparseArrayObject).add(idx, val)
return true
}
}
a.objCount++
}
} else {
if prop, ok := prop.(*valueProperty); ok {
if !prop.isWritable() {
a.val.runtime.typeErrorResult(throw)
return false
}
prop.set(a.val, val)
return true
}
}
a.values[idx] = val
return true
}
func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._setOwnIdx(idx, val, throw)
} else {
if name == "length" {
return a.setLength(a.val.runtime.toLengthUint32(val), throw)
} else {
return a.baseObject.setOwnStr(name, val, throw)
}
}
}
func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw)
}
func (a *arrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
}
type arrayPropIter struct {
a *arrayObject
limit int
idx int
}
func (i *arrayPropIter) next() (propIterItem, iterNextFunc) {
for i.idx < len(i.a.values) && i.idx < i.limit {
name := asciiString(strconv.Itoa(i.idx))
prop := i.a.values[i.idx]
i.idx++
if prop != nil {
return propIterItem{name: name, value: prop}, i.next
}
}
return i.a.baseObject.iterateStringKeys()()
}
func (a *arrayObject) iterateStringKeys() iterNextFunc {
return (&arrayPropIter{
a: a,
limit: len(a.values),
}).next
}
func (a *arrayObject) stringKeys(all bool, accum []Value) []Value {
for i, prop := range a.values {
name := strconv.Itoa(i)
if prop != nil {
if !all {
if prop, ok := prop.(*valueProperty); ok && !prop.enumerable {
continue
}
}
accum = append(accum, asciiString(name))
}
}
return a.baseObject.stringKeys(all, accum)
}
func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return idx < uint32(len(a.values)) && a.values[idx] != nil
} else {
return a.baseObject.hasOwnPropertyStr(name)
}
}
func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
return idx < uint32(len(a.values)) && a.values[idx] != nil
}
return a.baseObject.hasOwnPropertyStr(idx.string())
}
func (a *arrayObject) hasPropertyIdx(idx valueInt) bool {
if a.hasOwnPropertyIdx(idx) {
return true
}
if a.prototype != nil {
return a.prototype.self.hasPropertyIdx(idx)
}
return false
}
func (a *arrayObject) expand(idx uint32) bool {
targetLen := idx + 1
if targetLen > uint32(len(a.values)) {
if targetLen < uint32(cap(a.values)) {
a.values = a.values[:targetLen]
} else {
if idx > 4096 && (a.objCount == 0 || idx/uint32(a.objCount) > 10) {
//log.Println("Switching standard->sparse")
sa := &sparseArrayObject{
baseObject: a.baseObject,
length: a.length,
propValueCount: a.propValueCount,
}
sa.setValues(a.values, a.objCount+1)
sa.val.self = sa
sa.lengthProp.writable = a.lengthProp.writable
sa._put("length", &sa.lengthProp)
return false
} else {
if bits.UintSize == 32 {
if targetLen >= math.MaxInt32 {
panic(a.val.runtime.NewTypeError("Array index overflows int"))
}
}
tl := int(targetLen)
newValues := make([]Value, tl, growCap(tl, len(a.values), cap(a.values)))
copy(newValues, a.values)
a.values = newValues
}
}
}
return true
}
func (r *Runtime) defineArrayLength(prop *valueProperty, descr PropertyDescriptor, setter func(uint32, bool) bool, throw bool) bool {
var newLen uint32
ret := true
if descr.Value != nil {
newLen = r.toLengthUint32(descr.Value)
}
if descr.Configurable == FLAG_TRUE || descr.Enumerable == FLAG_TRUE || descr.Getter != nil || descr.Setter != nil {
ret = false
goto Reject
}
if descr.Value != nil {
oldLen := uint32(prop.value.ToInteger())
if oldLen != newLen {
ret = setter(newLen, false)
}
} else {
ret = true
}
if descr.Writable != FLAG_NOT_SET {
w := descr.Writable.Bool()
if prop.writable {
prop.writable = w
} else {
if w {
ret = false
goto Reject
}
}
}
Reject:
if !ret {
r.typeErrorResult(throw, "Cannot redefine property: length")
}
return ret
}
func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
var existing Value
if idx < uint32(len(a.values)) {
existing = a.values[idx]
}
prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
if ok {
if idx >= a.length {
if !a.setLengthInt(idx+1, throw) {
return false
}
}
if a.expand(idx) {
a.values[idx] = prop
a.objCount++
if _, ok := prop.(*valueProperty); ok {
a.propValueCount++
}
} else {
a.val.self.(*sparseArrayObject).add(idx, prop)
}
}
return ok
}
func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._defineIdxProperty(idx, descr, throw)
}
if name == "length" {
return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw)
}
return a.baseObject.defineOwnPropertyStr(name, descr, throw)
}
func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
return a._defineIdxProperty(idx, descr, throw)
}
return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
}
func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
if idx < uint32(len(a.values)) {
if v := a.values[idx]; v != nil {
if p, ok := v.(*valueProperty); ok {
if !p.configurable {
a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.toString())
return false
}
a.propValueCount--
}
a.values[idx] = nil
a.objCount--
}
}
return true
}
func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._deleteIdxProp(idx, throw)
}
return a.baseObject.deleteStr(name, throw)
}
func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
return a._deleteIdxProp(idx, throw)
}
return a.baseObject.deleteStr(idx.string(), throw)
}
func (a *arrayObject) export(ctx *objectExportCtx) interface{} {
if v, exists := ctx.get(a.val); exists {
return v
}
arr := make([]interface{}, a.length)
ctx.put(a.val, arr)
if a.propValueCount == 0 && a.length == uint32(len(a.values)) && uint32(a.objCount) == a.length {
for i, v := range a.values {
if v != nil {
arr[i] = exportValue(v, ctx)
}
}
} else {
for i := uint32(0); i < a.length; i++ {
v := a.getIdx(valueInt(i), nil)
if v != nil {
arr[i] = exportValue(v, ctx)
}
}
}
return arr
}
func (a *arrayObject) exportType() reflect.Type {
return reflectTypeArray
}
func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
r := a.val.runtime
if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
l := toIntStrict(int64(a.length))
if typ.Kind() == reflect.Array {
if dst.Len() != l {
return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
}
} else {
dst.Set(reflect.MakeSlice(typ, l, l))
}
ctx.putTyped(a.val, typ, dst.Interface())
for i := 0; i < l; i++ {
if i >= len(a.values) {
break
}
val := a.values[i]
if p, ok := val.(*valueProperty); ok {
val = p.get(a.val)
}
err := r.toReflectValue(val, dst.Index(i), ctx)
if err != nil {
return fmt.Errorf("could not convert array element %v to %v at %d: %w", val, typ, i, err)
}
}
return nil
}
return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
}
func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) {
a.values = make([]Value, newMaxIdx+1)
for _, item := range items {
a.values[item.idx] = item.value
}
a.objCount = len(items)
}
func toIdx(v valueInt) uint32 {
if v >= 0 && v < math.MaxUint32 {
return uint32(v)
}
return math.MaxUint32
}
+500
View File
@@ -0,0 +1,500 @@
package goja
import (
"fmt"
"math"
"math/bits"
"reflect"
"sort"
"strconv"
"github.com/dop251/goja/unistring"
)
type sparseArrayItem struct {
idx uint32
value Value
}
type sparseArrayObject struct {
baseObject
items []sparseArrayItem
length uint32
propValueCount int
lengthProp valueProperty
}
func (a *sparseArrayObject) findIdx(idx uint32) int {
return sort.Search(len(a.items), func(i int) bool {
return a.items[i].idx >= idx
})
}
func (a *sparseArrayObject) _setLengthInt(l uint32, throw bool) bool {
ret := true
if l <= a.length {
if a.propValueCount > 0 {
// Slow path
for i := len(a.items) - 1; i >= 0; i-- {
item := a.items[i]
if item.idx <= l {
break
}
if prop, ok := item.value.(*valueProperty); ok {
if !prop.configurable {
l = item.idx + 1
ret = false
break
}
a.propValueCount--
}
}
}
}
idx := a.findIdx(l)
aa := a.items[idx:]
for i := range aa {
aa[i].value = nil
}
a.items = a.items[:idx]
a.length = l
if !ret {
a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length")
}
return ret
}
func (a *sparseArrayObject) setLengthInt(l uint32, throw bool) bool {
if l == a.length {
return true
}
if !a.lengthProp.writable {
a.val.runtime.typeErrorResult(throw, "length is not writable")
return false
}
return a._setLengthInt(l, throw)
}
func (a *sparseArrayObject) setLength(v uint32, throw bool) bool {
if !a.lengthProp.writable {
a.val.runtime.typeErrorResult(throw, "length is not writable")
return false
}
return a._setLengthInt(v, throw)
}
func (a *sparseArrayObject) _getIdx(idx uint32) Value {
i := a.findIdx(idx)
if i < len(a.items) && a.items[i].idx == idx {
return a.items[i].value
}
return nil
}
func (a *sparseArrayObject) getStr(name unistring.String, receiver Value) Value {
return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
}
func (a *sparseArrayObject) getIdx(idx valueInt, receiver Value) Value {
prop := a.getOwnPropIdx(idx)
if prop == nil {
if a.prototype != nil {
if receiver == nil {
return a.prototype.self.getIdx(idx, a.val)
}
return a.prototype.self.getIdx(idx, receiver)
}
}
if prop, ok := prop.(*valueProperty); ok {
if receiver == nil {
return prop.get(a.val)
}
return prop.get(receiver)
}
return prop
}
func (a *sparseArrayObject) getLengthProp() *valueProperty {
a.lengthProp.value = intToValue(int64(a.length))
return &a.lengthProp
}
func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._getIdx(idx)
}
if name == "length" {
return a.getLengthProp()
}
return a.baseObject.getOwnPropStr(name)
}
func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value {
if idx := toIdx(idx); idx != math.MaxUint32 {
return a._getIdx(idx)
}
return a.baseObject.getOwnPropStr(idx.string())
}
func (a *sparseArrayObject) add(idx uint32, val Value) {
i := a.findIdx(idx)
a.items = append(a.items, sparseArrayItem{})
copy(a.items[i+1:], a.items[i:])
a.items[i] = sparseArrayItem{
idx: idx,
value: val,
}
}
func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool {
var prop Value
i := a.findIdx(idx)
if i < len(a.items) && a.items[i].idx == idx {
prop = a.items[i].value
}
if prop == nil {
if proto := a.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok {
return res
}
}
// new property
if !a.extensible {
a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
return false
}
if idx >= a.length {
if !a.setLengthInt(idx+1, throw) {
return false
}
}
if a.expand(idx) {
a.items = append(a.items, sparseArrayItem{})
copy(a.items[i+1:], a.items[i:])
a.items[i] = sparseArrayItem{
idx: idx,
value: val,
}
} else {
ar := a.val.self.(*arrayObject)
ar.values[idx] = val
ar.objCount++
return true
}
} else {
if prop, ok := prop.(*valueProperty); ok {
if !prop.isWritable() {
a.val.runtime.typeErrorResult(throw)
return false
}
prop.set(a.val, val)
} else {
a.items[i].value = val
}
}
return true
}
func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._setOwnIdx(idx, val, throw)
} else {
if name == "length" {
return a.setLength(a.val.runtime.toLengthUint32(val), throw)
} else {
return a.baseObject.setOwnStr(name, val, throw)
}
}
}
func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
return a._setOwnIdx(idx, val, throw)
}
return a.baseObject.setOwnStr(idx.string(), val, throw)
}
func (a *sparseArrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
}
func (a *sparseArrayObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
return a._setForeignIdx(name, a.getOwnPropIdx(name), val, receiver, throw)
}
type sparseArrayPropIter struct {
a *sparseArrayObject
idx int
}
func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) {
for i.idx < len(i.a.items) {
name := asciiString(strconv.Itoa(int(i.a.items[i.idx].idx)))
prop := i.a.items[i.idx].value
i.idx++
if prop != nil {
return propIterItem{name: name, value: prop}, i.next
}
}
return i.a.baseObject.iterateStringKeys()()
}
func (a *sparseArrayObject) iterateStringKeys() iterNextFunc {
return (&sparseArrayPropIter{
a: a,
}).next
}
func (a *sparseArrayObject) stringKeys(all bool, accum []Value) []Value {
if all {
for _, item := range a.items {
accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10)))
}
} else {
for _, item := range a.items {
if prop, ok := item.value.(*valueProperty); ok && !prop.enumerable {
continue
}
accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10)))
}
}
return a.baseObject.stringKeys(all, accum)
}
func (a *sparseArrayObject) setValues(values []Value, objCount int) {
a.items = make([]sparseArrayItem, 0, objCount)
for i, val := range values {
if val != nil {
a.items = append(a.items, sparseArrayItem{
idx: uint32(i),
value: val,
})
}
}
}
func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
i := a.findIdx(idx)
return i < len(a.items) && a.items[i].idx == idx
} else {
return a.baseObject.hasOwnPropertyStr(name)
}
}
func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
i := a.findIdx(idx)
return i < len(a.items) && a.items[i].idx == idx
}
return a.baseObject.hasOwnPropertyStr(idx.string())
}
func (a *sparseArrayObject) hasPropertyIdx(idx valueInt) bool {
if a.hasOwnPropertyIdx(idx) {
return true
}
if a.prototype != nil {
return a.prototype.self.hasPropertyIdx(idx)
}
return false
}
func (a *sparseArrayObject) expand(idx uint32) bool {
if l := len(a.items); l >= 1024 {
if ii := a.items[l-1].idx; ii > idx {
idx = ii
}
if (bits.UintSize == 64 || idx < math.MaxInt32) && int(idx)>>3 < l {
//log.Println("Switching sparse->standard")
ar := &arrayObject{
baseObject: a.baseObject,
length: a.length,
propValueCount: a.propValueCount,
}
ar.setValuesFromSparse(a.items, int(idx))
ar.val.self = ar
ar.lengthProp.writable = a.lengthProp.writable
a._put("length", &ar.lengthProp)
return false
}
}
return true
}
func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool {
var existing Value
i := a.findIdx(idx)
if i < len(a.items) && a.items[i].idx == idx {
existing = a.items[i].value
}
prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw)
if ok {
if idx >= a.length {
if !a.setLengthInt(idx+1, throw) {
return false
}
}
if i >= len(a.items) || a.items[i].idx != idx {
if a.expand(idx) {
a.items = append(a.items, sparseArrayItem{})
copy(a.items[i+1:], a.items[i:])
a.items[i] = sparseArrayItem{
idx: idx,
value: prop,
}
if idx >= a.length {
a.length = idx + 1
}
} else {
a.val.self.(*arrayObject).values[idx] = prop
}
} else {
a.items[i].value = prop
}
if _, ok := prop.(*valueProperty); ok {
a.propValueCount++
}
}
return ok
}
func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._defineIdxProperty(idx, descr, throw)
}
if name == "length" {
return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw)
}
return a.baseObject.defineOwnPropertyStr(name, descr, throw)
}
func (a *sparseArrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
return a._defineIdxProperty(idx, descr, throw)
}
return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw)
}
func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool {
i := a.findIdx(idx)
if i < len(a.items) && a.items[i].idx == idx {
if p, ok := a.items[i].value.(*valueProperty); ok {
if !p.configurable {
a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.toString())
return false
}
a.propValueCount--
}
copy(a.items[i:], a.items[i+1:])
a.items[len(a.items)-1].value = nil
a.items = a.items[:len(a.items)-1]
}
return true
}
func (a *sparseArrayObject) deleteStr(name unistring.String, throw bool) bool {
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
return a._deleteIdxProp(idx, throw)
}
return a.baseObject.deleteStr(name, throw)
}
func (a *sparseArrayObject) deleteIdx(idx valueInt, throw bool) bool {
if idx := toIdx(idx); idx != math.MaxUint32 {
return a._deleteIdxProp(idx, throw)
}
return a.baseObject.deleteStr(idx.string(), throw)
}
func (a *sparseArrayObject) sortLen() int {
if len(a.items) > 0 {
return toIntStrict(int64(a.items[len(a.items)-1].idx) + 1)
}
return 0
}
func (a *sparseArrayObject) export(ctx *objectExportCtx) interface{} {
if v, exists := ctx.get(a.val); exists {
return v
}
arr := make([]interface{}, a.length)
ctx.put(a.val, arr)
var prevIdx uint32
for _, item := range a.items {
idx := item.idx
for i := prevIdx; i < idx; i++ {
if a.prototype != nil {
if v := a.prototype.self.getIdx(valueInt(i), nil); v != nil {
arr[i] = exportValue(v, ctx)
}
}
}
v := item.value
if v != nil {
if prop, ok := v.(*valueProperty); ok {
v = prop.get(a.val)
}
arr[idx] = exportValue(v, ctx)
}
prevIdx = idx + 1
}
for i := prevIdx; i < a.length; i++ {
if a.prototype != nil {
if v := a.prototype.self.getIdx(valueInt(i), nil); v != nil {
arr[i] = exportValue(v, ctx)
}
}
}
return arr
}
func (a *sparseArrayObject) exportType() reflect.Type {
return reflectTypeArray
}
func (a *sparseArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
r := a.val.runtime
if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil {
l := toIntStrict(int64(a.length))
if typ.Kind() == reflect.Array {
if dst.Len() != l {
return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len())
}
} else {
dst.Set(reflect.MakeSlice(typ, l, l))
}
ctx.putTyped(a.val, typ, dst.Interface())
for _, item := range a.items {
val := item.value
if p, ok := val.(*valueProperty); ok {
val = p.get(a.val)
}
idx := toIntStrict(int64(item.idx))
if idx >= l {
break
}
err := r.toReflectValue(val, dst.Index(idx), ctx)
if err != nil {
return fmt.Errorf("could not convert array element %v to %v at %d: %w", item.value, typ, idx, err)
}
}
return nil
}
return a.baseObject.exportToArrayOrSlice(dst, typ, ctx)
}
File diff suppressed because it is too large Load Diff
+876
View File
@@ -0,0 +1,876 @@
/*
Package ast declares types representing a JavaScript AST.
# Warning
The parser and AST interfaces are still works-in-progress (particularly where
node types are concerned) and may change in the future.
*/
package ast
import (
"github.com/dop251/goja/file"
"github.com/dop251/goja/token"
"github.com/dop251/goja/unistring"
)
type PropertyKind string
const (
PropertyKindValue PropertyKind = "value"
PropertyKindGet PropertyKind = "get"
PropertyKindSet PropertyKind = "set"
PropertyKindMethod PropertyKind = "method"
)
// All nodes implement the Node interface.
type Node interface {
Idx0() file.Idx // The index of the first character belonging to the node
Idx1() file.Idx // The index of the first character immediately after the node
}
// ========== //
// Expression //
// ========== //
type (
// All expression nodes implement the Expression interface.
Expression interface {
Node
_expressionNode()
}
BindingTarget interface {
Expression
_bindingTarget()
}
Binding struct {
Target BindingTarget
Initializer Expression
}
Pattern interface {
BindingTarget
_pattern()
}
YieldExpression struct {
Yield file.Idx
Argument Expression
Delegate bool
}
AwaitExpression struct {
Await file.Idx
Argument Expression
}
ArrayLiteral struct {
LeftBracket file.Idx
RightBracket file.Idx
Value []Expression
}
ArrayPattern struct {
LeftBracket file.Idx
RightBracket file.Idx
Elements []Expression
Rest Expression
}
AssignExpression struct {
Operator token.Token
Left Expression
Right Expression
}
BadExpression struct {
From file.Idx
To file.Idx
}
BinaryExpression struct {
Operator token.Token
Left Expression
Right Expression
Comparison bool
}
BooleanLiteral struct {
Idx file.Idx
Literal string
Value bool
}
BracketExpression struct {
Left Expression
Member Expression
LeftBracket file.Idx
RightBracket file.Idx
}
CallExpression struct {
Callee Expression
LeftParenthesis file.Idx
ArgumentList []Expression
RightParenthesis file.Idx
}
ConditionalExpression struct {
Test Expression
Consequent Expression
Alternate Expression
}
DotExpression struct {
Left Expression
Identifier Identifier
}
PrivateDotExpression struct {
Left Expression
Identifier PrivateIdentifier
}
OptionalChain struct {
Expression
}
Optional struct {
Expression
}
FunctionLiteral struct {
Function file.Idx
Name *Identifier
ParameterList *ParameterList
Body *BlockStatement
Source string
DeclarationList []*VariableDeclaration
Async, Generator bool
}
ClassLiteral struct {
Class file.Idx
RightBrace file.Idx
Name *Identifier
SuperClass Expression
Body []ClassElement
Source string
}
ConciseBody interface {
Node
_conciseBody()
}
ExpressionBody struct {
Expression Expression
}
ArrowFunctionLiteral struct {
Start file.Idx
ParameterList *ParameterList
Body ConciseBody
Source string
DeclarationList []*VariableDeclaration
Async bool
}
Identifier struct {
Name unistring.String
Idx file.Idx
}
PrivateIdentifier struct {
Identifier
}
NewExpression struct {
New file.Idx
Callee Expression
LeftParenthesis file.Idx
ArgumentList []Expression
RightParenthesis file.Idx
}
NullLiteral struct {
Idx file.Idx
Literal string
}
NumberLiteral struct {
Idx file.Idx
Literal string
Value interface{}
}
ObjectLiteral struct {
LeftBrace file.Idx
RightBrace file.Idx
Value []Property
}
ObjectPattern struct {
LeftBrace file.Idx
RightBrace file.Idx
Properties []Property
Rest Expression
}
ParameterList struct {
Opening file.Idx
List []*Binding
Rest Expression
Closing file.Idx
}
Property interface {
Expression
_property()
}
PropertyShort struct {
Name Identifier
Initializer Expression
}
PropertyKeyed struct {
Key Expression
Kind PropertyKind
Value Expression
Computed bool
}
SpreadElement struct {
Expression
}
RegExpLiteral struct {
Idx file.Idx
Literal string
Pattern string
Flags string
}
SequenceExpression struct {
Sequence []Expression
}
StringLiteral struct {
Idx file.Idx
Literal string
Value unistring.String
}
TemplateElement struct {
Idx file.Idx
Literal string
Parsed unistring.String
Valid bool
}
TemplateLiteral struct {
OpenQuote file.Idx
CloseQuote file.Idx
Tag Expression
Elements []*TemplateElement
Expressions []Expression
}
ThisExpression struct {
Idx file.Idx
}
SuperExpression struct {
Idx file.Idx
}
UnaryExpression struct {
Operator token.Token
Idx file.Idx // If a prefix operation
Operand Expression
Postfix bool
}
MetaProperty struct {
Meta, Property *Identifier
Idx file.Idx
}
)
// _expressionNode
func (*ArrayLiteral) _expressionNode() {}
func (*AssignExpression) _expressionNode() {}
func (*YieldExpression) _expressionNode() {}
func (*AwaitExpression) _expressionNode() {}
func (*BadExpression) _expressionNode() {}
func (*BinaryExpression) _expressionNode() {}
func (*BooleanLiteral) _expressionNode() {}
func (*BracketExpression) _expressionNode() {}
func (*CallExpression) _expressionNode() {}
func (*ConditionalExpression) _expressionNode() {}
func (*DotExpression) _expressionNode() {}
func (*PrivateDotExpression) _expressionNode() {}
func (*FunctionLiteral) _expressionNode() {}
func (*ClassLiteral) _expressionNode() {}
func (*ArrowFunctionLiteral) _expressionNode() {}
func (*Identifier) _expressionNode() {}
func (*NewExpression) _expressionNode() {}
func (*NullLiteral) _expressionNode() {}
func (*NumberLiteral) _expressionNode() {}
func (*ObjectLiteral) _expressionNode() {}
func (*RegExpLiteral) _expressionNode() {}
func (*SequenceExpression) _expressionNode() {}
func (*StringLiteral) _expressionNode() {}
func (*TemplateLiteral) _expressionNode() {}
func (*ThisExpression) _expressionNode() {}
func (*SuperExpression) _expressionNode() {}
func (*UnaryExpression) _expressionNode() {}
func (*MetaProperty) _expressionNode() {}
func (*ObjectPattern) _expressionNode() {}
func (*ArrayPattern) _expressionNode() {}
func (*Binding) _expressionNode() {}
func (*PropertyShort) _expressionNode() {}
func (*PropertyKeyed) _expressionNode() {}
// ========= //
// Statement //
// ========= //
type (
// All statement nodes implement the Statement interface.
Statement interface {
Node
_statementNode()
}
BadStatement struct {
From file.Idx
To file.Idx
}
BlockStatement struct {
LeftBrace file.Idx
List []Statement
RightBrace file.Idx
}
BranchStatement struct {
Idx file.Idx
Token token.Token
Label *Identifier
}
CaseStatement struct {
Case file.Idx
Test Expression
Consequent []Statement
}
CatchStatement struct {
Catch file.Idx
Parameter BindingTarget
Body *BlockStatement
}
DebuggerStatement struct {
Debugger file.Idx
}
DoWhileStatement struct {
Do file.Idx
Test Expression
Body Statement
RightParenthesis file.Idx
}
EmptyStatement struct {
Semicolon file.Idx
}
ExpressionStatement struct {
Expression Expression
}
ForInStatement struct {
For file.Idx
Into ForInto
Source Expression
Body Statement
}
ForOfStatement struct {
For file.Idx
Into ForInto
Source Expression
Body Statement
}
ForStatement struct {
For file.Idx
Initializer ForLoopInitializer
Update Expression
Test Expression
Body Statement
}
IfStatement struct {
If file.Idx
Test Expression
Consequent Statement
Alternate Statement
}
LabelledStatement struct {
Label *Identifier
Colon file.Idx
Statement Statement
}
ReturnStatement struct {
Return file.Idx
Argument Expression
}
SwitchStatement struct {
Switch file.Idx
Discriminant Expression
Default int
Body []*CaseStatement
RightBrace file.Idx
}
ThrowStatement struct {
Throw file.Idx
Argument Expression
}
TryStatement struct {
Try file.Idx
Body *BlockStatement
Catch *CatchStatement
Finally *BlockStatement
}
VariableStatement struct {
Var file.Idx
List []*Binding
}
LexicalDeclaration struct {
Idx file.Idx
Token token.Token
List []*Binding
}
WhileStatement struct {
While file.Idx
Test Expression
Body Statement
}
WithStatement struct {
With file.Idx
Object Expression
Body Statement
}
FunctionDeclaration struct {
Function *FunctionLiteral
}
ClassDeclaration struct {
Class *ClassLiteral
}
)
// _statementNode
func (*BadStatement) _statementNode() {}
func (*BlockStatement) _statementNode() {}
func (*BranchStatement) _statementNode() {}
func (*CaseStatement) _statementNode() {}
func (*CatchStatement) _statementNode() {}
func (*DebuggerStatement) _statementNode() {}
func (*DoWhileStatement) _statementNode() {}
func (*EmptyStatement) _statementNode() {}
func (*ExpressionStatement) _statementNode() {}
func (*ForInStatement) _statementNode() {}
func (*ForOfStatement) _statementNode() {}
func (*ForStatement) _statementNode() {}
func (*IfStatement) _statementNode() {}
func (*LabelledStatement) _statementNode() {}
func (*ReturnStatement) _statementNode() {}
func (*SwitchStatement) _statementNode() {}
func (*ThrowStatement) _statementNode() {}
func (*TryStatement) _statementNode() {}
func (*VariableStatement) _statementNode() {}
func (*WhileStatement) _statementNode() {}
func (*WithStatement) _statementNode() {}
func (*LexicalDeclaration) _statementNode() {}
func (*FunctionDeclaration) _statementNode() {}
func (*ClassDeclaration) _statementNode() {}
// =========== //
// Declaration //
// =========== //
type (
VariableDeclaration struct {
Var file.Idx
List []*Binding
}
ClassElement interface {
Node
_classElement()
}
FieldDefinition struct {
Idx file.Idx
Key Expression
Initializer Expression
Computed bool
Static bool
}
MethodDefinition struct {
Idx file.Idx
Key Expression
Kind PropertyKind // "method", "get" or "set"
Body *FunctionLiteral
Computed bool
Static bool
}
ClassStaticBlock struct {
Static file.Idx
Block *BlockStatement
Source string
DeclarationList []*VariableDeclaration
}
)
type (
ForLoopInitializer interface {
Node
_forLoopInitializer()
}
ForLoopInitializerExpression struct {
Expression Expression
}
ForLoopInitializerVarDeclList struct {
Var file.Idx
List []*Binding
}
ForLoopInitializerLexicalDecl struct {
LexicalDeclaration LexicalDeclaration
}
ForInto interface {
Node
_forInto()
}
ForIntoVar struct {
Binding *Binding
}
ForDeclaration struct {
Idx file.Idx
IsConst bool
Target BindingTarget
}
ForIntoExpression struct {
Expression Expression
}
)
func (*ForLoopInitializerExpression) _forLoopInitializer() {}
func (*ForLoopInitializerVarDeclList) _forLoopInitializer() {}
func (*ForLoopInitializerLexicalDecl) _forLoopInitializer() {}
func (*ForIntoVar) _forInto() {}
func (*ForDeclaration) _forInto() {}
func (*ForIntoExpression) _forInto() {}
func (*ArrayPattern) _pattern() {}
func (*ArrayPattern) _bindingTarget() {}
func (*ObjectPattern) _pattern() {}
func (*ObjectPattern) _bindingTarget() {}
func (*BadExpression) _bindingTarget() {}
func (*PropertyShort) _property() {}
func (*PropertyKeyed) _property() {}
func (*SpreadElement) _property() {}
func (*Identifier) _bindingTarget() {}
func (*BlockStatement) _conciseBody() {}
func (*ExpressionBody) _conciseBody() {}
func (*FieldDefinition) _classElement() {}
func (*MethodDefinition) _classElement() {}
func (*ClassStaticBlock) _classElement() {}
// ==== //
// Node //
// ==== //
type Program struct {
Body []Statement
DeclarationList []*VariableDeclaration
File *file.File
}
// ==== //
// Idx0 //
// ==== //
func (self *ArrayLiteral) Idx0() file.Idx { return self.LeftBracket }
func (self *ArrayPattern) Idx0() file.Idx { return self.LeftBracket }
func (self *YieldExpression) Idx0() file.Idx { return self.Yield }
func (self *AwaitExpression) Idx0() file.Idx { return self.Await }
func (self *ObjectPattern) Idx0() file.Idx { return self.LeftBrace }
func (self *ParameterList) Idx0() file.Idx { return self.Opening }
func (self *AssignExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *BadExpression) Idx0() file.Idx { return self.From }
func (self *BinaryExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *BooleanLiteral) Idx0() file.Idx { return self.Idx }
func (self *BracketExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() }
func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() }
func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *PrivateDotExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *FunctionLiteral) Idx0() file.Idx { return self.Function }
func (self *ClassLiteral) Idx0() file.Idx { return self.Class }
func (self *ArrowFunctionLiteral) Idx0() file.Idx { return self.Start }
func (self *Identifier) Idx0() file.Idx { return self.Idx }
func (self *NewExpression) Idx0() file.Idx { return self.New }
func (self *NullLiteral) Idx0() file.Idx { return self.Idx }
func (self *NumberLiteral) Idx0() file.Idx { return self.Idx }
func (self *ObjectLiteral) Idx0() file.Idx { return self.LeftBrace }
func (self *RegExpLiteral) Idx0() file.Idx { return self.Idx }
func (self *SequenceExpression) Idx0() file.Idx { return self.Sequence[0].Idx0() }
func (self *StringLiteral) Idx0() file.Idx { return self.Idx }
func (self *TemplateElement) Idx0() file.Idx { return self.Idx }
func (self *TemplateLiteral) Idx0() file.Idx { return self.OpenQuote }
func (self *ThisExpression) Idx0() file.Idx { return self.Idx }
func (self *SuperExpression) Idx0() file.Idx { return self.Idx }
func (self *UnaryExpression) Idx0() file.Idx {
if self.Postfix {
return self.Operand.Idx0()
}
return self.Idx
}
func (self *MetaProperty) Idx0() file.Idx { return self.Idx }
func (self *BadStatement) Idx0() file.Idx { return self.From }
func (self *BlockStatement) Idx0() file.Idx { return self.LeftBrace }
func (self *BranchStatement) Idx0() file.Idx { return self.Idx }
func (self *CaseStatement) Idx0() file.Idx { return self.Case }
func (self *CatchStatement) Idx0() file.Idx { return self.Catch }
func (self *DebuggerStatement) Idx0() file.Idx { return self.Debugger }
func (self *DoWhileStatement) Idx0() file.Idx { return self.Do }
func (self *EmptyStatement) Idx0() file.Idx { return self.Semicolon }
func (self *ExpressionStatement) Idx0() file.Idx { return self.Expression.Idx0() }
func (self *ForInStatement) Idx0() file.Idx { return self.For }
func (self *ForOfStatement) Idx0() file.Idx { return self.For }
func (self *ForStatement) Idx0() file.Idx { return self.For }
func (self *IfStatement) Idx0() file.Idx { return self.If }
func (self *LabelledStatement) Idx0() file.Idx { return self.Label.Idx0() }
func (self *Program) Idx0() file.Idx { return self.Body[0].Idx0() }
func (self *ReturnStatement) Idx0() file.Idx { return self.Return }
func (self *SwitchStatement) Idx0() file.Idx { return self.Switch }
func (self *ThrowStatement) Idx0() file.Idx { return self.Throw }
func (self *TryStatement) Idx0() file.Idx { return self.Try }
func (self *VariableStatement) Idx0() file.Idx { return self.Var }
func (self *WhileStatement) Idx0() file.Idx { return self.While }
func (self *WithStatement) Idx0() file.Idx { return self.With }
func (self *LexicalDeclaration) Idx0() file.Idx { return self.Idx }
func (self *FunctionDeclaration) Idx0() file.Idx { return self.Function.Idx0() }
func (self *ClassDeclaration) Idx0() file.Idx { return self.Class.Idx0() }
func (self *Binding) Idx0() file.Idx { return self.Target.Idx0() }
func (self *ForLoopInitializerExpression) Idx0() file.Idx { return self.Expression.Idx0() }
func (self *ForLoopInitializerVarDeclList) Idx0() file.Idx { return self.List[0].Idx0() }
func (self *ForLoopInitializerLexicalDecl) Idx0() file.Idx { return self.LexicalDeclaration.Idx0() }
func (self *PropertyShort) Idx0() file.Idx { return self.Name.Idx }
func (self *PropertyKeyed) Idx0() file.Idx { return self.Key.Idx0() }
func (self *ExpressionBody) Idx0() file.Idx { return self.Expression.Idx0() }
func (self *VariableDeclaration) Idx0() file.Idx { return self.Var }
func (self *FieldDefinition) Idx0() file.Idx { return self.Idx }
func (self *MethodDefinition) Idx0() file.Idx { return self.Idx }
func (self *ClassStaticBlock) Idx0() file.Idx { return self.Static }
func (self *ForDeclaration) Idx0() file.Idx { return self.Idx }
func (self *ForIntoVar) Idx0() file.Idx { return self.Binding.Idx0() }
func (self *ForIntoExpression) Idx0() file.Idx { return self.Expression.Idx0() }
// ==== //
// Idx1 //
// ==== //
func (self *ArrayLiteral) Idx1() file.Idx { return self.RightBracket + 1 }
func (self *ArrayPattern) Idx1() file.Idx { return self.RightBracket + 1 }
func (self *AssignExpression) Idx1() file.Idx { return self.Right.Idx1() }
func (self *AwaitExpression) Idx1() file.Idx { return self.Argument.Idx1() }
func (self *BadExpression) Idx1() file.Idx { return self.To }
func (self *BinaryExpression) Idx1() file.Idx { return self.Right.Idx1() }
func (self *BooleanLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *BracketExpression) Idx1() file.Idx { return self.RightBracket + 1 }
func (self *CallExpression) Idx1() file.Idx { return self.RightParenthesis + 1 }
func (self *ConditionalExpression) Idx1() file.Idx { return self.Alternate.Idx1() }
func (self *DotExpression) Idx1() file.Idx { return self.Identifier.Idx1() }
func (self *PrivateDotExpression) Idx1() file.Idx { return self.Identifier.Idx1() }
func (self *FunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() }
func (self *ClassLiteral) Idx1() file.Idx { return self.RightBrace + 1 }
func (self *ArrowFunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() }
func (self *Identifier) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Name)) }
func (self *NewExpression) Idx1() file.Idx {
if self.ArgumentList != nil {
return self.RightParenthesis + 1
} else {
return self.Callee.Idx1()
}
}
func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null"
func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace + 1 }
func (self *ObjectPattern) Idx1() file.Idx { return self.RightBrace + 1 }
func (self *ParameterList) Idx1() file.Idx { return self.Closing + 1 }
func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() }
func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *TemplateElement) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *TemplateLiteral) Idx1() file.Idx { return self.CloseQuote + 1 }
func (self *ThisExpression) Idx1() file.Idx { return self.Idx + 4 }
func (self *SuperExpression) Idx1() file.Idx { return self.Idx + 5 }
func (self *UnaryExpression) Idx1() file.Idx {
if self.Postfix {
return self.Operand.Idx1() + 2 // ++ --
}
return self.Operand.Idx1()
}
func (self *MetaProperty) Idx1() file.Idx {
return self.Property.Idx1()
}
func (self *BadStatement) Idx1() file.Idx { return self.To }
func (self *BlockStatement) Idx1() file.Idx { return self.RightBrace + 1 }
func (self *BranchStatement) Idx1() file.Idx {
if self.Label == nil {
return file.Idx(int(self.Idx) + len(self.Token.String()))
}
return self.Label.Idx1()
}
func (self *CaseStatement) Idx1() file.Idx { return self.Consequent[len(self.Consequent)-1].Idx1() }
func (self *CatchStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *DebuggerStatement) Idx1() file.Idx { return self.Debugger + 8 }
func (self *DoWhileStatement) Idx1() file.Idx { return self.RightParenthesis + 1 }
func (self *EmptyStatement) Idx1() file.Idx { return self.Semicolon + 1 }
func (self *ExpressionStatement) Idx1() file.Idx { return self.Expression.Idx1() }
func (self *ForInStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *ForOfStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *ForStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *IfStatement) Idx1() file.Idx {
if self.Alternate != nil {
return self.Alternate.Idx1()
}
return self.Consequent.Idx1()
}
func (self *LabelledStatement) Idx1() file.Idx { return self.Statement.Idx1() }
func (self *Program) Idx1() file.Idx { return self.Body[len(self.Body)-1].Idx1() }
func (self *ReturnStatement) Idx1() file.Idx {
if self.Argument != nil {
return self.Argument.Idx1()
}
return self.Return + 6
}
func (self *SwitchStatement) Idx1() file.Idx { return self.RightBrace + 1 }
func (self *ThrowStatement) Idx1() file.Idx { return self.Argument.Idx1() }
func (self *TryStatement) Idx1() file.Idx {
if self.Finally != nil {
return self.Finally.Idx1()
}
if self.Catch != nil {
return self.Catch.Idx1()
}
return self.Body.Idx1()
}
func (self *VariableStatement) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
func (self *WhileStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *WithStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *LexicalDeclaration) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
func (self *FunctionDeclaration) Idx1() file.Idx { return self.Function.Idx1() }
func (self *ClassDeclaration) Idx1() file.Idx { return self.Class.Idx1() }
func (self *Binding) Idx1() file.Idx {
if self.Initializer != nil {
return self.Initializer.Idx1()
}
return self.Target.Idx1()
}
func (self *ForLoopInitializerExpression) Idx1() file.Idx { return self.Expression.Idx1() }
func (self *ForLoopInitializerVarDeclList) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
func (self *ForLoopInitializerLexicalDecl) Idx1() file.Idx { return self.LexicalDeclaration.Idx1() }
func (self *PropertyShort) Idx1() file.Idx {
if self.Initializer != nil {
return self.Initializer.Idx1()
}
return self.Name.Idx1()
}
func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() }
func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() }
func (self *VariableDeclaration) Idx1() file.Idx {
if len(self.List) > 0 {
return self.List[len(self.List)-1].Idx1()
}
return self.Var + 3
}
func (self *FieldDefinition) Idx1() file.Idx {
if self.Initializer != nil {
return self.Initializer.Idx1()
}
return self.Key.Idx1()
}
func (self *MethodDefinition) Idx1() file.Idx {
return self.Body.Idx1()
}
func (self *ClassStaticBlock) Idx1() file.Idx {
return self.Block.Idx1()
}
func (self *YieldExpression) Idx1() file.Idx {
if self.Argument != nil {
return self.Argument.Idx1()
}
return self.Yield + 5
}
func (self *ForDeclaration) Idx1() file.Idx { return self.Target.Idx1() }
func (self *ForIntoVar) Idx1() file.Idx { return self.Binding.Idx1() }
func (self *ForIntoExpression) Idx1() file.Idx { return self.Expression.Idx1() }
File diff suppressed because it is too large Load Diff
+369
View File
@@ -0,0 +1,369 @@
package goja
import (
"fmt"
"hash/maphash"
"math"
"math/big"
"reflect"
"strconv"
"sync"
"github.com/dop251/goja/unistring"
)
type valueBigInt big.Int
func (v *valueBigInt) ToInteger() int64 {
v.ToNumber()
return 0
}
func (v *valueBigInt) toString() String {
return asciiString((*big.Int)(v).String())
}
func (v *valueBigInt) string() unistring.String {
return unistring.String(v.String())
}
func (v *valueBigInt) ToString() Value {
return v
}
func (v *valueBigInt) String() string {
return (*big.Int)(v).String()
}
func (v *valueBigInt) ToFloat() float64 {
v.ToNumber()
return 0
}
func (v *valueBigInt) ToNumber() Value {
panic(typeError("Cannot convert a BigInt value to a number"))
}
func (v *valueBigInt) ToBoolean() bool {
return (*big.Int)(v).Sign() != 0
}
func (v *valueBigInt) ToObject(r *Runtime) *Object {
return r.newPrimitiveObject(v, r.getBigIntPrototype(), classObject)
}
func (v *valueBigInt) SameAs(other Value) bool {
if o, ok := other.(*valueBigInt); ok {
return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
}
return false
}
func (v *valueBigInt) Equals(other Value) bool {
switch o := other.(type) {
case *valueBigInt:
return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
case valueInt:
return (*big.Int)(v).Cmp(big.NewInt(int64(o))) == 0
case valueFloat:
if IsInfinity(o) || math.IsNaN(float64(o)) {
return false
}
if f := big.NewFloat(float64(o)); f.IsInt() {
i, _ := f.Int(nil)
return (*big.Int)(v).Cmp(i) == 0
}
return false
case String:
bigInt, err := stringToBigInt(o.toTrimmedUTF8())
if err != nil {
return false
}
return bigInt.Cmp((*big.Int)(v)) == 0
case valueBool:
return (*big.Int)(v).Int64() == o.ToInteger()
case *Object:
return v.Equals(o.toPrimitiveNumber())
}
return false
}
func (v *valueBigInt) StrictEquals(other Value) bool {
o, ok := other.(*valueBigInt)
if ok {
return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
}
return false
}
func (v *valueBigInt) Export() interface{} {
return new(big.Int).Set((*big.Int)(v))
}
func (v *valueBigInt) ExportType() reflect.Type {
return typeBigInt
}
func (v *valueBigInt) baseObject(rt *Runtime) *Object {
return rt.getBigIntPrototype()
}
func (v *valueBigInt) hash(hash *maphash.Hash) uint64 {
var sign byte
if (*big.Int)(v).Sign() < 0 {
sign = 0x01
} else {
sign = 0x00
}
_ = hash.WriteByte(sign)
_, _ = hash.Write((*big.Int)(v).Bytes())
h := hash.Sum64()
hash.Reset()
return h
}
func toBigInt(value Value) *valueBigInt {
// Undefined Throw a TypeError exception.
// Null Throw a TypeError exception.
// Boolean Return 1n if prim is true and 0n if prim is false.
// BigInt Return prim.
// Number Throw a TypeError exception.
// String 1. Let n be StringToBigInt(prim).
// 2. If n is undefined, throw a SyntaxError exception.
// 3. Return n.
// Symbol Throw a TypeError exception.
switch prim := value.(type) {
case *valueBigInt:
return prim
case String:
bigInt, err := stringToBigInt(prim.toTrimmedUTF8())
if err != nil {
panic(syntaxError(fmt.Sprintf("Cannot convert %s to a BigInt", prim)))
}
return (*valueBigInt)(bigInt)
case valueBool:
return (*valueBigInt)(big.NewInt(prim.ToInteger()))
case *Symbol:
panic(typeError("Cannot convert Symbol to a BigInt"))
case *Object:
return toBigInt(prim.toPrimitiveNumber())
default:
panic(typeError(fmt.Sprintf("Cannot convert %s to a BigInt", prim)))
}
}
func numberToBigInt(v Value) *valueBigInt {
switch v := toNumeric(v).(type) {
case *valueBigInt:
return v
case valueInt:
return (*valueBigInt)(big.NewInt(v.ToInteger()))
case valueFloat:
if IsInfinity(v) || math.IsNaN(float64(v)) {
panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v)))
}
if f := big.NewFloat(float64(v)); f.IsInt() {
n, _ := f.Int(nil)
return (*valueBigInt)(n)
}
panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v)))
case *Object:
prim := v.toPrimitiveNumber()
switch prim.(type) {
case valueInt, valueFloat:
return numberToBigInt(prim)
default:
return toBigInt(prim)
}
default:
panic(newTypeError("Cannot convert %s to a BigInt", v))
}
}
func stringToBigInt(str string) (*big.Int, error) {
var bigint big.Int
n, err := stringToInt(str)
if err != nil {
switch {
case isRangeErr(err):
bigint.SetString(str, 0)
case err == strconv.ErrSyntax:
default:
return nil, strconv.ErrSyntax
}
} else {
bigint.SetInt64(n)
}
return &bigint, nil
}
func (r *Runtime) thisBigIntValue(value Value) Value {
switch t := value.(type) {
case *valueBigInt:
return t
case *Object:
switch t := t.self.(type) {
case *primitiveValueObject:
return r.thisBigIntValue(t.pValue)
case *objectGoReflect:
if t.exportType() == typeBigInt && t.valueOf != nil {
return t.valueOf()
}
}
}
panic(r.NewTypeError("requires that 'this' be a BigInt"))
}
func (r *Runtime) bigintproto_valueOf(call FunctionCall) Value {
return r.thisBigIntValue(call.This)
}
func (r *Runtime) bigintproto_toString(call FunctionCall) Value {
x := (*big.Int)(r.thisBigIntValue(call.This).(*valueBigInt))
radix := call.Argument(0)
var radixMV int
if radix == _undefined {
radixMV = 10
} else {
radixMV = int(radix.ToInteger())
if radixMV < 2 || radixMV > 36 {
panic(r.newError(r.getRangeError(), "radix must be an integer between 2 and 36"))
}
}
return asciiString(x.Text(radixMV))
}
func (r *Runtime) bigint_asIntN(call FunctionCall) Value {
if len(call.Arguments) < 2 {
panic(r.NewTypeError("Cannot convert undefined to a BigInt"))
}
bits := r.toIndex(call.Argument(0).ToNumber())
if bits < 0 {
panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer"))
}
bigint := toBigInt(call.Argument(1))
twoToBits := new(big.Int).Lsh(big.NewInt(1), uint(bits))
mod := new(big.Int).Mod((*big.Int)(bigint), twoToBits)
if bits > 0 && mod.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bits-1))) >= 0 {
return (*valueBigInt)(mod.Sub(mod, twoToBits))
} else {
return (*valueBigInt)(mod)
}
}
func (r *Runtime) bigint_asUintN(call FunctionCall) Value {
if len(call.Arguments) < 2 {
panic(r.NewTypeError("Cannot convert undefined to a BigInt"))
}
bits := r.toIndex(call.Argument(0).ToNumber())
if bits < 0 {
panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer"))
}
bigint := (*big.Int)(toBigInt(call.Argument(1)))
ret := new(big.Int).Mod(bigint, new(big.Int).Lsh(big.NewInt(1), uint(bits)))
return (*valueBigInt)(ret)
}
var bigintTemplate *objectTemplate
var bigintTemplateOnce sync.Once
func getBigIntTemplate() *objectTemplate {
bigintTemplateOnce.Do(func() {
bigintTemplate = createBigIntTemplate()
})
return bigintTemplate
}
func createBigIntTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.getFunctionPrototype()
}
t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getBigIntPrototype(), false, false, false) })
t.putStr("asIntN", func(r *Runtime) Value { return r.methodProp(r.bigint_asIntN, "asIntN", 2) })
t.putStr("asUintN", func(r *Runtime) Value { return r.methodProp(r.bigint_asUintN, "asUintN", 2) })
return t
}
func (r *Runtime) builtin_BigInt(call FunctionCall) Value {
if len(call.Arguments) > 0 {
switch v := call.Argument(0).(type) {
case *valueBigInt, valueInt, valueFloat, *Object:
return numberToBigInt(v)
default:
return toBigInt(v)
}
}
return (*valueBigInt)(big.NewInt(0))
}
func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object {
if newTarget != nil {
panic(r.NewTypeError("BigInt is not a constructor"))
}
var v Value
if len(args) > 0 {
v = numberToBigInt(args[0])
} else {
v = (*valueBigInt)(big.NewInt(0))
}
return r.newPrimitiveObject(v, newTarget, classObject)
}
func (r *Runtime) getBigInt() *Object {
ret := r.global.BigInt
if ret == nil {
ret = &Object{runtime: r}
r.global.BigInt = ret
r.newTemplatedFuncObject(getBigIntTemplate(), ret, r.builtin_BigInt,
r.wrapNativeConstruct(r.builtin_newBigInt, ret, r.getBigIntPrototype()))
}
return ret
}
func createBigIntProtoTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.global.ObjectPrototype
}
t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, true) })
t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toLocaleString", 0) })
t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toString", 0) })
t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.bigintproto_valueOf, "valueOf", 0) })
t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
return t
}
var bigintProtoTemplate *objectTemplate
var bigintProtoTemplateOnce sync.Once
func getBigIntProtoTemplate() *objectTemplate {
bigintProtoTemplateOnce.Do(func() {
bigintProtoTemplate = createBigIntProtoTemplate()
})
return bigintProtoTemplate
}
func (r *Runtime) getBigIntPrototype() *Object {
ret := r.global.BigIntPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.BigIntPrototype = ret
o := r.newTemplatedObject(getBigIntProtoTemplate(), ret)
o.class = classObject
}
return ret
}
+75
View File
@@ -0,0 +1,75 @@
package goja
func (r *Runtime) booleanproto_toString(call FunctionCall) Value {
var b bool
switch o := call.This.(type) {
case valueBool:
b = bool(o)
goto success
case *Object:
if p, ok := o.self.(*primitiveValueObject); ok {
if b1, ok := p.pValue.(valueBool); ok {
b = bool(b1)
goto success
}
}
if o, ok := o.self.(*objectGoReflect); ok {
if o.class == classBoolean && o.toString != nil {
return o.toString()
}
}
}
r.typeErrorResult(true, "Method Boolean.prototype.toString is called on incompatible receiver")
success:
if b {
return stringTrue
}
return stringFalse
}
func (r *Runtime) booleanproto_valueOf(call FunctionCall) Value {
switch o := call.This.(type) {
case valueBool:
return o
case *Object:
if p, ok := o.self.(*primitiveValueObject); ok {
if b, ok := p.pValue.(valueBool); ok {
return b
}
}
if o, ok := o.self.(*objectGoReflect); ok {
if o.class == classBoolean && o.valueOf != nil {
return o.valueOf()
}
}
}
r.typeErrorResult(true, "Method Boolean.prototype.valueOf is called on incompatible receiver")
return nil
}
func (r *Runtime) getBooleanPrototype() *Object {
ret := r.global.BooleanPrototype
if ret == nil {
ret = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean)
r.global.BooleanPrototype = ret
o := ret.self
o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, "toString", 0), true, false, true)
o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, "valueOf", 0), true, false, true)
o._putProp("constructor", r.getBoolean(), true, false, true)
}
return ret
}
func (r *Runtime) getBoolean() *Object {
ret := r.global.Boolean
if ret == nil {
ret = &Object{runtime: r}
r.global.Boolean = ret
proto := r.getBooleanPrototype()
r.newNativeFuncAndConstruct(ret, r.builtin_Boolean,
r.wrapNativeConstruct(r.builtin_newBoolean, ret, proto), proto, "Boolean", intToValue(1))
}
return ret
}
File diff suppressed because it is too large Load Diff
+314
View File
@@ -0,0 +1,314 @@
package goja
import "github.com/dop251/goja/unistring"
const propNameStack = "stack"
type errorObject struct {
baseObject
stack []StackFrame
stackPropAdded bool
}
func (e *errorObject) formatStack() String {
var b StringBuilder
val := writeErrorString(&b, e.val)
if val != nil {
b.WriteString(val)
}
b.WriteRune('\n')
for _, frame := range e.stack {
b.writeASCII("\tat ")
frame.WriteToValueBuilder(&b)
b.WriteRune('\n')
}
return b.String()
}
func (e *errorObject) addStackProp() Value {
if !e.stackPropAdded {
res := e._putProp(propNameStack, e.formatStack(), true, false, true)
if len(e.propNames) > 1 {
// reorder property names to ensure 'stack' is the first one
copy(e.propNames[1:], e.propNames)
e.propNames[0] = propNameStack
}
e.stackPropAdded = true
return res
}
return nil
}
func (e *errorObject) getStr(p unistring.String, receiver Value) Value {
return e.getStrWithOwnProp(e.getOwnPropStr(p), p, receiver)
}
func (e *errorObject) getOwnPropStr(name unistring.String) Value {
res := e.baseObject.getOwnPropStr(name)
if res == nil && name == propNameStack {
return e.addStackProp()
}
return res
}
func (e *errorObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
if name == propNameStack {
e.addStackProp()
}
return e.baseObject.setOwnStr(name, val, throw)
}
func (e *errorObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return e._setForeignStr(name, e.getOwnPropStr(name), val, receiver, throw)
}
func (e *errorObject) deleteStr(name unistring.String, throw bool) bool {
if name == propNameStack {
e.addStackProp()
}
return e.baseObject.deleteStr(name, throw)
}
func (e *errorObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
if name == propNameStack {
e.addStackProp()
}
return e.baseObject.defineOwnPropertyStr(name, desc, throw)
}
func (e *errorObject) hasOwnPropertyStr(name unistring.String) bool {
if e.baseObject.hasOwnPropertyStr(name) {
return true
}
return name == propNameStack && !e.stackPropAdded
}
func (e *errorObject) stringKeys(all bool, accum []Value) []Value {
if all && !e.stackPropAdded {
accum = append(accum, asciiString(propNameStack))
}
return e.baseObject.stringKeys(all, accum)
}
func (e *errorObject) iterateStringKeys() iterNextFunc {
e.addStackProp()
return e.baseObject.iterateStringKeys()
}
func (e *errorObject) init() {
e.baseObject.init()
vm := e.val.runtime.vm
e.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0)
}
func (r *Runtime) newErrorObject(proto *Object, class string) *errorObject {
obj := &Object{runtime: r}
o := &errorObject{
baseObject: baseObject{
class: class,
val: obj,
extensible: true,
prototype: proto,
},
}
obj.self = o
o.init()
return o
}
func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object {
obj := r.newErrorObject(proto, classError)
if len(args) > 0 && args[0] != _undefined {
obj._putProp("message", args[0].ToString(), true, false, true)
}
if len(args) > 1 && args[1] != _undefined {
if options, ok := args[1].(*Object); ok {
if options.hasProperty(asciiString("cause")) {
obj.defineOwnPropertyStr("cause", PropertyDescriptor{
Writable: FLAG_TRUE,
Enumerable: FLAG_FALSE,
Configurable: FLAG_TRUE,
Value: options.Get("cause"),
}, true)
}
}
}
return obj.val
}
func (r *Runtime) builtin_AggregateError(args []Value, proto *Object) *Object {
obj := r.newErrorObject(proto, classError)
if len(args) > 1 && args[1] != nil && args[1] != _undefined {
obj._putProp("message", args[1].toString(), true, false, true)
}
var errors []Value
if len(args) > 0 {
errors = r.iterableToList(args[0], nil)
}
obj._putProp("errors", r.newArrayValues(errors), true, false, true)
if len(args) > 2 && args[2] != _undefined {
if options, ok := args[2].(*Object); ok {
if options.hasProperty(asciiString("cause")) {
obj.defineOwnPropertyStr("cause", PropertyDescriptor{
Writable: FLAG_TRUE,
Enumerable: FLAG_FALSE,
Configurable: FLAG_TRUE,
Value: options.Get("cause"),
}, true)
}
}
}
return obj.val
}
func writeErrorString(sb *StringBuilder, obj *Object) String {
var nameStr, msgStr String
name := obj.self.getStr("name", nil)
if name == nil || name == _undefined {
nameStr = asciiString("Error")
} else {
nameStr = name.toString()
}
msg := obj.self.getStr("message", nil)
if msg == nil || msg == _undefined {
msgStr = stringEmpty
} else {
msgStr = msg.toString()
}
if nameStr.Length() == 0 {
return msgStr
}
if msgStr.Length() == 0 {
return nameStr
}
sb.WriteString(nameStr)
sb.WriteString(asciiString(": "))
sb.WriteString(msgStr)
return nil
}
func (r *Runtime) error_toString(call FunctionCall) Value {
var sb StringBuilder
val := writeErrorString(&sb, r.toObject(call.This))
if val != nil {
return val
}
return sb.String()
}
func (r *Runtime) createErrorPrototype(name String, ctor *Object) *Object {
o := r.newBaseObject(r.getErrorPrototype(), classObject)
o._putProp("message", stringEmpty, true, false, true)
o._putProp("name", name, true, false, true)
o._putProp("constructor", ctor, true, false, true)
return o.val
}
func (r *Runtime) getErrorPrototype() *Object {
ret := r.global.ErrorPrototype
if ret == nil {
ret = r.NewObject()
r.global.ErrorPrototype = ret
o := ret.self
o._putProp("message", stringEmpty, true, false, true)
o._putProp("name", stringError, true, false, true)
o._putProp("toString", r.newNativeFunc(r.error_toString, "toString", 0), true, false, true)
o._putProp("constructor", r.getError(), true, false, true)
}
return ret
}
func (r *Runtime) getError() *Object {
ret := r.global.Error
if ret == nil {
ret = &Object{runtime: r}
r.global.Error = ret
r.newNativeFuncConstruct(ret, r.builtin_Error, "Error", r.getErrorPrototype(), 1)
}
return ret
}
func (r *Runtime) getAggregateError() *Object {
ret := r.global.AggregateError
if ret == nil {
ret = &Object{runtime: r}
r.global.AggregateError = ret
r.newNativeFuncConstructProto(ret, r.builtin_AggregateError, "AggregateError", r.createErrorPrototype(stringAggregateError, ret), r.getError(), 2)
}
return ret
}
func (r *Runtime) getTypeError() *Object {
ret := r.global.TypeError
if ret == nil {
ret = &Object{runtime: r}
r.global.TypeError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "TypeError", r.createErrorPrototype(stringTypeError, ret), r.getError(), 1)
}
return ret
}
func (r *Runtime) getReferenceError() *Object {
ret := r.global.ReferenceError
if ret == nil {
ret = &Object{runtime: r}
r.global.ReferenceError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "ReferenceError", r.createErrorPrototype(stringReferenceError, ret), r.getError(), 1)
}
return ret
}
func (r *Runtime) getSyntaxError() *Object {
ret := r.global.SyntaxError
if ret == nil {
ret = &Object{runtime: r}
r.global.SyntaxError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "SyntaxError", r.createErrorPrototype(stringSyntaxError, ret), r.getError(), 1)
}
return ret
}
func (r *Runtime) getRangeError() *Object {
ret := r.global.RangeError
if ret == nil {
ret = &Object{runtime: r}
r.global.RangeError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "RangeError", r.createErrorPrototype(stringRangeError, ret), r.getError(), 1)
}
return ret
}
func (r *Runtime) getEvalError() *Object {
ret := r.global.EvalError
if ret == nil {
ret = &Object{runtime: r}
r.global.EvalError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "EvalError", r.createErrorPrototype(stringEvalError, ret), r.getError(), 1)
}
return ret
}
func (r *Runtime) getURIError() *Object {
ret := r.global.URIError
if ret == nil {
ret = &Object{runtime: r}
r.global.URIError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "URIError", r.createErrorPrototype(stringURIError, ret), r.getError(), 1)
}
return ret
}
func (r *Runtime) getGoError() *Object {
ret := r.global.GoError
if ret == nil {
ret = &Object{runtime: r}
r.global.GoError = ret
r.newNativeFuncConstructProto(ret, r.builtin_Error, "GoError", r.createErrorPrototype(stringGoError, ret), r.getError(), 1)
}
return ret
}
+418
View File
@@ -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
View File
@@ -0,0 +1,576 @@
package goja
import (
"errors"
"io"
"math"
"regexp"
"strconv"
"strings"
"sync"
"unicode/utf8"
"github.com/dop251/goja/unistring"
)
const hexUpper = "0123456789ABCDEF"
var (
parseFloatRegexp = regexp.MustCompile(`^([+-]?(?:Infinity|[0-9]*\.?[0-9]*(?:[eE][+-]?[0-9]+)?))`)
)
func (r *Runtime) builtin_isNaN(call FunctionCall) Value {
if math.IsNaN(call.Argument(0).ToFloat()) {
return valueTrue
} else {
return valueFalse
}
}
func (r *Runtime) builtin_parseInt(call FunctionCall) Value {
str := call.Argument(0).toString().toTrimmedUTF8()
radix := int(toInt32(call.Argument(1)))
v, _ := parseInt(str, radix)
return v
}
func (r *Runtime) builtin_parseFloat(call FunctionCall) Value {
m := parseFloatRegexp.FindStringSubmatch(call.Argument(0).toString().toTrimmedUTF8())
if len(m) == 2 {
if s := m[1]; s != "" && s != "+" && s != "-" {
switch s {
case "+", "-":
case "Infinity", "+Infinity":
return _positiveInf
case "-Infinity":
return _negativeInf
default:
f, err := strconv.ParseFloat(s, 64)
if err == nil || isRangeErr(err) {
return floatToValue(f)
}
}
}
}
return _NaN
}
func (r *Runtime) builtin_isFinite(call FunctionCall) Value {
f := call.Argument(0).ToFloat()
if math.IsNaN(f) || math.IsInf(f, 0) {
return valueFalse
}
return valueTrue
}
func (r *Runtime) _encode(uriString String, unescaped *[256]bool) String {
reader := uriString.Reader()
utf8Buf := make([]byte, utf8.UTFMax)
needed := false
l := 0
for {
rn, _, err := reader.ReadRune()
if err != nil {
if err != io.EOF {
panic(r.newError(r.getURIError(), "Malformed URI"))
}
break
}
if rn >= utf8.RuneSelf {
needed = true
l += utf8.EncodeRune(utf8Buf, rn) * 3
} else if !unescaped[rn] {
needed = true
l += 3
} else {
l++
}
}
if !needed {
return uriString
}
buf := make([]byte, l)
i := 0
reader = uriString.Reader()
for {
rn, _, err := reader.ReadRune()
if err == io.EOF {
break
}
if rn >= utf8.RuneSelf {
n := utf8.EncodeRune(utf8Buf, rn)
for _, b := range utf8Buf[:n] {
buf[i] = '%'
buf[i+1] = hexUpper[b>>4]
buf[i+2] = hexUpper[b&15]
i += 3
}
} else if !unescaped[rn] {
buf[i] = '%'
buf[i+1] = hexUpper[rn>>4]
buf[i+2] = hexUpper[rn&15]
i += 3
} else {
buf[i] = byte(rn)
i++
}
}
return asciiString(buf)
}
func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String {
s := sv.String()
hexCount := 0
for i := 0; i < len(s); {
switch s[i] {
case '%':
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
panic(r.newError(r.getURIError(), "Malformed URI"))
}
c := unhex(s[i+1])<<4 | unhex(s[i+2])
if !reservedSet[c] {
hexCount++
}
i += 3
default:
i++
}
}
if hexCount == 0 {
return sv
}
t := make([]byte, len(s)-hexCount*2)
j := 0
isUnicode := false
for i := 0; i < len(s); {
ch := s[i]
switch ch {
case '%':
c := unhex(s[i+1])<<4 | unhex(s[i+2])
if reservedSet[c] {
t[j] = s[i]
t[j+1] = s[i+1]
t[j+2] = s[i+2]
j += 3
} else {
t[j] = c
if c >= utf8.RuneSelf {
isUnicode = true
}
j++
}
i += 3
default:
if ch >= utf8.RuneSelf {
isUnicode = true
}
t[j] = ch
j++
i++
}
}
if !isUnicode {
return asciiString(t)
}
us := make([]rune, 0, len(s))
for len(t) > 0 {
rn, size := utf8.DecodeRune(t)
if rn == utf8.RuneError {
if size != 3 || t[0] != 0xef || t[1] != 0xbf || t[2] != 0xbd {
panic(r.newError(r.getURIError(), "Malformed URI"))
}
}
us = append(us, rn)
t = t[size:]
}
return unicodeStringFromRunes(us)
}
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
func (r *Runtime) builtin_decodeURI(call FunctionCall) Value {
uriString := call.Argument(0).toString()
return r._decode(uriString, &uriReservedHash)
}
func (r *Runtime) builtin_decodeURIComponent(call FunctionCall) Value {
uriString := call.Argument(0).toString()
return r._decode(uriString, &emptyEscapeSet)
}
func (r *Runtime) builtin_encodeURI(call FunctionCall) Value {
uriString := call.Argument(0).toString()
return r._encode(uriString, &uriReservedUnescapedHash)
}
func (r *Runtime) builtin_encodeURIComponent(call FunctionCall) Value {
uriString := call.Argument(0).toString()
return r._encode(uriString, &uriUnescaped)
}
func (r *Runtime) builtin_escape(call FunctionCall) Value {
s := call.Argument(0).toString()
var sb strings.Builder
l := s.Length()
for i := 0; i < l; i++ {
r := s.CharAt(i)
if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' ||
r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' {
sb.WriteByte(byte(r))
} else if r <= 0xff {
sb.WriteByte('%')
sb.WriteByte(hexUpper[r>>4])
sb.WriteByte(hexUpper[r&0xf])
} else {
sb.WriteString("%u")
sb.WriteByte(hexUpper[r>>12])
sb.WriteByte(hexUpper[(r>>8)&0xf])
sb.WriteByte(hexUpper[(r>>4)&0xf])
sb.WriteByte(hexUpper[r&0xf])
}
}
return asciiString(sb.String())
}
func (r *Runtime) builtin_unescape(call FunctionCall) Value {
s := call.Argument(0).toString()
l := s.Length()
var asciiBuf []byte
var unicodeBuf []uint16
_, u := devirtualizeString(s)
unicode := u != nil
if unicode {
unicodeBuf = make([]uint16, 1, l+1)
unicodeBuf[0] = unistring.BOM
} else {
asciiBuf = make([]byte, 0, l)
}
for i := 0; i < l; {
r := s.CharAt(i)
if r == '%' {
if i <= l-6 && s.CharAt(i+1) == 'u' {
c0 := s.CharAt(i + 2)
c1 := s.CharAt(i + 3)
c2 := s.CharAt(i + 4)
c3 := s.CharAt(i + 5)
if c0 <= 0xff && ishex(byte(c0)) &&
c1 <= 0xff && ishex(byte(c1)) &&
c2 <= 0xff && ishex(byte(c2)) &&
c3 <= 0xff && ishex(byte(c3)) {
r = uint16(unhex(byte(c0)))<<12 |
uint16(unhex(byte(c1)))<<8 |
uint16(unhex(byte(c2)))<<4 |
uint16(unhex(byte(c3)))
i += 5
goto out
}
}
if i <= l-3 {
c0 := s.CharAt(i + 1)
c1 := s.CharAt(i + 2)
if c0 <= 0xff && ishex(byte(c0)) &&
c1 <= 0xff && ishex(byte(c1)) {
r = uint16(unhex(byte(c0))<<4 | unhex(byte(c1)))
i += 2
}
}
}
out:
if r >= utf8.RuneSelf && !unicode {
unicodeBuf = make([]uint16, 1, l+1)
unicodeBuf[0] = unistring.BOM
for _, b := range asciiBuf {
unicodeBuf = append(unicodeBuf, uint16(b))
}
asciiBuf = nil
unicode = true
}
if unicode {
unicodeBuf = append(unicodeBuf, r)
} else {
asciiBuf = append(asciiBuf, byte(r))
}
i++
}
if unicode {
return unicodeString(unicodeBuf)
}
return asciiString(asciiBuf)
}
func createGlobalObjectTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.global.ObjectPrototype
}
t.putStr("Object", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) })
t.putStr("Function", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) })
t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) })
t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) })
t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) })
t.putStr("BigInt", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) })
t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) })
t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) })
t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) })
t.putStr("Proxy", func(r *Runtime) Value { return valueProp(r.getProxy(), true, false, true) })
t.putStr("Reflect", func(r *Runtime) Value { return valueProp(r.getReflect(), true, false, true) })
t.putStr("Error", func(r *Runtime) Value { return valueProp(r.getError(), true, false, true) })
t.putStr("AggregateError", func(r *Runtime) Value { return valueProp(r.getAggregateError(), true, false, true) })
t.putStr("TypeError", func(r *Runtime) Value { return valueProp(r.getTypeError(), true, false, true) })
t.putStr("ReferenceError", func(r *Runtime) Value { return valueProp(r.getReferenceError(), true, false, true) })
t.putStr("SyntaxError", func(r *Runtime) Value { return valueProp(r.getSyntaxError(), true, false, true) })
t.putStr("RangeError", func(r *Runtime) Value { return valueProp(r.getRangeError(), true, false, true) })
t.putStr("EvalError", func(r *Runtime) Value { return valueProp(r.getEvalError(), true, false, true) })
t.putStr("URIError", func(r *Runtime) Value { return valueProp(r.getURIError(), true, false, true) })
t.putStr("GoError", func(r *Runtime) Value { return valueProp(r.getGoError(), true, false, true) })
t.putStr("eval", func(r *Runtime) Value { return valueProp(r.getEval(), true, false, true) })
t.putStr("Math", func(r *Runtime) Value { return valueProp(r.getMath(), true, false, true) })
t.putStr("JSON", func(r *Runtime) Value { return valueProp(r.getJSON(), true, false, true) })
addTypedArrays(t)
t.putStr("Symbol", func(r *Runtime) Value { return valueProp(r.getSymbol(), true, false, true) })
t.putStr("WeakSet", func(r *Runtime) Value { return valueProp(r.getWeakSet(), true, false, true) })
t.putStr("WeakMap", func(r *Runtime) Value { return valueProp(r.getWeakMap(), true, false, true) })
t.putStr("Map", func(r *Runtime) Value { return valueProp(r.getMap(), true, false, true) })
t.putStr("Set", func(r *Runtime) Value { return valueProp(r.getSet(), true, false, true) })
t.putStr("Promise", func(r *Runtime) Value { return valueProp(r.getPromise(), true, false, true) })
t.putStr("globalThis", func(r *Runtime) Value { return valueProp(r.globalObject, true, false, true) })
t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) })
t.putStr("undefined", func(r *Runtime) Value { return valueProp(_undefined, false, false, false) })
t.putStr("Infinity", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) })
t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.builtin_isNaN, "isNaN", 1) })
t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) })
t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) })
t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.builtin_isFinite, "isFinite", 1) })
t.putStr("decodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURI, "decodeURI", 1) })
t.putStr("decodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURIComponent, "decodeURIComponent", 1) })
t.putStr("encodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURI, "encodeURI", 1) })
t.putStr("encodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURIComponent, "encodeURIComponent", 1) })
t.putStr("escape", func(r *Runtime) Value { return r.methodProp(r.builtin_escape, "escape", 1) })
t.putStr("unescape", func(r *Runtime) Value { return r.methodProp(r.builtin_unescape, "unescape", 1) })
// TODO: Annex B
t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classGlobal), false, false, true) })
return t
}
var globalObjectTemplate *objectTemplate
var globalObjectTemplateOnce sync.Once
func getGlobalObjectTemplate() *objectTemplate {
globalObjectTemplateOnce.Do(func() {
globalObjectTemplate = createGlobalObjectTemplate()
})
return globalObjectTemplate
}
func (r *Runtime) getEval() *Object {
ret := r.global.Eval
if ret == nil {
ret = r.newNativeFunc(r.builtin_eval, "eval", 1)
r.global.Eval = ret
}
return ret
}
func digitVal(d byte) int {
var v byte
switch {
case '0' <= d && d <= '9':
v = d - '0'
case 'a' <= d && d <= 'z':
v = d - 'a' + 10
case 'A' <= d && d <= 'Z':
v = d - 'A' + 10
default:
return 36
}
return int(v)
}
// ECMAScript compatible version of strconv.ParseInt
func parseInt(s string, base int) (Value, error) {
var n int64
var err error
var cutoff, maxVal int64
var sign bool
i := 0
if len(s) < 1 {
err = strconv.ErrSyntax
goto Error
}
switch s[0] {
case '-':
sign = true
s = s[1:]
case '+':
s = s[1:]
}
if len(s) < 1 {
err = strconv.ErrSyntax
goto Error
}
// Look for hex prefix.
if s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X') {
if base == 0 || base == 16 {
base = 16
s = s[2:]
}
}
switch {
case len(s) < 1:
err = strconv.ErrSyntax
goto Error
case 2 <= base && base <= 36:
// valid base; nothing to do
case base == 0:
// Look for hex prefix.
switch {
case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
if len(s) < 3 {
err = strconv.ErrSyntax
goto Error
}
base = 16
s = s[2:]
default:
base = 10
}
default:
err = errors.New("invalid base " + strconv.Itoa(base))
goto Error
}
// Cutoff is the smallest number such that cutoff*base > maxInt64.
// Use compile-time constants for common cases.
switch base {
case 10:
cutoff = math.MaxInt64/10 + 1
case 16:
cutoff = math.MaxInt64/16 + 1
default:
cutoff = math.MaxInt64/int64(base) + 1
}
maxVal = math.MaxInt64
for ; i < len(s); i++ {
if n >= cutoff {
// n*base overflows
return parseLargeInt(float64(n), s[i:], base, sign)
}
v := digitVal(s[i])
if v >= base {
break
}
n *= int64(base)
n1 := n + int64(v)
if n1 < n || n1 > maxVal {
// n+v overflows
return parseLargeInt(float64(n)+float64(v), s[i+1:], base, sign)
}
n = n1
}
if i == 0 {
err = strconv.ErrSyntax
goto Error
}
if sign {
n = -n
}
return intToValue(n), nil
Error:
return _NaN, err
}
func parseLargeInt(n float64, s string, base int, sign bool) (Value, error) {
i := 0
b := float64(base)
for ; i < len(s); i++ {
v := digitVal(s[i])
if v >= base {
break
}
n = n*b + float64(v)
}
if sign {
n = -n
}
// We know it can't be represented as int, so use valueFloat instead of floatToValue
return valueFloat(n), nil
}
var (
uriUnescaped [256]bool
uriReserved [256]bool
uriReservedHash [256]bool
uriReservedUnescapedHash [256]bool
emptyEscapeSet [256]bool
)
func init() {
for _, c := range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()" {
uriUnescaped[c] = true
}
for _, c := range ";/?:@&=+$," {
uriReserved[c] = true
}
for i := 0; i < 256; i++ {
if uriUnescaped[i] || uriReserved[i] {
uriReservedUnescapedHash[i] = true
}
uriReservedHash[i] = uriReserved[i]
}
uriReservedUnescapedHash['#'] = true
uriReservedHash['#'] = true
}
+542
View File
@@ -0,0 +1,542 @@
package goja
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
"github.com/dop251/goja/unistring"
)
const hex = "0123456789abcdef"
func (r *Runtime) builtinJSON_parse(call FunctionCall) Value {
d := json.NewDecoder(strings.NewReader(call.Argument(0).toString().String()))
value, err := r.builtinJSON_decodeValue(d)
if errors.Is(err, io.EOF) {
panic(r.newError(r.getSyntaxError(), "Unexpected end of JSON input (%v)", err.Error()))
}
if err != nil {
panic(r.newError(r.getSyntaxError(), err.Error()))
}
if tok, err := d.Token(); err != io.EOF {
panic(r.newError(r.getSyntaxError(), "Unexpected token at the end: %v", tok))
}
var reviver func(FunctionCall) Value
if arg1 := call.Argument(1); arg1 != _undefined {
reviver, _ = arg1.ToObject(r).self.assertCallable()
}
if reviver != nil {
root := r.NewObject()
createDataPropertyOrThrow(root, stringEmpty, value)
return r.builtinJSON_reviveWalk(reviver, root, stringEmpty)
}
return value
}
func (r *Runtime) builtinJSON_decodeToken(d *json.Decoder, tok json.Token) (Value, error) {
switch tok := tok.(type) {
case json.Delim:
switch tok {
case '{':
return r.builtinJSON_decodeObject(d)
case '[':
return r.builtinJSON_decodeArray(d)
}
case nil:
return _null, nil
case string:
return newStringValue(tok), nil
case float64:
return floatToValue(tok), nil
case bool:
if tok {
return valueTrue, nil
}
return valueFalse, nil
}
return nil, fmt.Errorf("Unexpected token (%T): %v", tok, tok)
}
func (r *Runtime) builtinJSON_decodeValue(d *json.Decoder) (Value, error) {
tok, err := d.Token()
if err != nil {
return nil, err
}
return r.builtinJSON_decodeToken(d, tok)
}
func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) {
object := r.NewObject()
for {
key, end, err := r.builtinJSON_decodeObjectKey(d)
if err != nil {
return nil, err
}
if end {
break
}
value, err := r.builtinJSON_decodeValue(d)
if err != nil {
return nil, err
}
object.self._putProp(unistring.NewFromString(key), value, true, true, true)
}
return object, nil
}
func (r *Runtime) builtinJSON_decodeObjectKey(d *json.Decoder) (string, bool, error) {
tok, err := d.Token()
if err != nil {
return "", false, err
}
switch tok := tok.(type) {
case json.Delim:
if tok == '}' {
return "", true, nil
}
case string:
return tok, false, nil
}
return "", false, fmt.Errorf("Unexpected token (%T): %v", tok, tok)
}
func (r *Runtime) builtinJSON_decodeArray(d *json.Decoder) (*Object, error) {
var arrayValue []Value
for {
tok, err := d.Token()
if err != nil {
return nil, err
}
if delim, ok := tok.(json.Delim); ok {
if delim == ']' {
break
}
}
value, err := r.builtinJSON_decodeToken(d, tok)
if err != nil {
return nil, err
}
arrayValue = append(arrayValue, value)
}
return r.newArrayValues(arrayValue), nil
}
func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value {
value := nilSafe(holder.get(name, nil))
if object, ok := value.(*Object); ok {
if isArray(object) {
length := toLength(object.self.getStr("length", nil))
for index := int64(0); index < length; index++ {
name := asciiString(strconv.FormatInt(index, 10))
value := r.builtinJSON_reviveWalk(reviver, object, name)
if value == _undefined {
object.delete(name, false)
} else {
createDataProperty(object, name, value)
}
}
} else {
for _, name := range object.self.stringKeys(false, nil) {
value := r.builtinJSON_reviveWalk(reviver, object, name)
if value == _undefined {
object.self.deleteStr(name.string(), false)
} else {
createDataProperty(object, name, value)
}
}
}
}
return reviver(FunctionCall{
This: holder,
Arguments: []Value{name, value},
})
}
type _builtinJSON_stringifyContext struct {
r *Runtime
stack []*Object
propertyList []Value
replacerFunction func(FunctionCall) Value
gap, indent string
buf bytes.Buffer
allAscii bool
}
func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value {
ctx := _builtinJSON_stringifyContext{
r: r,
allAscii: true,
}
replacer, _ := call.Argument(1).(*Object)
if replacer != nil {
if isArray(replacer) {
length := toLength(replacer.self.getStr("length", nil))
seen := map[string]bool{}
propertyList := make([]Value, length)
length = 0
for index := range propertyList {
var name string
value := replacer.self.getIdx(valueInt(int64(index)), nil)
switch v := value.(type) {
case valueFloat, valueInt, String:
name = value.String()
case *Object:
switch v.self.className() {
case classNumber, classString:
name = value.String()
default:
continue
}
default:
continue
}
if seen[name] {
continue
}
seen[name] = true
propertyList[length] = newStringValue(name)
length += 1
}
ctx.propertyList = propertyList[0:length]
} else if c, ok := replacer.self.assertCallable(); ok {
ctx.replacerFunction = c
}
}
if spaceValue := call.Argument(2); spaceValue != _undefined {
if o, ok := spaceValue.(*Object); ok {
switch oImpl := o.self.(type) {
case *primitiveValueObject:
switch oImpl.pValue.(type) {
case valueInt, valueFloat:
spaceValue = o.ToNumber()
}
case *stringObject:
spaceValue = o.ToString()
}
}
isNum := false
var num int64
if i, ok := spaceValue.(valueInt); ok {
num = int64(i)
isNum = true
} else if f, ok := spaceValue.(valueFloat); ok {
num = int64(f)
isNum = true
}
if isNum {
if num > 0 {
if num > 10 {
num = 10
}
ctx.gap = strings.Repeat(" ", int(num))
}
} else {
if s, ok := spaceValue.(String); ok {
str := s.String()
if len(str) > 10 {
ctx.gap = str[:10]
} else {
ctx.gap = str
}
}
}
}
if ctx.do(call.Argument(0)) {
if ctx.allAscii {
return asciiString(ctx.buf.String())
} else {
return &importedString{
s: ctx.buf.String(),
}
}
}
return _undefined
}
func (ctx *_builtinJSON_stringifyContext) do(v Value) bool {
holder := ctx.r.NewObject()
createDataPropertyOrThrow(holder, stringEmpty, v)
return ctx.str(stringEmpty, holder)
}
func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool {
value := nilSafe(holder.get(key, nil))
switch value.(type) {
case *Object, *valueBigInt:
if toJSON, ok := ctx.r.getVStr(value, "toJSON").(*Object); ok {
if c, ok := toJSON.self.assertCallable(); ok {
value = c(FunctionCall{
This: value,
Arguments: []Value{key},
})
}
}
}
if ctx.replacerFunction != nil {
value = ctx.replacerFunction(FunctionCall{
This: holder,
Arguments: []Value{key, value},
})
}
if o, ok := value.(*Object); ok {
switch o1 := o.self.(type) {
case *primitiveValueObject:
switch pValue := o1.pValue.(type) {
case valueInt, valueFloat:
value = o.ToNumber()
default:
value = pValue
}
case *stringObject:
value = o.toString()
case *objectGoReflect:
if o1.toJson != nil {
value = ctx.r.ToValue(o1.toJson())
} else if v, ok := o1.origValue.Interface().(json.Marshaler); ok {
b, err := v.MarshalJSON()
if err != nil {
panic(ctx.r.NewGoError(err))
}
ctx.buf.Write(b)
ctx.allAscii = false
return true
} else {
switch o1.className() {
case classNumber:
value = o1.val.ordinaryToPrimitiveNumber()
case classString:
value = o1.val.ordinaryToPrimitiveString()
case classBoolean:
if o.ToInteger() != 0 {
value = valueTrue
} else {
value = valueFalse
}
}
if o1.exportType() == typeBigInt {
value = o1.val.ordinaryToPrimitiveNumber()
}
}
}
}
switch value1 := value.(type) {
case valueBool:
if value1 {
ctx.buf.WriteString("true")
} else {
ctx.buf.WriteString("false")
}
case String:
ctx.quote(value1)
case valueInt:
ctx.buf.WriteString(value.String())
case valueFloat:
if !math.IsNaN(float64(value1)) && !math.IsInf(float64(value1), 0) {
ctx.buf.WriteString(value.String())
} else {
ctx.buf.WriteString("null")
}
case valueNull:
ctx.buf.WriteString("null")
case *valueBigInt:
ctx.r.typeErrorResult(true, "Do not know how to serialize a BigInt")
case *Object:
for _, object := range ctx.stack {
if value1.SameAs(object) {
ctx.r.typeErrorResult(true, "Converting circular structure to JSON")
}
}
ctx.stack = append(ctx.stack, value1)
defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
if _, ok := value1.self.assertCallable(); !ok {
if isArray(value1) {
ctx.ja(value1)
} else {
ctx.jo(value1)
}
} else {
return false
}
default:
return false
}
return true
}
func (ctx *_builtinJSON_stringifyContext) ja(array *Object) {
var stepback string
if ctx.gap != "" {
stepback = ctx.indent
ctx.indent += ctx.gap
}
length := toLength(array.self.getStr("length", nil))
if length == 0 {
ctx.buf.WriteString("[]")
return
}
ctx.buf.WriteByte('[')
var separator string
if ctx.gap != "" {
ctx.buf.WriteByte('\n')
ctx.buf.WriteString(ctx.indent)
separator = ",\n" + ctx.indent
} else {
separator = ","
}
for i := int64(0); i < length; i++ {
if !ctx.str(asciiString(strconv.FormatInt(i, 10)), array) {
ctx.buf.WriteString("null")
}
if i < length-1 {
ctx.buf.WriteString(separator)
}
}
if ctx.gap != "" {
ctx.buf.WriteByte('\n')
ctx.buf.WriteString(stepback)
ctx.indent = stepback
}
ctx.buf.WriteByte(']')
}
func (ctx *_builtinJSON_stringifyContext) jo(object *Object) {
var stepback string
if ctx.gap != "" {
stepback = ctx.indent
ctx.indent += ctx.gap
}
ctx.buf.WriteByte('{')
mark := ctx.buf.Len()
var separator string
if ctx.gap != "" {
ctx.buf.WriteByte('\n')
ctx.buf.WriteString(ctx.indent)
separator = ",\n" + ctx.indent
} else {
separator = ","
}
var props []Value
if ctx.propertyList == nil {
props = object.self.stringKeys(false, nil)
} else {
props = ctx.propertyList
}
empty := true
for _, name := range props {
off := ctx.buf.Len()
if !empty {
ctx.buf.WriteString(separator)
}
ctx.quote(name.toString())
if ctx.gap != "" {
ctx.buf.WriteString(": ")
} else {
ctx.buf.WriteByte(':')
}
if ctx.str(name, object) {
if empty {
empty = false
}
} else {
ctx.buf.Truncate(off)
}
}
if empty {
ctx.buf.Truncate(mark)
} else {
if ctx.gap != "" {
ctx.buf.WriteByte('\n')
ctx.buf.WriteString(stepback)
ctx.indent = stepback
}
}
ctx.buf.WriteByte('}')
}
func (ctx *_builtinJSON_stringifyContext) quote(str String) {
ctx.buf.WriteByte('"')
reader := &lenientUtf16Decoder{utf16Reader: str.utf16Reader()}
for {
r, _, err := reader.ReadRune()
if err != nil {
break
}
switch r {
case '"', '\\':
ctx.buf.WriteByte('\\')
ctx.buf.WriteByte(byte(r))
case 0x08:
ctx.buf.WriteString(`\b`)
case 0x09:
ctx.buf.WriteString(`\t`)
case 0x0A:
ctx.buf.WriteString(`\n`)
case 0x0C:
ctx.buf.WriteString(`\f`)
case 0x0D:
ctx.buf.WriteString(`\r`)
default:
if r < 0x20 {
ctx.buf.WriteString(`\u00`)
ctx.buf.WriteByte(hex[r>>4])
ctx.buf.WriteByte(hex[r&0xF])
} else {
if utf16.IsSurrogate(r) {
ctx.buf.WriteString(`\u`)
ctx.buf.WriteByte(hex[r>>12])
ctx.buf.WriteByte(hex[(r>>8)&0xF])
ctx.buf.WriteByte(hex[(r>>4)&0xF])
ctx.buf.WriteByte(hex[r&0xF])
} else {
ctx.buf.WriteRune(r)
if ctx.allAscii && r >= utf8.RuneSelf {
ctx.allAscii = false
}
}
}
}
}
ctx.buf.WriteByte('"')
}
func (r *Runtime) getJSON() *Object {
ret := r.global.JSON
if ret == nil {
JSON := r.newBaseObject(r.global.ObjectPrototype, classObject)
ret = JSON.val
r.global.JSON = ret
JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, "parse", 2), true, false, true)
JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, "stringify", 3), true, false, true)
JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true))
}
return ret
}
+342
View File
@@ -0,0 +1,342 @@
package goja
import (
"reflect"
)
var mapExportType = reflect.TypeOf([][2]interface{}{})
type mapObject struct {
baseObject
m *orderedMap
}
type mapIterObject struct {
baseObject
iter *orderedMapIter
kind iterationKind
}
func (o *mapIterObject) next() Value {
if o.iter == nil {
return o.val.runtime.createIterResultObject(_undefined, true)
}
entry := o.iter.next()
if entry == nil {
o.iter = nil
return o.val.runtime.createIterResultObject(_undefined, true)
}
var result Value
switch o.kind {
case iterationKindKey:
result = entry.key
case iterationKindValue:
result = entry.value
default:
result = o.val.runtime.newArrayValues([]Value{entry.key, entry.value})
}
return o.val.runtime.createIterResultObject(result, false)
}
func (mo *mapObject) init() {
mo.baseObject.init()
mo.m = newOrderedMap(mo.val.runtime.getHash())
}
func (mo *mapObject) exportType() reflect.Type {
return mapExportType
}
func (mo *mapObject) export(ctx *objectExportCtx) interface{} {
m := make([][2]interface{}, mo.m.size)
ctx.put(mo.val, m)
iter := mo.m.newIter()
for i := 0; i < len(m); i++ {
entry := iter.next()
if entry == nil {
break
}
m[i][0] = exportValue(entry.key, ctx)
m[i][1] = exportValue(entry.value, ctx)
}
return m
}
func (mo *mapObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
dst.Set(reflect.MakeMap(typ))
ctx.putTyped(mo.val, typ, dst.Interface())
keyTyp := typ.Key()
elemTyp := typ.Elem()
iter := mo.m.newIter()
r := mo.val.runtime
for {
entry := iter.next()
if entry == nil {
break
}
keyVal := reflect.New(keyTyp).Elem()
err := r.toReflectValue(entry.key, keyVal, ctx)
if err != nil {
return err
}
elemVal := reflect.New(elemTyp).Elem()
err = r.toReflectValue(entry.value, elemVal, ctx)
if err != nil {
return err
}
dst.SetMapIndex(keyVal, elemVal)
}
return nil
}
func (r *Runtime) mapProto_clear(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method Map.prototype.clear called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
mo.m.clear()
return _undefined
}
func (r *Runtime) mapProto_delete(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method Map.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
return r.toBoolean(mo.m.remove(call.Argument(0)))
}
func (r *Runtime) mapProto_get(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method Map.prototype.get called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
return nilSafe(mo.m.get(call.Argument(0)))
}
func (r *Runtime) mapProto_has(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method Map.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
if mo.m.has(call.Argument(0)) {
return valueTrue
}
return valueFalse
}
func (r *Runtime) mapProto_set(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method Map.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
mo.m.set(call.Argument(0), call.Argument(1))
return call.This
}
func (r *Runtime) mapProto_entries(call FunctionCall) Value {
return r.createMapIterator(call.This, iterationKindKeyValue)
}
func (r *Runtime) mapProto_forEach(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method Map.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable()
if !ok {
panic(r.NewTypeError("object is not a function %s"))
}
t := call.Argument(1)
iter := mo.m.newIter()
for {
entry := iter.next()
if entry == nil {
break
}
callbackFn(FunctionCall{This: t, Arguments: []Value{entry.value, entry.key, thisObj}})
}
return _undefined
}
func (r *Runtime) mapProto_keys(call FunctionCall) Value {
return r.createMapIterator(call.This, iterationKindKey)
}
func (r *Runtime) mapProto_values(call FunctionCall) Value {
return r.createMapIterator(call.This, iterationKindValue)
}
func (r *Runtime) mapProto_getSize(call FunctionCall) Value {
thisObj := r.toObject(call.This)
mo, ok := thisObj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Method get Map.prototype.size called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
return intToValue(int64(mo.m.size))
}
func (r *Runtime) builtin_newMap(args []Value, newTarget *Object) *Object {
if newTarget == nil {
panic(r.needNew("Map"))
}
proto := r.getPrototypeFromCtor(newTarget, r.global.Map, r.global.MapPrototype)
o := &Object{runtime: r}
mo := &mapObject{}
mo.class = classObject
mo.val = o
mo.extensible = true
o.self = mo
mo.prototype = proto
mo.init()
if len(args) > 0 {
if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
adder := mo.getStr("set", nil)
adderFn := toMethod(adder)
if adderFn == nil {
panic(r.NewTypeError("Map.set in missing"))
}
iter := r.getIterator(arg, nil)
i0 := valueInt(0)
i1 := valueInt(1)
if adder == r.global.mapAdder {
iter.iterate(func(item Value) {
itemObj := r.toObject(item)
k := nilSafe(itemObj.self.getIdx(i0, nil))
v := nilSafe(itemObj.self.getIdx(i1, nil))
mo.m.set(k, v)
})
} else {
iter.iterate(func(item Value) {
itemObj := r.toObject(item)
k := itemObj.self.getIdx(i0, nil)
v := itemObj.self.getIdx(i1, nil)
adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
})
}
}
}
return o
}
func (r *Runtime) createMapIterator(mapValue Value, kind iterationKind) Value {
obj := r.toObject(mapValue)
mapObj, ok := obj.self.(*mapObject)
if !ok {
panic(r.NewTypeError("Object is not a Map"))
}
o := &Object{runtime: r}
mi := &mapIterObject{
iter: mapObj.m.newIter(),
kind: kind,
}
mi.class = classObject
mi.val = o
mi.extensible = true
o.self = mi
mi.prototype = r.getMapIteratorPrototype()
mi.init()
return o
}
func (r *Runtime) mapIterProto_next(call FunctionCall) Value {
thisObj := r.toObject(call.This)
if iter, ok := thisObj.self.(*mapIterObject); ok {
return iter.next()
}
panic(r.NewTypeError("Method Map Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
func (r *Runtime) createMapProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
o._putProp("constructor", r.getMap(), true, false, true)
o._putProp("clear", r.newNativeFunc(r.mapProto_clear, "clear", 0), true, false, true)
r.global.mapAdder = r.newNativeFunc(r.mapProto_set, "set", 2)
o._putProp("set", r.global.mapAdder, true, false, true)
o._putProp("delete", r.newNativeFunc(r.mapProto_delete, "delete", 1), true, false, true)
o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, "forEach", 1), true, false, true)
o._putProp("has", r.newNativeFunc(r.mapProto_has, "has", 1), true, false, true)
o._putProp("get", r.newNativeFunc(r.mapProto_get, "get", 1), true, false, true)
o.setOwnStr("size", &valueProperty{
getterFunc: r.newNativeFunc(r.mapProto_getSize, "get size", 0),
accessor: true,
writable: true,
configurable: true,
}, true)
o._putProp("keys", r.newNativeFunc(r.mapProto_keys, "keys", 0), true, false, true)
o._putProp("values", r.newNativeFunc(r.mapProto_values, "values", 0), true, false, true)
entriesFunc := r.newNativeFunc(r.mapProto_entries, "entries", 0)
o._putProp("entries", entriesFunc, true, false, true)
o._putSym(SymIterator, valueProp(entriesFunc, true, false, true))
o._putSym(SymToStringTag, valueProp(asciiString(classMap), false, false, true))
return o
}
func (r *Runtime) createMap(val *Object) objectImpl {
o := r.newNativeConstructOnly(val, r.builtin_newMap, r.getMapPrototype(), "Map", 0)
r.putSpeciesReturnThis(o)
return o
}
func (r *Runtime) createMapIterProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
o._putProp("next", r.newNativeFunc(r.mapIterProto_next, "next", 0), true, false, true)
o._putSym(SymToStringTag, valueProp(asciiString(classMapIterator), false, false, true))
return o
}
func (r *Runtime) getMapIteratorPrototype() *Object {
var o *Object
if o = r.global.MapIteratorPrototype; o == nil {
o = &Object{runtime: r}
r.global.MapIteratorPrototype = o
o.self = r.createMapIterProto(o)
}
return o
}
func (r *Runtime) getMapPrototype() *Object {
ret := r.global.MapPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.MapPrototype = ret
ret.self = r.createMapProto(ret)
}
return ret
}
func (r *Runtime) getMap() *Object {
ret := r.global.Map
if ret == nil {
ret = &Object{runtime: r}
r.global.Map = ret
ret.self = r.createMap(ret)
}
return ret
}
+358
View File
@@ -0,0 +1,358 @@
package goja
import (
"math"
"math/bits"
"sync"
)
func (r *Runtime) math_abs(call FunctionCall) Value {
return floatToValue(math.Abs(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_acos(call FunctionCall) Value {
return floatToValue(math.Acos(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_acosh(call FunctionCall) Value {
return floatToValue(math.Acosh(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_asin(call FunctionCall) Value {
return floatToValue(math.Asin(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_asinh(call FunctionCall) Value {
return floatToValue(math.Asinh(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_atan(call FunctionCall) Value {
return floatToValue(math.Atan(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_atanh(call FunctionCall) Value {
return floatToValue(math.Atanh(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_atan2(call FunctionCall) Value {
y := call.Argument(0).ToFloat()
x := call.Argument(1).ToFloat()
return floatToValue(math.Atan2(y, x))
}
func (r *Runtime) math_cbrt(call FunctionCall) Value {
return floatToValue(math.Cbrt(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_ceil(call FunctionCall) Value {
return floatToValue(math.Ceil(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_clz32(call FunctionCall) Value {
return intToValue(int64(bits.LeadingZeros32(toUint32(call.Argument(0)))))
}
func (r *Runtime) math_cos(call FunctionCall) Value {
return floatToValue(math.Cos(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_cosh(call FunctionCall) Value {
return floatToValue(math.Cosh(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_exp(call FunctionCall) Value {
return floatToValue(math.Exp(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_expm1(call FunctionCall) Value {
return floatToValue(math.Expm1(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_floor(call FunctionCall) Value {
return floatToValue(math.Floor(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_fround(call FunctionCall) Value {
return floatToValue(float64(float32(call.Argument(0).ToFloat())))
}
func (r *Runtime) math_hypot(call FunctionCall) Value {
var max float64
var hasNaN bool
absValues := make([]float64, 0, len(call.Arguments))
for _, v := range call.Arguments {
arg := nilSafe(v).ToFloat()
if math.IsNaN(arg) {
hasNaN = true
} else {
abs := math.Abs(arg)
if abs > max {
max = abs
}
absValues = append(absValues, abs)
}
}
if math.IsInf(max, 1) {
return _positiveInf
}
if hasNaN {
return _NaN
}
if max == 0 {
return _positiveZero
}
// Kahan summation to avoid rounding errors.
// Normalize the numbers to the largest one to avoid overflow.
var sum, compensation float64
for _, n := range absValues {
n /= max
summand := n*n - compensation
preliminary := sum + summand
compensation = (preliminary - sum) - summand
sum = preliminary
}
return floatToValue(math.Sqrt(sum) * max)
}
func (r *Runtime) math_imul(call FunctionCall) Value {
x := toUint32(call.Argument(0))
y := toUint32(call.Argument(1))
return intToValue(int64(int32(x * y)))
}
func (r *Runtime) math_log(call FunctionCall) Value {
return floatToValue(math.Log(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_log1p(call FunctionCall) Value {
return floatToValue(math.Log1p(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_log10(call FunctionCall) Value {
return floatToValue(math.Log10(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_log2(call FunctionCall) Value {
return floatToValue(math.Log2(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_max(call FunctionCall) Value {
result := math.Inf(-1)
args := call.Arguments
for i, arg := range args {
n := nilSafe(arg).ToFloat()
if math.IsNaN(n) {
args = args[i+1:]
goto NaNLoop
}
result = math.Max(result, n)
}
return floatToValue(result)
NaNLoop:
// All arguments still need to be coerced to number according to the specs.
for _, arg := range args {
nilSafe(arg).ToFloat()
}
return _NaN
}
func (r *Runtime) math_min(call FunctionCall) Value {
result := math.Inf(1)
args := call.Arguments
for i, arg := range args {
n := nilSafe(arg).ToFloat()
if math.IsNaN(n) {
args = args[i+1:]
goto NaNLoop
}
result = math.Min(result, n)
}
return floatToValue(result)
NaNLoop:
// All arguments still need to be coerced to number according to the specs.
for _, arg := range args {
nilSafe(arg).ToFloat()
}
return _NaN
}
func pow(x, y Value) Value {
if x, ok := x.(valueInt); ok {
if y, ok := y.(valueInt); ok && y >= 0 {
if y == 0 {
return intToValue(1)
}
if x == 0 {
return intToValue(0)
}
ip := ipow(int64(x), int64(y))
if ip != 0 {
return intToValue(ip)
}
}
}
xf := x.ToFloat()
yf := y.ToFloat()
if math.Abs(xf) == 1 && math.IsInf(yf, 0) {
return _NaN
}
if xf == 1 && math.IsNaN(yf) {
return _NaN
}
return floatToValue(math.Pow(xf, yf))
}
func (r *Runtime) math_pow(call FunctionCall) Value {
return pow(call.Argument(0), call.Argument(1))
}
func (r *Runtime) math_random(call FunctionCall) Value {
return floatToValue(r.rand())
}
func (r *Runtime) math_round(call FunctionCall) Value {
f := call.Argument(0).ToFloat()
if math.IsNaN(f) {
return _NaN
}
if f == 0 && math.Signbit(f) {
return _negativeZero
}
t := math.Trunc(f)
if f >= 0 {
if f-t >= 0.5 {
return floatToValue(t + 1)
}
} else {
if t-f > 0.5 {
return floatToValue(t - 1)
}
}
return floatToValue(t)
}
func (r *Runtime) math_sign(call FunctionCall) Value {
arg := call.Argument(0)
num := arg.ToFloat()
if math.IsNaN(num) || num == 0 { // this will match -0 too
return arg
}
if num > 0 {
return intToValue(1)
}
return intToValue(-1)
}
func (r *Runtime) math_sin(call FunctionCall) Value {
return floatToValue(math.Sin(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_sinh(call FunctionCall) Value {
return floatToValue(math.Sinh(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_sqrt(call FunctionCall) Value {
return floatToValue(math.Sqrt(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_tan(call FunctionCall) Value {
return floatToValue(math.Tan(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_tanh(call FunctionCall) Value {
return floatToValue(math.Tanh(call.Argument(0).ToFloat()))
}
func (r *Runtime) math_trunc(call FunctionCall) Value {
arg := call.Argument(0)
if i, ok := arg.(valueInt); ok {
return i
}
return floatToValue(math.Trunc(arg.ToFloat()))
}
func createMathTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.global.ObjectPrototype
}
t.putStr("E", func(r *Runtime) Value { return valueProp(valueFloat(math.E), false, false, false) })
t.putStr("LN10", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln10), false, false, false) })
t.putStr("LN2", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln2), false, false, false) })
t.putStr("LOG10E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log10E), false, false, false) })
t.putStr("LOG2E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log2E), false, false, false) })
t.putStr("PI", func(r *Runtime) Value { return valueProp(valueFloat(math.Pi), false, false, false) })
t.putStr("SQRT1_2", func(r *Runtime) Value { return valueProp(valueFloat(sqrt1_2), false, false, false) })
t.putStr("SQRT2", func(r *Runtime) Value { return valueProp(valueFloat(math.Sqrt2), false, false, false) })
t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classMath), false, false, true) })
t.putStr("abs", func(r *Runtime) Value { return r.methodProp(r.math_abs, "abs", 1) })
t.putStr("acos", func(r *Runtime) Value { return r.methodProp(r.math_acos, "acos", 1) })
t.putStr("acosh", func(r *Runtime) Value { return r.methodProp(r.math_acosh, "acosh", 1) })
t.putStr("asin", func(r *Runtime) Value { return r.methodProp(r.math_asin, "asin", 1) })
t.putStr("asinh", func(r *Runtime) Value { return r.methodProp(r.math_asinh, "asinh", 1) })
t.putStr("atan", func(r *Runtime) Value { return r.methodProp(r.math_atan, "atan", 1) })
t.putStr("atanh", func(r *Runtime) Value { return r.methodProp(r.math_atanh, "atanh", 1) })
t.putStr("atan2", func(r *Runtime) Value { return r.methodProp(r.math_atan2, "atan2", 2) })
t.putStr("cbrt", func(r *Runtime) Value { return r.methodProp(r.math_cbrt, "cbrt", 1) })
t.putStr("ceil", func(r *Runtime) Value { return r.methodProp(r.math_ceil, "ceil", 1) })
t.putStr("clz32", func(r *Runtime) Value { return r.methodProp(r.math_clz32, "clz32", 1) })
t.putStr("cos", func(r *Runtime) Value { return r.methodProp(r.math_cos, "cos", 1) })
t.putStr("cosh", func(r *Runtime) Value { return r.methodProp(r.math_cosh, "cosh", 1) })
t.putStr("exp", func(r *Runtime) Value { return r.methodProp(r.math_exp, "exp", 1) })
t.putStr("expm1", func(r *Runtime) Value { return r.methodProp(r.math_expm1, "expm1", 1) })
t.putStr("floor", func(r *Runtime) Value { return r.methodProp(r.math_floor, "floor", 1) })
t.putStr("fround", func(r *Runtime) Value { return r.methodProp(r.math_fround, "fround", 1) })
t.putStr("hypot", func(r *Runtime) Value { return r.methodProp(r.math_hypot, "hypot", 2) })
t.putStr("imul", func(r *Runtime) Value { return r.methodProp(r.math_imul, "imul", 2) })
t.putStr("log", func(r *Runtime) Value { return r.methodProp(r.math_log, "log", 1) })
t.putStr("log1p", func(r *Runtime) Value { return r.methodProp(r.math_log1p, "log1p", 1) })
t.putStr("log10", func(r *Runtime) Value { return r.methodProp(r.math_log10, "log10", 1) })
t.putStr("log2", func(r *Runtime) Value { return r.methodProp(r.math_log2, "log2", 1) })
t.putStr("max", func(r *Runtime) Value { return r.methodProp(r.math_max, "max", 2) })
t.putStr("min", func(r *Runtime) Value { return r.methodProp(r.math_min, "min", 2) })
t.putStr("pow", func(r *Runtime) Value { return r.methodProp(r.math_pow, "pow", 2) })
t.putStr("random", func(r *Runtime) Value { return r.methodProp(r.math_random, "random", 0) })
t.putStr("round", func(r *Runtime) Value { return r.methodProp(r.math_round, "round", 1) })
t.putStr("sign", func(r *Runtime) Value { return r.methodProp(r.math_sign, "sign", 1) })
t.putStr("sin", func(r *Runtime) Value { return r.methodProp(r.math_sin, "sin", 1) })
t.putStr("sinh", func(r *Runtime) Value { return r.methodProp(r.math_sinh, "sinh", 1) })
t.putStr("sqrt", func(r *Runtime) Value { return r.methodProp(r.math_sqrt, "sqrt", 1) })
t.putStr("tan", func(r *Runtime) Value { return r.methodProp(r.math_tan, "tan", 1) })
t.putStr("tanh", func(r *Runtime) Value { return r.methodProp(r.math_tanh, "tanh", 1) })
t.putStr("trunc", func(r *Runtime) Value { return r.methodProp(r.math_trunc, "trunc", 1) })
return t
}
var mathTemplate *objectTemplate
var mathTemplateOnce sync.Once
func getMathTemplate() *objectTemplate {
mathTemplateOnce.Do(func() {
mathTemplate = createMathTemplate()
})
return mathTemplate
}
func (r *Runtime) getMath() *Object {
ret := r.global.Math
if ret == nil {
ret = &Object{runtime: r}
r.global.Math = ret
r.newTemplatedObject(getMathTemplate(), ret)
}
return ret
}
+303
View File
@@ -0,0 +1,303 @@
package goja
import (
"math"
"sync"
"github.com/dop251/goja/ftoa"
)
func (r *Runtime) toNumber(v Value) Value {
switch t := v.(type) {
case valueFloat, valueInt:
return v
case *Object:
switch t := t.self.(type) {
case *primitiveValueObject:
return r.toNumber(t.pValue)
case *objectGoReflect:
if t.class == classNumber && t.valueOf != nil {
return t.valueOf()
}
}
if t == r.global.NumberPrototype {
return _positiveZero
}
}
panic(r.NewTypeError("Value is not a number: %s", v))
}
func (r *Runtime) numberproto_valueOf(call FunctionCall) Value {
return r.toNumber(call.This)
}
func (r *Runtime) numberproto_toString(call FunctionCall) Value {
var numVal Value
switch t := call.This.(type) {
case valueFloat, valueInt:
numVal = t
case *Object:
switch t := t.self.(type) {
case *primitiveValueObject:
numVal = r.toNumber(t.pValue)
case *objectGoReflect:
if t.class == classNumber {
if t.toString != nil {
return t.toString()
}
if t.valueOf != nil {
numVal = t.valueOf()
}
}
}
if t == r.global.NumberPrototype {
return asciiString("0")
}
}
if numVal == nil {
panic(r.NewTypeError("Value is not a number"))
}
var radix int
if arg := call.Argument(0); arg != _undefined {
radix = int(arg.ToInteger())
} else {
radix = 10
}
if radix < 2 || radix > 36 {
panic(r.newError(r.getRangeError(), "toString() radix argument must be between 2 and 36"))
}
num := numVal.ToFloat()
if math.IsNaN(num) {
return stringNaN
}
if math.IsInf(num, 1) {
return stringInfinity
}
if math.IsInf(num, -1) {
return stringNegInfinity
}
if radix == 10 {
return asciiString(fToStr(num, ftoa.ModeStandard, 0))
}
return asciiString(ftoa.FToBaseStr(num, radix))
}
func (r *Runtime) numberproto_toFixed(call FunctionCall) Value {
num := r.toNumber(call.This).ToFloat()
prec := call.Argument(0).ToInteger()
if prec < 0 || prec > 100 {
panic(r.newError(r.getRangeError(), "toFixed() precision must be between 0 and 100"))
}
if math.IsNaN(num) {
return stringNaN
}
return asciiString(fToStr(num, ftoa.ModeFixed, int(prec)))
}
func (r *Runtime) numberproto_toExponential(call FunctionCall) Value {
num := r.toNumber(call.This).ToFloat()
precVal := call.Argument(0)
var prec int64
if precVal == _undefined {
return asciiString(fToStr(num, ftoa.ModeStandardExponential, 0))
} else {
prec = precVal.ToInteger()
}
if math.IsNaN(num) {
return stringNaN
}
if math.IsInf(num, 1) {
return stringInfinity
}
if math.IsInf(num, -1) {
return stringNegInfinity
}
if prec < 0 || prec > 100 {
panic(r.newError(r.getRangeError(), "toExponential() precision must be between 0 and 100"))
}
return asciiString(fToStr(num, ftoa.ModeExponential, int(prec+1)))
}
func (r *Runtime) numberproto_toPrecision(call FunctionCall) Value {
numVal := r.toNumber(call.This)
precVal := call.Argument(0)
if precVal == _undefined {
return numVal.toString()
}
num := numVal.ToFloat()
prec := precVal.ToInteger()
if math.IsNaN(num) {
return stringNaN
}
if math.IsInf(num, 1) {
return stringInfinity
}
if math.IsInf(num, -1) {
return stringNegInfinity
}
if prec < 1 || prec > 100 {
panic(r.newError(r.getRangeError(), "toPrecision() precision must be between 1 and 100"))
}
return asciiString(fToStr(num, ftoa.ModePrecision, int(prec)))
}
func (r *Runtime) number_isFinite(call FunctionCall) Value {
switch arg := call.Argument(0).(type) {
case valueInt:
return valueTrue
case valueFloat:
f := float64(arg)
return r.toBoolean(!math.IsInf(f, 0) && !math.IsNaN(f))
default:
return valueFalse
}
}
func (r *Runtime) number_isInteger(call FunctionCall) Value {
switch arg := call.Argument(0).(type) {
case valueInt:
return valueTrue
case valueFloat:
f := float64(arg)
return r.toBoolean(!math.IsNaN(f) && !math.IsInf(f, 0) && math.Floor(f) == f)
default:
return valueFalse
}
}
func (r *Runtime) number_isNaN(call FunctionCall) Value {
if f, ok := call.Argument(0).(valueFloat); ok && math.IsNaN(float64(f)) {
return valueTrue
}
return valueFalse
}
func (r *Runtime) number_isSafeInteger(call FunctionCall) Value {
arg := call.Argument(0)
if i, ok := arg.(valueInt); ok && i >= -(maxInt-1) && i <= maxInt-1 {
return valueTrue
}
if arg == _negativeZero {
return valueTrue
}
return valueFalse
}
func createNumberProtoTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.global.ObjectPrototype
}
t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) })
t.putStr("toExponential", func(r *Runtime) Value { return r.methodProp(r.numberproto_toExponential, "toExponential", 1) })
t.putStr("toFixed", func(r *Runtime) Value { return r.methodProp(r.numberproto_toFixed, "toFixed", 1) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toLocaleString", 0) })
t.putStr("toPrecision", func(r *Runtime) Value { return r.methodProp(r.numberproto_toPrecision, "toPrecision", 1) })
t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toString", 1) })
t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.numberproto_valueOf, "valueOf", 0) })
return t
}
var numberProtoTemplate *objectTemplate
var numberProtoTemplateOnce sync.Once
func getNumberProtoTemplate() *objectTemplate {
numberProtoTemplateOnce.Do(func() {
numberProtoTemplate = createNumberProtoTemplate()
})
return numberProtoTemplate
}
func (r *Runtime) getNumberPrototype() *Object {
ret := r.global.NumberPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.NumberPrototype = ret
o := r.newTemplatedObject(getNumberProtoTemplate(), ret)
o.class = classNumber
}
return ret
}
func (r *Runtime) getParseFloat() *Object {
ret := r.global.parseFloat
if ret == nil {
ret = r.newNativeFunc(r.builtin_parseFloat, "parseFloat", 1)
r.global.parseFloat = ret
}
return ret
}
func (r *Runtime) getParseInt() *Object {
ret := r.global.parseInt
if ret == nil {
ret = r.newNativeFunc(r.builtin_parseInt, "parseInt", 2)
r.global.parseInt = ret
}
return ret
}
func createNumberTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.getFunctionPrototype()
}
t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Number"), false, false, true) })
t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getNumberPrototype(), false, false, false) })
t.putStr("EPSILON", func(r *Runtime) Value { return valueProp(_epsilon, false, false, false) })
t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.number_isFinite, "isFinite", 1) })
t.putStr("isInteger", func(r *Runtime) Value { return r.methodProp(r.number_isInteger, "isInteger", 1) })
t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.number_isNaN, "isNaN", 1) })
t.putStr("isSafeInteger", func(r *Runtime) Value { return r.methodProp(r.number_isSafeInteger, "isSafeInteger", 1) })
t.putStr("MAX_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(maxInt-1), false, false, false) })
t.putStr("MIN_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(-(maxInt - 1)), false, false, false) })
t.putStr("MIN_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.SmallestNonzeroFloat64), false, false, false) })
t.putStr("MAX_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.MaxFloat64), false, false, false) })
t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) })
t.putStr("NEGATIVE_INFINITY", func(r *Runtime) Value { return valueProp(_negativeInf, false, false, false) })
t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) })
t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) })
t.putStr("POSITIVE_INFINITY", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) })
return t
}
var numberTemplate *objectTemplate
var numberTemplateOnce sync.Once
func getNumberTemplate() *objectTemplate {
numberTemplateOnce.Do(func() {
numberTemplate = createNumberTemplate()
})
return numberTemplate
}
func (r *Runtime) getNumber() *Object {
ret := r.global.Number
if ret == nil {
ret = &Object{runtime: r}
r.global.Number = ret
r.newTemplatedFuncObject(getNumberTemplate(), ret, r.builtin_Number,
r.wrapNativeConstruct(r.builtin_newNumber, ret, r.getNumberPrototype()))
}
return ret
}
+711
View File
@@ -0,0 +1,711 @@
package goja
import (
"fmt"
"sync"
)
func (r *Runtime) builtin_Object(args []Value, newTarget *Object) *Object {
if newTarget != nil && newTarget != r.getObject() {
proto := r.getPrototypeFromCtor(newTarget, nil, r.global.ObjectPrototype)
return r.newBaseObject(proto, classObject).val
}
if len(args) > 0 {
arg := args[0]
if arg != _undefined && arg != _null {
return arg.ToObject(r)
}
}
return r.NewObject()
}
func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value {
o := call.Argument(0).ToObject(r)
p := o.self.proto()
if p == nil {
return _null
}
return p
}
func (r *Runtime) valuePropToDescriptorObject(desc Value) Value {
if desc == nil {
return _undefined
}
var writable, configurable, enumerable, accessor bool
var get, set *Object
var value Value
if v, ok := desc.(*valueProperty); ok {
writable = v.writable
configurable = v.configurable
enumerable = v.enumerable
accessor = v.accessor
value = v.value
get = v.getterFunc
set = v.setterFunc
} else {
writable = true
configurable = true
enumerable = true
value = desc
}
ret := r.NewObject()
obj := ret.self
if !accessor {
obj.setOwnStr("value", value, false)
obj.setOwnStr("writable", r.toBoolean(writable), false)
} else {
if get != nil {
obj.setOwnStr("get", get, false)
} else {
obj.setOwnStr("get", _undefined, false)
}
if set != nil {
obj.setOwnStr("set", set, false)
} else {
obj.setOwnStr("set", _undefined, false)
}
}
obj.setOwnStr("enumerable", r.toBoolean(enumerable), false)
obj.setOwnStr("configurable", r.toBoolean(configurable), false)
return ret
}
func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value {
o := call.Argument(0).ToObject(r)
propName := toPropertyKey(call.Argument(1))
return r.valuePropToDescriptorObject(o.getOwnProp(propName))
}
func (r *Runtime) object_getOwnPropertyDescriptors(call FunctionCall) Value {
o := call.Argument(0).ToObject(r)
result := r.newBaseObject(r.global.ObjectPrototype, classObject).val
for item, next := o.self.iterateKeys()(); next != nil; item, next = next() {
var prop Value
if item.value == nil {
prop = o.getOwnProp(item.name)
if prop == nil {
continue
}
} else {
prop = item.value
}
descriptor := r.valuePropToDescriptorObject(prop)
if descriptor != _undefined {
createDataPropertyOrThrow(result, item.name, descriptor)
}
}
return result
}
func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value {
obj := call.Argument(0).ToObject(r)
return r.newArrayValues(obj.self.stringKeys(true, nil))
}
func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value {
obj := call.Argument(0).ToObject(r)
return r.newArrayValues(obj.self.symbols(true, nil))
}
func (r *Runtime) toValueProp(v Value) *valueProperty {
if v == nil || v == _undefined {
return nil
}
obj := r.toObject(v)
getter := obj.self.getStr("get", nil)
setter := obj.self.getStr("set", nil)
writable := obj.self.getStr("writable", nil)
value := obj.self.getStr("value", nil)
if (getter != nil || setter != nil) && (value != nil || writable != nil) {
r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
}
ret := &valueProperty{}
if writable != nil && writable.ToBoolean() {
ret.writable = true
}
if e := obj.self.getStr("enumerable", nil); e != nil && e.ToBoolean() {
ret.enumerable = true
}
if c := obj.self.getStr("configurable", nil); c != nil && c.ToBoolean() {
ret.configurable = true
}
ret.value = value
if getter != nil && getter != _undefined {
o := r.toObject(getter)
if _, ok := o.self.assertCallable(); !ok {
r.typeErrorResult(true, "getter must be a function")
}
ret.getterFunc = o
}
if setter != nil && setter != _undefined {
o := r.toObject(setter)
if _, ok := o.self.assertCallable(); !ok {
r.typeErrorResult(true, "setter must be a function")
}
ret.setterFunc = o
}
if ret.getterFunc != nil || ret.setterFunc != nil {
ret.accessor = true
}
return ret
}
func (r *Runtime) toPropertyDescriptor(v Value) (ret PropertyDescriptor) {
if o, ok := v.(*Object); ok {
descr := o.self
// Save the original descriptor for reference
ret.jsDescriptor = o
ret.Value = descr.getStr("value", nil)
if p := descr.getStr("writable", nil); p != nil {
ret.Writable = ToFlag(p.ToBoolean())
}
if p := descr.getStr("enumerable", nil); p != nil {
ret.Enumerable = ToFlag(p.ToBoolean())
}
if p := descr.getStr("configurable", nil); p != nil {
ret.Configurable = ToFlag(p.ToBoolean())
}
ret.Getter = descr.getStr("get", nil)
ret.Setter = descr.getStr("set", nil)
if ret.Getter != nil && ret.Getter != _undefined {
if _, ok := r.toObject(ret.Getter).self.assertCallable(); !ok {
r.typeErrorResult(true, "getter must be a function")
}
}
if ret.Setter != nil && ret.Setter != _undefined {
if _, ok := r.toObject(ret.Setter).self.assertCallable(); !ok {
r.typeErrorResult(true, "setter must be a function")
}
}
if (ret.Getter != nil || ret.Setter != nil) && (ret.Value != nil || ret.Writable != FLAG_NOT_SET) {
r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")
}
} else {
r.typeErrorResult(true, "Property description must be an object: %s", v.String())
}
return
}
func (r *Runtime) _defineProperties(o *Object, p Value) {
type propItem struct {
name Value
prop PropertyDescriptor
}
props := p.ToObject(r)
var list []propItem
for item, next := iterateEnumerableProperties(props)(); next != nil; item, next = next() {
list = append(list, propItem{
name: item.name,
prop: r.toPropertyDescriptor(item.value),
})
}
for _, prop := range list {
o.defineOwnProperty(prop.name, prop.prop, true)
}
}
func (r *Runtime) object_create(call FunctionCall) Value {
var proto *Object
if arg := call.Argument(0); arg != _null {
if o, ok := arg.(*Object); ok {
proto = o
} else {
r.typeErrorResult(true, "Object prototype may only be an Object or null: %s", arg.String())
}
}
o := r.newBaseObject(proto, classObject).val
if props := call.Argument(1); props != _undefined {
r._defineProperties(o, props)
}
return o
}
func (r *Runtime) object_defineProperty(call FunctionCall) (ret Value) {
if obj, ok := call.Argument(0).(*Object); ok {
descr := r.toPropertyDescriptor(call.Argument(2))
obj.defineOwnProperty(toPropertyKey(call.Argument(1)), descr, true)
ret = call.Argument(0)
} else {
r.typeErrorResult(true, "Object.defineProperty called on non-object")
}
return
}
func (r *Runtime) object_defineProperties(call FunctionCall) Value {
obj := r.toObject(call.Argument(0))
r._defineProperties(obj, call.Argument(1))
return obj
}
func (r *Runtime) object_seal(call FunctionCall) Value {
// ES6
arg := call.Argument(0)
if obj, ok := arg.(*Object); ok {
obj.self.preventExtensions(true)
descr := PropertyDescriptor{
Configurable: FLAG_FALSE,
}
for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
if prop, ok := item.value.(*valueProperty); ok {
prop.configurable = false
} else {
obj.defineOwnProperty(item.name, descr, true)
}
}
return obj
}
return arg
}
func (r *Runtime) object_freeze(call FunctionCall) Value {
arg := call.Argument(0)
if obj, ok := arg.(*Object); ok {
obj.self.preventExtensions(true)
for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
if prop, ok := item.value.(*valueProperty); ok {
prop.configurable = false
if !prop.accessor {
prop.writable = false
}
} else {
prop := obj.getOwnProp(item.name)
descr := PropertyDescriptor{
Configurable: FLAG_FALSE,
}
if prop, ok := prop.(*valueProperty); ok && prop.accessor {
// no-op
} else {
descr.Writable = FLAG_FALSE
}
obj.defineOwnProperty(item.name, descr, true)
}
}
return obj
} else {
// ES6 behavior
return arg
}
}
func (r *Runtime) object_preventExtensions(call FunctionCall) (ret Value) {
arg := call.Argument(0)
if obj, ok := arg.(*Object); ok {
obj.self.preventExtensions(true)
}
return arg
}
func (r *Runtime) object_isSealed(call FunctionCall) Value {
if obj, ok := call.Argument(0).(*Object); ok {
if obj.self.isExtensible() {
return valueFalse
}
for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
var prop Value
if item.value == nil {
prop = obj.getOwnProp(item.name)
if prop == nil {
continue
}
} else {
prop = item.value
}
if prop, ok := prop.(*valueProperty); ok {
if prop.configurable {
return valueFalse
}
} else {
return valueFalse
}
}
}
return valueTrue
}
func (r *Runtime) object_isFrozen(call FunctionCall) Value {
if obj, ok := call.Argument(0).(*Object); ok {
if obj.self.isExtensible() {
return valueFalse
}
for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() {
var prop Value
if item.value == nil {
prop = obj.getOwnProp(item.name)
if prop == nil {
continue
}
} else {
prop = item.value
}
if prop, ok := prop.(*valueProperty); ok {
if prop.configurable || prop.value != nil && prop.writable {
return valueFalse
}
} else {
return valueFalse
}
}
}
return valueTrue
}
func (r *Runtime) object_isExtensible(call FunctionCall) Value {
if obj, ok := call.Argument(0).(*Object); ok {
if obj.self.isExtensible() {
return valueTrue
}
return valueFalse
} else {
// ES6
//r.typeErrorResult(true, "Object.isExtensible called on non-object")
return valueFalse
}
}
func (r *Runtime) object_keys(call FunctionCall) Value {
obj := call.Argument(0).ToObject(r)
return r.newArrayValues(obj.self.stringKeys(false, nil))
}
func (r *Runtime) object_entries(call FunctionCall) Value {
obj := call.Argument(0).ToObject(r)
var values []Value
for item, next := iterateEnumerableStringProperties(obj)(); next != nil; item, next = next() {
values = append(values, r.newArrayValues([]Value{item.name, item.value}))
}
return r.newArrayValues(values)
}
func (r *Runtime) object_values(call FunctionCall) Value {
obj := call.Argument(0).ToObject(r)
var values []Value
for item, next := iterateEnumerableStringProperties(obj)(); next != nil; item, next = next() {
values = append(values, item.value)
}
return r.newArrayValues(values)
}
func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value {
p := toPropertyKey(call.Argument(0))
o := call.This.ToObject(r)
if o.hasOwnProperty(p) {
return valueTrue
} else {
return valueFalse
}
}
func (r *Runtime) objectproto_isPrototypeOf(call FunctionCall) Value {
if v, ok := call.Argument(0).(*Object); ok {
o := call.This.ToObject(r)
for {
v = v.self.proto()
if v == nil {
break
}
if v == o {
return valueTrue
}
}
}
return valueFalse
}
func (r *Runtime) objectproto_propertyIsEnumerable(call FunctionCall) Value {
p := toPropertyKey(call.Argument(0))
o := call.This.ToObject(r)
pv := o.getOwnProp(p)
if pv == nil {
return valueFalse
}
if prop, ok := pv.(*valueProperty); ok {
if !prop.enumerable {
return valueFalse
}
}
return valueTrue
}
func (r *Runtime) objectproto_toString(call FunctionCall) Value {
switch o := call.This.(type) {
case valueNull:
return stringObjectNull
case valueUndefined:
return stringObjectUndefined
default:
obj := o.ToObject(r)
if o, ok := obj.self.(*objectGoReflect); ok {
if toString := o.toString; toString != nil {
return toString()
}
}
var clsName string
if isArray(obj) {
clsName = classArray
} else {
clsName = obj.self.className()
}
if tag := obj.self.getSym(SymToStringTag, nil); tag != nil {
if str, ok := tag.(String); ok {
clsName = str.String()
}
}
return newStringValue(fmt.Sprintf("[object %s]", clsName))
}
}
func (r *Runtime) objectproto_toLocaleString(call FunctionCall) Value {
toString := toMethod(r.getVStr(call.This, "toString"))
return toString(FunctionCall{This: call.This})
}
func (r *Runtime) objectproto_getProto(call FunctionCall) Value {
proto := call.This.ToObject(r).self.proto()
if proto != nil {
return proto
}
return _null
}
func (r *Runtime) setObjectProto(o, arg Value) {
r.checkObjectCoercible(o)
var proto *Object
if arg != _null {
if obj, ok := arg.(*Object); ok {
proto = obj
} else {
return
}
}
if o, ok := o.(*Object); ok {
o.self.setProto(proto, true)
}
}
func (r *Runtime) objectproto_setProto(call FunctionCall) Value {
r.setObjectProto(call.This, call.Argument(0))
return _undefined
}
func (r *Runtime) objectproto_valueOf(call FunctionCall) Value {
return call.This.ToObject(r)
}
func (r *Runtime) object_assign(call FunctionCall) Value {
to := call.Argument(0).ToObject(r)
if len(call.Arguments) > 1 {
for _, arg := range call.Arguments[1:] {
if arg != _undefined && arg != _null {
source := arg.ToObject(r)
for item, next := iterateEnumerableProperties(source)(); next != nil; item, next = next() {
to.setOwn(item.name, item.value, true)
}
}
}
}
return to
}
func (r *Runtime) object_is(call FunctionCall) Value {
return r.toBoolean(call.Argument(0).SameAs(call.Argument(1)))
}
func (r *Runtime) toProto(proto Value) *Object {
if proto != _null {
if obj, ok := proto.(*Object); ok {
return obj
} else {
panic(r.NewTypeError("Object prototype may only be an Object or null: %s", proto))
}
}
return nil
}
func (r *Runtime) object_setPrototypeOf(call FunctionCall) Value {
o := call.Argument(0)
r.checkObjectCoercible(o)
proto := r.toProto(call.Argument(1))
if o, ok := o.(*Object); ok {
o.self.setProto(proto, true)
}
return o
}
func (r *Runtime) object_fromEntries(call FunctionCall) Value {
o := call.Argument(0)
r.checkObjectCoercible(o)
result := r.newBaseObject(r.global.ObjectPrototype, classObject).val
iter := r.getIterator(o, nil)
iter.iterate(func(nextValue Value) {
i0 := valueInt(0)
i1 := valueInt(1)
itemObj := r.toObject(nextValue)
k := itemObj.self.getIdx(i0, nil)
v := itemObj.self.getIdx(i1, nil)
key := toPropertyKey(k)
createDataPropertyOrThrow(result, key, v)
})
return result
}
func (r *Runtime) object_hasOwn(call FunctionCall) Value {
o := call.Argument(0)
obj := o.ToObject(r)
p := toPropertyKey(call.Argument(1))
if obj.hasOwnProperty(p) {
return valueTrue
} else {
return valueFalse
}
}
func createObjectTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.getFunctionPrototype()
}
t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Object"), false, false, true) })
t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.global.ObjectPrototype, false, false, false) })
t.putStr("assign", func(r *Runtime) Value { return r.methodProp(r.object_assign, "assign", 2) })
t.putStr("defineProperty", func(r *Runtime) Value { return r.methodProp(r.object_defineProperty, "defineProperty", 3) })
t.putStr("defineProperties", func(r *Runtime) Value { return r.methodProp(r.object_defineProperties, "defineProperties", 2) })
t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.object_entries, "entries", 1) })
t.putStr("getOwnPropertyDescriptor", func(r *Runtime) Value {
return r.methodProp(r.object_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2)
})
t.putStr("getOwnPropertyDescriptors", func(r *Runtime) Value {
return r.methodProp(r.object_getOwnPropertyDescriptors, "getOwnPropertyDescriptors", 1)
})
t.putStr("getPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_getPrototypeOf, "getPrototypeOf", 1) })
t.putStr("is", func(r *Runtime) Value { return r.methodProp(r.object_is, "is", 2) })
t.putStr("getOwnPropertyNames", func(r *Runtime) Value { return r.methodProp(r.object_getOwnPropertyNames, "getOwnPropertyNames", 1) })
t.putStr("getOwnPropertySymbols", func(r *Runtime) Value {
return r.methodProp(r.object_getOwnPropertySymbols, "getOwnPropertySymbols", 1)
})
t.putStr("create", func(r *Runtime) Value { return r.methodProp(r.object_create, "create", 2) })
t.putStr("seal", func(r *Runtime) Value { return r.methodProp(r.object_seal, "seal", 1) })
t.putStr("freeze", func(r *Runtime) Value { return r.methodProp(r.object_freeze, "freeze", 1) })
t.putStr("preventExtensions", func(r *Runtime) Value { return r.methodProp(r.object_preventExtensions, "preventExtensions", 1) })
t.putStr("isSealed", func(r *Runtime) Value { return r.methodProp(r.object_isSealed, "isSealed", 1) })
t.putStr("isFrozen", func(r *Runtime) Value { return r.methodProp(r.object_isFrozen, "isFrozen", 1) })
t.putStr("isExtensible", func(r *Runtime) Value { return r.methodProp(r.object_isExtensible, "isExtensible", 1) })
t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.object_keys, "keys", 1) })
t.putStr("setPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_setPrototypeOf, "setPrototypeOf", 2) })
t.putStr("values", func(r *Runtime) Value { return r.methodProp(r.object_values, "values", 1) })
t.putStr("fromEntries", func(r *Runtime) Value { return r.methodProp(r.object_fromEntries, "fromEntries", 1) })
t.putStr("hasOwn", func(r *Runtime) Value { return r.methodProp(r.object_hasOwn, "hasOwn", 2) })
return t
}
var _objectTemplate *objectTemplate
var objectTemplateOnce sync.Once
func getObjectTemplate() *objectTemplate {
objectTemplateOnce.Do(func() {
_objectTemplate = createObjectTemplate()
})
return _objectTemplate
}
func (r *Runtime) getObject() *Object {
ret := r.global.Object
if ret == nil {
ret = &Object{runtime: r}
r.global.Object = ret
r.newTemplatedFuncObject(getObjectTemplate(), ret, func(call FunctionCall) Value {
return r.builtin_Object(call.Arguments, nil)
}, r.builtin_Object)
}
return ret
}
/*
func (r *Runtime) getObjectPrototype() *Object {
ret := r.global.ObjectPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.ObjectPrototype = ret
r.newTemplatedObject(getObjectProtoTemplate(), ret)
}
return ret
}
*/
var objectProtoTemplate *objectTemplate
var objectProtoTemplateOnce sync.Once
func getObjectProtoTemplate() *objectTemplate {
objectProtoTemplateOnce.Do(func() {
objectProtoTemplate = createObjectProtoTemplate()
})
return objectProtoTemplate
}
func createObjectProtoTemplate() *objectTemplate {
t := newObjectTemplate()
// null prototype
t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) })
t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toString, "toString", 0) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toLocaleString, "toLocaleString", 0) })
t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_valueOf, "valueOf", 0) })
t.putStr("hasOwnProperty", func(r *Runtime) Value { return r.methodProp(r.objectproto_hasOwnProperty, "hasOwnProperty", 1) })
t.putStr("isPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_isPrototypeOf, "isPrototypeOf", 1) })
t.putStr("propertyIsEnumerable", func(r *Runtime) Value {
return r.methodProp(r.objectproto_propertyIsEnumerable, "propertyIsEnumerable", 1)
})
t.putStr(__proto__, func(r *Runtime) Value {
return &valueProperty{
accessor: true,
getterFunc: r.newNativeFunc(r.objectproto_getProto, "get __proto__", 0),
setterFunc: r.newNativeFunc(r.objectproto_setProto, "set __proto__", 1),
configurable: true,
}
})
return t
}
+650
View File
@@ -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
View File
@@ -0,0 +1,396 @@
package goja
import (
"github.com/dop251/goja/unistring"
)
type nativeProxyHandler struct {
handler *ProxyTrapConfig
}
func (h *nativeProxyHandler) getPrototypeOf(target *Object) (Value, bool) {
if trap := h.handler.GetPrototypeOf; trap != nil {
return trap(target), true
}
return nil, false
}
func (h *nativeProxyHandler) setPrototypeOf(target *Object, proto *Object) (bool, bool) {
if trap := h.handler.SetPrototypeOf; trap != nil {
return trap(target, proto), true
}
return false, false
}
func (h *nativeProxyHandler) isExtensible(target *Object) (bool, bool) {
if trap := h.handler.IsExtensible; trap != nil {
return trap(target), true
}
return false, false
}
func (h *nativeProxyHandler) preventExtensions(target *Object) (bool, bool) {
if trap := h.handler.PreventExtensions; trap != nil {
return trap(target), true
}
return false, false
}
func (h *nativeProxyHandler) getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) {
if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil {
if idx, ok := strToInt(prop); ok {
desc := trap(target, idx)
return desc.toValue(target.runtime), true
}
}
if trap := h.handler.GetOwnPropertyDescriptor; trap != nil {
desc := trap(target, prop.String())
return desc.toValue(target.runtime), true
}
return nil, false
}
func (h *nativeProxyHandler) getOwnPropertyDescriptorIdx(target *Object, prop valueInt) (Value, bool) {
if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil {
desc := trap(target, toIntStrict(int64(prop)))
return desc.toValue(target.runtime), true
}
if trap := h.handler.GetOwnPropertyDescriptor; trap != nil {
desc := trap(target, prop.String())
return desc.toValue(target.runtime), true
}
return nil, false
}
func (h *nativeProxyHandler) getOwnPropertyDescriptorSym(target *Object, prop *Symbol) (Value, bool) {
if trap := h.handler.GetOwnPropertyDescriptorSym; trap != nil {
desc := trap(target, prop)
return desc.toValue(target.runtime), true
}
return nil, false
}
func (h *nativeProxyHandler) definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) {
if trap := h.handler.DefinePropertyIdx; trap != nil {
if idx, ok := strToInt(prop); ok {
return trap(target, idx, desc), true
}
}
if trap := h.handler.DefineProperty; trap != nil {
return trap(target, prop.String(), desc), true
}
return false, false
}
func (h *nativeProxyHandler) definePropertyIdx(target *Object, prop valueInt, desc PropertyDescriptor) (bool, bool) {
if trap := h.handler.DefinePropertyIdx; trap != nil {
return trap(target, toIntStrict(int64(prop)), desc), true
}
if trap := h.handler.DefineProperty; trap != nil {
return trap(target, prop.String(), desc), true
}
return false, false
}
func (h *nativeProxyHandler) definePropertySym(target *Object, prop *Symbol, desc PropertyDescriptor) (bool, bool) {
if trap := h.handler.DefinePropertySym; trap != nil {
return trap(target, prop, desc), true
}
return false, false
}
func (h *nativeProxyHandler) hasStr(target *Object, prop unistring.String) (bool, bool) {
if trap := h.handler.HasIdx; trap != nil {
if idx, ok := strToInt(prop); ok {
return trap(target, idx), true
}
}
if trap := h.handler.Has; trap != nil {
return trap(target, prop.String()), true
}
return false, false
}
func (h *nativeProxyHandler) hasIdx(target *Object, prop valueInt) (bool, bool) {
if trap := h.handler.HasIdx; trap != nil {
return trap(target, toIntStrict(int64(prop))), true
}
if trap := h.handler.Has; trap != nil {
return trap(target, prop.String()), true
}
return false, false
}
func (h *nativeProxyHandler) hasSym(target *Object, prop *Symbol) (bool, bool) {
if trap := h.handler.HasSym; trap != nil {
return trap(target, prop), true
}
return false, false
}
func (h *nativeProxyHandler) getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) {
if trap := h.handler.GetIdx; trap != nil {
if idx, ok := strToInt(prop); ok {
return trap(target, idx, receiver), true
}
}
if trap := h.handler.Get; trap != nil {
return trap(target, prop.String(), receiver), true
}
return nil, false
}
func (h *nativeProxyHandler) getIdx(target *Object, prop valueInt, receiver Value) (Value, bool) {
if trap := h.handler.GetIdx; trap != nil {
return trap(target, toIntStrict(int64(prop)), receiver), true
}
if trap := h.handler.Get; trap != nil {
return trap(target, prop.String(), receiver), true
}
return nil, false
}
func (h *nativeProxyHandler) getSym(target *Object, prop *Symbol, receiver Value) (Value, bool) {
if trap := h.handler.GetSym; trap != nil {
return trap(target, prop, receiver), true
}
return nil, false
}
func (h *nativeProxyHandler) setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) {
if trap := h.handler.SetIdx; trap != nil {
if idx, ok := strToInt(prop); ok {
return trap(target, idx, value, receiver), true
}
}
if trap := h.handler.Set; trap != nil {
return trap(target, prop.String(), value, receiver), true
}
return false, false
}
func (h *nativeProxyHandler) setIdx(target *Object, prop valueInt, value Value, receiver Value) (bool, bool) {
if trap := h.handler.SetIdx; trap != nil {
return trap(target, toIntStrict(int64(prop)), value, receiver), true
}
if trap := h.handler.Set; trap != nil {
return trap(target, prop.String(), value, receiver), true
}
return false, false
}
func (h *nativeProxyHandler) setSym(target *Object, prop *Symbol, value Value, receiver Value) (bool, bool) {
if trap := h.handler.SetSym; trap != nil {
return trap(target, prop, value, receiver), true
}
return false, false
}
func (h *nativeProxyHandler) deleteStr(target *Object, prop unistring.String) (bool, bool) {
if trap := h.handler.DeletePropertyIdx; trap != nil {
if idx, ok := strToInt(prop); ok {
return trap(target, idx), true
}
}
if trap := h.handler.DeleteProperty; trap != nil {
return trap(target, prop.String()), true
}
return false, false
}
func (h *nativeProxyHandler) deleteIdx(target *Object, prop valueInt) (bool, bool) {
if trap := h.handler.DeletePropertyIdx; trap != nil {
return trap(target, toIntStrict(int64(prop))), true
}
if trap := h.handler.DeleteProperty; trap != nil {
return trap(target, prop.String()), true
}
return false, false
}
func (h *nativeProxyHandler) deleteSym(target *Object, prop *Symbol) (bool, bool) {
if trap := h.handler.DeletePropertySym; trap != nil {
return trap(target, prop), true
}
return false, false
}
func (h *nativeProxyHandler) ownKeys(target *Object) (*Object, bool) {
if trap := h.handler.OwnKeys; trap != nil {
return trap(target), true
}
return nil, false
}
func (h *nativeProxyHandler) apply(target *Object, this Value, args []Value) (Value, bool) {
if trap := h.handler.Apply; trap != nil {
return trap(target, this, args), true
}
return nil, false
}
func (h *nativeProxyHandler) construct(target *Object, args []Value, newTarget *Object) (Value, bool) {
if trap := h.handler.Construct; trap != nil {
return trap(target, args, newTarget), true
}
return nil, false
}
func (h *nativeProxyHandler) toObject(runtime *Runtime) *Object {
return runtime.ToValue(h.handler).ToObject(runtime)
}
func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) proxyHandler {
return &nativeProxyHandler{handler: nativeHandler}
}
// ProxyTrapConfig provides a simplified Go-friendly API for implementing Proxy traps.
// If an *Idx trap is defined it gets called for integer property keys, including negative ones. Note that
// this only includes string property keys that represent a canonical integer
// (i.e. "0", "123", but not "00", "01", " 1" or "-0").
// For efficiency strings representing integers exceeding 2^53 are not checked to see if they are canonical,
// i.e. the *Idx traps will receive "9007199254740993" as well as "9007199254740994", even though the former is not
// a canonical representation in ECMAScript (Number("9007199254740993") === 9007199254740992).
// See https://262.ecma-international.org/#sec-canonicalnumericindexstring
// If an *Idx trap is not set, the corresponding string one is used.
type ProxyTrapConfig struct {
// A trap for Object.getPrototypeOf, Reflect.getPrototypeOf, __proto__, Object.prototype.isPrototypeOf, instanceof
GetPrototypeOf func(target *Object) (prototype *Object)
// A trap for Object.setPrototypeOf, Reflect.setPrototypeOf
SetPrototypeOf func(target *Object, prototype *Object) (success bool)
// A trap for Object.isExtensible, Reflect.isExtensible
IsExtensible func(target *Object) (success bool)
// A trap for Object.preventExtensions, Reflect.preventExtensions
PreventExtensions func(target *Object) (success bool)
// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (string properties)
GetOwnPropertyDescriptor func(target *Object, prop string) (propertyDescriptor PropertyDescriptor)
// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (integer properties)
GetOwnPropertyDescriptorIdx func(target *Object, prop int) (propertyDescriptor PropertyDescriptor)
// A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (Symbol properties)
GetOwnPropertyDescriptorSym func(target *Object, prop *Symbol) (propertyDescriptor PropertyDescriptor)
// A trap for Object.defineProperty, Reflect.defineProperty (string properties)
DefineProperty func(target *Object, key string, propertyDescriptor PropertyDescriptor) (success bool)
// A trap for Object.defineProperty, Reflect.defineProperty (integer properties)
DefinePropertyIdx func(target *Object, key int, propertyDescriptor PropertyDescriptor) (success bool)
// A trap for Object.defineProperty, Reflect.defineProperty (Symbol properties)
DefinePropertySym func(target *Object, key *Symbol, propertyDescriptor PropertyDescriptor) (success bool)
// A trap for the in operator, with operator, Reflect.has (string properties)
Has func(target *Object, property string) (available bool)
// A trap for the in operator, with operator, Reflect.has (integer properties)
HasIdx func(target *Object, property int) (available bool)
// A trap for the in operator, with operator, Reflect.has (Symbol properties)
HasSym func(target *Object, property *Symbol) (available bool)
// A trap for getting property values, Reflect.get (string properties)
Get func(target *Object, property string, receiver Value) (value Value)
// A trap for getting property values, Reflect.get (integer properties)
GetIdx func(target *Object, property int, receiver Value) (value Value)
// A trap for getting property values, Reflect.get (Symbol properties)
GetSym func(target *Object, property *Symbol, receiver Value) (value Value)
// A trap for setting property values, Reflect.set (string properties)
Set func(target *Object, property string, value Value, receiver Value) (success bool)
// A trap for setting property values, Reflect.set (integer properties)
SetIdx func(target *Object, property int, value Value, receiver Value) (success bool)
// A trap for setting property values, Reflect.set (Symbol properties)
SetSym func(target *Object, property *Symbol, value Value, receiver Value) (success bool)
// A trap for the delete operator, Reflect.deleteProperty (string properties)
DeleteProperty func(target *Object, property string) (success bool)
// A trap for the delete operator, Reflect.deleteProperty (integer properties)
DeletePropertyIdx func(target *Object, property int) (success bool)
// A trap for the delete operator, Reflect.deleteProperty (Symbol properties)
DeletePropertySym func(target *Object, property *Symbol) (success bool)
// A trap for Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.keys, Reflect.ownKeys
OwnKeys func(target *Object) (object *Object)
// A trap for a function call, Function.prototype.apply, Function.prototype.call, Reflect.apply
Apply func(target *Object, this Value, argumentsList []Value) (value Value)
// A trap for the new operator, Reflect.construct
Construct func(target *Object, argumentsList []Value, newTarget *Object) (value *Object)
}
func (r *Runtime) newProxy(args []Value, proto *Object) *Object {
if len(args) >= 2 {
if target, ok := args[0].(*Object); ok {
if proxyHandler, ok := args[1].(*Object); ok {
return r.newProxyObject(target, proxyHandler, proto).val
}
}
}
panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler"))
}
func (r *Runtime) builtin_newProxy(args []Value, newTarget *Object) *Object {
if newTarget == nil {
panic(r.needNew("Proxy"))
}
return r.newProxy(args, r.getPrototypeFromCtor(newTarget, r.getProxy(), r.global.ObjectPrototype))
}
func (r *Runtime) NewProxy(target *Object, nativeHandler *ProxyTrapConfig) Proxy {
if p, ok := target.self.(*proxyObject); ok {
if p.handler == nil {
panic(r.NewTypeError("Cannot create proxy with a revoked proxy as target"))
}
}
handler := r.newNativeProxyHandler(nativeHandler)
proxy := r._newProxyObject(target, handler, nil)
return Proxy{proxy: proxy}
}
func (r *Runtime) builtin_proxy_revocable(call FunctionCall) Value {
if len(call.Arguments) >= 2 {
if target, ok := call.Argument(0).(*Object); ok {
if proxyHandler, ok := call.Argument(1).(*Object); ok {
proxy := r.newProxyObject(target, proxyHandler, nil)
revoke := r.newNativeFunc(func(FunctionCall) Value {
proxy.revoke()
return _undefined
}, "", 0)
ret := r.NewObject()
ret.self._putProp("proxy", proxy.val, true, true, true)
ret.self._putProp("revoke", revoke, true, true, true)
return ret
}
}
}
panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler"))
}
func (r *Runtime) createProxy(val *Object) objectImpl {
o := r.newNativeConstructOnly(val, r.builtin_newProxy, nil, "Proxy", 2)
o._putProp("revocable", r.newNativeFunc(r.builtin_proxy_revocable, "revocable", 2), true, false, true)
return o
}
func (r *Runtime) getProxy() *Object {
ret := r.global.Proxy
if ret == nil {
ret = &Object{runtime: r}
r.global.Proxy = ret
r.createProxy(ret)
}
return ret
}
+140
View File
@@ -0,0 +1,140 @@
package goja
func (r *Runtime) builtin_reflect_apply(call FunctionCall) Value {
return r.toCallable(call.Argument(0))(FunctionCall{
This: call.Argument(1),
Arguments: r.createListFromArrayLike(call.Argument(2))})
}
func (r *Runtime) toConstructor(v Value) func(args []Value, newTarget *Object) *Object {
if ctor := r.toObject(v).self.assertConstructor(); ctor != nil {
return ctor
}
panic(r.NewTypeError("Value is not a constructor"))
}
func (r *Runtime) builtin_reflect_construct(call FunctionCall) Value {
target := call.Argument(0)
ctor := r.toConstructor(target)
var newTarget Value
if len(call.Arguments) > 2 {
newTarget = call.Argument(2)
r.toConstructor(newTarget)
} else {
newTarget = target
}
return ctor(r.createListFromArrayLike(call.Argument(1)), r.toObject(newTarget))
}
func (r *Runtime) builtin_reflect_defineProperty(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
key := toPropertyKey(call.Argument(1))
desc := r.toPropertyDescriptor(call.Argument(2))
return r.toBoolean(target.defineOwnProperty(key, desc, false))
}
func (r *Runtime) builtin_reflect_deleteProperty(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
key := toPropertyKey(call.Argument(1))
return r.toBoolean(target.delete(key, false))
}
func (r *Runtime) builtin_reflect_get(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
key := toPropertyKey(call.Argument(1))
var receiver Value
if len(call.Arguments) > 2 {
receiver = call.Arguments[2]
}
return target.get(key, receiver)
}
func (r *Runtime) builtin_reflect_getOwnPropertyDescriptor(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
key := toPropertyKey(call.Argument(1))
return r.valuePropToDescriptorObject(target.getOwnProp(key))
}
func (r *Runtime) builtin_reflect_getPrototypeOf(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
if proto := target.self.proto(); proto != nil {
return proto
}
return _null
}
func (r *Runtime) builtin_reflect_has(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
key := toPropertyKey(call.Argument(1))
return r.toBoolean(target.hasProperty(key))
}
func (r *Runtime) builtin_reflect_isExtensible(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
return r.toBoolean(target.self.isExtensible())
}
func (r *Runtime) builtin_reflect_ownKeys(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
return r.newArrayValues(target.self.keys(true, nil))
}
func (r *Runtime) builtin_reflect_preventExtensions(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
return r.toBoolean(target.self.preventExtensions(false))
}
func (r *Runtime) builtin_reflect_set(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
var receiver Value
if len(call.Arguments) >= 4 {
receiver = call.Argument(3)
} else {
receiver = target
}
return r.toBoolean(target.set(call.Argument(1), call.Argument(2), receiver, false))
}
func (r *Runtime) builtin_reflect_setPrototypeOf(call FunctionCall) Value {
target := r.toObject(call.Argument(0))
var proto *Object
if arg := call.Argument(1); arg != _null {
proto = r.toObject(arg)
}
return r.toBoolean(target.self.setProto(proto, false))
}
func (r *Runtime) createReflect(val *Object) objectImpl {
o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
o._putProp("apply", r.newNativeFunc(r.builtin_reflect_apply, "apply", 3), true, false, true)
o._putProp("construct", r.newNativeFunc(r.builtin_reflect_construct, "construct", 2), true, false, true)
o._putProp("defineProperty", r.newNativeFunc(r.builtin_reflect_defineProperty, "defineProperty", 3), true, false, true)
o._putProp("deleteProperty", r.newNativeFunc(r.builtin_reflect_deleteProperty, "deleteProperty", 2), true, false, true)
o._putProp("get", r.newNativeFunc(r.builtin_reflect_get, "get", 2), true, false, true)
o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.builtin_reflect_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2), true, false, true)
o._putProp("getPrototypeOf", r.newNativeFunc(r.builtin_reflect_getPrototypeOf, "getPrototypeOf", 1), true, false, true)
o._putProp("has", r.newNativeFunc(r.builtin_reflect_has, "has", 2), true, false, true)
o._putProp("isExtensible", r.newNativeFunc(r.builtin_reflect_isExtensible, "isExtensible", 1), true, false, true)
o._putProp("ownKeys", r.newNativeFunc(r.builtin_reflect_ownKeys, "ownKeys", 1), true, false, true)
o._putProp("preventExtensions", r.newNativeFunc(r.builtin_reflect_preventExtensions, "preventExtensions", 1), true, false, true)
o._putProp("set", r.newNativeFunc(r.builtin_reflect_set, "set", 3), true, false, true)
o._putProp("setPrototypeOf", r.newNativeFunc(r.builtin_reflect_setPrototypeOf, "setPrototypeOf", 2), true, false, true)
o._putSym(SymToStringTag, valueProp(asciiString("Reflect"), false, false, true))
return o
}
func (r *Runtime) getReflect() *Object {
ret := r.global.Reflect
if ret == nil {
ret = &Object{runtime: r}
r.global.Reflect = ret
ret.self = r.createReflect(ret)
}
return ret
}
File diff suppressed because it is too large Load Diff
+346
View File
@@ -0,0 +1,346 @@
package goja
import (
"fmt"
"reflect"
)
var setExportType = reflectTypeArray
type setObject struct {
baseObject
m *orderedMap
}
type setIterObject struct {
baseObject
iter *orderedMapIter
kind iterationKind
}
func (o *setIterObject) next() Value {
if o.iter == nil {
return o.val.runtime.createIterResultObject(_undefined, true)
}
entry := o.iter.next()
if entry == nil {
o.iter = nil
return o.val.runtime.createIterResultObject(_undefined, true)
}
var result Value
switch o.kind {
case iterationKindValue:
result = entry.key
default:
result = o.val.runtime.newArrayValues([]Value{entry.key, entry.key})
}
return o.val.runtime.createIterResultObject(result, false)
}
func (so *setObject) init() {
so.baseObject.init()
so.m = newOrderedMap(so.val.runtime.getHash())
}
func (so *setObject) exportType() reflect.Type {
return setExportType
}
func (so *setObject) export(ctx *objectExportCtx) interface{} {
a := make([]interface{}, so.m.size)
ctx.put(so.val, a)
iter := so.m.newIter()
for i := 0; i < len(a); i++ {
entry := iter.next()
if entry == nil {
break
}
a[i] = exportValue(entry.key, ctx)
}
return a
}
func (so *setObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
l := so.m.size
if typ.Kind() == reflect.Array {
if dst.Len() != l {
return fmt.Errorf("cannot convert a Set into an array, lengths mismatch: have %d, need %d)", l, dst.Len())
}
} else {
dst.Set(reflect.MakeSlice(typ, l, l))
}
ctx.putTyped(so.val, typ, dst.Interface())
iter := so.m.newIter()
r := so.val.runtime
for i := 0; i < l; i++ {
entry := iter.next()
if entry == nil {
break
}
err := r.toReflectValue(entry.key, dst.Index(i), ctx)
if err != nil {
return err
}
}
return nil
}
func (so *setObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
dst.Set(reflect.MakeMap(typ))
keyTyp := typ.Key()
elemTyp := typ.Elem()
iter := so.m.newIter()
r := so.val.runtime
for {
entry := iter.next()
if entry == nil {
break
}
keyVal := reflect.New(keyTyp).Elem()
err := r.toReflectValue(entry.key, keyVal, ctx)
if err != nil {
return err
}
dst.SetMapIndex(keyVal, reflect.Zero(elemTyp))
}
return nil
}
func (r *Runtime) setProto_add(call FunctionCall) Value {
thisObj := r.toObject(call.This)
so, ok := thisObj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Method Set.prototype.add called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
so.m.set(call.Argument(0), nil)
return call.This
}
func (r *Runtime) setProto_clear(call FunctionCall) Value {
thisObj := r.toObject(call.This)
so, ok := thisObj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Method Set.prototype.clear called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
so.m.clear()
return _undefined
}
func (r *Runtime) setProto_delete(call FunctionCall) Value {
thisObj := r.toObject(call.This)
so, ok := thisObj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Method Set.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
return r.toBoolean(so.m.remove(call.Argument(0)))
}
func (r *Runtime) setProto_entries(call FunctionCall) Value {
return r.createSetIterator(call.This, iterationKindKeyValue)
}
func (r *Runtime) setProto_forEach(call FunctionCall) Value {
thisObj := r.toObject(call.This)
so, ok := thisObj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Method Set.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable()
if !ok {
panic(r.NewTypeError("object is not a function %s"))
}
t := call.Argument(1)
iter := so.m.newIter()
for {
entry := iter.next()
if entry == nil {
break
}
callbackFn(FunctionCall{This: t, Arguments: []Value{entry.key, entry.key, thisObj}})
}
return _undefined
}
func (r *Runtime) setProto_has(call FunctionCall) Value {
thisObj := r.toObject(call.This)
so, ok := thisObj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Method Set.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
return r.toBoolean(so.m.has(call.Argument(0)))
}
func (r *Runtime) setProto_getSize(call FunctionCall) Value {
thisObj := r.toObject(call.This)
so, ok := thisObj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Method get Set.prototype.size called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
return intToValue(int64(so.m.size))
}
func (r *Runtime) setProto_values(call FunctionCall) Value {
return r.createSetIterator(call.This, iterationKindValue)
}
func (r *Runtime) builtin_newSet(args []Value, newTarget *Object) *Object {
if newTarget == nil {
panic(r.needNew("Set"))
}
proto := r.getPrototypeFromCtor(newTarget, r.global.Set, r.global.SetPrototype)
o := &Object{runtime: r}
so := &setObject{}
so.class = classObject
so.val = o
so.extensible = true
o.self = so
so.prototype = proto
so.init()
if len(args) > 0 {
if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
adder := so.getStr("add", nil)
stdArr := r.checkStdArrayIter(arg)
if adder == r.global.setAdder {
if stdArr != nil {
for _, v := range stdArr.values {
so.m.set(v, nil)
}
} else {
r.getIterator(arg, nil).iterate(func(item Value) {
so.m.set(item, nil)
})
}
} else {
adderFn := toMethod(adder)
if adderFn == nil {
panic(r.NewTypeError("Set.add in missing"))
}
if stdArr != nil {
for _, item := range stdArr.values {
adderFn(FunctionCall{This: o, Arguments: []Value{item}})
}
} else {
r.getIterator(arg, nil).iterate(func(item Value) {
adderFn(FunctionCall{This: o, Arguments: []Value{item}})
})
}
}
}
}
return o
}
func (r *Runtime) createSetIterator(setValue Value, kind iterationKind) Value {
obj := r.toObject(setValue)
setObj, ok := obj.self.(*setObject)
if !ok {
panic(r.NewTypeError("Object is not a Set"))
}
o := &Object{runtime: r}
si := &setIterObject{
iter: setObj.m.newIter(),
kind: kind,
}
si.class = classObject
si.val = o
si.extensible = true
o.self = si
si.prototype = r.getSetIteratorPrototype()
si.init()
return o
}
func (r *Runtime) setIterProto_next(call FunctionCall) Value {
thisObj := r.toObject(call.This)
if iter, ok := thisObj.self.(*setIterObject); ok {
return iter.next()
}
panic(r.NewTypeError("Method Set Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
func (r *Runtime) createSetProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
o._putProp("constructor", r.getSet(), true, false, true)
r.global.setAdder = r.newNativeFunc(r.setProto_add, "add", 1)
o._putProp("add", r.global.setAdder, true, false, true)
o._putProp("clear", r.newNativeFunc(r.setProto_clear, "clear", 0), true, false, true)
o._putProp("delete", r.newNativeFunc(r.setProto_delete, "delete", 1), true, false, true)
o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, "forEach", 1), true, false, true)
o._putProp("has", r.newNativeFunc(r.setProto_has, "has", 1), true, false, true)
o.setOwnStr("size", &valueProperty{
getterFunc: r.newNativeFunc(r.setProto_getSize, "get size", 0),
accessor: true,
writable: true,
configurable: true,
}, true)
valuesFunc := r.newNativeFunc(r.setProto_values, "values", 0)
o._putProp("values", valuesFunc, true, false, true)
o._putProp("keys", valuesFunc, true, false, true)
o._putProp("entries", r.newNativeFunc(r.setProto_entries, "entries", 0), true, false, true)
o._putSym(SymIterator, valueProp(valuesFunc, true, false, true))
o._putSym(SymToStringTag, valueProp(asciiString(classSet), false, false, true))
return o
}
func (r *Runtime) createSet(val *Object) objectImpl {
o := r.newNativeConstructOnly(val, r.builtin_newSet, r.getSetPrototype(), "Set", 0)
r.putSpeciesReturnThis(o)
return o
}
func (r *Runtime) createSetIterProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject)
o._putProp("next", r.newNativeFunc(r.setIterProto_next, "next", 0), true, false, true)
o._putSym(SymToStringTag, valueProp(asciiString(classSetIterator), false, false, true))
return o
}
func (r *Runtime) getSetIteratorPrototype() *Object {
var o *Object
if o = r.global.SetIteratorPrototype; o == nil {
o = &Object{runtime: r}
r.global.SetIteratorPrototype = o
o.self = r.createSetIterProto(o)
}
return o
}
func (r *Runtime) getSetPrototype() *Object {
ret := r.global.SetPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.SetPrototype = ret
ret.self = r.createSetProto(ret)
}
return ret
}
func (r *Runtime) getSet() *Object {
ret := r.global.Set
if ret == nil {
ret = &Object{runtime: r}
r.global.Set = ret
ret.self = r.createSet(ret)
}
return ret
}
File diff suppressed because it is too large Load Diff
+177
View File
@@ -0,0 +1,177 @@
package goja
import "github.com/dop251/goja/unistring"
var (
SymHasInstance = newSymbol(asciiString("Symbol.hasInstance"))
SymIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable"))
SymIterator = newSymbol(asciiString("Symbol.iterator"))
SymMatch = newSymbol(asciiString("Symbol.match"))
SymMatchAll = newSymbol(asciiString("Symbol.matchAll"))
SymReplace = newSymbol(asciiString("Symbol.replace"))
SymSearch = newSymbol(asciiString("Symbol.search"))
SymSpecies = newSymbol(asciiString("Symbol.species"))
SymSplit = newSymbol(asciiString("Symbol.split"))
SymToPrimitive = newSymbol(asciiString("Symbol.toPrimitive"))
SymToStringTag = newSymbol(asciiString("Symbol.toStringTag"))
SymUnscopables = newSymbol(asciiString("Symbol.unscopables"))
)
func (r *Runtime) builtin_symbol(call FunctionCall) Value {
var desc String
if arg := call.Argument(0); !IsUndefined(arg) {
desc = arg.toString()
}
return newSymbol(desc)
}
func (r *Runtime) symbolproto_tostring(call FunctionCall) Value {
sym, ok := call.This.(*Symbol)
if !ok {
if obj, ok := call.This.(*Object); ok {
if v, ok := obj.self.(*primitiveValueObject); ok {
if sym1, ok := v.pValue.(*Symbol); ok {
sym = sym1
}
}
}
}
if sym == nil {
panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver"))
}
return sym.descriptiveString()
}
func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value {
_, ok := call.This.(*Symbol)
if ok {
return call.This
}
if obj, ok := call.This.(*Object); ok {
if v, ok := obj.self.(*primitiveValueObject); ok {
if sym, ok := v.pValue.(*Symbol); ok {
return sym
}
}
}
panic(r.NewTypeError("Symbol.prototype.valueOf requires that 'this' be a Symbol"))
}
func (r *Runtime) symbol_for(call FunctionCall) Value {
key := call.Argument(0).toString()
keyStr := key.string()
if v := r.symbolRegistry[keyStr]; v != nil {
return v
}
if r.symbolRegistry == nil {
r.symbolRegistry = make(map[unistring.String]*Symbol)
}
v := newSymbol(key)
r.symbolRegistry[keyStr] = v
return v
}
func (r *Runtime) symbol_keyfor(call FunctionCall) Value {
arg := call.Argument(0)
sym, ok := arg.(*Symbol)
if !ok {
panic(r.NewTypeError("%s is not a symbol", arg.String()))
}
for key, s := range r.symbolRegistry {
if s == sym {
return stringValueFromRaw(key)
}
}
return _undefined
}
func (r *Runtime) thisSymbolValue(v Value) *Symbol {
if sym, ok := v.(*Symbol); ok {
return sym
}
if obj, ok := v.(*Object); ok {
if pVal, ok := obj.self.(*primitiveValueObject); ok {
if sym, ok := pVal.pValue.(*Symbol); ok {
return sym
}
}
}
panic(r.NewTypeError("Value is not a Symbol"))
}
func (r *Runtime) createSymbolProto(val *Object) objectImpl {
o := &baseObject{
class: classObject,
val: val,
extensible: true,
prototype: r.global.ObjectPrototype,
}
o.init()
o._putProp("constructor", r.getSymbol(), true, false, true)
o.setOwnStr("description", &valueProperty{
configurable: true,
getterFunc: r.newNativeFunc(func(call FunctionCall) Value {
return r.thisSymbolValue(call.This).desc
}, "get description", 0),
accessor: true,
}, false)
o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, "toString", 0), true, false, true)
o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, "valueOf", 0), true, false, true)
o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, "[Symbol.toPrimitive]", 1), false, false, true))
o._putSym(SymToStringTag, valueProp(newStringValue("Symbol"), false, false, true))
return o
}
func (r *Runtime) createSymbol(val *Object) objectImpl {
o := r.newNativeFuncAndConstruct(val, r.builtin_symbol, func(args []Value, newTarget *Object) *Object {
panic(r.NewTypeError("Symbol is not a constructor"))
}, r.getSymbolPrototype(), "Symbol", _positiveZero)
o._putProp("for", r.newNativeFunc(r.symbol_for, "for", 1), true, false, true)
o._putProp("keyFor", r.newNativeFunc(r.symbol_keyfor, "keyFor", 1), true, false, true)
for _, s := range []*Symbol{
SymHasInstance,
SymIsConcatSpreadable,
SymIterator,
SymMatch,
SymMatchAll,
SymReplace,
SymSearch,
SymSpecies,
SymSplit,
SymToPrimitive,
SymToStringTag,
SymUnscopables,
} {
n := s.desc.(asciiString)
n = n[len("Symbol."):]
o._putProp(unistring.String(n), s, false, false, false)
}
return o
}
func (r *Runtime) getSymbolPrototype() *Object {
ret := r.global.SymbolPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.SymbolPrototype = ret
ret.self = r.createSymbolProto(ret)
}
return ret
}
func (r *Runtime) getSymbol() *Object {
ret := r.global.Symbol
if ret == nil {
ret = &Object{runtime: r}
r.global.Symbol = ret
ret.self = r.createSymbol(ret)
}
return ret
}
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
package goja
type weakMap uint64
type weakMapObject struct {
baseObject
m weakMap
}
func (wmo *weakMapObject) init() {
wmo.baseObject.init()
wmo.m = weakMap(wmo.val.runtime.genId())
}
func (wm weakMap) set(key *Object, value Value) {
key.getWeakRefs()[wm] = value
}
func (wm weakMap) get(key *Object) Value {
return key.weakRefs[wm]
}
func (wm weakMap) remove(key *Object) bool {
if _, exists := key.weakRefs[wm]; exists {
delete(key.weakRefs, wm)
return true
}
return false
}
func (wm weakMap) has(key *Object) bool {
_, exists := key.weakRefs[wm]
return exists
}
func (r *Runtime) weakMapProto_delete(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wmo, ok := thisObj.self.(*weakMapObject)
if !ok {
panic(r.NewTypeError("Method WeakMap.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
key, ok := call.Argument(0).(*Object)
if ok && wmo.m.remove(key) {
return valueTrue
}
return valueFalse
}
func (r *Runtime) weakMapProto_get(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wmo, ok := thisObj.self.(*weakMapObject)
if !ok {
panic(r.NewTypeError("Method WeakMap.prototype.get called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
var res Value
if key, ok := call.Argument(0).(*Object); ok {
res = wmo.m.get(key)
}
if res == nil {
return _undefined
}
return res
}
func (r *Runtime) weakMapProto_has(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wmo, ok := thisObj.self.(*weakMapObject)
if !ok {
panic(r.NewTypeError("Method WeakMap.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
key, ok := call.Argument(0).(*Object)
if ok && wmo.m.has(key) {
return valueTrue
}
return valueFalse
}
func (r *Runtime) weakMapProto_set(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wmo, ok := thisObj.self.(*weakMapObject)
if !ok {
panic(r.NewTypeError("Method WeakMap.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
key := r.toObject(call.Argument(0))
wmo.m.set(key, call.Argument(1))
return call.This
}
func (r *Runtime) needNew(name string) *Object {
return r.NewTypeError("Constructor %s requires 'new'", name)
}
func (r *Runtime) builtin_newWeakMap(args []Value, newTarget *Object) *Object {
if newTarget == nil {
panic(r.needNew("WeakMap"))
}
proto := r.getPrototypeFromCtor(newTarget, r.global.WeakMap, r.global.WeakMapPrototype)
o := &Object{runtime: r}
wmo := &weakMapObject{}
wmo.class = classObject
wmo.val = o
wmo.extensible = true
o.self = wmo
wmo.prototype = proto
wmo.init()
if len(args) > 0 {
if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
adder := wmo.getStr("set", nil)
adderFn := toMethod(adder)
if adderFn == nil {
panic(r.NewTypeError("WeakMap.set in missing"))
}
iter := r.getIterator(arg, nil)
i0 := valueInt(0)
i1 := valueInt(1)
if adder == r.global.weakMapAdder {
iter.iterate(func(item Value) {
itemObj := r.toObject(item)
k := itemObj.self.getIdx(i0, nil)
v := nilSafe(itemObj.self.getIdx(i1, nil))
wmo.m.set(r.toObject(k), v)
})
} else {
iter.iterate(func(item Value) {
itemObj := r.toObject(item)
k := itemObj.self.getIdx(i0, nil)
v := itemObj.self.getIdx(i1, nil)
adderFn(FunctionCall{This: o, Arguments: []Value{k, v}})
})
}
}
}
return o
}
func (r *Runtime) createWeakMapProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
o._putProp("constructor", r.getWeakMap(), true, false, true)
r.global.weakMapAdder = r.newNativeFunc(r.weakMapProto_set, "set", 2)
o._putProp("set", r.global.weakMapAdder, true, false, true)
o._putProp("delete", r.newNativeFunc(r.weakMapProto_delete, "delete", 1), true, false, true)
o._putProp("has", r.newNativeFunc(r.weakMapProto_has, "has", 1), true, false, true)
o._putProp("get", r.newNativeFunc(r.weakMapProto_get, "get", 1), true, false, true)
o._putSym(SymToStringTag, valueProp(asciiString(classWeakMap), false, false, true))
return o
}
func (r *Runtime) createWeakMap(val *Object) objectImpl {
o := r.newNativeConstructOnly(val, r.builtin_newWeakMap, r.getWeakMapPrototype(), "WeakMap", 0)
return o
}
func (r *Runtime) getWeakMapPrototype() *Object {
ret := r.global.WeakMapPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.WeakMapPrototype = ret
ret.self = r.createWeakMapProto(ret)
}
return ret
}
func (r *Runtime) getWeakMap() *Object {
ret := r.global.WeakMap
if ret == nil {
ret = &Object{runtime: r}
r.global.WeakMap = ret
ret.self = r.createWeakMap(ret)
}
return ret
}
+135
View File
@@ -0,0 +1,135 @@
package goja
type weakSetObject struct {
baseObject
s weakMap
}
func (ws *weakSetObject) init() {
ws.baseObject.init()
ws.s = weakMap(ws.val.runtime.genId())
}
func (r *Runtime) weakSetProto_add(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wso, ok := thisObj.self.(*weakSetObject)
if !ok {
panic(r.NewTypeError("Method WeakSet.prototype.add called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
wso.s.set(r.toObject(call.Argument(0)), nil)
return call.This
}
func (r *Runtime) weakSetProto_delete(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wso, ok := thisObj.self.(*weakSetObject)
if !ok {
panic(r.NewTypeError("Method WeakSet.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
obj, ok := call.Argument(0).(*Object)
if ok && wso.s.remove(obj) {
return valueTrue
}
return valueFalse
}
func (r *Runtime) weakSetProto_has(call FunctionCall) Value {
thisObj := r.toObject(call.This)
wso, ok := thisObj.self.(*weakSetObject)
if !ok {
panic(r.NewTypeError("Method WeakSet.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj})))
}
obj, ok := call.Argument(0).(*Object)
if ok && wso.s.has(obj) {
return valueTrue
}
return valueFalse
}
func (r *Runtime) builtin_newWeakSet(args []Value, newTarget *Object) *Object {
if newTarget == nil {
panic(r.needNew("WeakSet"))
}
proto := r.getPrototypeFromCtor(newTarget, r.global.WeakSet, r.global.WeakSetPrototype)
o := &Object{runtime: r}
wso := &weakSetObject{}
wso.class = classObject
wso.val = o
wso.extensible = true
o.self = wso
wso.prototype = proto
wso.init()
if len(args) > 0 {
if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
adder := wso.getStr("add", nil)
stdArr := r.checkStdArrayIter(arg)
if adder == r.global.weakSetAdder {
if stdArr != nil {
for _, v := range stdArr.values {
wso.s.set(r.toObject(v), nil)
}
} else {
r.getIterator(arg, nil).iterate(func(item Value) {
wso.s.set(r.toObject(item), nil)
})
}
} else {
adderFn := toMethod(adder)
if adderFn == nil {
panic(r.NewTypeError("WeakSet.add in missing"))
}
if stdArr != nil {
for _, item := range stdArr.values {
adderFn(FunctionCall{This: o, Arguments: []Value{item}})
}
} else {
r.getIterator(arg, nil).iterate(func(item Value) {
adderFn(FunctionCall{This: o, Arguments: []Value{item}})
})
}
}
}
}
return o
}
func (r *Runtime) createWeakSetProto(val *Object) objectImpl {
o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject)
o._putProp("constructor", r.global.WeakSet, true, false, true)
r.global.weakSetAdder = r.newNativeFunc(r.weakSetProto_add, "add", 1)
o._putProp("add", r.global.weakSetAdder, true, false, true)
o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, "delete", 1), true, false, true)
o._putProp("has", r.newNativeFunc(r.weakSetProto_has, "has", 1), true, false, true)
o._putSym(SymToStringTag, valueProp(asciiString(classWeakSet), false, false, true))
return o
}
func (r *Runtime) createWeakSet(val *Object) objectImpl {
o := r.newNativeConstructOnly(val, r.builtin_newWeakSet, r.getWeakSetPrototype(), "WeakSet", 0)
return o
}
func (r *Runtime) getWeakSetPrototype() *Object {
ret := r.global.WeakSetPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.WeakSetPrototype = ret
ret.self = r.createWeakSetProto(ret)
}
return ret
}
func (r *Runtime) getWeakSet() *Object {
ret := r.global.WeakSet
if ret == nil {
ret = &Object{runtime: r}
r.global.WeakSet = ret
ret.self = r.createWeakSet(ret)
}
return ret
}
+1487
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+124
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,3 @@
#!/bin/sh
sed -En 's/^.*FAIL: TestTC39\/tc39\/(test\/.*.js).*$/"\1": true,/p'
+110
View File
@@ -0,0 +1,110 @@
# file
--
import "github.com/dop251/goja/file"
Package file encapsulates the file abstractions used by the ast & parser.
## Usage
#### type File
```go
type File struct {
}
```
#### func NewFile
```go
func NewFile(filename, src string, base int) *File
```
#### func (*File) Base
```go
func (fl *File) Base() int
```
#### func (*File) Name
```go
func (fl *File) Name() string
```
#### func (*File) Source
```go
func (fl *File) Source() string
```
#### type FileSet
```go
type FileSet struct {
}
```
A FileSet represents a set of source files.
#### func (*FileSet) AddFile
```go
func (self *FileSet) AddFile(filename, src string) int
```
AddFile adds a new file with the given filename and src.
This an internal method, but exported for cross-package use.
#### func (*FileSet) File
```go
func (self *FileSet) File(idx Idx) *File
```
#### func (*FileSet) Position
```go
func (self *FileSet) Position(idx Idx) *Position
```
Position converts an Idx in the FileSet into a Position.
#### type Idx
```go
type Idx int
```
Idx is a compact encoding of a source position within a file set. It can be
converted into a Position for a more convenient, but much larger,
representation.
#### type Position
```go
type Position struct {
Filename string // The filename where the error occurred, if any
Offset int // The src offset
Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count)
}
```
Position describes an arbitrary source position including the filename, line,
and column location.
#### func (*Position) String
```go
func (self *Position) String() string
```
String returns a string in one of several forms:
file:line:column A valid position with filename
line:column A valid position without filename
file An invalid position with filename
- An invalid position without filename
--
**godocdown** http://github.com/robertkrimen/godocdown
+236
View File
@@ -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
View File
@@ -0,0 +1,21 @@
Copyright (C) 1998, 1999 by Lucent Technologies
All Rights Reserved
Permission to use, copy, modify, and distribute this software and
its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all
copies and that both that the copyright notice and this
permission notice and warranty disclaimer appear in supporting
documentation, and that the name of Lucent or any of its entities
not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
+150
View File
@@ -0,0 +1,150 @@
/*
Package ftoa provides ECMAScript-compliant floating point number conversion to string.
It contains code ported from Rhino (https://github.com/mozilla/rhino/blob/master/src/org/mozilla/javascript/DToA.java)
as well as from the original code by David M. Gay.
See LICENSE_LUCENE for the original copyright message and disclaimer.
*/
package ftoa
import (
"math"
)
const (
frac_mask = 0xfffff
exp_shift = 20
exp_msk1 = 0x100000
exp_shiftL = 52
exp_mask_shifted = 0x7ff
frac_maskL = 0xfffffffffffff
exp_msk1L = 0x10000000000000
exp_shift1 = 20
exp_mask = 0x7ff00000
bias = 1023
p = 53
bndry_mask = 0xfffff
log2P = 1
)
func lo0bits(x uint32) (k int) {
if (x & 7) != 0 {
if (x & 1) != 0 {
return 0
}
if (x & 2) != 0 {
return 1
}
return 2
}
if (x & 0xffff) == 0 {
k = 16
x >>= 16
}
if (x & 0xff) == 0 {
k += 8
x >>= 8
}
if (x & 0xf) == 0 {
k += 4
x >>= 4
}
if (x & 0x3) == 0 {
k += 2
x >>= 2
}
if (x & 1) == 0 {
k++
x >>= 1
if (x & 1) == 0 {
return 32
}
}
return
}
func hi0bits(x uint32) (k int) {
if (x & 0xffff0000) == 0 {
k = 16
x <<= 16
}
if (x & 0xff000000) == 0 {
k += 8
x <<= 8
}
if (x & 0xf0000000) == 0 {
k += 4
x <<= 4
}
if (x & 0xc0000000) == 0 {
k += 2
x <<= 2
}
if (x & 0x80000000) == 0 {
k++
if (x & 0x40000000) == 0 {
return 32
}
}
return
}
func stuffBits(bits []byte, offset int, val uint32) {
bits[offset] = byte(val >> 24)
bits[offset+1] = byte(val >> 16)
bits[offset+2] = byte(val >> 8)
bits[offset+3] = byte(val)
}
func d2b(d float64, b []byte) (e, bits int, dblBits []byte) {
dBits := math.Float64bits(d)
d0 := uint32(dBits >> 32)
d1 := uint32(dBits)
z := d0 & frac_mask
d0 &= 0x7fffffff /* clear sign bit, which we ignore */
var de, k, i int
if de = int(d0 >> exp_shift); de != 0 {
z |= exp_msk1
}
y := d1
if y != 0 {
dblBits = b[:8]
k = lo0bits(y)
y >>= k
if k != 0 {
stuffBits(dblBits, 4, y|z<<(32-k))
z >>= k
} else {
stuffBits(dblBits, 4, y)
}
stuffBits(dblBits, 0, z)
if z != 0 {
i = 2
} else {
i = 1
}
} else {
dblBits = b[:4]
k = lo0bits(z)
z >>= k
stuffBits(dblBits, 0, z)
k += 32
i = 1
}
if de != 0 {
e = de - bias - (p - 1) + k
bits = p - k
} else {
e = de - bias - (p - 1) + 1 + k
bits = 32*i - hi0bits(z)
}
return
}
+699
View File
@@ -0,0 +1,699 @@
package ftoa
import (
"math"
"math/big"
)
const (
exp_11 = 0x3ff00000
frac_mask1 = 0xfffff
bletch = 0x10
quick_max = 14
int_max = 14
)
var (
tens = [...]float64{
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
1e20, 1e21, 1e22,
}
bigtens = [...]float64{1e16, 1e32, 1e64, 1e128, 1e256}
big5 = big.NewInt(5)
big10 = big.NewInt(10)
p05 = []*big.Int{big5, big.NewInt(25), big.NewInt(125)}
pow5Cache [7]*big.Int
dtoaModes = []int{
ModeStandard: 0,
ModeStandardExponential: 0,
ModeFixed: 3,
ModeExponential: 2,
ModePrecision: 2,
}
)
/*
d must be > 0 and must not be Inf
mode:
0 ==> shortest string that yields d when read in
and rounded to nearest.
1 ==> like 0, but with Steele & White stopping rule;
e.g. with IEEE P754 arithmetic , mode 0 gives
1e23 whereas mode 1 gives 9.999999999999999e22.
2 ==> max(1,ndigits) significant digits. This gives a
return value similar to that of ecvt, except
that trailing zeros are suppressed.
3 ==> through ndigits past the decimal point. This
gives a return value similar to that from fcvt,
except that trailing zeros are suppressed, and
ndigits can be negative.
4,5 ==> similar to 2 and 3, respectively, but (in
round-nearest mode) with the tests of mode 0 to
possibly return a shorter string that rounds to d.
With IEEE arithmetic and compilation with
-DHonor_FLT_ROUNDS, modes 4 and 5 behave the same
as modes 2 and 3 when FLT_ROUNDS != 1.
6-9 ==> Debugging modes similar to mode - 4: don't try
fast floating-point estimate (if applicable).
Values of mode other than 0-9 are treated as mode 0.
*/
func ftoa(d float64, mode int, biasUp bool, ndigits int, buf []byte) ([]byte, int) {
startPos := len(buf)
dblBits := make([]byte, 0, 8)
be, bbits, dblBits := d2b(d, dblBits)
dBits := math.Float64bits(d)
word0 := uint32(dBits >> 32)
word1 := uint32(dBits)
i := int((word0 >> exp_shift1) & (exp_mask >> exp_shift1))
var d2 float64
var denorm bool
if i != 0 {
d2 = setWord0(d, (word0&frac_mask1)|exp_11)
i -= bias
denorm = false
} else {
/* d is denormalized */
i = bbits + be + (bias + (p - 1) - 1)
var x uint64
if i > 32 {
x = uint64(word0)<<(64-i) | uint64(word1)>>(i-32)
} else {
x = uint64(word1) << (32 - i)
}
d2 = setWord0(float64(x), uint32((x>>32)-31*exp_mask))
i -= (bias + (p - 1) - 1) + 1
denorm = true
}
/* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */
ds := (d2-1.5)*0.289529654602168 + 0.1760912590558 + float64(i)*0.301029995663981
k := int(ds)
if ds < 0.0 && ds != float64(k) {
k-- /* want k = floor(ds) */
}
k_check := true
if k >= 0 && k < len(tens) {
if d < tens[k] {
k--
}
k_check = false
}
/* At this point floor(log10(d)) <= k <= floor(log10(d))+1.
If k_check is zero, we're guaranteed that k = floor(log10(d)). */
j := bbits - i - 1
var b2, s2, b5, s5 int
/* At this point d = b/2^j, where b is an odd integer. */
if j >= 0 {
b2 = 0
s2 = j
} else {
b2 = -j
s2 = 0
}
if k >= 0 {
b5 = 0
s5 = k
s2 += k
} else {
b2 -= k
b5 = -k
s5 = 0
}
/* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer,
b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */
if mode < 0 || mode > 9 {
mode = 0
}
try_quick := true
if mode > 5 {
mode -= 4
try_quick = false
}
leftright := true
var ilim, ilim1 int
switch mode {
case 0, 1:
ilim, ilim1 = -1, -1
ndigits = 0
case 2:
leftright = false
fallthrough
case 4:
if ndigits <= 0 {
ndigits = 1
}
ilim, ilim1 = ndigits, ndigits
case 3:
leftright = false
fallthrough
case 5:
i = ndigits + k + 1
ilim = i
ilim1 = i - 1
}
/* ilim is the maximum number of significant digits we want, based on k and ndigits. */
/* ilim1 is the maximum number of significant digits we want, based on k and ndigits,
when it turns out that k was computed too high by one. */
fast_failed := false
if ilim >= 0 && ilim <= quick_max && try_quick {
/* Try to get by with floating-point arithmetic. */
i = 0
d2 = d
k0 := k
ilim0 := ilim
ieps := 2 /* conservative */
/* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */
if k > 0 {
ds = tens[k&0xf]
j = k >> 4
if (j & bletch) != 0 {
/* prevent overflows */
j &= bletch - 1
d /= bigtens[len(bigtens)-1]
ieps++
}
for ; j != 0; i++ {
if (j & 1) != 0 {
ieps++
ds *= bigtens[i]
}
j >>= 1
}
d /= ds
} else if j1 := -k; j1 != 0 {
d *= tens[j1&0xf]
for j = j1 >> 4; j != 0; i++ {
if (j & 1) != 0 {
ieps++
d *= bigtens[i]
}
j >>= 1
}
}
/* Check that k was computed correctly. */
if k_check && d < 1.0 && ilim > 0 {
if ilim1 <= 0 {
fast_failed = true
} else {
ilim = ilim1
k--
d *= 10.
ieps++
}
}
/* eps bounds the cumulative error. */
eps := float64(ieps)*d + 7.0
eps = setWord0(eps, _word0(eps)-(p-1)*exp_msk1)
if ilim == 0 {
d -= 5.0
if d > eps {
buf = append(buf, '1')
k++
return buf, k + 1
}
if d < -eps {
buf = append(buf, '0')
return buf, 1
}
fast_failed = true
}
if !fast_failed {
fast_failed = true
if leftright {
/* Use Steele & White method of only
* generating digits needed.
*/
eps = 0.5/tens[ilim-1] - eps
for i = 0; ; {
l := int64(d)
d -= float64(l)
buf = append(buf, byte('0'+l))
if d < eps {
return buf, k + 1
}
if 1.0-d < eps {
buf, k = bumpUp(buf, k)
return buf, k + 1
}
i++
if i >= ilim {
break
}
eps *= 10.0
d *= 10.0
}
} else {
/* Generate ilim digits, then fix them up. */
eps *= tens[ilim-1]
for i = 1; ; i++ {
l := int64(d)
d -= float64(l)
buf = append(buf, byte('0'+l))
if i == ilim {
if d > 0.5+eps {
buf, k = bumpUp(buf, k)
return buf, k + 1
} else if d < 0.5-eps {
buf = stripTrailingZeroes(buf, startPos)
return buf, k + 1
}
break
}
d *= 10.0
}
}
}
if fast_failed {
buf = buf[:startPos]
d = d2
k = k0
ilim = ilim0
}
}
/* Do we have a "small" integer? */
if be >= 0 && k <= int_max {
/* Yes. */
ds = tens[k]
if ndigits < 0 && ilim <= 0 {
if ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds) {
buf = buf[:startPos]
buf = append(buf, '0')
return buf, 1
}
buf = append(buf, '1')
k++
return buf, k + 1
}
for i = 1; ; i++ {
l := int64(d / ds)
d -= float64(l) * ds
buf = append(buf, byte('0'+l))
if i == ilim {
d += d
if (d > ds) || (d == ds && (((l & 1) != 0) || biasUp)) {
buf, k = bumpUp(buf, k)
}
break
}
d *= 10.0
if d == 0 {
break
}
}
return buf, k + 1
}
m2 := b2
m5 := b5
var mhi, mlo *big.Int
if leftright {
if mode < 2 {
if denorm {
i = be + (bias + (p - 1) - 1 + 1)
} else {
i = 1 + p - bbits
}
/* i is 1 plus the number of trailing zero bits in d's significand. Thus,
(2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */
} else {
j = ilim - 1
if m5 >= j {
m5 -= j
} else {
j -= m5
s5 += j
b5 += j
m5 = 0
}
i = ilim
if i < 0 {
m2 -= i
i = 0
}
/* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */
}
b2 += i
s2 += i
mhi = big.NewInt(1)
/* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or
input (when mode < 2) significant digit, divided by 10^k. */
}
/* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in
b2, m2, and s2 without changing the equalities. */
if m2 > 0 && s2 > 0 {
if m2 < s2 {
i = m2
} else {
i = s2
}
b2 -= i
m2 -= i
s2 -= i
}
b := new(big.Int).SetBytes(dblBits)
/* Fold b5 into b and m5 into mhi. */
if b5 > 0 {
if leftright {
if m5 > 0 {
pow5mult(mhi, m5)
b.Mul(mhi, b)
}
j = b5 - m5
if j != 0 {
pow5mult(b, j)
}
} else {
pow5mult(b, b5)
}
}
/* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and
(mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */
S := big.NewInt(1)
if s5 > 0 {
pow5mult(S, s5)
}
/* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and
(mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */
/* Check for special case that d is a normalized power of 2. */
spec_case := false
if mode < 2 {
if (_word1(d) == 0) && ((_word0(d) & bndry_mask) == 0) &&
((_word0(d) & (exp_mask & (exp_mask << 1))) != 0) {
/* The special case. Here we want to be within a quarter of the last input
significant digit instead of one half of it when the decimal output string's value is less than d. */
b2 += log2P
s2 += log2P
spec_case = true
}
}
/* Arrange for convenient computation of quotients:
* shift left if necessary so divisor has 4 leading 0 bits.
*
* Perhaps we should just compute leading 28 bits of S once
* and for all and pass them and a shift to quorem, so it
* can do shifts and ors to compute the numerator for q.
*/
var zz int
if s5 != 0 {
S_bytes := S.Bytes()
var S_hiWord uint32
for idx := 0; idx < 4; idx++ {
S_hiWord = S_hiWord << 8
if idx < len(S_bytes) {
S_hiWord |= uint32(S_bytes[idx])
}
}
zz = 32 - hi0bits(S_hiWord)
} else {
zz = 1
}
i = (zz + s2) & 0x1f
if i != 0 {
i = 32 - i
}
/* i is the number of leading zero bits in the most significant word of S*2^s2. */
if i > 4 {
i -= 4
b2 += i
m2 += i
s2 += i
} else if i < 4 {
i += 28
b2 += i
m2 += i
s2 += i
}
/* Now S*2^s2 has exactly four leading zero bits in its most significant word. */
if b2 > 0 {
b = b.Lsh(b, uint(b2))
}
if s2 > 0 {
S.Lsh(S, uint(s2))
}
/* Now we have d/10^k = b/S and
(mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */
if k_check {
if b.Cmp(S) < 0 {
k--
b.Mul(b, big10) /* we botched the k estimate */
if leftright {
mhi.Mul(mhi, big10)
}
ilim = ilim1
}
}
/* At this point 1 <= d/10^k = b/S < 10. */
if ilim <= 0 && mode > 2 {
/* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode.
Output either zero or the minimum nonzero output depending on which is closer to d. */
if ilim >= 0 {
i = b.Cmp(S.Mul(S, big5))
}
if ilim < 0 || i < 0 || i == 0 && !biasUp {
/* Always emit at least one digit. If the number appears to be zero
using the current mode, then emit one '0' digit and set decpt to 1. */
buf = buf[:startPos]
buf = append(buf, '0')
return buf, 1
}
buf = append(buf, '1')
k++
return buf, k + 1
}
var dig byte
if leftright {
if m2 > 0 {
mhi.Lsh(mhi, uint(m2))
}
/* Compute mlo -- check for special case
* that d is a normalized power of 2.
*/
mlo = mhi
if spec_case {
mhi = mlo
mhi = new(big.Int).Lsh(mhi, log2P)
}
/* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */
/* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */
var z, delta big.Int
for i = 1; ; i++ {
z.DivMod(b, S, b)
dig = byte(z.Int64() + '0')
/* Do we yet have the shortest decimal string
* that will round to d?
*/
j = b.Cmp(mlo)
/* j is b/S compared with mlo/S. */
delta.Sub(S, mhi)
var j1 int
if delta.Sign() <= 0 {
j1 = 1
} else {
j1 = b.Cmp(&delta)
}
/* j1 is b/S compared with 1 - mhi/S. */
if (j1 == 0) && (mode == 0) && ((_word1(d) & 1) == 0) {
if dig == '9' {
var flag bool
buf = append(buf, '9')
if buf, flag = roundOff(buf, startPos); flag {
k++
buf = append(buf, '1')
}
return buf, k + 1
}
if j > 0 {
dig++
}
buf = append(buf, dig)
return buf, k + 1
}
if (j < 0) || ((j == 0) && (mode == 0) && ((_word1(d) & 1) == 0)) {
if j1 > 0 {
/* Either dig or dig+1 would work here as the least significant decimal digit.
Use whichever would produce a decimal value closer to d. */
b.Lsh(b, 1)
j1 = b.Cmp(S)
if (j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp)) {
dig++
if dig == '9' {
buf = append(buf, '9')
buf, flag := roundOff(buf, startPos)
if flag {
k++
buf = append(buf, '1')
}
return buf, k + 1
}
}
}
buf = append(buf, dig)
return buf, k + 1
}
if j1 > 0 {
if dig == '9' { /* possible if i == 1 */
buf = append(buf, '9')
buf, flag := roundOff(buf, startPos)
if flag {
k++
buf = append(buf, '1')
}
return buf, k + 1
}
buf = append(buf, dig+1)
return buf, k + 1
}
buf = append(buf, dig)
if i == ilim {
break
}
b.Mul(b, big10)
if mlo == mhi {
mhi.Mul(mhi, big10)
} else {
mlo.Mul(mlo, big10)
mhi.Mul(mhi, big10)
}
}
} else {
var z big.Int
for i = 1; ; i++ {
z.DivMod(b, S, b)
dig = byte(z.Int64() + '0')
buf = append(buf, dig)
if i >= ilim {
break
}
b.Mul(b, big10)
}
}
/* Round off last digit */
b.Lsh(b, 1)
j = b.Cmp(S)
if (j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp)) {
var flag bool
buf, flag = roundOff(buf, startPos)
if flag {
k++
buf = append(buf, '1')
return buf, k + 1
}
} else {
buf = stripTrailingZeroes(buf, startPos)
}
return buf, k + 1
}
func bumpUp(buf []byte, k int) ([]byte, int) {
var lastCh byte
stop := 0
if len(buf) > 0 && buf[0] == '-' {
stop = 1
}
for {
lastCh = buf[len(buf)-1]
buf = buf[:len(buf)-1]
if lastCh != '9' {
break
}
if len(buf) == stop {
k++
lastCh = '0'
break
}
}
buf = append(buf, lastCh+1)
return buf, k
}
func setWord0(d float64, w uint32) float64 {
dBits := math.Float64bits(d)
return math.Float64frombits(uint64(w)<<32 | dBits&0xffffffff)
}
func _word0(d float64) uint32 {
dBits := math.Float64bits(d)
return uint32(dBits >> 32)
}
func _word1(d float64) uint32 {
dBits := math.Float64bits(d)
return uint32(dBits)
}
func stripTrailingZeroes(buf []byte, startPos int) []byte {
bl := len(buf) - 1
for bl >= startPos && buf[bl] == '0' {
bl--
}
return buf[:bl+1]
}
/* Set b = b * 5^k. k must be nonnegative. */
func pow5mult(b *big.Int, k int) *big.Int {
if k < (1 << (len(pow5Cache) + 2)) {
i := k & 3
if i != 0 {
b.Mul(b, p05[i-1])
}
k >>= 2
i = 0
for {
if k&1 != 0 {
b.Mul(b, pow5Cache[i])
}
k >>= 1
if k == 0 {
break
}
i++
}
return b
}
return b.Mul(b, new(big.Int).Exp(big5, big.NewInt(int64(k)), nil))
}
func roundOff(buf []byte, startPos int) ([]byte, bool) {
i := len(buf)
for i != startPos {
i--
if buf[i] != '9' {
buf[i]++
return buf[:i+1], false
}
}
return buf[:startPos], true
}
func init() {
p := big.NewInt(625)
pow5Cache[0] = p
for i := 1; i < len(pow5Cache); i++ {
p = new(big.Int).Mul(p, p)
pow5Cache[i] = p
}
}
+153
View File
@@ -0,0 +1,153 @@
package ftoa
import (
"fmt"
"math"
"math/big"
"strconv"
"strings"
)
const (
digits = "0123456789abcdefghijklmnopqrstuvwxyz"
)
func FToBaseStr(num float64, radix int) string {
var negative bool
if num < 0 {
num = -num
negative = true
}
dfloor := math.Floor(num)
ldfloor := int64(dfloor)
var intDigits string
if dfloor == float64(ldfloor) {
if negative {
ldfloor = -ldfloor
}
intDigits = strconv.FormatInt(ldfloor, radix)
} else {
floorBits := math.Float64bits(num)
exp := int(floorBits>>exp_shiftL) & exp_mask_shifted
var mantissa int64
if exp == 0 {
mantissa = int64((floorBits & frac_maskL) << 1)
} else {
mantissa = int64((floorBits & frac_maskL) | exp_msk1L)
}
if negative {
mantissa = -mantissa
}
exp -= 1075
x := big.NewInt(mantissa)
if exp > 0 {
x.Lsh(x, uint(exp))
} else if exp < 0 {
x.Rsh(x, uint(-exp))
}
intDigits = x.Text(radix)
}
if num == dfloor {
// No fraction part
return intDigits
} else {
/* We have a fraction. */
var buffer strings.Builder
buffer.WriteString(intDigits)
buffer.WriteByte('.')
df := num - dfloor
dBits := math.Float64bits(num)
word0 := uint32(dBits >> 32)
word1 := uint32(dBits)
dblBits := make([]byte, 0, 8)
e, _, dblBits := d2b(df, dblBits)
// JS_ASSERT(e < 0);
/* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */
s2 := -int((word0 >> exp_shift1) & (exp_mask >> exp_shift1))
if s2 == 0 {
s2 = -1
}
s2 += bias + p
/* 1/2^s2 = (nextDouble(d) - d)/2 */
// JS_ASSERT(-s2 < e);
if -s2 >= e {
panic(fmt.Errorf("-s2 >= e: %d, %d", -s2, e))
}
mlo := big.NewInt(1)
mhi := mlo
if (word1 == 0) && ((word0 & bndry_mask) == 0) && ((word0 & (exp_mask & (exp_mask << 1))) != 0) {
/* The special case. Here we want to be within a quarter of the last input
significant digit instead of one half of it when the output string's value is less than d. */
s2 += log2P
mhi = big.NewInt(1 << log2P)
}
b := new(big.Int).SetBytes(dblBits)
b.Lsh(b, uint(e+s2))
s := big.NewInt(1)
s.Lsh(s, uint(s2))
/* At this point we have the following:
* s = 2^s2;
* 1 > df = b/2^s2 > 0;
* (d - prevDouble(d))/2 = mlo/2^s2;
* (nextDouble(d) - d)/2 = mhi/2^s2. */
bigBase := big.NewInt(int64(radix))
done := false
m := &big.Int{}
delta := &big.Int{}
for !done {
b.Mul(b, bigBase)
b.DivMod(b, s, m)
digit := byte(b.Int64())
b, m = m, b
mlo.Mul(mlo, bigBase)
if mlo != mhi {
mhi.Mul(mhi, bigBase)
}
/* Do we yet have the shortest string that will round to d? */
j := b.Cmp(mlo)
/* j is b/2^s2 compared with mlo/2^s2. */
delta.Sub(s, mhi)
var j1 int
if delta.Sign() <= 0 {
j1 = 1
} else {
j1 = b.Cmp(delta)
}
/* j1 is b/2^s2 compared with 1 - mhi/2^s2. */
if j1 == 0 && (word1&1) == 0 {
if j > 0 {
digit++
}
done = true
} else if j < 0 || (j == 0 && ((word1 & 1) == 0)) {
if j1 > 0 {
/* Either dig or dig+1 would work here as the least significant digit.
Use whichever would produce an output value closer to d. */
b.Lsh(b, 1)
j1 = b.Cmp(s)
if j1 > 0 { /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output such as 3.5 in base 3. */
digit++
}
}
done = true
} else if j1 > 0 {
digit++
done = true
}
// JS_ASSERT(digit < (uint32)base);
buffer.WriteByte(digits[digit])
}
return buffer.String()
}
}
+147
View File
@@ -0,0 +1,147 @@
package ftoa
import (
"math"
"strconv"
"github.com/dop251/goja/ftoa/internal/fast"
)
type FToStrMode int
const (
// Either fixed or exponential format; round-trip
ModeStandard FToStrMode = iota
// Always exponential format; round-trip
ModeStandardExponential
// Round to <precision> digits after the decimal point; exponential if number is large
ModeFixed
// Always exponential format; <precision> significant digits
ModeExponential
// Either fixed or exponential format; <precision> significant digits
ModePrecision
)
func insert(b []byte, p int, c byte) []byte {
b = append(b, 0)
copy(b[p+1:], b[p:])
b[p] = c
return b
}
func expand(b []byte, delta int) []byte {
newLen := len(b) + delta
if newLen <= cap(b) {
return b[:newLen]
}
b1 := make([]byte, newLen)
copy(b1, b)
return b1
}
func FToStr(d float64, mode FToStrMode, precision int, buffer []byte) []byte {
if math.IsNaN(d) {
buffer = append(buffer, "NaN"...)
return buffer
}
if math.IsInf(d, 0) {
if math.Signbit(d) {
buffer = append(buffer, '-')
}
buffer = append(buffer, "Infinity"...)
return buffer
}
if mode == ModeFixed && (d >= 1e21 || d <= -1e21) {
mode = ModeStandard
}
var decPt int
var ok bool
startPos := len(buffer)
if d != 0 { // also matches -0
if d < 0 {
buffer = append(buffer, '-')
d = -d
startPos++
}
switch mode {
case ModeStandard, ModeStandardExponential:
buffer, decPt, ok = fast.Dtoa(d, fast.ModeShortest, 0, buffer)
case ModeExponential, ModePrecision:
buffer, decPt, ok = fast.Dtoa(d, fast.ModePrecision, precision, buffer)
}
} else {
buffer = append(buffer, '0')
decPt, ok = 1, true
}
if !ok {
buffer, decPt = ftoa(d, dtoaModes[mode], mode >= ModeFixed, precision, buffer)
}
exponentialNotation := false
minNDigits := 0 /* Minimum number of significand digits required by mode and precision */
nDigits := len(buffer) - startPos
switch mode {
case ModeStandard:
if decPt < -5 || decPt > 21 {
exponentialNotation = true
} else {
minNDigits = decPt
}
case ModeFixed:
if precision >= 0 {
minNDigits = decPt + precision
} else {
minNDigits = decPt
}
case ModeExponential:
// JS_ASSERT(precision > 0);
minNDigits = precision
fallthrough
case ModeStandardExponential:
exponentialNotation = true
case ModePrecision:
// JS_ASSERT(precision > 0);
minNDigits = precision
if decPt < -5 || decPt > precision {
exponentialNotation = true
}
}
for nDigits < minNDigits {
buffer = append(buffer, '0')
nDigits++
}
if exponentialNotation {
/* Insert a decimal point if more than one significand digit */
if nDigits != 1 {
buffer = insert(buffer, startPos+1, '.')
}
buffer = append(buffer, 'e')
if decPt-1 >= 0 {
buffer = append(buffer, '+')
}
buffer = strconv.AppendInt(buffer, int64(decPt-1), 10)
} else if decPt != nDigits {
/* Some kind of a fraction in fixed notation */
// JS_ASSERT(decPt <= nDigits);
if decPt > 0 {
/* dd...dd . dd...dd */
buffer = insert(buffer, startPos+decPt, '.')
} else {
/* 0 . 00...00dd...dd */
buffer = expand(buffer, 2-decPt)
copy(buffer[startPos+2-decPt:], buffer[startPos:])
buffer[startPos] = '0'
buffer[startPos+1] = '.'
for i := startPos + 2; i < startPos+2-decPt; i++ {
buffer[i] = '0'
}
}
}
return buffer
}
+26
View File
@@ -0,0 +1,26 @@
Copyright 2014, the V8 project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+120
View File
@@ -0,0 +1,120 @@
package fast
import "math"
const (
kCachedPowersOffset = 348 // -1 * the first decimal_exponent.
kD_1_LOG2_10 = 0.30102999566398114 // 1 / lg(10)
kDecimalExponentDistance = 8
)
type cachedPower struct {
significand uint64
binary_exponent int16
decimal_exponent int16
}
var (
cachedPowers = [...]cachedPower{
{0xFA8FD5A0081C0288, -1220, -348},
{0xBAAEE17FA23EBF76, -1193, -340},
{0x8B16FB203055AC76, -1166, -332},
{0xCF42894A5DCE35EA, -1140, -324},
{0x9A6BB0AA55653B2D, -1113, -316},
{0xE61ACF033D1A45DF, -1087, -308},
{0xAB70FE17C79AC6CA, -1060, -300},
{0xFF77B1FCBEBCDC4F, -1034, -292},
{0xBE5691EF416BD60C, -1007, -284},
{0x8DD01FAD907FFC3C, -980, -276},
{0xD3515C2831559A83, -954, -268},
{0x9D71AC8FADA6C9B5, -927, -260},
{0xEA9C227723EE8BCB, -901, -252},
{0xAECC49914078536D, -874, -244},
{0x823C12795DB6CE57, -847, -236},
{0xC21094364DFB5637, -821, -228},
{0x9096EA6F3848984F, -794, -220},
{0xD77485CB25823AC7, -768, -212},
{0xA086CFCD97BF97F4, -741, -204},
{0xEF340A98172AACE5, -715, -196},
{0xB23867FB2A35B28E, -688, -188},
{0x84C8D4DFD2C63F3B, -661, -180},
{0xC5DD44271AD3CDBA, -635, -172},
{0x936B9FCEBB25C996, -608, -164},
{0xDBAC6C247D62A584, -582, -156},
{0xA3AB66580D5FDAF6, -555, -148},
{0xF3E2F893DEC3F126, -529, -140},
{0xB5B5ADA8AAFF80B8, -502, -132},
{0x87625F056C7C4A8B, -475, -124},
{0xC9BCFF6034C13053, -449, -116},
{0x964E858C91BA2655, -422, -108},
{0xDFF9772470297EBD, -396, -100},
{0xA6DFBD9FB8E5B88F, -369, -92},
{0xF8A95FCF88747D94, -343, -84},
{0xB94470938FA89BCF, -316, -76},
{0x8A08F0F8BF0F156B, -289, -68},
{0xCDB02555653131B6, -263, -60},
{0x993FE2C6D07B7FAC, -236, -52},
{0xE45C10C42A2B3B06, -210, -44},
{0xAA242499697392D3, -183, -36},
{0xFD87B5F28300CA0E, -157, -28},
{0xBCE5086492111AEB, -130, -20},
{0x8CBCCC096F5088CC, -103, -12},
{0xD1B71758E219652C, -77, -4},
{0x9C40000000000000, -50, 4},
{0xE8D4A51000000000, -24, 12},
{0xAD78EBC5AC620000, 3, 20},
{0x813F3978F8940984, 30, 28},
{0xC097CE7BC90715B3, 56, 36},
{0x8F7E32CE7BEA5C70, 83, 44},
{0xD5D238A4ABE98068, 109, 52},
{0x9F4F2726179A2245, 136, 60},
{0xED63A231D4C4FB27, 162, 68},
{0xB0DE65388CC8ADA8, 189, 76},
{0x83C7088E1AAB65DB, 216, 84},
{0xC45D1DF942711D9A, 242, 92},
{0x924D692CA61BE758, 269, 100},
{0xDA01EE641A708DEA, 295, 108},
{0xA26DA3999AEF774A, 322, 116},
{0xF209787BB47D6B85, 348, 124},
{0xB454E4A179DD1877, 375, 132},
{0x865B86925B9BC5C2, 402, 140},
{0xC83553C5C8965D3D, 428, 148},
{0x952AB45CFA97A0B3, 455, 156},
{0xDE469FBD99A05FE3, 481, 164},
{0xA59BC234DB398C25, 508, 172},
{0xF6C69A72A3989F5C, 534, 180},
{0xB7DCBF5354E9BECE, 561, 188},
{0x88FCF317F22241E2, 588, 196},
{0xCC20CE9BD35C78A5, 614, 204},
{0x98165AF37B2153DF, 641, 212},
{0xE2A0B5DC971F303A, 667, 220},
{0xA8D9D1535CE3B396, 694, 228},
{0xFB9B7CD9A4A7443C, 720, 236},
{0xBB764C4CA7A44410, 747, 244},
{0x8BAB8EEFB6409C1A, 774, 252},
{0xD01FEF10A657842C, 800, 260},
{0x9B10A4E5E9913129, 827, 268},
{0xE7109BFBA19C0C9D, 853, 276},
{0xAC2820D9623BF429, 880, 284},
{0x80444B5E7AA7CF85, 907, 292},
{0xBF21E44003ACDD2D, 933, 300},
{0x8E679C2F5E44FF8F, 960, 308},
{0xD433179D9C8CB841, 986, 316},
{0x9E19DB92B4E31BA9, 1013, 324},
{0xEB96BF6EBADF77D9, 1039, 332},
{0xAF87023B9BF0EE6B, 1066, 340},
}
)
func getCachedPowerForBinaryExponentRange(min_exponent, max_exponent int) (power diyfp, decimal_exponent int) {
kQ := diyFpKSignificandSize
k := int(math.Ceil(float64(min_exponent+kQ-1) * kD_1_LOG2_10))
index := (kCachedPowersOffset+k-1)/kDecimalExponentDistance + 1
cached_power := cachedPowers[index]
_DCHECK(min_exponent <= int(cached_power.binary_exponent))
_DCHECK(int(cached_power.binary_exponent) <= max_exponent)
decimal_exponent = int(cached_power.decimal_exponent)
power = diyfp{f: cached_power.significand, e: int(cached_power.binary_exponent)}
return
}
+18
View File
@@ -0,0 +1,18 @@
/*
Package fast contains code ported from V8 (https://github.com/v8/v8/blob/master/src/numbers/fast-dtoa.cc)
See LICENSE_V8 for the original copyright message and disclaimer.
*/
package fast
import "errors"
var (
dcheckFailure = errors.New("DCHECK assertion failed")
)
func _DCHECK(f bool) {
if !f {
panic(dcheckFailure)
}
}
+152
View File
@@ -0,0 +1,152 @@
package fast
import "math"
const (
diyFpKSignificandSize = 64
kSignificandSize = 53
kUint64MSB uint64 = 1 << 63
kSignificandMask = 0x000FFFFFFFFFFFFF
kHiddenBit = 0x0010000000000000
kExponentMask = 0x7FF0000000000000
kPhysicalSignificandSize = 52 // Excludes the hidden bit.
kExponentBias = 0x3FF + kPhysicalSignificandSize
kDenormalExponent = -kExponentBias + 1
)
type double float64
type diyfp struct {
f uint64
e int
}
// f =- o.
// The exponents of both numbers must be the same and the significand of this
// must be bigger than the significand of other.
// The result will not be normalized.
func (f *diyfp) subtract(o diyfp) {
_DCHECK(f.e == o.e)
_DCHECK(f.f >= o.f)
f.f -= o.f
}
// Returns f - o
// The exponents of both numbers must be the same and this must be bigger
// than other. The result will not be normalized.
func (f diyfp) minus(o diyfp) diyfp {
res := f
res.subtract(o)
return res
}
// f *= o
func (f *diyfp) mul(o diyfp) {
// Simply "emulates" a 128 bit multiplication.
// However: the resulting number only contains 64 bits. The least
// significant 64 bits are only used for rounding the most significant 64
// bits.
const kM32 uint64 = 0xFFFFFFFF
a := f.f >> 32
b := f.f & kM32
c := o.f >> 32
d := o.f & kM32
ac := a * c
bc := b * c
ad := a * d
bd := b * d
tmp := (bd >> 32) + (ad & kM32) + (bc & kM32)
// By adding 1U << 31 to tmp we round the final result.
// Halfway cases will be round up.
tmp += 1 << 31
result_f := ac + (ad >> 32) + (bc >> 32) + (tmp >> 32)
f.e += o.e + 64
f.f = result_f
}
// Returns f * o
func (f diyfp) times(o diyfp) diyfp {
res := f
res.mul(o)
return res
}
func (f *diyfp) _normalize() {
f_, e := f.f, f.e
// This method is mainly called for normalizing boundaries. In general
// boundaries need to be shifted by 10 bits. We thus optimize for this case.
const k10MSBits uint64 = 0x3FF << 54
for f_&k10MSBits == 0 {
f_ <<= 10
e -= 10
}
for f_&kUint64MSB == 0 {
f_ <<= 1
e--
}
f.f, f.e = f_, e
}
func normalizeDiyfp(f diyfp) diyfp {
res := f
res._normalize()
return res
}
// f must be strictly greater than 0.
func (d double) toNormalizedDiyfp() diyfp {
f, e := d.sigExp()
// The current float could be a denormal.
for (f & kHiddenBit) == 0 {
f <<= 1
e--
}
// Do the final shifts in one go.
f <<= diyFpKSignificandSize - kSignificandSize
e -= diyFpKSignificandSize - kSignificandSize
return diyfp{f, e}
}
// Returns the two boundaries of this.
// The bigger boundary (m_plus) is normalized. The lower boundary has the same
// exponent as m_plus.
// Precondition: the value encoded by this Double must be greater than 0.
func (d double) normalizedBoundaries() (m_minus, m_plus diyfp) {
v := d.toDiyFp()
significand_is_zero := v.f == kHiddenBit
m_plus = normalizeDiyfp(diyfp{f: (v.f << 1) + 1, e: v.e - 1})
if significand_is_zero && v.e != kDenormalExponent {
// The boundary is closer. Think of v = 1000e10 and v- = 9999e9.
// Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
// at a distance of 1e8.
// The only exception is for the smallest normal: the largest denormal is
// at the same distance as its successor.
// Note: denormals have the same exponent as the smallest normals.
m_minus = diyfp{f: (v.f << 2) - 1, e: v.e - 2}
} else {
m_minus = diyfp{f: (v.f << 1) - 1, e: v.e - 1}
}
m_minus.f <<= m_minus.e - m_plus.e
m_minus.e = m_plus.e
return
}
func (d double) toDiyFp() diyfp {
f, e := d.sigExp()
return diyfp{f: f, e: e}
}
func (d double) sigExp() (significand uint64, exponent int) {
d64 := math.Float64bits(float64(d))
significand = d64 & kSignificandMask
if d64&kExponentMask != 0 { // not denormal
significand += kHiddenBit
exponent = int((d64&kExponentMask)>>kPhysicalSignificandSize) - kExponentBias
} else {
exponent = kDenormalExponent
}
return
}
+642
View File
@@ -0,0 +1,642 @@
package fast
import (
"fmt"
"strconv"
)
const (
kMinimalTargetExponent = -60
kMaximalTargetExponent = -32
kTen4 = 10000
kTen5 = 100000
kTen6 = 1000000
kTen7 = 10000000
kTen8 = 100000000
kTen9 = 1000000000
)
type Mode int
const (
ModeShortest Mode = iota
ModePrecision
)
// Adjusts the last digit of the generated number, and screens out generated
// solutions that may be inaccurate. A solution may be inaccurate if it is
// outside the safe interval, or if we cannot prove that it is closer to the
// input than a neighboring representation of the same length.
//
// Input: * buffer containing the digits of too_high / 10^kappa
// - distance_too_high_w == (too_high - w).f() * unit
// - unsafe_interval == (too_high - too_low).f() * unit
// - rest = (too_high - buffer * 10^kappa).f() * unit
// - ten_kappa = 10^kappa * unit
// - unit = the common multiplier
//
// Output: returns true if the buffer is guaranteed to contain the closest
//
// representable number to the input.
// Modifies the generated digits in the buffer to approach (round towards) w.
func roundWeed(buffer []byte, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit uint64) bool {
small_distance := distance_too_high_w - unit
big_distance := distance_too_high_w + unit
// Let w_low = too_high - big_distance, and
// w_high = too_high - small_distance.
// Note: w_low < w < w_high
//
// The real w (* unit) must lie somewhere inside the interval
// ]w_low; w_high[ (often written as "(w_low; w_high)")
// Basically the buffer currently contains a number in the unsafe interval
// ]too_low; too_high[ with too_low < w < too_high
//
// too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ^v 1 unit ^ ^ ^ ^
// boundary_high --------------------- . . . .
// ^v 1 unit . . . .
// - - - - - - - - - - - - - - - - - - - + - - + - - - - - - . .
// . . ^ . .
// . big_distance . . .
// . . . . rest
// small_distance . . . .
// v . . . .
// w_high - - - - - - - - - - - - - - - - - - . . . .
// ^v 1 unit . . . .
// w ---------------------------------------- . . . .
// ^v 1 unit v . . .
// w_low - - - - - - - - - - - - - - - - - - - - - . . .
// . . v
// buffer --------------------------------------------------+-------+--------
// . .
// safe_interval .
// v .
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .
// ^v 1 unit .
// boundary_low ------------------------- unsafe_interval
// ^v 1 unit v
// too_low - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
//
// Note that the value of buffer could lie anywhere inside the range too_low
// to too_high.
//
// boundary_low, boundary_high and w are approximations of the real boundaries
// and v (the input number). They are guaranteed to be precise up to one unit.
// In fact the error is guaranteed to be strictly less than one unit.
//
// Anything that lies outside the unsafe interval is guaranteed not to round
// to v when read again.
// Anything that lies inside the safe interval is guaranteed to round to v
// when read again.
// If the number inside the buffer lies inside the unsafe interval but not
// inside the safe interval then we simply do not know and bail out (returning
// false).
//
// Similarly we have to take into account the imprecision of 'w' when finding
// the closest representation of 'w'. If we have two potential
// representations, and one is closer to both w_low and w_high, then we know
// it is closer to the actual value v.
//
// By generating the digits of too_high we got the largest (closest to
// too_high) buffer that is still in the unsafe interval. In the case where
// w_high < buffer < too_high we try to decrement the buffer.
// This way the buffer approaches (rounds towards) w.
// There are 3 conditions that stop the decrementation process:
// 1) the buffer is already below w_high
// 2) decrementing the buffer would make it leave the unsafe interval
// 3) decrementing the buffer would yield a number below w_high and farther
// away than the current number. In other words:
// (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high
// Instead of using the buffer directly we use its distance to too_high.
// Conceptually rest ~= too_high - buffer
// We need to do the following tests in this order to avoid over- and
// underflows.
_DCHECK(rest <= unsafe_interval)
for rest < small_distance && // Negated condition 1
unsafe_interval-rest >= ten_kappa && // Negated condition 2
(rest+ten_kappa < small_distance || // buffer{-1} > w_high
small_distance-rest >= rest+ten_kappa-small_distance) {
buffer[len(buffer)-1]--
rest += ten_kappa
}
// We have approached w+ as much as possible. We now test if approaching w-
// would require changing the buffer. If yes, then we have two possible
// representations close to w, but we cannot decide which one is closer.
if rest < big_distance && unsafe_interval-rest >= ten_kappa &&
(rest+ten_kappa < big_distance ||
big_distance-rest > rest+ten_kappa-big_distance) {
return false
}
// Weeding test.
// The safe interval is [too_low + 2 ulp; too_high - 2 ulp]
// Since too_low = too_high - unsafe_interval this is equivalent to
// [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp]
// Conceptually we have: rest ~= too_high - buffer
return (2*unit <= rest) && (rest <= unsafe_interval-4*unit)
}
// Rounds the buffer upwards if the result is closer to v by possibly adding
// 1 to the buffer. If the precision of the calculation is not sufficient to
// round correctly, return false.
// The rounding might shift the whole buffer in which case the kappa is
// adjusted. For example "99", kappa = 3 might become "10", kappa = 4.
//
// If 2*rest > ten_kappa then the buffer needs to be round up.
// rest can have an error of +/- 1 unit. This function accounts for the
// imprecision and returns false, if the rounding direction cannot be
// unambiguously determined.
//
// Precondition: rest < ten_kappa.
func roundWeedCounted(buffer []byte, rest, ten_kappa, unit uint64, kappa *int) bool {
_DCHECK(rest < ten_kappa)
// The following tests are done in a specific order to avoid overflows. They
// will work correctly with any uint64 values of rest < ten_kappa and unit.
//
// If the unit is too big, then we don't know which way to round. For example
// a unit of 50 means that the real number lies within rest +/- 50. If
// 10^kappa == 40 then there is no way to tell which way to round.
if unit >= ten_kappa {
return false
}
// Even if unit is just half the size of 10^kappa we are already completely
// lost. (And after the previous test we know that the expression will not
// over/underflow.)
if ten_kappa-unit <= unit {
return false
}
// If 2 * (rest + unit) <= 10^kappa we can safely round down.
if (ten_kappa-rest > rest) && (ten_kappa-2*rest >= 2*unit) {
return true
}
// If 2 * (rest - unit) >= 10^kappa, then we can safely round up.
if (rest > unit) && (ten_kappa-(rest-unit) <= (rest - unit)) {
// Increment the last digit recursively until we find a non '9' digit.
buffer[len(buffer)-1]++
for i := len(buffer) - 1; i > 0; i-- {
if buffer[i] != '0'+10 {
break
}
buffer[i] = '0'
buffer[i-1]++
}
// If the first digit is now '0'+ 10 we had a buffer with all '9's. With the
// exception of the first digit all digits are now '0'. Simply switch the
// first digit to '1' and adjust the kappa. Example: "99" becomes "10" and
// the power (the kappa) is increased.
if buffer[0] == '0'+10 {
buffer[0] = '1'
*kappa += 1
}
return true
}
return false
}
// Returns the biggest power of ten that is less than or equal than the given
// number. We furthermore receive the maximum number of bits 'number' has.
// If number_bits == 0 then 0^-1 is returned
// The number of bits must be <= 32.
// Precondition: number < (1 << (number_bits + 1)).
func biggestPowerTen(number uint32, number_bits int) (power uint32, exponent int) {
switch number_bits {
case 32, 31, 30:
if kTen9 <= number {
power = kTen9
exponent = 9
break
}
fallthrough
case 29, 28, 27:
if kTen8 <= number {
power = kTen8
exponent = 8
break
}
fallthrough
case 26, 25, 24:
if kTen7 <= number {
power = kTen7
exponent = 7
break
}
fallthrough
case 23, 22, 21, 20:
if kTen6 <= number {
power = kTen6
exponent = 6
break
}
fallthrough
case 19, 18, 17:
if kTen5 <= number {
power = kTen5
exponent = 5
break
}
fallthrough
case 16, 15, 14:
if kTen4 <= number {
power = kTen4
exponent = 4
break
}
fallthrough
case 13, 12, 11, 10:
if 1000 <= number {
power = 1000
exponent = 3
break
}
fallthrough
case 9, 8, 7:
if 100 <= number {
power = 100
exponent = 2
break
}
fallthrough
case 6, 5, 4:
if 10 <= number {
power = 10
exponent = 1
break
}
fallthrough
case 3, 2, 1:
if 1 <= number {
power = 1
exponent = 0
break
}
fallthrough
case 0:
power = 0
exponent = -1
}
return
}
// Generates the digits of input number w.
// w is a floating-point number (DiyFp), consisting of a significand and an
// exponent. Its exponent is bounded by kMinimalTargetExponent and
// kMaximalTargetExponent.
//
// Hence -60 <= w.e() <= -32.
//
// Returns false if it fails, in which case the generated digits in the buffer
// should not be used.
// Preconditions:
// - low, w and high are correct up to 1 ulp (unit in the last place). That
// is, their error must be less than a unit of their last digits.
// - low.e() == w.e() == high.e()
// - low < w < high, and taking into account their error: low~ <= high~
// - kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
//
// Postconditions: returns false if procedure fails.
//
// otherwise:
// * buffer is not null-terminated, but len contains the number of digits.
// * buffer contains the shortest possible decimal digit-sequence
// such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
// correct values of low and high (without their error).
// * if more than one decimal representation gives the minimal number of
// decimal digits then the one closest to W (where W is the correct value
// of w) is chosen.
//
// Remark: this procedure takes into account the imprecision of its input
//
// numbers. If the precision is not enough to guarantee all the postconditions
// then false is returned. This usually happens rarely (~0.5%).
//
// Say, for the sake of example, that
//
// w.e() == -48, and w.f() == 0x1234567890ABCDEF
//
// w's value can be computed by w.f() * 2^w.e()
// We can obtain w's integral digits by simply shifting w.f() by -w.e().
//
// -> w's integral part is 0x1234
// w's fractional part is therefore 0x567890ABCDEF.
//
// Printing w's integral part is easy (simply print 0x1234 in decimal).
// In order to print its fraction we repeatedly multiply the fraction by 10 and
// get each digit. Example the first digit after the point would be computed by
//
// (0x567890ABCDEF * 10) >> 48. -> 3
//
// The whole thing becomes slightly more complicated because we want to stop
// once we have enough digits. That is, once the digits inside the buffer
// represent 'w' we can stop. Everything inside the interval low - high
// represents w. However we have to pay attention to low, high and w's
// imprecision.
func digitGen(low, w, high diyfp, buffer []byte) (kappa int, buf []byte, res bool) {
_DCHECK(low.e == w.e && w.e == high.e)
_DCHECK(low.f+1 <= high.f-1)
_DCHECK(kMinimalTargetExponent <= w.e && w.e <= kMaximalTargetExponent)
// low, w and high are imprecise, but by less than one ulp (unit in the last
// place).
// If we remove (resp. add) 1 ulp from low (resp. high) we are certain that
// the new numbers are outside of the interval we want the final
// representation to lie in.
// Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield
// numbers that are certain to lie in the interval. We will use this fact
// later on.
// We will now start by generating the digits within the uncertain
// interval. Later we will weed out representations that lie outside the safe
// interval and thus _might_ lie outside the correct interval.
unit := uint64(1)
too_low := diyfp{f: low.f - unit, e: low.e}
too_high := diyfp{f: high.f + unit, e: high.e}
// too_low and too_high are guaranteed to lie outside the interval we want the
// generated number in.
unsafe_interval := too_high.minus(too_low)
// We now cut the input number into two parts: the integral digits and the
// fractionals. We will not write any decimal separator though, but adapt
// kappa instead.
// Reminder: we are currently computing the digits (stored inside the buffer)
// such that: too_low < buffer * 10^kappa < too_high
// We use too_high for the digit_generation and stop as soon as possible.
// If we stop early we effectively round down.
one := diyfp{f: 1 << -w.e, e: w.e}
// Division by one is a shift.
integrals := uint32(too_high.f >> -one.e)
// Modulo by one is an and.
fractionals := too_high.f & (one.f - 1)
divisor, divisor_exponent := biggestPowerTen(integrals, diyFpKSignificandSize-(-one.e))
kappa = divisor_exponent + 1
buf = buffer
for kappa > 0 {
digit := int(integrals / divisor)
buf = append(buf, byte('0'+digit))
integrals %= divisor
kappa--
// Note that kappa now equals the exponent of the divisor and that the
// invariant thus holds again.
rest := uint64(integrals)<<-one.e + fractionals
// Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e)
// Reminder: unsafe_interval.e == one.e
if rest < unsafe_interval.f {
// Rounding down (by not emitting the remaining digits) yields a number
// that lies within the unsafe interval.
res = roundWeed(buf, too_high.minus(w).f,
unsafe_interval.f, rest,
uint64(divisor)<<-one.e, unit)
return
}
divisor /= 10
}
// The integrals have been generated. We are at the point of the decimal
// separator. In the following loop we simply multiply the remaining digits by
// 10 and divide by one. We just need to pay attention to multiply associated
// data (like the interval or 'unit'), too.
// Note that the multiplication by 10 does not overflow, because w.e >= -60
// and thus one.e >= -60.
_DCHECK(one.e >= -60)
_DCHECK(fractionals < one.f)
_DCHECK(0xFFFFFFFFFFFFFFFF/10 >= one.f)
for {
fractionals *= 10
unit *= 10
unsafe_interval.f *= 10
// Integer division by one.
digit := byte(fractionals >> -one.e)
buf = append(buf, '0'+digit)
fractionals &= one.f - 1 // Modulo by one.
kappa--
if fractionals < unsafe_interval.f {
res = roundWeed(buf, too_high.minus(w).f*unit, unsafe_interval.f, fractionals, one.f, unit)
return
}
}
}
// Generates (at most) requested_digits of input number w.
// w is a floating-point number (DiyFp), consisting of a significand and an
// exponent. Its exponent is bounded by kMinimalTargetExponent and
// kMaximalTargetExponent.
//
// Hence -60 <= w.e() <= -32.
//
// Returns false if it fails, in which case the generated digits in the buffer
// should not be used.
// Preconditions:
// - w is correct up to 1 ulp (unit in the last place). That
// is, its error must be strictly less than a unit of its last digit.
// - kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
//
// Postconditions: returns false if procedure fails.
//
// otherwise:
// * buffer is not null-terminated, but length contains the number of
// digits.
// * the representation in buffer is the most precise representation of
// requested_digits digits.
// * buffer contains at most requested_digits digits of w. If there are less
// than requested_digits digits then some trailing '0's have been removed.
// * kappa is such that
// w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2.
//
// Remark: This procedure takes into account the imprecision of its input
//
// numbers. If the precision is not enough to guarantee all the postconditions
// then false is returned. This usually happens rarely, but the failure-rate
// increases with higher requested_digits.
func digitGenCounted(w diyfp, requested_digits int, buffer []byte) (kappa int, buf []byte, res bool) {
_DCHECK(kMinimalTargetExponent <= w.e && w.e <= kMaximalTargetExponent)
// w is assumed to have an error less than 1 unit. Whenever w is scaled we
// also scale its error.
w_error := uint64(1)
// We cut the input number into two parts: the integral digits and the
// fractional digits. We don't emit any decimal separator, but adapt kappa
// instead. Example: instead of writing "1.2" we put "12" into the buffer and
// increase kappa by 1.
one := diyfp{f: 1 << -w.e, e: w.e}
// Division by one is a shift.
integrals := uint32(w.f >> -one.e)
// Modulo by one is an and.
fractionals := w.f & (one.f - 1)
divisor, divisor_exponent := biggestPowerTen(integrals, diyFpKSignificandSize-(-one.e))
kappa = divisor_exponent + 1
buf = buffer
// Loop invariant: buffer = w / 10^kappa (integer division)
// The invariant holds for the first iteration: kappa has been initialized
// with the divisor exponent + 1. And the divisor is the biggest power of ten
// that is smaller than 'integrals'.
for kappa > 0 {
digit := byte(integrals / divisor)
buf = append(buf, '0'+digit)
requested_digits--
integrals %= divisor
kappa--
// Note that kappa now equals the exponent of the divisor and that the
// invariant thus holds again.
if requested_digits == 0 {
break
}
divisor /= 10
}
if requested_digits == 0 {
rest := uint64(integrals)<<-one.e + fractionals
res = roundWeedCounted(buf, rest, uint64(divisor)<<-one.e, w_error, &kappa)
return
}
// The integrals have been generated. We are at the point of the decimal
// separator. In the following loop we simply multiply the remaining digits by
// 10 and divide by one. We just need to pay attention to multiply associated
// data (the 'unit'), too.
// Note that the multiplication by 10 does not overflow, because w.e >= -60
// and thus one.e >= -60.
_DCHECK(one.e >= -60)
_DCHECK(fractionals < one.f)
_DCHECK(0xFFFFFFFFFFFFFFFF/10 >= one.f)
for requested_digits > 0 && fractionals > w_error {
fractionals *= 10
w_error *= 10
// Integer division by one.
digit := byte(fractionals >> -one.e)
buf = append(buf, '0'+digit)
requested_digits--
fractionals &= one.f - 1 // Modulo by one.
kappa--
}
if requested_digits != 0 {
res = false
} else {
res = roundWeedCounted(buf, fractionals, one.f, w_error, &kappa)
}
return
}
// Provides a decimal representation of v.
// Returns true if it succeeds, otherwise the result cannot be trusted.
// There will be *length digits inside the buffer (not null-terminated).
// If the function returns true then
//
// v == (double) (buffer * 10^decimal_exponent).
//
// The digits in the buffer are the shortest representation possible: no
// 0.09999999999999999 instead of 0.1. The shorter representation will even be
// chosen even if the longer one would be closer to v.
// The last digit will be closest to the actual v. That is, even if several
// digits might correctly yield 'v' when read again, the closest will be
// computed.
func grisu3(f float64, buffer []byte) (digits []byte, decimal_exponent int, result bool) {
v := double(f)
w := v.toNormalizedDiyfp()
// boundary_minus and boundary_plus are the boundaries between v and its
// closest floating-point neighbors. Any number strictly between
// boundary_minus and boundary_plus will round to v when convert to a double.
// Grisu3 will never output representations that lie exactly on a boundary.
boundary_minus, boundary_plus := v.normalizedBoundaries()
ten_mk_minimal_binary_exponent := kMinimalTargetExponent - (w.e + diyFpKSignificandSize)
ten_mk_maximal_binary_exponent := kMaximalTargetExponent - (w.e + diyFpKSignificandSize)
ten_mk, mk := getCachedPowerForBinaryExponentRange(ten_mk_minimal_binary_exponent, ten_mk_maximal_binary_exponent)
_DCHECK(
(kMinimalTargetExponent <=
w.e+ten_mk.e+diyFpKSignificandSize) &&
(kMaximalTargetExponent >= w.e+ten_mk.e+diyFpKSignificandSize))
// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
// 64 bit significand and ten_mk is thus only precise up to 64 bits.
// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
// off by a small amount.
// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
// (f-1) * 2^e < w*10^k < (f+1) * 2^e
scaled_w := w.times(ten_mk)
_DCHECK(scaled_w.e ==
boundary_plus.e+ten_mk.e+diyFpKSignificandSize)
// In theory it would be possible to avoid some recomputations by computing
// the difference between w and boundary_minus/plus (a power of 2) and to
// compute scaled_boundary_minus/plus by subtracting/adding from
// scaled_w. However the code becomes much less readable and the speed
// enhancements are not terrific.
scaled_boundary_minus := boundary_minus.times(ten_mk)
scaled_boundary_plus := boundary_plus.times(ten_mk)
// DigitGen will generate the digits of scaled_w. Therefore we have
// v == (double) (scaled_w * 10^-mk).
// Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
// integer than it will be updated. For instance if scaled_w == 1.23 then
// the buffer will be filled with "123" und the decimal_exponent will be
// decreased by 2.
var kappa int
kappa, digits, result = digitGen(scaled_boundary_minus, scaled_w, scaled_boundary_plus, buffer)
decimal_exponent = -mk + kappa
return
}
// The "counted" version of grisu3 (see above) only generates requested_digits
// number of digits. This version does not generate the shortest representation,
// and with enough requested digits 0.1 will at some point print as 0.9999999...
// Grisu3 is too imprecise for real halfway cases (1.5 will not work) and
// therefore the rounding strategy for halfway cases is irrelevant.
func grisu3Counted(v float64, requested_digits int, buffer []byte) (digits []byte, decimal_exponent int, result bool) {
w := double(v).toNormalizedDiyfp()
ten_mk_minimal_binary_exponent := kMinimalTargetExponent - (w.e + diyFpKSignificandSize)
ten_mk_maximal_binary_exponent := kMaximalTargetExponent - (w.e + diyFpKSignificandSize)
ten_mk, mk := getCachedPowerForBinaryExponentRange(ten_mk_minimal_binary_exponent, ten_mk_maximal_binary_exponent)
_DCHECK(
(kMinimalTargetExponent <=
w.e+ten_mk.e+diyFpKSignificandSize) &&
(kMaximalTargetExponent >= w.e+ten_mk.e+diyFpKSignificandSize))
// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
// 64 bit significand and ten_mk is thus only precise up to 64 bits.
// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
// off by a small amount.
// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
// (f-1) * 2^e < w*10^k < (f+1) * 2^e
scaled_w := w.times(ten_mk)
// We now have (double) (scaled_w * 10^-mk).
// DigitGen will generate the first requested_digits digits of scaled_w and
// return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
// will not always be exactly the same since DigitGenCounted only produces a
// limited number of digits.)
var kappa int
kappa, digits, result = digitGenCounted(scaled_w, requested_digits, buffer)
decimal_exponent = -mk + kappa
return
}
// v must be > 0 and must not be Inf or NaN
func Dtoa(v float64, mode Mode, requested_digits int, buffer []byte) (digits []byte, decimal_point int, result bool) {
defer func() {
if x := recover(); x != nil {
if x == dcheckFailure {
panic(fmt.Errorf("DCHECK assertion failed while formatting %s in mode %d", strconv.FormatFloat(v, 'e', 50, 64), mode))
}
panic(x)
}
}()
var decimal_exponent int
startPos := len(buffer)
switch mode {
case ModeShortest:
digits, decimal_exponent, result = grisu3(v, buffer)
case ModePrecision:
digits, decimal_exponent, result = grisu3Counted(v, requested_digits, buffer)
}
if result {
decimal_point = len(digits) - startPos + decimal_exponent
} else {
digits = digits[:startPos]
}
return
}
+1113
View File
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
package goja
// inspired by https://gist.github.com/orlp/3551590
var overflows = [64]int64{
9223372036854775807, 9223372036854775807, 3037000499, 2097151,
55108, 6208, 1448, 511,
234, 127, 78, 52,
38, 28, 22, 18,
15, 13, 11, 9,
8, 7, 7, 6,
6, 5, 5, 5,
4, 4, 4, 4,
3, 3, 3, 3,
3, 3, 3, 3,
2, 2, 2, 2,
2, 2, 2, 2,
2, 2, 2, 2,
2, 2, 2, 2,
2, 2, 2, 2,
2, 2, 2, 2,
}
var highestBitSet = [63]byte{
0, 1, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6,
}
func ipow(base, exp int64) (result int64) {
if exp >= 63 {
if base == 1 {
return 1
}
if base == -1 {
return 1 - 2*(exp&1)
}
return 0
}
if base > overflows[exp] || -base > overflows[exp] {
return 0
}
result = 1
switch highestBitSet[byte(exp)] {
case 6:
if exp&1 != 0 {
result *= base
}
exp >>= 1
base *= base
fallthrough
case 5:
if exp&1 != 0 {
result *= base
}
exp >>= 1
base *= base
fallthrough
case 4:
if exp&1 != 0 {
result *= base
}
exp >>= 1
base *= base
fallthrough
case 3:
if exp&1 != 0 {
result *= base
}
exp >>= 1
base *= base
fallthrough
case 2:
if exp&1 != 0 {
result *= base
}
exp >>= 1
base *= base
fallthrough
case 1:
if exp&1 != 0 {
result *= base
}
fallthrough
default:
return result
}
}
+169
View File
@@ -0,0 +1,169 @@
package goja
import (
"hash/maphash"
)
type mapEntry struct {
key, value Value
iterPrev, iterNext *mapEntry
hNext *mapEntry
}
type orderedMap struct {
hash *maphash.Hash
hashTable map[uint64]*mapEntry
iterFirst, iterLast *mapEntry
size int
}
type orderedMapIter struct {
m *orderedMap
cur *mapEntry
}
func (m *orderedMap) lookup(key Value) (h uint64, entry, hPrev *mapEntry) {
if key == _negativeZero {
key = intToValue(0)
}
h = key.hash(m.hash)
for entry = m.hashTable[h]; entry != nil && !entry.key.SameAs(key); hPrev, entry = entry, entry.hNext {
}
return
}
func (m *orderedMap) set(key, value Value) {
h, entry, hPrev := m.lookup(key)
if entry != nil {
entry.value = value
} else {
if key == _negativeZero {
key = intToValue(0)
}
entry = &mapEntry{key: key, value: value}
if hPrev == nil {
m.hashTable[h] = entry
} else {
hPrev.hNext = entry
}
if m.iterLast != nil {
entry.iterPrev = m.iterLast
m.iterLast.iterNext = entry
} else {
m.iterFirst = entry
}
m.iterLast = entry
m.size++
}
}
func (m *orderedMap) get(key Value) Value {
_, entry, _ := m.lookup(key)
if entry != nil {
return entry.value
}
return nil
}
func (m *orderedMap) remove(key Value) bool {
h, entry, hPrev := m.lookup(key)
if entry != nil {
entry.key = nil
entry.value = nil
// remove from the doubly-linked list
if entry.iterPrev != nil {
entry.iterPrev.iterNext = entry.iterNext
} else {
m.iterFirst = entry.iterNext
}
if entry.iterNext != nil {
entry.iterNext.iterPrev = entry.iterPrev
} else {
m.iterLast = entry.iterPrev
}
// remove from the hashTable
if hPrev == nil {
if entry.hNext == nil {
delete(m.hashTable, h)
} else {
m.hashTable[h] = entry.hNext
}
} else {
hPrev.hNext = entry.hNext
}
m.size--
return true
}
return false
}
func (m *orderedMap) has(key Value) bool {
_, entry, _ := m.lookup(key)
return entry != nil
}
func (iter *orderedMapIter) next() *mapEntry {
if iter.m == nil {
// closed iterator
return nil
}
cur := iter.cur
// if the current item was deleted, track back to find the latest that wasn't
for cur != nil && cur.key == nil {
cur = cur.iterPrev
}
if cur != nil {
cur = cur.iterNext
} else {
cur = iter.m.iterFirst
}
if cur == nil {
iter.close()
} else {
iter.cur = cur
}
return cur
}
func (iter *orderedMapIter) close() {
iter.m = nil
iter.cur = nil
}
func newOrderedMap(h *maphash.Hash) *orderedMap {
return &orderedMap{
hash: h,
hashTable: make(map[uint64]*mapEntry),
}
}
func (m *orderedMap) newIter() *orderedMapIter {
iter := &orderedMapIter{
m: m,
}
return iter
}
func (m *orderedMap) clear() {
for item := m.iterFirst; item != nil; item = item.iterNext {
item.key = nil
item.value = nil
if item.iterPrev != nil {
item.iterPrev.iterNext = nil
}
}
m.iterFirst = nil
m.iterLast = nil
m.hashTable = make(map[uint64]*mapEntry)
m.size = 0
}
+1864
View File
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
package goja
import "github.com/dop251/goja/unistring"
type argumentsObject struct {
baseObject
length int
}
type mappedProperty struct {
valueProperty
v *Value
}
func (a *argumentsObject) getStr(name unistring.String, receiver Value) Value {
return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver)
}
func (a *argumentsObject) getOwnPropStr(name unistring.String) Value {
if mapped, ok := a.values[name].(*mappedProperty); ok {
if mapped.writable && mapped.enumerable && mapped.configurable {
return *mapped.v
}
return &valueProperty{
value: *mapped.v,
writable: mapped.writable,
configurable: mapped.configurable,
enumerable: mapped.enumerable,
}
}
return a.baseObject.getOwnPropStr(name)
}
func (a *argumentsObject) init() {
a.baseObject.init()
a._putProp("length", intToValue(int64(a.length)), true, false, true)
}
func (a *argumentsObject) setOwnStr(name unistring.String, val Value, throw bool) bool {
if prop, ok := a.values[name].(*mappedProperty); ok {
if !prop.writable {
a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name)
return false
}
*prop.v = val
return true
}
return a.baseObject.setOwnStr(name, val, throw)
}
func (a *argumentsObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw)
}
func (a *argumentsObject) deleteStr(name unistring.String, throw bool) bool {
if prop, ok := a.values[name].(*mappedProperty); ok {
if !a.checkDeleteProp(name, &prop.valueProperty, throw) {
return false
}
a._delete(name)
return true
}
return a.baseObject.deleteStr(name, throw)
}
type argumentsPropIter struct {
wrapped iterNextFunc
}
func (i *argumentsPropIter) next() (propIterItem, iterNextFunc) {
var item propIterItem
item, i.wrapped = i.wrapped()
if i.wrapped == nil {
return propIterItem{}, nil
}
if prop, ok := item.value.(*mappedProperty); ok {
item.value = *prop.v
}
return item, i.next
}
func (a *argumentsObject) iterateStringKeys() iterNextFunc {
return (&argumentsPropIter{
wrapped: a.baseObject.iterateStringKeys(),
}).next
}
func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if mapped, ok := a.values[name].(*mappedProperty); ok {
existing := &valueProperty{
configurable: mapped.configurable,
writable: true,
enumerable: mapped.enumerable,
value: *mapped.v,
}
val, ok := a.baseObject._defineOwnProperty(name, existing, descr, throw)
if !ok {
return false
}
if prop, ok := val.(*valueProperty); ok {
if !prop.accessor {
*mapped.v = prop.value
}
if prop.accessor || !prop.writable {
a._put(name, prop)
return true
}
mapped.configurable = prop.configurable
mapped.enumerable = prop.enumerable
} else {
*mapped.v = val
mapped.configurable = true
mapped.enumerable = true
}
return true
}
return a.baseObject.defineOwnPropertyStr(name, descr, throw)
}
func (a *argumentsObject) export(ctx *objectExportCtx) interface{} {
if v, exists := ctx.get(a.val); exists {
return v
}
arr := make([]interface{}, a.length)
ctx.put(a.val, arr)
for i := range arr {
v := a.getIdx(valueInt(int64(i)), nil)
if v != nil {
arr[i] = exportValue(v, ctx)
}
}
return arr
}
+794
View File
@@ -0,0 +1,794 @@
package goja
import (
"fmt"
"reflect"
"strconv"
"github.com/dop251/goja/unistring"
)
/*
DynamicObject is an interface representing a handler for a dynamic Object. Such an object can be created
using the Runtime.NewDynamicObject() method.
Note that Runtime.ToValue() does not have any special treatment for DynamicObject. The only way to create
a dynamic object is by using the Runtime.NewDynamicObject() method. This is done deliberately to avoid
silent code breaks when this interface changes.
*/
type DynamicObject interface {
// Get a property value for the key. May return nil if the property does not exist.
Get(key string) Value
// Set a property value for the key. Return true if success, false otherwise.
Set(key string, val Value) bool
// Has should return true if and only if the property exists.
Has(key string) bool
// Delete the property for the key. Returns true on success (note, that includes missing property).
Delete(key string) bool
// Keys returns a list of all existing property keys. There are no checks for duplicates or to make sure
// that the order conforms to https://262.ecma-international.org/#sec-ordinaryownpropertykeys
Keys() []string
}
/*
DynamicArray is an interface representing a handler for a dynamic array Object. Such an object can be created
using the Runtime.NewDynamicArray() method.
Any integer property key or a string property key that can be parsed into an int value (including negative
ones) is treated as an index and passed to the trap methods of the DynamicArray. Note this is different from
the regular ECMAScript arrays which only support positive indexes up to 2^32-1.
DynamicArray cannot be sparse, i.e. hasOwnProperty(num) will return true for num >= 0 && num < Len(). Deleting
such a property is equivalent to setting it to undefined. Note that this creates a slight peculiarity because
hasOwnProperty() will still return true, even after deletion.
Note that Runtime.ToValue() does not have any special treatment for DynamicArray. The only way to create
a dynamic array is by using the Runtime.NewDynamicArray() method. This is done deliberately to avoid
silent code breaks when this interface changes.
*/
type DynamicArray interface {
// Len returns the current array length.
Len() int
// Get an item at index idx. Note that idx may be any integer, negative or beyond the current length.
Get(idx int) Value
// Set an item at index idx. Note that idx may be any integer, negative or beyond the current length.
// The expected behaviour when it's beyond length is that the array's length is increased to accommodate
// the item. All elements in the 'new' section of the array should be zeroed.
Set(idx int, val Value) bool
// SetLen is called when the array's 'length' property is changed. If the length is increased all elements in the
// 'new' section of the array should be zeroed.
SetLen(int) bool
}
type baseDynamicObject struct {
val *Object
prototype *Object
}
type dynamicObject struct {
baseDynamicObject
d DynamicObject
}
type dynamicArray struct {
baseDynamicObject
a DynamicArray
}
/*
NewDynamicObject creates an Object backed by the provided DynamicObject handler.
All properties of this Object are Writable, Enumerable and Configurable data properties. Any attempt to define
a property that does not conform to this will fail.
The Object is always extensible and cannot be made non-extensible. Object.preventExtensions() will fail.
The Object's prototype is initially set to Object.prototype, but can be changed using regular mechanisms
(Object.SetPrototype() in Go or Object.setPrototypeOf() in JS).
The Object cannot have own Symbol properties, however its prototype can. If you need an iterator support for
example, you could create a regular object, set Symbol.iterator on that object and then use it as a
prototype. See TestDynamicObjectCustomProto for more details.
Export() returns the original DynamicObject.
This mechanism is similar to ECMAScript Proxy, however because all properties are enumerable and the object
is always extensible there is no need for invariant checks which removes the need to have a target object and
makes it a lot more efficient.
*/
func (r *Runtime) NewDynamicObject(d DynamicObject) *Object {
v := &Object{runtime: r}
o := &dynamicObject{
d: d,
baseDynamicObject: baseDynamicObject{
val: v,
prototype: r.global.ObjectPrototype,
},
}
v.self = o
return v
}
/*
NewSharedDynamicObject is similar to Runtime.NewDynamicObject but the resulting Object can be shared across multiple
Runtimes. The Object's prototype will be null. The provided DynamicObject must be goroutine-safe.
*/
func NewSharedDynamicObject(d DynamicObject) *Object {
v := &Object{}
o := &dynamicObject{
d: d,
baseDynamicObject: baseDynamicObject{
val: v,
},
}
v.self = o
return v
}
/*
NewDynamicArray creates an array Object backed by the provided DynamicArray handler.
It is similar to NewDynamicObject, the differences are:
- the Object is an array (i.e. Array.isArray() will return true and it will have the length property).
- the prototype will be initially set to Array.prototype.
- the Object cannot have any own string properties except for the 'length'.
*/
func (r *Runtime) NewDynamicArray(a DynamicArray) *Object {
v := &Object{runtime: r}
o := &dynamicArray{
a: a,
baseDynamicObject: baseDynamicObject{
val: v,
prototype: r.getArrayPrototype(),
},
}
v.self = o
return v
}
/*
NewSharedDynamicArray is similar to Runtime.NewDynamicArray but the resulting Object can be shared across multiple
Runtimes. The Object's prototype will be null. If you need to run Array's methods on it, use Array.prototype.[...].call(a, ...).
The provided DynamicArray must be goroutine-safe.
*/
func NewSharedDynamicArray(a DynamicArray) *Object {
v := &Object{}
o := &dynamicArray{
a: a,
baseDynamicObject: baseDynamicObject{
val: v,
},
}
v.self = o
return v
}
func (*dynamicObject) sortLen() int {
return 0
}
func (*dynamicObject) sortGet(i int) Value {
return nil
}
func (*dynamicObject) swap(i int, i2 int) {
}
func (*dynamicObject) className() string {
return classObject
}
func (o *baseDynamicObject) getParentStr(p unistring.String, receiver Value) Value {
if proto := o.prototype; proto != nil {
if receiver == nil {
return proto.self.getStr(p, o.val)
}
return proto.self.getStr(p, receiver)
}
return nil
}
func (o *dynamicObject) getStr(p unistring.String, receiver Value) Value {
prop := o.d.Get(p.String())
if prop == nil {
return o.getParentStr(p, receiver)
}
return prop
}
func (o *baseDynamicObject) getParentIdx(p valueInt, receiver Value) Value {
if proto := o.prototype; proto != nil {
if receiver == nil {
return proto.self.getIdx(p, o.val)
}
return proto.self.getIdx(p, receiver)
}
return nil
}
func (o *dynamicObject) getIdx(p valueInt, receiver Value) Value {
prop := o.d.Get(p.String())
if prop == nil {
return o.getParentIdx(p, receiver)
}
return prop
}
func (o *baseDynamicObject) getSym(p *Symbol, receiver Value) Value {
if proto := o.prototype; proto != nil {
if receiver == nil {
return proto.self.getSym(p, o.val)
}
return proto.self.getSym(p, receiver)
}
return nil
}
func (o *dynamicObject) getOwnPropStr(u unistring.String) Value {
return o.d.Get(u.String())
}
func (o *dynamicObject) getOwnPropIdx(v valueInt) Value {
return o.d.Get(v.String())
}
func (*baseDynamicObject) getOwnPropSym(*Symbol) Value {
return nil
}
func (o *dynamicObject) _set(prop string, v Value, throw bool) bool {
if o.d.Set(prop, v) {
return true
}
typeErrorResult(throw, "'Set' on a dynamic object returned false")
return false
}
func (o *baseDynamicObject) _setSym(throw bool) {
typeErrorResult(throw, "Dynamic objects do not support Symbol properties")
}
func (o *dynamicObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
prop := p.String()
if !o.d.Has(prop) {
if proto := o.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, handled := proto.self.setForeignStr(p, v, o.val, throw); handled {
return res
}
}
}
return o._set(prop, v, throw)
}
func (o *dynamicObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
prop := p.String()
if !o.d.Has(prop) {
if proto := o.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, handled := proto.self.setForeignIdx(p, v, o.val, throw); handled {
return res
}
}
}
return o._set(prop, v, throw)
}
func (o *baseDynamicObject) setOwnSym(s *Symbol, v Value, throw bool) bool {
if proto := o.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, handled := proto.self.setForeignSym(s, v, o.val, throw); handled {
return res
}
}
o._setSym(throw)
return false
}
func (o *baseDynamicObject) setParentForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
if proto := o.prototype; proto != nil {
if receiver != proto {
return proto.self.setForeignStr(p, v, receiver, throw)
}
return proto.self.setOwnStr(p, v, throw), true
}
return false, false
}
func (o *dynamicObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
prop := p.String()
if !o.d.Has(prop) {
return o.setParentForeignStr(p, v, receiver, throw)
}
return false, false
}
func (o *baseDynamicObject) setParentForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
if proto := o.prototype; proto != nil {
if receiver != proto {
return proto.self.setForeignIdx(p, v, receiver, throw)
}
return proto.self.setOwnIdx(p, v, throw), true
}
return false, false
}
func (o *dynamicObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
prop := p.String()
if !o.d.Has(prop) {
return o.setParentForeignIdx(p, v, receiver, throw)
}
return false, false
}
func (o *baseDynamicObject) setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) {
if proto := o.prototype; proto != nil {
if receiver != proto {
return proto.self.setForeignSym(p, v, receiver, throw)
}
return proto.self.setOwnSym(p, v, throw), true
}
return false, false
}
func (o *dynamicObject) hasPropertyStr(u unistring.String) bool {
if o.hasOwnPropertyStr(u) {
return true
}
if proto := o.prototype; proto != nil {
return proto.self.hasPropertyStr(u)
}
return false
}
func (o *dynamicObject) hasPropertyIdx(idx valueInt) bool {
if o.hasOwnPropertyIdx(idx) {
return true
}
if proto := o.prototype; proto != nil {
return proto.self.hasPropertyIdx(idx)
}
return false
}
func (o *baseDynamicObject) hasPropertySym(s *Symbol) bool {
if proto := o.prototype; proto != nil {
return proto.self.hasPropertySym(s)
}
return false
}
func (o *dynamicObject) hasOwnPropertyStr(u unistring.String) bool {
return o.d.Has(u.String())
}
func (o *dynamicObject) hasOwnPropertyIdx(v valueInt) bool {
return o.d.Has(v.String())
}
func (*baseDynamicObject) hasOwnPropertySym(_ *Symbol) bool {
return false
}
func (o *baseDynamicObject) checkDynamicObjectPropertyDescr(name fmt.Stringer, descr PropertyDescriptor, throw bool) bool {
if descr.Getter != nil || descr.Setter != nil {
typeErrorResult(throw, "Dynamic objects do not support accessor properties")
return false
}
if descr.Writable == FLAG_FALSE {
typeErrorResult(throw, "Dynamic object field %q cannot be made read-only", name.String())
return false
}
if descr.Enumerable == FLAG_FALSE {
typeErrorResult(throw, "Dynamic object field %q cannot be made non-enumerable", name.String())
return false
}
if descr.Configurable == FLAG_FALSE {
typeErrorResult(throw, "Dynamic object field %q cannot be made non-configurable", name.String())
return false
}
return true
}
func (o *dynamicObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
if o.checkDynamicObjectPropertyDescr(name, desc, throw) {
return o._set(name.String(), desc.Value, throw)
}
return false
}
func (o *dynamicObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
if o.checkDynamicObjectPropertyDescr(name, desc, throw) {
return o._set(name.String(), desc.Value, throw)
}
return false
}
func (o *baseDynamicObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool {
o._setSym(throw)
return false
}
func (o *dynamicObject) _delete(prop string, throw bool) bool {
if o.d.Delete(prop) {
return true
}
typeErrorResult(throw, "Could not delete property %q of a dynamic object", prop)
return false
}
func (o *dynamicObject) deleteStr(name unistring.String, throw bool) bool {
return o._delete(name.String(), throw)
}
func (o *dynamicObject) deleteIdx(idx valueInt, throw bool) bool {
return o._delete(idx.String(), throw)
}
func (*baseDynamicObject) deleteSym(_ *Symbol, _ bool) bool {
return true
}
func (o *baseDynamicObject) assertCallable() (call func(FunctionCall) Value, ok bool) {
return nil, false
}
func (o *baseDynamicObject) vmCall(vm *vm, n int) {
panic(vm.r.NewTypeError("Dynamic object is not callable"))
}
func (*baseDynamicObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
return nil
}
func (o *baseDynamicObject) proto() *Object {
return o.prototype
}
func (o *baseDynamicObject) setProto(proto *Object, throw bool) bool {
o.prototype = proto
return true
}
func (o *baseDynamicObject) hasInstance(v Value) bool {
panic(newTypeError("Expecting a function in instanceof check, but got a dynamic object"))
}
func (*baseDynamicObject) isExtensible() bool {
return true
}
func (o *baseDynamicObject) preventExtensions(throw bool) bool {
typeErrorResult(throw, "Cannot make a dynamic object non-extensible")
return false
}
type dynamicObjectPropIter struct {
o *dynamicObject
propNames []string
idx int
}
func (i *dynamicObjectPropIter) next() (propIterItem, iterNextFunc) {
for i.idx < len(i.propNames) {
name := i.propNames[i.idx]
i.idx++
if i.o.d.Has(name) {
return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next
}
}
return propIterItem{}, nil
}
func (o *dynamicObject) iterateStringKeys() iterNextFunc {
keys := o.d.Keys()
return (&dynamicObjectPropIter{
o: o,
propNames: keys,
}).next
}
func (o *baseDynamicObject) iterateSymbols() iterNextFunc {
return func() (propIterItem, iterNextFunc) {
return propIterItem{}, nil
}
}
func (o *dynamicObject) iterateKeys() iterNextFunc {
return o.iterateStringKeys()
}
func (o *dynamicObject) export(ctx *objectExportCtx) interface{} {
return o.d
}
func (o *dynamicObject) exportType() reflect.Type {
return reflect.TypeOf(o.d)
}
func (o *baseDynamicObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
return genericExportToMap(o.val, dst, typ, ctx)
}
func (o *baseDynamicObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error {
return genericExportToArrayOrSlice(o.val, dst, typ, ctx)
}
func (o *dynamicObject) equal(impl objectImpl) bool {
if other, ok := impl.(*dynamicObject); ok {
return o.d == other.d
}
return false
}
func (o *dynamicObject) stringKeys(all bool, accum []Value) []Value {
keys := o.d.Keys()
if l := len(accum) + len(keys); l > cap(accum) {
oldAccum := accum
accum = make([]Value, len(accum), l)
copy(accum, oldAccum)
}
for _, key := range keys {
accum = append(accum, newStringValue(key))
}
return accum
}
func (*baseDynamicObject) symbols(all bool, accum []Value) []Value {
return accum
}
func (o *dynamicObject) keys(all bool, accum []Value) []Value {
return o.stringKeys(all, accum)
}
func (*baseDynamicObject) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
return nil
}
func (*baseDynamicObject) _putSym(s *Symbol, prop Value) {
}
func (o *baseDynamicObject) getPrivateEnv(*privateEnvType, bool) *privateElements {
panic(newTypeError("Dynamic objects cannot have private elements"))
}
func (o *baseDynamicObject) typeOf() String {
return stringObjectC
}
func (a *dynamicArray) sortLen() int {
return a.a.Len()
}
func (a *dynamicArray) sortGet(i int) Value {
return a.a.Get(i)
}
func (a *dynamicArray) swap(i int, j int) {
x := a.sortGet(i)
y := a.sortGet(j)
a.a.Set(int(i), y)
a.a.Set(int(j), x)
}
func (a *dynamicArray) className() string {
return classArray
}
func (a *dynamicArray) getStr(p unistring.String, receiver Value) Value {
if p == "length" {
return intToValue(int64(a.a.Len()))
}
if idx, ok := strToInt(p); ok {
return a.a.Get(idx)
}
return a.getParentStr(p, receiver)
}
func (a *dynamicArray) getIdx(p valueInt, receiver Value) Value {
if val := a.getOwnPropIdx(p); val != nil {
return val
}
return a.getParentIdx(p, receiver)
}
func (a *dynamicArray) getOwnPropStr(u unistring.String) Value {
if u == "length" {
return &valueProperty{
value: intToValue(int64(a.a.Len())),
writable: true,
}
}
if idx, ok := strToInt(u); ok {
return a.a.Get(idx)
}
return nil
}
func (a *dynamicArray) getOwnPropIdx(v valueInt) Value {
return a.a.Get(toIntStrict(int64(v)))
}
func (a *dynamicArray) _setLen(v Value, throw bool) bool {
if a.a.SetLen(toIntStrict(v.ToInteger())) {
return true
}
typeErrorResult(throw, "'SetLen' on a dynamic array returned false")
return false
}
func (a *dynamicArray) setOwnStr(p unistring.String, v Value, throw bool) bool {
if p == "length" {
return a._setLen(v, throw)
}
if idx, ok := strToInt(p); ok {
return a._setIdx(idx, v, throw)
}
typeErrorResult(throw, "Cannot set property %q on a dynamic array", p.String())
return false
}
func (a *dynamicArray) _setIdx(idx int, v Value, throw bool) bool {
if a.a.Set(idx, v) {
return true
}
typeErrorResult(throw, "'Set' on a dynamic array returned false")
return false
}
func (a *dynamicArray) setOwnIdx(p valueInt, v Value, throw bool) bool {
return a._setIdx(toIntStrict(int64(p)), v, throw)
}
func (a *dynamicArray) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
return a.setParentForeignStr(p, v, receiver, throw)
}
func (a *dynamicArray) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
return a.setParentForeignIdx(p, v, receiver, throw)
}
func (a *dynamicArray) hasPropertyStr(u unistring.String) bool {
if a.hasOwnPropertyStr(u) {
return true
}
if proto := a.prototype; proto != nil {
return proto.self.hasPropertyStr(u)
}
return false
}
func (a *dynamicArray) hasPropertyIdx(idx valueInt) bool {
if a.hasOwnPropertyIdx(idx) {
return true
}
if proto := a.prototype; proto != nil {
return proto.self.hasPropertyIdx(idx)
}
return false
}
func (a *dynamicArray) _has(idx int) bool {
return idx >= 0 && idx < a.a.Len()
}
func (a *dynamicArray) hasOwnPropertyStr(u unistring.String) bool {
if u == "length" {
return true
}
if idx, ok := strToInt(u); ok {
return a._has(idx)
}
return false
}
func (a *dynamicArray) hasOwnPropertyIdx(v valueInt) bool {
return a._has(toIntStrict(int64(v)))
}
func (a *dynamicArray) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
if a.checkDynamicObjectPropertyDescr(name, desc, throw) {
if idx, ok := strToInt(name); ok {
return a._setIdx(idx, desc.Value, throw)
}
typeErrorResult(throw, "Cannot define property %q on a dynamic array", name.String())
}
return false
}
func (a *dynamicArray) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
if a.checkDynamicObjectPropertyDescr(name, desc, throw) {
return a._setIdx(toIntStrict(int64(name)), desc.Value, throw)
}
return false
}
func (a *dynamicArray) _delete(idx int, throw bool) bool {
if a._has(idx) {
a._setIdx(idx, _undefined, throw)
}
return true
}
func (a *dynamicArray) deleteStr(name unistring.String, throw bool) bool {
if idx, ok := strToInt(name); ok {
return a._delete(idx, throw)
}
if a.hasOwnPropertyStr(name) {
typeErrorResult(throw, "Cannot delete property %q on a dynamic array", name.String())
return false
}
return true
}
func (a *dynamicArray) deleteIdx(idx valueInt, throw bool) bool {
return a._delete(toIntStrict(int64(idx)), throw)
}
type dynArrayPropIter struct {
a DynamicArray
idx, limit int
}
func (i *dynArrayPropIter) next() (propIterItem, iterNextFunc) {
if i.idx < i.limit && i.idx < i.a.Len() {
name := strconv.Itoa(i.idx)
i.idx++
return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
}
return propIterItem{}, nil
}
func (a *dynamicArray) iterateStringKeys() iterNextFunc {
return (&dynArrayPropIter{
a: a.a,
limit: a.a.Len(),
}).next
}
func (a *dynamicArray) iterateKeys() iterNextFunc {
return a.iterateStringKeys()
}
func (a *dynamicArray) export(ctx *objectExportCtx) interface{} {
return a.a
}
func (a *dynamicArray) exportType() reflect.Type {
return reflect.TypeOf(a.a)
}
func (a *dynamicArray) equal(impl objectImpl) bool {
if other, ok := impl.(*dynamicArray); ok {
return a == other
}
return false
}
func (a *dynamicArray) stringKeys(all bool, accum []Value) []Value {
al := a.a.Len()
l := len(accum) + al
if all {
l++
}
if l > cap(accum) {
oldAccum := accum
accum = make([]Value, len(oldAccum), l)
copy(accum, oldAccum)
}
for i := 0; i < al; i++ {
accum = append(accum, asciiString(strconv.Itoa(i)))
}
if all {
accum = append(accum, asciiString("length"))
}
return accum
}
func (a *dynamicArray) keys(all bool, accum []Value) []Value {
return a.stringKeys(all, accum)
}
+358
View File
@@ -0,0 +1,358 @@
package goja
import (
"reflect"
"strconv"
"github.com/dop251/goja/unistring"
)
type objectGoArrayReflect struct {
objectGoReflect
lengthProp valueProperty
valueCache valueArrayCache
putIdx func(idx int, v Value, throw bool) bool
}
type valueArrayCache []reflectValueWrapper
func (c *valueArrayCache) get(idx int) reflectValueWrapper {
if idx < len(*c) {
return (*c)[idx]
}
return nil
}
func (c *valueArrayCache) grow(newlen int) {
oldcap := cap(*c)
if oldcap < newlen {
a := make([]reflectValueWrapper, newlen, growCap(newlen, len(*c), oldcap))
copy(a, *c)
*c = a
} else {
*c = (*c)[:newlen]
}
}
func (c *valueArrayCache) put(idx int, w reflectValueWrapper) {
if len(*c) <= idx {
c.grow(idx + 1)
}
(*c)[idx] = w
}
func (c *valueArrayCache) shrink(newlen int) {
if len(*c) > newlen {
tail := (*c)[newlen:]
for i, item := range tail {
if item != nil {
copyReflectValueWrapper(item)
tail[i] = nil
}
}
*c = (*c)[:newlen]
}
}
func (o *objectGoArrayReflect) _init() {
o.objectGoReflect.init()
o.class = classArray
o.prototype = o.val.runtime.getArrayPrototype()
o.baseObject._put("length", &o.lengthProp)
}
func (o *objectGoArrayReflect) init() {
o._init()
o.updateLen()
o.putIdx = o._putIdx
}
func (o *objectGoArrayReflect) updateLen() {
o.lengthProp.value = intToValue(int64(o.fieldsValue.Len()))
}
func (o *objectGoArrayReflect) _hasIdx(idx valueInt) bool {
if idx := int64(idx); idx >= 0 && idx < int64(o.fieldsValue.Len()) {
return true
}
return false
}
func (o *objectGoArrayReflect) _hasStr(name unistring.String) bool {
if idx := strToIdx64(name); idx >= 0 && idx < int64(o.fieldsValue.Len()) {
return true
}
return false
}
func (o *objectGoArrayReflect) _getIdx(idx int) Value {
if v := o.valueCache.get(idx); v != nil {
return v.esValue()
}
v := o.fieldsValue.Index(idx)
res, w := o.elemToValue(v)
if w != nil {
o.valueCache.put(idx, w)
}
return res
}
func (o *objectGoArrayReflect) getIdx(idx valueInt, receiver Value) Value {
if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.fieldsValue.Len() {
return o._getIdx(idx)
}
return o.objectGoReflect.getStr(idx.string(), receiver)
}
func (o *objectGoArrayReflect) getStr(name unistring.String, receiver Value) Value {
var ownProp Value
if idx := strToGoIdx(name); idx >= 0 && idx < o.fieldsValue.Len() {
ownProp = o._getIdx(idx)
} else if name == "length" {
if o.fieldsValue.Kind() == reflect.Slice {
o.updateLen()
}
ownProp = &o.lengthProp
} else {
ownProp = o.objectGoReflect.getOwnPropStr(name)
}
return o.getStrWithOwnProp(ownProp, name, receiver)
}
func (o *objectGoArrayReflect) getOwnPropStr(name unistring.String) Value {
if idx := strToGoIdx(name); idx >= 0 {
if idx < o.fieldsValue.Len() {
return &valueProperty{
value: o._getIdx(idx),
writable: true,
enumerable: true,
}
}
return nil
}
if name == "length" {
if o.fieldsValue.Kind() == reflect.Slice {
o.updateLen()
}
return &o.lengthProp
}
return o.objectGoReflect.getOwnPropStr(name)
}
func (o *objectGoArrayReflect) getOwnPropIdx(idx valueInt) Value {
if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.fieldsValue.Len() {
return &valueProperty{
value: o._getIdx(idx),
writable: true,
enumerable: true,
}
}
return nil
}
func (o *objectGoArrayReflect) _putIdx(idx int, v Value, throw bool) bool {
cached := o.valueCache.get(idx)
if cached != nil {
copyReflectValueWrapper(cached)
}
rv := o.fieldsValue.Index(idx)
err := o.val.runtime.toReflectValue(v, rv, &objectExportCtx{})
if err != nil {
if cached != nil {
cached.setReflectValue(rv)
}
o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err)
return false
}
if cached != nil {
o.valueCache[idx] = nil
}
return true
}
func (o *objectGoArrayReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
if i := toIntStrict(int64(idx)); i >= 0 {
if i >= o.fieldsValue.Len() {
if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
return res
}
}
return o.putIdx(i, val, throw)
} else {
name := idx.string()
if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
return false
} else {
return res
}
}
}
func (o *objectGoArrayReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
if idx := strToGoIdx(name); idx >= 0 {
if idx >= o.fieldsValue.Len() {
if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
return res
}
}
return o.putIdx(idx, val, throw)
} else {
if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
return false
} else {
return res
}
}
}
func (o *objectGoArrayReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw)
}
func (o *objectGoArrayReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
}
func (o *objectGoArrayReflect) hasOwnPropertyIdx(idx valueInt) bool {
return o._hasIdx(idx)
}
func (o *objectGoArrayReflect) hasOwnPropertyStr(name unistring.String) bool {
if o._hasStr(name) || name == "length" {
return true
}
return o.objectGoReflect.hasOwnPropertyStr(name)
}
func (o *objectGoArrayReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
if i := toIntStrict(int64(idx)); i >= 0 {
if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
return false
}
val := descr.Value
if val == nil {
val = _undefined
}
return o.putIdx(i, val, throw)
}
o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
return false
}
func (o *objectGoArrayReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if idx := strToGoIdx(name); idx >= 0 {
if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
return false
}
val := descr.Value
if val == nil {
val = _undefined
}
return o.putIdx(idx, val, throw)
}
o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
return false
}
func (o *objectGoArrayReflect) _deleteIdx(idx int) {
if idx < o.fieldsValue.Len() {
if cv := o.valueCache.get(idx); cv != nil {
copyReflectValueWrapper(cv)
o.valueCache[idx] = nil
}
o.fieldsValue.Index(idx).Set(reflect.Zero(o.fieldsValue.Type().Elem()))
}
}
func (o *objectGoArrayReflect) deleteStr(name unistring.String, throw bool) bool {
if idx := strToGoIdx(name); idx >= 0 {
o._deleteIdx(idx)
return true
}
return o.objectGoReflect.deleteStr(name, throw)
}
func (o *objectGoArrayReflect) deleteIdx(i valueInt, throw bool) bool {
idx := toIntStrict(int64(i))
if idx >= 0 {
o._deleteIdx(idx)
}
return true
}
type goArrayReflectPropIter struct {
o *objectGoArrayReflect
idx, limit int
}
func (i *goArrayReflectPropIter) next() (propIterItem, iterNextFunc) {
if i.idx < i.limit && i.idx < i.o.fieldsValue.Len() {
name := strconv.Itoa(i.idx)
i.idx++
return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next
}
return i.o.objectGoReflect.iterateStringKeys()()
}
func (o *objectGoArrayReflect) stringKeys(all bool, accum []Value) []Value {
for i := 0; i < o.fieldsValue.Len(); i++ {
accum = append(accum, asciiString(strconv.Itoa(i)))
}
return o.objectGoReflect.stringKeys(all, accum)
}
func (o *objectGoArrayReflect) iterateStringKeys() iterNextFunc {
return (&goArrayReflectPropIter{
o: o,
limit: o.fieldsValue.Len(),
}).next
}
func (o *objectGoArrayReflect) sortLen() int {
return o.fieldsValue.Len()
}
func (o *objectGoArrayReflect) sortGet(i int) Value {
return o.getIdx(valueInt(i), nil)
}
func (o *objectGoArrayReflect) swap(i int, j int) {
vi := o.fieldsValue.Index(i)
vj := o.fieldsValue.Index(j)
tmp := reflect.New(o.fieldsValue.Type().Elem()).Elem()
tmp.Set(vi)
vi.Set(vj)
vj.Set(tmp)
cachedI := o.valueCache.get(i)
cachedJ := o.valueCache.get(j)
if cachedI != nil {
cachedI.setReflectValue(vj)
o.valueCache.put(j, cachedI)
} else {
if j < len(o.valueCache) {
o.valueCache[j] = nil
}
}
if cachedJ != nil {
cachedJ.setReflectValue(vi)
o.valueCache.put(i, cachedJ)
} else {
if i < len(o.valueCache) {
o.valueCache[i] = nil
}
}
}
+158
View File
@@ -0,0 +1,158 @@
package goja
import (
"reflect"
"github.com/dop251/goja/unistring"
)
type objectGoMapSimple struct {
baseObject
data map[string]interface{}
}
func (o *objectGoMapSimple) init() {
o.baseObject.init()
o.prototype = o.val.runtime.global.ObjectPrototype
o.class = classObject
o.extensible = true
}
func (o *objectGoMapSimple) _getStr(name string) Value {
v, exists := o.data[name]
if !exists {
return nil
}
return o.val.runtime.ToValue(v)
}
func (o *objectGoMapSimple) getStr(name unistring.String, receiver Value) Value {
if v := o._getStr(name.String()); v != nil {
return v
}
return o.baseObject.getStr(name, receiver)
}
func (o *objectGoMapSimple) getOwnPropStr(name unistring.String) Value {
if v := o._getStr(name.String()); v != nil {
return v
}
return nil
}
func (o *objectGoMapSimple) setOwnStr(name unistring.String, val Value, throw bool) bool {
n := name.String()
if _, exists := o.data[n]; exists {
o.data[n] = val.Export()
return true
}
if proto := o.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok {
return res
}
}
// new property
if !o.extensible {
o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name)
return false
} else {
o.data[n] = val.Export()
}
return true
}
func trueValIfPresent(present bool) Value {
if present {
return valueTrue
}
return nil
}
func (o *objectGoMapSimple) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignStr(name, trueValIfPresent(o._hasStr(name.String())), val, receiver, throw)
}
func (o *objectGoMapSimple) _hasStr(name string) bool {
_, exists := o.data[name]
return exists
}
func (o *objectGoMapSimple) hasOwnPropertyStr(name unistring.String) bool {
return o._hasStr(name.String())
}
func (o *objectGoMapSimple) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
return false
}
n := name.String()
if o.extensible || o._hasStr(n) {
o.data[n] = descr.Value.Export()
return true
}
o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", n)
return false
}
func (o *objectGoMapSimple) deleteStr(name unistring.String, _ bool) bool {
delete(o.data, name.String())
return true
}
type gomapPropIter struct {
o *objectGoMapSimple
propNames []string
idx int
}
func (i *gomapPropIter) next() (propIterItem, iterNextFunc) {
for i.idx < len(i.propNames) {
name := i.propNames[i.idx]
i.idx++
if _, exists := i.o.data[name]; exists {
return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next
}
}
return propIterItem{}, nil
}
func (o *objectGoMapSimple) iterateStringKeys() iterNextFunc {
propNames := make([]string, len(o.data))
i := 0
for key := range o.data {
propNames[i] = key
i++
}
return (&gomapPropIter{
o: o,
propNames: propNames,
}).next
}
func (o *objectGoMapSimple) stringKeys(_ bool, accum []Value) []Value {
// all own keys are enumerable
for key := range o.data {
accum = append(accum, newStringValue(key))
}
return accum
}
func (o *objectGoMapSimple) export(*objectExportCtx) interface{} {
return o.data
}
func (o *objectGoMapSimple) exportType() reflect.Type {
return reflectTypeMap
}
func (o *objectGoMapSimple) equal(other objectImpl) bool {
if other, ok := other.(*objectGoMapSimple); ok {
return o == other
}
return false
}
+294
View File
@@ -0,0 +1,294 @@
package goja
import (
"fmt"
"reflect"
"github.com/dop251/goja/unistring"
)
type objectGoMapReflect struct {
objectGoReflect
keyType, valueType reflect.Type
}
func (o *objectGoMapReflect) init() {
o.objectGoReflect.init()
o.keyType = o.fieldsValue.Type().Key()
o.valueType = o.fieldsValue.Type().Elem()
}
func (o *objectGoMapReflect) toKey(n Value, throw bool) reflect.Value {
key := reflect.New(o.keyType).Elem()
err := o.val.runtime.toReflectValue(n, key, &objectExportCtx{})
if err != nil {
o.val.runtime.typeErrorResult(throw, "map key conversion error: %v", err)
return reflect.Value{}
}
return key
}
func (o *objectGoMapReflect) strToKey(name string, throw bool) reflect.Value {
if o.keyType.Kind() == reflect.String {
return reflect.ValueOf(name).Convert(o.keyType)
}
return o.toKey(newStringValue(name), throw)
}
func (o *objectGoMapReflect) _getKey(key reflect.Value) Value {
if !key.IsValid() {
return nil
}
if v := o.fieldsValue.MapIndex(key); v.IsValid() {
rv := v
if rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
return o.val.runtime.toValue(v.Interface(), rv)
}
return nil
}
func (o *objectGoMapReflect) _get(n Value) Value {
return o._getKey(o.toKey(n, false))
}
func (o *objectGoMapReflect) _getStr(name string) Value {
return o._getKey(o.strToKey(name, false))
}
func (o *objectGoMapReflect) getStr(name unistring.String, receiver Value) Value {
if v := o._getStr(name.String()); v != nil {
return v
}
return o.objectGoReflect.getStr(name, receiver)
}
func (o *objectGoMapReflect) getIdx(idx valueInt, receiver Value) Value {
if v := o._get(idx); v != nil {
return v
}
return o.objectGoReflect.getIdx(idx, receiver)
}
func (o *objectGoMapReflect) getOwnPropStr(name unistring.String) Value {
if v := o._getStr(name.String()); v != nil {
return &valueProperty{
value: v,
writable: true,
enumerable: true,
}
}
return o.objectGoReflect.getOwnPropStr(name)
}
func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value {
if v := o._get(idx); v != nil {
return &valueProperty{
value: v,
writable: true,
enumerable: true,
}
}
return o.objectGoReflect.getOwnPropStr(idx.string())
}
func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) {
v := reflect.New(o.valueType).Elem()
err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{})
if err != nil {
o.val.runtime.typeErrorResult(throw, "map value conversion error: %v", err)
return reflect.Value{}, false
}
return v, true
}
func (o *objectGoMapReflect) _put(key reflect.Value, val Value, throw bool) bool {
if key.IsValid() {
if o.extensible || o.fieldsValue.MapIndex(key).IsValid() {
v, ok := o.toValue(val, throw)
if !ok {
return false
}
o.fieldsValue.SetMapIndex(key, v)
} else {
o.val.runtime.typeErrorResult(throw, "Cannot set property %v, object is not extensible", key)
return false
}
return true
}
return false
}
func (o *objectGoMapReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
n := name.String()
key := o.strToKey(n, false)
if !key.IsValid() || !o.fieldsValue.MapIndex(key).IsValid() {
if proto := o.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok {
return res
}
}
// new property
if !o.extensible {
o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", n)
return false
} else {
if throw && !key.IsValid() {
o.strToKey(n, true)
return false
}
}
}
o._put(key, val, throw)
return true
}
func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool {
key := o.toKey(idx, false)
if !key.IsValid() || !o.fieldsValue.MapIndex(key).IsValid() {
if proto := o.prototype; proto != nil {
// we know it's foreign because prototype loops are not allowed
if res, ok := proto.self.setForeignIdx(idx, val, o.val, throw); ok {
return res
}
}
// new property
if !o.extensible {
o.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx)
return false
} else {
if throw && !key.IsValid() {
o.toKey(idx, true)
return false
}
}
}
o._put(key, val, throw)
return true
}
func (o *objectGoMapReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
}
func (o *objectGoMapReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
}
func (o *objectGoMapReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
return false
}
return o._put(o.strToKey(name.String(), throw), descr.Value, throw)
}
func (o *objectGoMapReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
return false
}
return o._put(o.toKey(idx, throw), descr.Value, throw)
}
func (o *objectGoMapReflect) hasOwnPropertyStr(name unistring.String) bool {
key := o.strToKey(name.String(), false)
if key.IsValid() && o.fieldsValue.MapIndex(key).IsValid() {
return true
}
return false
}
func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool {
key := o.toKey(idx, false)
if key.IsValid() && o.fieldsValue.MapIndex(key).IsValid() {
return true
}
return false
}
func (o *objectGoMapReflect) deleteStr(name unistring.String, throw bool) bool {
key := o.strToKey(name.String(), throw)
if !key.IsValid() {
return false
}
o.fieldsValue.SetMapIndex(key, reflect.Value{})
return true
}
func (o *objectGoMapReflect) deleteIdx(idx valueInt, throw bool) bool {
key := o.toKey(idx, throw)
if !key.IsValid() {
return false
}
o.fieldsValue.SetMapIndex(key, reflect.Value{})
return true
}
type gomapReflectPropIter struct {
o *objectGoMapReflect
keys []reflect.Value
idx int
}
func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) {
for i.idx < len(i.keys) {
key := i.keys[i.idx]
v := i.o.fieldsValue.MapIndex(key)
i.idx++
if v.IsValid() {
return propIterItem{name: i.o.keyToString(key), enumerable: _ENUM_TRUE}, i.next
}
}
return propIterItem{}, nil
}
func (o *objectGoMapReflect) iterateStringKeys() iterNextFunc {
return (&gomapReflectPropIter{
o: o,
keys: o.fieldsValue.MapKeys(),
}).next
}
func (o *objectGoMapReflect) stringKeys(_ bool, accum []Value) []Value {
// all own keys are enumerable
for _, key := range o.fieldsValue.MapKeys() {
accum = append(accum, o.keyToString(key))
}
return accum
}
func (*objectGoMapReflect) keyToString(key reflect.Value) String {
kind := key.Kind()
if kind == reflect.String {
return newStringValue(key.String())
}
str := fmt.Sprintf("%v", key)
switch kind {
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Float32,
reflect.Float64:
return asciiString(str)
default:
return newStringValue(str)
}
}
+677
View File
@@ -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
View File
@@ -0,0 +1,343 @@
package goja
import (
"math"
"math/bits"
"reflect"
"strconv"
"github.com/dop251/goja/unistring"
)
type objectGoSlice struct {
baseObject
data *[]interface{}
lengthProp valueProperty
origIsPtr bool
}
func (r *Runtime) newObjectGoSlice(data *[]interface{}, isPtr bool) *objectGoSlice {
obj := &Object{runtime: r}
a := &objectGoSlice{
baseObject: baseObject{
val: obj,
},
data: data,
origIsPtr: isPtr,
}
obj.self = a
a.init()
return a
}
func (o *objectGoSlice) init() {
o.baseObject.init()
o.class = classArray
o.prototype = o.val.runtime.getArrayPrototype()
o.lengthProp.writable = true
o.extensible = true
o.baseObject._put("length", &o.lengthProp)
}
func (o *objectGoSlice) updateLen() {
o.lengthProp.value = intToValue(int64(len(*o.data)))
}
func (o *objectGoSlice) _getIdx(idx int) Value {
return o.val.runtime.ToValue((*o.data)[idx])
}
func (o *objectGoSlice) getStr(name unistring.String, receiver Value) Value {
var ownProp Value
if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) {
ownProp = o._getIdx(idx)
} else if name == "length" {
o.updateLen()
ownProp = &o.lengthProp
}
return o.getStrWithOwnProp(ownProp, name, receiver)
}
func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value {
if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) {
return o._getIdx(int(idx))
}
if o.prototype != nil {
if receiver == nil {
return o.prototype.self.getIdx(idx, o.val)
}
return o.prototype.self.getIdx(idx, receiver)
}
return nil
}
func (o *objectGoSlice) getOwnPropStr(name unistring.String) Value {
if idx := strToGoIdx(name); idx >= 0 {
if idx < len(*o.data) {
return &valueProperty{
value: o._getIdx(idx),
writable: true,
enumerable: true,
}
}
return nil
}
if name == "length" {
o.updateLen()
return &o.lengthProp
}
return nil
}
func (o *objectGoSlice) getOwnPropIdx(idx valueInt) Value {
if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) {
return &valueProperty{
value: o._getIdx(int(idx)),
writable: true,
enumerable: true,
}
}
return nil
}
func (o *objectGoSlice) grow(size int) {
oldcap := cap(*o.data)
if oldcap < size {
n := make([]interface{}, size, growCap(size, len(*o.data), oldcap))
copy(n, *o.data)
*o.data = n
} else {
tail := (*o.data)[len(*o.data):size]
for k := range tail {
tail[k] = nil
}
*o.data = (*o.data)[:size]
}
}
func (o *objectGoSlice) shrink(size int) {
tail := (*o.data)[size:]
for k := range tail {
tail[k] = nil
}
*o.data = (*o.data)[:size]
}
func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) {
if idx >= len(*o.data) {
o.grow(idx + 1)
}
(*o.data)[idx] = v.Export()
}
func (o *objectGoSlice) putLength(v uint32, throw bool) bool {
if bits.UintSize == 32 && v > math.MaxInt32 {
panic(rangeError("Integer value overflows 32-bit int"))
}
newLen := int(v)
curLen := len(*o.data)
if newLen > curLen {
o.grow(newLen)
} else if newLen < curLen {
o.shrink(newLen)
}
return true
}
func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool {
if i := toIntStrict(int64(idx)); i >= 0 {
if i >= len(*o.data) {
if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok {
return res
}
}
o.putIdx(i, val, throw)
} else {
name := idx.string()
if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
return false
} else {
return res
}
}
return true
}
func (o *objectGoSlice) setOwnStr(name unistring.String, val Value, throw bool) bool {
if idx := strToGoIdx(name); idx >= 0 {
if idx >= len(*o.data) {
if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok {
return res
}
}
o.putIdx(idx, val, throw)
} else {
if name == "length" {
return o.putLength(o.val.runtime.toLengthUint32(val), throw)
}
if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name)
return false
} else {
return res
}
}
return true
}
func (o *objectGoSlice) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw)
}
func (o *objectGoSlice) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw)
}
func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool {
if idx := int64(idx); idx >= 0 {
return idx < int64(len(*o.data))
}
return false
}
func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool {
if idx := strToIdx64(name); idx >= 0 {
return idx < int64(len(*o.data))
}
return name == "length"
}
func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool {
if i := toIntStrict(int64(idx)); i >= 0 {
if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) {
return false
}
val := descr.Value
if val == nil {
val = _undefined
}
o.putIdx(i, val, throw)
return true
}
o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx)
return false
}
func (o *objectGoSlice) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if idx := strToGoIdx(name); idx >= 0 {
if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
return false
}
val := descr.Value
if val == nil {
val = _undefined
}
o.putIdx(idx, val, throw)
return true
}
if name == "length" {
return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
}
o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name)
return false
}
func (o *objectGoSlice) _deleteIdx(idx int64) {
if idx < int64(len(*o.data)) {
(*o.data)[idx] = nil
}
}
func (o *objectGoSlice) deleteStr(name unistring.String, throw bool) bool {
if idx := strToIdx64(name); idx >= 0 {
o._deleteIdx(idx)
return true
}
return o.baseObject.deleteStr(name, throw)
}
func (o *objectGoSlice) deleteIdx(i valueInt, throw bool) bool {
idx := int64(i)
if idx >= 0 {
o._deleteIdx(idx)
}
return true
}
type goslicePropIter struct {
o *objectGoSlice
idx, limit int
}
func (i *goslicePropIter) next() (propIterItem, iterNextFunc) {
if i.idx < i.limit && i.idx < len(*i.o.data) {
name := strconv.Itoa(i.idx)
i.idx++
return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next
}
return propIterItem{}, nil
}
func (o *objectGoSlice) iterateStringKeys() iterNextFunc {
return (&goslicePropIter{
o: o,
limit: len(*o.data),
}).next
}
func (o *objectGoSlice) stringKeys(_ bool, accum []Value) []Value {
for i := range *o.data {
accum = append(accum, asciiString(strconv.Itoa(i)))
}
return accum
}
func (o *objectGoSlice) export(*objectExportCtx) interface{} {
if o.origIsPtr {
return o.data
}
return *o.data
}
func (o *objectGoSlice) exportType() reflect.Type {
if o.origIsPtr {
return reflectTypeArrayPtr
}
return reflectTypeArray
}
func (o *objectGoSlice) equal(other objectImpl) bool {
if other, ok := other.(*objectGoSlice); ok {
return o.data == other.data
}
return false
}
func (o *objectGoSlice) esValue() Value {
return o.val
}
func (o *objectGoSlice) reflectValue() reflect.Value {
return reflect.ValueOf(o.data).Elem()
}
func (o *objectGoSlice) setReflectValue(value reflect.Value) {
o.data = value.Addr().Interface().(*[]interface{})
}
func (o *objectGoSlice) sortLen() int {
return len(*o.data)
}
func (o *objectGoSlice) sortGet(i int) Value {
return o.getIdx(valueInt(i), nil)
}
func (o *objectGoSlice) swap(i int, j int) {
(*o.data)[i], (*o.data)[j] = (*o.data)[j], (*o.data)[i]
}
+89
View File
@@ -0,0 +1,89 @@
package goja
import (
"math"
"math/bits"
"reflect"
"github.com/dop251/goja/unistring"
)
type objectGoSliceReflect struct {
objectGoArrayReflect
}
func (o *objectGoSliceReflect) init() {
o.objectGoArrayReflect._init()
o.lengthProp.writable = true
o.putIdx = o._putIdx
}
func (o *objectGoSliceReflect) _putIdx(idx int, v Value, throw bool) bool {
if idx >= o.fieldsValue.Len() {
o.grow(idx + 1)
}
return o.objectGoArrayReflect._putIdx(idx, v, throw)
}
func (o *objectGoSliceReflect) grow(size int) {
oldcap := o.fieldsValue.Cap()
if oldcap < size {
n := reflect.MakeSlice(o.fieldsValue.Type(), size, growCap(size, o.fieldsValue.Len(), oldcap))
reflect.Copy(n, o.fieldsValue)
o.fieldsValue.Set(n)
l := len(o.valueCache)
if l > size {
l = size
}
for i, w := range o.valueCache[:l] {
if w != nil {
w.setReflectValue(o.fieldsValue.Index(i))
}
}
} else {
tail := o.fieldsValue.Slice(o.fieldsValue.Len(), size)
zero := reflect.Zero(o.fieldsValue.Type().Elem())
for i := 0; i < tail.Len(); i++ {
tail.Index(i).Set(zero)
}
o.fieldsValue.SetLen(size)
}
}
func (o *objectGoSliceReflect) shrink(size int) {
o.valueCache.shrink(size)
tail := o.fieldsValue.Slice(size, o.fieldsValue.Len())
zero := reflect.Zero(o.fieldsValue.Type().Elem())
for i := 0; i < tail.Len(); i++ {
tail.Index(i).Set(zero)
}
o.fieldsValue.SetLen(size)
}
func (o *objectGoSliceReflect) putLength(v uint32, throw bool) bool {
if bits.UintSize == 32 && v > math.MaxInt32 {
panic(rangeError("Integer value overflows 32-bit int"))
}
newLen := int(v)
curLen := o.fieldsValue.Len()
if newLen > curLen {
o.grow(newLen)
} else if newLen < curLen {
o.shrink(newLen)
}
return true
}
func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
if name == "length" {
return o.putLength(o.val.runtime.toLengthUint32(val), throw)
}
return o.objectGoArrayReflect.setOwnStr(name, val, throw)
}
func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if name == "length" {
return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw)
}
return o.objectGoArrayReflect.defineOwnPropertyStr(name, descr, throw)
}
+469
View File
@@ -0,0 +1,469 @@
package goja
import (
"fmt"
"github.com/dop251/goja/unistring"
"math"
"reflect"
"sort"
)
type templatePropFactory func(*Runtime) Value
type objectTemplate struct {
propNames []unistring.String
props map[unistring.String]templatePropFactory
symProps map[*Symbol]templatePropFactory
symPropNames []*Symbol
protoFactory func(*Runtime) *Object
}
type templatedObject struct {
baseObject
tmpl *objectTemplate
protoMaterialised bool
}
type templatedFuncObject struct {
templatedObject
f func(FunctionCall) Value
construct func(args []Value, newTarget *Object) *Object
}
// This type exists because Array.prototype is supposed to be an array itself and I could not find
// a different way of implementing it without either introducing another layer of interfaces or hoisting
// the templates to baseObject both of which would have had a negative effect on the performance.
// The implementation is as simple as possible and is not optimised in any way, but I very much doubt anybody
// uses Array.prototype as an actual array.
type templatedArrayObject struct {
templatedObject
}
func newObjectTemplate() *objectTemplate {
return &objectTemplate{
props: make(map[unistring.String]templatePropFactory),
}
}
func (t *objectTemplate) putStr(name unistring.String, f templatePropFactory) {
t.props[name] = f
t.propNames = append(t.propNames, name)
}
func (t *objectTemplate) putSym(s *Symbol, f templatePropFactory) {
if t.symProps == nil {
t.symProps = make(map[*Symbol]templatePropFactory)
}
t.symProps[s] = f
t.symPropNames = append(t.symPropNames, s)
}
func (r *Runtime) newTemplatedObject(tmpl *objectTemplate, obj *Object) *templatedObject {
if obj == nil {
obj = &Object{runtime: r}
}
o := &templatedObject{
baseObject: baseObject{
class: classObject,
val: obj,
extensible: true,
},
tmpl: tmpl,
}
obj.self = o
o.init()
return o
}
func (o *templatedObject) materialiseProto() {
if !o.protoMaterialised {
if o.tmpl.protoFactory != nil {
o.prototype = o.tmpl.protoFactory(o.val.runtime)
}
o.protoMaterialised = true
}
}
func (o *templatedObject) getStr(name unistring.String, receiver Value) Value {
ownProp := o.getOwnPropStr(name)
if ownProp == nil {
o.materialiseProto()
}
return o.getStrWithOwnProp(ownProp, name, receiver)
}
func (o *templatedObject) getSym(s *Symbol, receiver Value) Value {
ownProp := o.getOwnPropSym(s)
if ownProp == nil {
o.materialiseProto()
}
return o.getWithOwnProp(ownProp, s, receiver)
}
func (o *templatedObject) getOwnPropStr(p unistring.String) Value {
if v, exists := o.values[p]; exists {
return v
}
if f := o.tmpl.props[p]; f != nil {
v := f(o.val.runtime)
o.values[p] = v
return v
}
return nil
}
func (o *templatedObject) materialiseSymbols() {
if o.symValues == nil {
o.symValues = newOrderedMap(nil)
for _, p := range o.tmpl.symPropNames {
o.symValues.set(p, o.tmpl.symProps[p](o.val.runtime))
}
}
}
func (o *templatedObject) getOwnPropSym(s *Symbol) Value {
if o.symValues == nil && o.tmpl.symProps[s] == nil {
return nil
}
o.materialiseSymbols()
return o.baseObject.getOwnPropSym(s)
}
func (o *templatedObject) materialisePropNames() {
if o.propNames == nil {
o.propNames = append(([]unistring.String)(nil), o.tmpl.propNames...)
}
}
func (o *templatedObject) setOwnStr(p unistring.String, v Value, throw bool) bool {
existing := o.getOwnPropStr(p) // materialise property (in case it's an accessor)
if existing == nil {
o.materialiseProto()
o.materialisePropNames()
}
return o.baseObject.setOwnStr(p, v, throw)
}
func (o *templatedObject) setOwnSym(name *Symbol, val Value, throw bool) bool {
o.materialiseSymbols()
o.materialiseProto()
return o.baseObject.setOwnSym(name, val, throw)
}
func (o *templatedObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
ownProp := o.getOwnPropStr(name)
if ownProp == nil {
o.materialiseProto()
}
return o._setForeignStr(name, ownProp, val, receiver, throw)
}
func (o *templatedObject) proto() *Object {
o.materialiseProto()
return o.prototype
}
func (o *templatedObject) setProto(proto *Object, throw bool) bool {
o.protoMaterialised = true
ret := o.baseObject.setProto(proto, throw)
if ret {
o.protoMaterialised = true
}
return ret
}
func (o *templatedObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) {
return o.setForeignStr(name.string(), val, receiver, throw)
}
func (o *templatedObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) {
o.materialiseProto()
o.materialiseSymbols()
return o.baseObject.setForeignSym(name, val, receiver, throw)
}
func (o *templatedObject) hasPropertyStr(name unistring.String) bool {
if o.val.self.hasOwnPropertyStr(name) {
return true
}
o.materialiseProto()
if o.prototype != nil {
return o.prototype.self.hasPropertyStr(name)
}
return false
}
func (o *templatedObject) hasPropertySym(s *Symbol) bool {
if o.hasOwnPropertySym(s) {
return true
}
o.materialiseProto()
if o.prototype != nil {
return o.prototype.self.hasPropertySym(s)
}
return false
}
func (o *templatedObject) hasOwnPropertyStr(name unistring.String) bool {
if v, exists := o.values[name]; exists {
return v != nil
}
_, exists := o.tmpl.props[name]
return exists
}
func (o *templatedObject) hasOwnPropertySym(s *Symbol) bool {
if o.symValues != nil {
return o.symValues.has(s)
}
_, exists := o.tmpl.symProps[s]
return exists
}
func (o *templatedObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
existingVal := o.getOwnPropStr(name)
if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok {
o.values[name] = v
if existingVal == nil {
o.materialisePropNames()
names := copyNamesIfNeeded(o.propNames, 1)
o.propNames = append(names, name)
}
return true
}
return false
}
func (o *templatedObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool {
o.materialiseSymbols()
return o.baseObject.defineOwnPropertySym(s, descr, throw)
}
func (o *templatedObject) deleteStr(name unistring.String, throw bool) bool {
if val := o.getOwnPropStr(name); val != nil {
if !o.checkDelete(name, val, throw) {
return false
}
o.materialisePropNames()
o._delete(name)
if _, exists := o.tmpl.props[name]; exists {
o.values[name] = nil // white hole
}
}
return true
}
func (o *templatedObject) deleteSym(s *Symbol, throw bool) bool {
o.materialiseSymbols()
return o.baseObject.deleteSym(s, throw)
}
func (o *templatedObject) materialiseProps() {
for name, f := range o.tmpl.props {
if _, exists := o.values[name]; !exists {
o.values[name] = f(o.val.runtime)
}
}
o.materialisePropNames()
}
func (o *templatedObject) iterateStringKeys() iterNextFunc {
o.materialiseProps()
return o.baseObject.iterateStringKeys()
}
func (o *templatedObject) iterateSymbols() iterNextFunc {
o.materialiseSymbols()
return o.baseObject.iterateSymbols()
}
func (o *templatedObject) stringKeys(all bool, keys []Value) []Value {
if all {
o.materialisePropNames()
} else {
o.materialiseProps()
}
return o.baseObject.stringKeys(all, keys)
}
func (o *templatedObject) symbols(all bool, accum []Value) []Value {
o.materialiseSymbols()
return o.baseObject.symbols(all, accum)
}
func (o *templatedObject) keys(all bool, accum []Value) []Value {
return o.symbols(all, o.stringKeys(all, accum))
}
func (r *Runtime) newTemplatedFuncObject(tmpl *objectTemplate, obj *Object, f func(FunctionCall) Value, ctor func([]Value, *Object) *Object) *templatedFuncObject {
if obj == nil {
obj = &Object{runtime: r}
}
o := &templatedFuncObject{
templatedObject: templatedObject{
baseObject: baseObject{
class: classFunction,
val: obj,
extensible: true,
},
tmpl: tmpl,
},
f: f,
construct: ctor,
}
obj.self = o
o.init()
return o
}
func (f *templatedFuncObject) source() String {
return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
}
func (f *templatedFuncObject) export(*objectExportCtx) interface{} {
return f.f
}
func (f *templatedFuncObject) assertCallable() (func(FunctionCall) Value, bool) {
if f.f != nil {
return f.f, true
}
return nil, false
}
func (f *templatedFuncObject) vmCall(vm *vm, n int) {
var nf nativeFuncObject
nf.f = f.f
nf.vmCall(vm, n)
}
func (f *templatedFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object {
return f.construct
}
func (f *templatedFuncObject) exportType() reflect.Type {
return reflectTypeFunc
}
func (f *templatedFuncObject) typeOf() String {
return stringFunction
}
func (f *templatedFuncObject) hasInstance(v Value) bool {
return hasInstance(f.val, v)
}
func (r *Runtime) newTemplatedArrayObject(tmpl *objectTemplate, obj *Object) *templatedArrayObject {
if obj == nil {
obj = &Object{runtime: r}
}
o := &templatedArrayObject{
templatedObject: templatedObject{
baseObject: baseObject{
class: classArray,
val: obj,
extensible: true,
},
tmpl: tmpl,
},
}
obj.self = o
o.init()
return o
}
func (a *templatedArrayObject) getLenProp() *valueProperty {
lenProp, _ := a.getOwnPropStr("length").(*valueProperty)
if lenProp == nil {
panic(a.val.runtime.NewTypeError("missing length property"))
}
return lenProp
}
func (a *templatedArrayObject) _setOwnIdx(idx uint32) {
lenProp := a.getLenProp()
l := uint32(lenProp.value.ToInteger())
if idx >= l {
lenProp.value = intToValue(int64(idx) + 1)
}
}
func (a *templatedArrayObject) setLength(l uint32, throw bool) bool {
lenProp := a.getLenProp()
oldLen := uint32(lenProp.value.ToInteger())
if l == oldLen {
return true
}
if !lenProp.writable {
a.val.runtime.typeErrorResult(throw, "length is not writable")
return false
}
ret := true
if l < oldLen {
a.materialisePropNames()
a.fixPropOrder()
i := sort.Search(a.idxPropCount, func(idx int) bool {
return strToArrayIdx(a.propNames[idx]) >= l
})
for j := a.idxPropCount - 1; j >= i; j-- {
if !a.deleteStr(a.propNames[j], false) {
l = strToArrayIdx(a.propNames[j]) + 1
ret = false
break
}
}
}
lenProp.value = intToValue(int64(l))
return ret
}
func (a *templatedArrayObject) setOwnStr(name unistring.String, value Value, throw bool) bool {
if name == "length" {
return a.setLength(a.val.runtime.toLengthUint32(value), throw)
}
if !a.templatedObject.setOwnStr(name, value, throw) {
return false
}
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
a._setOwnIdx(idx)
}
return true
}
func (a *templatedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool {
if !a.templatedObject.setOwnStr(p.string(), v, throw) {
return false
}
if idx := toIdx(p); idx != math.MaxUint32 {
a._setOwnIdx(idx)
}
return true
}
func (a *templatedArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
if name == "length" {
return a.val.runtime.defineArrayLength(a.getLenProp(), descr, a.setLength, throw)
}
if !a.templatedObject.defineOwnPropertyStr(name, descr, throw) {
return false
}
if idx := strToArrayIdx(name); idx != math.MaxUint32 {
a._setOwnIdx(idx)
}
return true
}
func (a *templatedArrayObject) defineOwnPropertyIdx(p valueInt, desc PropertyDescriptor, throw bool) bool {
if !a.templatedObject.defineOwnPropertyStr(p.string(), desc, throw) {
return false
}
if idx := toIdx(p); idx != math.MaxUint32 {
a._setOwnIdx(idx)
}
return true
}
+184
View File
@@ -0,0 +1,184 @@
# parser
--
import "github.com/dop251/goja/parser"
Package parser implements a parser for JavaScript. Borrowed from https://github.com/robertkrimen/otto/tree/master/parser
import (
"github.com/dop251/goja/parser"
)
Parse and return an AST
filename := "" // A filename is optional
src := `
// Sample xyzzy example
(function(){
if (3.14159 > 0) {
console.log("Hello, World.");
return;
}
var xyzzy = NaN;
console.log("Nothing happens.");
return xyzzy;
})();
`
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
program, err := parser.ParseFile(nil, filename, src, 0)
### Warning
The parser and AST interfaces are still works-in-progress (particularly where
node types are concerned) and may change in the future.
## Usage
#### func ParseFile
```go
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
```
ParseFile parses the source code of a single JavaScript/ECMAScript source file
and returns the corresponding ast.Program node.
If fileSet == nil, ParseFile parses source without a FileSet. If fileSet != nil,
ParseFile first adds filename and src to fileSet.
The filename argument is optional and is used for labelling errors, etc.
src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST
always be in UTF-8.
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
#### func ParseFunction
```go
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error)
```
ParseFunction parses a given parameter list and body as a function and returns
the corresponding ast.FunctionLiteral node.
The parameter list, if any, should be a comma-separated list of identifiers.
#### func ReadSource
```go
func ReadSource(filename string, src interface{}) ([]byte, error)
```
#### func TransformRegExp
```go
func TransformRegExp(pattern string) (string, error)
```
TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
backreference (\1, \2, ...) will cause an error.
re2 (Go) has a different definition for \s: [\t\n\f\r ]. The JavaScript
definition, on the other hand, also includes \v, Unicode "Separator, Space",
etc.
If the pattern is invalid (not valid even in JavaScript), then this function
returns the empty string and an error.
If the pattern is valid, but incompatible (contains a lookahead or
backreference), then this function returns the transformation (a non-empty
string) AND an error.
#### type Error
```go
type Error struct {
Position file.Position
Message string
}
```
An Error represents a parsing error. It includes the position where the error
occurred and a message/description.
#### func (Error) Error
```go
func (self Error) Error() string
```
#### type ErrorList
```go
type ErrorList []*Error
```
ErrorList is a list of *Errors.
#### func (*ErrorList) Add
```go
func (self *ErrorList) Add(position file.Position, msg string)
```
Add adds an Error with given position and message to an ErrorList.
#### func (ErrorList) Err
```go
func (self ErrorList) Err() error
```
Err returns an error equivalent to this ErrorList. If the list is empty, Err
returns nil.
#### func (ErrorList) Error
```go
func (self ErrorList) Error() string
```
Error implements the Error interface.
#### func (ErrorList) Len
```go
func (self ErrorList) Len() int
```
#### func (ErrorList) Less
```go
func (self ErrorList) Less(i, j int) bool
```
#### func (*ErrorList) Reset
```go
func (self *ErrorList) Reset()
```
Reset resets an ErrorList to no errors.
#### func (ErrorList) Sort
```go
func (self ErrorList) Sort()
```
#### func (ErrorList) Swap
```go
func (self ErrorList) Swap(i, j int)
```
#### type Mode
```go
type Mode uint
```
A Mode value is a set of flags (or 0). They control optional parser
functionality.
--
**godocdown** http://github.com/robertkrimen/godocdown
+176
View File
@@ -0,0 +1,176 @@
package parser
import (
"fmt"
"sort"
"github.com/dop251/goja/file"
"github.com/dop251/goja/token"
)
const (
err_UnexpectedToken = "Unexpected token %v"
err_UnexpectedEndOfInput = "Unexpected end of input"
err_UnexpectedEscape = "Unexpected escape"
)
// UnexpectedNumber: 'Unexpected number',
// UnexpectedString: 'Unexpected string',
// UnexpectedIdentifier: 'Unexpected identifier',
// UnexpectedReserved: 'Unexpected reserved word',
// NewlineAfterThrow: 'Illegal newline after throw',
// InvalidRegExp: 'Invalid regular expression',
// UnterminatedRegExp: 'Invalid regular expression: missing /',
// InvalidLHSInAssignment: 'Invalid left-hand side in assignment',
// InvalidLHSInForIn: 'Invalid left-hand side in for-in',
// MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
// NoCatchOrFinally: 'Missing catch or finally after try',
// UnknownLabel: 'Undefined label \'%0\'',
// Redeclaration: '%0 \'%1\' has already been declared',
// IllegalContinue: 'Illegal continue statement',
// IllegalBreak: 'Illegal break statement',
// IllegalReturn: 'Illegal return statement',
// StrictModeWith: 'Strict mode code may not include a with statement',
// StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode',
// StrictVarName: 'Variable name may not be eval or arguments in strict mode',
// StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode',
// StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
// StrictFunctionName: 'Function name may not be eval or arguments in strict mode',
// StrictOctalLiteral: 'Octal literals are not allowed in strict mode.',
// StrictDelete: 'Delete of an unqualified identifier in strict mode.',
// StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode',
// AccessorDataProperty: 'Object literal may not have data and accessor property with the same name',
// AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name',
// StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode',
// StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
// StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
// StrictReservedWord: 'Use of future reserved word in strict mode'
// A SyntaxError is a description of an ECMAScript syntax error.
// An Error represents a parsing error. It includes the position where the error occurred and a message/description.
type Error struct {
Position file.Position
Message string
}
// FIXME Should this be "SyntaxError"?
func (self Error) Error() string {
filename := self.Position.Filename
if filename == "" {
filename = "(anonymous)"
}
return fmt.Sprintf("%s: Line %d:%d %s",
filename,
self.Position.Line,
self.Position.Column,
self.Message,
)
}
func (self *_parser) error(place interface{}, msg string, msgValues ...interface{}) *Error {
idx := file.Idx(0)
switch place := place.(type) {
case int:
idx = self.idxOf(place)
case file.Idx:
if place == 0 {
idx = self.idxOf(self.chrOffset)
} else {
idx = place
}
default:
panic(fmt.Errorf("error(%T, ...)", place))
}
position := self.position(idx)
msg = fmt.Sprintf(msg, msgValues...)
self.errors.Add(position, msg)
return self.errors[len(self.errors)-1]
}
func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error {
if chr == -1 {
return self.error(idx, err_UnexpectedEndOfInput)
}
return self.error(idx, err_UnexpectedToken, token.ILLEGAL)
}
func (self *_parser) errorUnexpectedToken(tkn token.Token) error {
switch tkn {
case token.EOF:
return self.error(file.Idx(0), err_UnexpectedEndOfInput)
}
value := tkn.String()
switch tkn {
case token.BOOLEAN, token.NULL:
value = self.literal
case token.IDENTIFIER:
return self.error(self.idx, "Unexpected identifier")
case token.KEYWORD:
// TODO Might be a future reserved word
return self.error(self.idx, "Unexpected reserved word")
case token.ESCAPED_RESERVED_WORD:
return self.error(self.idx, "Keyword must not contain escaped characters")
case token.NUMBER:
return self.error(self.idx, "Unexpected number")
case token.STRING:
return self.error(self.idx, "Unexpected string")
}
return self.error(self.idx, err_UnexpectedToken, value)
}
// ErrorList is a list of *Errors.
type ErrorList []*Error
// Add adds an Error with given position and message to an ErrorList.
func (self *ErrorList) Add(position file.Position, msg string) {
*self = append(*self, &Error{position, msg})
}
// Reset resets an ErrorList to no errors.
func (self *ErrorList) Reset() { *self = (*self)[0:0] }
func (self ErrorList) Len() int { return len(self) }
func (self ErrorList) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
func (self ErrorList) Less(i, j int) bool {
x := &self[i].Position
y := &self[j].Position
if x.Filename < y.Filename {
return true
}
if x.Filename == y.Filename {
if x.Line < y.Line {
return true
}
if x.Line == y.Line {
return x.Column < y.Column
}
}
return false
}
func (self ErrorList) Sort() {
sort.Sort(self)
}
// Error implements the Error interface.
func (self ErrorList) Error() string {
switch len(self) {
case 0:
return "no errors"
case 1:
return self[0].Error()
}
return fmt.Sprintf("%s (and %d more errors)", self[0].Error(), len(self)-1)
}
// Err returns an error equivalent to this ErrorList.
// If the list is empty, Err returns nil.
func (self ErrorList) Err() error {
if len(self) == 0 {
return nil
}
return self
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+268
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+350
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+639
View File
@@ -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 &regexp2Wrapper{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 := &regexpPattern{
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 &regexp2Wrapper{
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
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
checks = ["all", "-ST1000", "-ST1003", "-ST1005", "-ST1006", "-ST1012", "-ST1021", "-ST1020", "-ST1008"]
+364
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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