expressions working

Signed-off-by: Casey Lee <[email protected]>
This commit is contained in:
Casey Lee committed 2020-02-20 21:05:55 -05:00
1 parent 409060c847
commit e40ab0145f
114 files changed
+32361

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
walk
====
Fast parallel version of golang filepath.Walk()
Performs traversals in parallel so set GOMAXPROCS appropriately. Vaues of 8 to 16 seem to work best on my
4-CPU plus 4 SMT pseudo-CPU MacBookPro. The result is about 4x-6x the traversal rate of the standard Walk().
The two are not identical since we are walking the file system in a tumult of asynchronous walkFunc calls by
a number of goroutines. So, take note of the following:
1. This walk honors all of the walkFunc error semantics but as multiple user-supplied walkFuncs may simultaneously encounter a traversal error or generate one to stop traversal, only the FIRST of these will be returned as the Walk() result.
2. Further, since there may be a few files in flight at the instant of error discovery, a few more walkFunc calls may happen after the first error-generating call has signaled its desire to stop. In general this is a non-issue but it could matter so pay attention when designing your walkFunc. (For example, if you accumulate results then you need to have your own means to know to stop accumulating once you signal an error.)
3. Because the walkFunc is called concurrently in multiple goroutines, it needs to be careful about what it does with external data to avoid collisions. Results may be printed using fmt, but generally the best plan is to send results over a channel or accumulate counts using a locked mutex.
These issues are illustrated/handled in the simple traversal programs supplied with walk. There is also a test file that is just the tests from filepath in the Go language's standard library. Walk passes these tests when run in single process mode, and passes most of them in concurrent mode (GOMAXPROCS > 1). The problem is not a real problem, but one of the test expecting a specific number of errors to be found based on presumed sequential traversals.
Copyright (c) 2016 Michael T Jones
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.
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package walk
import "strings"
// IsAbs returns true if the path is absolute.
func IsAbs(path string) bool {
return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "#")
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func volumeNameLen(path string) int {
return 0
}
// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
func splitList(path string) []string {
if path == "" {
return []string{}
}
return strings.Split(path, string(ListSeparator))
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package walk
import "strings"
// IsAbs returns true if the path is absolute.
func IsAbs(path string) bool {
return strings.HasPrefix(path, "/")
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func volumeNameLen(path string) int {
return 0
}
// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
func splitList(path string) []string {
if path == "" {
return []string{}
}
return strings.Split(path, string(ListSeparator))
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package walk
import (
"strings"
)
func isSlash(c uint8) bool {
return c == '\\' || c == '/'
}
// IsAbs returns true if the path is absolute.
func IsAbs(path string) (b bool) {
l := volumeNameLen(path)
if l == 0 {
return false
}
path = path[l:]
if path == "" {
return false
}
return isSlash(path[0])
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func volumeNameLen(path string) int {
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}
// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
if strings.HasPrefix(p, prefix) {
return true
}
return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
}
func splitList(path string) []string {
// The same implementation is used in LookPath in os/exec;
// consider changing os/exec when changing this.
if path == "" {
return []string{}
}
// Split path, respecting but preserving quotes.
list := []string{}
start := 0
quo := false
for i := 0; i < len(path); i++ {
switch c := path[i]; {
case c == '"':
quo = !quo
case c == ListSeparator && !quo:
list = append(list, path[start:i])
start = i + 1
}
}
list = append(list, path[start:])
// Remove quotes.
for i, s := range list {
if strings.Contains(s, `"`) {
list[i] = strings.Replace(s, `"`, ``, -1)
}
}
return list
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package walk
import (
"bytes"
"errors"
"os"
"strings"
)
func evalSymlinks(path string) (string, error) {
const maxIter = 255
originalPath := path
// consume path by taking each frontmost path element,
// expanding it if it's a symlink, and appending it to b
var b bytes.Buffer
for n := 0; path != ""; n++ {
if n > maxIter {
return "", errors.New("EvalSymlinks: too many links in " + originalPath)
}
// find next path component, p
i := strings.IndexRune(path, Separator)
var p string
if i == -1 {
p, path = path, ""
} else {
p, path = path[:i], path[i+1:]
}
if p == "" {
if b.Len() == 0 {
// must be absolute path
b.WriteRune(Separator)
}
continue
}
fi, err := os.Lstat(b.String() + p)
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
b.WriteString(p)
if path != "" {
b.WriteRune(Separator)
}
continue
}
// it's a symlink, put it at the front of path
dest, err := os.Readlink(b.String() + p)
if err != nil {
return "", err
}
if IsAbs(dest) {
b.Reset()
}
path = dest + string(Separator) + path
}
return Clean(b.String()), nil
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package walk
import (
"syscall"
)
func toShort(path string) (string, error) {
p, err := syscall.UTF16FromString(path)
if err != nil {
return "", err
}
b := p // GetShortPathName says we can reuse buffer
n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
if n > uint32(len(b)) {
b = make([]uint16, n)
n, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
}
return syscall.UTF16ToString(b), nil
}
func toLong(path string) (string, error) {
p, err := syscall.UTF16FromString(path)
if err != nil {
return "", err
}
b := p // GetLongPathName says we can reuse buffer
n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
if n > uint32(len(b)) {
b = make([]uint16, n)
n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
}
b = b[:n]
return syscall.UTF16ToString(b), nil
}
func evalSymlinks(path string) (string, error) {
p, err := toShort(path)
if err != nil {
return "", err
}
p, err = toLong(p)
if err != nil {
return "", err
}
// syscall.GetLongPathName does not change the case of the drive letter,
// but the result of EvalSymlinks must be unique, so we have
// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
// Make drive letter upper case.
if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' {
p = string(p[0]+'A'-'a') + p[1:]
}
return Clean(p), nil
}
+444
View File
@@ -0,0 +1,444 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package filepath implements utility routines for manipulating filename paths
// in a way compatible with the target operating system-defined file paths.
package walk
import (
"errors"
"os"
"sort"
"strings"
"sync"
)
// SkipDir is used as a return value from WalkFuncs to indicate that
// the directory named in the call is to be skipped. It is not returned
// as an error by any function.
var SkipDir = errors.New("skip this directory")
// WalkFunc is the type of the function called for each file or directory
// visited by Walk. The path argument contains the argument to Walk as a
// prefix; that is, if Walk is called with "dir", which is a directory
// containing the file "a", the walk function will be called with argument
// "dir/a". The info argument is the os.FileInfo for the named path.
//
// If there was a problem walking to the file or directory named by path, the
// incoming error will describe the problem and the function can decide how
// to handle that error (and Walk will not descend into that directory). If
// an error is returned, processing stops. The sole exception is that if path
// is a directory and the function returns the special value SkipDir, the
// contents of the directory are skipped and processing continues as usual on
// the next file.
type WalkFunc func(path string, info os.FileInfo, err error) error
var lstat = os.Lstat // for testing
var LstatP = &lstat
type VisitData struct {
path string
info os.FileInfo
}
type WalkState struct {
walkFn WalkFunc
v chan VisitData // files to be processed
active sync.WaitGroup // number of files to process
lock sync.RWMutex
firstError error // accessed using lock
}
func (ws *WalkState) terminated() bool {
ws.lock.RLock()
done := ws.firstError != nil
ws.lock.RUnlock()
return done
}
func (ws *WalkState) setTerminated(err error) {
ws.lock.Lock()
if ws.firstError == nil {
ws.firstError = err
}
ws.lock.Unlock()
return
}
func (ws *WalkState) visitChannel() {
for file := range ws.v {
ws.visitFile(file)
ws.active.Add(-1)
}
}
func (ws *WalkState) visitFile(file VisitData) {
if ws.terminated() {
return
}
err := ws.walkFn(file.path, file.info, nil)
if err != nil {
if !(file.info.IsDir() && err == SkipDir) {
ws.setTerminated(err)
}
return
}
if !file.info.IsDir() {
return
}
names, err := readDirNames(file.path)
if err != nil {
err = ws.walkFn(file.path, file.info, err)
if err != nil {
ws.setTerminated(err)
}
return
}
here := file.path
for _, name := range names {
file.path = Join(here, name)
file.info, err = lstat(file.path)
if err != nil {
err = ws.walkFn(file.path, file.info, err)
if err != nil && (!file.info.IsDir() || err != SkipDir) {
ws.setTerminated(err)
return
}
} else {
switch file.info.IsDir() {
case true:
ws.active.Add(1) // presume channel send will succeed
select {
case ws.v <- file:
// push directory info to queue for concurrent traversal
default:
// undo increment when send fails and handle now
ws.active.Add(-1)
ws.visitFile(file)
}
case false:
err = ws.walkFn(file.path, file.info, nil)
if err != nil {
ws.setTerminated(err)
return
}
}
}
}
}
// Walk walks the file tree rooted at root, calling walkFn for each file or
// directory in the tree, including root. All errors that arise visiting files
// and directories are filtered by walkFn. The files are walked in a random
// order. Walk does not follow symbolic links.
func Walk(root string, walkFn WalkFunc) error {
info, err := os.Lstat(root)
if err != nil {
return walkFn(root, nil, err)
}
ws := &WalkState{
walkFn: walkFn,
v: make(chan VisitData, 1024),
}
defer close(ws.v)
ws.active.Add(1)
ws.v <- VisitData{root, info}
walkers := 16
for i := 0; i < walkers; i++ {
go ws.visitChannel()
}
ws.active.Wait()
return ws.firstError
}
//
// THE REMAINDER IS UNCHANGED FROM THE ORGINAL GO LIBRARY ORIGINAL
//
// readDirNames reads the directory named by dirname and returns
// a sorted list of directory entries.
func readDirNames(dirname string) ([]string, error) {
f, err := os.Open(dirname)
if err != nil {
return nil, err
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
return nil, err
}
sort.Strings(names) // omit sort to save 1-2%
return names, nil
}
// A lazybuf is a lazily constructed path buffer.
// It supports append, reading previously appended bytes,
// and retrieving the final string. It does not allocate a buffer
// to hold the output until that output diverges from s.
type lazybuf struct {
path string
buf []byte
w int
volAndPath string
volLen int
}
func (b *lazybuf) index(i int) byte {
if b.buf != nil {
return b.buf[i]
}
return b.path[i]
}
func (b *lazybuf) append(c byte) {
if b.buf == nil {
if b.w < len(b.path) && b.path[b.w] == c {
b.w++
return
}
b.buf = make([]byte, len(b.path))
copy(b.buf, b.path[:b.w])
}
b.buf[b.w] = c
b.w++
}
func (b *lazybuf) string() string {
if b.buf == nil {
return b.volAndPath[:b.volLen+b.w]
}
return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
}
const (
Separator = os.PathSeparator
ListSeparator = os.PathListSeparator
)
// Clean returns the shortest path name equivalent to path
// by purely lexical processing. It applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple Separator elements with a single one.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path,
// assuming Separator is '/'.
//
// The returned path ends in a slash only if it represents a root directory,
// such as "/" on Unix or `C:\` on Windows.
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,''
// http://plan9.bell-labs.com/sys/doc/lexnames.html
func Clean(path string) string {
originalPath := path
volLen := volumeNameLen(path)
path = path[volLen:]
if path == "" {
if volLen > 1 && originalPath[1] != ':' {
// should be UNC
return FromSlash(originalPath)
}
return originalPath + "."
}
rooted := os.IsPathSeparator(path[0])
// Invariants:
// reading from path; r is index of next byte to process.
// writing to buf; w is index of next byte to write.
// dotdot is index in buf where .. must stop, either because
// it is the leading slash or it is a leading ../../.. prefix.
n := len(path)
out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
r, dotdot := 0, 0
if rooted {
out.append(Separator)
r, dotdot = 1, 1
}
for r < n {
switch {
case os.IsPathSeparator(path[r]):
// empty path element
r++
case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
// . element
r++
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
// .. element: remove to last separator
r += 2
switch {
case out.w > dotdot:
// can backtrack
out.w--
for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
out.w--
}
case !rooted:
// cannot backtrack, but not rooted, so append .. element.
if out.w > 0 {
out.append(Separator)
}
out.append('.')
out.append('.')
dotdot = out.w
}
default:
// real path element.
// add slash if needed
if rooted && out.w != 1 || !rooted && out.w != 0 {
out.append(Separator)
}
// copy element
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
out.append(path[r])
}
}
}
// Turn empty string into "."
if out.w == 0 {
out.append('.')
}
return FromSlash(out.string())
}
// ToSlash returns the result of replacing each separator character
// in path with a slash ('/') character. Multiple separators are
// replaced by multiple slashes.
func ToSlash(path string) string {
if Separator == '/' {
return path
}
return strings.Replace(path, string(Separator), "/", -1)
}
// FromSlash returns the result of replacing each slash ('/') character
// in path with a separator character. Multiple slashes are replaced
// by multiple separators.
func FromSlash(path string) string {
if Separator == '/' {
return path
}
return strings.Replace(path, "/", string(Separator), -1)
}
// Join joins any number of path elements into a single path, adding
// a Separator if necessary. The result is Cleaned, in particular
// all empty strings are ignored.
func Join(elem ...string) string {
for i, e := range elem {
if e != "" {
return Clean(strings.Join(elem[i:], string(Separator)))
}
}
return ""
}
// Rel returns a relative path that is lexically equivalent to targpath when
// joined to basepath with an intervening separator. That is,
// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
// On success, the returned path will always be relative to basepath,
// even if basepath and targpath share no elements.
// An error is returned if targpath can't be made relative to basepath or if
// knowing the current working directory would be necessary to compute it.
func Rel(basepath, targpath string) (string, error) {
baseVol := VolumeName(basepath)
targVol := VolumeName(targpath)
base := Clean(basepath)
targ := Clean(targpath)
if targ == base {
return ".", nil
}
base = base[len(baseVol):]
targ = targ[len(targVol):]
if base == "." {
base = ""
}
// Can't use IsAbs - `\a` and `a` are both relative in Windows.
baseSlashed := len(base) > 0 && base[0] == Separator
targSlashed := len(targ) > 0 && targ[0] == Separator
if baseSlashed != targSlashed || baseVol != targVol {
return "", errors.New("Rel: can't make " + targ + " relative to " + base)
}
// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
bl := len(base)
tl := len(targ)
var b0, bi, t0, ti int
for {
for bi < bl && base[bi] != Separator {
bi++
}
for ti < tl && targ[ti] != Separator {
ti++
}
if targ[t0:ti] != base[b0:bi] {
break
}
if bi < bl {
bi++
}
if ti < tl {
ti++
}
b0 = bi
t0 = ti
}
if base[b0:bi] == ".." {
return "", errors.New("Rel: can't make " + targ + " relative to " + base)
}
if b0 != bl {
// Base elements left. Must go up before going down.
seps := strings.Count(base[b0:bl], string(Separator))
size := 2 + seps*3
if tl != t0 {
size += 1 + tl - t0
}
buf := make([]byte, size)
n := copy(buf, "..")
for i := 0; i < seps; i++ {
buf[n] = Separator
copy(buf[n+1:], "..")
n += 3
}
if t0 != tl {
buf[n] = Separator
copy(buf[n+1:], targ[t0:])
}
return string(buf), nil
}
return targ[t0:], nil
}
// VolumeName returns leading volume name.
// Given "C:\foo\bar" it returns "C:" under windows.
// Given "\\host\share\foo" it returns "\\host\share".
// On other platforms it returns "".
func VolumeName(path string) (v string) {
return path[:volumeNameLen(path)]
}
// EvalSymlinks returns the path name after the evaluation of any symbolic
// links.
// If path is relative the result will be relative to the current directory,
// unless one of the components is an absolute symbolic link.
func EvalSymlinks(path string) (string, error) {
return evalSymlinks(path)
}
+7
View File
@@ -0,0 +1,7 @@
tmp/
*.log
_*
node_modules
example/dist
/Gododir/godobin*
/Gododir/Gododir
+5
View File
@@ -0,0 +1,5 @@
* [string.js](http://stringjs.com) - I contributed several
functions to this project.
* [bbgen.net](http://bbgen.net/blog/2011/06/string-to-argc-argv/)
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-2014 Mario L. Gutierrez <[email protected]>
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.
+649
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
1.1.0
+19
View File
@@ -0,0 +1,19 @@
// Package str is a comprehensive set of string functions to build more
// Go awesomeness. Str complements Go's standard packages and does not duplicate
// functionality found in `strings` or `strconv`.
//
// Str is based on plain functions instead of object-based methods,
// consistent with Go standard string packages.
//
// str.Between("<a>foo</a>", "<a>", "</a>") == "foo"
//
// Str supports pipelining instead of chaining
//
// s := str.Pipe("\nabcdef\n", Clean, BetweenF("a", "f"), ChompLeftF("bc"))
//
// User-defined filters can be added to the pipeline by inserting a function
// or closure that returns a function with this signature
//
// func(string) string
//
package str
+337
View File
@@ -0,0 +1,337 @@
package str
import (
"fmt"
"html"
//"log"
"regexp"
"strings"
)
// Verbose flag enables console output for those functions that have
// counterparts in Go's excellent stadard packages.
var Verbose = false
var templateOpen = "{{"
var templateClose = "}}"
var beginEndSpacesRe = regexp.MustCompile("^\\s+|\\s+$")
var camelizeRe = regexp.MustCompile(`(\-|_|\s)+(.)?`)
var camelizeRe2 = regexp.MustCompile(`(\-|_|\s)+`)
var capitalsRe = regexp.MustCompile("([A-Z])")
var dashSpaceRe = regexp.MustCompile(`[-\s]+`)
var dashesRe = regexp.MustCompile("-+")
var isAlphaNumericRe = regexp.MustCompile(`[^0-9a-z\xC0-\xFF]`)
var isAlphaRe = regexp.MustCompile(`[^a-z\xC0-\xFF]`)
var nWhitespaceRe = regexp.MustCompile(`\s+`)
var notDigitsRe = regexp.MustCompile(`[^0-9]`)
var slugifyRe = regexp.MustCompile(`[^\w\s\-]`)
var spaceUnderscoreRe = regexp.MustCompile("[_\\s]+")
var spacesRe = regexp.MustCompile("[\\s\\xA0]+")
var stripPuncRe = regexp.MustCompile(`[^\w\s]|_`)
var templateRe = regexp.MustCompile(`([\-\[\]()*\s])`)
var templateRe2 = regexp.MustCompile(`\$`)
var underscoreRe = regexp.MustCompile(`([a-z\d])([A-Z]+)`)
var whitespaceRe = regexp.MustCompile(`^[\s\xa0]*$`)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// Between extracts a string between left and right strings.
func Between(s, left, right string) string {
l := len(left)
startPos := strings.Index(s, left)
if startPos < 0 {
return ""
}
endPos := IndexOf(s, right, startPos+l)
//log.Printf("%s: left %s right %s start %d end %d", s, left, right, startPos+l, endPos)
if endPos < 0 {
return ""
} else if right == "" {
return s[endPos:]
} else {
return s[startPos+l : endPos]
}
}
// BetweenF is the filter form for Between.
func BetweenF(left, right string) func(string) string {
return func(s string) string {
return Between(s, left, right)
}
}
// Camelize return new string which removes any underscores or dashes and convert a string into camel casing.
func Camelize(s string) string {
return camelizeRe.ReplaceAllStringFunc(s, func(val string) string {
val = strings.ToUpper(val)
val = camelizeRe2.ReplaceAllString(val, "")
return val
})
}
// Capitalize uppercases the first char of s and lowercases the rest.
func Capitalize(s string) string {
return strings.ToUpper(s[0:1]) + strings.ToLower(s[1:])
}
// CharAt returns a string from the character at the specified position.
func CharAt(s string, index int) string {
l := len(s)
shortcut := index < 0 || index > l-1 || l == 0
if shortcut {
return ""
}
return s[index : index+1]
}
// CharAtF is the filter form of CharAt.
func CharAtF(index int) func(string) string {
return func(s string) string {
return CharAt(s, index)
}
}
// ChompLeft removes prefix at the start of a string.
func ChompLeft(s, prefix string) string {
if strings.HasPrefix(s, prefix) {
return s[len(prefix):]
}
return s
}
// ChompLeftF is the filter form of ChompLeft.
func ChompLeftF(prefix string) func(string) string {
return func(s string) string {
return ChompLeft(s, prefix)
}
}
// ChompRight removes suffix from end of s.
func ChompRight(s, suffix string) string {
if strings.HasSuffix(s, suffix) {
return s[:len(s)-len(suffix)]
}
return s
}
// ChompRightF is the filter form of ChompRight.
func ChompRightF(suffix string) func(string) string {
return func(s string) string {
return ChompRight(s, suffix)
}
}
// Classify returns a camelized string with the first letter upper cased.
func Classify(s string) string {
return Camelize("-" + s)
}
// ClassifyF is the filter form of Classify.
func ClassifyF(s string) func(string) string {
return func(s string) string {
return Classify(s)
}
}
// Clean compresses all adjacent whitespace to a single space and trims s.
func Clean(s string) string {
s = spacesRe.ReplaceAllString(s, " ")
s = beginEndSpacesRe.ReplaceAllString(s, "")
return s
}
// Dasherize converts a camel cased string into a string delimited by dashes.
func Dasherize(s string) string {
s = strings.TrimSpace(s)
s = spaceUnderscoreRe.ReplaceAllString(s, "-")
s = capitalsRe.ReplaceAllString(s, "-$1")
s = dashesRe.ReplaceAllString(s, "-")
s = strings.ToLower(s)
return s
}
// EscapeHTML is alias for html.EscapeString.
func EscapeHTML(s string) string {
if Verbose {
fmt.Println("Use html.EscapeString instead of EscapeHTML")
}
return html.EscapeString(s)
}
// DecodeHTMLEntities decodes HTML entities into their proper string representation.
// DecodeHTMLEntities is an alias for html.UnescapeString
func DecodeHTMLEntities(s string) string {
if Verbose {
fmt.Println("Use html.UnescapeString instead of DecodeHTMLEntities")
}
return html.UnescapeString(s)
}
// EnsurePrefix ensures s starts with prefix.
func EnsurePrefix(s, prefix string) string {
if strings.HasPrefix(s, prefix) {
return s
}
return prefix + s
}
// EnsurePrefixF is the filter form of EnsurePrefix.
func EnsurePrefixF(prefix string) func(string) string {
return func(s string) string {
return EnsurePrefix(s, prefix)
}
}
// EnsureSuffix ensures s ends with suffix.
func EnsureSuffix(s, suffix string) string {
if strings.HasSuffix(s, suffix) {
return s
}
return s + suffix
}
// EnsureSuffixF is the filter form of EnsureSuffix.
func EnsureSuffixF(suffix string) func(string) string {
return func(s string) string {
return EnsureSuffix(s, suffix)
}
}
// Humanize transforms s into a human friendly form.
func Humanize(s string) string {
if s == "" {
return s
}
s = Underscore(s)
var humanizeRe = regexp.MustCompile(`_id$`)
s = humanizeRe.ReplaceAllString(s, "")
s = strings.Replace(s, "_", " ", -1)
s = strings.TrimSpace(s)
s = Capitalize(s)
return s
}
// Iif is short for immediate if. If condition is true return truthy else falsey.
func Iif(condition bool, truthy string, falsey string) string {
if condition {
return truthy
}
return falsey
}
// IndexOf finds the index of needle in s starting from start.
func IndexOf(s string, needle string, start int) int {
l := len(s)
if needle == "" {
if start < 0 {
return 0
} else if start < l {
return start
} else {
return l
}
}
if start < 0 || start > l-1 {
return -1
}
pos := strings.Index(s[start:], needle)
if pos == -1 {
return -1
}
return start + pos
}
// IsAlpha returns true if a string contains only letters from ASCII (a-z,A-Z). Other letters from other languages are not supported.
func IsAlpha(s string) bool {
return !isAlphaRe.MatchString(strings.ToLower(s))
}
// IsAlphaNumeric returns true if a string contains letters and digits.
func IsAlphaNumeric(s string) bool {
return !isAlphaNumericRe.MatchString(strings.ToLower(s))
}
// IsLower returns true if s comprised of all lower case characters.
func IsLower(s string) bool {
return IsAlpha(s) && s == strings.ToLower(s)
}
// IsNumeric returns true if a string contains only digits from 0-9. Other digits not in Latin (such as Arabic) are not currently supported.
func IsNumeric(s string) bool {
return !notDigitsRe.MatchString(s)
}
// IsUpper returns true if s contains all upper case chracters.
func IsUpper(s string) bool {
return IsAlpha(s) && s == strings.ToUpper(s)
}
// IsEmpty returns true if the string is solely composed of whitespace.
func IsEmpty(s string) bool {
if s == "" {
return true
}
return whitespaceRe.MatchString(s)
}
// Left returns the left substring of length n.
func Left(s string, n int) string {
if n < 0 {
return Right(s, -n)
}
return Substr(s, 0, n)
}
// LeftF is the filter form of Left.
func LeftF(n int) func(string) string {
return func(s string) string {
return Left(s, n)
}
}
// LeftOf returns the substring left of needle.
func LeftOf(s string, needle string) string {
return Between(s, "", needle)
}
// Letters returns an array of runes as strings so it can be indexed into.
func Letters(s string) []string {
result := []string{}
for _, r := range s {
result = append(result, string(r))
}
return result
}
// Lines convert windows newlines to unix newlines then convert to an Array of lines.
func Lines(s string) []string {
s = strings.Replace(s, "\r\n", "\n", -1)
return strings.Split(s, "\n")
}
// Map maps an array's iitem through an iterator.
func Map(arr []string, iterator func(string) string) []string {
r := []string{}
for _, item := range arr {
r = append(r, iterator(item))
}
return r
}
// Match returns true if patterns matches the string
func Match(s, pattern string) bool {
r := regexp.MustCompile(pattern)
return r.MatchString(s)
}
+534
View File
File diff suppressed because it is too large. Load diff
+5
View File
@@ -0,0 +1,5 @@
/.test
/otto/otto
/otto/otto-*
/test/test-*.js
/test/tester
+1
View File
@@ -0,0 +1 @@
* Designate the filename of "anonymous" source code by the hash (md5/sha1, etc.)
+7
View File
@@ -0,0 +1,7 @@
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.
+63
View File
@@ -0,0 +1,63 @@
.PHONY: test test-race test-release release release-check test-262
.PHONY: parser
.PHONY: otto assets underscore
TESTS := \
~
TEST := -v --run
TEST := -v
TEST := -v --run Test\($(subst $(eval) ,\|,$(TESTS))\)
TEST := .
test: parser inline.go
go test -i
go test $(TEST)
@echo PASS
parser:
$(MAKE) -C parser
inline.go: inline.pl
./$< > $@
#################
# release, test #
#################
release: test-race test-release
for package in . parser token ast file underscore registry; do (cd $$package && godocdown --signature > README.markdown); done
@echo \*\*\* make release-check
@echo PASS
release-check: .test
$(MAKE) -C test build test
$(MAKE) -C .test/test262 build test
@echo PASS
test-262: .test
$(MAKE) -C .test/test262 build test
@echo PASS
test-release:
go test -i
go test
test-race:
go test -race -i
go test -race
#################################
# otto, assets, underscore, ... #
#################################
otto:
$(MAKE) -C otto
assets:
mkdir -p .assets
for file in underscore/test/*.js; do tr "\`" "_" < $$file > .assets/`basename $$file`; done
underscore:
$(MAKE) -C $@
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+278
View File
@@ -0,0 +1,278 @@
package ast
import (
"fmt"
"github.com/robertkrimen/otto/file"
)
// CommentPosition determines where the comment is in a given context
type CommentPosition int
const (
_ CommentPosition = iota
LEADING // Before the pertinent expression
TRAILING // After the pertinent expression
KEY // Before a key in an object
COLON // After a colon in a field declaration
FINAL // Final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal
IF // After an if keyword
WHILE // After a while keyword
DO // After do keyword
FOR // After a for keyword
WITH // After a with keyword
TBD
)
// Comment contains the data of the comment
type Comment struct {
Begin file.Idx
Text string
Position CommentPosition
}
// NewComment creates a new comment
func NewComment(text string, idx file.Idx) *Comment {
comment := &Comment{
Begin: idx,
Text: text,
Position: TBD,
}
return comment
}
// String returns a stringified version of the position
func (cp CommentPosition) String() string {
switch cp {
case LEADING:
return "Leading"
case TRAILING:
return "Trailing"
case KEY:
return "Key"
case COLON:
return "Colon"
case FINAL:
return "Final"
case IF:
return "If"
case WHILE:
return "While"
case DO:
return "Do"
case FOR:
return "For"
case WITH:
return "With"
default:
return "???"
}
}
// String returns a stringified version of the comment
func (c Comment) String() string {
return fmt.Sprintf("Comment: %v", c.Text)
}
// Comments defines the current view of comments from the parser
type Comments struct {
// CommentMap is a reference to the parser comment map
CommentMap CommentMap
// Comments lists the comments scanned, not linked to a node yet
Comments []*Comment
// future lists the comments after a line break during a sequence of comments
future []*Comment
// Current is node for which comments are linked to
Current Expression
// wasLineBreak determines if a line break occured while scanning for comments
wasLineBreak bool
// primary determines whether or not processing a primary expression
primary bool
// afterBlock determines whether or not being after a block statement
afterBlock bool
}
func NewComments() *Comments {
comments := &Comments{
CommentMap: CommentMap{},
}
return comments
}
func (c *Comments) String() string {
return fmt.Sprintf("NODE: %v, Comments: %v, Future: %v(LINEBREAK:%v)", c.Current, len(c.Comments), len(c.future), c.wasLineBreak)
}
// FetchAll returns all the currently scanned comments,
// including those from the next line
func (c *Comments) FetchAll() []*Comment {
defer func() {
c.Comments = nil
c.future = nil
}()
return append(c.Comments, c.future...)
}
// Fetch returns all the currently scanned comments
func (c *Comments) Fetch() []*Comment {
defer func() {
c.Comments = nil
}()
return c.Comments
}
// ResetLineBreak marks the beginning of a new statement
func (c *Comments) ResetLineBreak() {
c.wasLineBreak = false
}
// MarkPrimary will mark the context as processing a primary expression
func (c *Comments) MarkPrimary() {
c.primary = true
c.wasLineBreak = false
}
// AfterBlock will mark the context as being after a block.
func (c *Comments) AfterBlock() {
c.afterBlock = true
}
// AddComment adds a comment to the view.
// Depending on the context, comments are added normally or as post line break.
func (c *Comments) AddComment(comment *Comment) {
if c.primary {
if !c.wasLineBreak {
c.Comments = append(c.Comments, comment)
} else {
c.future = append(c.future, comment)
}
} else {
if !c.wasLineBreak || (c.Current == nil && !c.afterBlock) {
c.Comments = append(c.Comments, comment)
} else {
c.future = append(c.future, comment)
}
}
}
// MarkComments will mark the found comments as the given position.
func (c *Comments) MarkComments(position CommentPosition) {
for _, comment := range c.Comments {
if comment.Position == TBD {
comment.Position = position
}
}
for _, c := range c.future {
if c.Position == TBD {
c.Position = position
}
}
}
// Unset the current node and apply the comments to the current expression.
// Resets context variables.
func (c *Comments) Unset() {
if c.Current != nil {
c.applyComments(c.Current, c.Current, TRAILING)
c.Current = nil
}
c.wasLineBreak = false
c.primary = false
c.afterBlock = false
}
// SetExpression sets the current expression.
// It is applied the found comments, unless the previous expression has not been unset.
// It is skipped if the node is already set or if it is a part of the previous node.
func (c *Comments) SetExpression(node Expression) {
// Skipping same node
if c.Current == node {
return
}
if c.Current != nil && c.Current.Idx1() == node.Idx1() {
c.Current = node
return
}
previous := c.Current
c.Current = node
// Apply the found comments and futures to the node and the previous.
c.applyComments(node, previous, TRAILING)
}
// PostProcessNode applies all found comments to the given node
func (c *Comments) PostProcessNode(node Node) {
c.applyComments(node, nil, TRAILING)
}
// applyComments applies both the comments and the future comments to the given node and the previous one,
// based on the context.
func (c *Comments) applyComments(node, previous Node, position CommentPosition) {
if previous != nil {
c.CommentMap.AddComments(previous, c.Comments, position)
c.Comments = nil
} else {
c.CommentMap.AddComments(node, c.Comments, position)
c.Comments = nil
}
// Only apply the future comments to the node if the previous is set.
// This is for detecting end of line comments and which node comments on the following lines belongs to
if previous != nil {
c.CommentMap.AddComments(node, c.future, position)
c.future = nil
}
}
// AtLineBreak will mark a line break
func (c *Comments) AtLineBreak() {
c.wasLineBreak = true
}
// CommentMap is the data structure where all found comments are stored
type CommentMap map[Node][]*Comment
// AddComment adds a single comment to the map
func (cm CommentMap) AddComment(node Node, comment *Comment) {
list := cm[node]
list = append(list, comment)
cm[node] = list
}
// AddComments adds a slice of comments, given a node and an updated position
func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) {
for _, comment := range comments {
if comment.Position == TBD {
comment.Position = position
}
cm.AddComment(node, comment)
}
}
// Size returns the size of the map
func (cm CommentMap) Size() int {
size := 0
for _, comments := range cm {
size += len(comments)
}
return size
}
// MoveComments moves comments with a given position from a node to another
func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) {
for i, c := range cm[from] {
if c.Position == position {
cm.AddComment(to, c)
// Remove the comment from the "from" slice
cm[from][i] = cm[from][len(cm[from])-1]
cm[from][len(cm[from])-1] = nil
cm[from] = cm[from][:len(cm[from])-1]
}
}
}
+515
View File
File diff suppressed because it is too large. Load diff
+217
View File
@@ -0,0 +1,217 @@
package ast
import "fmt"
// Visitor Enter method is invoked for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children
// of node with the visitor v, followed by a call of the Exit method.
type Visitor interface {
Enter(n Node) (v Visitor)
Exit(n Node)
}
// Walk traverses an AST in depth-first order: It starts by calling
// v.Enter(node); node must not be nil. If the visitor v returned by
// v.Enter(node) is not nil, Walk is invoked recursively with visitor
// v for each of the non-nil children of node, followed by a call
// of v.Exit(node).
func Walk(v Visitor, n Node) {
if n == nil {
return
}
if v = v.Enter(n); v == nil {
return
}
defer v.Exit(n)
switch n := n.(type) {
case *ArrayLiteral:
if n != nil {
for _, ex := range n.Value {
Walk(v, ex)
}
}
case *AssignExpression:
if n != nil {
Walk(v, n.Left)
Walk(v, n.Right)
}
case *BadExpression:
case *BinaryExpression:
if n != nil {
Walk(v, n.Left)
Walk(v, n.Right)
}
case *BlockStatement:
if n != nil {
for _, s := range n.List {
Walk(v, s)
}
}
case *BooleanLiteral:
case *BracketExpression:
if n != nil {
Walk(v, n.Left)
Walk(v, n.Member)
}
case *BranchStatement:
if n != nil {
Walk(v, n.Label)
}
case *CallExpression:
if n != nil {
Walk(v, n.Callee)
for _, a := range n.ArgumentList {
Walk(v, a)
}
}
case *CaseStatement:
if n != nil {
Walk(v, n.Test)
for _, c := range n.Consequent {
Walk(v, c)
}
}
case *CatchStatement:
if n != nil {
Walk(v, n.Parameter)
Walk(v, n.Body)
}
case *ConditionalExpression:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Consequent)
Walk(v, n.Alternate)
}
case *DebuggerStatement:
case *DoWhileStatement:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Body)
}
case *DotExpression:
if n != nil {
Walk(v, n.Left)
}
case *EmptyExpression:
case *EmptyStatement:
case *ExpressionStatement:
if n != nil {
Walk(v, n.Expression)
}
case *ForInStatement:
if n != nil {
Walk(v, n.Into)
Walk(v, n.Source)
Walk(v, n.Body)
}
case *ForStatement:
if n != nil {
Walk(v, n.Initializer)
Walk(v, n.Update)
Walk(v, n.Test)
Walk(v, n.Body)
}
case *FunctionLiteral:
if n != nil {
Walk(v, n.Name)
for _, p := range n.ParameterList.List {
Walk(v, p)
}
Walk(v, n.Body)
}
case *FunctionStatement:
if n != nil {
Walk(v, n.Function)
}
case *Identifier:
case *IfStatement:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Consequent)
Walk(v, n.Alternate)
}
case *LabelledStatement:
if n != nil {
Walk(v, n.Statement)
}
case *NewExpression:
if n != nil {
Walk(v, n.Callee)
for _, a := range n.ArgumentList {
Walk(v, a)
}
}
case *NullLiteral:
case *NumberLiteral:
case *ObjectLiteral:
if n != nil {
for _, p := range n.Value {
Walk(v, p.Value)
}
}
case *Program:
if n != nil {
for _, b := range n.Body {
Walk(v, b)
}
}
case *RegExpLiteral:
case *ReturnStatement:
if n != nil {
Walk(v, n.Argument)
}
case *SequenceExpression:
if n != nil {
for _, e := range n.Sequence {
Walk(v, e)
}
}
case *StringLiteral:
case *SwitchStatement:
if n != nil {
Walk(v, n.Discriminant)
for _, c := range n.Body {
Walk(v, c)
}
}
case *ThisExpression:
case *ThrowStatement:
if n != nil {
Walk(v, n.Argument)
}
case *TryStatement:
if n != nil {
Walk(v, n.Body)
Walk(v, n.Catch)
Walk(v, n.Finally)
}
case *UnaryExpression:
if n != nil {
Walk(v, n.Operand)
}
case *VariableExpression:
if n != nil {
Walk(v, n.Initializer)
}
case *VariableStatement:
if n != nil {
for _, e := range n.List {
Walk(v, e)
}
}
case *WhileStatement:
if n != nil {
Walk(v, n.Test)
Walk(v, n.Body)
}
case *WithStatement:
if n != nil {
Walk(v, n.Object)
Walk(v, n.Body)
}
default:
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
}
}
+354
View File
@@ -0,0 +1,354 @@
package otto
import (
"encoding/hex"
"math"
"net/url"
"regexp"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
)
// Global
func builtinGlobal_eval(call FunctionCall) Value {
src := call.Argument(0)
if !src.IsString() {
return src
}
runtime := call.runtime
program := runtime.cmpl_parseOrThrow(src.string(), nil)
if !call.eval {
// Not a direct call to eval, so we enter the global ExecutionContext
runtime.enterGlobalScope()
defer runtime.leaveScope()
}
returnValue := runtime.cmpl_evaluate_nodeProgram(program, true)
if returnValue.isEmpty() {
return Value{}
}
return returnValue
}
func builtinGlobal_isNaN(call FunctionCall) Value {
value := call.Argument(0).float64()
return toValue_bool(math.IsNaN(value))
}
func builtinGlobal_isFinite(call FunctionCall) Value {
value := call.Argument(0).float64()
return toValue_bool(!math.IsNaN(value) && !math.IsInf(value, 0))
}
// radix 3 => 2 (ASCII 50) +47
// radix 11 => A/a (ASCII 65/97) +54/+86
var parseInt_alphabetTable = func() []string {
table := []string{"", "", "01"}
for radix := 3; radix <= 36; radix += 1 {
alphabet := table[radix-1]
if radix <= 10 {
alphabet += string(radix + 47)
} else {
alphabet += string(radix+54) + string(radix+86)
}
table = append(table, alphabet)
}
return table
}()
func digitValue(chr rune) int {
switch {
case '0' <= chr && chr <= '9':
return int(chr - '0')
case 'a' <= chr && chr <= 'z':
return int(chr - 'a' + 10)
case 'A' <= chr && chr <= 'Z':
return int(chr - 'A' + 10)
}
return 36 // Larger than any legal digit value
}
func builtinGlobal_parseInt(call FunctionCall) Value {
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
if len(input) == 0 {
return NaNValue()
}
radix := int(toInt32(call.Argument(1)))
negative := false
switch input[0] {
case '+':
input = input[1:]
case '-':
negative = true
input = input[1:]
}
strip := true
if radix == 0 {
radix = 10
} else {
if radix < 2 || radix > 36 {
return NaNValue()
} else if radix != 16 {
strip = false
}
}
switch len(input) {
case 0:
return NaNValue()
case 1:
default:
if strip {
if input[0] == '0' && (input[1] == 'x' || input[1] == 'X') {
input = input[2:]
radix = 16
}
}
}
base := radix
index := 0
for ; index < len(input); index++ {
digit := digitValue(rune(input[index])) // If not ASCII, then an error anyway
if digit >= base {
break
}
}
input = input[0:index]
value, err := strconv.ParseInt(input, radix, 64)
if err != nil {
if err.(*strconv.NumError).Err == strconv.ErrRange {
base := float64(base)
// Could just be a very large number (e.g. 0x8000000000000000)
var value float64
for _, chr := range input {
digit := float64(digitValue(chr))
if digit >= base {
goto error
}
value = value*base + digit
}
if negative {
value *= -1
}
return toValue_float64(value)
}
error:
return NaNValue()
}
if negative {
value *= -1
}
return toValue_int64(value)
}
var parseFloat_matchBadSpecial = regexp.MustCompile(`[\+\-]?(?:[Ii]nf$|infinity)`)
var parseFloat_matchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`)
func builtinGlobal_parseFloat(call FunctionCall) Value {
// Caveat emptor: This implementation does NOT match the specification
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
if parseFloat_matchBadSpecial.MatchString(input) {
return NaNValue()
}
value, err := strconv.ParseFloat(input, 64)
if err != nil {
for end := len(input); end > 0; end-- {
input := input[0:end]
if !parseFloat_matchValid.MatchString(input) {
return NaNValue()
}
value, err = strconv.ParseFloat(input, 64)
if err == nil {
break
}
}
if err != nil {
return NaNValue()
}
}
return toValue_float64(value)
}
// encodeURI/decodeURI
func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
value := call.Argument(0)
var input []uint16
switch vl := value.value.(type) {
case []uint16:
input = vl
default:
input = utf16.Encode([]rune(value.string()))
}
if len(input) == 0 {
return toValue_string("")
}
output := []byte{}
length := len(input)
encode := make([]byte, 4)
for index := 0; index < length; {
value := input[index]
decode := utf16.Decode(input[index : index+1])
if value >= 0xDC00 && value <= 0xDFFF {
panic(call.runtime.panicURIError("URI malformed"))
}
if value >= 0xD800 && value <= 0xDBFF {
index += 1
if index >= length {
panic(call.runtime.panicURIError("URI malformed"))
}
// input = ..., value, value1, ...
value1 := input[index]
if value1 < 0xDC00 || value1 > 0xDFFF {
panic(call.runtime.panicURIError("URI malformed"))
}
decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000}
}
index += 1
size := utf8.EncodeRune(encode, decode[0])
encode := encode[0:size]
output = append(output, encode...)
}
{
value := escape.ReplaceAllFunc(output, func(target []byte) []byte {
// Probably a better way of doing this
if target[0] == ' ' {
return []byte("%20")
}
return []byte(url.QueryEscape(string(target)))
})
return toValue_string(string(value))
}
}
var encodeURI_Regexp = regexp.MustCompile(`([^~!@#$&*()=:/,;?+'])`)
func builtinGlobal_encodeURI(call FunctionCall) Value {
return _builtinGlobal_encodeURI(call, encodeURI_Regexp)
}
var encodeURIComponent_Regexp = regexp.MustCompile(`([^~!*()'])`)
func builtinGlobal_encodeURIComponent(call FunctionCall) Value {
return _builtinGlobal_encodeURI(call, encodeURIComponent_Regexp)
}
// 3B/2F/3F/3A/40/26/3D/2B/24/2C/23
var decodeURI_guard = regexp.MustCompile(`(?i)(?:%)(3B|2F|3F|3A|40|26|3D|2B|24|2C|23)`)
func _decodeURI(input string, reserve bool) (string, bool) {
if reserve {
input = decodeURI_guard.ReplaceAllString(input, "%25$1")
}
input = strings.Replace(input, "+", "%2B", -1) // Ugly hack to make QueryUnescape work with our use case
output, err := url.QueryUnescape(input)
if err != nil || !utf8.ValidString(output) {
return "", true
}
return output, false
}
func builtinGlobal_decodeURI(call FunctionCall) Value {
output, err := _decodeURI(call.Argument(0).string(), true)
if err {
panic(call.runtime.panicURIError("URI malformed"))
}
return toValue_string(output)
}
func builtinGlobal_decodeURIComponent(call FunctionCall) Value {
output, err := _decodeURI(call.Argument(0).string(), false)
if err {
panic(call.runtime.panicURIError("URI malformed"))
}
return toValue_string(output)
}
// escape/unescape
func builtin_shouldEscape(chr byte) bool {
if 'A' <= chr && chr <= 'Z' || 'a' <= chr && chr <= 'z' || '0' <= chr && chr <= '9' {
return false
}
return !strings.ContainsRune("*_+-./", rune(chr))
}
const escapeBase16 = "0123456789ABCDEF"
func builtin_escape(input string) string {
output := make([]byte, 0, len(input))
length := len(input)
for index := 0; index < length; {
if builtin_shouldEscape(input[index]) {
chr, width := utf8.DecodeRuneInString(input[index:])
chr16 := utf16.Encode([]rune{chr})[0]
if 256 > chr16 {
output = append(output, '%',
escapeBase16[chr16>>4],
escapeBase16[chr16&15],
)
} else {
output = append(output, '%', 'u',
escapeBase16[chr16>>12],
escapeBase16[(chr16>>8)&15],
escapeBase16[(chr16>>4)&15],
escapeBase16[chr16&15],
)
}
index += width
} else {
output = append(output, input[index])
index += 1
}
}
return string(output)
}
func builtin_unescape(input string) string {
output := make([]rune, 0, len(input))
length := len(input)
for index := 0; index < length; {
if input[index] == '%' {
if index <= length-6 && input[index+1] == 'u' {
byte16, err := hex.DecodeString(input[index+2 : index+6])
if err == nil {
value := uint16(byte16[0])<<8 + uint16(byte16[1])
chr := utf16.Decode([]uint16{value})[0]
output = append(output, chr)
index += 6
continue
}
}
if index <= length-3 {
byte8, err := hex.DecodeString(input[index+1 : index+3])
if err == nil {
value := uint16(byte8[0])
chr := utf16.Decode([]uint16{value})[0]
output = append(output, chr)
index += 3
continue
}
}
}
output = append(output, rune(input[index]))
index += 1
}
return string(output)
}
func builtinGlobal_escape(call FunctionCall) Value {
return toValue_string(builtin_escape(call.Argument(0).string()))
}
func builtinGlobal_unescape(call FunctionCall) Value {
return toValue_string(builtin_unescape(call.Argument(0).string()))
}
File diff suppressed because it is too large. Load diff
+28
View File
@@ -0,0 +1,28 @@
package otto
// Boolean
func builtinBoolean(call FunctionCall) Value {
return toValue_bool(call.Argument(0).bool())
}
func builtinNewBoolean(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newBoolean(valueOfArrayIndex(argumentList, 0)))
}
func builtinBoolean_toString(call FunctionCall) Value {
value := call.This
if !value.IsBoolean() {
// Will throw a TypeError if ThisObject is not a Boolean
value = call.thisClassObject("Boolean").primitiveValue()
}
return toValue_string(value.string())
}
func builtinBoolean_valueOf(call FunctionCall) Value {
value := call.This
if !value.IsBoolean() {
value = call.thisClassObject("Boolean").primitiveValue()
}
return value
}
File diff suppressed because it is too large. Load diff
+126
View File
@@ -0,0 +1,126 @@
package otto
import (
"fmt"
)
func builtinError(call FunctionCall) Value {
return toValue_object(call.runtime.newError("Error", call.Argument(0), 1))
}
func builtinNewError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newError("Error", valueOfArrayIndex(argumentList, 0), 0))
}
func builtinError_toString(call FunctionCall) Value {
thisObject := call.thisObject()
if thisObject == nil {
panic(call.runtime.panicTypeError())
}
name := "Error"
nameValue := thisObject.get("name")
if nameValue.IsDefined() {
name = nameValue.string()
}
message := ""
messageValue := thisObject.get("message")
if messageValue.IsDefined() {
message = messageValue.string()
}
if len(name) == 0 {
return toValue_string(message)
}
if len(message) == 0 {
return toValue_string(name)
}
return toValue_string(fmt.Sprintf("%s: %s", name, message))
}
func (runtime *_runtime) newEvalError(message Value) *_object {
self := runtime.newErrorObject("EvalError", message, 0)
self.prototype = runtime.global.EvalErrorPrototype
return self
}
func builtinEvalError(call FunctionCall) Value {
return toValue_object(call.runtime.newEvalError(call.Argument(0)))
}
func builtinNewEvalError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newEvalError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newTypeError(message Value) *_object {
self := runtime.newErrorObject("TypeError", message, 0)
self.prototype = runtime.global.TypeErrorPrototype
return self
}
func builtinTypeError(call FunctionCall) Value {
return toValue_object(call.runtime.newTypeError(call.Argument(0)))
}
func builtinNewTypeError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newTypeError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newRangeError(message Value) *_object {
self := runtime.newErrorObject("RangeError", message, 0)
self.prototype = runtime.global.RangeErrorPrototype
return self
}
func builtinRangeError(call FunctionCall) Value {
return toValue_object(call.runtime.newRangeError(call.Argument(0)))
}
func builtinNewRangeError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newRangeError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newURIError(message Value) *_object {
self := runtime.newErrorObject("URIError", message, 0)
self.prototype = runtime.global.URIErrorPrototype
return self
}
func (runtime *_runtime) newReferenceError(message Value) *_object {
self := runtime.newErrorObject("ReferenceError", message, 0)
self.prototype = runtime.global.ReferenceErrorPrototype
return self
}
func builtinReferenceError(call FunctionCall) Value {
return toValue_object(call.runtime.newReferenceError(call.Argument(0)))
}
func builtinNewReferenceError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newReferenceError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newSyntaxError(message Value) *_object {
self := runtime.newErrorObject("SyntaxError", message, 0)
self.prototype = runtime.global.SyntaxErrorPrototype
return self
}
func builtinSyntaxError(call FunctionCall) Value {
return toValue_object(call.runtime.newSyntaxError(call.Argument(0)))
}
func builtinNewSyntaxError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newSyntaxError(valueOfArrayIndex(argumentList, 0)))
}
func builtinURIError(call FunctionCall) Value {
return toValue_object(call.runtime.newURIError(call.Argument(0)))
}
func builtinNewURIError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newURIError(valueOfArrayIndex(argumentList, 0)))
}
+129
View File
@@ -0,0 +1,129 @@
package otto
import (
"fmt"
"regexp"
"strings"
"unicode"
"github.com/robertkrimen/otto/parser"
)
// Function
func builtinFunction(call FunctionCall) Value {
return toValue_object(builtinNewFunctionNative(call.runtime, call.ArgumentList))
}
func builtinNewFunction(self *_object, argumentList []Value) Value {
return toValue_object(builtinNewFunctionNative(self.runtime, argumentList))
}
func argumentList2parameterList(argumentList []Value) []string {
parameterList := make([]string, 0, len(argumentList))
for _, value := range argumentList {
tmp := strings.FieldsFunc(value.string(), func(chr rune) bool {
return chr == ',' || unicode.IsSpace(chr)
})
parameterList = append(parameterList, tmp...)
}
return parameterList
}
var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`)
func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object {
var parameterList, body string
count := len(argumentList)
if count > 0 {
tmp := make([]string, 0, count-1)
for _, value := range argumentList[0 : count-1] {
tmp = append(tmp, value.string())
}
parameterList = strings.Join(tmp, ",")
body = argumentList[count-1].string()
}
// FIXME
function, err := parser.ParseFunction(parameterList, body)
runtime.parseThrow(err) // Will panic/throw appropriately
cmpl := _compiler{}
cmpl_function := cmpl.parseExpression(function)
return runtime.newNodeFunction(cmpl_function.(*_nodeFunctionLiteral), runtime.globalStash)
}
func builtinFunction_toString(call FunctionCall) Value {
object := call.thisClassObject("Function") // Should throw a TypeError unless Function
switch fn := object.value.(type) {
case _nativeFunctionObject:
return toValue_string(fmt.Sprintf("function %s() { [native code] }", fn.name))
case _nodeFunctionObject:
return toValue_string(fn.node.source)
case _bindFunctionObject:
return toValue_string("function () { [native code] }")
}
panic(call.runtime.panicTypeError("Function.toString()"))
}
func builtinFunction_apply(call FunctionCall) Value {
if !call.This.isCallable() {
panic(call.runtime.panicTypeError())
}
this := call.Argument(0)
if this.IsUndefined() {
// FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject)
}
argumentList := call.Argument(1)
switch argumentList.kind {
case valueUndefined, valueNull:
return call.thisObject().call(this, nil, false, nativeFrame)
case valueObject:
default:
panic(call.runtime.panicTypeError())
}
arrayObject := argumentList._object()
thisObject := call.thisObject()
length := int64(toUint32(arrayObject.get("length")))
valueArray := make([]Value, length)
for index := int64(0); index < length; index++ {
valueArray[index] = arrayObject.get(arrayIndexToString(index))
}
return thisObject.call(this, valueArray, false, nativeFrame)
}
func builtinFunction_call(call FunctionCall) Value {
if !call.This.isCallable() {
panic(call.runtime.panicTypeError())
}
thisObject := call.thisObject()
this := call.Argument(0)
if this.IsUndefined() {
// FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject)
}
if len(call.ArgumentList) >= 1 {
return thisObject.call(this, call.ArgumentList[1:], false, nativeFrame)
}
return thisObject.call(this, nil, false, nativeFrame)
}
func builtinFunction_bind(call FunctionCall) Value {
target := call.This
if !target.isCallable() {
panic(call.runtime.panicTypeError())
}
targetObject := target._object()
this := call.Argument(0)
argumentList := call.slice(1)
if this.IsUndefined() {
// FIXME Do this elsewhere?
this = toValue_object(call.runtime.globalObject)
}
return toValue_object(call.runtime.newBoundFunction(targetObject, this, argumentList))
}
+299
View File
@@ -0,0 +1,299 @@
package otto
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
type _builtinJSON_parseContext struct {
call FunctionCall
reviver Value
}
func builtinJSON_parse(call FunctionCall) Value {
ctx := _builtinJSON_parseContext{
call: call,
}
revive := false
if reviver := call.Argument(1); reviver.isCallable() {
revive = true
ctx.reviver = reviver
}
var root interface{}
err := json.Unmarshal([]byte(call.Argument(0).string()), &root)
if err != nil {
panic(call.runtime.panicSyntaxError(err.Error()))
}
value, exists := builtinJSON_parseWalk(ctx, root)
if !exists {
value = Value{}
}
if revive {
root := ctx.call.runtime.newObject()
root.put("", value, false)
return builtinJSON_reviveWalk(ctx, root, "")
}
return value
}
func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name string) Value {
value := holder.get(name)
if object := value._object(); object != nil {
if isArray(object) {
length := int64(objectLength(object))
for index := int64(0); index < length; index += 1 {
name := arrayIndexToString(index)
value := builtinJSON_reviveWalk(ctx, object, name)
if value.IsUndefined() {
object.delete(name, false)
} else {
object.defineProperty(name, value, 0111, false)
}
}
} else {
object.enumerate(false, func(name string) bool {
value := builtinJSON_reviveWalk(ctx, object, name)
if value.IsUndefined() {
object.delete(name, false)
} else {
object.defineProperty(name, value, 0111, false)
}
return true
})
}
}
return ctx.reviver.call(ctx.call.runtime, toValue_object(holder), name, value)
}
func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) {
switch value := rawValue.(type) {
case nil:
return nullValue, true
case bool:
return toValue_bool(value), true
case string:
return toValue_string(value), true
case float64:
return toValue_float64(value), true
case []interface{}:
arrayValue := make([]Value, len(value))
for index, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
arrayValue[index] = value
}
}
return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true
case map[string]interface{}:
object := ctx.call.runtime.newObject()
for name, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
object.put(name, value, false)
}
}
return toValue_object(object), true
}
return Value{}, false
}
type _builtinJSON_stringifyContext struct {
call FunctionCall
stack []*_object
propertyList []string
replacerFunction *Value
gap string
}
func builtinJSON_stringify(call FunctionCall) Value {
ctx := _builtinJSON_stringifyContext{
call: call,
stack: []*_object{nil},
}
replacer := call.Argument(1)._object()
if replacer != nil {
if isArray(replacer) {
length := objectLength(replacer)
seen := map[string]bool{}
propertyList := make([]string, length)
length = 0
for index, _ := range propertyList {
value := replacer.get(arrayIndexToString(int64(index)))
switch value.kind {
case valueObject:
switch value.value.(*_object).class {
case "String":
case "Number":
default:
continue
}
case valueString:
case valueNumber:
default:
continue
}
name := value.string()
if seen[name] {
continue
}
seen[name] = true
length += 1
propertyList[index] = name
}
ctx.propertyList = propertyList[0:length]
} else if replacer.class == "Function" {
value := toValue_object(replacer)
ctx.replacerFunction = &value
}
}
if spaceValue, exists := call.getArgument(2); exists {
if spaceValue.kind == valueObject {
switch spaceValue.value.(*_object).class {
case "String":
spaceValue = toValue_string(spaceValue.string())
case "Number":
spaceValue = spaceValue.numberValue()
}
}
switch spaceValue.kind {
case valueString:
value := spaceValue.string()
if len(value) > 10 {
ctx.gap = value[0:10]
} else {
ctx.gap = value
}
case valueNumber:
value := spaceValue.number().int64
if value > 10 {
value = 10
} else if value < 0 {
value = 0
}
ctx.gap = strings.Repeat(" ", int(value))
}
}
holder := call.runtime.newObject()
holder.put("", call.Argument(0), false)
value, exists := builtinJSON_stringifyWalk(ctx, "", holder)
if !exists {
return Value{}
}
valueJSON, err := json.Marshal(value)
if err != nil {
panic(call.runtime.panicTypeError(err.Error()))
}
if ctx.gap != "" {
valueJSON1 := bytes.Buffer{}
json.Indent(&valueJSON1, valueJSON, "", ctx.gap)
valueJSON = valueJSON1.Bytes()
}
return toValue_string(string(valueJSON))
}
func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, holder *_object) (interface{}, bool) {
value := holder.get(key)
if value.IsObject() {
object := value._object()
if toJSON := object.get("toJSON"); toJSON.IsFunction() {
value = toJSON.call(ctx.call.runtime, value, key)
} else {
// If the object is a GoStruct or something that implements json.Marshaler
if object.objectClass.marshalJSON != nil {
marshaler := object.objectClass.marshalJSON(object)
if marshaler != nil {
return marshaler, true
}
}
}
}
if ctx.replacerFunction != nil {
value = (*ctx.replacerFunction).call(ctx.call.runtime, toValue_object(holder), key, value)
}
if value.kind == valueObject {
switch value.value.(*_object).class {
case "Boolean":
value = value._object().value.(Value)
case "String":
value = toValue_string(value.string())
case "Number":
value = value.numberValue()
}
}
switch value.kind {
case valueBoolean:
return value.bool(), true
case valueString:
return value.string(), true
case valueNumber:
integer := value.number()
switch integer.kind {
case numberInteger:
return integer.int64, true
case numberFloat:
return integer.float64, true
default:
return nil, true
}
case valueNull:
return nil, true
case valueObject:
holder := value._object()
if value := value._object(); nil != value {
for _, object := range ctx.stack {
if holder == object {
panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON"))
}
}
ctx.stack = append(ctx.stack, value)
defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
}
if isArray(holder) {
var length uint32
switch value := holder.get("length").value.(type) {
case uint32:
length = value
case int:
if value >= 0 {
length = uint32(value)
}
default:
panic(ctx.call.runtime.panicTypeError(fmt.Sprintf("JSON.stringify: invalid length: %v (%[1]T)", value)))
}
array := make([]interface{}, length)
for index, _ := range array {
name := arrayIndexToString(int64(index))
value, _ := builtinJSON_stringifyWalk(ctx, name, holder)
array[index] = value
}
return array, true
} else if holder.class != "Function" {
object := map[string]interface{}{}
if ctx.propertyList != nil {
for _, name := range ctx.propertyList {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
if exists {
object[name] = value
}
}
} else {
// Go maps are without order, so this doesn't conform to the ECMA ordering
// standard, but oh well...
holder.enumerate(false, func(name string) bool {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
if exists {
object[name] = value
}
return true
})
}
return object, true
}
}
return nil, false
}
+151
View File
@@ -0,0 +1,151 @@
package otto
import (
"math"
"math/rand"
)
// Math
func builtinMath_abs(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Abs(number))
}
func builtinMath_acos(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Acos(number))
}
func builtinMath_asin(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Asin(number))
}
func builtinMath_atan(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Atan(number))
}
func builtinMath_atan2(call FunctionCall) Value {
y := call.Argument(0).float64()
if math.IsNaN(y) {
return NaNValue()
}
x := call.Argument(1).float64()
if math.IsNaN(x) {
return NaNValue()
}
return toValue_float64(math.Atan2(y, x))
}
func builtinMath_cos(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Cos(number))
}
func builtinMath_ceil(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Ceil(number))
}
func builtinMath_exp(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Exp(number))
}
func builtinMath_floor(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Floor(number))
}
func builtinMath_log(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Log(number))
}
func builtinMath_max(call FunctionCall) Value {
switch len(call.ArgumentList) {
case 0:
return negativeInfinityValue()
case 1:
return toValue_float64(call.ArgumentList[0].float64())
}
result := call.ArgumentList[0].float64()
if math.IsNaN(result) {
return NaNValue()
}
for _, value := range call.ArgumentList[1:] {
value := value.float64()
if math.IsNaN(value) {
return NaNValue()
}
result = math.Max(result, value)
}
return toValue_float64(result)
}
func builtinMath_min(call FunctionCall) Value {
switch len(call.ArgumentList) {
case 0:
return positiveInfinityValue()
case 1:
return toValue_float64(call.ArgumentList[0].float64())
}
result := call.ArgumentList[0].float64()
if math.IsNaN(result) {
return NaNValue()
}
for _, value := range call.ArgumentList[1:] {
value := value.float64()
if math.IsNaN(value) {
return NaNValue()
}
result = math.Min(result, value)
}
return toValue_float64(result)
}
func builtinMath_pow(call FunctionCall) Value {
// TODO Make sure this works according to the specification (15.8.2.13)
x := call.Argument(0).float64()
y := call.Argument(1).float64()
if math.Abs(x) == 1 && math.IsInf(y, 0) {
return NaNValue()
}
return toValue_float64(math.Pow(x, y))
}
func builtinMath_random(call FunctionCall) Value {
var v float64
if call.runtime.random != nil {
v = call.runtime.random()
} else {
v = rand.Float64()
}
return toValue_float64(v)
}
func builtinMath_round(call FunctionCall) Value {
number := call.Argument(0).float64()
value := math.Floor(number + 0.5)
if value == 0 {
value = math.Copysign(0, number)
}
return toValue_float64(value)
}
func builtinMath_sin(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Sin(number))
}
func builtinMath_sqrt(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Sqrt(number))
}
func builtinMath_tan(call FunctionCall) Value {
number := call.Argument(0).float64()
return toValue_float64(math.Tan(number))
}
+100
View File
@@ -0,0 +1,100 @@
package otto
import (
"math"
"strconv"
)
// Number
func numberValueFromNumberArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 {
return argumentList[0].numberValue()
}
return toValue_int(0)
}
func builtinNumber(call FunctionCall) Value {
return numberValueFromNumberArgumentList(call.ArgumentList)
}
func builtinNewNumber(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newNumber(numberValueFromNumberArgumentList(argumentList)))
}
func builtinNumber_toString(call FunctionCall) Value {
// Will throw a TypeError if ThisObject is not a Number
value := call.thisClassObject("Number").primitiveValue()
radix := 10
radixArgument := call.Argument(0)
if radixArgument.IsDefined() {
integer := toIntegerFloat(radixArgument)
if integer < 2 || integer > 36 {
panic(call.runtime.panicRangeError("toString() radix must be between 2 and 36"))
}
radix = int(integer)
}
if radix == 10 {
return toValue_string(value.string())
}
return toValue_string(numberToStringRadix(value, radix))
}
func builtinNumber_valueOf(call FunctionCall) Value {
return call.thisClassObject("Number").primitiveValue()
}
func builtinNumber_toFixed(call FunctionCall) Value {
precision := toIntegerFloat(call.Argument(0))
if 20 < precision || 0 > precision {
panic(call.runtime.panicRangeError("toFixed() precision must be between 0 and 20"))
}
if call.This.IsNaN() {
return toValue_string("NaN")
}
value := call.This.float64()
if math.Abs(value) >= 1e21 {
return toValue_string(floatToString(value, 64))
}
return toValue_string(strconv.FormatFloat(call.This.float64(), 'f', int(precision), 64))
}
func builtinNumber_toExponential(call FunctionCall) Value {
if call.This.IsNaN() {
return toValue_string("NaN")
}
precision := float64(-1)
if value := call.Argument(0); value.IsDefined() {
precision = toIntegerFloat(value)
if 0 > precision {
panic(call.runtime.panicRangeError("toString() radix must be between 2 and 36"))
}
}
return toValue_string(strconv.FormatFloat(call.This.float64(), 'e', int(precision), 64))
}
func builtinNumber_toPrecision(call FunctionCall) Value {
if call.This.IsNaN() {
return toValue_string("NaN")
}
value := call.Argument(0)
if value.IsUndefined() {
return toValue_string(call.This.string())
}
precision := toIntegerFloat(value)
if 1 > precision {
panic(call.runtime.panicRangeError("toPrecision() precision must be greater than 1"))
}
return toValue_string(strconv.FormatFloat(call.This.float64(), 'g', int(precision), 64))
}
func builtinNumber_isNaN(call FunctionCall) Value {
if len(call.ArgumentList) < 1 {
return toValue_bool(false)
}
return toValue_bool(call.Argument(0).IsNaN())
}
func builtinNumber_toLocaleString(call FunctionCall) Value {
return builtinNumber_toString(call)
}
+289
View File
@@ -0,0 +1,289 @@
package otto
import (
"fmt"
)
// Object
func builtinObject(call FunctionCall) Value {
value := call.Argument(0)
switch value.kind {
case valueUndefined, valueNull:
return toValue_object(call.runtime.newObject())
}
return toValue_object(call.runtime.toObject(value))
}
func builtinNewObject(self *_object, argumentList []Value) Value {
value := valueOfArrayIndex(argumentList, 0)
switch value.kind {
case valueNull, valueUndefined:
case valueNumber, valueString, valueBoolean:
return toValue_object(self.runtime.toObject(value))
case valueObject:
return value
default:
}
return toValue_object(self.runtime.newObject())
}
func builtinObject_valueOf(call FunctionCall) Value {
return toValue_object(call.thisObject())
}
func builtinObject_hasOwnProperty(call FunctionCall) Value {
propertyName := call.Argument(0).string()
thisObject := call.thisObject()
return toValue_bool(thisObject.hasOwnProperty(propertyName))
}
func builtinObject_isPrototypeOf(call FunctionCall) Value {
value := call.Argument(0)
if !value.IsObject() {
return falseValue
}
prototype := call.toObject(value).prototype
thisObject := call.thisObject()
for prototype != nil {
if thisObject == prototype {
return trueValue
}
prototype = prototype.prototype
}
return falseValue
}
func builtinObject_propertyIsEnumerable(call FunctionCall) Value {
propertyName := call.Argument(0).string()
thisObject := call.thisObject()
property := thisObject.getOwnProperty(propertyName)
if property != nil && property.enumerable() {
return trueValue
}
return falseValue
}
func builtinObject_toString(call FunctionCall) Value {
result := ""
if call.This.IsUndefined() {
result = "[object Undefined]"
} else if call.This.IsNull() {
result = "[object Null]"
} else {
result = fmt.Sprintf("[object %s]", call.thisObject().class)
}
return toValue_string(result)
}
func builtinObject_toLocaleString(call FunctionCall) Value {
toString := call.thisObject().get("toString")
if !toString.isCallable() {
panic(call.runtime.panicTypeError())
}
return toString.call(call.runtime, call.This)
}
func builtinObject_getPrototypeOf(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
if object.prototype == nil {
return nullValue
}
return toValue_object(object.prototype)
}
func builtinObject_getOwnPropertyDescriptor(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
name := call.Argument(1).string()
descriptor := object.getOwnProperty(name)
if descriptor == nil {
return Value{}
}
return toValue_object(call.runtime.fromPropertyDescriptor(*descriptor))
}
func builtinObject_defineProperty(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
name := call.Argument(1).string()
descriptor := toPropertyDescriptor(call.runtime, call.Argument(2))
object.defineOwnProperty(name, descriptor, true)
return objectValue
}
func builtinObject_defineProperties(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
}
properties := call.runtime.toObject(call.Argument(1))
properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true)
return true
})
return objectValue
}
func builtinObject_create(call FunctionCall) Value {
prototypeValue := call.Argument(0)
if !prototypeValue.IsNull() && !prototypeValue.IsObject() {
panic(call.runtime.panicTypeError())
}
object := call.runtime.newObject()
object.prototype = prototypeValue._object()
propertiesValue := call.Argument(1)
if propertiesValue.IsDefined() {
properties := call.runtime.toObject(propertiesValue)
properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true)
return true
})
}
return toValue_object(object)
}
func builtinObject_isExtensible(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
return toValue_bool(object.extensible)
}
panic(call.runtime.panicTypeError())
}
func builtinObject_preventExtensions(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
}
return object
}
func builtinObject_isSealed(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
if object.extensible {
return toValue_bool(false)
}
result := true
object.enumerate(true, func(name string) bool {
property := object.getProperty(name)
if property.configurable() {
result = false
}
return true
})
return toValue_bool(result)
}
panic(call.runtime.panicTypeError())
}
func builtinObject_seal(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.enumerate(true, func(name string) bool {
if property := object.getOwnProperty(name); nil != property && property.configurable() {
property.configureOff()
object.defineOwnProperty(name, *property, true)
}
return true
})
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
}
return object
}
func builtinObject_isFrozen(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
if object.extensible {
return toValue_bool(false)
}
result := true
object.enumerate(true, func(name string) bool {
property := object.getProperty(name)
if property.configurable() || property.writable() {
result = false
}
return true
})
return toValue_bool(result)
}
panic(call.runtime.panicTypeError())
}
func builtinObject_freeze(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.enumerate(true, func(name string) bool {
if property, update := object.getOwnProperty(name), false; nil != property {
if property.isDataDescriptor() && property.writable() {
property.writeOff()
update = true
}
if property.configurable() {
property.configureOff()
update = true
}
if update {
object.defineOwnProperty(name, *property, true)
}
}
return true
})
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
}
return object
}
func builtinObject_keys(call FunctionCall) Value {
if object, keys := call.Argument(0)._object(), []Value(nil); nil != object {
object.enumerate(false, func(name string) bool {
keys = append(keys, toValue_string(name))
return true
})
return toValue_object(call.runtime.newArrayOf(keys))
}
panic(call.runtime.panicTypeError())
}
func builtinObject_getOwnPropertyNames(call FunctionCall) Value {
if object, propertyNames := call.Argument(0)._object(), []Value(nil); nil != object {
object.enumerate(true, func(name string) bool {
if object.hasOwnProperty(name) {
propertyNames = append(propertyNames, toValue_string(name))
}
return true
})
return toValue_object(call.runtime.newArrayOf(propertyNames))
}
panic(call.runtime.panicTypeError())
}
+65
View File
@@ -0,0 +1,65 @@
package otto
import (
"fmt"
)
// RegExp
func builtinRegExp(call FunctionCall) Value {
pattern := call.Argument(0)
flags := call.Argument(1)
if object := pattern._object(); object != nil {
if object.class == "RegExp" && flags.IsUndefined() {
return pattern
}
}
return toValue_object(call.runtime.newRegExp(pattern, flags))
}
func builtinNewRegExp(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newRegExp(
valueOfArrayIndex(argumentList, 0),
valueOfArrayIndex(argumentList, 1),
))
}
func builtinRegExp_toString(call FunctionCall) Value {
thisObject := call.thisObject()
source := thisObject.get("source").string()
flags := []byte{}
if thisObject.get("global").bool() {
flags = append(flags, 'g')
}
if thisObject.get("ignoreCase").bool() {
flags = append(flags, 'i')
}
if thisObject.get("multiline").bool() {
flags = append(flags, 'm')
}
return toValue_string(fmt.Sprintf("/%s/%s", source, flags))
}
func builtinRegExp_exec(call FunctionCall) Value {
thisObject := call.thisObject()
target := call.Argument(0).string()
match, result := execRegExp(thisObject, target)
if !match {
return nullValue
}
return toValue_object(execResultToArray(call.runtime, target, result))
}
func builtinRegExp_test(call FunctionCall) Value {
thisObject := call.thisObject()
target := call.Argument(0).string()
match, _ := execRegExp(thisObject, target)
return toValue_bool(match)
}
func builtinRegExp_compile(call FunctionCall) Value {
// This (useless) function is deprecated, but is here to provide some
// semblance of compatibility.
// Caveat emptor: it may not be around for long.
return Value{}
}
+500
View File
@@ -0,0 +1,500 @@
package otto
import (
"bytes"
"regexp"
"strconv"
"strings"
"unicode/utf8"
)
// String
func stringValueFromStringArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 {
return toValue_string(argumentList[0].string())
}
return toValue_string("")
}
func builtinString(call FunctionCall) Value {
return stringValueFromStringArgumentList(call.ArgumentList)
}
func builtinNewString(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newString(stringValueFromStringArgumentList(argumentList)))
}
func builtinString_toString(call FunctionCall) Value {
return call.thisClassObject("String").primitiveValue()
}
func builtinString_valueOf(call FunctionCall) Value {
return call.thisClassObject("String").primitiveValue()
}
func builtinString_fromCharCode(call FunctionCall) Value {
chrList := make([]uint16, len(call.ArgumentList))
for index, value := range call.ArgumentList {
chrList[index] = toUint16(value)
}
return toValue_string16(chrList)
}
func builtinString_charAt(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
idx := int(call.Argument(0).number().int64)
chr := stringAt(call.This._object().stringValue(), idx)
if chr == utf8.RuneError {
return toValue_string("")
}
return toValue_string(string(chr))
}
func builtinString_charCodeAt(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
idx := int(call.Argument(0).number().int64)
chr := stringAt(call.This._object().stringValue(), idx)
if chr == utf8.RuneError {
return NaNValue()
}
return toValue_uint16(uint16(chr))
}
func builtinString_concat(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
var value bytes.Buffer
value.WriteString(call.This.string())
for _, item := range call.ArgumentList {
value.WriteString(item.string())
}
return toValue_string(value.String())
}
func builtinString_indexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
value := call.This.string()
target := call.Argument(0).string()
if 2 > len(call.ArgumentList) {
return toValue_int(strings.Index(value, target))
}
start := toIntegerFloat(call.Argument(1))
if 0 > start {
start = 0
} else if start >= float64(len(value)) {
if target == "" {
return toValue_int(len(value))
}
return toValue_int(-1)
}
index := strings.Index(value[int(start):], target)
if index >= 0 {
index += int(start)
}
return toValue_int(index)
}
func builtinString_lastIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
value := call.This.string()
target := call.Argument(0).string()
if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
return toValue_int(strings.LastIndex(value, target))
}
length := len(value)
if length == 0 {
return toValue_int(strings.LastIndex(value, target))
}
start := call.ArgumentList[1].number()
if start.kind == numberInfinity { // FIXME
// startNumber is infinity, so start is the end of string (start = length)
return toValue_int(strings.LastIndex(value, target))
}
if 0 > start.int64 {
start.int64 = 0
}
end := int(start.int64) + len(target)
if end > length {
end = length
}
return toValue_int(strings.LastIndex(value[:end], target))
}
func builtinString_match(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
matcherValue := call.Argument(0)
matcher := matcherValue._object()
if !matcherValue.IsObject() || matcher.class != "RegExp" {
matcher = call.runtime.newRegExp(matcherValue, Value{})
}
global := matcher.get("global").bool()
if !global {
match, result := execRegExp(matcher, target)
if !match {
return nullValue
}
return toValue_object(execResultToArray(call.runtime, target, result))
}
{
result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
matchCount := len(result)
if result == nil {
matcher.put("lastIndex", toValue_int(0), true)
return Value{} // !match
}
matchCount = len(result)
valueArray := make([]Value, matchCount)
for index := 0; index < matchCount; index++ {
valueArray[index] = toValue_string(target[result[index][0]:result[index][1]])
}
matcher.put("lastIndex", toValue_int(result[matchCount-1][1]), true)
return toValue_object(call.runtime.newArrayOf(valueArray))
}
}
var builtinString_replace_Regexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")
func builtinString_findAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) (output []byte) {
matchCount := len(match) / 2
output = input
if match[0] != lastIndex {
output = append(output, target[lastIndex:match[0]]...)
}
replacement := builtinString_replace_Regexp.ReplaceAllFunc(replaceValue, func(part []byte) []byte {
// TODO Check if match[0] or match[1] can be -1 in this scenario
switch part[1] {
case '$':
return []byte{'$'}
case '&':
return target[match[0]:match[1]]
case '`':
return target[:match[0]]
case '\'':
return target[match[1]:len(target)]
}
matchNumberParse, error := strconv.ParseInt(string(part[1:]), 10, 64)
matchNumber := int(matchNumberParse)
if error != nil || matchNumber >= matchCount {
return []byte{}
}
offset := 2 * matchNumber
if match[offset] != -1 {
return target[match[offset]:match[offset+1]]
}
return []byte{} // The empty string
})
output = append(output, replacement...)
return output
}
func builtinString_replace(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := []byte(call.This.string())
searchValue := call.Argument(0)
searchObject := searchValue._object()
// TODO If a capture is -1?
var search *regexp.Regexp
global := false
find := 1
if searchValue.IsObject() && searchObject.class == "RegExp" {
regExp := searchObject.regExpValue()
search = regExp.regularExpression
if regExp.global {
find = -1
}
} else {
search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
}
found := search.FindAllSubmatchIndex(target, find)
if found == nil {
return toValue_string(string(target)) // !match
}
{
lastIndex := 0
result := []byte{}
replaceValue := call.Argument(1)
if replaceValue.isCallable() {
target := string(target)
replace := replaceValue._object()
for _, match := range found {
if match[0] != lastIndex {
result = append(result, target[lastIndex:match[0]]...)
}
matchCount := len(match) / 2
argumentList := make([]Value, matchCount+2)
for index := 0; index < matchCount; index++ {
offset := 2 * index
if match[offset] != -1 {
argumentList[index] = toValue_string(target[match[offset]:match[offset+1]])
} else {
argumentList[index] = Value{}
}
}
argumentList[matchCount+0] = toValue_int(match[0])
argumentList[matchCount+1] = toValue_string(target)
replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
result = append(result, []byte(replacement)...)
lastIndex = match[1]
}
} else {
replace := []byte(replaceValue.string())
for _, match := range found {
result = builtinString_findAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1]
}
}
if lastIndex != len(target) {
result = append(result, target[lastIndex:]...)
}
if global && searchObject != nil {
searchObject.put("lastIndex", toValue_int(lastIndex), true)
}
return toValue_string(string(result))
}
}
func builtinString_search(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
searchValue := call.Argument(0)
search := searchValue._object()
if !searchValue.IsObject() || search.class != "RegExp" {
search = call.runtime.newRegExp(searchValue, Value{})
}
result := search.regExpValue().regularExpression.FindStringIndex(target)
if result == nil {
return toValue_int(-1)
}
return toValue_int(result[0])
}
func stringSplitMatch(target string, targetLength int64, index uint, search string, searchLength int64) (bool, uint) {
if int64(index)+searchLength > searchLength {
return false, 0
}
found := strings.Index(target[index:], search)
if 0 > found {
return false, 0
}
return true, uint(found)
}
func builtinString_split(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
separatorValue := call.Argument(0)
limitValue := call.Argument(1)
limit := -1
if limitValue.IsDefined() {
limit = int(toUint32(limitValue))
}
if limit == 0 {
return toValue_object(call.runtime.newArray(0))
}
if separatorValue.IsUndefined() {
return toValue_object(call.runtime.newArrayOf([]Value{toValue_string(target)}))
}
if separatorValue.isRegExp() {
targetLength := len(target)
search := separatorValue._object().regExpValue().regularExpression
valueArray := []Value{}
result := search.FindAllStringSubmatchIndex(target, -1)
lastIndex := 0
found := 0
for _, match := range result {
if match[0] == match[1] {
// FIXME Ugh, this is a hack
if match[0] == 0 || match[0] == targetLength {
continue
}
}
if lastIndex != match[0] {
valueArray = append(valueArray, toValue_string(target[lastIndex:match[0]]))
found++
} else if lastIndex == match[0] {
if lastIndex != -1 {
valueArray = append(valueArray, toValue_string(""))
found++
}
}
lastIndex = match[1]
if found == limit {
goto RETURN
}
captureCount := len(match) / 2
for index := 1; index < captureCount; index++ {
offset := index * 2
value := Value{}
if match[offset] != -1 {
value = toValue_string(target[match[offset]:match[offset+1]])
}
valueArray = append(valueArray, value)
found++
if found == limit {
goto RETURN
}
}
}
if found != limit {
if lastIndex != targetLength {
valueArray = append(valueArray, toValue_string(target[lastIndex:targetLength]))
} else {
valueArray = append(valueArray, toValue_string(""))
}
}
RETURN:
return toValue_object(call.runtime.newArrayOf(valueArray))
} else {
separator := separatorValue.string()
splitLimit := limit
excess := false
if limit > 0 {
splitLimit = limit + 1
excess = true
}
split := strings.SplitN(target, separator, splitLimit)
if excess && len(split) > limit {
split = split[:limit]
}
valueArray := make([]Value, len(split))
for index, value := range split {
valueArray[index] = toValue_string(value)
}
return toValue_object(call.runtime.newArrayOf(valueArray))
}
}
func builtinString_slice(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, false)
if end-start <= 0 {
return toValue_string("")
}
return toValue_string(target[start:end])
}
func builtinString_substring(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, true)
if start > end {
start, end = end, start
}
return toValue_string(target[start:end])
}
func builtinString_substr(call FunctionCall) Value {
target := call.This.string()
size := int64(len(target))
start, length := rangeStartLength(call.ArgumentList, size)
if start >= size {
return toValue_string("")
}
if length <= 0 {
return toValue_string("")
}
if start+length >= size {
// Cap length to be to the end of the string
// start = 3, length = 5, size = 4 [0, 1, 2, 3]
// 4 - 3 = 1
// target[3:4]
length = size - start
}
return toValue_string(target[start : start+length])
}
func builtinString_toLowerCase(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue_string(strings.ToLower(call.This.string()))
}
func builtinString_toUpperCase(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue_string(strings.ToUpper(call.This.string()))
}
// 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters
const builtinString_trim_whitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
func builtinString_trim(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.Trim(call.This.string(),
builtinString_trim_whitespace))
}
// Mozilla extension, not ECMAScript 5
func builtinString_trimLeft(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimLeft(call.This.string(),
builtinString_trim_whitespace))
}
// Mozilla extension, not ECMAScript 5
func builtinString_trimRight(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimRight(call.This.string(),
builtinString_trim_whitespace))
}
func builtinString_localeCompare(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
this := call.This.string()
that := call.Argument(0).string()
if this < that {
return toValue_int(-1)
} else if this == that {
return toValue_int(0)
}
return toValue_int(1)
}
/*
An alternate version of String.trim
func builtinString_trim(call FunctionCall) Value {
checkObjectCoercible(call.This)
return toValue_string(strings.TrimFunc(call.string(.This), isWhiteSpaceOrLineTerminator))
}
*/
func builtinString_toLocaleLowerCase(call FunctionCall) Value {
return builtinString_toLowerCase(call)
}
func builtinString_toLocaleUpperCase(call FunctionCall) Value {
return builtinString_toUpperCase(call)
}
+173
View File
@@ -0,0 +1,173 @@
package otto
import (
"fmt"
)
type _clone struct {
runtime *_runtime
_object map[*_object]*_object
_objectStash map[*_objectStash]*_objectStash
_dclStash map[*_dclStash]*_dclStash
_fnStash map[*_fnStash]*_fnStash
}
func (in *_runtime) clone() *_runtime {
in.lck.Lock()
defer in.lck.Unlock()
out := &_runtime{
debugger: in.debugger,
random: in.random,
stackLimit: in.stackLimit,
traceLimit: in.traceLimit,
}
clone := _clone{
runtime: out,
_object: make(map[*_object]*_object),
_objectStash: make(map[*_objectStash]*_objectStash),
_dclStash: make(map[*_dclStash]*_dclStash),
_fnStash: make(map[*_fnStash]*_fnStash),
}
globalObject := clone.object(in.globalObject)
out.globalStash = out.newObjectStash(globalObject, nil)
out.globalObject = globalObject
out.global = _global{
clone.object(in.global.Object),
clone.object(in.global.Function),
clone.object(in.global.Array),
clone.object(in.global.String),
clone.object(in.global.Boolean),
clone.object(in.global.Number),
clone.object(in.global.Math),
clone.object(in.global.Date),
clone.object(in.global.RegExp),
clone.object(in.global.Error),
clone.object(in.global.EvalError),
clone.object(in.global.TypeError),
clone.object(in.global.RangeError),
clone.object(in.global.ReferenceError),
clone.object(in.global.SyntaxError),
clone.object(in.global.URIError),
clone.object(in.global.JSON),
clone.object(in.global.ObjectPrototype),
clone.object(in.global.FunctionPrototype),
clone.object(in.global.ArrayPrototype),
clone.object(in.global.StringPrototype),
clone.object(in.global.BooleanPrototype),
clone.object(in.global.NumberPrototype),
clone.object(in.global.DatePrototype),
clone.object(in.global.RegExpPrototype),
clone.object(in.global.ErrorPrototype),
clone.object(in.global.EvalErrorPrototype),
clone.object(in.global.TypeErrorPrototype),
clone.object(in.global.RangeErrorPrototype),
clone.object(in.global.ReferenceErrorPrototype),
clone.object(in.global.SyntaxErrorPrototype),
clone.object(in.global.URIErrorPrototype),
}
out.eval = out.globalObject.property["eval"].value.(Value).value.(*_object)
out.globalObject.prototype = out.global.ObjectPrototype
// Not sure if this is necessary, but give some help to the GC
clone.runtime = nil
clone._object = nil
clone._objectStash = nil
clone._dclStash = nil
clone._fnStash = nil
return out
}
func (clone *_clone) object(in *_object) *_object {
if out, exists := clone._object[in]; exists {
return out
}
out := &_object{}
clone._object[in] = out
return in.objectClass.clone(in, out, clone)
}
func (clone *_clone) dclStash(in *_dclStash) (*_dclStash, bool) {
if out, exists := clone._dclStash[in]; exists {
return out, true
}
out := &_dclStash{}
clone._dclStash[in] = out
return out, false
}
func (clone *_clone) objectStash(in *_objectStash) (*_objectStash, bool) {
if out, exists := clone._objectStash[in]; exists {
return out, true
}
out := &_objectStash{}
clone._objectStash[in] = out
return out, false
}
func (clone *_clone) fnStash(in *_fnStash) (*_fnStash, bool) {
if out, exists := clone._fnStash[in]; exists {
return out, true
}
out := &_fnStash{}
clone._fnStash[in] = out
return out, false
}
func (clone *_clone) value(in Value) Value {
out := in
switch value := in.value.(type) {
case *_object:
out.value = clone.object(value)
}
return out
}
func (clone *_clone) valueArray(in []Value) []Value {
out := make([]Value, len(in))
for index, value := range in {
out[index] = clone.value(value)
}
return out
}
func (clone *_clone) stash(in _stash) _stash {
if in == nil {
return nil
}
return in.clone(clone)
}
func (clone *_clone) property(in _property) _property {
out := in
switch value := in.value.(type) {
case Value:
out.value = clone.value(value)
case _propertyGetSet:
p := _propertyGetSet{}
if value[0] != nil {
p[0] = clone.object(value[0])
}
if value[1] != nil {
p[1] = clone.object(value[1])
}
out.value = p
default:
panic(fmt.Errorf("in.value.(Value) != true; in.value is %T", in.value))
}
return out
}
func (clone *_clone) dclProperty(in _dclProperty) _dclProperty {
out := in
out.value = clone.value(in.value)
return out
}
+24
View File
@@ -0,0 +1,24 @@
package otto
import (
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
)
type _file struct {
name string
src string
base int // This will always be 1 or greater
}
type _compiler struct {
file *file.File
program *ast.Program
}
func (cmpl *_compiler) parse() *_nodeProgram {
if cmpl.program != nil {
cmpl.file = cmpl.program.File
}
return cmpl._parse(cmpl.program)
}
+96
View File
@@ -0,0 +1,96 @@
package otto
import (
"strconv"
)
func (self *_runtime) cmpl_evaluate_nodeProgram(node *_nodeProgram, eval bool) Value {
if !eval {
self.enterGlobalScope()
defer func() {
self.leaveScope()
}()
}
self.cmpl_functionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList)
self.scope.frame.file = node.file
return self.cmpl_evaluate_nodeStatementList(node.body)
}
func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash, node *_nodeFunctionLiteral, this Value, argumentList []Value) Value {
indexOfParameterName := make([]string, len(argumentList))
// function(abc, def, ghi)
// indexOfParameterName[0] = "abc"
// indexOfParameterName[1] = "def"
// indexOfParameterName[2] = "ghi"
// ...
argumentsFound := false
for index, name := range node.parameterList {
if name == "arguments" {
argumentsFound = true
}
value := Value{}
if index < len(argumentList) {
value = argumentList[index]
indexOfParameterName[index] = name
}
// strict = false
self.scope.lexical.setValue(name, value, false)
}
if !argumentsFound {
arguments := self.newArgumentsObject(indexOfParameterName, stash, len(argumentList))
arguments.defineProperty("callee", toValue_object(function), 0101, false)
stash.arguments = arguments
// strict = false
self.scope.lexical.setValue("arguments", toValue_object(arguments), false)
for index, _ := range argumentList {
if index < len(node.parameterList) {
continue
}
indexAsString := strconv.FormatInt(int64(index), 10)
arguments.defineProperty(indexAsString, argumentList[index], 0111, false)
}
}
self.cmpl_functionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList)
result := self.cmpl_evaluate_nodeStatement(node.body)
if result.kind == valueResult {
return result
}
return Value{}
}
func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) {
executionContext := self.scope
eval := executionContext.eval
stash := executionContext.variable
for _, function := range list {
name := function.name
value := self.cmpl_evaluate_nodeExpression(function)
if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, value)
} else {
// TODO 10.5.5.e
stash.setBinding(name, value, false) // TODO strict
}
}
}
func (self *_runtime) cmpl_variableDeclaration(list []string) {
executionContext := self.scope
eval := executionContext.eval
stash := executionContext.variable
for _, name := range list {
if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, Value{}) // TODO strict?
}
}
}
+460
View File
@@ -0,0 +1,460 @@
package otto
import (
"fmt"
"math"
"runtime"
"github.com/robertkrimen/otto/token"
)
func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value {
// Allow interpreter interruption
// If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this
if self.otto.Interrupt != nil {
runtime.Gosched()
select {
case value := <-self.otto.Interrupt:
value()
default:
}
}
switch node := node.(type) {
case *_nodeArrayLiteral:
return self.cmpl_evaluate_nodeArrayLiteral(node)
case *_nodeAssignExpression:
return self.cmpl_evaluate_nodeAssignExpression(node)
case *_nodeBinaryExpression:
if node.comparison {
return self.cmpl_evaluate_nodeBinaryExpression_comparison(node)
} else {
return self.cmpl_evaluate_nodeBinaryExpression(node)
}
case *_nodeBracketExpression:
return self.cmpl_evaluate_nodeBracketExpression(node)
case *_nodeCallExpression:
return self.cmpl_evaluate_nodeCallExpression(node, nil)
case *_nodeConditionalExpression:
return self.cmpl_evaluate_nodeConditionalExpression(node)
case *_nodeDotExpression:
return self.cmpl_evaluate_nodeDotExpression(node)
case *_nodeFunctionLiteral:
var local = self.scope.lexical
if node.name != "" {
local = self.newDeclarationStash(local)
}
value := toValue_object(self.newNodeFunction(node, local))
if node.name != "" {
local.createBinding(node.name, false, value)
}
return value
case *_nodeIdentifier:
name := node.name
// TODO Should be true or false (strictness) depending on context
// getIdentifierReference should not return nil, but we check anyway and panic
// so as not to propagate the nil into something else
reference := getIdentifierReference(self, self.scope.lexical, name, false, _at(node.idx))
if reference == nil {
// Should never get here!
panic(hereBeDragons("referenceError == nil: " + name))
}
return toValue(reference)
case *_nodeLiteral:
return node.value
case *_nodeNewExpression:
return self.cmpl_evaluate_nodeNewExpression(node)
case *_nodeObjectLiteral:
return self.cmpl_evaluate_nodeObjectLiteral(node)
case *_nodeRegExpLiteral:
return toValue_object(self._newRegExp(node.pattern, node.flags))
case *_nodeSequenceExpression:
return self.cmpl_evaluate_nodeSequenceExpression(node)
case *_nodeThisExpression:
return toValue_object(self.scope.this)
case *_nodeUnaryExpression:
return self.cmpl_evaluate_nodeUnaryExpression(node)
case *_nodeVariableExpression:
return self.cmpl_evaluate_nodeVariableExpression(node)
}
panic(fmt.Errorf("Here be dragons: evaluate_nodeExpression(%T)", node))
}
func (self *_runtime) cmpl_evaluate_nodeArrayLiteral(node *_nodeArrayLiteral) Value {
valueArray := []Value{}
for _, node := range node.value {
if node == nil {
valueArray = append(valueArray, emptyValue)
} else {
valueArray = append(valueArray, self.cmpl_evaluate_nodeExpression(node).resolve())
}
}
result := self.newArrayOf(valueArray)
return toValue_object(result)
}
func (self *_runtime) cmpl_evaluate_nodeAssignExpression(node *_nodeAssignExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left)
right := self.cmpl_evaluate_nodeExpression(node.right)
rightValue := right.resolve()
result := rightValue
if node.operator != token.ASSIGN {
result = self.calculateBinaryExpression(node.operator, left, rightValue)
}
self.putValue(left.reference(), result)
return result
}
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left)
leftValue := left.resolve()
switch node.operator {
// Logical
case token.LOGICAL_AND:
if !leftValue.bool() {
return leftValue
}
right := self.cmpl_evaluate_nodeExpression(node.right)
return right.resolve()
case token.LOGICAL_OR:
if leftValue.bool() {
return leftValue
}
right := self.cmpl_evaluate_nodeExpression(node.right)
return right.resolve()
}
return self.calculateBinaryExpression(node.operator, leftValue, self.cmpl_evaluate_nodeExpression(node.right))
}
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression_comparison(node *_nodeBinaryExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left).resolve()
right := self.cmpl_evaluate_nodeExpression(node.right).resolve()
return toValue_bool(self.calculateComparison(node.operator, left, right))
}
func (self *_runtime) cmpl_evaluate_nodeBracketExpression(node *_nodeBracketExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.left)
targetValue := target.resolve()
member := self.cmpl_evaluate_nodeExpression(node.member)
memberValue := member.resolve()
// TODO Pass in base value as-is, and defer toObject till later?
object, err := self.objectCoerce(targetValue)
if err != nil {
panic(self.panicTypeError("Cannot access member '%s' of %s", memberValue.string(), err.Error(), _at(node.idx)))
}
return toValue(newPropertyReference(self, object, memberValue.string(), false, _at(node.idx)))
}
func (self *_runtime) cmpl_evaluate_nodeCallExpression(node *_nodeCallExpression, withArgumentList []interface{}) Value {
rt := self
this := Value{}
callee := self.cmpl_evaluate_nodeExpression(node.callee)
argumentList := []Value{}
if withArgumentList != nil {
argumentList = self.toValueArray(withArgumentList...)
} else {
for _, argumentNode := range node.argumentList {
argumentList = append(argumentList, self.cmpl_evaluate_nodeExpression(argumentNode).resolve())
}
}
rf := callee.reference()
vl := callee.resolve()
eval := false // Whether this call is a (candidate for) direct call to eval
name := ""
if rf != nil {
switch rf := rf.(type) {
case *_propertyReference:
name = rf.name
object := rf.base
this = toValue_object(object)
eval = rf.name == "eval" // Possible direct eval
case *_stashReference:
// TODO ImplicitThisValue
name = rf.name
eval = rf.name == "eval" // Possible direct eval
default:
// FIXME?
panic(rt.panicTypeError("Here be dragons"))
}
}
at := _at(-1)
switch callee := node.callee.(type) {
case *_nodeIdentifier:
at = _at(callee.idx)
case *_nodeDotExpression:
at = _at(callee.idx)
case *_nodeBracketExpression:
at = _at(callee.idx)
}
frame := _frame{
callee: name,
file: self.scope.frame.file,
}
if !vl.IsFunction() {
if name == "" {
// FIXME Maybe typeof?
panic(rt.panicTypeError("%v is not a function", vl, at))
}
panic(rt.panicTypeError("'%s' is not a function", name, at))
}
self.scope.frame.offset = int(at)
return vl._object().call(this, argumentList, eval, frame)
}
func (self *_runtime) cmpl_evaluate_nodeConditionalExpression(node *_nodeConditionalExpression) Value {
test := self.cmpl_evaluate_nodeExpression(node.test)
testValue := test.resolve()
if testValue.bool() {
return self.cmpl_evaluate_nodeExpression(node.consequent)
}
return self.cmpl_evaluate_nodeExpression(node.alternate)
}
func (self *_runtime) cmpl_evaluate_nodeDotExpression(node *_nodeDotExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.left)
targetValue := target.resolve()
// TODO Pass in base value as-is, and defer toObject till later?
object, err := self.objectCoerce(targetValue)
if err != nil {
panic(self.panicTypeError("Cannot access member '%s' of %s", node.identifier, err.Error(), _at(node.idx)))
}
return toValue(newPropertyReference(self, object, node.identifier, false, _at(node.idx)))
}
func (self *_runtime) cmpl_evaluate_nodeNewExpression(node *_nodeNewExpression) Value {
rt := self
callee := self.cmpl_evaluate_nodeExpression(node.callee)
argumentList := []Value{}
for _, argumentNode := range node.argumentList {
argumentList = append(argumentList, self.cmpl_evaluate_nodeExpression(argumentNode).resolve())
}
rf := callee.reference()
vl := callee.resolve()
name := ""
if rf != nil {
switch rf := rf.(type) {
case *_propertyReference:
name = rf.name
case *_stashReference:
name = rf.name
default:
panic(rt.panicTypeError("Here be dragons"))
}
}
at := _at(-1)
switch callee := node.callee.(type) {
case *_nodeIdentifier:
at = _at(callee.idx)
case *_nodeDotExpression:
at = _at(callee.idx)
case *_nodeBracketExpression:
at = _at(callee.idx)
}
if !vl.IsFunction() {
if name == "" {
// FIXME Maybe typeof?
panic(rt.panicTypeError("%v is not a function", vl, at))
}
panic(rt.panicTypeError("'%s' is not a function", name, at))
}
self.scope.frame.offset = int(at)
return vl._object().construct(argumentList)
}
func (self *_runtime) cmpl_evaluate_nodeObjectLiteral(node *_nodeObjectLiteral) Value {
result := self.newObject()
for _, property := range node.value {
switch property.kind {
case "value":
result.defineProperty(property.key, self.cmpl_evaluate_nodeExpression(property.value).resolve(), 0111, false)
case "get":
getter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical)
descriptor := _property{}
descriptor.mode = 0211
descriptor.value = _propertyGetSet{getter, nil}
result.defineOwnProperty(property.key, descriptor, false)
case "set":
setter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical)
descriptor := _property{}
descriptor.mode = 0211
descriptor.value = _propertyGetSet{nil, setter}
result.defineOwnProperty(property.key, descriptor, false)
default:
panic(fmt.Errorf("Here be dragons: evaluate_nodeObjectLiteral: invalid property.Kind: %v", property.kind))
}
}
return toValue_object(result)
}
func (self *_runtime) cmpl_evaluate_nodeSequenceExpression(node *_nodeSequenceExpression) Value {
var result Value
for _, node := range node.sequence {
result = self.cmpl_evaluate_nodeExpression(node)
result = result.resolve()
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.operand)
switch node.operator {
case token.TYPEOF, token.DELETE:
if target.kind == valueReference && target.reference().invalid() {
if node.operator == token.TYPEOF {
return toValue_string("undefined")
}
return trueValue
}
}
switch node.operator {
case token.NOT:
targetValue := target.resolve()
if targetValue.bool() {
return falseValue
}
return trueValue
case token.BITWISE_NOT:
targetValue := target.resolve()
integerValue := toInt32(targetValue)
return toValue_int32(^integerValue)
case token.PLUS:
targetValue := target.resolve()
return toValue_float64(targetValue.float64())
case token.MINUS:
targetValue := target.resolve()
value := targetValue.float64()
// TODO Test this
sign := float64(-1)
if math.Signbit(value) {
sign = 1
}
return toValue_float64(math.Copysign(value, sign))
case token.INCREMENT:
targetValue := target.resolve()
if node.postfix {
// Postfix++
oldValue := targetValue.float64()
newValue := toValue_float64(+1 + oldValue)
self.putValue(target.reference(), newValue)
return toValue_float64(oldValue)
} else {
// ++Prefix
newValue := toValue_float64(+1 + targetValue.float64())
self.putValue(target.reference(), newValue)
return newValue
}
case token.DECREMENT:
targetValue := target.resolve()
if node.postfix {
// Postfix--
oldValue := targetValue.float64()
newValue := toValue_float64(-1 + oldValue)
self.putValue(target.reference(), newValue)
return toValue_float64(oldValue)
} else {
// --Prefix
newValue := toValue_float64(-1 + targetValue.float64())
self.putValue(target.reference(), newValue)
return newValue
}
case token.VOID:
target.resolve() // FIXME Side effect?
return Value{}
case token.DELETE:
reference := target.reference()
if reference == nil {
return trueValue
}
return toValue_bool(target.reference().delete())
case token.TYPEOF:
targetValue := target.resolve()
switch targetValue.kind {
case valueUndefined:
return toValue_string("undefined")
case valueNull:
return toValue_string("object")
case valueBoolean:
return toValue_string("boolean")
case valueNumber:
return toValue_string("number")
case valueString:
return toValue_string("string")
case valueObject:
if targetValue._object().isCall() {
return toValue_string("function")
}
return toValue_string("object")
default:
// FIXME ?
}
}
panic(hereBeDragons())
}
func (self *_runtime) cmpl_evaluate_nodeVariableExpression(node *_nodeVariableExpression) Value {
if node.initializer != nil {
// FIXME If reference is nil
left := getIdentifierReference(self, self.scope.lexical, node.name, false, _at(node.idx))
right := self.cmpl_evaluate_nodeExpression(node.initializer)
rightValue := right.resolve()
self.putValue(left, rightValue)
}
return toValue_string(node.name)
}
+424
View File
@@ -0,0 +1,424 @@
package otto
import (
"fmt"
"runtime"
"github.com/robertkrimen/otto/token"
)
func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
// Allow interpreter interruption
// If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this
if self.otto.Interrupt != nil {
runtime.Gosched()
select {
case value := <-self.otto.Interrupt:
value()
default:
}
}
switch node := node.(type) {
case *_nodeBlockStatement:
labels := self.labels
self.labels = nil
value := self.cmpl_evaluate_nodeStatementList(node.list)
switch value.kind {
case valueResult:
switch value.evaluateBreak(labels) {
case resultBreak:
return emptyValue
}
}
return value
case *_nodeBranchStatement:
target := node.label
switch node.branch { // FIXME Maybe node.kind? node.operator?
case token.BREAK:
return toValue(newBreakResult(target))
case token.CONTINUE:
return toValue(newContinueResult(target))
}
case *_nodeDebuggerStatement:
if self.debugger != nil {
self.debugger(self.otto)
}
return emptyValue // Nothing happens.
case *_nodeDoWhileStatement:
return self.cmpl_evaluate_nodeDoWhileStatement(node)
case *_nodeEmptyStatement:
return emptyValue
case *_nodeExpressionStatement:
return self.cmpl_evaluate_nodeExpression(node.expression)
case *_nodeForInStatement:
return self.cmpl_evaluate_nodeForInStatement(node)
case *_nodeForStatement:
return self.cmpl_evaluate_nodeForStatement(node)
case *_nodeIfStatement:
return self.cmpl_evaluate_nodeIfStatement(node)
case *_nodeLabelledStatement:
self.labels = append(self.labels, node.label)
defer func() {
if len(self.labels) > 0 {
self.labels = self.labels[:len(self.labels)-1] // Pop the label
} else {
self.labels = nil
}
}()
return self.cmpl_evaluate_nodeStatement(node.statement)
case *_nodeReturnStatement:
if node.argument != nil {
return toValue(newReturnResult(self.cmpl_evaluate_nodeExpression(node.argument).resolve()))
}
return toValue(newReturnResult(Value{}))
case *_nodeSwitchStatement:
return self.cmpl_evaluate_nodeSwitchStatement(node)
case *_nodeThrowStatement:
value := self.cmpl_evaluate_nodeExpression(node.argument).resolve()
panic(newException(value))
case *_nodeTryStatement:
return self.cmpl_evaluate_nodeTryStatement(node)
case *_nodeVariableStatement:
// Variables are already defined, this is initialization only
for _, variable := range node.list {
self.cmpl_evaluate_nodeVariableExpression(variable.(*_nodeVariableExpression))
}
return emptyValue
case *_nodeWhileStatement:
return self.cmpl_evaluate_nodeWhileStatement(node)
case *_nodeWithStatement:
return self.cmpl_evaluate_nodeWithStatement(node)
}
panic(fmt.Errorf("Here be dragons: evaluate_nodeStatement(%T)", node))
}
func (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Value {
var result Value
for _, node := range list {
value := self.cmpl_evaluate_nodeStatement(node)
switch value.kind {
case valueResult:
return value
case valueEmpty:
default:
// We have getValue here to (for example) trigger a
// ReferenceError (of the not defined variety)
// Not sure if this is the best way to error out early
// for such errors or if there is a better way
// TODO Do we still need this?
result = value.resolve()
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileStatement) Value {
labels := append(self.labels, "")
self.labels = nil
test := node.test
result := emptyValue
resultBreak:
for {
for _, node := range node.body {
value := self.cmpl_evaluate_nodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
case resultReturn:
return value
case resultBreak:
break resultBreak
case resultContinue:
goto resultContinue
}
case valueEmpty:
default:
result = value
}
}
resultContinue:
if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() {
// Stahp: do ... while (false)
break
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement) Value {
labels := append(self.labels, "")
self.labels = nil
source := self.cmpl_evaluate_nodeExpression(node.source)
sourceValue := source.resolve()
switch sourceValue.kind {
case valueUndefined, valueNull:
return emptyValue
}
sourceObject := self.toObject(sourceValue)
into := node.into
body := node.body
result := emptyValue
object := sourceObject
for object != nil {
enumerateValue := emptyValue
object.enumerate(false, func(name string) bool {
into := self.cmpl_evaluate_nodeExpression(into)
// In the case of: for (var abc in def) ...
if into.reference() == nil {
identifier := into.string()
// TODO Should be true or false (strictness) depending on context
into = toValue(getIdentifierReference(self, self.scope.lexical, identifier, false, -1))
}
self.putValue(into.reference(), toValue_string(name))
for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
case resultReturn:
enumerateValue = value
return false
case resultBreak:
object = nil
return false
case resultContinue:
return true
}
case valueEmpty:
default:
enumerateValue = value
}
}
return true
})
if object == nil {
break
}
object = object.prototype
if !enumerateValue.isEmpty() {
result = enumerateValue
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Value {
labels := append(self.labels, "")
self.labels = nil
initializer := node.initializer
test := node.test
update := node.update
body := node.body
if initializer != nil {
initialResult := self.cmpl_evaluate_nodeExpression(initializer)
initialResult.resolve() // Side-effect trigger
}
result := emptyValue
resultBreak:
for {
if test != nil {
testResult := self.cmpl_evaluate_nodeExpression(test)
testResultValue := testResult.resolve()
if testResultValue.bool() == false {
break
}
}
for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
case resultReturn:
return value
case resultBreak:
break resultBreak
case resultContinue:
goto resultContinue
}
case valueEmpty:
default:
result = value
}
}
resultContinue:
if update != nil {
updateResult := self.cmpl_evaluate_nodeExpression(update)
updateResult.resolve() // Side-effect trigger
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeIfStatement(node *_nodeIfStatement) Value {
test := self.cmpl_evaluate_nodeExpression(node.test)
testValue := test.resolve()
if testValue.bool() {
return self.cmpl_evaluate_nodeStatement(node.consequent)
} else if node.alternate != nil {
return self.cmpl_evaluate_nodeStatement(node.alternate)
}
return emptyValue
}
func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStatement) Value {
labels := append(self.labels, "")
self.labels = nil
discriminantResult := self.cmpl_evaluate_nodeExpression(node.discriminant)
target := node.default_
for index, clause := range node.body {
test := clause.test
if test != nil {
if self.calculateComparison(token.STRICT_EQUAL, discriminantResult, self.cmpl_evaluate_nodeExpression(test)) {
target = index
break
}
}
}
result := emptyValue
if target != -1 {
for _, clause := range node.body[target:] {
for _, statement := range clause.consequent {
value := self.cmpl_evaluate_nodeStatement(statement)
switch value.kind {
case valueResult:
switch value.evaluateBreak(labels) {
case resultReturn:
return value
case resultBreak:
return emptyValue
}
case valueEmpty:
default:
result = value
}
}
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeTryStatement(node *_nodeTryStatement) Value {
tryCatchValue, exception := self.tryCatchEvaluate(func() Value {
return self.cmpl_evaluate_nodeStatement(node.body)
})
if exception && node.catch != nil {
outer := self.scope.lexical
self.scope.lexical = self.newDeclarationStash(outer)
defer func() {
self.scope.lexical = outer
}()
// TODO If necessary, convert TypeError<runtime> => TypeError
// That, is, such errors can be thrown despite not being JavaScript "native"
// strict = false
self.scope.lexical.setValue(node.catch.parameter, tryCatchValue, false)
// FIXME node.CatchParameter
// FIXME node.Catch
tryCatchValue, exception = self.tryCatchEvaluate(func() Value {
return self.cmpl_evaluate_nodeStatement(node.catch.body)
})
}
if node.finally != nil {
finallyValue := self.cmpl_evaluate_nodeStatement(node.finally)
if finallyValue.kind == valueResult {
return finallyValue
}
}
if exception {
panic(newException(tryCatchValue))
}
return tryCatchValue
}
func (self *_runtime) cmpl_evaluate_nodeWhileStatement(node *_nodeWhileStatement) Value {
test := node.test
body := node.body
labels := append(self.labels, "")
self.labels = nil
result := emptyValue
resultBreakContinue:
for {
if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() {
// Stahp: while (false) ...
break
}
for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
case resultReturn:
return value
case resultBreak:
break resultBreakContinue
case resultContinue:
continue resultBreakContinue
}
case valueEmpty:
default:
result = value
}
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeWithStatement(node *_nodeWithStatement) Value {
object := self.cmpl_evaluate_nodeExpression(node.object)
outer := self.scope.lexical
lexical := self.newObjectStash(self.toObject(object.resolve()), outer)
self.scope.lexical = lexical
defer func() {
self.scope.lexical = outer
}()
return self.cmpl_evaluate_nodeStatement(node.body)
}
File diff suppressed because it is too large. Load diff
+51
View File
@@ -0,0 +1,51 @@
package otto
import (
"fmt"
"os"
"strings"
)
func formatForConsole(argumentList []Value) string {
output := []string{}
for _, argument := range argumentList {
output = append(output, fmt.Sprintf("%v", argument))
}
return strings.Join(output, " ")
}
func builtinConsole_log(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return Value{}
}
func builtinConsole_error(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return Value{}
}
// Nothing happens.
func builtinConsole_dir(call FunctionCall) Value {
return Value{}
}
func builtinConsole_time(call FunctionCall) Value {
return Value{}
}
func builtinConsole_timeEnd(call FunctionCall) Value {
return Value{}
}
func builtinConsole_trace(call FunctionCall) Value {
return Value{}
}
func builtinConsole_assert(call FunctionCall) Value {
return Value{}
}
func (runtime *_runtime) newConsole() *_object {
return newConsoleObject(runtime)
}
+9
View File
@@ -0,0 +1,9 @@
// This file was AUTOMATICALLY GENERATED by dbg-import (smuggol) for github.com/robertkrimen/dbg
package otto
import (
Dbg "github.com/robertkrimen/otto/dbg"
)
var dbg, dbgf = Dbg.New()
+387
View File
@@ -0,0 +1,387 @@
// This file was AUTOMATICALLY GENERATED by dbg-import (smuggol) from github.com/robertkrimen/dbg
/*
Package dbg is a println/printf/log-debugging utility library.
import (
Dbg "github.com/robertkrimen/dbg"
)
dbg, dbgf := Dbg.New()
dbg("Emit some debug stuff", []byte{120, 121, 122, 122, 121}, math.Pi)
# "2013/01/28 16:50:03 Emit some debug stuff [120 121 122 122 121] 3.141592653589793"
dbgf("With a %s formatting %.2f", "little", math.Pi)
# "2013/01/28 16:51:55 With a little formatting (3.14)"
dbgf("%/fatal//A fatal debug statement: should not be here")
# "A fatal debug statement: should not be here"
# ...and then, os.Exit(1)
dbgf("%/panic//Can also panic %s", "this")
# "Can also panic this"
# ...as a panic, equivalent to: panic("Can also panic this")
dbgf("Any %s arguments without a corresponding %%", "extra", "are treated like arguments to dbg()")
# "2013/01/28 17:14:40 Any extra arguments (without a corresponding %) are treated like arguments to dbg()"
dbgf("%d %d", 1, 2, 3, 4, 5)
# "2013/01/28 17:16:32 Another example: 1 2 3 4 5"
dbgf("%@: Include the function name for a little context (via %s)", "%@")
# "2013... github.com/robertkrimen/dbg.TestSynopsis: Include the function name for a little context (via %@)"
By default, dbg uses log (log.Println, log.Printf, log.Panic, etc.) for output.
However, you can also provide your own output destination by invoking dbg.New with
a customization function:
import (
"bytes"
Dbg "github.com/robertkrimen/dbg"
"os"
)
# dbg to os.Stderr
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr)
})
# A slightly contrived example:
var buffer bytes.Buffer
dbg, dbgf := New(func(dbgr *Dbgr) {
dbgr.SetOutput(&buffer)
})
*/
package dbg
import (
"bytes"
"fmt"
"io"
"log"
"os"
"regexp"
"runtime"
"strings"
"unicode"
)
type _frmt struct {
ctl string
format string
operandCount int
panic bool
fatal bool
check bool
}
var (
ctlTest = regexp.MustCompile(`^\s*%/`)
ctlScan = regexp.MustCompile(`%?/(panic|fatal|check)(?:\s|$)`)
)
func operandCount(format string) int {
count := 0
end := len(format)
for at := 0; at < end; {
for at < end && format[at] != '%' {
at++
}
at++
if at < end {
if format[at] != '%' && format[at] != '@' {
count++
}
at++
}
}
return count
}
func parseFormat(format string) (frmt _frmt) {
if ctlTest.MatchString(format) {
format = strings.TrimLeftFunc(format, unicode.IsSpace)
index := strings.Index(format, "//")
if index != -1 {
frmt.ctl = format[0:index]
format = format[index+2:] // Skip the second slash via +2 (instead of +1)
} else {
frmt.ctl = format
format = ""
}
for _, tmp := range ctlScan.FindAllStringSubmatch(frmt.ctl, -1) {
for _, value := range tmp[1:] {
switch value {
case "panic":
frmt.panic = true
case "fatal":
frmt.fatal = true
case "check":
frmt.check = true
}
}
}
}
frmt.format = format
frmt.operandCount = operandCount(format)
return
}
type Dbgr struct {
emit _emit
}
type DbgFunction func(values ...interface{})
func NewDbgr() *Dbgr {
self := &Dbgr{}
return self
}
/*
New will create and return a pair of debugging functions. You can customize where
they output to by passing in an (optional) customization function:
import (
Dbg "github.com/robertkrimen/dbg"
"os"
)
# dbg to os.Stderr
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr)
})
*/
func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) {
dbgr := NewDbgr()
if len(options) > 0 {
if fn, ok := options[0].(func(*Dbgr)); ok {
fn(dbgr)
}
}
return dbgr.DbgDbgf()
}
func (self Dbgr) Dbg(values ...interface{}) {
self.getEmit().emit(_frmt{}, "", values...)
}
func (self Dbgr) Dbgf(values ...interface{}) {
self.dbgf(values...)
}
func (self Dbgr) DbgDbgf() (dbg DbgFunction, dbgf DbgFunction) {
dbg = func(vl ...interface{}) {
self.Dbg(vl...)
}
dbgf = func(vl ...interface{}) {
self.dbgf(vl...)
}
return dbg, dbgf // Redundant, but...
}
func (self Dbgr) dbgf(values ...interface{}) {
var frmt _frmt
if len(values) > 0 {
tmp := fmt.Sprint(values[0])
frmt = parseFormat(tmp)
values = values[1:]
}
buffer_f := bytes.Buffer{}
format := frmt.format
end := len(format)
for at := 0; at < end; {
last := at
for at < end && format[at] != '%' {
at++
}
if at > last {
buffer_f.WriteString(format[last:at])
}
if at >= end {
break
}
// format[at] == '%'
at++
// format[at] == ?
if format[at] == '@' {
depth := 2
pc, _, _, _ := runtime.Caller(depth)
name := runtime.FuncForPC(pc).Name()
buffer_f.WriteString(name)
} else {
buffer_f.WriteString(format[at-1 : at+1])
}
at++
}
//values_f := append([]interface{}{}, values[0:frmt.operandCount]...)
values_f := values[0:frmt.operandCount]
values_dbg := values[frmt.operandCount:]
if len(values_dbg) > 0 {
// Adjust frmt.format:
// (%v instead of %s because: frmt.check)
{
tmp := format
if len(tmp) > 0 {
if unicode.IsSpace(rune(tmp[len(tmp)-1])) {
buffer_f.WriteString("%v")
} else {
buffer_f.WriteString(" %v")
}
} else if frmt.check {
// Performing a check, so no output
} else {
buffer_f.WriteString("%v")
}
}
// Adjust values_f:
if !frmt.check {
tmp := []string{}
for _, value := range values_dbg {
tmp = append(tmp, fmt.Sprintf("%v", value))
}
// First, make a copy of values_f, so we avoid overwriting values_dbg when appending
values_f = append([]interface{}{}, values_f...)
values_f = append(values_f, strings.Join(tmp, " "))
}
}
format = buffer_f.String()
if frmt.check {
// We do not actually emit to the log, but panic if
// a non-nil value is detected (e.g. a non-nil error)
for _, value := range values_dbg {
if value != nil {
if format == "" {
panic(value)
} else {
panic(fmt.Sprintf(format, append(values_f, value)...))
}
}
}
} else {
self.getEmit().emit(frmt, format, values_f...)
}
}
// Idiot-proof &Dbgr{}, etc.
func (self *Dbgr) getEmit() _emit {
if self.emit == nil {
self.emit = standardEmit()
}
return self.emit
}
// SetOutput will accept the following as a destination for output:
//
// *log.Logger Print*/Panic*/Fatal* of the logger
// io.Writer -
// nil Reset to the default output (os.Stderr)
// "log" Print*/Panic*/Fatal* via the "log" package
//
func (self *Dbgr) SetOutput(output interface{}) {
if output == nil {
self.emit = standardEmit()
return
}
switch output := output.(type) {
case *log.Logger:
self.emit = _emitLogger{
logger: output,
}
return
case io.Writer:
self.emit = _emitWriter{
writer: output,
}
return
case string:
if output == "log" {
self.emit = _emitLog{}
return
}
}
panic(output)
}
// ======== //
// = emit = //
// ======== //
func standardEmit() _emit {
return _emitWriter{
writer: os.Stderr,
}
}
func ln(tmp string) string {
length := len(tmp)
if length > 0 && tmp[length-1] != '\n' {
return tmp + "\n"
}
return tmp
}
type _emit interface {
emit(_frmt, string, ...interface{})
}
type _emitWriter struct {
writer io.Writer
}
func (self _emitWriter) emit(frmt _frmt, format string, values ...interface{}) {
if format == "" {
fmt.Fprintln(self.writer, values...)
} else {
if frmt.panic {
panic(fmt.Sprintf(format, values...))
}
fmt.Fprintf(self.writer, ln(format), values...)
if frmt.fatal {
os.Exit(1)
}
}
}
type _emitLogger struct {
logger *log.Logger
}
func (self _emitLogger) emit(frmt _frmt, format string, values ...interface{}) {
if format == "" {
self.logger.Println(values...)
} else {
if frmt.panic {
self.logger.Panicf(format, values...)
} else if frmt.fatal {
self.logger.Fatalf(format, values...)
} else {
self.logger.Printf(format, values...)
}
}
}
type _emitLog struct {
}
func (self _emitLog) emit(frmt _frmt, format string, values ...interface{}) {
if format == "" {
log.Println(values...)
} else {
if frmt.panic {
log.Panicf(format, values...)
} else if frmt.fatal {
log.Fatalf(format, values...)
} else {
log.Printf(format, values...)
}
}
}
+253
View File
@@ -0,0 +1,253 @@
package otto
import (
"errors"
"fmt"
"github.com/robertkrimen/otto/file"
)
type _exception struct {
value interface{}
}
func newException(value interface{}) *_exception {
return &_exception{
value: value,
}
}
func (self *_exception) eject() interface{} {
value := self.value
self.value = nil // Prevent Go from holding on to the value, whatever it is
return value
}
type _error struct {
name string
message string
trace []_frame
offset int
}
func (err _error) format() string {
if len(err.name) == 0 {
return err.message
}
if len(err.message) == 0 {
return err.name
}
return fmt.Sprintf("%s: %s", err.name, err.message)
}
func (err _error) formatWithStack() string {
str := err.format() + "\n"
for _, frame := range err.trace {
str += " at " + frame.location() + "\n"
}
return str
}
type _frame struct {
native bool
nativeFile string
nativeLine int
file *file.File
offset int
callee string
fn interface{}
}
var (
nativeFrame = _frame{}
)
type _at int
func (fr _frame) location() string {
str := "<unknown>"
switch {
case fr.native:
str = "<native code>"
if fr.nativeFile != "" && fr.nativeLine != 0 {
str = fmt.Sprintf("%s:%d", fr.nativeFile, fr.nativeLine)
}
case fr.file != nil:
if p := fr.file.Position(file.Idx(fr.offset)); p != nil {
path, line, column := p.Filename, p.Line, p.Column
if path == "" {
path = "<anonymous>"
}
str = fmt.Sprintf("%s:%d:%d", path, line, column)
}
}
if fr.callee != "" {
str = fmt.Sprintf("%s (%s)", fr.callee, str)
}
return str
}
// An Error represents a runtime error, e.g. a TypeError, a ReferenceError, etc.
type Error struct {
_error
}
// Error returns a description of the error
//
// TypeError: 'def' is not a function
//
func (err Error) Error() string {
return err.format()
}
// String returns a description of the error and a trace of where the
// error occurred.
//
// TypeError: 'def' is not a function
// at xyz (<anonymous>:3:9)
// at <anonymous>:7:1/
//
func (err Error) String() string {
return err.formatWithStack()
}
func (err _error) describe(format string, in ...interface{}) string {
return fmt.Sprintf(format, in...)
}
func (self _error) messageValue() Value {
if self.message == "" {
return Value{}
}
return toValue_string(self.message)
}
func (rt *_runtime) typeErrorResult(throw bool) bool {
if throw {
panic(rt.panicTypeError())
}
return false
}
func newError(rt *_runtime, name string, stackFramesToPop int, in ...interface{}) _error {
err := _error{
name: name,
offset: -1,
}
description := ""
length := len(in)
if rt != nil && rt.scope != nil {
scope := rt.scope
for i := 0; i < stackFramesToPop; i++ {
if scope.outer != nil {
scope = scope.outer
}
}
frame := scope.frame
if length > 0 {
if at, ok := in[length-1].(_at); ok {
in = in[0 : length-1]
if scope != nil {
frame.offset = int(at)
}
length--
}
if length > 0 {
description, in = in[0].(string), in[1:]
}
}
limit := rt.traceLimit
err.trace = append(err.trace, frame)
if scope != nil {
for scope = scope.outer; scope != nil; scope = scope.outer {
if limit--; limit == 0 {
break
}
if scope.frame.offset >= 0 {
err.trace = append(err.trace, scope.frame)
}
}
}
} else {
if length > 0 {
description, in = in[0].(string), in[1:]
}
}
err.message = err.describe(description, in...)
return err
}
func (rt *_runtime) panicTypeError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "TypeError", 0, argumentList...),
}
}
func (rt *_runtime) panicReferenceError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "ReferenceError", 0, argumentList...),
}
}
func (rt *_runtime) panicURIError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "URIError", 0, argumentList...),
}
}
func (rt *_runtime) panicSyntaxError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "SyntaxError", 0, argumentList...),
}
}
func (rt *_runtime) panicRangeError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "RangeError", 0, argumentList...),
}
}
func catchPanic(function func()) (err error) {
defer func() {
if caught := recover(); caught != nil {
if exception, ok := caught.(*_exception); ok {
caught = exception.eject()
}
switch caught := caught.(type) {
case *Error:
err = caught
return
case _error:
err = &Error{caught}
return
case Value:
if vl := caught._object(); vl != nil {
switch vl := vl.value.(type) {
case _error:
err = &Error{vl}
return
}
}
err = errors.New(caught.string())
return
}
panic(caught)
}
}()
function()
return nil
}
+318
View File
@@ -0,0 +1,318 @@
package otto
import (
"fmt"
"math"
"strings"
"github.com/robertkrimen/otto/token"
)
func (self *_runtime) evaluateMultiply(left float64, right float64) Value {
// TODO 11.5.1
return Value{}
}
func (self *_runtime) evaluateDivide(left float64, right float64) Value {
if math.IsNaN(left) || math.IsNaN(right) {
return NaNValue()
}
if math.IsInf(left, 0) && math.IsInf(right, 0) {
return NaNValue()
}
if left == 0 && right == 0 {
return NaNValue()
}
if math.IsInf(left, 0) {
if math.Signbit(left) == math.Signbit(right) {
return positiveInfinityValue()
} else {
return negativeInfinityValue()
}
}
if math.IsInf(right, 0) {
if math.Signbit(left) == math.Signbit(right) {
return positiveZeroValue()
} else {
return negativeZeroValue()
}
}
if right == 0 {
if math.Signbit(left) == math.Signbit(right) {
return positiveInfinityValue()
} else {
return negativeInfinityValue()
}
}
return toValue_float64(left / right)
}
func (self *_runtime) evaluateModulo(left float64, right float64) Value {
// TODO 11.5.3
return Value{}
}
func (self *_runtime) calculateBinaryExpression(operator token.Token, left Value, right Value) Value {
leftValue := left.resolve()
switch operator {
// Additive
case token.PLUS:
leftValue = toPrimitive(leftValue)
rightValue := right.resolve()
rightValue = toPrimitive(rightValue)
if leftValue.IsString() || rightValue.IsString() {
return toValue_string(strings.Join([]string{leftValue.string(), rightValue.string()}, ""))
} else {
return toValue_float64(leftValue.float64() + rightValue.float64())
}
case token.MINUS:
rightValue := right.resolve()
return toValue_float64(leftValue.float64() - rightValue.float64())
// Multiplicative
case token.MULTIPLY:
rightValue := right.resolve()
return toValue_float64(leftValue.float64() * rightValue.float64())
case token.SLASH:
rightValue := right.resolve()
return self.evaluateDivide(leftValue.float64(), rightValue.float64())
case token.REMAINDER:
rightValue := right.resolve()
return toValue_float64(math.Mod(leftValue.float64(), rightValue.float64()))
// Logical
case token.LOGICAL_AND:
left := leftValue.bool()
if !left {
return falseValue
}
return toValue_bool(right.resolve().bool())
case token.LOGICAL_OR:
left := leftValue.bool()
if left {
return trueValue
}
return toValue_bool(right.resolve().bool())
// Bitwise
case token.AND:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) & toInt32(rightValue))
case token.OR:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) | toInt32(rightValue))
case token.EXCLUSIVE_OR:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) ^ toInt32(rightValue))
// Shift
// (Masking of 0x1f is to restrict the shift to a maximum of 31 places)
case token.SHIFT_LEFT:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) << (toUint32(rightValue) & 0x1f))
case token.SHIFT_RIGHT:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.UNSIGNED_SHIFT_RIGHT:
rightValue := right.resolve()
// Shifting an unsigned integer is a logical shift
return toValue_uint32(toUint32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.INSTANCEOF:
rightValue := right.resolve()
if !rightValue.IsObject() {
panic(self.panicTypeError("Expecting a function in instanceof check, but got: %v", rightValue))
}
return toValue_bool(rightValue._object().hasInstance(leftValue))
case token.IN:
rightValue := right.resolve()
if !rightValue.IsObject() {
panic(self.panicTypeError())
}
return toValue_bool(rightValue._object().hasProperty(leftValue.string()))
}
panic(hereBeDragons(operator))
}
func valueKindDispatchKey(left _valueKind, right _valueKind) int {
return (int(left) << 2) + int(right)
}
var equalDispatch map[int](func(Value, Value) bool) = makeEqualDispatch()
func makeEqualDispatch() map[int](func(Value, Value) bool) {
key := valueKindDispatchKey
return map[int](func(Value, Value) bool){
key(valueNumber, valueObject): func(x Value, y Value) bool { return x.float64() == y.float64() },
key(valueString, valueObject): func(x Value, y Value) bool { return x.float64() == y.float64() },
key(valueObject, valueNumber): func(x Value, y Value) bool { return x.float64() == y.float64() },
key(valueObject, valueString): func(x Value, y Value) bool { return x.float64() == y.float64() },
}
}
type _lessThanResult int
const (
lessThanFalse _lessThanResult = iota
lessThanTrue
lessThanUndefined
)
func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult {
x := Value{}
y := x
if leftFirst {
x = toNumberPrimitive(left)
y = toNumberPrimitive(right)
} else {
y = toNumberPrimitive(right)
x = toNumberPrimitive(left)
}
result := false
if x.kind != valueString || y.kind != valueString {
x, y := x.float64(), y.float64()
if math.IsNaN(x) || math.IsNaN(y) {
return lessThanUndefined
}
result = x < y
} else {
x, y := x.string(), y.string()
result = x < y
}
if result {
return lessThanTrue
}
return lessThanFalse
}
// FIXME Probably a map is not the most efficient way to do this
var lessThanTable [4](map[_lessThanResult]bool) = [4](map[_lessThanResult]bool){
// <
map[_lessThanResult]bool{
lessThanFalse: false,
lessThanTrue: true,
lessThanUndefined: false,
},
// >
map[_lessThanResult]bool{
lessThanFalse: false,
lessThanTrue: true,
lessThanUndefined: false,
},
// <=
map[_lessThanResult]bool{
lessThanFalse: true,
lessThanTrue: false,
lessThanUndefined: false,
},
// >=
map[_lessThanResult]bool{
lessThanFalse: true,
lessThanTrue: false,
lessThanUndefined: false,
},
}
func (self *_runtime) calculateComparison(comparator token.Token, left Value, right Value) bool {
// FIXME Use strictEqualityComparison?
// TODO This might be redundant now (with regards to evaluateComparison)
x := left.resolve()
y := right.resolve()
kindEqualKind := false
result := true
negate := false
switch comparator {
case token.LESS:
result = lessThanTable[0][calculateLessThan(x, y, true)]
case token.GREATER:
result = lessThanTable[1][calculateLessThan(y, x, false)]
case token.LESS_OR_EQUAL:
result = lessThanTable[2][calculateLessThan(y, x, false)]
case token.GREATER_OR_EQUAL:
result = lessThanTable[3][calculateLessThan(x, y, true)]
case token.STRICT_NOT_EQUAL:
negate = true
fallthrough
case token.STRICT_EQUAL:
if x.kind != y.kind {
result = false
} else {
kindEqualKind = true
}
case token.NOT_EQUAL:
negate = true
fallthrough
case token.EQUAL:
if x.kind == y.kind {
kindEqualKind = true
} else if x.kind <= valueNull && y.kind <= valueNull {
result = true
} else if x.kind <= valueNull || y.kind <= valueNull {
result = false
} else if x.kind <= valueString && y.kind <= valueString {
result = x.float64() == y.float64()
} else if x.kind == valueBoolean {
result = self.calculateComparison(token.EQUAL, toValue_float64(x.float64()), y)
} else if y.kind == valueBoolean {
result = self.calculateComparison(token.EQUAL, x, toValue_float64(y.float64()))
} else if x.kind == valueObject {
result = self.calculateComparison(token.EQUAL, toPrimitive(x), y)
} else if y.kind == valueObject {
result = self.calculateComparison(token.EQUAL, x, toPrimitive(y))
} else {
panic(hereBeDragons("Unable to test for equality: %v ==? %v", x, y))
}
default:
panic(fmt.Errorf("Unknown comparator %s", comparator.String()))
}
if kindEqualKind {
switch x.kind {
case valueUndefined, valueNull:
result = true
case valueNumber:
x := x.float64()
y := y.float64()
if math.IsNaN(x) || math.IsNaN(y) {
result = false
} else {
result = x == y
}
case valueString:
result = x.string() == y.string()
case valueBoolean:
result = x.bool() == y.bool()
case valueObject:
result = x._object() == y._object()
default:
goto ERROR
}
}
if negate {
result = !result
}
return result
ERROR:
panic(hereBeDragons("%v (%v) %s %v (%v)", x, x.kind, comparator, y, y.kind))
}
+110
View File
@@ -0,0 +1,110 @@
# file
--
import "github.com/robertkrimen/otto/file"
Package file encapsulates the file abstractions used by the ast & parser.
## Usage
#### type File
```go
type File struct {
}
```
#### func NewFile
```go
func NewFile(filename, src string, base int) *File
```
#### func (*File) Base
```go
func (fl *File) Base() int
```
#### func (*File) Name
```go
func (fl *File) Name() string
```
#### func (*File) Source
```go
func (fl *File) Source() string
```
#### type FileSet
```go
type FileSet struct {
}
```
A FileSet represents a set of source files.
#### func (*FileSet) AddFile
```go
func (self *FileSet) AddFile(filename, src string) int
```
AddFile adds a new file with the given filename and src.
This an internal method, but exported for cross-package use.
#### func (*FileSet) File
```go
func (self *FileSet) File(idx Idx) *File
```
#### func (*FileSet) Position
```go
func (self *FileSet) Position(idx Idx) *Position
```
Position converts an Idx in the FileSet into a Position.
#### type Idx
```go
type Idx int
```
Idx is a compact encoding of a source position within a file set. It can be
converted into a Position for a more convenient, but much larger,
representation.
#### type Position
```go
type Position struct {
Filename string // The filename where the error occurred, if any
Offset int // The src offset
Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count)
}
```
Position describes an arbitrary source position including the filename, line,
and column location.
#### func (*Position) String
```go
func (self *Position) String() string
```
String returns a string in one of several forms:
file:line:column A valid position with filename
line:column A valid position without filename
file An invalid position with filename
- An invalid position without filename
--
**godocdown** http://github.com/robertkrimen/godocdown
+164
View File
@@ -0,0 +1,164 @@
// Package file encapsulates the file abstractions used by the ast & parser.
//
package file
import (
"fmt"
"strings"
"gopkg.in/sourcemap.v1"
)
// Idx is a compact encoding of a source position within a file set.
// It can be converted into a Position for a more convenient, but much
// larger, representation.
type Idx int
// Position describes an arbitrary source position
// including the filename, line, and column location.
type Position struct {
Filename string // The filename where the error occurred, if any
Offset int // The src offset
Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count)
}
// A Position is valid if the line number is > 0.
func (self *Position) isValid() bool {
return self.Line > 0
}
// String returns a string in one of several forms:
//
// file:line:column A valid position with filename
// line:column A valid position without filename
// file An invalid position with filename
// - An invalid position without filename
//
func (self *Position) String() string {
str := self.Filename
if self.isValid() {
if str != "" {
str += ":"
}
str += fmt.Sprintf("%d:%d", self.Line, self.Column)
}
if str == "" {
str = "-"
}
return str
}
// FileSet
// A FileSet represents a set of source files.
type FileSet struct {
files []*File
last *File
}
// AddFile adds a new file with the given filename and src.
//
// This an internal method, but exported for cross-package use.
func (self *FileSet) AddFile(filename, src string) int {
base := self.nextBase()
file := &File{
name: filename,
src: src,
base: base,
}
self.files = append(self.files, file)
self.last = file
return base
}
func (self *FileSet) nextBase() int {
if self.last == nil {
return 1
}
return self.last.base + len(self.last.src) + 1
}
func (self *FileSet) File(idx Idx) *File {
for _, file := range self.files {
if idx <= Idx(file.base+len(file.src)) {
return file
}
}
return nil
}
// Position converts an Idx in the FileSet into a Position.
func (self *FileSet) Position(idx Idx) *Position {
for _, file := range self.files {
if idx <= Idx(file.base+len(file.src)) {
return file.Position(idx - Idx(file.base))
}
}
return nil
}
type File struct {
name string
src string
base int // This will always be 1 or greater
sm *sourcemap.Consumer
}
func NewFile(filename, src string, base int) *File {
return &File{
name: filename,
src: src,
base: base,
}
}
func (fl *File) WithSourceMap(sm *sourcemap.Consumer) *File {
fl.sm = sm
return fl
}
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) Position(idx Idx) *Position {
position := &Position{}
offset := int(idx) - fl.base
if offset >= len(fl.src) || offset < 0 {
return nil
}
src := fl.src[:offset]
position.Filename = fl.name
position.Offset = offset
position.Line = strings.Count(src, "\n") + 1
if index := strings.LastIndex(src, "\n"); index >= 0 {
position.Column = offset - index
} else {
position.Column = len(src) + 1
}
if fl.sm != nil {
if f, _, l, c, ok := fl.sm.Source(position.Line, position.Column); ok {
position.Filename, position.Line, position.Column = f, l, c
}
}
return position
}
+221
View File
@@ -0,0 +1,221 @@
package otto
import (
"strconv"
"time"
)
var (
prototypeValueObject = interface{}(nil)
prototypeValueFunction = _nativeFunctionObject{
call: func(_ FunctionCall) Value {
return Value{}
},
}
prototypeValueString = _stringASCII("")
// TODO Make this just false?
prototypeValueBoolean = Value{
kind: valueBoolean,
value: false,
}
prototypeValueNumber = Value{
kind: valueNumber,
value: 0,
}
prototypeValueDate = _dateObject{
epoch: 0,
isNaN: false,
time: time.Unix(0, 0).UTC(),
value: Value{
kind: valueNumber,
value: 0,
},
}
prototypeValueRegExp = _regExpObject{
regularExpression: nil,
global: false,
ignoreCase: false,
multiline: false,
source: "",
flags: "",
}
)
func newContext() *_runtime {
self := &_runtime{}
self.globalStash = self.newObjectStash(nil, nil)
self.globalObject = self.globalStash.object
_newContext(self)
self.eval = self.globalObject.property["eval"].value.(Value).value.(*_object)
self.globalObject.prototype = self.global.ObjectPrototype
return self
}
func (runtime *_runtime) newBaseObject() *_object {
self := newObject(runtime, "")
return self
}
func (runtime *_runtime) newClassObject(class string) *_object {
return newObject(runtime, class)
}
func (runtime *_runtime) newPrimitiveObject(class string, value Value) *_object {
self := runtime.newClassObject(class)
self.value = value
return self
}
func (self *_object) primitiveValue() Value {
switch value := self.value.(type) {
case Value:
return value
case _stringObject:
return toValue_string(value.String())
}
return Value{}
}
func (self *_object) hasPrimitive() bool {
switch self.value.(type) {
case Value, _stringObject:
return true
}
return false
}
func (runtime *_runtime) newObject() *_object {
self := runtime.newClassObject("Object")
self.prototype = runtime.global.ObjectPrototype
return self
}
func (runtime *_runtime) newArray(length uint32) *_object {
self := runtime.newArrayObject(length)
self.prototype = runtime.global.ArrayPrototype
return self
}
func (runtime *_runtime) newArrayOf(valueArray []Value) *_object {
self := runtime.newArray(uint32(len(valueArray)))
for index, value := range valueArray {
if value.isEmpty() {
continue
}
self.defineProperty(strconv.FormatInt(int64(index), 10), value, 0111, false)
}
return self
}
func (runtime *_runtime) newString(value Value) *_object {
self := runtime.newStringObject(value)
self.prototype = runtime.global.StringPrototype
return self
}
func (runtime *_runtime) newBoolean(value Value) *_object {
self := runtime.newBooleanObject(value)
self.prototype = runtime.global.BooleanPrototype
return self
}
func (runtime *_runtime) newNumber(value Value) *_object {
self := runtime.newNumberObject(value)
self.prototype = runtime.global.NumberPrototype
return self
}
func (runtime *_runtime) newRegExp(patternValue Value, flagsValue Value) *_object {
pattern := ""
flags := ""
if object := patternValue._object(); object != nil && object.class == "RegExp" {
if flagsValue.IsDefined() {
panic(runtime.panicTypeError("Cannot supply flags when constructing one RegExp from another"))
}
regExp := object.regExpValue()
pattern = regExp.source
flags = regExp.flags
} else {
if patternValue.IsDefined() {
pattern = patternValue.string()
}
if flagsValue.IsDefined() {
flags = flagsValue.string()
}
}
return runtime._newRegExp(pattern, flags)
}
func (runtime *_runtime) _newRegExp(pattern string, flags string) *_object {
self := runtime.newRegExpObject(pattern, flags)
self.prototype = runtime.global.RegExpPrototype
return self
}
// TODO Should (probably) be one argument, right? This is redundant
func (runtime *_runtime) newDate(epoch float64) *_object {
self := runtime.newDateObject(epoch)
self.prototype = runtime.global.DatePrototype
return self
}
func (runtime *_runtime) newError(name string, message Value, stackFramesToPop int) *_object {
var self *_object
switch name {
case "EvalError":
return runtime.newEvalError(message)
case "TypeError":
return runtime.newTypeError(message)
case "RangeError":
return runtime.newRangeError(message)
case "ReferenceError":
return runtime.newReferenceError(message)
case "SyntaxError":
return runtime.newSyntaxError(message)
case "URIError":
return runtime.newURIError(message)
}
self = runtime.newErrorObject(name, message, stackFramesToPop)
self.prototype = runtime.global.ErrorPrototype
if name != "" {
self.defineProperty("name", toValue_string(name), 0111, false)
}
return self
}
func (runtime *_runtime) newNativeFunction(name, file string, line int, _nativeFunction _nativeFunction) *_object {
self := runtime.newNativeFunctionObject(name, file, line, _nativeFunction, 0)
self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false)
return self
}
func (runtime *_runtime) newNodeFunction(node *_nodeFunctionLiteral, scopeEnvironment _stash) *_object {
// TODO Implement 13.2 fully
self := runtime.newNodeFunctionObject(node, scopeEnvironment)
self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0101, false)
return self
}
// FIXME Only in one place...
func (runtime *_runtime) newBoundFunction(target *_object, this Value, argumentList []Value) *_object {
self := runtime.newBoundFunctionObject(target, this, argumentList)
self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false)
return self
}
Loaded 50 of 114 files, more files were not shown because too many files have changed in this diff. Show more