mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-13 19:33:41 +00:00
114 files changed
+32361
No files matched your search
+5
@@ -0,0 +1,5 @@
|
||||
/.test
|
||||
/otto/otto
|
||||
/otto/otto-*
|
||||
/test/test-*.js
|
||||
/test/tester
|
||||
+1
@@ -0,0 +1 @@
|
||||
* Designate the filename of "anonymous" source code by the hash (md5/sha1, etc.)
|
||||
+7
@@ -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
@@ -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 $@
|
||||
|
||||
+871
File diff suppressed because it is too large.
Load diff
+1068
File diff suppressed because it is too large.
Load diff
+278
@@ -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
File diff suppressed because it is too large.
Load diff
+217
@@ -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
@@ -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()))
|
||||
}
|
||||
+684
File diff suppressed because it is too large.
Load diff
+28
@@ -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
|
||||
}
|
||||
+615
File diff suppressed because it is too large.
Load diff
+126
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
+656
File diff suppressed because it is too large.
Load diff
+51
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+6680
File diff suppressed because it is too large.
Load diff
+1087
File diff suppressed because it is too large.
Load diff
+156
@@ -0,0 +1,156 @@
|
||||
package otto
|
||||
|
||||
type _object struct {
|
||||
runtime *_runtime
|
||||
|
||||
class string
|
||||
objectClass *_objectClass
|
||||
value interface{}
|
||||
|
||||
prototype *_object
|
||||
extensible bool
|
||||
|
||||
property map[string]_property
|
||||
propertyOrder []string
|
||||
}
|
||||
|
||||
func newObject(runtime *_runtime, class string) *_object {
|
||||
self := &_object{
|
||||
runtime: runtime,
|
||||
class: class,
|
||||
objectClass: _classObject,
|
||||
property: make(map[string]_property),
|
||||
extensible: true,
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
// 8.12
|
||||
|
||||
// 8.12.1
|
||||
func (self *_object) getOwnProperty(name string) *_property {
|
||||
return self.objectClass.getOwnProperty(self, name)
|
||||
}
|
||||
|
||||
// 8.12.2
|
||||
func (self *_object) getProperty(name string) *_property {
|
||||
return self.objectClass.getProperty(self, name)
|
||||
}
|
||||
|
||||
// 8.12.3
|
||||
func (self *_object) get(name string) Value {
|
||||
return self.objectClass.get(self, name)
|
||||
}
|
||||
|
||||
// 8.12.4
|
||||
func (self *_object) canPut(name string) bool {
|
||||
return self.objectClass.canPut(self, name)
|
||||
}
|
||||
|
||||
// 8.12.5
|
||||
func (self *_object) put(name string, value Value, throw bool) {
|
||||
self.objectClass.put(self, name, value, throw)
|
||||
}
|
||||
|
||||
// 8.12.6
|
||||
func (self *_object) hasProperty(name string) bool {
|
||||
return self.objectClass.hasProperty(self, name)
|
||||
}
|
||||
|
||||
func (self *_object) hasOwnProperty(name string) bool {
|
||||
return self.objectClass.hasOwnProperty(self, name)
|
||||
}
|
||||
|
||||
type _defaultValueHint int
|
||||
|
||||
const (
|
||||
defaultValueNoHint _defaultValueHint = iota
|
||||
defaultValueHintString
|
||||
defaultValueHintNumber
|
||||
)
|
||||
|
||||
// 8.12.8
|
||||
func (self *_object) DefaultValue(hint _defaultValueHint) Value {
|
||||
if hint == defaultValueNoHint {
|
||||
if self.class == "Date" {
|
||||
// Date exception
|
||||
hint = defaultValueHintString
|
||||
} else {
|
||||
hint = defaultValueHintNumber
|
||||
}
|
||||
}
|
||||
methodSequence := []string{"valueOf", "toString"}
|
||||
if hint == defaultValueHintString {
|
||||
methodSequence = []string{"toString", "valueOf"}
|
||||
}
|
||||
for _, methodName := range methodSequence {
|
||||
method := self.get(methodName)
|
||||
// FIXME This is redundant...
|
||||
if method.isCallable() {
|
||||
result := method._object().call(toValue_object(self), nil, false, nativeFrame)
|
||||
if result.IsPrimitive() {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic(self.runtime.panicTypeError())
|
||||
}
|
||||
|
||||
func (self *_object) String() string {
|
||||
return self.DefaultValue(defaultValueHintString).string()
|
||||
}
|
||||
|
||||
func (self *_object) defineProperty(name string, value Value, mode _propertyMode, throw bool) bool {
|
||||
return self.defineOwnProperty(name, _property{value, mode}, throw)
|
||||
}
|
||||
|
||||
// 8.12.9
|
||||
func (self *_object) defineOwnProperty(name string, descriptor _property, throw bool) bool {
|
||||
return self.objectClass.defineOwnProperty(self, name, descriptor, throw)
|
||||
}
|
||||
|
||||
func (self *_object) delete(name string, throw bool) bool {
|
||||
return self.objectClass.delete(self, name, throw)
|
||||
}
|
||||
|
||||
func (self *_object) enumerate(all bool, each func(string) bool) {
|
||||
self.objectClass.enumerate(self, all, each)
|
||||
}
|
||||
|
||||
func (self *_object) _exists(name string) bool {
|
||||
_, exists := self.property[name]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (self *_object) _read(name string) (_property, bool) {
|
||||
property, exists := self.property[name]
|
||||
return property, exists
|
||||
}
|
||||
|
||||
func (self *_object) _write(name string, value interface{}, mode _propertyMode) {
|
||||
if value == nil {
|
||||
value = Value{}
|
||||
}
|
||||
_, exists := self.property[name]
|
||||
self.property[name] = _property{value, mode}
|
||||
if !exists {
|
||||
self.propertyOrder = append(self.propertyOrder, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_object) _delete(name string) {
|
||||
_, exists := self.property[name]
|
||||
delete(self.property, name)
|
||||
if exists {
|
||||
for index, property := range self.propertyOrder {
|
||||
if name == property {
|
||||
if index == len(self.propertyOrder)-1 {
|
||||
self.propertyOrder = self.propertyOrder[:index]
|
||||
} else {
|
||||
self.propertyOrder = append(self.propertyOrder[:index], self.propertyOrder[index+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
package otto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type _objectClass struct {
|
||||
getOwnProperty func(*_object, string) *_property
|
||||
getProperty func(*_object, string) *_property
|
||||
get func(*_object, string) Value
|
||||
canPut func(*_object, string) bool
|
||||
put func(*_object, string, Value, bool)
|
||||
hasProperty func(*_object, string) bool
|
||||
hasOwnProperty func(*_object, string) bool
|
||||
defineOwnProperty func(*_object, string, _property, bool) bool
|
||||
delete func(*_object, string, bool) bool
|
||||
enumerate func(*_object, bool, func(string) bool)
|
||||
clone func(*_object, *_object, *_clone) *_object
|
||||
marshalJSON func(*_object) json.Marshaler
|
||||
}
|
||||
|
||||
func objectEnumerate(self *_object, all bool, each func(string) bool) {
|
||||
for _, name := range self.propertyOrder {
|
||||
if all || self.property[name].enumerable() {
|
||||
if !each(name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_classObject,
|
||||
_classArray,
|
||||
_classString,
|
||||
_classArguments,
|
||||
_classGoStruct,
|
||||
_classGoMap,
|
||||
_classGoArray,
|
||||
_classGoSlice,
|
||||
_ *_objectClass
|
||||
)
|
||||
|
||||
func init() {
|
||||
_classObject = &_objectClass{
|
||||
objectGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
objectDefineOwnProperty,
|
||||
objectDelete,
|
||||
objectEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
|
||||
_classArray = &_objectClass{
|
||||
objectGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
arrayDefineOwnProperty,
|
||||
objectDelete,
|
||||
objectEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
|
||||
_classString = &_objectClass{
|
||||
stringGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
objectDefineOwnProperty,
|
||||
objectDelete,
|
||||
stringEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
|
||||
_classArguments = &_objectClass{
|
||||
argumentsGetOwnProperty,
|
||||
objectGetProperty,
|
||||
argumentsGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
argumentsDefineOwnProperty,
|
||||
argumentsDelete,
|
||||
objectEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
|
||||
_classGoStruct = &_objectClass{
|
||||
goStructGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
goStructCanPut,
|
||||
goStructPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
objectDefineOwnProperty,
|
||||
objectDelete,
|
||||
goStructEnumerate,
|
||||
objectClone,
|
||||
goStructMarshalJSON,
|
||||
}
|
||||
|
||||
_classGoMap = &_objectClass{
|
||||
goMapGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
goMapDefineOwnProperty,
|
||||
goMapDelete,
|
||||
goMapEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
|
||||
_classGoArray = &_objectClass{
|
||||
goArrayGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
goArrayDefineOwnProperty,
|
||||
goArrayDelete,
|
||||
goArrayEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
|
||||
_classGoSlice = &_objectClass{
|
||||
goSliceGetOwnProperty,
|
||||
objectGetProperty,
|
||||
objectGet,
|
||||
objectCanPut,
|
||||
objectPut,
|
||||
objectHasProperty,
|
||||
objectHasOwnProperty,
|
||||
goSliceDefineOwnProperty,
|
||||
goSliceDelete,
|
||||
goSliceEnumerate,
|
||||
objectClone,
|
||||
nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Allons-y
|
||||
|
||||
// 8.12.1
|
||||
func objectGetOwnProperty(self *_object, name string) *_property {
|
||||
// Return a _copy_ of the property
|
||||
property, exists := self._read(name)
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return &property
|
||||
}
|
||||
|
||||
// 8.12.2
|
||||
func objectGetProperty(self *_object, name string) *_property {
|
||||
property := self.getOwnProperty(name)
|
||||
if property != nil {
|
||||
return property
|
||||
}
|
||||
if self.prototype != nil {
|
||||
return self.prototype.getProperty(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 8.12.3
|
||||
func objectGet(self *_object, name string) Value {
|
||||
property := self.getProperty(name)
|
||||
if property != nil {
|
||||
return property.get(self)
|
||||
}
|
||||
return Value{}
|
||||
}
|
||||
|
||||
// 8.12.4
|
||||
func objectCanPut(self *_object, name string) bool {
|
||||
canPut, _, _ := _objectCanPut(self, name)
|
||||
return canPut
|
||||
}
|
||||
|
||||
func _objectCanPut(self *_object, name string) (canPut bool, property *_property, setter *_object) {
|
||||
property = self.getOwnProperty(name)
|
||||
if property != nil {
|
||||
switch propertyValue := property.value.(type) {
|
||||
case Value:
|
||||
canPut = property.writable()
|
||||
return
|
||||
case _propertyGetSet:
|
||||
setter = propertyValue[1]
|
||||
canPut = setter != nil
|
||||
return
|
||||
default:
|
||||
panic(self.runtime.panicTypeError())
|
||||
}
|
||||
}
|
||||
|
||||
if self.prototype == nil {
|
||||
return self.extensible, nil, nil
|
||||
}
|
||||
|
||||
property = self.prototype.getProperty(name)
|
||||
if property == nil {
|
||||
return self.extensible, nil, nil
|
||||
}
|
||||
|
||||
switch propertyValue := property.value.(type) {
|
||||
case Value:
|
||||
if !self.extensible {
|
||||
return false, nil, nil
|
||||
}
|
||||
return property.writable(), nil, nil
|
||||
case _propertyGetSet:
|
||||
setter = propertyValue[1]
|
||||
canPut = setter != nil
|
||||
return
|
||||
default:
|
||||
panic(self.runtime.panicTypeError())
|
||||
}
|
||||
}
|
||||
|
||||
// 8.12.5
|
||||
func objectPut(self *_object, name string, value Value, throw bool) {
|
||||
|
||||
if true {
|
||||
// Shortcut...
|
||||
//
|
||||
// So, right now, every class is using objectCanPut and every class
|
||||
// is using objectPut.
|
||||
//
|
||||
// If that were to no longer be the case, we would have to have
|
||||
// something to detect that here, so that we do not use an
|
||||
// incompatible canPut routine
|
||||
canPut, property, setter := _objectCanPut(self, name)
|
||||
if !canPut {
|
||||
self.runtime.typeErrorResult(throw)
|
||||
} else if setter != nil {
|
||||
setter.call(toValue(self), []Value{value}, false, nativeFrame)
|
||||
} else if property != nil {
|
||||
property.value = value
|
||||
self.defineOwnProperty(name, *property, throw)
|
||||
} else {
|
||||
self.defineProperty(name, value, 0111, throw)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// The long way...
|
||||
//
|
||||
// Right now, code should never get here, see above
|
||||
if !self.canPut(name) {
|
||||
self.runtime.typeErrorResult(throw)
|
||||
return
|
||||
}
|
||||
|
||||
property := self.getOwnProperty(name)
|
||||
if property == nil {
|
||||
property = self.getProperty(name)
|
||||
if property != nil {
|
||||
if getSet, isAccessor := property.value.(_propertyGetSet); isAccessor {
|
||||
getSet[1].call(toValue(self), []Value{value}, false, nativeFrame)
|
||||
return
|
||||
}
|
||||
}
|
||||
self.defineProperty(name, value, 0111, throw)
|
||||
} else {
|
||||
switch propertyValue := property.value.(type) {
|
||||
case Value:
|
||||
property.value = value
|
||||
self.defineOwnProperty(name, *property, throw)
|
||||
case _propertyGetSet:
|
||||
if propertyValue[1] != nil {
|
||||
propertyValue[1].call(toValue(self), []Value{value}, false, nativeFrame)
|
||||
return
|
||||
}
|
||||
if throw {
|
||||
panic(self.runtime.panicTypeError())
|
||||
}
|
||||
default:
|
||||
panic(self.runtime.panicTypeError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8.12.6
|
||||
func objectHasProperty(self *_object, name string) bool {
|
||||
return self.getProperty(name) != nil
|
||||
}
|
||||
|
||||
func objectHasOwnProperty(self *_object, name string) bool {
|
||||
return self.getOwnProperty(name) != nil
|
||||
}
|
||||
|
||||
// 8.12.9
|
||||
func objectDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool {
|
||||
property, exists := self._read(name)
|
||||
{
|
||||
if !exists {
|
||||
if !self.extensible {
|
||||
goto Reject
|
||||
}
|
||||
if newGetSet, isAccessor := descriptor.value.(_propertyGetSet); isAccessor {
|
||||
if newGetSet[0] == &_nilGetSetObject {
|
||||
newGetSet[0] = nil
|
||||
}
|
||||
if newGetSet[1] == &_nilGetSetObject {
|
||||
newGetSet[1] = nil
|
||||
}
|
||||
descriptor.value = newGetSet
|
||||
}
|
||||
self._write(name, descriptor.value, descriptor.mode)
|
||||
return true
|
||||
}
|
||||
if descriptor.isEmpty() {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO Per 8.12.9.6 - We should shortcut here (returning true) if
|
||||
// the current and new (define) properties are the same
|
||||
|
||||
configurable := property.configurable()
|
||||
if !configurable {
|
||||
if descriptor.configurable() {
|
||||
goto Reject
|
||||
}
|
||||
// Test that, if enumerable is set on the property descriptor, then it should
|
||||
// be the same as the existing property
|
||||
if descriptor.enumerateSet() && descriptor.enumerable() != property.enumerable() {
|
||||
goto Reject
|
||||
}
|
||||
}
|
||||
value, isDataDescriptor := property.value.(Value)
|
||||
getSet, _ := property.value.(_propertyGetSet)
|
||||
if descriptor.isGenericDescriptor() {
|
||||
// GenericDescriptor
|
||||
} else if isDataDescriptor != descriptor.isDataDescriptor() {
|
||||
// DataDescriptor <=> AccessorDescriptor
|
||||
if !configurable {
|
||||
goto Reject
|
||||
}
|
||||
} else if isDataDescriptor && descriptor.isDataDescriptor() {
|
||||
// DataDescriptor <=> DataDescriptor
|
||||
if !configurable {
|
||||
if !property.writable() && descriptor.writable() {
|
||||
goto Reject
|
||||
}
|
||||
if !property.writable() {
|
||||
if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) {
|
||||
goto Reject
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// AccessorDescriptor <=> AccessorDescriptor
|
||||
newGetSet, _ := descriptor.value.(_propertyGetSet)
|
||||
presentGet, presentSet := true, true
|
||||
if newGetSet[0] == &_nilGetSetObject {
|
||||
// Present, but nil
|
||||
newGetSet[0] = nil
|
||||
} else if newGetSet[0] == nil {
|
||||
// Missing, not even nil
|
||||
newGetSet[0] = getSet[0]
|
||||
presentGet = false
|
||||
}
|
||||
if newGetSet[1] == &_nilGetSetObject {
|
||||
// Present, but nil
|
||||
newGetSet[1] = nil
|
||||
} else if newGetSet[1] == nil {
|
||||
// Missing, not even nil
|
||||
newGetSet[1] = getSet[1]
|
||||
presentSet = false
|
||||
}
|
||||
if !configurable {
|
||||
if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) {
|
||||
goto Reject
|
||||
}
|
||||
}
|
||||
descriptor.value = newGetSet
|
||||
}
|
||||
{
|
||||
// This section will preserve attributes of
|
||||
// the original property, if necessary
|
||||
value1 := descriptor.value
|
||||
if value1 == nil {
|
||||
value1 = property.value
|
||||
} else if newGetSet, isAccessor := descriptor.value.(_propertyGetSet); isAccessor {
|
||||
if newGetSet[0] == &_nilGetSetObject {
|
||||
newGetSet[0] = nil
|
||||
}
|
||||
if newGetSet[1] == &_nilGetSetObject {
|
||||
newGetSet[1] = nil
|
||||
}
|
||||
value1 = newGetSet
|
||||
}
|
||||
mode1 := descriptor.mode
|
||||
if mode1&0222 != 0 {
|
||||
// TODO Factor this out into somewhere testable
|
||||
// (Maybe put into switch ...)
|
||||
mode0 := property.mode
|
||||
if mode1&0200 != 0 {
|
||||
if descriptor.isDataDescriptor() {
|
||||
mode1 &= ^0200 // Turn off "writable" missing
|
||||
mode1 |= (mode0 & 0100)
|
||||
}
|
||||
}
|
||||
if mode1&020 != 0 {
|
||||
mode1 |= (mode0 & 010)
|
||||
}
|
||||
if mode1&02 != 0 {
|
||||
mode1 |= (mode0 & 01)
|
||||
}
|
||||
mode1 &= 0311 // 0311 to preserve the non-setting on "writable"
|
||||
}
|
||||
self._write(name, value1, mode1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reject:
|
||||
if throw {
|
||||
panic(self.runtime.panicTypeError())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func objectDelete(self *_object, name string, throw bool) bool {
|
||||
property_ := self.getOwnProperty(name)
|
||||
if property_ == nil {
|
||||
return true
|
||||
}
|
||||
if property_.configurable() {
|
||||
self._delete(name)
|
||||
return true
|
||||
}
|
||||
return self.runtime.typeErrorResult(throw)
|
||||
}
|
||||
|
||||
func objectClone(in *_object, out *_object, clone *_clone) *_object {
|
||||
*out = *in
|
||||
|
||||
out.runtime = clone.runtime
|
||||
if out.prototype != nil {
|
||||
out.prototype = clone.object(in.prototype)
|
||||
}
|
||||
out.property = make(map[string]_property, len(in.property))
|
||||
out.propertyOrder = make([]string, len(in.propertyOrder))
|
||||
copy(out.propertyOrder, in.propertyOrder)
|
||||
for index, property := range in.property {
|
||||
out.property[index] = clone.property(property)
|
||||
}
|
||||
|
||||
switch value := in.value.(type) {
|
||||
case _nativeFunctionObject:
|
||||
out.value = value
|
||||
case _bindFunctionObject:
|
||||
out.value = _bindFunctionObject{
|
||||
target: clone.object(value.target),
|
||||
this: clone.value(value.this),
|
||||
argumentList: clone.valueArray(value.argumentList),
|
||||
}
|
||||
case _nodeFunctionObject:
|
||||
out.value = _nodeFunctionObject{
|
||||
node: value.node,
|
||||
stash: clone.stash(value.stash),
|
||||
}
|
||||
case _argumentsObject:
|
||||
out.value = value.clone(clone)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
+770
File diff suppressed because it is too large.
Load diff
+178
@@ -0,0 +1,178 @@
|
||||
package otto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
runtime_ "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var isIdentifier_Regexp *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z\$][a-zA-Z0-9\$]*$`)
|
||||
|
||||
func isIdentifier(string_ string) bool {
|
||||
return isIdentifier_Regexp.MatchString(string_)
|
||||
}
|
||||
|
||||
func (self *_runtime) toValueArray(arguments ...interface{}) []Value {
|
||||
length := len(arguments)
|
||||
if length == 1 {
|
||||
if valueArray, ok := arguments[0].([]Value); ok {
|
||||
return valueArray
|
||||
}
|
||||
return []Value{self.toValue(arguments[0])}
|
||||
}
|
||||
|
||||
valueArray := make([]Value, length)
|
||||
for index, value := range arguments {
|
||||
valueArray[index] = self.toValue(value)
|
||||
}
|
||||
|
||||
return valueArray
|
||||
}
|
||||
|
||||
func stringToArrayIndex(name string) int64 {
|
||||
index, err := strconv.ParseInt(name, 10, 64)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
if index < 0 {
|
||||
return -1
|
||||
}
|
||||
if index >= maxUint32 {
|
||||
// The value 2^32 (or above) is not a valid index because
|
||||
// you cannot store a uint32 length for an index of uint32
|
||||
return -1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func isUint32(value int64) bool {
|
||||
return value >= 0 && value <= maxUint32
|
||||
}
|
||||
|
||||
func arrayIndexToString(index int64) string {
|
||||
return strconv.FormatInt(index, 10)
|
||||
}
|
||||
|
||||
func valueOfArrayIndex(array []Value, index int) Value {
|
||||
value, _ := getValueOfArrayIndex(array, index)
|
||||
return value
|
||||
}
|
||||
|
||||
func getValueOfArrayIndex(array []Value, index int) (Value, bool) {
|
||||
if index >= 0 && index < len(array) {
|
||||
value := array[index]
|
||||
if !value.isEmpty() {
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
return Value{}, false
|
||||
}
|
||||
|
||||
// A range index can be anything from 0 up to length. It is NOT safe to use as an index
|
||||
// to an array, but is useful for slicing and in some ECMA algorithms.
|
||||
func valueToRangeIndex(indexValue Value, length int64, negativeIsZero bool) int64 {
|
||||
index := indexValue.number().int64
|
||||
if negativeIsZero {
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
// minimum(index, length)
|
||||
if index >= length {
|
||||
index = length
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
index += length
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
} else {
|
||||
if index > length {
|
||||
index = length
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func rangeStartEnd(array []Value, size int64, negativeIsZero bool) (start, end int64) {
|
||||
start = valueToRangeIndex(valueOfArrayIndex(array, 0), size, negativeIsZero)
|
||||
if len(array) == 1 {
|
||||
// If there is only the start argument, then end = size
|
||||
end = size
|
||||
return
|
||||
}
|
||||
|
||||
// Assuming the argument is undefined...
|
||||
end = size
|
||||
endValue := valueOfArrayIndex(array, 1)
|
||||
if !endValue.IsUndefined() {
|
||||
// Which it is not, so get the value as an array index
|
||||
end = valueToRangeIndex(endValue, size, negativeIsZero)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func rangeStartLength(source []Value, size int64) (start, length int64) {
|
||||
start = valueToRangeIndex(valueOfArrayIndex(source, 0), size, false)
|
||||
|
||||
// Assume the second argument is missing or undefined
|
||||
length = int64(size)
|
||||
if len(source) == 1 {
|
||||
// If there is only the start argument, then length = size
|
||||
return
|
||||
}
|
||||
|
||||
lengthValue := valueOfArrayIndex(source, 1)
|
||||
if !lengthValue.IsUndefined() {
|
||||
// Which it is not, so get the value as an array index
|
||||
length = lengthValue.number().int64
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func boolFields(input string) (result map[string]bool) {
|
||||
result = map[string]bool{}
|
||||
for _, word := range strings.Fields(input) {
|
||||
result[word] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func hereBeDragons(arguments ...interface{}) string {
|
||||
pc, _, _, _ := runtime_.Caller(1)
|
||||
name := runtime_.FuncForPC(pc).Name()
|
||||
message := fmt.Sprintf("Here be dragons -- %s", name)
|
||||
if len(arguments) > 0 {
|
||||
message += ": "
|
||||
argument0 := fmt.Sprintf("%s", arguments[0])
|
||||
if len(arguments) == 1 {
|
||||
message += argument0
|
||||
} else {
|
||||
message += fmt.Sprintf(argument0, arguments[1:]...)
|
||||
}
|
||||
} else {
|
||||
message += "."
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func throwHereBeDragons(arguments ...interface{}) {
|
||||
panic(hereBeDragons(arguments...))
|
||||
}
|
||||
|
||||
func eachPair(list []interface{}, fn func(_0, _1 interface{})) {
|
||||
for len(list) > 0 {
|
||||
var _0, _1 interface{}
|
||||
_0 = list[0]
|
||||
list = list[1:] // Pop off first
|
||||
if len(list) > 0 {
|
||||
_1 = list[0]
|
||||
list = list[1:] // Pop off second
|
||||
}
|
||||
fn(_0, _1)
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
.PHONY: test
|
||||
|
||||
test:
|
||||
go test
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
# parser
|
||||
--
|
||||
import "github.com/robertkrimen/otto/parser"
|
||||
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
|
||||
filename := "" // A filename is optional
|
||||
src := `
|
||||
// Sample xyzzy example
|
||||
(function(){
|
||||
if (3.14159 > 0) {
|
||||
console.log("Hello, World.");
|
||||
return;
|
||||
}
|
||||
|
||||
var xyzzy = NaN;
|
||||
console.log("Nothing happens.");
|
||||
return xyzzy;
|
||||
})();
|
||||
`
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, filename, src, 0)
|
||||
|
||||
|
||||
### Warning
|
||||
|
||||
The parser and AST interfaces are still works-in-progress (particularly where
|
||||
node types are concerned) and may change in the future.
|
||||
|
||||
## Usage
|
||||
|
||||
#### func ParseFile
|
||||
|
||||
```go
|
||||
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
|
||||
```
|
||||
ParseFile parses the source code of a single JavaScript/ECMAScript source file
|
||||
and returns the corresponding ast.Program node.
|
||||
|
||||
If fileSet == nil, ParseFile parses source without a FileSet. If fileSet != nil,
|
||||
ParseFile first adds filename and src to fileSet.
|
||||
|
||||
The filename argument is optional and is used for labelling errors, etc.
|
||||
|
||||
src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST
|
||||
always be in UTF-8.
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
|
||||
|
||||
#### func ParseFunction
|
||||
|
||||
```go
|
||||
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error)
|
||||
```
|
||||
ParseFunction parses a given parameter list and body as a function and returns
|
||||
the corresponding ast.FunctionLiteral node.
|
||||
|
||||
The parameter list, if any, should be a comma-separated list of identifiers.
|
||||
|
||||
#### func ReadSource
|
||||
|
||||
```go
|
||||
func ReadSource(filename string, src interface{}) ([]byte, error)
|
||||
```
|
||||
|
||||
#### func TransformRegExp
|
||||
|
||||
```go
|
||||
func TransformRegExp(pattern string) (string, error)
|
||||
```
|
||||
TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
|
||||
|
||||
re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
|
||||
backreference (\1, \2, ...) will cause an error.
|
||||
|
||||
re2 (Go) has a different definition for \s: [\t\n\f\r ]. The JavaScript
|
||||
definition, on the other hand, also includes \v, Unicode "Separator, Space",
|
||||
etc.
|
||||
|
||||
If the pattern is invalid (not valid even in JavaScript), then this function
|
||||
returns the empty string and an error.
|
||||
|
||||
If the pattern is valid, but incompatible (contains a lookahead or
|
||||
backreference), then this function returns the transformation (a non-empty
|
||||
string) AND an error.
|
||||
|
||||
#### type Error
|
||||
|
||||
```go
|
||||
type Error struct {
|
||||
Position file.Position
|
||||
Message string
|
||||
}
|
||||
```
|
||||
|
||||
An Error represents a parsing error. It includes the position where the error
|
||||
occurred and a message/description.
|
||||
|
||||
#### func (Error) Error
|
||||
|
||||
```go
|
||||
func (self Error) Error() string
|
||||
```
|
||||
|
||||
#### type ErrorList
|
||||
|
||||
```go
|
||||
type ErrorList []*Error
|
||||
```
|
||||
|
||||
ErrorList is a list of *Errors.
|
||||
|
||||
#### func (*ErrorList) Add
|
||||
|
||||
```go
|
||||
func (self *ErrorList) Add(position file.Position, msg string)
|
||||
```
|
||||
Add adds an Error with given position and message to an ErrorList.
|
||||
|
||||
#### func (ErrorList) Err
|
||||
|
||||
```go
|
||||
func (self ErrorList) Err() error
|
||||
```
|
||||
Err returns an error equivalent to this ErrorList. If the list is empty, Err
|
||||
returns nil.
|
||||
|
||||
#### func (ErrorList) Error
|
||||
|
||||
```go
|
||||
func (self ErrorList) Error() string
|
||||
```
|
||||
Error implements the Error interface.
|
||||
|
||||
#### func (ErrorList) Len
|
||||
|
||||
```go
|
||||
func (self ErrorList) Len() int
|
||||
```
|
||||
|
||||
#### func (ErrorList) Less
|
||||
|
||||
```go
|
||||
func (self ErrorList) Less(i, j int) bool
|
||||
```
|
||||
|
||||
#### func (*ErrorList) Reset
|
||||
|
||||
```go
|
||||
func (self *ErrorList) Reset()
|
||||
```
|
||||
Reset resets an ErrorList to no errors.
|
||||
|
||||
#### func (ErrorList) Sort
|
||||
|
||||
```go
|
||||
func (self ErrorList) Sort()
|
||||
```
|
||||
|
||||
#### func (ErrorList) Swap
|
||||
|
||||
```go
|
||||
func (self ErrorList) Swap(i, j int)
|
||||
```
|
||||
|
||||
#### type Mode
|
||||
|
||||
```go
|
||||
type Mode uint
|
||||
```
|
||||
|
||||
A Mode value is a set of flags (or 0). They control optional parser
|
||||
functionality.
|
||||
|
||||
```go
|
||||
const (
|
||||
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
|
||||
)
|
||||
```
|
||||
|
||||
--
|
||||
**godocdown** http://github.com/robertkrimen/godocdown
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// This file was AUTOMATICALLY GENERATED by dbg-import (smuggol) for github.com/robertkrimen/dbg
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
Dbg "github.com/robertkrimen/otto/dbg"
|
||||
)
|
||||
|
||||
var dbg, dbgf = Dbg.New()
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/robertkrimen/otto/file"
|
||||
"github.com/robertkrimen/otto/token"
|
||||
)
|
||||
|
||||
const (
|
||||
err_UnexpectedToken = "Unexpected token %v"
|
||||
err_UnexpectedEndOfInput = "Unexpected end of input"
|
||||
err_UnexpectedEscape = "Unexpected escape"
|
||||
)
|
||||
|
||||
// UnexpectedNumber: 'Unexpected number',
|
||||
// UnexpectedString: 'Unexpected string',
|
||||
// UnexpectedIdentifier: 'Unexpected identifier',
|
||||
// UnexpectedReserved: 'Unexpected reserved word',
|
||||
// NewlineAfterThrow: 'Illegal newline after throw',
|
||||
// InvalidRegExp: 'Invalid regular expression',
|
||||
// UnterminatedRegExp: 'Invalid regular expression: missing /',
|
||||
// InvalidLHSInAssignment: 'Invalid left-hand side in assignment',
|
||||
// InvalidLHSInForIn: 'Invalid left-hand side in for-in',
|
||||
// MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
|
||||
// NoCatchOrFinally: 'Missing catch or finally after try',
|
||||
// UnknownLabel: 'Undefined label \'%0\'',
|
||||
// Redeclaration: '%0 \'%1\' has already been declared',
|
||||
// IllegalContinue: 'Illegal continue statement',
|
||||
// IllegalBreak: 'Illegal break statement',
|
||||
// IllegalReturn: 'Illegal return statement',
|
||||
// StrictModeWith: 'Strict mode code may not include a with statement',
|
||||
// StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode',
|
||||
// StrictVarName: 'Variable name may not be eval or arguments in strict mode',
|
||||
// StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode',
|
||||
// StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
|
||||
// StrictFunctionName: 'Function name may not be eval or arguments in strict mode',
|
||||
// StrictOctalLiteral: 'Octal literals are not allowed in strict mode.',
|
||||
// StrictDelete: 'Delete of an unqualified identifier in strict mode.',
|
||||
// StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode',
|
||||
// AccessorDataProperty: 'Object literal may not have data and accessor property with the same name',
|
||||
// AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name',
|
||||
// StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode',
|
||||
// StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
|
||||
// StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
|
||||
// StrictReservedWord: 'Use of future reserved word in strict mode'
|
||||
|
||||
// A SyntaxError is a description of an ECMAScript syntax error.
|
||||
|
||||
// An Error represents a parsing error. It includes the position where the error occurred and a message/description.
|
||||
type Error struct {
|
||||
Position file.Position
|
||||
Message string
|
||||
}
|
||||
|
||||
// FIXME Should this be "SyntaxError"?
|
||||
|
||||
func (self Error) Error() string {
|
||||
filename := self.Position.Filename
|
||||
if filename == "" {
|
||||
filename = "(anonymous)"
|
||||
}
|
||||
return fmt.Sprintf("%s: Line %d:%d %s",
|
||||
filename,
|
||||
self.Position.Line,
|
||||
self.Position.Column,
|
||||
self.Message,
|
||||
)
|
||||
}
|
||||
|
||||
func (self *_parser) error(place interface{}, msg string, msgValues ...interface{}) *Error {
|
||||
idx := file.Idx(0)
|
||||
switch place := place.(type) {
|
||||
case int:
|
||||
idx = self.idxOf(place)
|
||||
case file.Idx:
|
||||
if place == 0 {
|
||||
idx = self.idxOf(self.chrOffset)
|
||||
} else {
|
||||
idx = place
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("error(%T, ...)", place))
|
||||
}
|
||||
|
||||
position := self.position(idx)
|
||||
msg = fmt.Sprintf(msg, msgValues...)
|
||||
self.errors.Add(position, msg)
|
||||
return self.errors[len(self.errors)-1]
|
||||
}
|
||||
|
||||
func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error {
|
||||
if chr == -1 {
|
||||
return self.error(idx, err_UnexpectedEndOfInput)
|
||||
}
|
||||
return self.error(idx, err_UnexpectedToken, token.ILLEGAL)
|
||||
}
|
||||
|
||||
func (self *_parser) errorUnexpectedToken(tkn token.Token) error {
|
||||
switch tkn {
|
||||
case token.EOF:
|
||||
return self.error(file.Idx(0), err_UnexpectedEndOfInput)
|
||||
}
|
||||
value := tkn.String()
|
||||
switch tkn {
|
||||
case token.BOOLEAN, token.NULL:
|
||||
value = self.literal
|
||||
case token.IDENTIFIER:
|
||||
return self.error(self.idx, "Unexpected identifier")
|
||||
case token.KEYWORD:
|
||||
// TODO Might be a future reserved word
|
||||
return self.error(self.idx, "Unexpected reserved word")
|
||||
case token.NUMBER:
|
||||
return self.error(self.idx, "Unexpected number")
|
||||
case token.STRING:
|
||||
return self.error(self.idx, "Unexpected string")
|
||||
}
|
||||
return self.error(self.idx, err_UnexpectedToken, value)
|
||||
}
|
||||
|
||||
// ErrorList is a list of *Errors.
|
||||
//
|
||||
type ErrorList []*Error
|
||||
|
||||
// Add adds an Error with given position and message to an ErrorList.
|
||||
func (self *ErrorList) Add(position file.Position, msg string) {
|
||||
*self = append(*self, &Error{position, msg})
|
||||
}
|
||||
|
||||
// Reset resets an ErrorList to no errors.
|
||||
func (self *ErrorList) Reset() { *self = (*self)[0:0] }
|
||||
|
||||
func (self ErrorList) Len() int { return len(self) }
|
||||
func (self ErrorList) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
|
||||
func (self ErrorList) Less(i, j int) bool {
|
||||
x := &self[i].Position
|
||||
y := &self[j].Position
|
||||
if x.Filename < y.Filename {
|
||||
return true
|
||||
}
|
||||
if x.Filename == y.Filename {
|
||||
if x.Line < y.Line {
|
||||
return true
|
||||
}
|
||||
if x.Line == y.Line {
|
||||
return x.Column < y.Column
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self ErrorList) Sort() {
|
||||
sort.Sort(self)
|
||||
}
|
||||
|
||||
// Error implements the Error interface.
|
||||
func (self ErrorList) Error() string {
|
||||
switch len(self) {
|
||||
case 0:
|
||||
return "no errors"
|
||||
case 1:
|
||||
return self[0].Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d more errors)", self[0].Error(), len(self)-1)
|
||||
}
|
||||
|
||||
// Err returns an error equivalent to this ErrorList.
|
||||
// If the list is empty, Err returns nil.
|
||||
func (self ErrorList) Err() error {
|
||||
if len(self) == 0 {
|
||||
return nil
|
||||
}
|
||||
return self
|
||||
}
|
||||
+1005
File diff suppressed because it is too large.
Load diff
+866
File diff suppressed because it is too large.
Load diff
+344
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
|
||||
filename := "" // A filename is optional
|
||||
src := `
|
||||
// Sample xyzzy example
|
||||
(function(){
|
||||
if (3.14159 > 0) {
|
||||
console.log("Hello, World.");
|
||||
return;
|
||||
}
|
||||
|
||||
var xyzzy = NaN;
|
||||
console.log("Nothing happens.");
|
||||
return xyzzy;
|
||||
})();
|
||||
`
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, filename, src, 0)
|
||||
|
||||
Warning
|
||||
|
||||
The parser and AST interfaces are still works-in-progress (particularly where
|
||||
node types are concerned) and may change in the future.
|
||||
|
||||
*/
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
"github.com/robertkrimen/otto/file"
|
||||
"github.com/robertkrimen/otto/token"
|
||||
"gopkg.in/sourcemap.v1"
|
||||
)
|
||||
|
||||
// A Mode value is a set of flags (or 0). They control optional parser functionality.
|
||||
type Mode uint
|
||||
|
||||
const (
|
||||
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
|
||||
StoreComments // Store the comments from source to the comments map
|
||||
)
|
||||
|
||||
type _parser struct {
|
||||
str string
|
||||
length int
|
||||
base int
|
||||
|
||||
chr rune // The current character
|
||||
chrOffset int // The offset of current character
|
||||
offset int // The offset after current character (may be greater than 1)
|
||||
|
||||
idx file.Idx // The index of token
|
||||
token token.Token // The token
|
||||
literal string // The literal of the token, if any
|
||||
|
||||
scope *_scope
|
||||
insertSemicolon bool // If we see a newline, then insert an implicit semicolon
|
||||
implicitSemicolon bool // An implicit semicolon exists
|
||||
|
||||
errors ErrorList
|
||||
|
||||
recover struct {
|
||||
// Scratch when trying to seek to the next statement, etc.
|
||||
idx file.Idx
|
||||
count int
|
||||
}
|
||||
|
||||
mode Mode
|
||||
|
||||
file *file.File
|
||||
|
||||
comments *ast.Comments
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Scan() (tkn token.Token, literal string, idx file.Idx)
|
||||
}
|
||||
|
||||
func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser {
|
||||
return &_parser{
|
||||
chr: ' ', // This is set so we can start scanning by skipping whitespace
|
||||
str: src,
|
||||
length: len(src),
|
||||
base: base,
|
||||
file: file.NewFile(filename, src, base).WithSourceMap(sm),
|
||||
comments: ast.NewComments(),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new Parser.
|
||||
func NewParser(filename, src string) Parser {
|
||||
return _newParser(filename, src, 1, nil)
|
||||
}
|
||||
|
||||
func ReadSource(filename string, src interface{}) ([]byte, error) {
|
||||
if src != nil {
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return []byte(src), nil
|
||||
case []byte:
|
||||
return src, nil
|
||||
case *bytes.Buffer:
|
||||
if src != nil {
|
||||
return src.Bytes(), nil
|
||||
}
|
||||
case io.Reader:
|
||||
var bfr bytes.Buffer
|
||||
if _, err := io.Copy(&bfr, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bfr.Bytes(), nil
|
||||
}
|
||||
return nil, errors.New("invalid source")
|
||||
}
|
||||
return ioutil.ReadFile(filename)
|
||||
}
|
||||
|
||||
func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return sourcemap.Parse(filename, []byte(src))
|
||||
case []byte:
|
||||
return sourcemap.Parse(filename, src)
|
||||
case *bytes.Buffer:
|
||||
if src != nil {
|
||||
return sourcemap.Parse(filename, src.Bytes())
|
||||
}
|
||||
case io.Reader:
|
||||
var bfr bytes.Buffer
|
||||
if _, err := io.Copy(&bfr, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sourcemap.Parse(filename, bfr.Bytes())
|
||||
case *sourcemap.Consumer:
|
||||
return src, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid sourcemap type")
|
||||
}
|
||||
|
||||
func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) {
|
||||
src, err := ReadSource(filename, javascriptSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sourcemapSource == nil {
|
||||
lines := bytes.Split(src, []byte("\n"))
|
||||
lastLine := lines[len(lines)-1]
|
||||
if bytes.HasPrefix(lastLine, []byte("//# sourceMappingURL=data:application/json")) {
|
||||
bits := bytes.SplitN(lastLine, []byte(","), 2)
|
||||
if len(bits) == 2 {
|
||||
if d, err := base64.StdEncoding.DecodeString(string(bits[1])); err == nil {
|
||||
sourcemapSource = d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sm, err := ReadSourceMap(filename, sourcemapSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := 1
|
||||
if fileSet != nil {
|
||||
base = fileSet.AddFile(filename, string(src))
|
||||
}
|
||||
|
||||
parser := _newParser(filename, string(src), base, sm)
|
||||
parser.mode = mode
|
||||
program, err := parser.parse()
|
||||
program.Comments = parser.comments.CommentMap
|
||||
|
||||
return program, err
|
||||
}
|
||||
|
||||
// ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
|
||||
// the corresponding ast.Program node.
|
||||
//
|
||||
// If fileSet == nil, ParseFile parses source without a FileSet.
|
||||
// If fileSet != nil, ParseFile first adds filename and src to fileSet.
|
||||
//
|
||||
// The filename argument is optional and is used for labelling errors, etc.
|
||||
//
|
||||
// src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
|
||||
//
|
||||
// // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
// program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
|
||||
//
|
||||
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) {
|
||||
return ParseFileWithSourceMap(fileSet, filename, src, nil, mode)
|
||||
}
|
||||
|
||||
// ParseFunction parses a given parameter list and body as a function and returns the
|
||||
// corresponding ast.FunctionLiteral node.
|
||||
//
|
||||
// The parameter list, if any, should be a comma-separated list of identifiers.
|
||||
//
|
||||
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
|
||||
|
||||
src := "(function(" + parameterList + ") {\n" + body + "\n})"
|
||||
|
||||
parser := _newParser("", src, 1, nil)
|
||||
program, err := parser.parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
|
||||
}
|
||||
|
||||
// Scan reads a single token from the source at the current offset, increments the offset and
|
||||
// returns the token.Token token, a string literal representing the value of the token (if applicable)
|
||||
// and it's current file.Idx index.
|
||||
func (self *_parser) Scan() (tkn token.Token, literal string, idx file.Idx) {
|
||||
return self.scan()
|
||||
}
|
||||
|
||||
func (self *_parser) slice(idx0, idx1 file.Idx) string {
|
||||
from := int(idx0) - self.base
|
||||
to := int(idx1) - self.base
|
||||
if from >= 0 && to <= len(self.str) {
|
||||
return self.str[from:to]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *_parser) parse() (*ast.Program, error) {
|
||||
self.next()
|
||||
program := self.parseProgram()
|
||||
if false {
|
||||
self.errors.Sort()
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(program, self.comments.FetchAll(), ast.TRAILING)
|
||||
}
|
||||
|
||||
return program, self.errors.Err()
|
||||
}
|
||||
|
||||
func (self *_parser) next() {
|
||||
self.token, self.literal, self.idx = self.scan()
|
||||
}
|
||||
|
||||
func (self *_parser) optionalSemicolon() {
|
||||
if self.token == token.SEMICOLON {
|
||||
self.next()
|
||||
return
|
||||
}
|
||||
|
||||
if self.implicitSemicolon {
|
||||
self.implicitSemicolon = false
|
||||
return
|
||||
}
|
||||
|
||||
if self.token != token.EOF && self.token != token.RIGHT_BRACE {
|
||||
self.expect(token.SEMICOLON)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) semicolon() {
|
||||
if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
|
||||
if self.implicitSemicolon {
|
||||
self.implicitSemicolon = false
|
||||
return
|
||||
}
|
||||
|
||||
self.expect(token.SEMICOLON)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) idxOf(offset int) file.Idx {
|
||||
return file.Idx(self.base + offset)
|
||||
}
|
||||
|
||||
func (self *_parser) expect(value token.Token) file.Idx {
|
||||
idx := self.idx
|
||||
if self.token != value {
|
||||
self.errorUnexpectedToken(self.token)
|
||||
}
|
||||
self.next()
|
||||
return idx
|
||||
}
|
||||
|
||||
func lineCount(str string) (int, int) {
|
||||
line, last := 0, -1
|
||||
pair := false
|
||||
for index, chr := range str {
|
||||
switch chr {
|
||||
case '\r':
|
||||
line += 1
|
||||
last = index
|
||||
pair = true
|
||||
continue
|
||||
case '\n':
|
||||
if !pair {
|
||||
line += 1
|
||||
}
|
||||
last = index
|
||||
case '\u2028', '\u2029':
|
||||
line += 1
|
||||
last = index + 2
|
||||
}
|
||||
pair = false
|
||||
}
|
||||
return line, last
|
||||
}
|
||||
|
||||
func (self *_parser) position(idx file.Idx) file.Position {
|
||||
position := file.Position{}
|
||||
offset := int(idx) - self.base
|
||||
str := self.str[:offset]
|
||||
position.Filename = self.file.Name()
|
||||
line, last := lineCount(str)
|
||||
position.Line = 1 + line
|
||||
if last >= 0 {
|
||||
position.Column = offset - last
|
||||
} else {
|
||||
position.Column = 1 + len(str)
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type _RegExp_parser struct {
|
||||
str string
|
||||
length int
|
||||
|
||||
chr rune // The current character
|
||||
chrOffset int // The offset of current character
|
||||
offset int // The offset after current character (may be greater than 1)
|
||||
|
||||
errors []error
|
||||
invalid bool // The input is an invalid JavaScript RegExp
|
||||
|
||||
goRegexp *bytes.Buffer
|
||||
}
|
||||
|
||||
// TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
|
||||
//
|
||||
// re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
|
||||
// backreference (\1, \2, ...) will cause an error.
|
||||
//
|
||||
// re2 (Go) has a different definition for \s: [\t\n\f\r ].
|
||||
// The JavaScript definition, on the other hand, also includes \v, Unicode "Separator, Space", etc.
|
||||
//
|
||||
// If the pattern is invalid (not valid even in JavaScript), then this function
|
||||
// returns the empty string and an error.
|
||||
//
|
||||
// If the pattern is valid, but incompatible (contains a lookahead or backreference),
|
||||
// then this function returns the transformation (a non-empty string) AND an error.
|
||||
func TransformRegExp(pattern string) (string, error) {
|
||||
|
||||
if pattern == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// TODO If without \, if without (?=, (?!, then another shortcut
|
||||
|
||||
parser := _RegExp_parser{
|
||||
str: pattern,
|
||||
length: len(pattern),
|
||||
goRegexp: bytes.NewBuffer(make([]byte, 0, 3*len(pattern)/2)),
|
||||
}
|
||||
parser.read() // Pull in the first character
|
||||
parser.scan()
|
||||
var err error
|
||||
if len(parser.errors) > 0 {
|
||||
err = parser.errors[0]
|
||||
}
|
||||
if parser.invalid {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Might not be re2 compatible, but is still a valid JavaScript RegExp
|
||||
return parser.goRegexp.String(), err
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) scan() {
|
||||
for self.chr != -1 {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.pass()
|
||||
self.scanBracket()
|
||||
case ')':
|
||||
self.error(-1, "Unmatched ')'")
|
||||
self.invalid = true
|
||||
self.pass()
|
||||
default:
|
||||
self.pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (...)
|
||||
func (self *_RegExp_parser) scanGroup() {
|
||||
str := self.str[self.chrOffset:]
|
||||
if len(str) > 1 { // A possibility of (?= or (?!
|
||||
if str[0] == '?' {
|
||||
if str[1] == '=' || str[1] == '!' {
|
||||
self.error(-1, "re2: Invalid (%s) <lookahead>", self.str[self.chrOffset:self.chrOffset+2])
|
||||
}
|
||||
}
|
||||
}
|
||||
for self.chr != -1 && self.chr != ')' {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.pass()
|
||||
self.scanBracket()
|
||||
default:
|
||||
self.pass()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if self.chr != ')' {
|
||||
self.error(-1, "Unterminated group")
|
||||
self.invalid = true
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// [...]
|
||||
func (self *_RegExp_parser) scanBracket() {
|
||||
for self.chr != -1 {
|
||||
if self.chr == ']' {
|
||||
break
|
||||
} else if self.chr == '\\' {
|
||||
self.read()
|
||||
self.scanEscape(true)
|
||||
continue
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
if self.chr != ']' {
|
||||
self.error(-1, "Unterminated character class")
|
||||
self.invalid = true
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// \...
|
||||
func (self *_RegExp_parser) scanEscape(inClass bool) {
|
||||
offset := self.chrOffset
|
||||
|
||||
var length, base uint32
|
||||
switch self.chr {
|
||||
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
var value int64
|
||||
size := 0
|
||||
for {
|
||||
digit := int64(digitValue(self.chr))
|
||||
if digit >= 8 {
|
||||
// Not a valid digit
|
||||
break
|
||||
}
|
||||
value = value*8 + digit
|
||||
self.read()
|
||||
size += 1
|
||||
}
|
||||
if size == 1 { // The number of characters read
|
||||
_, err := self.goRegexp.Write([]byte{'\\', byte(value) + '0'})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
if value != 0 {
|
||||
// An invalid backreference
|
||||
self.error(-1, "re2: Invalid \\%d <backreference>", value)
|
||||
}
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
_, err := self.goRegexp.Write(tmp)
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
return
|
||||
|
||||
case '8', '9':
|
||||
size := 0
|
||||
for {
|
||||
digit := digitValue(self.chr)
|
||||
if digit >= 10 {
|
||||
// Not a valid digit
|
||||
break
|
||||
}
|
||||
self.read()
|
||||
size += 1
|
||||
}
|
||||
err := self.goRegexp.WriteByte('\\')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
_, err = self.goRegexp.WriteString(self.str[offset:self.chrOffset])
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.error(-1, "re2: Invalid \\%s <backreference>", self.str[offset:self.chrOffset])
|
||||
return
|
||||
|
||||
case 'x':
|
||||
self.read()
|
||||
length, base = 2, 16
|
||||
|
||||
case 'u':
|
||||
self.read()
|
||||
length, base = 4, 16
|
||||
|
||||
case 'b':
|
||||
if inClass {
|
||||
_, err := self.goRegexp.Write([]byte{'\\', 'x', '0', '8'})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 'B':
|
||||
fallthrough
|
||||
|
||||
case 'd', 'D', 's', 'S', 'w', 'W':
|
||||
// This is slightly broken, because ECMAScript
|
||||
// includes \v in \s, \S, while re2 does not
|
||||
fallthrough
|
||||
|
||||
case '\\':
|
||||
fallthrough
|
||||
|
||||
case 'f', 'n', 'r', 't', 'v':
|
||||
err := self.goRegexp.WriteByte('\\')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.pass()
|
||||
return
|
||||
|
||||
case 'c':
|
||||
self.read()
|
||||
var value int64
|
||||
if 'a' <= self.chr && self.chr <= 'z' {
|
||||
value = int64(self.chr) - 'a' + 1
|
||||
} else if 'A' <= self.chr && self.chr <= 'Z' {
|
||||
value = int64(self.chr) - 'A' + 1
|
||||
} else {
|
||||
err := self.goRegexp.WriteByte('c')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
_, err := self.goRegexp.Write(tmp)
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
|
||||
default:
|
||||
// $ is an identifier character, so we have to have
|
||||
// a special case for it here
|
||||
if self.chr == '$' || !isIdentifierPart(self.chr) {
|
||||
// A non-identifier character needs escaping
|
||||
err := self.goRegexp.WriteByte('\\')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
} else {
|
||||
// Unescape the character for re2
|
||||
}
|
||||
self.pass()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we're a \u.... or \x...
|
||||
valueOffset := self.chrOffset
|
||||
|
||||
var value uint32
|
||||
{
|
||||
length := length
|
||||
for ; length > 0; length-- {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
// Not a valid digit
|
||||
goto skip
|
||||
}
|
||||
value = value*base + digit
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
if length == 4 {
|
||||
_, err := self.goRegexp.Write([]byte{
|
||||
'\\',
|
||||
'x',
|
||||
'{',
|
||||
self.str[valueOffset+0],
|
||||
self.str[valueOffset+1],
|
||||
self.str[valueOffset+2],
|
||||
self.str[valueOffset+3],
|
||||
'}',
|
||||
})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
} else if length == 2 {
|
||||
_, err := self.goRegexp.Write([]byte{
|
||||
'\\',
|
||||
'x',
|
||||
self.str[valueOffset+0],
|
||||
self.str[valueOffset+1],
|
||||
})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
} else {
|
||||
// Should never, ever get here...
|
||||
self.error(-1, "re2: Illegal branch in scanEscape")
|
||||
goto skip
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
skip:
|
||||
_, err := self.goRegexp.WriteString(self.str[offset:self.chrOffset])
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) pass() {
|
||||
if self.chr != -1 {
|
||||
_, err := self.goRegexp.WriteRune(self.chr)
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
|
||||
// TODO Better error reporting, use the offset, etc.
|
||||
func (self *_RegExp_parser) error(offset int, msg string, msgValues ...interface{}) error {
|
||||
err := fmt.Errorf(msg, msgValues...)
|
||||
self.errors = append(self.errors, err)
|
||||
return err
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
)
|
||||
|
||||
type _scope struct {
|
||||
outer *_scope
|
||||
allowIn bool
|
||||
inIteration bool
|
||||
inSwitch bool
|
||||
inFunction bool
|
||||
declarationList []ast.Declaration
|
||||
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (self *_parser) openScope() {
|
||||
self.scope = &_scope{
|
||||
outer: self.scope,
|
||||
allowIn: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) closeScope() {
|
||||
self.scope = self.scope.outer
|
||||
}
|
||||
|
||||
func (self *_scope) declare(declaration ast.Declaration) {
|
||||
self.declarationList = append(self.declarationList, declaration)
|
||||
}
|
||||
|
||||
func (self *_scope) hasLabel(name string) bool {
|
||||
for _, label := range self.labels {
|
||||
if label == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if self.outer != nil && !self.inFunction {
|
||||
// Crossing a function boundary to look for a label is verboten
|
||||
return self.outer.hasLabel(name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
Loaded 50 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user