using robertkrimen/otto, godeps updated

This commit is contained in:
zsfelfoldi 2015-03-20 13:22:01 +01:00
parent 91f9f355b2
commit 8324b683b4
144 changed files with 4987 additions and 13117 deletions

4
Godeps/Godeps.json generated
View File

@ -47,8 +47,8 @@
"Rev": "ccfcd0245381f0c94c68f50626665eed3c6b726a" "Rev": "ccfcd0245381f0c94c68f50626665eed3c6b726a"
}, },
{ {
"ImportPath": "github.com/obscuren/otto", "ImportPath": "github.com/robertkrimen/otto",
"Rev": "cf13cc4228c5e5ce0fe27a7aea90bc10091c4f19" "Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
}, },
{ {
"ImportPath": "github.com/obscuren/qml", "ImportPath": "github.com/obscuren/qml",

File diff suppressed because it is too large Load Diff

View File

@ -1,496 +0,0 @@
/*
Package ast declares types representing a JavaScript AST.
Warning
The parser and AST interfaces are still works-in-progress (particularly where
node types are concerned) and may change in the future.
*/
package ast
import (
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token"
)
// All nodes implement the Node interface.
type Node interface {
Idx0() file.Idx // The index of the first character belonging to the node
Idx1() file.Idx // The index of the first character immediately after the node
}
// ========== //
// Expression //
// ========== //
type (
// All expression nodes implement the Expression interface.
Expression interface {
Node
_expressionNode()
}
ArrayLiteral struct {
LeftBracket file.Idx
RightBracket file.Idx
Value []Expression
}
AssignExpression struct {
Operator token.Token
Left Expression
Right Expression
}
BadExpression struct {
From file.Idx
To file.Idx
}
BinaryExpression struct {
Operator token.Token
Left Expression
Right Expression
Comparison bool
}
BooleanLiteral struct {
Idx file.Idx
Literal string
Value bool
}
BracketExpression struct {
Left Expression
Member Expression
LeftBracket file.Idx
RightBracket file.Idx
}
CallExpression struct {
Callee Expression
LeftParenthesis file.Idx
ArgumentList []Expression
RightParenthesis file.Idx
}
ConditionalExpression struct {
Test Expression
Consequent Expression
Alternate Expression
}
DotExpression struct {
Left Expression
Identifier Identifier
}
FunctionLiteral struct {
Function file.Idx
Name *Identifier
ParameterList *ParameterList
Body Statement
Source string
DeclarationList []Declaration
}
Identifier struct {
Name string
Idx file.Idx
}
NewExpression struct {
New file.Idx
Callee Expression
LeftParenthesis file.Idx
ArgumentList []Expression
RightParenthesis file.Idx
}
NullLiteral struct {
Idx file.Idx
Literal string
}
NumberLiteral struct {
Idx file.Idx
Literal string
Value interface{}
}
ObjectLiteral struct {
LeftBrace file.Idx
RightBrace file.Idx
Value []Property
}
ParameterList struct {
Opening file.Idx
List []*Identifier
Closing file.Idx
}
Property struct {
Key string
Kind string
Value Expression
}
RegExpLiteral struct {
Idx file.Idx
Literal string
Pattern string
Flags string
Value string
}
SequenceExpression struct {
Sequence []Expression
}
StringLiteral struct {
Idx file.Idx
Literal string
Value string
}
ThisExpression struct {
Idx file.Idx
}
UnaryExpression struct {
Operator token.Token
Idx file.Idx // If a prefix operation
Operand Expression
Postfix bool
}
VariableExpression struct {
Name string
Idx file.Idx
Initializer Expression
}
)
// _expressionNode
func (*ArrayLiteral) _expressionNode() {}
func (*AssignExpression) _expressionNode() {}
func (*BadExpression) _expressionNode() {}
func (*BinaryExpression) _expressionNode() {}
func (*BooleanLiteral) _expressionNode() {}
func (*BracketExpression) _expressionNode() {}
func (*CallExpression) _expressionNode() {}
func (*ConditionalExpression) _expressionNode() {}
func (*DotExpression) _expressionNode() {}
func (*FunctionLiteral) _expressionNode() {}
func (*Identifier) _expressionNode() {}
func (*NewExpression) _expressionNode() {}
func (*NullLiteral) _expressionNode() {}
func (*NumberLiteral) _expressionNode() {}
func (*ObjectLiteral) _expressionNode() {}
func (*RegExpLiteral) _expressionNode() {}
func (*SequenceExpression) _expressionNode() {}
func (*StringLiteral) _expressionNode() {}
func (*ThisExpression) _expressionNode() {}
func (*UnaryExpression) _expressionNode() {}
func (*VariableExpression) _expressionNode() {}
// ========= //
// Statement //
// ========= //
type (
// All statement nodes implement the Statement interface.
Statement interface {
Node
_statementNode()
}
BadStatement struct {
From file.Idx
To file.Idx
}
BlockStatement struct {
LeftBrace file.Idx
List []Statement
RightBrace file.Idx
}
BranchStatement struct {
Idx file.Idx
Token token.Token
Label *Identifier
}
CaseStatement struct {
Case file.Idx
Test Expression
Consequent []Statement
}
CatchStatement struct {
Catch file.Idx
Parameter *Identifier
Body Statement
}
DebuggerStatement struct {
Debugger file.Idx
}
DoWhileStatement struct {
Do file.Idx
Test Expression
Body Statement
}
EmptyStatement struct {
Semicolon file.Idx
}
ExpressionStatement struct {
Expression Expression
}
ForInStatement struct {
For file.Idx
Into Expression
Source Expression
Body Statement
}
ForStatement struct {
For file.Idx
Initializer Expression
Update Expression
Test Expression
Body Statement
}
IfStatement struct {
If file.Idx
Test Expression
Consequent Statement
Alternate Statement
}
LabelledStatement struct {
Label *Identifier
Colon file.Idx
Statement Statement
}
ReturnStatement struct {
Return file.Idx
Argument Expression
}
SwitchStatement struct {
Switch file.Idx
Discriminant Expression
Default int
Body []*CaseStatement
}
ThrowStatement struct {
Throw file.Idx
Argument Expression
}
TryStatement struct {
Try file.Idx
Body Statement
Catch *CatchStatement
Finally Statement
}
VariableStatement struct {
Var file.Idx
List []Expression
}
WhileStatement struct {
While file.Idx
Test Expression
Body Statement
}
WithStatement struct {
With file.Idx
Object Expression
Body Statement
}
)
// _statementNode
func (*BadStatement) _statementNode() {}
func (*BlockStatement) _statementNode() {}
func (*BranchStatement) _statementNode() {}
func (*CaseStatement) _statementNode() {}
func (*CatchStatement) _statementNode() {}
func (*DebuggerStatement) _statementNode() {}
func (*DoWhileStatement) _statementNode() {}
func (*EmptyStatement) _statementNode() {}
func (*ExpressionStatement) _statementNode() {}
func (*ForInStatement) _statementNode() {}
func (*ForStatement) _statementNode() {}
func (*IfStatement) _statementNode() {}
func (*LabelledStatement) _statementNode() {}
func (*ReturnStatement) _statementNode() {}
func (*SwitchStatement) _statementNode() {}
func (*ThrowStatement) _statementNode() {}
func (*TryStatement) _statementNode() {}
func (*VariableStatement) _statementNode() {}
func (*WhileStatement) _statementNode() {}
func (*WithStatement) _statementNode() {}
// =========== //
// Declaration //
// =========== //
type (
// All declaration nodes implement the Declaration interface.
Declaration interface {
_declarationNode()
}
FunctionDeclaration struct {
Function *FunctionLiteral
}
VariableDeclaration struct {
Var file.Idx
List []*VariableExpression
}
)
// _declarationNode
func (*FunctionDeclaration) _declarationNode() {}
func (*VariableDeclaration) _declarationNode() {}
// ==== //
// Node //
// ==== //
type Program struct {
Body []Statement
DeclarationList []Declaration
}
// ==== //
// Idx0 //
// ==== //
func (self *ArrayLiteral) Idx0() file.Idx { return self.LeftBracket }
func (self *AssignExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *BadExpression) Idx0() file.Idx { return self.From }
func (self *BinaryExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *BooleanLiteral) Idx0() file.Idx { return self.Idx }
func (self *BracketExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() }
func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() }
func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() }
func (self *FunctionLiteral) Idx0() file.Idx { return self.Function }
func (self *Identifier) Idx0() file.Idx { return self.Idx }
func (self *NewExpression) Idx0() file.Idx { return self.New }
func (self *NullLiteral) Idx0() file.Idx { return self.Idx }
func (self *NumberLiteral) Idx0() file.Idx { return self.Idx }
func (self *ObjectLiteral) Idx0() file.Idx { return self.LeftBrace }
func (self *RegExpLiteral) Idx0() file.Idx { return self.Idx }
func (self *SequenceExpression) Idx0() file.Idx { return self.Sequence[0].Idx0() }
func (self *StringLiteral) Idx0() file.Idx { return self.Idx }
func (self *ThisExpression) Idx0() file.Idx { return self.Idx }
func (self *UnaryExpression) Idx0() file.Idx { return self.Idx }
func (self *VariableExpression) Idx0() file.Idx { return self.Idx }
func (self *BadStatement) Idx0() file.Idx { return self.From }
func (self *BlockStatement) Idx0() file.Idx { return self.LeftBrace }
func (self *BranchStatement) Idx0() file.Idx { return self.Idx }
func (self *CaseStatement) Idx0() file.Idx { return self.Case }
func (self *CatchStatement) Idx0() file.Idx { return self.Catch }
func (self *DebuggerStatement) Idx0() file.Idx { return self.Debugger }
func (self *DoWhileStatement) Idx0() file.Idx { return self.Do }
func (self *EmptyStatement) Idx0() file.Idx { return self.Semicolon }
func (self *ExpressionStatement) Idx0() file.Idx { return self.Expression.Idx0() }
func (self *ForInStatement) Idx0() file.Idx { return self.For }
func (self *ForStatement) Idx0() file.Idx { return self.For }
func (self *IfStatement) Idx0() file.Idx { return self.If }
func (self *LabelledStatement) Idx0() file.Idx { return self.Label.Idx0() }
func (self *Program) Idx0() file.Idx { return self.Body[0].Idx0() }
func (self *ReturnStatement) Idx0() file.Idx { return self.Return }
func (self *SwitchStatement) Idx0() file.Idx { return self.Switch }
func (self *ThrowStatement) Idx0() file.Idx { return self.Throw }
func (self *TryStatement) Idx0() file.Idx { return self.Try }
func (self *VariableStatement) Idx0() file.Idx { return self.Var }
func (self *WhileStatement) Idx0() file.Idx { return self.While }
func (self *WithStatement) Idx0() file.Idx { return self.With }
// ==== //
// Idx1 //
// ==== //
func (self *ArrayLiteral) Idx1() file.Idx { return self.RightBracket }
func (self *AssignExpression) Idx1() file.Idx { return self.Right.Idx1() }
func (self *BadExpression) Idx1() file.Idx { return self.To }
func (self *BinaryExpression) Idx1() file.Idx { return self.Right.Idx1() }
func (self *BooleanLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *BracketExpression) Idx1() file.Idx { return self.RightBracket + 1 }
func (self *CallExpression) Idx1() file.Idx { return self.RightParenthesis + 1 }
func (self *ConditionalExpression) Idx1() file.Idx { return self.Test.Idx1() }
func (self *DotExpression) Idx1() file.Idx { return self.Identifier.Idx1() }
func (self *FunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() }
func (self *Identifier) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Name)) }
func (self *NewExpression) Idx1() file.Idx { return self.RightParenthesis + 1 }
func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null"
func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace }
func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[0].Idx1() }
func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) }
func (self *ThisExpression) Idx1() file.Idx { return self.Idx }
func (self *UnaryExpression) Idx1() file.Idx {
if self.Postfix {
return self.Operand.Idx1() + 2 // ++ --
}
return self.Operand.Idx1()
}
func (self *VariableExpression) Idx1() file.Idx {
if self.Initializer == nil {
return file.Idx(int(self.Idx) + len(self.Name) + 1)
}
return self.Initializer.Idx1()
}
func (self *BadStatement) Idx1() file.Idx { return self.To }
func (self *BlockStatement) Idx1() file.Idx { return self.RightBrace + 1 }
func (self *BranchStatement) Idx1() file.Idx { return self.Idx }
func (self *CaseStatement) Idx1() file.Idx { return self.Consequent[len(self.Consequent)-1].Idx1() }
func (self *CatchStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *DebuggerStatement) Idx1() file.Idx { return self.Debugger + 8 }
func (self *DoWhileStatement) Idx1() file.Idx { return self.Test.Idx1() }
func (self *EmptyStatement) Idx1() file.Idx { return self.Semicolon + 1 }
func (self *ExpressionStatement) Idx1() file.Idx { return self.Expression.Idx1() }
func (self *ForInStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *ForStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *IfStatement) Idx1() file.Idx {
if self.Alternate != nil {
return self.Alternate.Idx1()
}
return self.Consequent.Idx1()
}
func (self *LabelledStatement) Idx1() file.Idx { return self.Colon + 1 }
func (self *Program) Idx1() file.Idx { return self.Body[len(self.Body)-1].Idx1() }
func (self *ReturnStatement) Idx1() file.Idx { return self.Return }
func (self *SwitchStatement) Idx1() file.Idx { return self.Body[len(self.Body)-1].Idx1() }
func (self *ThrowStatement) Idx1() file.Idx { return self.Throw }
func (self *TryStatement) Idx1() file.Idx { return self.Try }
func (self *VariableStatement) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
func (self *WhileStatement) Idx1() file.Idx { return self.Body.Idx1() }
func (self *WithStatement) Idx1() file.Idx { return self.Body.Idx1() }

View File

@ -1,85 +0,0 @@
package otto
func (runtime *_runtime) newEvalError(message Value) *_object {
self := runtime.newErrorObject(message)
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, _ Value, argumentList []Value) Value {
return toValue_object(self.runtime.newEvalError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newTypeError(message Value) *_object {
self := runtime.newErrorObject(message)
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, _ Value, argumentList []Value) Value {
return toValue_object(self.runtime.newTypeError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newRangeError(message Value) *_object {
self := runtime.newErrorObject(message)
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, _ Value, argumentList []Value) Value {
return toValue_object(self.runtime.newRangeError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newURIError(message Value) *_object {
self := runtime.newErrorObject(message)
self.prototype = runtime.Global.URIErrorPrototype
return self
}
func (runtime *_runtime) newReferenceError(message Value) *_object {
self := runtime.newErrorObject(message)
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, _ Value, argumentList []Value) Value {
return toValue_object(self.runtime.newReferenceError(valueOfArrayIndex(argumentList, 0)))
}
func (runtime *_runtime) newSyntaxError(message Value) *_object {
self := runtime.newErrorObject(message)
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, _ Value, 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, _ Value, argumentList []Value) Value {
return toValue_object(self.runtime.newURIError(valueOfArrayIndex(argumentList, 0)))
}

View File

@ -1,144 +0,0 @@
package otto
import (
"fmt"
)
type _clone struct {
runtime *_runtime
stash struct {
object map[*_object]*_object
objectEnvironment map[*_objectEnvironment]*_objectEnvironment
declarativeEnvironment map[*_declarativeEnvironment]*_declarativeEnvironment
}
}
func (runtime *_runtime) clone() *_runtime {
self := &_runtime{}
clone := &_clone{
runtime: self,
}
clone.stash.object = make(map[*_object]*_object)
clone.stash.objectEnvironment = make(map[*_objectEnvironment]*_objectEnvironment)
clone.stash.declarativeEnvironment = make(map[*_declarativeEnvironment]*_declarativeEnvironment)
globalObject := clone.object(runtime.GlobalObject)
self.GlobalEnvironment = self.newObjectEnvironment(globalObject, nil)
self.GlobalObject = globalObject
self.Global = _global{
clone.object(runtime.Global.Object),
clone.object(runtime.Global.Function),
clone.object(runtime.Global.Array),
clone.object(runtime.Global.String),
clone.object(runtime.Global.Boolean),
clone.object(runtime.Global.Number),
clone.object(runtime.Global.Math),
clone.object(runtime.Global.Date),
clone.object(runtime.Global.RegExp),
clone.object(runtime.Global.Error),
clone.object(runtime.Global.EvalError),
clone.object(runtime.Global.TypeError),
clone.object(runtime.Global.RangeError),
clone.object(runtime.Global.ReferenceError),
clone.object(runtime.Global.SyntaxError),
clone.object(runtime.Global.URIError),
clone.object(runtime.Global.JSON),
clone.object(runtime.Global.ObjectPrototype),
clone.object(runtime.Global.FunctionPrototype),
clone.object(runtime.Global.ArrayPrototype),
clone.object(runtime.Global.StringPrototype),
clone.object(runtime.Global.BooleanPrototype),
clone.object(runtime.Global.NumberPrototype),
clone.object(runtime.Global.DatePrototype),
clone.object(runtime.Global.RegExpPrototype),
clone.object(runtime.Global.ErrorPrototype),
clone.object(runtime.Global.EvalErrorPrototype),
clone.object(runtime.Global.TypeErrorPrototype),
clone.object(runtime.Global.RangeErrorPrototype),
clone.object(runtime.Global.ReferenceErrorPrototype),
clone.object(runtime.Global.SyntaxErrorPrototype),
clone.object(runtime.Global.URIErrorPrototype),
}
self.EnterGlobalExecutionContext()
self.eval = self.GlobalObject.property["eval"].value.(Value).value.(*_object)
self.GlobalObject.prototype = self.Global.ObjectPrototype
return self
}
func (clone *_clone) object(self0 *_object) *_object {
if self1, exists := clone.stash.object[self0]; exists {
return self1
}
self1 := &_object{}
clone.stash.object[self0] = self1
return self0.objectClass.clone(self0, self1, clone)
}
func (clone *_clone) declarativeEnvironment(self0 *_declarativeEnvironment) (*_declarativeEnvironment, bool) {
if self1, exists := clone.stash.declarativeEnvironment[self0]; exists {
return self1, true
}
self1 := &_declarativeEnvironment{}
clone.stash.declarativeEnvironment[self0] = self1
return self1, false
}
func (clone *_clone) objectEnvironment(self0 *_objectEnvironment) (*_objectEnvironment, bool) {
if self1, exists := clone.stash.objectEnvironment[self0]; exists {
return self1, true
}
self1 := &_objectEnvironment{}
clone.stash.objectEnvironment[self0] = self1
return self1, false
}
func (clone *_clone) value(self0 Value) Value {
self1 := self0
switch value := self0.value.(type) {
case *_object:
self1.value = clone.object(value)
}
return self1
}
func (clone *_clone) valueArray(self0 []Value) []Value {
self1 := make([]Value, len(self0))
for index, value := range self0 {
self1[index] = clone.value(value)
}
return self1
}
func (clone *_clone) environment(self0 _environment) _environment {
if self0 == nil {
return nil
}
return self0.clone(clone)
}
func (clone *_clone) property(self0 _property) _property {
self1 := self0
if value, valid := self0.value.(Value); valid {
self1.value = clone.value(value)
} else {
panic(fmt.Errorf("self0.value.(Value) != true"))
}
return self1
}
func (clone *_clone) declarativeProperty(self0 _declarativeProperty) _declarativeProperty {
self1 := self0
self1.value = clone.value(self0.value)
return self1
}
func (clone *_clone) callFunction(self0 _callFunction) _callFunction {
if self0 == nil {
return nil
}
return self0.clone(clone)
}

View File

@ -1,46 +0,0 @@
package otto
// _cmpl_nodeCallFunction
type _cmpl_nodeCallFunction struct {
node *_nodeFunctionLiteral
scopeEnvironment _environment // Can be either Lexical or Variable
}
func new_nodeCallFunction(node *_nodeFunctionLiteral, scopeEnvironment _environment) *_cmpl_nodeCallFunction {
self := &_cmpl_nodeCallFunction{
node: node,
}
self.scopeEnvironment = scopeEnvironment
return self
}
func (self _cmpl_nodeCallFunction) Dispatch(function *_object, environment *_functionEnvironment, runtime *_runtime, this Value, argumentList []Value, _ bool) Value {
return runtime.cmpl_call_nodeFunction(function, environment, self.node, this, argumentList)
}
func (self _cmpl_nodeCallFunction) ScopeEnvironment() _environment {
return self.scopeEnvironment
}
func (self _cmpl_nodeCallFunction) Source(object *_object) string {
return self.node.source
}
func (self0 _cmpl_nodeCallFunction) clone(clone *_clone) _callFunction {
return _cmpl_nodeCallFunction{
node: self0.node,
scopeEnvironment: clone.environment(self0.scopeEnvironment),
}
}
// ---
func (runtime *_runtime) newNodeFunctionObject(node *_nodeFunctionLiteral, scopeEnvironment _environment) *_object {
self := runtime.newClassObject("Function")
self.value = _functionObject{
call: new_nodeCallFunction(node, scopeEnvironment),
construct: defaultConstructFunction,
}
self.defineProperty("length", toValue_int(len(node.parameterList)), 0000, false)
return self
}

View File

@ -1,387 +0,0 @@
// 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...)
}
}
}

View File

@ -1,280 +0,0 @@
package otto
import (
"fmt"
)
// _environment
type _environment interface {
HasBinding(string) bool
CreateMutableBinding(string, bool)
SetMutableBinding(string, Value, bool)
// SetMutableBinding with Lazy CreateMutableBinding(..., true)
SetValue(string, Value, bool)
GetBindingValue(string, bool) Value
GetValue(string, bool) Value // GetBindingValue
DeleteBinding(string) bool
ImplicitThisValue() *_object
Outer() _environment
newReference(string, bool) _reference
clone(clone *_clone) _environment
runtimeOf() *_runtime
}
// _functionEnvironment
type _functionEnvironment struct {
_declarativeEnvironment
arguments *_object
indexOfArgumentName map[string]string
}
func (runtime *_runtime) newFunctionEnvironment(outer _environment) *_functionEnvironment {
return &_functionEnvironment{
_declarativeEnvironment: _declarativeEnvironment{
runtime: runtime,
outer: outer,
property: map[string]_declarativeProperty{},
},
}
}
func (self0 _functionEnvironment) clone(clone *_clone) _environment {
return &_functionEnvironment{
*(self0._declarativeEnvironment.clone(clone).(*_declarativeEnvironment)),
clone.object(self0.arguments),
self0.indexOfArgumentName,
}
}
func (self _functionEnvironment) runtimeOf() *_runtime {
return self._declarativeEnvironment.runtimeOf()
}
// _objectEnvironment
type _objectEnvironment struct {
runtime *_runtime
outer _environment
Object *_object
ProvideThis bool
}
func (self *_objectEnvironment) runtimeOf() *_runtime {
return self.runtime
}
func (runtime *_runtime) newObjectEnvironment(object *_object, outer _environment) *_objectEnvironment {
if object == nil {
object = runtime.newBaseObject()
object.class = "environment"
}
return &_objectEnvironment{
runtime: runtime,
outer: outer,
Object: object,
}
}
func (self0 *_objectEnvironment) clone(clone *_clone) _environment {
self1, exists := clone.objectEnvironment(self0)
if exists {
return self1
}
*self1 = _objectEnvironment{
clone.runtime,
clone.environment(self0.outer),
clone.object(self0.Object),
self0.ProvideThis,
}
return self1
}
func (self *_objectEnvironment) HasBinding(name string) bool {
return self.Object.hasProperty(name)
}
func (self *_objectEnvironment) CreateMutableBinding(name string, deletable bool) {
if self.Object.hasProperty(name) {
panic(hereBeDragons())
}
mode := _propertyMode(0111)
if !deletable {
mode = _propertyMode(0110)
}
// TODO False?
self.Object.defineProperty(name, UndefinedValue(), mode, false)
}
func (self *_objectEnvironment) SetMutableBinding(name string, value Value, strict bool) {
self.Object.put(name, value, strict)
}
func (self *_objectEnvironment) SetValue(name string, value Value, throw bool) {
if !self.HasBinding(name) {
self.CreateMutableBinding(name, true) // Configurable by default
}
self.SetMutableBinding(name, value, throw)
}
func (self *_objectEnvironment) GetBindingValue(name string, strict bool) Value {
if self.Object.hasProperty(name) {
return self.Object.get(name)
}
if strict {
panic(newReferenceError("Not Defined", name))
}
return UndefinedValue()
}
func (self *_objectEnvironment) GetValue(name string, throw bool) Value {
return self.GetBindingValue(name, throw)
}
func (self *_objectEnvironment) DeleteBinding(name string) bool {
return self.Object.delete(name, false)
}
func (self *_objectEnvironment) ImplicitThisValue() *_object {
if self.ProvideThis {
return self.Object
}
return nil
}
func (self *_objectEnvironment) Outer() _environment {
return self.outer
}
func (self *_objectEnvironment) newReference(name string, strict bool) _reference {
return newPropertyReference(self.Object, name, strict)
}
// _declarativeEnvironment
func (runtime *_runtime) newDeclarativeEnvironment(outer _environment) *_declarativeEnvironment {
return &_declarativeEnvironment{
runtime: runtime,
outer: outer,
property: map[string]_declarativeProperty{},
}
}
func (self0 *_declarativeEnvironment) clone(clone *_clone) _environment {
self1, exists := clone.declarativeEnvironment(self0)
if exists {
return self1
}
property := make(map[string]_declarativeProperty, len(self0.property))
for index, value := range self0.property {
property[index] = clone.declarativeProperty(value)
}
*self1 = _declarativeEnvironment{
clone.runtime,
clone.environment(self0.outer),
property,
}
return self1
}
type _declarativeProperty struct {
value Value
mutable bool
deletable bool
readable bool
}
type _declarativeEnvironment struct {
runtime *_runtime
outer _environment
property map[string]_declarativeProperty
}
func (self *_declarativeEnvironment) HasBinding(name string) bool {
_, exists := self.property[name]
return exists
}
func (self *_declarativeEnvironment) runtimeOf() *_runtime {
return self.runtime
}
func (self *_declarativeEnvironment) CreateMutableBinding(name string, deletable bool) {
_, exists := self.property[name]
if exists {
panic(fmt.Errorf("CreateMutableBinding: %s: already exists", name))
}
self.property[name] = _declarativeProperty{
value: UndefinedValue(),
mutable: true,
deletable: deletable,
readable: false,
}
}
func (self *_declarativeEnvironment) SetMutableBinding(name string, value Value, strict bool) {
property, exists := self.property[name]
if !exists {
panic(fmt.Errorf("SetMutableBinding: %s: missing", name))
}
if property.mutable {
property.value = value
self.property[name] = property
} else {
typeErrorResult(strict)
}
}
func (self *_declarativeEnvironment) SetValue(name string, value Value, throw bool) {
if !self.HasBinding(name) {
self.CreateMutableBinding(name, false) // NOT deletable by default
}
self.SetMutableBinding(name, value, throw)
}
func (self *_declarativeEnvironment) GetBindingValue(name string, strict bool) Value {
property, exists := self.property[name]
if !exists {
panic(fmt.Errorf("GetBindingValue: %s: missing", name))
}
if !property.mutable && !property.readable {
if strict {
panic(newTypeError())
}
return UndefinedValue()
}
return property.value
}
func (self *_declarativeEnvironment) GetValue(name string, throw bool) Value {
return self.GetBindingValue(name, throw)
}
func (self *_declarativeEnvironment) DeleteBinding(name string) bool {
property, exists := self.property[name]
if !exists {
return true
}
if !property.deletable {
return false
}
delete(self.property, name)
return true
}
func (self *_declarativeEnvironment) ImplicitThisValue() *_object {
return nil
}
func (self *_declarativeEnvironment) Outer() _environment {
return self.outer
}
func (self *_declarativeEnvironment) newReference(name string, strict bool) _reference {
return newEnvironmentReference(self, name, strict, nil)
}

View File

@ -1,152 +0,0 @@
package otto
import (
"errors"
"fmt"
"github.com/robertkrimen/otto/ast"
)
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
Line int // Hackish -- line where the error/exception occurred
}
var messageDetail map[string]string = map[string]string{
"notDefined": "%v is not defined",
}
func messageFromDescription(description string, argumentList ...interface{}) string {
message := messageDetail[description]
if message == "" {
message = description
}
message = fmt.Sprintf(message, argumentList...)
return message
}
func (self _error) MessageValue() Value {
if self.Message == "" {
return UndefinedValue()
}
return toValue_string(self.Message)
}
func (self _error) String() string {
if len(self.Name) == 0 {
return self.Message
}
if len(self.Message) == 0 {
return self.Name
}
return fmt.Sprintf("%s: %s", self.Name, self.Message)
}
func newError(name string, argumentList ...interface{}) _error {
description := ""
var node ast.Node = nil
length := len(argumentList)
if length > 0 {
if node, _ = argumentList[length-1].(ast.Node); node != nil || argumentList[length-1] == nil {
argumentList = argumentList[0 : length-1]
length -= 1
}
if length > 0 {
description, argumentList = argumentList[0].(string), argumentList[1:]
}
}
return _error{
Name: name,
Message: messageFromDescription(description, argumentList...),
Line: -1,
}
//error := _error{
// Name: name,
// Message: messageFromDescription(description, argumentList...),
// Line: -1,
//}
//if node != nil {
// error.Line = ast.position()
//}
//return error
}
func newReferenceError(argumentList ...interface{}) _error {
return newError("ReferenceError", argumentList...)
}
func newTypeError(argumentList ...interface{}) _error {
return newError("TypeError", argumentList...)
}
func newRangeError(argumentList ...interface{}) _error {
return newError("RangeError", argumentList...)
}
func newSyntaxError(argumentList ...interface{}) _error {
return newError("SyntaxError", argumentList...)
}
func newURIError(argumentList ...interface{}) _error {
return newError("URIError", argumentList...)
}
func typeErrorResult(throw bool) bool {
if throw {
panic(newTypeError())
}
return false
}
func catchPanic(function func()) (err error) {
// FIXME
defer func() {
if caught := recover(); caught != nil {
if exception, ok := caught.(*_exception); ok {
caught = exception.eject()
}
switch caught := caught.(type) {
//case *_syntaxError:
// err = errors.New(fmt.Sprintf("%s (line %d)", caught.String(), caught.Line+0))
// return
case _error:
if caught.Line == -1 {
err = errors.New(caught.String())
} else {
// We're 0-based (for now), hence the + 1
err = errors.New(fmt.Sprintf("%s (line %d)", caught.String(), caught.Line+1))
}
return
case Value:
err = errors.New(toString(caught))
return
//case string:
// if strings.HasPrefix(caught, "SyntaxError:") {
// err = errors.New(caught)
// return
// }
}
panic(caught)
}
}()
function()
return nil
}

View File

@ -1,62 +0,0 @@
package otto
import (
"testing"
)
func TestError(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`
[ Error.prototype.name, Error.prototype.message, Error.prototype.hasOwnProperty("message") ];
`, "Error,,true")
})
}
func TestError_instanceof(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`(new TypeError()) instanceof Error`, true)
})
}
func TestPanicValue(t *testing.T) {
tt(t, func() {
test, vm := test()
vm.Set("abc", func(call FunctionCall) Value {
value, err := call.Otto.Run(`({ def: 3.14159 })`)
is(err, nil)
panic(value)
})
test(`
try {
abc();
}
catch (err) {
error = err;
}
[ error instanceof Error, error.message, error.def ];
`, "false,,3.14159")
})
}
func Test_catchPanic(t *testing.T) {
tt(t, func() {
vm := New()
_, err := vm.Run(`
A syntax error that
does not define
var;
abc;
`)
is(err, "!=", nil)
_, err = vm.Call(`abc.def`, nil)
is(err, "!=", nil)
})
}

View File

@ -1,40 +0,0 @@
package otto
type _executionContext struct {
LexicalEnvironment _environment
VariableEnvironment _environment
this *_object
eval bool // Replace this with kind?
}
func newExecutionContext(lexical _environment, variable _environment, this *_object) *_executionContext {
return &_executionContext{
LexicalEnvironment: lexical,
VariableEnvironment: variable,
this: this,
}
}
func (self *_executionContext) getValue(name string) Value {
strict := false
return self.LexicalEnvironment.GetValue(name, strict)
}
func (self *_executionContext) setValue(name string, value Value, throw bool) {
self.LexicalEnvironment.SetValue(name, value, throw)
}
func (self *_executionContext) newLexicalEnvironment(object *_object) (_environment, *_objectEnvironment) {
// Get runtime from the object (for now)
runtime := object.runtime
previousLexical := self.LexicalEnvironment
newLexical := runtime.newObjectEnvironment(object, self.LexicalEnvironment)
self.LexicalEnvironment = newLexical
return previousLexical, newLexical
}
func (self *_executionContext) newDeclarativeEnvironment(runtime *_runtime) _environment {
previousLexical := self.LexicalEnvironment
self.LexicalEnvironment = runtime.newDeclarativeEnvironment(self.LexicalEnvironment)
return previousLexical
}

View File

@ -1,72 +0,0 @@
# file
--
import "github.com/robertkrimen/otto/file"
Package file encapsulates the file abstractions used by the ast & parser.
## Usage
#### 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) 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

View File

@ -1,106 +0,0 @@
// Package file encapsulates the file abstractions used by the ast & parser.
//
package file
import (
"fmt"
"strings"
)
// 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{
filename: 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
}
// Position converts an Idx in the FileSet into a Position.
func (self *FileSet) Position(idx Idx) *Position {
position := &Position{}
for _, file := range self.files {
if idx <= Idx(file.base+len(file.src)) {
offset := int(idx) - file.base
src := file.src[:offset]
position.Filename = file.filename
position.Offset = offset
position.Line = 1 + strings.Count(src, "\n")
if index := strings.LastIndex(src, "\n"); index >= 0 {
position.Column = offset - index
} else {
position.Column = 1 + len(src)
}
}
}
return position
}
type _file struct {
filename string
src string
base int // This will always be 1 or greater
}

View File

@ -1,4 +0,0 @@
.PHONY: test
test:
go test

View File

@ -1,190 +0,0 @@
# 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

View File

@ -1,9 +0,0 @@
// 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()

View File

@ -1,175 +0,0 @@
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
}
// FXIME 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
}

View File

@ -1,815 +0,0 @@
package parser
import (
"regexp"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token"
)
func (self *_parser) parseIdentifier() *ast.Identifier {
literal := self.literal
idx := self.idx
self.next()
return &ast.Identifier{
Name: literal,
Idx: idx,
}
}
func (self *_parser) parsePrimaryExpression() ast.Expression {
literal := self.literal
idx := self.idx
switch self.token {
case token.IDENTIFIER:
self.next()
if len(literal) > 1 {
tkn, strict := token.IsKeyword(literal)
if tkn == token.KEYWORD {
if !strict {
self.error(idx, "Unexpected reserved word")
}
}
}
return &ast.Identifier{
Name: literal,
Idx: idx,
}
case token.NULL:
self.next()
return &ast.NullLiteral{
Idx: idx,
Literal: literal,
}
case token.BOOLEAN:
self.next()
value := false
switch literal {
case "true":
value = true
case "false":
value = false
default:
self.error(idx, "Illegal boolean literal")
}
return &ast.BooleanLiteral{
Idx: idx,
Literal: literal,
Value: value,
}
case token.STRING:
self.next()
value, err := parseStringLiteral(literal[1 : len(literal)-1])
if err != nil {
self.error(idx, err.Error())
}
return &ast.StringLiteral{
Idx: idx,
Literal: literal,
Value: value,
}
case token.NUMBER:
self.next()
value, err := parseNumberLiteral(literal)
if err != nil {
self.error(idx, err.Error())
value = 0
}
return &ast.NumberLiteral{
Idx: idx,
Literal: literal,
Value: value,
}
case token.SLASH, token.QUOTIENT_ASSIGN:
return self.parseRegExpLiteral()
case token.LEFT_BRACE:
return self.parseObjectLiteral()
case token.LEFT_BRACKET:
return self.parseArrayLiteral()
case token.LEFT_PARENTHESIS:
self.expect(token.LEFT_PARENTHESIS)
expression := self.parseExpression()
self.expect(token.RIGHT_PARENTHESIS)
return expression
case token.THIS:
self.next()
return &ast.ThisExpression{
Idx: idx,
}
case token.FUNCTION:
return self.parseFunction(false)
}
self.errorUnexpectedToken(self.token)
self.nextStatement()
return &ast.BadExpression{From: idx, To: self.idx}
}
func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral {
offset := self.chrOffset - 1 // Opening slash already gotten
if self.token == token.QUOTIENT_ASSIGN {
offset -= 1 // =
}
idx := self.idxOf(offset)
pattern, err := self.scanString(offset)
endOffset := self.chrOffset
self.next()
if err == nil {
pattern = pattern[1 : len(pattern)-1]
}
flags := ""
if self.token == token.IDENTIFIER { // gim
flags = self.literal
self.next()
endOffset = self.chrOffset - 1
}
var value string
// TODO 15.10
{
// Test during parsing that this is a valid regular expression
// Sorry, (?=) and (?!) are invalid (for now)
pattern, err := TransformRegExp(pattern)
if err != nil {
if pattern == "" || self.mode&IgnoreRegExpErrors == 0 {
self.error(idx, "Invalid regular expression: %s", err.Error())
}
} else {
_, err = regexp.Compile(pattern)
if err != nil {
// We should not get here, ParseRegExp should catch any errors
self.error(idx, "Invalid regular expression: %s", err.Error()[22:]) // Skip redundant "parse regexp error"
} else {
value = pattern
}
}
}
literal := self.str[offset:endOffset]
return &ast.RegExpLiteral{
Idx: idx,
Literal: literal,
Pattern: pattern,
Flags: flags,
Value: value,
}
}
func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.VariableExpression) ast.Expression {
if self.token != token.IDENTIFIER {
idx := self.expect(token.IDENTIFIER)
self.nextStatement()
return &ast.BadExpression{From: idx, To: self.idx}
}
literal := self.literal
idx := self.idx
self.next()
node := &ast.VariableExpression{
Name: literal,
Idx: idx,
}
if declarationList != nil {
*declarationList = append(*declarationList, node)
}
if self.token == token.ASSIGN {
self.next()
node.Initializer = self.parseAssignmentExpression()
}
return node
}
func (self *_parser) parseVariableDeclarationList(var_ file.Idx) []ast.Expression {
var declarationList []*ast.VariableExpression // Avoid bad expressions
var list []ast.Expression
for {
list = append(list, self.parseVariableDeclaration(&declarationList))
if self.token != token.COMMA {
break
}
self.next()
}
self.scope.declare(&ast.VariableDeclaration{
Var: var_,
List: declarationList,
})
return list
}
func (self *_parser) parseObjectPropertyKey() (string, string) {
idx, tkn, literal := self.idx, self.token, self.literal
value := ""
self.next()
switch tkn {
case token.IDENTIFIER:
value = literal
case token.NUMBER:
var err error
_, err = parseNumberLiteral(literal)
if err != nil {
self.error(idx, err.Error())
} else {
value = literal
}
case token.STRING:
var err error
value, err = parseStringLiteral(literal[1 : len(literal)-1])
if err != nil {
self.error(idx, err.Error())
}
default:
// null, false, class, etc.
if matchIdentifier.MatchString(literal) {
value = literal
}
}
return literal, value
}
func (self *_parser) parseObjectProperty() ast.Property {
literal, value := self.parseObjectPropertyKey()
if literal == "get" && self.token != token.COLON {
idx := self.idx
_, value := self.parseObjectPropertyKey()
parameterList := self.parseFunctionParameterList()
node := &ast.FunctionLiteral{
Function: idx,
ParameterList: parameterList,
}
self.parseFunctionBlock(node)
return ast.Property{
Key: value,
Kind: "get",
Value: node,
}
} else if literal == "set" && self.token != token.COLON {
idx := self.idx
_, value := self.parseObjectPropertyKey()
parameterList := self.parseFunctionParameterList()
node := &ast.FunctionLiteral{
Function: idx,
ParameterList: parameterList,
}
self.parseFunctionBlock(node)
return ast.Property{
Key: value,
Kind: "set",
Value: node,
}
}
self.expect(token.COLON)
return ast.Property{
Key: value,
Kind: "value",
Value: self.parseAssignmentExpression(),
}
}
func (self *_parser) parseObjectLiteral() ast.Expression {
var value []ast.Property
idx0 := self.expect(token.LEFT_BRACE)
for self.token != token.RIGHT_BRACE && self.token != token.EOF {
property := self.parseObjectProperty()
value = append(value, property)
if self.token == token.COMMA {
self.next()
continue
}
}
idx1 := self.expect(token.RIGHT_BRACE)
return &ast.ObjectLiteral{
LeftBrace: idx0,
RightBrace: idx1,
Value: value,
}
}
func (self *_parser) parseArrayLiteral() ast.Expression {
idx0 := self.expect(token.LEFT_BRACKET)
var value []ast.Expression
for self.token != token.RIGHT_BRACKET && self.token != token.EOF {
if self.token == token.COMMA {
self.next()
value = append(value, nil)
continue
}
value = append(value, self.parseAssignmentExpression())
if self.token != token.RIGHT_BRACKET {
self.expect(token.COMMA)
}
}
idx1 := self.expect(token.RIGHT_BRACKET)
return &ast.ArrayLiteral{
LeftBracket: idx0,
RightBracket: idx1,
Value: value,
}
}
func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, idx1 file.Idx) {
idx0 = self.expect(token.LEFT_PARENTHESIS)
if self.token != token.RIGHT_PARENTHESIS {
for {
argumentList = append(argumentList, self.parseAssignmentExpression())
if self.token != token.COMMA {
break
}
self.next()
}
}
idx1 = self.expect(token.RIGHT_PARENTHESIS)
return
}
func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression {
argumentList, idx0, idx1 := self.parseArgumentList()
return &ast.CallExpression{
Callee: left,
LeftParenthesis: idx0,
ArgumentList: argumentList,
RightParenthesis: idx1,
}
}
func (self *_parser) parseDotMember(left ast.Expression) ast.Expression {
period := self.expect(token.PERIOD)
literal := self.literal
idx := self.idx
if !matchIdentifier.MatchString(literal) {
self.expect(token.IDENTIFIER)
self.nextStatement()
return &ast.BadExpression{From: period, To: self.idx}
}
self.next()
return &ast.DotExpression{
Left: left,
Identifier: ast.Identifier{
Idx: idx,
Name: literal,
},
}
}
func (self *_parser) parseBracketMember(left ast.Expression) ast.Expression {
idx0 := self.expect(token.LEFT_BRACKET)
member := self.parseExpression()
idx1 := self.expect(token.RIGHT_BRACKET)
return &ast.BracketExpression{
LeftBracket: idx0,
Left: left,
Member: member,
RightBracket: idx1,
}
}
func (self *_parser) parseNewExpression() ast.Expression {
idx := self.expect(token.NEW)
callee := self.parseLeftHandSideExpression()
node := &ast.NewExpression{
New: idx,
Callee: callee,
}
if self.token == token.LEFT_PARENTHESIS {
argumentList, idx0, idx1 := self.parseArgumentList()
node.ArgumentList = argumentList
node.LeftParenthesis = idx0
node.RightParenthesis = idx1
}
return node
}
func (self *_parser) parseLeftHandSideExpression() ast.Expression {
var left ast.Expression
if self.token == token.NEW {
left = self.parseNewExpression()
} else {
left = self.parsePrimaryExpression()
}
for {
if self.token == token.PERIOD {
left = self.parseDotMember(left)
} else if self.token == token.LEFT_BRACE {
left = self.parseBracketMember(left)
} else {
break
}
}
return left
}
func (self *_parser) parseLeftHandSideExpressionAllowCall() ast.Expression {
allowIn := self.scope.allowIn
self.scope.allowIn = true
defer func() {
self.scope.allowIn = allowIn
}()
var left ast.Expression
if self.token == token.NEW {
left = self.parseNewExpression()
} else {
left = self.parsePrimaryExpression()
}
for {
if self.token == token.PERIOD {
left = self.parseDotMember(left)
} else if self.token == token.LEFT_BRACKET {
left = self.parseBracketMember(left)
} else if self.token == token.LEFT_PARENTHESIS {
left = self.parseCallExpression(left)
} else {
break
}
}
return left
}
func (self *_parser) parsePostfixExpression() ast.Expression {
operand := self.parseLeftHandSideExpressionAllowCall()
switch self.token {
case token.INCREMENT, token.DECREMENT:
// Make sure there is no line terminator here
if self.implicitSemicolon {
break
}
tkn := self.token
idx := self.idx
self.next()
switch operand.(type) {
case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
default:
self.error(idx, "Invalid left-hand side in assignment")
self.nextStatement()
return &ast.BadExpression{From: idx, To: self.idx}
}
return &ast.UnaryExpression{
Operator: tkn,
Idx: idx,
Operand: operand,
Postfix: true,
}
}
return operand
}
func (self *_parser) parseUnaryExpression() ast.Expression {
switch self.token {
case token.PLUS, token.MINUS, token.NOT, token.BITWISE_NOT:
fallthrough
case token.DELETE, token.VOID, token.TYPEOF:
tkn := self.token
idx := self.idx
self.next()
return &ast.UnaryExpression{
Operator: tkn,
Idx: idx,
Operand: self.parseUnaryExpression(),
}
case token.INCREMENT, token.DECREMENT:
tkn := self.token
idx := self.idx
self.next()
operand := self.parseUnaryExpression()
switch operand.(type) {
case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
default:
self.error(idx, "Invalid left-hand side in assignment")
self.nextStatement()
return &ast.BadExpression{From: idx, To: self.idx}
}
return &ast.UnaryExpression{
Operator: tkn,
Idx: idx,
Operand: operand,
}
}
return self.parsePostfixExpression()
}
func (self *_parser) parseMultiplicativeExpression() ast.Expression {
next := self.parseUnaryExpression
left := next()
for self.token == token.MULTIPLY || self.token == token.SLASH ||
self.token == token.REMAINDER {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseAdditiveExpression() ast.Expression {
next := self.parseMultiplicativeExpression
left := next()
for self.token == token.PLUS || self.token == token.MINUS {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseShiftExpression() ast.Expression {
next := self.parseAdditiveExpression
left := next()
for self.token == token.SHIFT_LEFT || self.token == token.SHIFT_RIGHT ||
self.token == token.UNSIGNED_SHIFT_RIGHT {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseRelationalExpression() ast.Expression {
next := self.parseShiftExpression
left := next()
allowIn := self.scope.allowIn
self.scope.allowIn = true
defer func() {
self.scope.allowIn = allowIn
}()
switch self.token {
case token.LESS, token.LESS_OR_EQUAL, token.GREATER, token.GREATER_OR_EQUAL:
tkn := self.token
self.next()
return &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: self.parseRelationalExpression(),
Comparison: true,
}
case token.INSTANCEOF:
tkn := self.token
self.next()
return &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: self.parseRelationalExpression(),
}
case token.IN:
if !allowIn {
return left
}
tkn := self.token
self.next()
return &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: self.parseRelationalExpression(),
}
}
return left
}
func (self *_parser) parseEqualityExpression() ast.Expression {
next := self.parseRelationalExpression
left := next()
for self.token == token.EQUAL || self.token == token.NOT_EQUAL ||
self.token == token.STRICT_EQUAL || self.token == token.STRICT_NOT_EQUAL {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
Comparison: true,
}
}
return left
}
func (self *_parser) parseBitwiseAndExpression() ast.Expression {
next := self.parseEqualityExpression
left := next()
for self.token == token.AND {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseBitwiseExclusiveOrExpression() ast.Expression {
next := self.parseBitwiseAndExpression
left := next()
for self.token == token.EXCLUSIVE_OR {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseBitwiseOrExpression() ast.Expression {
next := self.parseBitwiseExclusiveOrExpression
left := next()
for self.token == token.OR {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseLogicalAndExpression() ast.Expression {
next := self.parseBitwiseOrExpression
left := next()
for self.token == token.LOGICAL_AND {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseLogicalOrExpression() ast.Expression {
next := self.parseLogicalAndExpression
left := next()
for self.token == token.LOGICAL_OR {
tkn := self.token
self.next()
left = &ast.BinaryExpression{
Operator: tkn,
Left: left,
Right: next(),
}
}
return left
}
func (self *_parser) parseConditionlExpression() ast.Expression {
left := self.parseLogicalOrExpression()
if self.token == token.QUESTION_MARK {
self.next()
consequent := self.parseAssignmentExpression()
self.expect(token.COLON)
return &ast.ConditionalExpression{
Test: left,
Consequent: consequent,
Alternate: self.parseAssignmentExpression(),
}
}
return left
}
func (self *_parser) parseAssignmentExpression() ast.Expression {
left := self.parseConditionlExpression()
var operator token.Token
switch self.token {
case token.ASSIGN:
operator = self.token
case token.ADD_ASSIGN:
operator = token.PLUS
case token.SUBTRACT_ASSIGN:
operator = token.MINUS
case token.MULTIPLY_ASSIGN:
operator = token.MULTIPLY
case token.QUOTIENT_ASSIGN:
operator = token.SLASH
case token.REMAINDER_ASSIGN:
operator = token.REMAINDER
case token.AND_ASSIGN:
operator = token.AND
case token.AND_NOT_ASSIGN:
operator = token.AND_NOT
case token.OR_ASSIGN:
operator = token.OR
case token.EXCLUSIVE_OR_ASSIGN:
operator = token.EXCLUSIVE_OR
case token.SHIFT_LEFT_ASSIGN:
operator = token.SHIFT_LEFT
case token.SHIFT_RIGHT_ASSIGN:
operator = token.SHIFT_RIGHT
case token.UNSIGNED_SHIFT_RIGHT_ASSIGN:
operator = token.UNSIGNED_SHIFT_RIGHT
}
if operator != 0 {
idx := self.idx
self.next()
switch left.(type) {
case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
default:
self.error(left.Idx0(), "Invalid left-hand side in assignment")
self.nextStatement()
return &ast.BadExpression{From: idx, To: self.idx}
}
return &ast.AssignExpression{
Left: left,
Operator: operator,
Right: self.parseAssignmentExpression(),
}
}
return left
}
func (self *_parser) parseExpression() ast.Expression {
next := self.parseAssignmentExpression
left := next()
if self.token == token.COMMA {
sequence := []ast.Expression{left}
for {
if self.token != token.COMMA {
break
}
self.next()
sequence = append(sequence, next())
}
return &ast.SequenceExpression{
Sequence: sequence,
}
}
return left
}

View File

@ -1,819 +0,0 @@
package parser
import (
"bytes"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token"
)
type _chr struct {
value rune
width int
}
var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`)
func isDecimalDigit(chr rune) bool {
return '0' <= chr && chr <= '9'
}
func digitValue(chr rune) int {
switch {
case '0' <= chr && chr <= '9':
return int(chr - '0')
case 'a' <= chr && chr <= 'f':
return int(chr - 'a' + 10)
case 'A' <= chr && chr <= 'F':
return int(chr - 'A' + 10)
}
return 16 // Larger than any legal digit value
}
func isDigit(chr rune, base int) bool {
return digitValue(chr) < base
}
func isIdentifierStart(chr rune) bool {
return chr == '$' || chr == '_' || chr == '\\' ||
'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
chr >= utf8.RuneSelf && unicode.IsLetter(chr)
}
func isIdentifierPart(chr rune) bool {
return chr == '$' || chr == '_' || chr == '\\' ||
'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
'0' <= chr && chr <= '9' ||
chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
}
func (self *_parser) scanIdentifier() (string, error) {
offset := self.chrOffset
parse := false
for isIdentifierPart(self.chr) {
if self.chr == '\\' {
distance := self.chrOffset - offset
self.read()
if self.chr != 'u' {
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
}
parse = true
var value rune
for j := 0; j < 4; j++ {
self.read()
decimal, ok := hex2decimal(byte(self.chr))
if !ok {
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
}
value = value<<4 | decimal
}
if value == '\\' {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
} else if distance == 0 {
if !isIdentifierStart(value) {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
}
} else if distance > 0 {
if !isIdentifierPart(value) {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
}
}
}
self.read()
}
literal := string(self.str[offset:self.chrOffset])
if parse {
return parseStringLiteral(literal)
}
return literal, nil
}
// 7.2
func isLineWhiteSpace(chr rune) bool {
switch chr {
case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff':
return true
case '\u000a', '\u000d', '\u2028', '\u2029':
return false
case '\u0085':
return false
}
return unicode.IsSpace(chr)
}
// 7.3
func isLineTerminator(chr rune) bool {
switch chr {
case '\u000a', '\u000d', '\u2028', '\u2029':
return true
}
return false
}
func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
self.implicitSemicolon = false
for {
self.skipWhiteSpace()
idx = self.idxOf(self.chrOffset)
insertSemicolon := false
switch chr := self.chr; {
case isIdentifierStart(chr):
var err error
literal, err = self.scanIdentifier()
if err != nil {
tkn = token.ILLEGAL
break
}
if len(literal) > 1 {
// Keywords are longer than 1 character, avoid lookup otherwise
var strict bool
tkn, strict = token.IsKeyword(literal)
switch tkn {
case 0: // Not a keyword
if literal == "true" || literal == "false" {
self.insertSemicolon = true
tkn = token.BOOLEAN
return
} else if literal == "null" {
self.insertSemicolon = true
tkn = token.NULL
return
}
case token.KEYWORD:
tkn = token.KEYWORD
if strict {
// TODO If strict and in strict mode, then this is not a break
break
}
return
case
token.THIS,
token.BREAK,
token.THROW, // A newline after a throw is not allowed, but we need to detect it
token.RETURN,
token.CONTINUE,
token.DEBUGGER:
self.insertSemicolon = true
return
default:
return
}
}
self.insertSemicolon = true
tkn = token.IDENTIFIER
return
case '0' <= chr && chr <= '9':
self.insertSemicolon = true
tkn, literal = self.scanNumericLiteral(false)
return
default:
self.read()
switch chr {
case -1:
if self.insertSemicolon {
self.insertSemicolon = false
self.implicitSemicolon = true
}
tkn = token.EOF
case '\r', '\n', '\u2028', '\u2029':
self.insertSemicolon = false
self.implicitSemicolon = true
continue
case ':':
tkn = token.COLON
case '.':
if digitValue(self.chr) < 10 {
insertSemicolon = true
tkn, literal = self.scanNumericLiteral(true)
} else {
tkn = token.PERIOD
}
case ',':
tkn = token.COMMA
case ';':
tkn = token.SEMICOLON
case '(':
tkn = token.LEFT_PARENTHESIS
case ')':
tkn = token.RIGHT_PARENTHESIS
insertSemicolon = true
case '[':
tkn = token.LEFT_BRACKET
case ']':
tkn = token.RIGHT_BRACKET
insertSemicolon = true
case '{':
tkn = token.LEFT_BRACE
case '}':
tkn = token.RIGHT_BRACE
insertSemicolon = true
case '+':
tkn = self.switch3(token.PLUS, token.ADD_ASSIGN, '+', token.INCREMENT)
if tkn == token.INCREMENT {
insertSemicolon = true
}
case '-':
tkn = self.switch3(token.MINUS, token.SUBTRACT_ASSIGN, '-', token.DECREMENT)
if tkn == token.DECREMENT {
insertSemicolon = true
}
case '*':
tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
case '/':
if self.chr == '/' {
self.skipSingleLineComment()
continue
} else if self.chr == '*' {
self.skipMultiLineComment()
continue
} else {
// Could be division, could be RegExp literal
tkn = self.switch2(token.SLASH, token.QUOTIENT_ASSIGN)
insertSemicolon = true
}
case '%':
tkn = self.switch2(token.REMAINDER, token.REMAINDER_ASSIGN)
case '^':
tkn = self.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN)
case '<':
tkn = self.switch4(token.LESS, token.LESS_OR_EQUAL, '<', token.SHIFT_LEFT, token.SHIFT_LEFT_ASSIGN)
case '>':
tkn = self.switch6(token.GREATER, token.GREATER_OR_EQUAL, '>', token.SHIFT_RIGHT, token.SHIFT_RIGHT_ASSIGN, '>', token.UNSIGNED_SHIFT_RIGHT, token.UNSIGNED_SHIFT_RIGHT_ASSIGN)
case '=':
tkn = self.switch2(token.ASSIGN, token.EQUAL)
if tkn == token.EQUAL && self.chr == '=' {
self.read()
tkn = token.STRICT_EQUAL
}
case '!':
tkn = self.switch2(token.NOT, token.NOT_EQUAL)
if tkn == token.NOT_EQUAL && self.chr == '=' {
self.read()
tkn = token.STRICT_NOT_EQUAL
}
case '&':
if self.chr == '^' {
self.read()
tkn = self.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
} else {
tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
}
case '|':
tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
case '~':
tkn = token.BITWISE_NOT
case '?':
tkn = token.QUESTION_MARK
case '"', '\'':
insertSemicolon = true
tkn = token.STRING
var err error
literal, err = self.scanString(self.chrOffset - 1)
if err != nil {
tkn = token.ILLEGAL
}
default:
self.errorUnexpected(idx, chr)
tkn = token.ILLEGAL
}
}
self.insertSemicolon = insertSemicolon
return
}
}
func (self *_parser) switch2(tkn0, tkn1 token.Token) token.Token {
if self.chr == '=' {
self.read()
return tkn1
}
return tkn0
}
func (self *_parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token {
if self.chr == '=' {
self.read()
return tkn1
}
if self.chr == chr2 {
self.read()
return tkn2
}
return tkn0
}
func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token {
if self.chr == '=' {
self.read()
return tkn1
}
if self.chr == chr2 {
self.read()
if self.chr == '=' {
self.read()
return tkn3
}
return tkn2
}
return tkn0
}
func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token {
if self.chr == '=' {
self.read()
return tkn1
}
if self.chr == chr2 {
self.read()
if self.chr == '=' {
self.read()
return tkn3
}
if self.chr == chr3 {
self.read()
if self.chr == '=' {
self.read()
return tkn5
}
return tkn4
}
return tkn2
}
return tkn0
}
func (self *_parser) chrAt(index int) _chr {
value, width := utf8.DecodeRuneInString(self.str[index:])
return _chr{
value: value,
width: width,
}
}
func (self *_parser) _peek() rune {
if self.offset+1 < self.length {
return rune(self.str[self.offset+1])
}
return -1
}
func (self *_parser) read() {
if self.offset < self.length {
self.chrOffset = self.offset
chr, width := rune(self.str[self.offset]), 1
if chr >= utf8.RuneSelf { // !ASCII
chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
if chr == utf8.RuneError && width == 1 {
self.error(self.chrOffset, "Invalid UTF-8 character")
}
}
self.offset += width
self.chr = chr
} else {
self.chrOffset = self.length
self.chr = -1 // EOF
}
}
// This is here since the functions are so similar
func (self *_RegExp_parser) read() {
if self.offset < self.length {
self.chrOffset = self.offset
chr, width := rune(self.str[self.offset]), 1
if chr >= utf8.RuneSelf { // !ASCII
chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
if chr == utf8.RuneError && width == 1 {
self.error(self.chrOffset, "Invalid UTF-8 character")
}
}
self.offset += width
self.chr = chr
} else {
self.chrOffset = self.length
self.chr = -1 // EOF
}
}
func (self *_parser) skipSingleLineComment() {
for self.chr != -1 {
self.read()
if isLineTerminator(self.chr) {
return
}
}
}
func (self *_parser) skipMultiLineComment() {
self.read()
for self.chr >= 0 {
chr := self.chr
self.read()
if chr == '*' && self.chr == '/' {
self.read()
return
}
}
self.errorUnexpected(0, self.chr)
}
func (self *_parser) skipWhiteSpace() {
for {
switch self.chr {
case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff':
self.read()
continue
case '\r':
if self._peek() == '\n' {
self.read()
}
fallthrough
case '\u2028', '\u2029', '\n':
if self.insertSemicolon {
return
}
self.read()
continue
}
if self.chr >= utf8.RuneSelf {
if unicode.IsSpace(self.chr) {
self.read()
continue
}
}
break
}
}
func (self *_parser) skipLineWhiteSpace() {
for isLineWhiteSpace(self.chr) {
self.read()
}
}
func (self *_parser) scanMantissa(base int) {
for digitValue(self.chr) < base {
self.read()
}
}
func (self *_parser) scanEscape(quote rune) {
var length, base uint32
switch self.chr {
//case '0', '1', '2', '3', '4', '5', '6', '7':
// Octal:
// length, base, limit = 3, 8, 255
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0':
self.read()
return
case '\r', '\n', '\u2028', '\u2029':
self.scanNewline()
return
case 'x':
self.read()
length, base = 2, 16
case 'u':
self.read()
length, base = 4, 16
default:
self.read() // Always make progress
return
}
var value uint32
for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
digit := uint32(digitValue(self.chr))
if digit >= base {
break
}
value = value*base + digit
self.read()
}
}
func (self *_parser) scanString(offset int) (string, error) {
// " ' /
quote := rune(self.str[offset])
for self.chr != quote {
chr := self.chr
if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
goto newline
}
self.read()
if chr == '\\' {
if quote == '/' {
if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 {
goto newline
}
self.read()
} else {
self.scanEscape(quote)
}
} else if chr == '[' && quote == '/' {
// Allow a slash (/) in a bracket character class ([...])
// TODO Fix this, this is hacky...
quote = -1
} else if chr == ']' && quote == -1 {
quote = '/'
}
}
// " ' /
self.read()
return string(self.str[offset:self.chrOffset]), nil
newline:
self.scanNewline()
err := "String not terminated"
if quote == '/' {
err = "Invalid regular expression: missing /"
self.error(self.idxOf(offset), err)
}
return "", errors.New(err)
}
func (self *_parser) scanNewline() {
if self.chr == '\r' {
self.read()
if self.chr != '\n' {
return
}
}
self.read()
}
func hex2decimal(chr byte) (value rune, ok bool) {
{
chr := rune(chr)
switch {
case '0' <= chr && chr <= '9':
return chr - '0', true
case 'a' <= chr && chr <= 'f':
return chr - 'a' + 10, true
case 'A' <= chr && chr <= 'F':
return chr - 'A' + 10, true
}
return
}
}
func parseNumberLiteral(literal string) (value interface{}, err error) {
// TODO Is Uint okay? What about -MAX_UINT
value, err = strconv.ParseInt(literal, 0, 64)
if err == nil {
return
}
parseIntErr := err // Save this first error, just in case
value, err = strconv.ParseFloat(literal, 64)
if err == nil {
return
} else if err.(*strconv.NumError).Err == strconv.ErrRange {
// Infinity, etc.
return value, nil
}
err = parseIntErr
if err.(*strconv.NumError).Err == strconv.ErrRange {
if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') {
// Could just be a very large number (e.g. 0x8000000000000000)
var value float64
literal = literal[2:]
for _, chr := range literal {
digit := digitValue(chr)
if digit >= 16 {
goto error
}
value = value*16 + float64(digit)
}
return value, nil
}
}
error:
return nil, errors.New("Illegal numeric literal")
}
func parseStringLiteral(literal string) (string, error) {
// Best case scenario...
if literal == "" {
return "", nil
}
// Slightly less-best case scenario...
if !strings.ContainsRune(literal, '\\') {
return literal, nil
}
str := literal
buffer := bytes.NewBuffer(make([]byte, 0, 3*len(literal)/2))
for len(str) > 0 {
switch chr := str[0]; {
// We do not explicitly handle the case of the quote
// value, which can be: " ' /
// This assumes we're already passed a partially well-formed literal
case chr >= utf8.RuneSelf:
chr, size := utf8.DecodeRuneInString(str)
buffer.WriteRune(chr)
str = str[size:]
continue
case chr != '\\':
buffer.WriteByte(chr)
str = str[1:]
continue
}
if len(str) <= 1 {
panic("len(str) <= 1")
}
chr := str[1]
var value rune
if chr >= utf8.RuneSelf {
str = str[1:]
var size int
value, size = utf8.DecodeRuneInString(str)
str = str[size:] // \ + <character>
} else {
str = str[2:] // \<character>
switch chr {
case 'b':
value = '\b'
case 'f':
value = '\f'
case 'n':
value = '\n'
case 'r':
value = '\r'
case 't':
value = '\t'
case 'v':
value = '\v'
case 'x', 'u':
size := 0
switch chr {
case 'x':
size = 2
case 'u':
size = 4
}
if len(str) < size {
return "", fmt.Errorf("invalid escape: \\%s: len(%q) != %d", string(chr), str, size)
}
for j := 0; j < size; j++ {
decimal, ok := hex2decimal(str[j])
if !ok {
return "", fmt.Errorf("invalid escape: \\%s: %q", string(chr), str[:size])
}
value = value<<4 | decimal
}
str = str[size:]
if chr == 'x' {
break
}
if value > utf8.MaxRune {
panic("value > utf8.MaxRune")
}
case '0':
if len(str) == 0 || '0' > str[0] || str[0] > '7' {
value = 0
break
}
fallthrough
case '1', '2', '3', '4', '5', '6', '7':
// TODO strict
value = rune(chr) - '0'
j := 0
for ; j < 2; j++ {
if len(str) < j+1 {
break
}
chr := str[j]
if '0' > chr || chr > '7' {
break
}
decimal := rune(str[j]) - '0'
value = (value << 3) | decimal
}
str = str[j:]
case '\\':
value = '\\'
case '\'', '"':
value = rune(chr)
case '\r':
if len(str) > 0 {
if str[0] == '\n' {
str = str[1:]
}
}
fallthrough
case '\n':
continue
default:
value = rune(chr)
}
}
buffer.WriteRune(value)
}
return buffer.String(), nil
}
func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {
offset := self.chrOffset
tkn := token.NUMBER
if decimalPoint {
offset--
self.scanMantissa(10)
goto exponent
}
if self.chr == '0' {
offset := self.chrOffset
self.read()
if self.chr == 'x' || self.chr == 'X' {
// Hexadecimal
self.read()
if isDigit(self.chr, 16) {
self.read()
} else {
return token.ILLEGAL, self.str[offset:self.chrOffset]
}
self.scanMantissa(16)
if self.chrOffset-offset <= 2 {
// Only "0x" or "0X"
self.error(0, "Illegal hexadecimal number")
}
goto hexadecimal
} else if self.chr == '.' {
// Float
goto float
} else {
// Octal, Float
if self.chr == 'e' || self.chr == 'E' {
goto exponent
}
self.scanMantissa(8)
if self.chr == '8' || self.chr == '9' {
return token.ILLEGAL, self.str[offset:self.chrOffset]
}
goto octal
}
}
self.scanMantissa(10)
float:
if self.chr == '.' {
self.read()
self.scanMantissa(10)
}
exponent:
if self.chr == 'e' || self.chr == 'E' {
self.read()
if self.chr == '-' || self.chr == '+' {
self.read()
}
if isDecimalDigit(self.chr) {
self.read()
self.scanMantissa(10)
} else {
return token.ILLEGAL, self.str[offset:self.chrOffset]
}
}
hexadecimal:
octal:
if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) {
return token.ILLEGAL, self.str[offset:self.chrOffset]
}
return tkn, self.str[offset:self.chrOffset]
}

View File

@ -1,380 +0,0 @@
package parser
import (
"../terst"
"testing"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token"
)
var tt = terst.Terst
var is = terst.Is
func TestLexer(t *testing.T) {
tt(t, func() {
setup := func(src string) *_parser {
parser := newParser("", src)
return parser
}
test := func(src string, test ...interface{}) {
parser := setup(src)
for len(test) > 0 {
tkn, literal, idx := parser.scan()
if len(test) > 0 {
is(tkn, test[0].(token.Token))
test = test[1:]
}
if len(test) > 0 {
is(literal, test[0].(string))
test = test[1:]
}
if len(test) > 0 {
// FIXME terst, Fix this so that cast to file.Idx is not necessary?
is(idx, file.Idx(test[0].(int)))
test = test[1:]
}
}
}
test("",
token.EOF, "", 1,
)
test("1",
token.NUMBER, "1", 1,
token.EOF, "", 2,
)
test(".0",
token.NUMBER, ".0", 1,
token.EOF, "", 3,
)
test("abc",
token.IDENTIFIER, "abc", 1,
token.EOF, "", 4,
)
test("abc(1)",
token.IDENTIFIER, "abc", 1,
token.LEFT_PARENTHESIS, "", 4,
token.NUMBER, "1", 5,
token.RIGHT_PARENTHESIS, "", 6,
token.EOF, "", 7,
)
test(".",
token.PERIOD, "", 1,
token.EOF, "", 2,
)
test("===.",
token.STRICT_EQUAL, "", 1,
token.PERIOD, "", 4,
token.EOF, "", 5,
)
test(">>>=.0",
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
token.NUMBER, ".0", 5,
token.EOF, "", 7,
)
test(">>>=0.0.",
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
token.NUMBER, "0.0", 5,
token.PERIOD, "", 8,
token.EOF, "", 9,
)
test("\"abc\"",
token.STRING, "\"abc\"", 1,
token.EOF, "", 6,
)
test("abc = //",
token.IDENTIFIER, "abc", 1,
token.ASSIGN, "", 5,
token.EOF, "", 9,
)
test("abc = 1 / 2",
token.IDENTIFIER, "abc", 1,
token.ASSIGN, "", 5,
token.NUMBER, "1", 7,
token.SLASH, "", 9,
token.NUMBER, "2", 11,
token.EOF, "", 12,
)
test("xyzzy = 'Nothing happens.'",
token.IDENTIFIER, "xyzzy", 1,
token.ASSIGN, "", 7,
token.STRING, "'Nothing happens.'", 9,
token.EOF, "", 27,
)
test("abc = !false",
token.IDENTIFIER, "abc", 1,
token.ASSIGN, "", 5,
token.NOT, "", 7,
token.BOOLEAN, "false", 8,
token.EOF, "", 13,
)
test("abc = !!true",
token.IDENTIFIER, "abc", 1,
token.ASSIGN, "", 5,
token.NOT, "", 7,
token.NOT, "", 8,
token.BOOLEAN, "true", 9,
token.EOF, "", 13,
)
test("abc *= 1",
token.IDENTIFIER, "abc", 1,
token.MULTIPLY_ASSIGN, "", 5,
token.NUMBER, "1", 8,
token.EOF, "", 9,
)
test("if 1 else",
token.IF, "if", 1,
token.NUMBER, "1", 4,
token.ELSE, "else", 6,
token.EOF, "", 10,
)
test("null",
token.NULL, "null", 1,
token.EOF, "", 5,
)
test(`"\u007a\x79\u000a\x78"`,
token.STRING, "\"\\u007a\\x79\\u000a\\x78\"", 1,
token.EOF, "", 23,
)
test(`"[First line \
Second line \
Third line\
. ]"
`,
token.STRING, "\"[First line \\\nSecond line \\\n Third line\\\n. ]\"", 1,
token.EOF, "", 53,
)
test("/",
token.SLASH, "", 1,
token.EOF, "", 2,
)
test("var abc = \"abc\uFFFFabc\"",
token.VAR, "var", 1,
token.IDENTIFIER, "abc", 5,
token.ASSIGN, "", 9,
token.STRING, "\"abc\uFFFFabc\"", 11,
token.EOF, "", 22,
)
test(`'\t' === '\r'`,
token.STRING, "'\\t'", 1,
token.STRICT_EQUAL, "", 6,
token.STRING, "'\\r'", 10,
token.EOF, "", 14,
)
test(`var \u0024 = 1`,
token.VAR, "var", 1,
token.IDENTIFIER, "$", 5,
token.ASSIGN, "", 12,
token.NUMBER, "1", 14,
token.EOF, "", 15,
)
test("10e10000",
token.NUMBER, "10e10000", 1,
token.EOF, "", 9,
)
test(`var if var class`,
token.VAR, "var", 1,
token.IF, "if", 5,
token.VAR, "var", 8,
token.KEYWORD, "class", 12,
token.EOF, "", 17,
)
test(`-0`,
token.MINUS, "", 1,
token.NUMBER, "0", 2,
token.EOF, "", 3,
)
test(`.01`,
token.NUMBER, ".01", 1,
token.EOF, "", 4,
)
test(`.01e+2`,
token.NUMBER, ".01e+2", 1,
token.EOF, "", 7,
)
test(";",
token.SEMICOLON, "", 1,
token.EOF, "", 2,
)
test(";;",
token.SEMICOLON, "", 1,
token.SEMICOLON, "", 2,
token.EOF, "", 3,
)
test("//",
token.EOF, "", 3,
)
test(";;//",
token.SEMICOLON, "", 1,
token.SEMICOLON, "", 2,
token.EOF, "", 5,
)
test("1",
token.NUMBER, "1", 1,
)
test("12 123",
token.NUMBER, "12", 1,
token.NUMBER, "123", 4,
)
test("1.2 12.3",
token.NUMBER, "1.2", 1,
token.NUMBER, "12.3", 5,
)
test("/ /=",
token.SLASH, "", 1,
token.QUOTIENT_ASSIGN, "", 3,
)
test(`"abc"`,
token.STRING, `"abc"`, 1,
)
test(`'abc'`,
token.STRING, `'abc'`, 1,
)
test("++",
token.INCREMENT, "", 1,
)
test(">",
token.GREATER, "", 1,
)
test(">=",
token.GREATER_OR_EQUAL, "", 1,
)
test(">>",
token.SHIFT_RIGHT, "", 1,
)
test(">>=",
token.SHIFT_RIGHT_ASSIGN, "", 1,
)
test(">>>",
token.UNSIGNED_SHIFT_RIGHT, "", 1,
)
test(">>>=",
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
)
test("1 \"abc\"",
token.NUMBER, "1", 1,
token.STRING, "\"abc\"", 3,
)
test(",",
token.COMMA, "", 1,
)
test("1, \"abc\"",
token.NUMBER, "1", 1,
token.COMMA, "", 2,
token.STRING, "\"abc\"", 4,
)
test("new abc(1, 3.14159);",
token.NEW, "new", 1,
token.IDENTIFIER, "abc", 5,
token.LEFT_PARENTHESIS, "", 8,
token.NUMBER, "1", 9,
token.COMMA, "", 10,
token.NUMBER, "3.14159", 12,
token.RIGHT_PARENTHESIS, "", 19,
token.SEMICOLON, "", 20,
)
test("1 == \"1\"",
token.NUMBER, "1", 1,
token.EQUAL, "", 3,
token.STRING, "\"1\"", 6,
)
test("1\n[]\n",
token.NUMBER, "1", 1,
token.LEFT_BRACKET, "", 3,
token.RIGHT_BRACKET, "", 4,
)
test("1\ufeff[]\ufeff",
token.NUMBER, "1", 1,
token.LEFT_BRACKET, "", 5,
token.RIGHT_BRACKET, "", 6,
)
// ILLEGAL
test(`3ea`,
token.ILLEGAL, "3e", 1,
token.IDENTIFIER, "a", 3,
token.EOF, "", 4,
)
test(`3in`,
token.ILLEGAL, "3", 1,
token.IN, "in", 2,
token.EOF, "", 4,
)
test("\"Hello\nWorld\"",
token.ILLEGAL, "", 1,
token.IDENTIFIER, "World", 8,
token.ILLEGAL, "", 13,
token.EOF, "", 14,
)
test("\u203f = 10",
token.ILLEGAL, "", 1,
token.ASSIGN, "", 5,
token.NUMBER, "10", 7,
token.EOF, "", 9,
)
test(`"\x0G"`,
token.STRING, "\"\\x0G\"", 1,
token.EOF, "", 7,
)
})
}

View File

@ -1,930 +0,0 @@
package parser
import (
"bytes"
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"testing"
"github.com/robertkrimen/otto/ast"
)
func marshal(name string, children ...interface{}) interface{} {
if len(children) == 1 {
if name == "" {
return testMarshalNode(children[0])
}
return map[string]interface{}{
name: children[0],
}
}
map_ := map[string]interface{}{}
length := len(children) / 2
for i := 0; i < length; i++ {
name := children[i*2].(string)
value := children[i*2+1]
map_[name] = value
}
if name == "" {
return map_
}
return map[string]interface{}{
name: map_,
}
}
func testMarshalNode(node interface{}) interface{} {
switch node := node.(type) {
// Expression
case *ast.ArrayLiteral:
return marshal("Array", testMarshalNode(node.Value))
case *ast.AssignExpression:
return marshal("Assign",
"Left", testMarshalNode(node.Left),
"Right", testMarshalNode(node.Right),
)
case *ast.BinaryExpression:
return marshal("BinaryExpression",
"Operator", node.Operator.String(),
"Left", testMarshalNode(node.Left),
"Right", testMarshalNode(node.Right),
)
case *ast.BooleanLiteral:
return marshal("Literal", node.Value)
case *ast.CallExpression:
return marshal("Call",
"Callee", testMarshalNode(node.Callee),
"ArgumentList", testMarshalNode(node.ArgumentList),
)
case *ast.ConditionalExpression:
return marshal("Conditional",
"Test", testMarshalNode(node.Test),
"Consequent", testMarshalNode(node.Consequent),
"Alternate", testMarshalNode(node.Alternate),
)
case *ast.DotExpression:
return marshal("Dot",
"Left", testMarshalNode(node.Left),
"Member", node.Identifier.Name,
)
case *ast.NewExpression:
return marshal("New",
"Callee", testMarshalNode(node.Callee),
"ArgumentList", testMarshalNode(node.ArgumentList),
)
case *ast.NullLiteral:
return marshal("Literal", nil)
case *ast.NumberLiteral:
return marshal("Literal", node.Value)
case *ast.ObjectLiteral:
return marshal("Object", testMarshalNode(node.Value))
case *ast.RegExpLiteral:
return marshal("Literal", node.Literal)
case *ast.StringLiteral:
return marshal("Literal", node.Literal)
case *ast.VariableExpression:
return []interface{}{node.Name, testMarshalNode(node.Initializer)}
// Statement
case *ast.Program:
return testMarshalNode(node.Body)
case *ast.BlockStatement:
return marshal("BlockStatement", testMarshalNode(node.List))
case *ast.EmptyStatement:
return "EmptyStatement"
case *ast.ExpressionStatement:
return testMarshalNode(node.Expression)
case *ast.ForInStatement:
return marshal("ForIn",
"Into", marshal("", node.Into),
"Source", marshal("", node.Source),
"Body", marshal("", node.Body),
)
case *ast.FunctionLiteral:
return marshal("Function", testMarshalNode(node.Body))
case *ast.Identifier:
return marshal("Identifier", node.Name)
case *ast.IfStatement:
if_ := marshal("",
"Test", testMarshalNode(node.Test),
"Consequent", testMarshalNode(node.Consequent),
).(map[string]interface{})
if node.Alternate != nil {
if_["Alternate"] = testMarshalNode(node.Alternate)
}
return marshal("If", if_)
case *ast.LabelledStatement:
return marshal("Label",
"Name", node.Label.Name,
"Statement", testMarshalNode(node.Statement),
)
case ast.Property:
return marshal("",
"Key", node.Key,
"Value", testMarshalNode(node.Value),
)
case *ast.ReturnStatement:
return marshal("Return", testMarshalNode(node.Argument))
case *ast.SequenceExpression:
return marshal("Sequence", testMarshalNode(node.Sequence))
case *ast.ThrowStatement:
return marshal("Throw", testMarshalNode(node.Argument))
case *ast.VariableStatement:
return marshal("Var", testMarshalNode(node.List))
}
{
value := reflect.ValueOf(node)
if value.Kind() == reflect.Slice {
tmp0 := []interface{}{}
for index := 0; index < value.Len(); index++ {
tmp0 = append(tmp0, testMarshalNode(value.Index(index).Interface()))
}
return tmp0
}
}
if node != nil {
fmt.Fprintf(os.Stderr, "testMarshalNode(%T)\n", node)
}
return nil
}
func testMarshal(node interface{}) string {
value, err := json.Marshal(testMarshalNode(node))
if err != nil {
panic(err)
}
return string(value)
}
func TestParserAST(t *testing.T) {
tt(t, func() {
test := func(inputOutput string) {
match := matchBeforeAfterSeparator.FindStringIndex(inputOutput)
input := strings.TrimSpace(inputOutput[0:match[0]])
wantOutput := strings.TrimSpace(inputOutput[match[1]:])
_, program, err := testParse(input)
is(err, nil)
haveOutput := testMarshal(program)
tmp0, tmp1 := bytes.Buffer{}, bytes.Buffer{}
json.Indent(&tmp0, []byte(haveOutput), "\t\t", " ")
json.Indent(&tmp1, []byte(wantOutput), "\t\t", " ")
is("\n\t\t"+tmp0.String(), "\n\t\t"+tmp1.String())
}
test(`
---
[]
`)
test(`
;
---
[
"EmptyStatement"
]
`)
test(`
;;;
---
[
"EmptyStatement",
"EmptyStatement",
"EmptyStatement"
]
`)
test(`
1; true; abc; "abc"; null;
---
[
{
"Literal": 1
},
{
"Literal": true
},
{
"Identifier": "abc"
},
{
"Literal": "\"abc\""
},
{
"Literal": null
}
]
`)
test(`
{ 1; null; 3.14159; ; }
---
[
{
"BlockStatement": [
{
"Literal": 1
},
{
"Literal": null
},
{
"Literal": 3.14159
},
"EmptyStatement"
]
}
]
`)
test(`
new abc();
---
[
{
"New": {
"ArgumentList": [],
"Callee": {
"Identifier": "abc"
}
}
}
]
`)
test(`
new abc(1, 3.14159)
---
[
{
"New": {
"ArgumentList": [
{
"Literal": 1
},
{
"Literal": 3.14159
}
],
"Callee": {
"Identifier": "abc"
}
}
}
]
`)
test(`
true ? false : true
---
[
{
"Conditional": {
"Alternate": {
"Literal": true
},
"Consequent": {
"Literal": false
},
"Test": {
"Literal": true
}
}
}
]
`)
test(`
true || false
---
[
{
"BinaryExpression": {
"Left": {
"Literal": true
},
"Operator": "||",
"Right": {
"Literal": false
}
}
}
]
`)
test(`
0 + { abc: true }
---
[
{
"BinaryExpression": {
"Left": {
"Literal": 0
},
"Operator": "+",
"Right": {
"Object": [
{
"Key": "abc",
"Value": {
"Literal": true
}
}
]
}
}
}
]
`)
test(`
1 == "1"
---
[
{
"BinaryExpression": {
"Left": {
"Literal": 1
},
"Operator": "==",
"Right": {
"Literal": "\"1\""
}
}
}
]
`)
test(`
abc(1)
---
[
{
"Call": {
"ArgumentList": [
{
"Literal": 1
}
],
"Callee": {
"Identifier": "abc"
}
}
}
]
`)
test(`
Math.pow(3, 2)
---
[
{
"Call": {
"ArgumentList": [
{
"Literal": 3
},
{
"Literal": 2
}
],
"Callee": {
"Dot": {
"Left": {
"Identifier": "Math"
},
"Member": "pow"
}
}
}
}
]
`)
test(`
1, 2, 3
---
[
{
"Sequence": [
{
"Literal": 1
},
{
"Literal": 2
},
{
"Literal": 3
}
]
}
]
`)
test(`
/ abc / gim;
---
[
{
"Literal": "/ abc / gim"
}
]
`)
test(`
if (0)
1;
---
[
{
"If": {
"Consequent": {
"Literal": 1
},
"Test": {
"Literal": 0
}
}
}
]
`)
test(`
0+function(){
return;
}
---
[
{
"BinaryExpression": {
"Left": {
"Literal": 0
},
"Operator": "+",
"Right": {
"Function": {
"BlockStatement": [
{
"Return": null
}
]
}
}
}
}
]
`)
test(`
xyzzy // Ignore it
// Ignore this
// And this
/* And all..
... of this!
*/
"Nothing happens."
// And finally this
---
[
{
"Identifier": "xyzzy"
},
{
"Literal": "\"Nothing happens.\""
}
]
`)
test(`
((x & (x = 1)) !== 0)
---
[
{
"BinaryExpression": {
"Left": {
"BinaryExpression": {
"Left": {
"Identifier": "x"
},
"Operator": "\u0026",
"Right": {
"Assign": {
"Left": {
"Identifier": "x"
},
"Right": {
"Literal": 1
}
}
}
}
},
"Operator": "!==",
"Right": {
"Literal": 0
}
}
}
]
`)
test(`
{ abc: 'def' }
---
[
{
"BlockStatement": [
{
"Label": {
"Name": "abc",
"Statement": {
"Literal": "'def'"
}
}
}
]
}
]
`)
test(`
// This is not an object, this is a string literal with a label!
({ abc: 'def' })
---
[
{
"Object": [
{
"Key": "abc",
"Value": {
"Literal": "'def'"
}
}
]
}
]
`)
test(`
[,]
---
[
{
"Array": [
null
]
}
]
`)
test(`
[,,]
---
[
{
"Array": [
null,
null
]
}
]
`)
test(`
({ get abc() {} })
---
[
{
"Object": [
{
"Key": "abc",
"Value": {
"Function": {
"BlockStatement": []
}
}
}
]
}
]
`)
test(`
/abc/.source
---
[
{
"Dot": {
"Left": {
"Literal": "/abc/"
},
"Member": "source"
}
}
]
`)
test(`
xyzzy
throw new TypeError("Nothing happens.")
---
[
{
"Identifier": "xyzzy"
},
{
"Throw": {
"New": {
"ArgumentList": [
{
"Literal": "\"Nothing happens.\""
}
],
"Callee": {
"Identifier": "TypeError"
}
}
}
}
]
`)
// When run, this will call a type error to be thrown
// This is essentially the same as:
//
// var abc = 1(function(){})()
//
test(`
var abc = 1
(function(){
})()
---
[
{
"Var": [
[
"abc",
{
"Call": {
"ArgumentList": [],
"Callee": {
"Call": {
"ArgumentList": [
{
"Function": {
"BlockStatement": []
}
}
],
"Callee": {
"Literal": 1
}
}
}
}
}
]
]
}
]
`)
test(`
"use strict"
---
[
{
"Literal": "\"use strict\""
}
]
`)
test(`
"use strict"
abc = 1 + 2 + 11
---
[
{
"Literal": "\"use strict\""
},
{
"Assign": {
"Left": {
"Identifier": "abc"
},
"Right": {
"BinaryExpression": {
"Left": {
"BinaryExpression": {
"Left": {
"Literal": 1
},
"Operator": "+",
"Right": {
"Literal": 2
}
}
},
"Operator": "+",
"Right": {
"Literal": 11
}
}
}
}
}
]
`)
test(`
abc = function() { 'use strict' }
---
[
{
"Assign": {
"Left": {
"Identifier": "abc"
},
"Right": {
"Function": {
"BlockStatement": [
{
"Literal": "'use strict'"
}
]
}
}
}
}
]
`)
test(`
for (var abc in def) {
}
---
[
{
"ForIn": {
"Body": {
"BlockStatement": []
},
"Into": [
"abc",
null
],
"Source": {
"Identifier": "def"
}
}
}
]
`)
test(`
abc = {
'"': "'",
"'": '"',
}
---
[
{
"Assign": {
"Left": {
"Identifier": "abc"
},
"Right": {
"Object": [
{
"Key": "\"",
"Value": {
"Literal": "\"'\""
}
},
{
"Key": "'",
"Value": {
"Literal": "'\"'"
}
}
]
}
}
}
]
`)
return
test(`
if (!abc && abc.jkl(def) && abc[0] === +abc[0] && abc.length < ghi) {
}
---
[
{
"If": {
"Consequent": {
"BlockStatement": []
},
"Test": {
"BinaryExpression": {
"Left": {
"BinaryExpression": {
"Left": {
"BinaryExpression": {
"Left": null,
"Operator": "\u0026\u0026",
"Right": {
"Call": {
"ArgumentList": [
{
"Identifier": "def"
}
],
"Callee": {
"Dot": {
"Left": {
"Identifier": "abc"
},
"Member": "jkl"
}
}
}
}
}
},
"Operator": "\u0026\u0026",
"Right": {
"BinaryExpression": {
"Left": null,
"Operator": "===",
"Right": null
}
}
}
},
"Operator": "\u0026\u0026",
"Right": {
"BinaryExpression": {
"Left": {
"Dot": {
"Left": {
"Identifier": "abc"
},
"Member": "length"
}
},
"Operator": "\u003c",
"Right": {
"Identifier": "ghi"
}
}
}
}
}
}
}
]
`)
})
}

View File

@ -1,270 +0,0 @@
/*
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"
"errors"
"io"
"io/ioutil"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token"
)
// A Mode value is a set of flags (or 0). They control optional parser functionality.
type Mode uint
const (
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
)
type _parser struct {
filename string
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
}
func _newParser(filename, src string, base int) *_parser {
return &_parser{
chr: ' ', // This is set so we can start scanning by skipping whitespace
str: src,
length: len(src),
base: base,
}
}
func newParser(filename, src string) *_parser {
return _newParser(filename, src, 1)
}
func ReadSource(filename string, src interface{}) ([]byte, error) {
if src != nil {
switch src := src.(type) {
case string:
return []byte(src), nil
case []byte:
return src, nil
case *bytes.Buffer:
if src != nil {
return src.Bytes(), nil
}
case io.Reader:
var bfr bytes.Buffer
if _, err := io.Copy(&bfr, src); err != nil {
return nil, err
}
return bfr.Bytes(), nil
}
return nil, errors.New("invalid source")
}
return ioutil.ReadFile(filename)
}
// ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
// the corresponding ast.Program node.
//
// If fileSet == nil, ParseFile parses source without a FileSet.
// If fileSet != nil, ParseFile first adds filename and src to fileSet.
//
// The filename argument is optional and is used for labelling errors, etc.
//
// src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
//
// // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
// program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
//
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) {
str, err := ReadSource(filename, src)
if err != nil {
return nil, err
}
{
str := string(str)
base := 1
if fileSet != nil {
base = fileSet.AddFile(filename, str)
}
parser := _newParser(filename, str, base)
parser.mode = mode
return parser.parse()
}
}
// ParseFunction parses a given parameter list and body as a function and returns the
// corresponding ast.FunctionLiteral node.
//
// The parameter list, if any, should be a comma-separated list of identifiers.
//
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
src := "(function(" + parameterList + ") {\n" + body + "\n})"
parser := _newParser("", src, 1)
program, err := parser.parse()
if err != nil {
return nil, err
}
return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
}
func (self *_parser) slice(idx0, idx1 file.Idx) string {
from := int(idx0) - self.base
to := int(idx1) - self.base
if from >= 0 && to <= len(self.str) {
return self.str[from:to]
}
return ""
}
func (self *_parser) parse() (*ast.Program, error) {
self.next()
program := self.parseProgram()
if false {
self.errors.Sort()
}
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.filename
line, last := lineCount(str)
position.Line = 1 + line
if last >= 0 {
position.Column = offset - last
} else {
position.Column = 1 + len(str)
}
return position
}

File diff suppressed because it is too large Load Diff

View File

@ -1,358 +0,0 @@
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
}

View File

@ -1,149 +0,0 @@
package parser
import (
"regexp"
"testing"
)
func TestRegExp(t *testing.T) {
tt(t, func() {
{
// err
test := func(input string, expect interface{}) {
_, err := TransformRegExp(input)
is(err, expect)
}
test("[", "Unterminated character class")
test("(", "Unterminated group")
test("(?=)", "re2: Invalid (?=) <lookahead>")
test("(?=)", "re2: Invalid (?=) <lookahead>")
test("(?!)", "re2: Invalid (?!) <lookahead>")
// An error anyway
test("(?=", "re2: Invalid (?=) <lookahead>")
test("\\1", "re2: Invalid \\1 <backreference>")
test("\\90", "re2: Invalid \\90 <backreference>")
test("\\9123456789", "re2: Invalid \\9123456789 <backreference>")
test("\\(?=)", "Unmatched ')'")
test(")", "Unmatched ')'")
}
{
// err
test := func(input, expect string, expectErr interface{}) {
output, err := TransformRegExp(input)
is(output, expect)
is(err, expectErr)
}
test("(?!)", "(?!)", "re2: Invalid (?!) <lookahead>")
test(")", "", "Unmatched ')'")
test("(?!))", "", "re2: Invalid (?!) <lookahead>")
test("\\0", "\\0", nil)
test("\\1", "\\1", "re2: Invalid \\1 <backreference>")
test("\\9123456789", "\\9123456789", "re2: Invalid \\9123456789 <backreference>")
}
{
// err
test := func(input string, expect string) {
result, err := TransformRegExp(input)
is(err, nil)
if is(result, expect) {
_, err := regexp.Compile(result)
if !is(err, nil) {
t.Log(result)
}
}
}
test("", "")
test("abc", "abc")
test(`\abc`, `abc`)
test(`\a\b\c`, `a\bc`)
test(`\x`, `x`)
test(`\c`, `c`)
test(`\cA`, `\x01`)
test(`\cz`, `\x1a`)
test(`\ca`, `\x01`)
test(`\cj`, `\x0a`)
test(`\ck`, `\x0b`)
test(`\+`, `\+`)
test(`[\b]`, `[\x08]`)
test(`\u0z01\x\undefined`, `u0z01xundefined`)
test(`\\|'|\r|\n|\t|\u2028|\u2029`, `\\|'|\r|\n|\t|\x{2028}|\x{2029}`)
test("]", "]")
test("}", "}")
test("%", "%")
test("(%)", "(%)")
test("(?:[%\\s])", "(?:[%\\s])")
test("[[]", "[[]")
test("\\101", "\\x41")
test("\\51", "\\x29")
test("\\051", "\\x29")
test("\\175", "\\x7d")
test("\\04", "\\x04")
test(`<%([\s\S]+?)%>`, `<%([\s\S]+?)%>`)
test(`(.)^`, "(.)^")
test(`<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$`, `<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$`)
test(`\$`, `\$`)
test(`[G-b]`, `[G-b]`)
test(`[G-b\0]`, `[G-b\0]`)
}
})
}
func TestTransformRegExp(t *testing.T) {
tt(t, func() {
pattern, err := TransformRegExp(`\s+abc\s+`)
is(err, nil)
is(pattern, `\s+abc\s+`)
is(regexp.MustCompile(pattern).MatchString("\t abc def"), true)
})
}

View File

@ -1,44 +0,0 @@
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
}

View File

@ -1,662 +0,0 @@
package parser
import (
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/token"
)
func (self *_parser) parseBlockStatement() *ast.BlockStatement {
node := &ast.BlockStatement{}
node.LeftBrace = self.expect(token.LEFT_BRACE)
node.List = self.parseStatementList()
node.RightBrace = self.expect(token.RIGHT_BRACE)
return node
}
func (self *_parser) parseEmptyStatement() ast.Statement {
idx := self.expect(token.SEMICOLON)
return &ast.EmptyStatement{Semicolon: idx}
}
func (self *_parser) parseStatementList() (list []ast.Statement) {
for self.token != token.RIGHT_BRACE && self.token != token.EOF {
list = append(list, self.parseStatement())
}
return
}
func (self *_parser) parseStatement() ast.Statement {
if self.token == token.EOF {
self.errorUnexpectedToken(self.token)
return &ast.BadStatement{From: self.idx, To: self.idx + 1}
}
switch self.token {
case token.SEMICOLON:
return self.parseEmptyStatement()
case token.LEFT_BRACE:
return self.parseBlockStatement()
case token.IF:
return self.parseIfStatement()
case token.DO:
return self.parseDoWhileStatement()
case token.WHILE:
return self.parseWhileStatement()
case token.FOR:
return self.parseForOrForInStatement()
case token.BREAK:
return self.parseBreakStatement()
case token.CONTINUE:
return self.parseContinueStatement()
case token.DEBUGGER:
return self.parseDebuggerStatement()
case token.WITH:
return self.parseWithStatement()
case token.VAR:
return self.parseVariableStatement()
case token.FUNCTION:
self.parseFunction(true)
// FIXME
return &ast.EmptyStatement{}
case token.SWITCH:
return self.parseSwitchStatement()
case token.RETURN:
return self.parseReturnStatement()
case token.THROW:
return self.parseThrowStatement()
case token.TRY:
return self.parseTryStatement()
}
expression := self.parseExpression()
if identifier, isIdentifier := expression.(*ast.Identifier); isIdentifier && self.token == token.COLON {
// LabelledStatement
colon := self.idx
self.next() // :
label := identifier.Name
for _, value := range self.scope.labels {
if label == value {
self.error(identifier.Idx0(), "Label '%s' already exists", label)
}
}
self.scope.labels = append(self.scope.labels, label) // Push the label
statement := self.parseStatement()
self.scope.labels = self.scope.labels[:len(self.scope.labels)-1] // Pop the label
return &ast.LabelledStatement{
Label: identifier,
Colon: colon,
Statement: statement,
}
}
self.optionalSemicolon()
return &ast.ExpressionStatement{
Expression: expression,
}
}
func (self *_parser) parseTryStatement() ast.Statement {
node := &ast.TryStatement{
Try: self.expect(token.TRY),
Body: self.parseBlockStatement(),
}
if self.token == token.CATCH {
catch := self.idx
self.next()
self.expect(token.LEFT_PARENTHESIS)
if self.token != token.IDENTIFIER {
self.expect(token.IDENTIFIER)
self.nextStatement()
return &ast.BadStatement{From: catch, To: self.idx}
} else {
identifier := self.parseIdentifier()
self.expect(token.RIGHT_PARENTHESIS)
node.Catch = &ast.CatchStatement{
Catch: catch,
Parameter: identifier,
Body: self.parseBlockStatement(),
}
}
}
if self.token == token.FINALLY {
self.next()
node.Finally = self.parseBlockStatement()
}
if node.Catch == nil && node.Finally == nil {
self.error(node.Try, "Missing catch or finally after try")
return &ast.BadStatement{From: node.Try, To: node.Body.Idx1()}
}
return node
}
func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
opening := self.expect(token.LEFT_PARENTHESIS)
var list []*ast.Identifier
for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF {
if self.token != token.IDENTIFIER {
self.expect(token.IDENTIFIER)
} else {
list = append(list, self.parseIdentifier())
}
if self.token != token.RIGHT_PARENTHESIS {
self.expect(token.COMMA)
}
}
closing := self.expect(token.RIGHT_PARENTHESIS)
return &ast.ParameterList{
Opening: opening,
List: list,
Closing: closing,
}
}
func (self *_parser) parseParameterList() (list []string) {
for self.token != token.EOF {
if self.token != token.IDENTIFIER {
self.expect(token.IDENTIFIER)
}
list = append(list, self.literal)
self.next()
if self.token != token.EOF {
self.expect(token.COMMA)
}
}
return
}
func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral {
node := &ast.FunctionLiteral{
Function: self.expect(token.FUNCTION),
}
var name *ast.Identifier
if self.token == token.IDENTIFIER {
name = self.parseIdentifier()
if declaration {
self.scope.declare(&ast.FunctionDeclaration{
Function: node,
})
}
} else if declaration {
// Use expect error handling
self.expect(token.IDENTIFIER)
}
node.Name = name
node.ParameterList = self.parseFunctionParameterList()
self.parseFunctionBlock(node)
node.Source = self.slice(node.Idx0(), node.Idx1())
return node
}
func (self *_parser) parseFunctionBlock(node *ast.FunctionLiteral) {
{
self.openScope()
inFunction := self.scope.inFunction
self.scope.inFunction = true
defer func() {
self.scope.inFunction = inFunction
self.closeScope()
}()
node.Body = self.parseBlockStatement()
node.DeclarationList = self.scope.declarationList
}
}
func (self *_parser) parseDebuggerStatement() ast.Statement {
idx := self.expect(token.DEBUGGER)
node := &ast.DebuggerStatement{
Debugger: idx,
}
self.semicolon()
return node
}
func (self *_parser) parseReturnStatement() ast.Statement {
idx := self.expect(token.RETURN)
if !self.scope.inFunction {
self.error(idx, "Illegal return statement")
self.nextStatement()
return &ast.BadStatement{From: idx, To: self.idx}
}
node := &ast.ReturnStatement{
Return: idx,
}
if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE && self.token != token.EOF {
node.Argument = self.parseExpression()
}
self.semicolon()
return node
}
func (self *_parser) parseThrowStatement() ast.Statement {
idx := self.expect(token.THROW)
if self.implicitSemicolon {
if self.chr == -1 { // Hackish
self.error(idx, "Unexpected end of input")
} else {
self.error(idx, "Illegal newline after throw")
}
self.nextStatement()
return &ast.BadStatement{From: idx, To: self.idx}
}
node := &ast.ThrowStatement{
Argument: self.parseExpression(),
}
self.semicolon()
return node
}
func (self *_parser) parseSwitchStatement() ast.Statement {
self.expect(token.SWITCH)
self.expect(token.LEFT_PARENTHESIS)
node := &ast.SwitchStatement{
Discriminant: self.parseExpression(),
Default: -1,
}
self.expect(token.RIGHT_PARENTHESIS)
self.expect(token.LEFT_BRACE)
inSwitch := self.scope.inSwitch
self.scope.inSwitch = true
defer func() {
self.scope.inSwitch = inSwitch
}()
for index := 0; self.token != token.EOF; index++ {
if self.token == token.RIGHT_BRACE {
self.next()
break
}
clause := self.parseCaseStatement()
if clause.Test == nil {
if node.Default != -1 {
self.error(clause.Case, "Already saw a default in switch")
}
node.Default = index
}
node.Body = append(node.Body, clause)
}
return node
}
func (self *_parser) parseWithStatement() ast.Statement {
self.expect(token.WITH)
self.expect(token.LEFT_PARENTHESIS)
node := &ast.WithStatement{
Object: self.parseExpression(),
}
self.expect(token.RIGHT_PARENTHESIS)
node.Body = self.parseStatement()
return node
}
func (self *_parser) parseCaseStatement() *ast.CaseStatement {
node := &ast.CaseStatement{
Case: self.idx,
}
if self.token == token.DEFAULT {
self.next()
} else {
self.expect(token.CASE)
node.Test = self.parseExpression()
}
self.expect(token.COLON)
for {
if self.token == token.EOF ||
self.token == token.RIGHT_BRACE ||
self.token == token.CASE ||
self.token == token.DEFAULT {
break
}
node.Consequent = append(node.Consequent, self.parseStatement())
}
return node
}
func (self *_parser) parseIterationStatement() ast.Statement {
inIteration := self.scope.inIteration
self.scope.inIteration = true
defer func() {
self.scope.inIteration = inIteration
}()
return self.parseStatement()
}
func (self *_parser) parseForIn(into ast.Expression) *ast.ForInStatement {
// Already have consumed "<into> in"
source := self.parseExpression()
self.expect(token.RIGHT_PARENTHESIS)
return &ast.ForInStatement{
Into: into,
Source: source,
Body: self.parseIterationStatement(),
}
}
func (self *_parser) parseFor(initializer ast.Expression) *ast.ForStatement {
// Already have consumed "<initializer> ;"
var test, update ast.Expression
if self.token != token.SEMICOLON {
test = self.parseExpression()
}
self.expect(token.SEMICOLON)
if self.token != token.RIGHT_PARENTHESIS {
update = self.parseExpression()
}
self.expect(token.RIGHT_PARENTHESIS)
return &ast.ForStatement{
Initializer: initializer,
Test: test,
Update: update,
Body: self.parseIterationStatement(),
}
}
func (self *_parser) parseForOrForInStatement() ast.Statement {
idx := self.expect(token.FOR)
self.expect(token.LEFT_PARENTHESIS)
var left []ast.Expression
forIn := false
if self.token != token.SEMICOLON {
allowIn := self.scope.allowIn
self.scope.allowIn = false
if self.token == token.VAR {
var_ := self.idx
self.next()
list := self.parseVariableDeclarationList(var_)
if len(list) == 1 && self.token == token.IN {
self.next() // in
forIn = true
left = []ast.Expression{list[0]} // There is only one declaration
} else {
left = list
}
} else {
left = append(left, self.parseExpression())
if self.token == token.IN {
self.next()
forIn = true
}
}
self.scope.allowIn = allowIn
}
if forIn {
switch left[0].(type) {
case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression, *ast.VariableExpression:
// These are all acceptable
default:
self.error(idx, "Invalid left-hand side in for-in")
self.nextStatement()
return &ast.BadStatement{From: idx, To: self.idx}
}
return self.parseForIn(left[0])
}
self.expect(token.SEMICOLON)
return self.parseFor(&ast.SequenceExpression{Sequence: left})
}
func (self *_parser) parseVariableStatement() *ast.VariableStatement {
idx := self.expect(token.VAR)
list := self.parseVariableDeclarationList(idx)
self.semicolon()
return &ast.VariableStatement{
Var: idx,
List: list,
}
}
func (self *_parser) parseDoWhileStatement() ast.Statement {
inIteration := self.scope.inIteration
self.scope.inIteration = true
defer func() {
self.scope.inIteration = inIteration
}()
self.expect(token.DO)
node := &ast.DoWhileStatement{}
if self.token == token.LEFT_BRACE {
node.Body = self.parseBlockStatement()
} else {
node.Body = self.parseStatement()
}
self.expect(token.WHILE)
self.expect(token.LEFT_PARENTHESIS)
node.Test = self.parseExpression()
self.expect(token.RIGHT_PARENTHESIS)
return node
}
func (self *_parser) parseWhileStatement() ast.Statement {
self.expect(token.WHILE)
self.expect(token.LEFT_PARENTHESIS)
node := &ast.WhileStatement{
Test: self.parseExpression(),
}
self.expect(token.RIGHT_PARENTHESIS)
node.Body = self.parseIterationStatement()
return node
}
func (self *_parser) parseIfStatement() ast.Statement {
self.expect(token.IF)
self.expect(token.LEFT_PARENTHESIS)
node := &ast.IfStatement{
Test: self.parseExpression(),
}
self.expect(token.RIGHT_PARENTHESIS)
if self.token == token.LEFT_BRACE {
node.Consequent = self.parseBlockStatement()
} else {
node.Consequent = self.parseStatement()
}
if self.token == token.ELSE {
self.next()
node.Alternate = self.parseStatement()
}
return node
}
func (self *_parser) parseSourceElement() ast.Statement {
return self.parseStatement()
}
func (self *_parser) parseSourceElements() []ast.Statement {
body := []ast.Statement(nil)
for {
if self.token != token.STRING {
break
}
body = append(body, self.parseSourceElement())
}
for self.token != token.EOF {
body = append(body, self.parseSourceElement())
}
return body
}
func (self *_parser) parseProgram() *ast.Program {
self.openScope()
defer self.closeScope()
return &ast.Program{
Body: self.parseSourceElements(),
DeclarationList: self.scope.declarationList,
}
}
func (self *_parser) parseBreakStatement() ast.Statement {
idx := self.expect(token.BREAK)
semicolon := self.implicitSemicolon
if self.token == token.SEMICOLON {
semicolon = true
self.next()
}
if semicolon || self.token == token.RIGHT_BRACE {
self.implicitSemicolon = false
if !self.scope.inIteration && !self.scope.inSwitch {
goto illegal
}
return &ast.BranchStatement{
Idx: idx,
Token: token.BREAK,
}
}
if self.token == token.IDENTIFIER {
identifier := self.parseIdentifier()
if !self.scope.hasLabel(identifier.Name) {
self.error(idx, "Undefined label '%s'", identifier.Name)
return &ast.BadStatement{From: idx, To: identifier.Idx1()}
}
self.semicolon()
return &ast.BranchStatement{
Idx: idx,
Token: token.BREAK,
Label: identifier,
}
}
self.expect(token.IDENTIFIER)
illegal:
self.error(idx, "Illegal break statement")
self.nextStatement()
return &ast.BadStatement{From: idx, To: self.idx}
}
func (self *_parser) parseContinueStatement() ast.Statement {
idx := self.expect(token.CONTINUE)
semicolon := self.implicitSemicolon
if self.token == token.SEMICOLON {
semicolon = true
self.next()
}
if semicolon || self.token == token.RIGHT_BRACE {
self.implicitSemicolon = false
if !self.scope.inIteration {
goto illegal
}
return &ast.BranchStatement{
Idx: idx,
Token: token.CONTINUE,
}
}
if self.token == token.IDENTIFIER {
identifier := self.parseIdentifier()
if !self.scope.hasLabel(identifier.Name) {
self.error(idx, "Undefined label '%s'", identifier.Name)
return &ast.BadStatement{From: idx, To: identifier.Idx1()}
}
if !self.scope.inIteration {
goto illegal
}
self.semicolon()
return &ast.BranchStatement{
Idx: idx,
Token: token.CONTINUE,
Label: identifier,
}
}
self.expect(token.IDENTIFIER)
illegal:
self.error(idx, "Illegal continue statement")
self.nextStatement()
return &ast.BadStatement{From: idx, To: self.idx}
}
// Find the next statement after an error (recover)
func (self *_parser) nextStatement() {
for {
switch self.token {
case token.BREAK, token.CONTINUE,
token.FOR, token.IF, token.RETURN, token.SWITCH,
token.VAR, token.DO, token.TRY, token.WITH,
token.WHILE, token.THROW, token.CATCH, token.FINALLY:
// Return only if parser made some progress since last
// sync or if it has not reached 10 next calls without
// progress. Otherwise consume at least one token to
// avoid an endless parser loop
if self.idx == self.recover.idx && self.recover.count < 10 {
self.recover.count++
return
}
if self.idx > self.recover.idx {
self.recover.idx = self.idx
self.recover.count = 0
return
}
// Reaching here indicates a parser bug, likely an
// incorrect token list in this function, but it only
// leads to skipping of possibly correct code if a
// previous error is present, and thus is preferred
// over a non-terminating parse.
case token.EOF:
return
}
self.next()
}
}

View File

@ -1,51 +0,0 @@
# registry
--
import "github.com/robertkrimen/otto/registry"
Package registry is an expirmental package to facillitate altering the otto
runtime via import.
This interface can change at any time.
## Usage
#### func Apply
```go
func Apply(callback func(Entry))
```
#### type Entry
```go
type Entry struct {
}
```
#### func Register
```go
func Register(source func() string) *Entry
```
#### func (*Entry) Disable
```go
func (self *Entry) Disable()
```
#### func (*Entry) Enable
```go
func (self *Entry) Enable()
```
#### func (Entry) Source
```go
func (self Entry) Source() string
```
--
**godocdown** http://github.com/robertkrimen/godocdown

View File

@ -1,47 +0,0 @@
/*
Package registry is an expirmental package to facillitate altering the otto runtime via import.
This interface can change at any time.
*/
package registry
var registry []*Entry = make([]*Entry, 0)
type Entry struct {
active bool
source func() string
}
func newEntry(source func() string) *Entry {
return &Entry{
active: true,
source: source,
}
}
func (self *Entry) Enable() {
self.active = true
}
func (self *Entry) Disable() {
self.active = false
}
func (self Entry) Source() string {
return self.source()
}
func Apply(callback func(Entry)) {
for _, entry := range registry {
if !entry.active {
continue
}
callback(*entry)
}
}
func Register(source func() string) *Entry {
entry := newEntry(source)
registry = append(registry, entry)
return entry
}

View File

@ -1,2 +0,0 @@
token_const.go: tokenfmt
./$^ | gofmt > $@

View File

@ -1,171 +0,0 @@
# token
--
import "github.com/robertkrimen/otto/token"
Package token defines constants representing the lexical tokens of JavaScript
(ECMA5).
## Usage
```go
const (
ILLEGAL
EOF
COMMENT
KEYWORD
STRING
BOOLEAN
NULL
NUMBER
IDENTIFIER
PLUS // +
MINUS // -
MULTIPLY // *
SLASH // /
REMAINDER // %
AND // &
OR // |
EXCLUSIVE_OR // ^
SHIFT_LEFT // <<
SHIFT_RIGHT // >>
UNSIGNED_SHIFT_RIGHT // >>>
AND_NOT // &^
ADD_ASSIGN // +=
SUBTRACT_ASSIGN // -=
MULTIPLY_ASSIGN // *=
QUOTIENT_ASSIGN // /=
REMAINDER_ASSIGN // %=
AND_ASSIGN // &=
OR_ASSIGN // |=
EXCLUSIVE_OR_ASSIGN // ^=
SHIFT_LEFT_ASSIGN // <<=
SHIFT_RIGHT_ASSIGN // >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
AND_NOT_ASSIGN // &^=
LOGICAL_AND // &&
LOGICAL_OR // ||
INCREMENT // ++
DECREMENT // --
EQUAL // ==
STRICT_EQUAL // ===
LESS // <
GREATER // >
ASSIGN // =
NOT // !
BITWISE_NOT // ~
NOT_EQUAL // !=
STRICT_NOT_EQUAL // !==
LESS_OR_EQUAL // <=
GREATER_OR_EQUAL // <=
LEFT_PARENTHESIS // (
LEFT_BRACKET // [
LEFT_BRACE // {
COMMA // ,
PERIOD // .
RIGHT_PARENTHESIS // )
RIGHT_BRACKET // ]
RIGHT_BRACE // }
SEMICOLON // ;
COLON // :
QUESTION_MARK // ?
IF
IN
DO
VAR
FOR
NEW
TRY
THIS
ELSE
CASE
VOID
WITH
WHILE
BREAK
CATCH
THROW
RETURN
TYPEOF
DELETE
SWITCH
DEFAULT
FINALLY
FUNCTION
CONTINUE
DEBUGGER
INSTANCEOF
)
```
#### type Token
```go
type Token int
```
Token is the set of lexical tokens in JavaScript (ECMA5).
#### func IsKeyword
```go
func IsKeyword(literal string) (Token, bool)
```
IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token if
the literal is a future keyword (const, let, class, super, ...), or 0 if the
literal is not a keyword.
If the literal is a keyword, IsKeyword returns a second value indicating if the
literal is considered a future keyword in strict-mode only.
7.6.1.2 Future Reserved Words:
const
class
enum
export
extends
import
super
7.6.1.2 Future Reserved Words (strict):
implements
interface
let
package
private
protected
public
static
#### func (Token) String
```go
func (tkn Token) String() string
```
String returns the string corresponding to the token. For operators, delimiters,
and keywords the string is the actual token string (e.g., for the token PLUS,
the String() is "+"). For all other tokens the string corresponds to the token
name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
--
**godocdown** http://github.com/robertkrimen/godocdown

View File

@ -1,116 +0,0 @@
// Package token defines constants representing the lexical tokens of JavaScript (ECMA5).
package token
import (
"strconv"
)
// Token is the set of lexical tokens in JavaScript (ECMA5).
type Token int
// String returns the string corresponding to the token.
// For operators, delimiters, and keywords the string is the actual
// token string (e.g., for the token PLUS, the String() is
// "+"). For all other tokens the string corresponds to the token
// name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
//
func (tkn Token) String() string {
if 0 == tkn {
return "UNKNOWN"
}
if tkn < Token(len(token2string)) {
return token2string[tkn]
}
return "token(" + strconv.Itoa(int(tkn)) + ")"
}
// This is not used for anything
func (tkn Token) precedence(in bool) int {
switch tkn {
case LOGICAL_OR:
return 1
case LOGICAL_AND:
return 2
case OR, OR_ASSIGN:
return 3
case EXCLUSIVE_OR:
return 4
case AND, AND_ASSIGN, AND_NOT, AND_NOT_ASSIGN:
return 5
case EQUAL,
NOT_EQUAL,
STRICT_EQUAL,
STRICT_NOT_EQUAL:
return 6
case LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL, INSTANCEOF:
return 7
case IN:
if in {
return 7
}
return 0
case SHIFT_LEFT, SHIFT_RIGHT, UNSIGNED_SHIFT_RIGHT:
fallthrough
case SHIFT_LEFT_ASSIGN, SHIFT_RIGHT_ASSIGN, UNSIGNED_SHIFT_RIGHT_ASSIGN:
return 8
case PLUS, MINUS, ADD_ASSIGN, SUBTRACT_ASSIGN:
return 9
case MULTIPLY, SLASH, REMAINDER, MULTIPLY_ASSIGN, QUOTIENT_ASSIGN, REMAINDER_ASSIGN:
return 11
}
return 0
}
type _keyword struct {
token Token
futureKeyword bool
strict bool
}
// IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token
// if the literal is a future keyword (const, let, class, super, ...), or 0 if the literal is not a keyword.
//
// If the literal is a keyword, IsKeyword returns a second value indicating if the literal
// is considered a future keyword in strict-mode only.
//
// 7.6.1.2 Future Reserved Words:
//
// const
// class
// enum
// export
// extends
// import
// super
//
// 7.6.1.2 Future Reserved Words (strict):
//
// implements
// interface
// let
// package
// private
// protected
// public
// static
//
func IsKeyword(literal string) (Token, bool) {
if keyword, exists := keywordTable[literal]; exists {
if keyword.futureKeyword {
return KEYWORD, keyword.strict
}
return keyword.token, false
}
return 0, false
}

View File

@ -1,349 +0,0 @@
package token
const (
_ Token = iota
ILLEGAL
EOF
COMMENT
KEYWORD
STRING
BOOLEAN
NULL
NUMBER
IDENTIFIER
PLUS // +
MINUS // -
MULTIPLY // *
SLASH // /
REMAINDER // %
AND // &
OR // |
EXCLUSIVE_OR // ^
SHIFT_LEFT // <<
SHIFT_RIGHT // >>
UNSIGNED_SHIFT_RIGHT // >>>
AND_NOT // &^
ADD_ASSIGN // +=
SUBTRACT_ASSIGN // -=
MULTIPLY_ASSIGN // *=
QUOTIENT_ASSIGN // /=
REMAINDER_ASSIGN // %=
AND_ASSIGN // &=
OR_ASSIGN // |=
EXCLUSIVE_OR_ASSIGN // ^=
SHIFT_LEFT_ASSIGN // <<=
SHIFT_RIGHT_ASSIGN // >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
AND_NOT_ASSIGN // &^=
LOGICAL_AND // &&
LOGICAL_OR // ||
INCREMENT // ++
DECREMENT // --
EQUAL // ==
STRICT_EQUAL // ===
LESS // <
GREATER // >
ASSIGN // =
NOT // !
BITWISE_NOT // ~
NOT_EQUAL // !=
STRICT_NOT_EQUAL // !==
LESS_OR_EQUAL // <=
GREATER_OR_EQUAL // <=
LEFT_PARENTHESIS // (
LEFT_BRACKET // [
LEFT_BRACE // {
COMMA // ,
PERIOD // .
RIGHT_PARENTHESIS // )
RIGHT_BRACKET // ]
RIGHT_BRACE // }
SEMICOLON // ;
COLON // :
QUESTION_MARK // ?
firstKeyword
IF
IN
DO
VAR
FOR
NEW
TRY
THIS
ELSE
CASE
VOID
WITH
WHILE
BREAK
CATCH
THROW
RETURN
TYPEOF
DELETE
SWITCH
DEFAULT
FINALLY
FUNCTION
CONTINUE
DEBUGGER
INSTANCEOF
lastKeyword
)
var token2string = [...]string{
ILLEGAL: "ILLEGAL",
EOF: "EOF",
COMMENT: "COMMENT",
KEYWORD: "KEYWORD",
STRING: "STRING",
BOOLEAN: "BOOLEAN",
NULL: "NULL",
NUMBER: "NUMBER",
IDENTIFIER: "IDENTIFIER",
PLUS: "+",
MINUS: "-",
MULTIPLY: "*",
SLASH: "/",
REMAINDER: "%",
AND: "&",
OR: "|",
EXCLUSIVE_OR: "^",
SHIFT_LEFT: "<<",
SHIFT_RIGHT: ">>",
UNSIGNED_SHIFT_RIGHT: ">>>",
AND_NOT: "&^",
ADD_ASSIGN: "+=",
SUBTRACT_ASSIGN: "-=",
MULTIPLY_ASSIGN: "*=",
QUOTIENT_ASSIGN: "/=",
REMAINDER_ASSIGN: "%=",
AND_ASSIGN: "&=",
OR_ASSIGN: "|=",
EXCLUSIVE_OR_ASSIGN: "^=",
SHIFT_LEFT_ASSIGN: "<<=",
SHIFT_RIGHT_ASSIGN: ">>=",
UNSIGNED_SHIFT_RIGHT_ASSIGN: ">>>=",
AND_NOT_ASSIGN: "&^=",
LOGICAL_AND: "&&",
LOGICAL_OR: "||",
INCREMENT: "++",
DECREMENT: "--",
EQUAL: "==",
STRICT_EQUAL: "===",
LESS: "<",
GREATER: ">",
ASSIGN: "=",
NOT: "!",
BITWISE_NOT: "~",
NOT_EQUAL: "!=",
STRICT_NOT_EQUAL: "!==",
LESS_OR_EQUAL: "<=",
GREATER_OR_EQUAL: "<=",
LEFT_PARENTHESIS: "(",
LEFT_BRACKET: "[",
LEFT_BRACE: "{",
COMMA: ",",
PERIOD: ".",
RIGHT_PARENTHESIS: ")",
RIGHT_BRACKET: "]",
RIGHT_BRACE: "}",
SEMICOLON: ";",
COLON: ":",
QUESTION_MARK: "?",
IF: "if",
IN: "in",
DO: "do",
VAR: "var",
FOR: "for",
NEW: "new",
TRY: "try",
THIS: "this",
ELSE: "else",
CASE: "case",
VOID: "void",
WITH: "with",
WHILE: "while",
BREAK: "break",
CATCH: "catch",
THROW: "throw",
RETURN: "return",
TYPEOF: "typeof",
DELETE: "delete",
SWITCH: "switch",
DEFAULT: "default",
FINALLY: "finally",
FUNCTION: "function",
CONTINUE: "continue",
DEBUGGER: "debugger",
INSTANCEOF: "instanceof",
}
var keywordTable = map[string]_keyword{
"if": _keyword{
token: IF,
},
"in": _keyword{
token: IN,
},
"do": _keyword{
token: DO,
},
"var": _keyword{
token: VAR,
},
"for": _keyword{
token: FOR,
},
"new": _keyword{
token: NEW,
},
"try": _keyword{
token: TRY,
},
"this": _keyword{
token: THIS,
},
"else": _keyword{
token: ELSE,
},
"case": _keyword{
token: CASE,
},
"void": _keyword{
token: VOID,
},
"with": _keyword{
token: WITH,
},
"while": _keyword{
token: WHILE,
},
"break": _keyword{
token: BREAK,
},
"catch": _keyword{
token: CATCH,
},
"throw": _keyword{
token: THROW,
},
"return": _keyword{
token: RETURN,
},
"typeof": _keyword{
token: TYPEOF,
},
"delete": _keyword{
token: DELETE,
},
"switch": _keyword{
token: SWITCH,
},
"default": _keyword{
token: DEFAULT,
},
"finally": _keyword{
token: FINALLY,
},
"function": _keyword{
token: FUNCTION,
},
"continue": _keyword{
token: CONTINUE,
},
"debugger": _keyword{
token: DEBUGGER,
},
"instanceof": _keyword{
token: INSTANCEOF,
},
"const": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"class": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"enum": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"export": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"extends": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"import": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"super": _keyword{
token: KEYWORD,
futureKeyword: true,
},
"implements": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"interface": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"let": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"package": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"private": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"protected": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"public": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"static": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
}

View File

@ -1,222 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
my (%token, @order, @keywords);
{
my $keywords;
my @const;
push @const, <<_END_;
package token
const(
_ Token = iota
_END_
for (split m/\n/, <<_END_) {
ILLEGAL
EOF
COMMENT
KEYWORD
STRING
BOOLEAN
NULL
NUMBER
IDENTIFIER
PLUS +
MINUS -
MULTIPLY *
SLASH /
REMAINDER %
AND &
OR |
EXCLUSIVE_OR ^
SHIFT_LEFT <<
SHIFT_RIGHT >>
UNSIGNED_SHIFT_RIGHT >>>
AND_NOT &^
ADD_ASSIGN +=
SUBTRACT_ASSIGN -=
MULTIPLY_ASSIGN *=
QUOTIENT_ASSIGN /=
REMAINDER_ASSIGN %=
AND_ASSIGN &=
OR_ASSIGN |=
EXCLUSIVE_OR_ASSIGN ^=
SHIFT_LEFT_ASSIGN <<=
SHIFT_RIGHT_ASSIGN >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN >>>=
AND_NOT_ASSIGN &^=
LOGICAL_AND &&
LOGICAL_OR ||
INCREMENT ++
DECREMENT --
EQUAL ==
STRICT_EQUAL ===
LESS <
GREATER >
ASSIGN =
NOT !
BITWISE_NOT ~
NOT_EQUAL !=
STRICT_NOT_EQUAL !==
LESS_OR_EQUAL <=
GREATER_OR_EQUAL <=
LEFT_PARENTHESIS (
LEFT_BRACKET [
LEFT_BRACE {
COMMA ,
PERIOD .
RIGHT_PARENTHESIS )
RIGHT_BRACKET ]
RIGHT_BRACE }
SEMICOLON ;
COLON :
QUESTION_MARK ?
firstKeyword
IF
IN
DO
VAR
FOR
NEW
TRY
THIS
ELSE
CASE
VOID
WITH
WHILE
BREAK
CATCH
THROW
RETURN
TYPEOF
DELETE
SWITCH
DEFAULT
FINALLY
FUNCTION
CONTINUE
DEBUGGER
INSTANCEOF
lastKeyword
_END_
chomp;
next if m/^\s*#/;
my ($name, $symbol) = m/(\w+)\s*(\S+)?/;
if (defined $symbol) {
push @order, $name;
push @const, "$name // $symbol";
$token{$name} = $symbol;
} elsif (defined $name) {
$keywords ||= $name eq 'firstKeyword';
push @const, $name;
#$const[-1] .= " Token = iota" if 2 == @const;
if ($name =~ m/^([A-Z]+)/) {
push @keywords, $name if $keywords;
push @order, $name;
if ($token{SEMICOLON}) {
$token{$name} = lc $1;
} else {
$token{$name} = $name;
}
}
} else {
push @const, "";
}
}
push @const, ")";
print join "\n", @const, "";
}
{
print <<_END_;
var token2string = [...]string{
_END_
for my $name (@order) {
print "$name: \"$token{$name}\",\n";
}
print <<_END_;
}
_END_
print <<_END_;
var keywordTable = map[string]_keyword{
_END_
for my $name (@keywords) {
print <<_END_
"@{[ lc $name ]}": _keyword{
token: $name,
},
_END_
}
for my $name (qw/
const
class
enum
export
extends
import
super
/) {
print <<_END_
"$name": _keyword{
token: KEYWORD,
futureKeyword: true,
},
_END_
}
for my $name (qw/
implements
interface
let
package
private
protected
public
static
/) {
print <<_END_
"$name": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
_END_
}
print <<_END_;
}
_END_
}

View File

@ -1,9 +0,0 @@
package otto
func (runtime *_runtime) newErrorObject(message Value) *_object {
self := runtime.newClassObject("Error")
if message.IsDefined() {
self.defineProperty("message", toValue_string(toString(message)), 0111, false)
}
return self
}

View File

@ -1,276 +0,0 @@
package otto
import (
"fmt"
)
type _functionObject struct {
call _callFunction
construct _constructFunction
}
func (self _functionObject) source(object *_object) string {
return self.call.Source(object)
}
func (self0 _functionObject) clone(clone *_clone) _functionObject {
return _functionObject{
clone.callFunction(self0.call),
self0.construct,
}
}
func (runtime *_runtime) newNativeFunctionObject(name string, native _nativeFunction, length int) *_object {
self := runtime.newClassObject("Function")
self.value = _functionObject{
call: newNativeCallFunction(native),
construct: defaultConstructFunction,
}
self.defineProperty("length", toValue_int(length), 0000, false)
return self
}
func (runtime *_runtime) newBoundFunctionObject(target *_object, this Value, argumentList []Value) *_object {
self := runtime.newClassObject("Function")
self.value = _functionObject{
call: newBoundCallFunction(target, this, argumentList),
construct: newBoundConstructFunction(target),
}
length := int(toInt32(target.get("length")))
length -= len(argumentList)
if length < 0 {
length = 0
}
self.defineProperty("length", toValue_int(length), 0000, false)
self.defineProperty("caller", UndefinedValue(), 0000, false) // TODO Should throw a TypeError
self.defineProperty("arguments", UndefinedValue(), 0000, false) // TODO Should throw a TypeError
return self
}
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
}
func (self *_object) functionValue() _functionObject {
value, _ := self.value.(_functionObject)
return value
}
func (self *_object) Call(this Value, argumentList ...interface{}) Value {
if self.functionValue().call == nil {
panic(newTypeError("%v is not a function", toValue_object(self)))
}
return self.runtime.Call(self, this, self.runtime.toValueArray(argumentList...), false)
// ... -> runtime -> self.Function.Call.Dispatch -> ...
}
func (self *_object) Construct(this Value, argumentList ...interface{}) Value {
function := self.functionValue()
if function.call == nil {
panic(newTypeError("%v is not a function", toValue_object(self)))
}
if function.construct == nil {
panic(newTypeError("%v is not a constructor", toValue_object(self)))
}
return function.construct(self, this, self.runtime.toValueArray(argumentList...))
}
func defaultConstructFunction(self *_object, this Value, argumentList []Value) Value {
newObject := self.runtime.newObject()
newObject.class = "Object"
prototypeValue := self.get("prototype")
if !prototypeValue.IsObject() {
prototypeValue = toValue_object(self.runtime.Global.ObjectPrototype)
}
newObject.prototype = prototypeValue._object()
newObjectValue := toValue_object(newObject)
result := self.Call(newObjectValue, argumentList)
if result.IsObject() {
return result
}
return newObjectValue
}
func (self *_object) callGet(this Value) Value {
return self.runtime.Call(self, this, []Value(nil), false)
}
func (self *_object) callSet(this Value, value Value) {
self.runtime.Call(self, this, []Value{value}, false)
}
// 15.3.5.3
func (self *_object) HasInstance(of Value) bool {
if self.functionValue().call == nil {
// We should not have a HasInstance method
panic(newTypeError())
}
if !of.IsObject() {
return false
}
prototype := self.get("prototype")
if !prototype.IsObject() {
panic(newTypeError())
}
prototypeObject := prototype._object()
value := of._object().prototype
for value != nil {
if value == prototypeObject {
return true
}
value = value.prototype
}
return false
}
type _nativeFunction func(FunctionCall) Value
// _constructFunction
type _constructFunction func(*_object, Value, []Value) Value
// _callFunction
type _callFunction interface {
Dispatch(*_object, *_functionEnvironment, *_runtime, Value, []Value, bool) Value
Source(*_object) string
ScopeEnvironment() _environment
clone(clone *_clone) _callFunction
}
// _nativeCallFunction
type _nativeCallFunction struct {
name string
function _nativeFunction
}
func newNativeCallFunction(native _nativeFunction) _nativeCallFunction {
return _nativeCallFunction{"", native}
}
func (self _nativeCallFunction) Dispatch(_ *_object, _ *_functionEnvironment, runtime *_runtime, this Value, argumentList []Value, evalHint bool) Value {
return self.function(FunctionCall{
runtime: runtime,
evalHint: evalHint,
This: this,
ArgumentList: argumentList,
Otto: runtime.Otto,
})
}
func (self _nativeCallFunction) ScopeEnvironment() _environment {
return nil
}
func (self _nativeCallFunction) Source(*_object) string {
return fmt.Sprintf("function %s() { [native code] }", self.name)
}
func (self0 _nativeCallFunction) clone(clone *_clone) _callFunction {
return self0
}
// _boundCallFunction
type _boundCallFunction struct {
target *_object
this Value
argumentList []Value
}
func newBoundCallFunction(target *_object, this Value, argumentList []Value) *_boundCallFunction {
self := &_boundCallFunction{
target: target,
this: this,
argumentList: argumentList,
}
return self
}
func (self _boundCallFunction) Dispatch(_ *_object, _ *_functionEnvironment, runtime *_runtime, this Value, argumentList []Value, _ bool) Value {
argumentList = append(self.argumentList, argumentList...)
return runtime.Call(self.target, self.this, argumentList, false)
}
func (self _boundCallFunction) ScopeEnvironment() _environment {
return nil
}
func (self _boundCallFunction) Source(*_object) string {
return ""
}
func (self0 _boundCallFunction) clone(clone *_clone) _callFunction {
return _boundCallFunction{
target: clone.object(self0.target),
this: clone.value(self0.this),
argumentList: clone.valueArray(self0.argumentList),
}
}
func newBoundConstructFunction(target *_object) _constructFunction {
// This is not exactly as described in 15.3.4.5.2, we let [[Call]] supply the
// bound arguments, etc.
return func(self *_object, this Value, argumentList []Value) Value {
switch value := target.value.(type) {
case _functionObject:
return value.construct(self, this, argumentList)
}
panic(newTypeError())
}
}
// FunctionCall{}
// FunctionCall is an encapsulation of a JavaScript function call.
type FunctionCall struct {
runtime *_runtime
_thisObject *_object
evalHint bool
This Value
ArgumentList []Value
Otto *Otto
}
// Argument will return the value of the argument at the given index.
//
// If no such argument exists, undefined is returned.
func (self FunctionCall) Argument(index int) Value {
return valueOfArrayIndex(self.ArgumentList, index)
}
func (self FunctionCall) getArgument(index int) (Value, bool) {
return getValueOfArrayIndex(self.ArgumentList, index)
}
func (self FunctionCall) slice(index int) []Value {
if index < len(self.ArgumentList) {
return self.ArgumentList[index:]
}
return []Value{}
}
func (self *FunctionCall) thisObject() *_object {
if self._thisObject == nil {
this := self.runtime.GetValue(self.This) // FIXME Is this right?
self._thisObject = self.runtime.toObject(this)
}
return self._thisObject
}
func (self *FunctionCall) thisClassObject(class string) *_object {
thisObject := self.thisObject()
if thisObject.class != class {
panic(newTypeError())
}
return self._thisObject
}
func (self FunctionCall) toObject(value Value) *_object {
return self.runtime.toObject(value)
}

View File

@ -1,157 +0,0 @@
package otto
import (
"github.com/robertkrimen/otto/ast"
)
type _reference interface {
GetBase() interface{} // GetBase
GetName() string // GetReferencedName
IsStrict() bool // IsStrictReference
IsUnresolvable() bool // IsUnresolvableReference
IsPropertyReference() bool // IsPropertyReference
GetValue() Value // GetValue
PutValue(Value) bool // PutValue
Delete() bool
}
// Reference
type _referenceDefault struct {
name string
strict bool
}
func (self _referenceDefault) GetName() string {
return self.name
}
func (self _referenceDefault) IsStrict() bool {
return self.strict
}
// PropertyReference
type _propertyReference struct {
_referenceDefault
Base *_object
}
func newPropertyReference(base *_object, name string, strict bool) *_propertyReference {
return &_propertyReference{
Base: base,
_referenceDefault: _referenceDefault{
name: name,
strict: strict,
},
}
}
func (self *_propertyReference) GetBase() interface{} {
return self.Base
}
func (self *_propertyReference) IsUnresolvable() bool {
return self.Base == nil
}
func (self *_propertyReference) IsPropertyReference() bool {
return true
}
func (self *_propertyReference) GetValue() Value {
if self.Base == nil {
panic(newReferenceError("notDefined", self.name))
}
return self.Base.get(self.name)
}
func (self *_propertyReference) PutValue(value Value) bool {
if self.Base == nil {
return false
}
self.Base.put(self.name, value, self.IsStrict())
return true
}
func (self *_propertyReference) Delete() bool {
if self.Base == nil {
// TODO Throw an error if strict
return true
}
return self.Base.delete(self.name, self.IsStrict())
}
// ArgumentReference
func newArgumentReference(base *_object, name string, strict bool) *_propertyReference {
if base == nil {
panic(hereBeDragons())
}
return newPropertyReference(base, name, strict)
}
type _environmentReference struct {
_referenceDefault
Base _environment
node ast.Node
}
func newEnvironmentReference(base _environment, name string, strict bool, node ast.Node) *_environmentReference {
return &_environmentReference{
Base: base,
_referenceDefault: _referenceDefault{
name: name,
strict: strict,
},
node: node,
}
}
func (self *_environmentReference) GetBase() interface{} {
return self.Base
}
func (self *_environmentReference) IsUnresolvable() bool {
return self.Base == nil // The base (an environment) will never be nil
}
func (self *_environmentReference) IsPropertyReference() bool {
return false
}
func (self *_environmentReference) GetValue() Value {
if self.Base == nil {
// This should never be reached, but just in case
}
return self.Base.GetValue(self.name, self.IsStrict())
}
func (self *_environmentReference) PutValue(value Value) bool {
if self.Base == nil {
// This should never be reached, but just in case
return false
}
self.Base.SetValue(self.name, value, self.IsStrict())
return true
}
func (self *_environmentReference) Delete() bool {
if self.Base == nil {
// This should never be reached, but just in case
return false
}
return self.Base.DeleteBinding(self.name)
}
// getIdentifierReference
func getIdentifierReference(environment _environment, name string, strict bool) _reference {
if environment == nil {
return newPropertyReference(nil, name, strict)
}
if environment.HasBinding(name) {
return environment.newReference(name, strict)
}
return getIdentifierReference(environment.Outer(), name, strict)
}

View File

@ -6,8 +6,8 @@ TESTS := \
~ ~
TEST := -v --run TEST := -v --run
TEST := -v --run Test\($(subst $(eval) ,\|,$(TESTS))\)
TEST := -v TEST := -v
TEST := -v --run Test\($(subst $(eval) ,\|,$(TESTS))\)
TEST := . TEST := .
test: parser inline.go test: parser inline.go

View File

@ -60,7 +60,7 @@ Set a Go function
vm.Set("sayHello", func(call otto.FunctionCall) otto.Value { vm.Set("sayHello", func(call otto.FunctionCall) otto.Value {
fmt.Printf("Hello, %s.\n", call.Argument(0).String()) fmt.Printf("Hello, %s.\n", call.Argument(0).String())
return otto.UndefinedValue() return otto.Value{}
}) })
Set a Go function that returns something useful Set a Go function that returns something useful
@ -139,7 +139,6 @@ For more information: http://github.com/robertkrimen/otto/tree/master/underscore
The following are some limitations with otto: The following are some limitations with otto:
* "use strict" will parse, but does nothing. * "use strict" will parse, but does nothing.
* Error reporting needs to be improved.
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification. * The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
@ -205,16 +204,18 @@ the interrupt channel to do this:
} }
fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration) fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration)
}() }()
vm := otto.New() vm := otto.New()
vm.Interrupt = make(chan func()) vm.Interrupt = make(chan func(), 1) // The buffer prevents blocking
go func() { go func() {
time.Sleep(2 * time.Second) // Stop after two seconds time.Sleep(2 * time.Second) // Stop after two seconds
vm.Interrupt <- func() { vm.Interrupt <- func() {
panic(halt) panic(halt)
} }
}() }()
vm.Run(unsafe) // Here be dragons (risky code) vm.Run(unsafe) // Here be dragons (risky code)
vm.Interrupt = nil
} }
Where is setTimeout/setInterval? Where is setTimeout/setInterval?
@ -242,6 +243,36 @@ Here is some more discussion of the issue:
var ErrVersion = errors.New("version mismatch") var ErrVersion = errors.New("version mismatch")
``` ```
#### type Error
```go
type Error struct {
}
```
An Error represents a runtime error, e.g. a TypeError, a ReferenceError, etc.
#### func (Error) Error
```go
func (err Error) Error() string
```
Error returns a description of the error
TypeError: 'def' is not a function
#### func (Error) String
```go
func (err Error) String() string
```
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/
#### type FunctionCall #### type FunctionCall
```go ```go
@ -416,16 +447,16 @@ error if there was a problem during compilation.
#### func (*Otto) Copy #### func (*Otto) Copy
```go ```go
func (self *Otto) Copy() *Otto func (in *Otto) Copy() *Otto
``` ```
Copy will create a copy/clone of the runtime. Copy will create a copy/clone of the runtime.
Copy is useful for saving some processing time when creating many similar Copy is useful for saving some time when creating many similar runtimes.
runtimes.
This implementation is alpha-ish, and works by introspecting every part of the This method works by walking the original runtime and cloning each object,
runtime and reallocating and then relinking everything back together. Please scope, stash, etc. into a new runtime.
report if you notice any inadvertent sharing of data between copies.
Be on the lookout for memory leaks or inadvertent sharing of resources.
#### func (Otto) Get #### func (Otto) Get
@ -562,12 +593,10 @@ NullValue will return a Value representing null.
func ToValue(value interface{}) (Value, error) func ToValue(value interface{}) (Value, error)
``` ```
ToValue will convert an interface{} value to a value digestible by ToValue will convert an interface{} value to a value digestible by
otto/JavaScript This function will not work for advanced types (struct, map, otto/JavaScript
slice/array, etc.) and you probably should not use it.
ToValue may be deprecated and removed in the near future. This function will not work for advanced types (struct, map, slice/array, etc.)
and you should use Otto.ToValue instead.
Try Otto.ToValue for a replacement.
#### func TrueValue #### func TrueValue
@ -630,15 +659,13 @@ func (self Value) Export() (interface{}, error)
Export will attempt to convert the value to a Go representation and return it Export will attempt to convert the value to a Go representation and return it
via an interface{} kind. via an interface{} kind.
WARNING: The interface function will be changing soon to: Export returns an error, but it will always be nil. It is present for backwards
compatibility.
Export() interface{} If a reasonable conversion is not possible, then the original value is returned.
If a reasonable conversion is not possible, then the original result is undefined -> nil (FIXME?: Should be Value{})
returned. null -> nil
undefined -> otto.Value (UndefinedValue())
null -> interface{}(nil)
boolean -> bool boolean -> bool
number -> A number type (int, float32, uint64, ...) number -> A number type (int, float32, uint64, ...)
string -> string string -> string

View File

@ -55,7 +55,7 @@ func Test_issue13(t *testing.T) {
t.Error(err) t.Error(err)
t.FailNow() t.FailNow()
} }
is(result.toString(), "Xyzzy,42,def,ghi") is(result.string(), "Xyzzy,42,def,ghi")
anything := struct { anything := struct {
Abc interface{} Abc interface{}
@ -231,12 +231,12 @@ func Test_issue24(t *testing.T) {
} }
{ {
vm.Set("abc", testStruct{Abc: true, Ghi: "Nothing happens."}) vm.Set("abc", _abcStruct{Abc: true, Ghi: "Nothing happens."})
value, err := vm.Get("abc") value, err := vm.Get("abc")
is(err, nil) is(err, nil)
export, _ := value.Export() export, _ := value.Export()
{ {
value, valid := export.(testStruct) value, valid := export.(_abcStruct)
is(valid, true) is(valid, true)
is(value.Abc, true) is(value.Abc, true)
@ -245,12 +245,12 @@ func Test_issue24(t *testing.T) {
} }
{ {
vm.Set("abc", &testStruct{Abc: true, Ghi: "Nothing happens."}) vm.Set("abc", &_abcStruct{Abc: true, Ghi: "Nothing happens."})
value, err := vm.Get("abc") value, err := vm.Get("abc")
is(err, nil) is(err, nil)
export, _ := value.Export() export, _ := value.Export()
{ {
value, valid := export.(*testStruct) value, valid := export.(*_abcStruct)
is(valid, true) is(valid, true)
is(value.Abc, true) is(value.Abc, true)
@ -313,10 +313,22 @@ func Test_issue64(t *testing.T) {
}) })
} }
func Test_issue73(t *testing.T) {
tt(t, func() {
test, vm := test()
vm.Set("abc", [4]int{3, 2, 1, 0})
test(`
var def = [ 0, 1, 2, 3 ];
JSON.stringify(def) + JSON.stringify(abc);
`, "[0,1,2,3][3,2,1,0]")
})
}
func Test_7_3_1(t *testing.T) { func Test_7_3_1(t *testing.T) {
tt(t, func() { tt(t, func() {
test(` test(`
eval("var test7_3_1\u2028abc = 66;"); eval("var test7_3_1\u2028abc = 66;");
[ abc, typeof test7_3_1 ]; [ abc, typeof test7_3_1 ];
`, "66,undefined") `, "66,undefined")
@ -430,13 +442,13 @@ def"
test(` test(`
var abc = 0; var abc = 0;
do { do {
if(typeof(def) === "function"){ if(typeof(def) === "function"){
abc = -1; abc = -1;
break; break;
} else { } else {
abc = 1; abc = 1;
break; break;
} }
} while(function def(){}); } while(function def(){});
abc; abc;
`, 1) `, 1)
@ -502,3 +514,104 @@ def"
`, "1,0,\t abc def,\t abc ") `, "1,0,\t abc def,\t abc ")
}) })
} }
func Test_issue79(t *testing.T) {
tt(t, func() {
test, vm := test()
vm.Set("abc", []_abcStruct{
{
Ghi: "一",
Def: 1,
},
{
Def: 3,
Ghi: "三",
},
{
Def: 2,
Ghi: "二",
},
{
Def: 4,
Ghi: "四",
},
})
test(`
abc.sort(function(a,b){ return b.Def-a.Def });
def = [];
for (i = 0; i < abc.length; i++) {
def.push(abc[i].String())
}
def;
`, "四,三,二,一")
})
}
func Test_issue80(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`
JSON.stringify([
1401868959,
14018689591,
140186895901,
1401868959001,
14018689590001,
140186895900001,
1401868959000001,
1401868959000001.5,
14018689590000001,
140186895900000001,
1401868959000000001,
14018689590000000001,
140186895900000000001,
140186895900000000001.5
]);
`, "[1401868959,14018689591,140186895901,1401868959001,14018689590001,140186895900001,1401868959000001,1.4018689590000015e+15,14018689590000001,140186895900000001,1401868959000000001,1.401868959e+19,1.401868959e+20,1.401868959e+20]")
})
}
func Test_issue87(t *testing.T) {
tt(t, func() {
test, vm := test()
test(`
var def = 0;
abc: {
for (;;) {
def = !1;
break abc;
}
def = !0;
}
def;
`, false)
_, err := vm.Run(`
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new r.init(c,a)}}),l=f.enc={},k=l.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b,
2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}},
u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);c.sigBytes-=b}return new r.init(g,b)},clone:function(){var a=m.clone.call(this);
a._data=this._data.clone();return a},_minBufferSize:0});g.Hasher=u.extend({cfg:m.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){u.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new t.HMAC.init(a,
d)).finalize(c)}}});var t=f.algo={};return f}(Math);
(function(h){for(var s=CryptoJS,f=s.lib,g=f.WordArray,q=f.Hasher,f=s.algo,m=[],r=[],l=function(a){return 4294967296*(a-(a|0))|0},k=2,n=0;64>n;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]=
c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes;
d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math);
(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j<h;j++)k[j]^=1549556828,n[j]^=909522486;r.sigBytes=l.sigBytes=m;this.reset()},reset:function(){var f=this._hasher;f.reset();f.update(this._iKey)},update:function(f){this._hasher.update(f);return this},finalize:function(f){var g=
this._hasher;f=g.finalize(f);g.reset();return g.finalize(this._oKey.clone().concat(f))}})})();
`)
is(err, nil)
test(`CryptoJS.HmacSHA256("Message", "secret");`, "aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597")
})
}

View File

@ -2,7 +2,6 @@ package otto
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"math" "math"
"net/url" "net/url"
"regexp" "regexp"
@ -19,25 +18,26 @@ func builtinGlobal_eval(call FunctionCall) Value {
return src return src
} }
runtime := call.runtime runtime := call.runtime
program := runtime.cmpl_parseOrThrow(toString(src)) program := runtime.cmpl_parseOrThrow(src.string())
if call.evalHint { if !call.eval {
runtime.EnterEvalExecutionContext(call) // Not a direct call to eval, so we enter the global ExecutionContext
defer runtime.LeaveExecutionContext() runtime.enterGlobalScope()
defer runtime.leaveScope()
} }
returnValue := runtime.cmpl_evaluate_nodeProgram(program) returnValue := runtime.cmpl_evaluate_nodeProgram(program, true)
if returnValue.isEmpty() { if returnValue.isEmpty() {
return UndefinedValue() return Value{}
} }
return returnValue return returnValue
} }
func builtinGlobal_isNaN(call FunctionCall) Value { func builtinGlobal_isNaN(call FunctionCall) Value {
value := toFloat(call.Argument(0)) value := call.Argument(0).float64()
return toValue_bool(math.IsNaN(value)) return toValue_bool(math.IsNaN(value))
} }
func builtinGlobal_isFinite(call FunctionCall) Value { func builtinGlobal_isFinite(call FunctionCall) Value {
value := toFloat(call.Argument(0)) value := call.Argument(0).float64()
return toValue_bool(!math.IsNaN(value) && !math.IsInf(value, 0)) return toValue_bool(!math.IsNaN(value) && !math.IsInf(value, 0))
} }
@ -70,7 +70,7 @@ func digitValue(chr rune) int {
} }
func builtinGlobal_parseInt(call FunctionCall) Value { func builtinGlobal_parseInt(call FunctionCall) Value {
input := strings.TrimSpace(toString(call.Argument(0))) input := strings.TrimSpace(call.Argument(0).string())
if len(input) == 0 { if len(input) == 0 {
return NaNValue() return NaNValue()
} }
@ -153,7 +153,7 @@ var parseFloat_matchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`)
func builtinGlobal_parseFloat(call FunctionCall) Value { func builtinGlobal_parseFloat(call FunctionCall) Value {
// Caveat emptor: This implementation does NOT match the specification // Caveat emptor: This implementation does NOT match the specification
input := strings.TrimSpace(toString(call.Argument(0))) input := strings.TrimSpace(call.Argument(0).string())
if parseFloat_matchBadSpecial.MatchString(input) { if parseFloat_matchBadSpecial.MatchString(input) {
return NaNValue() return NaNValue()
} }
@ -185,7 +185,7 @@ func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
case []uint16: case []uint16:
input = vl input = vl
default: default:
input = utf16.Encode([]rune(toString(value))) input = utf16.Encode([]rune(value.string()))
} }
if len(input) == 0 { if len(input) == 0 {
return toValue_string("") return toValue_string("")
@ -197,18 +197,17 @@ func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
value := input[index] value := input[index]
decode := utf16.Decode(input[index : index+1]) decode := utf16.Decode(input[index : index+1])
if value >= 0xDC00 && value <= 0xDFFF { if value >= 0xDC00 && value <= 0xDFFF {
panic(newURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
if value >= 0xD800 && value <= 0xDBFF { if value >= 0xD800 && value <= 0xDBFF {
index += 1 index += 1
if index >= length { if index >= length {
panic(newURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
// input = ..., value, value1, ... // input = ..., value, value1, ...
value = value
value1 := input[index] value1 := input[index]
if value1 < 0xDC00 || value1 > 0xDFFF { if value1 < 0xDC00 || value1 > 0xDFFF {
panic(newURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000} decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000}
} }
@ -257,17 +256,17 @@ func _decodeURI(input string, reserve bool) (string, bool) {
} }
func builtinGlobal_decodeURI(call FunctionCall) Value { func builtinGlobal_decodeURI(call FunctionCall) Value {
output, err := _decodeURI(toString(call.Argument(0)), true) output, err := _decodeURI(call.Argument(0).string(), true)
if err { if err {
panic(newURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
return toValue_string(output) return toValue_string(output)
} }
func builtinGlobal_decodeURIComponent(call FunctionCall) Value { func builtinGlobal_decodeURIComponent(call FunctionCall) Value {
output, err := _decodeURI(toString(call.Argument(0)), false) output, err := _decodeURI(call.Argument(0).string(), false)
if err { if err {
panic(newURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
return toValue_string(output) return toValue_string(output)
} }
@ -346,48 +345,9 @@ func builtin_unescape(input string) string {
} }
func builtinGlobal_escape(call FunctionCall) Value { func builtinGlobal_escape(call FunctionCall) Value {
return toValue_string(builtin_escape(toString(call.Argument(0)))) return toValue_string(builtin_escape(call.Argument(0).string()))
} }
func builtinGlobal_unescape(call FunctionCall) Value { func builtinGlobal_unescape(call FunctionCall) Value {
return toValue_string(builtin_unescape(toString(call.Argument(0)))) return toValue_string(builtin_unescape(call.Argument(0).string()))
}
// Error
func builtinError(call FunctionCall) Value {
return toValue_object(call.runtime.newError("", call.Argument(0)))
}
func builtinNewError(self *_object, _ Value, argumentList []Value) Value {
return toValue_object(self.runtime.newError("", valueOfArrayIndex(argumentList, 0)))
}
func builtinError_toString(call FunctionCall) Value {
thisObject := call.thisObject()
if thisObject == nil {
panic(newTypeError())
}
name := "Error"
nameValue := thisObject.get("name")
if nameValue.IsDefined() {
name = toString(nameValue)
}
message := ""
messageValue := thisObject.get("message")
if messageValue.IsDefined() {
message = toString(messageValue)
}
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))
} }

View File

@ -11,7 +11,7 @@ func builtinArray(call FunctionCall) Value {
return toValue_object(builtinNewArrayNative(call.runtime, call.ArgumentList)) return toValue_object(builtinNewArrayNative(call.runtime, call.ArgumentList))
} }
func builtinNewArray(self *_object, _ Value, argumentList []Value) Value { func builtinNewArray(self *_object, argumentList []Value) Value {
return toValue_object(builtinNewArrayNative(self.runtime, argumentList)) return toValue_object(builtinNewArrayNative(self.runtime, argumentList))
} }
@ -19,7 +19,7 @@ func builtinNewArrayNative(runtime *_runtime, argumentList []Value) *_object {
if len(argumentList) == 1 { if len(argumentList) == 1 {
firstArgument := argumentList[0] firstArgument := argumentList[0]
if firstArgument.IsNumber() { if firstArgument.IsNumber() {
return runtime.newArray(arrayUint32(firstArgument)) return runtime.newArray(arrayUint32(runtime, firstArgument))
} }
} }
return runtime.newArrayOf(argumentList) return runtime.newArrayOf(argumentList)
@ -30,7 +30,7 @@ func builtinArray_toString(call FunctionCall) Value {
join := thisObject.get("join") join := thisObject.get("join")
if join.isCallable() { if join.isCallable() {
join := join._object() join := join._object()
return join.Call(call.This, call.ArgumentList) return join.call(call.This, call.ArgumentList, false, nativeFrame)
} }
return builtinObject_toString(call) return builtinObject_toString(call)
} }
@ -46,15 +46,15 @@ func builtinArray_toLocaleString(call FunctionCall) Value {
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index += 1 {
value := thisObject.get(arrayIndexToString(index)) value := thisObject.get(arrayIndexToString(index))
stringValue := "" stringValue := ""
switch value._valueType { switch value.kind {
case valueEmpty, valueUndefined, valueNull: case valueEmpty, valueUndefined, valueNull:
default: default:
object := call.runtime.toObject(value) object := call.runtime.toObject(value)
toLocaleString := object.get("toLocaleString") toLocaleString := object.get("toLocaleString")
if !toLocaleString.isCallable() { if !toLocaleString.isCallable() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
stringValue = toLocaleString.call(toValue_object(object)).toString() stringValue = toLocaleString.call(call.runtime, toValue_object(object)).string()
} }
stringList = append(stringList, stringValue) stringList = append(stringList, stringValue)
} }
@ -66,11 +66,11 @@ func builtinArray_concat(call FunctionCall) Value {
valueArray := []Value{} valueArray := []Value{}
source := append([]Value{toValue_object(thisObject)}, call.ArgumentList...) source := append([]Value{toValue_object(thisObject)}, call.ArgumentList...)
for _, item := range source { for _, item := range source {
switch item._valueType { switch item.kind {
case valueObject: case valueObject:
object := item._object() object := item._object()
if isArray(object) { if isArray(object) {
length := toInteger(object.get("length")).value length := object.get("length").number().int64
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index += 1 {
name := strconv.FormatInt(index, 10) name := strconv.FormatInt(index, 10)
if object.hasProperty(name) { if object.hasProperty(name) {
@ -94,7 +94,7 @@ func builtinArray_shift(call FunctionCall) Value {
length := int64(toUint32(thisObject.get("length"))) length := int64(toUint32(thisObject.get("length")))
if 0 == length { if 0 == length {
thisObject.put("length", toValue_int64(0), true) thisObject.put("length", toValue_int64(0), true)
return UndefinedValue() return Value{}
} }
first := thisObject.get("0") first := thisObject.get("0")
for index := int64(1); index < length; index++ { for index := int64(1); index < length; index++ {
@ -130,7 +130,7 @@ func builtinArray_pop(call FunctionCall) Value {
length := int64(toUint32(thisObject.get("length"))) length := int64(toUint32(thisObject.get("length")))
if 0 == length { if 0 == length {
thisObject.put("length", toValue_uint32(0), true) thisObject.put("length", toValue_uint32(0), true)
return UndefinedValue() return Value{}
} }
last := thisObject.get(arrayIndexToString(length - 1)) last := thisObject.get(arrayIndexToString(length - 1))
thisObject.delete(arrayIndexToString(length-1), true) thisObject.delete(arrayIndexToString(length-1), true)
@ -143,7 +143,7 @@ func builtinArray_join(call FunctionCall) Value {
{ {
argument := call.Argument(0) argument := call.Argument(0)
if argument.IsDefined() { if argument.IsDefined() {
separator = toString(argument) separator = argument.string()
} }
} }
thisObject := call.thisObject() thisObject := call.thisObject()
@ -155,10 +155,10 @@ func builtinArray_join(call FunctionCall) Value {
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index += 1 {
value := thisObject.get(arrayIndexToString(index)) value := thisObject.get(arrayIndexToString(index))
stringValue := "" stringValue := ""
switch value._valueType { switch value.kind {
case valueEmpty, valueUndefined, valueNull: case valueEmpty, valueUndefined, valueNull:
default: default:
stringValue = toString(value) stringValue = value.string()
} }
stringList = append(stringList, stringValue) stringList = append(stringList, stringValue)
} }
@ -370,8 +370,8 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
} }
if compare == nil { if compare == nil {
j.value = toString(x) j.value = x.string()
k.value = toString(y) k.value = y.string()
if j.value == k.value { if j.value == k.value {
return 0 return 0
@ -382,7 +382,7 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
return 1 return 1
} }
return int(toInt32(compare.Call(UndefinedValue(), []Value{x, y}))) return int(toInt32(compare.call(Value{}, []Value{x, y}, false, nativeFrame)))
} }
func arraySortSwap(thisObject *_object, index0, index1 uint) { func arraySortSwap(thisObject *_object, index0, index1 uint) {
@ -447,7 +447,7 @@ func builtinArray_sort(call FunctionCall) Value {
compare := compareValue._object() compare := compareValue._object()
if compareValue.IsUndefined() { if compareValue.IsUndefined() {
} else if !compareValue.isCallable() { } else if !compareValue.isCallable() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
if length > 1 { if length > 1 {
arraySortQuickSort(thisObject, 0, length-1, compare) arraySortQuickSort(thisObject, 0, length-1, compare)
@ -464,7 +464,7 @@ func builtinArray_indexOf(call FunctionCall) Value {
if length := int64(toUint32(thisObject.get("length"))); length > 0 { if length := int64(toUint32(thisObject.get("length"))); length > 0 {
index := int64(0) index := int64(0)
if len(call.ArgumentList) > 1 { if len(call.ArgumentList) > 1 {
index = toInteger(call.Argument(1)).value index = call.Argument(1).number().int64
} }
if index < 0 { if index < 0 {
if index += length; index < 0 { if index += length; index < 0 {
@ -492,7 +492,7 @@ func builtinArray_lastIndexOf(call FunctionCall) Value {
length := int64(toUint32(thisObject.get("length"))) length := int64(toUint32(thisObject.get("length")))
index := length - 1 index := length - 1
if len(call.ArgumentList) > 1 { if len(call.ArgumentList) > 1 {
index = toInteger(call.Argument(1)).value index = call.Argument(1).number().int64
} }
if 0 > index { if 0 > index {
index += length index += length
@ -523,15 +523,15 @@ func builtinArray_every(call FunctionCall) Value {
callThis := call.Argument(1) callThis := call.Argument(1)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
if value := thisObject.get(key); iterator.call(callThis, value, toValue_int64(index), this).isTrue() { if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, toValue_int64(index), this).bool() {
continue continue
} }
return FalseValue() return falseValue
} }
} }
return TrueValue() return trueValue
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinArray_some(call FunctionCall) Value { func builtinArray_some(call FunctionCall) Value {
@ -542,14 +542,14 @@ func builtinArray_some(call FunctionCall) Value {
callThis := call.Argument(1) callThis := call.Argument(1)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
if value := thisObject.get(key); iterator.call(callThis, value, toValue_int64(index), this).isTrue() { if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, toValue_int64(index), this).bool() {
return TrueValue() return trueValue
} }
} }
} }
return FalseValue() return falseValue
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinArray_forEach(call FunctionCall) Value { func builtinArray_forEach(call FunctionCall) Value {
@ -560,12 +560,12 @@ func builtinArray_forEach(call FunctionCall) Value {
callThis := call.Argument(1) callThis := call.Argument(1)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
iterator.call(callThis, thisObject.get(key), toValue_int64(index), this) iterator.call(call.runtime, callThis, thisObject.get(key), toValue_int64(index), this)
} }
} }
return UndefinedValue() return Value{}
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinArray_map(call FunctionCall) Value { func builtinArray_map(call FunctionCall) Value {
@ -577,14 +577,14 @@ func builtinArray_map(call FunctionCall) Value {
values := make([]Value, length) values := make([]Value, length)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
values[index] = iterator.call(callThis, thisObject.get(key), index, this) values[index] = iterator.call(call.runtime, callThis, thisObject.get(key), index, this)
} else { } else {
values[index] = UndefinedValue() values[index] = Value{}
} }
} }
return toValue_object(call.runtime.newArrayOf(values)) return toValue_object(call.runtime.newArrayOf(values))
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinArray_filter(call FunctionCall) Value { func builtinArray_filter(call FunctionCall) Value {
@ -597,14 +597,14 @@ func builtinArray_filter(call FunctionCall) Value {
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
value := thisObject.get(key) value := thisObject.get(key)
if iterator.call(callThis, value, index, this).isTrue() { if iterator.call(call.runtime, callThis, value, index, this).bool() {
values = append(values, value) values = append(values, value)
} }
} }
} }
return toValue_object(call.runtime.newArrayOf(values)) return toValue_object(call.runtime.newArrayOf(values))
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinArray_reduce(call FunctionCall) Value { func builtinArray_reduce(call FunctionCall) Value {
@ -630,13 +630,13 @@ func builtinArray_reduce(call FunctionCall) Value {
} }
for ; index < length; index++ { for ; index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
accumulator = iterator.call(UndefinedValue(), accumulator, thisObject.get(key), key, this) accumulator = iterator.call(call.runtime, Value{}, accumulator, thisObject.get(key), key, this)
} }
} }
return accumulator return accumulator
} }
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinArray_reduceRight(call FunctionCall) Value { func builtinArray_reduceRight(call FunctionCall) Value {
@ -662,11 +662,11 @@ func builtinArray_reduceRight(call FunctionCall) Value {
} }
for ; index >= 0; index-- { for ; index >= 0; index-- {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
accumulator = iterator.call(UndefinedValue(), accumulator, thisObject.get(key), key, this) accumulator = iterator.call(call.runtime, Value{}, accumulator, thisObject.get(key), key, this)
} }
} }
return accumulator return accumulator
} }
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }

View File

@ -3,10 +3,10 @@ package otto
// Boolean // Boolean
func builtinBoolean(call FunctionCall) Value { func builtinBoolean(call FunctionCall) Value {
return toValue_bool(toBoolean(call.Argument(0))) return toValue_bool(call.Argument(0).bool())
} }
func builtinNewBoolean(self *_object, _ Value, argumentList []Value) Value { func builtinNewBoolean(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newBoolean(valueOfArrayIndex(argumentList, 0))) return toValue_object(self.runtime.newBoolean(valueOfArrayIndex(argumentList, 0)))
} }
@ -16,7 +16,7 @@ func builtinBoolean_toString(call FunctionCall) Value {
// Will throw a TypeError if ThisObject is not a Boolean // Will throw a TypeError if ThisObject is not a Boolean
value = call.thisClassObject("Boolean").primitiveValue() value = call.thisClassObject("Boolean").primitiveValue()
} }
return toValue_string(toString(value)) return toValue_string(value.string())
} }
func builtinBoolean_valueOf(call FunctionCall) Value { func builtinBoolean_valueOf(call FunctionCall) Value {

View File

@ -21,12 +21,12 @@ func builtinDate(call FunctionCall) Value {
return toValue_string(date.Time().Format(builtinDate_goDateTimeLayout)) return toValue_string(date.Time().Format(builtinDate_goDateTimeLayout))
} }
func builtinNewDate(self *_object, _ Value, argumentList []Value) Value { func builtinNewDate(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newDate(newDateTime(argumentList, Time.Local))) return toValue_object(self.runtime.newDate(newDateTime(argumentList, Time.Local)))
} }
func builtinDate_toString(call FunctionCall) Value { func builtinDate_toString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -34,7 +34,7 @@ func builtinDate_toString(call FunctionCall) Value {
} }
func builtinDate_toDateString(call FunctionCall) Value { func builtinDate_toDateString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -42,7 +42,7 @@ func builtinDate_toDateString(call FunctionCall) Value {
} }
func builtinDate_toTimeString(call FunctionCall) Value { func builtinDate_toTimeString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -50,7 +50,7 @@ func builtinDate_toTimeString(call FunctionCall) Value {
} }
func builtinDate_toUTCString(call FunctionCall) Value { func builtinDate_toUTCString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -58,7 +58,7 @@ func builtinDate_toUTCString(call FunctionCall) Value {
} }
func builtinDate_toISOString(call FunctionCall) Value { func builtinDate_toISOString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -69,20 +69,21 @@ func builtinDate_toJSON(call FunctionCall) Value {
object := call.thisObject() object := call.thisObject()
value := object.DefaultValue(defaultValueHintNumber) // FIXME object.primitiveNumberValue value := object.DefaultValue(defaultValueHintNumber) // FIXME object.primitiveNumberValue
{ // FIXME value.isFinite { // FIXME value.isFinite
value := toFloat(value) value := value.float64()
if math.IsNaN(value) || math.IsInf(value, 0) { if math.IsNaN(value) || math.IsInf(value, 0) {
return NullValue() return nullValue
} }
} }
toISOString := object.get("toISOString") toISOString := object.get("toISOString")
if !toISOString.isCallable() { if !toISOString.isCallable() {
panic(newTypeError()) // FIXME
panic(call.runtime.panicTypeError())
} }
return toISOString.call(toValue_object(object), []Value{}) return toISOString.call(call.runtime, toValue_object(object), []Value{})
} }
func builtinDate_toGMTString(call FunctionCall) Value { func builtinDate_toGMTString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -90,7 +91,7 @@ func builtinDate_toGMTString(call FunctionCall) Value {
} }
func builtinDate_getTime(call FunctionCall) Value { func builtinDate_getTime(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -101,15 +102,15 @@ func builtinDate_getTime(call FunctionCall) Value {
func builtinDate_setTime(call FunctionCall) Value { func builtinDate_setTime(call FunctionCall) Value {
object := call.thisObject() object := call.thisObject()
date := dateObjectOf(object) date := dateObjectOf(call.runtime, call.thisObject())
date.Set(toFloat(call.Argument(0))) date.Set(call.Argument(0).float64())
object.value = date object.value = date
return date.Value() return date.Value()
} }
func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool) (*_object, *_dateObject, *_ecmaTime, []int) { func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool) (*_object, *_dateObject, *_ecmaTime, []int) {
object := call.thisObject() object := call.thisObject()
date := dateObjectOf(object) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return nil, nil, nil, nil return nil, nil, nil, nil
} }
@ -126,16 +127,14 @@ func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool
valueList := make([]int, argumentLimit) valueList := make([]int, argumentLimit)
for index := 0; index < argumentLimit; index++ { for index := 0; index < argumentLimit; index++ {
value := call.ArgumentList[index] value := call.ArgumentList[index]
if value.IsNaN() { nm := value.number()
switch nm.kind {
case numberInteger, numberFloat:
default:
object.value = invalidDateObject object.value = invalidDateObject
return nil, nil, nil, nil return nil, nil, nil, nil
} }
integer := toInteger(value) valueList[index] = int(nm.int64)
if !integer.valid() {
object.value = invalidDateObject
return nil, nil, nil, nil
}
valueList[index] = int(integer.value)
} }
baseTime := date.Time() baseTime := date.Time()
if timeLocal { if timeLocal {
@ -146,7 +145,7 @@ func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool
} }
func builtinDate_parse(call FunctionCall) Value { func builtinDate_parse(call FunctionCall) Value {
date := toString(call.Argument(0)) date := call.Argument(0).string()
return toValue_float64(dateParse(date)) return toValue_float64(dateParse(date))
} }
@ -161,7 +160,7 @@ func builtinDate_now(call FunctionCall) Value {
// This is a placeholder // This is a placeholder
func builtinDate_toLocaleString(call FunctionCall) Value { func builtinDate_toLocaleString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -170,7 +169,7 @@ func builtinDate_toLocaleString(call FunctionCall) Value {
// This is a placeholder // This is a placeholder
func builtinDate_toLocaleDateString(call FunctionCall) Value { func builtinDate_toLocaleDateString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -179,7 +178,7 @@ func builtinDate_toLocaleDateString(call FunctionCall) Value {
// This is a placeholder // This is a placeholder
func builtinDate_toLocaleTimeString(call FunctionCall) Value { func builtinDate_toLocaleTimeString(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return toValue_string("Invalid Date")
} }
@ -187,7 +186,7 @@ func builtinDate_toLocaleTimeString(call FunctionCall) Value {
} }
func builtinDate_valueOf(call FunctionCall) Value { func builtinDate_valueOf(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -197,7 +196,7 @@ func builtinDate_valueOf(call FunctionCall) Value {
func builtinDate_getYear(call FunctionCall) Value { func builtinDate_getYear(call FunctionCall) Value {
// Will throw a TypeError is ThisObject is nil or // Will throw a TypeError is ThisObject is nil or
// does not have Class of "Date" // does not have Class of "Date"
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -207,7 +206,7 @@ func builtinDate_getYear(call FunctionCall) Value {
func builtinDate_getFullYear(call FunctionCall) Value { func builtinDate_getFullYear(call FunctionCall) Value {
// Will throw a TypeError is ThisObject is nil or // Will throw a TypeError is ThisObject is nil or
// does not have Class of "Date" // does not have Class of "Date"
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -215,7 +214,7 @@ func builtinDate_getFullYear(call FunctionCall) Value {
} }
func builtinDate_getUTCFullYear(call FunctionCall) Value { func builtinDate_getUTCFullYear(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -223,7 +222,7 @@ func builtinDate_getUTCFullYear(call FunctionCall) Value {
} }
func builtinDate_getMonth(call FunctionCall) Value { func builtinDate_getMonth(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -231,7 +230,7 @@ func builtinDate_getMonth(call FunctionCall) Value {
} }
func builtinDate_getUTCMonth(call FunctionCall) Value { func builtinDate_getUTCMonth(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -239,7 +238,7 @@ func builtinDate_getUTCMonth(call FunctionCall) Value {
} }
func builtinDate_getDate(call FunctionCall) Value { func builtinDate_getDate(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -247,7 +246,7 @@ func builtinDate_getDate(call FunctionCall) Value {
} }
func builtinDate_getUTCDate(call FunctionCall) Value { func builtinDate_getUTCDate(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -256,7 +255,7 @@ func builtinDate_getUTCDate(call FunctionCall) Value {
func builtinDate_getDay(call FunctionCall) Value { func builtinDate_getDay(call FunctionCall) Value {
// Actually day of the week // Actually day of the week
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -264,7 +263,7 @@ func builtinDate_getDay(call FunctionCall) Value {
} }
func builtinDate_getUTCDay(call FunctionCall) Value { func builtinDate_getUTCDay(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -272,7 +271,7 @@ func builtinDate_getUTCDay(call FunctionCall) Value {
} }
func builtinDate_getHours(call FunctionCall) Value { func builtinDate_getHours(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -280,7 +279,7 @@ func builtinDate_getHours(call FunctionCall) Value {
} }
func builtinDate_getUTCHours(call FunctionCall) Value { func builtinDate_getUTCHours(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -288,7 +287,7 @@ func builtinDate_getUTCHours(call FunctionCall) Value {
} }
func builtinDate_getMinutes(call FunctionCall) Value { func builtinDate_getMinutes(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -296,7 +295,7 @@ func builtinDate_getMinutes(call FunctionCall) Value {
} }
func builtinDate_getUTCMinutes(call FunctionCall) Value { func builtinDate_getUTCMinutes(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -304,7 +303,7 @@ func builtinDate_getUTCMinutes(call FunctionCall) Value {
} }
func builtinDate_getSeconds(call FunctionCall) Value { func builtinDate_getSeconds(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -312,7 +311,7 @@ func builtinDate_getSeconds(call FunctionCall) Value {
} }
func builtinDate_getUTCSeconds(call FunctionCall) Value { func builtinDate_getUTCSeconds(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -320,7 +319,7 @@ func builtinDate_getUTCSeconds(call FunctionCall) Value {
} }
func builtinDate_getMilliseconds(call FunctionCall) Value { func builtinDate_getMilliseconds(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -328,7 +327,7 @@ func builtinDate_getMilliseconds(call FunctionCall) Value {
} }
func builtinDate_getUTCMilliseconds(call FunctionCall) Value { func builtinDate_getUTCMilliseconds(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
@ -336,7 +335,7 @@ func builtinDate_getUTCMilliseconds(call FunctionCall) Value {
} }
func builtinDate_getTimezoneOffset(call FunctionCall) Value { func builtinDate_getTimezoneOffset(call FunctionCall) Value {
date := dateObjectOf(call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }

View File

@ -0,0 +1,126 @@
package otto
import (
"fmt"
)
func builtinError(call FunctionCall) Value {
return toValue_object(call.runtime.newError("", call.Argument(0)))
}
func builtinNewError(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newError("", valueOfArrayIndex(argumentList, 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)
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)
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)
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)
self.prototype = runtime.global.URIErrorPrototype
return self
}
func (runtime *_runtime) newReferenceError(message Value) *_object {
self := runtime.newErrorObject("ReferenceError", message)
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)
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)))
}

View File

@ -1,6 +1,7 @@
package otto package otto
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
"unicode" "unicode"
@ -14,14 +15,14 @@ func builtinFunction(call FunctionCall) Value {
return toValue_object(builtinNewFunctionNative(call.runtime, call.ArgumentList)) return toValue_object(builtinNewFunctionNative(call.runtime, call.ArgumentList))
} }
func builtinNewFunction(self *_object, _ Value, argumentList []Value) Value { func builtinNewFunction(self *_object, argumentList []Value) Value {
return toValue_object(builtinNewFunctionNative(self.runtime, argumentList)) return toValue_object(builtinNewFunctionNative(self.runtime, argumentList))
} }
func argumentList2parameterList(argumentList []Value) []string { func argumentList2parameterList(argumentList []Value) []string {
parameterList := make([]string, 0, len(argumentList)) parameterList := make([]string, 0, len(argumentList))
for _, value := range argumentList { for _, value := range argumentList {
tmp := strings.FieldsFunc(toString(value), func(chr rune) bool { tmp := strings.FieldsFunc(value.string(), func(chr rune) bool {
return chr == ',' || unicode.IsSpace(chr) return chr == ',' || unicode.IsSpace(chr)
}) })
parameterList = append(parameterList, tmp...) parameterList = append(parameterList, tmp...)
@ -37,40 +38,51 @@ func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object
if count > 0 { if count > 0 {
tmp := make([]string, 0, count-1) tmp := make([]string, 0, count-1)
for _, value := range argumentList[0 : count-1] { for _, value := range argumentList[0 : count-1] {
tmp = append(tmp, toString(value)) tmp = append(tmp, value.string())
} }
parameterList = strings.Join(tmp, ",") parameterList = strings.Join(tmp, ",")
body = toString(argumentList[count-1]) body = argumentList[count-1].string()
} }
// FIXME
function, err := parser.ParseFunction(parameterList, body) function, err := parser.ParseFunction(parameterList, body)
runtime.parseThrow(err) // Will panic/throw appropriately runtime.parseThrow(err) // Will panic/throw appropriately
cmpl_function := parseExpression(function) cmpl := _compiler{}
cmpl_function := cmpl.parseExpression(function)
return runtime.newNodeFunction(cmpl_function.(*_nodeFunctionLiteral), runtime.GlobalEnvironment) return runtime.newNodeFunction(cmpl_function.(*_nodeFunctionLiteral), runtime.globalStash)
} }
func builtinFunction_toString(call FunctionCall) Value { func builtinFunction_toString(call FunctionCall) Value {
object := call.thisClassObject("Function") // Should throw a TypeError unless Function object := call.thisClassObject("Function") // Should throw a TypeError unless Function
return toValue_string(object.value.(_functionObject).source(object)) 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 { func builtinFunction_apply(call FunctionCall) Value {
if !call.This.isCallable() { if !call.This.isCallable() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
this := call.Argument(0) this := call.Argument(0)
if this.IsUndefined() { if this.IsUndefined() {
// FIXME Not ECMA5 // FIXME Not ECMA5
this = toValue_object(call.runtime.GlobalObject) this = toValue_object(call.runtime.globalObject)
} }
argumentList := call.Argument(1) argumentList := call.Argument(1)
switch argumentList._valueType { switch argumentList.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
return call.thisObject().Call(this, []Value{}) return call.thisObject().call(this, nil, false, nativeFrame)
case valueObject: case valueObject:
default: default:
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
arrayObject := argumentList._object() arrayObject := argumentList._object()
@ -80,29 +92,29 @@ func builtinFunction_apply(call FunctionCall) Value {
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
valueArray[index] = arrayObject.get(arrayIndexToString(index)) valueArray[index] = arrayObject.get(arrayIndexToString(index))
} }
return thisObject.Call(this, valueArray) return thisObject.call(this, valueArray, false, nativeFrame)
} }
func builtinFunction_call(call FunctionCall) Value { func builtinFunction_call(call FunctionCall) Value {
if !call.This.isCallable() { if !call.This.isCallable() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
thisObject := call.thisObject() thisObject := call.thisObject()
this := call.Argument(0) this := call.Argument(0)
if this.IsUndefined() { if this.IsUndefined() {
// FIXME Not ECMA5 // FIXME Not ECMA5
this = toValue_object(call.runtime.GlobalObject) this = toValue_object(call.runtime.globalObject)
} }
if len(call.ArgumentList) >= 1 { if len(call.ArgumentList) >= 1 {
return thisObject.Call(this, call.ArgumentList[1:]) return thisObject.call(this, call.ArgumentList[1:], false, nativeFrame)
} }
return thisObject.Call(this, []Value{}) return thisObject.call(this, nil, false, nativeFrame)
} }
func builtinFunction_bind(call FunctionCall) Value { func builtinFunction_bind(call FunctionCall) Value {
target := call.This target := call.This
if !target.isCallable() { if !target.isCallable() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
targetObject := target._object() targetObject := target._object()
@ -110,7 +122,7 @@ func builtinFunction_bind(call FunctionCall) Value {
argumentList := call.slice(1) argumentList := call.slice(1)
if this.IsUndefined() { if this.IsUndefined() {
// FIXME Do this elsewhere? // FIXME Do this elsewhere?
this = toValue_object(call.runtime.GlobalObject) this = toValue_object(call.runtime.globalObject)
} }
return toValue_object(call.runtime.newBoundFunction(targetObject, this, argumentList)) return toValue_object(call.runtime.newBoundFunction(targetObject, this, argumentList))

View File

@ -3,7 +3,7 @@ package otto
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"math" "fmt"
"strings" "strings"
) )
@ -23,13 +23,13 @@ func builtinJSON_parse(call FunctionCall) Value {
} }
var root interface{} var root interface{}
err := json.Unmarshal([]byte(toString(call.Argument(0))), &root) err := json.Unmarshal([]byte(call.Argument(0).string()), &root)
if err != nil { if err != nil {
panic(newSyntaxError(err.Error())) panic(call.runtime.panicSyntaxError(err.Error()))
} }
value, exists := builtinJSON_parseWalk(ctx, root) value, exists := builtinJSON_parseWalk(ctx, root)
if !exists { if !exists {
value = UndefinedValue() value = Value{}
} }
if revive { if revive {
root := ctx.call.runtime.newObject() root := ctx.call.runtime.newObject()
@ -65,13 +65,13 @@ func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name
}) })
} }
} }
return ctx.reviver.call(toValue_object(holder), name, value) return ctx.reviver.call(ctx.call.runtime, toValue_object(holder), name, value)
} }
func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) { func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) {
switch value := rawValue.(type) { switch value := rawValue.(type) {
case nil: case nil:
return NullValue(), true return nullValue, true
case bool: case bool:
return toValue_bool(value), true return toValue_bool(value), true
case string: case string:
@ -120,7 +120,7 @@ func builtinJSON_stringify(call FunctionCall) Value {
length = 0 length = 0
for index, _ := range propertyList { for index, _ := range propertyList {
value := replacer.get(arrayIndexToString(int64(index))) value := replacer.get(arrayIndexToString(int64(index)))
switch value._valueType { switch value.kind {
case valueObject: case valueObject:
switch value.value.(*_object).class { switch value.value.(*_object).class {
case "String": case "String":
@ -133,7 +133,7 @@ func builtinJSON_stringify(call FunctionCall) Value {
default: default:
continue continue
} }
name := toString(value) name := value.string()
if seen[name] { if seen[name] {
continue continue
} }
@ -148,24 +148,24 @@ func builtinJSON_stringify(call FunctionCall) Value {
} }
} }
if spaceValue, exists := call.getArgument(2); exists { if spaceValue, exists := call.getArgument(2); exists {
if spaceValue._valueType == valueObject { if spaceValue.kind == valueObject {
switch spaceValue.value.(*_object).class { switch spaceValue.value.(*_object).class {
case "String": case "String":
spaceValue = toValue_string(toString(spaceValue)) spaceValue = toValue_string(spaceValue.string())
case "Number": case "Number":
spaceValue = toNumber(spaceValue) spaceValue = spaceValue.numberValue()
} }
} }
switch spaceValue._valueType { switch spaceValue.kind {
case valueString: case valueString:
value := toString(spaceValue) value := spaceValue.string()
if len(value) > 10 { if len(value) > 10 {
ctx.gap = value[0:10] ctx.gap = value[0:10]
} else { } else {
ctx.gap = value ctx.gap = value
} }
case valueNumber: case valueNumber:
value := toInteger(spaceValue).value value := spaceValue.number().int64
if value > 10 { if value > 10 {
value = 10 value = 10
} else if value < 0 { } else if value < 0 {
@ -178,11 +178,11 @@ func builtinJSON_stringify(call FunctionCall) Value {
holder.put("", call.Argument(0), false) holder.put("", call.Argument(0), false)
value, exists := builtinJSON_stringifyWalk(ctx, "", holder) value, exists := builtinJSON_stringifyWalk(ctx, "", holder)
if !exists { if !exists {
return UndefinedValue() return Value{}
} }
valueJSON, err := json.Marshal(value) valueJSON, err := json.Marshal(value)
if err != nil { if err != nil {
panic(newTypeError(err.Error())) panic(call.runtime.panicTypeError(err.Error()))
} }
if ctx.gap != "" { if ctx.gap != "" {
valueJSON1 := bytes.Buffer{} valueJSON1 := bytes.Buffer{}
@ -198,7 +198,7 @@ func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, ho
if value.IsObject() { if value.IsObject() {
object := value._object() object := value._object()
if toJSON := object.get("toJSON"); toJSON.IsFunction() { if toJSON := object.get("toJSON"); toJSON.IsFunction() {
value = toJSON.call(value, key) value = toJSON.call(ctx.call.runtime, value, key)
} else { } else {
// If the object is a GoStruct or something that implements json.Marshaler // If the object is a GoStruct or something that implements json.Marshaler
if object.objectClass.marshalJSON != nil { if object.objectClass.marshalJSON != nil {
@ -211,31 +211,35 @@ func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, ho
} }
if ctx.replacerFunction != nil { if ctx.replacerFunction != nil {
value = (*ctx.replacerFunction).call(toValue_object(holder), key, value) value = (*ctx.replacerFunction).call(ctx.call.runtime, toValue_object(holder), key, value)
} }
if value._valueType == valueObject { if value.kind == valueObject {
switch value.value.(*_object).class { switch value.value.(*_object).class {
case "Boolean": case "Boolean":
value = value._object().value.(Value) value = value._object().value.(Value)
case "String": case "String":
value = toValue_string(toString(value)) value = toValue_string(value.string())
case "Number": case "Number":
value = toNumber(value) value = value.numberValue()
} }
} }
switch value._valueType { switch value.kind {
case valueBoolean: case valueBoolean:
return toBoolean(value), true return value.bool(), true
case valueString: case valueString:
return toString(value), true return value.string(), true
case valueNumber: case valueNumber:
value := toFloat(value) integer := value.number()
if math.IsNaN(value) || math.IsInf(value, 0) { switch integer.kind {
case numberInteger:
return integer.int64, true
case numberFloat:
return integer.float64, true
default:
return nil, true return nil, true
} }
return value, true
case valueNull: case valueNull:
return nil, true return nil, true
case valueObject: case valueObject:
@ -243,14 +247,24 @@ func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, ho
if value := value._object(); nil != value { if value := value._object(); nil != value {
for _, object := range ctx.stack { for _, object := range ctx.stack {
if holder == object { if holder == object {
panic(newTypeError("Converting circular structure to JSON")) panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON"))
} }
} }
ctx.stack = append(ctx.stack, value) ctx.stack = append(ctx.stack, value)
defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }() defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }()
} }
if isArray(holder) { if isArray(holder) {
length := holder.get("length").value.(uint32) 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) array := make([]interface{}, length)
for index, _ := range array { for index, _ := range array {
name := arrayIndexToString(int64(index)) name := arrayIndexToString(int64(index))

View File

@ -8,31 +8,31 @@ import (
// Math // Math
func builtinMath_abs(call FunctionCall) Value { func builtinMath_abs(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Abs(number)) return toValue_float64(math.Abs(number))
} }
func builtinMath_acos(call FunctionCall) Value { func builtinMath_acos(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Acos(number)) return toValue_float64(math.Acos(number))
} }
func builtinMath_asin(call FunctionCall) Value { func builtinMath_asin(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Asin(number)) return toValue_float64(math.Asin(number))
} }
func builtinMath_atan(call FunctionCall) Value { func builtinMath_atan(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Atan(number)) return toValue_float64(math.Atan(number))
} }
func builtinMath_atan2(call FunctionCall) Value { func builtinMath_atan2(call FunctionCall) Value {
y := toFloat(call.Argument(0)) y := call.Argument(0).float64()
if math.IsNaN(y) { if math.IsNaN(y) {
return NaNValue() return NaNValue()
} }
x := toFloat(call.Argument(1)) x := call.Argument(1).float64()
if math.IsNaN(x) { if math.IsNaN(x) {
return NaNValue() return NaNValue()
} }
@ -40,27 +40,27 @@ func builtinMath_atan2(call FunctionCall) Value {
} }
func builtinMath_cos(call FunctionCall) Value { func builtinMath_cos(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Cos(number)) return toValue_float64(math.Cos(number))
} }
func builtinMath_ceil(call FunctionCall) Value { func builtinMath_ceil(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Ceil(number)) return toValue_float64(math.Ceil(number))
} }
func builtinMath_exp(call FunctionCall) Value { func builtinMath_exp(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Exp(number)) return toValue_float64(math.Exp(number))
} }
func builtinMath_floor(call FunctionCall) Value { func builtinMath_floor(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Floor(number)) return toValue_float64(math.Floor(number))
} }
func builtinMath_log(call FunctionCall) Value { func builtinMath_log(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Log(number)) return toValue_float64(math.Log(number))
} }
@ -69,14 +69,14 @@ func builtinMath_max(call FunctionCall) Value {
case 0: case 0:
return negativeInfinityValue() return negativeInfinityValue()
case 1: case 1:
return toValue_float64(toFloat(call.ArgumentList[0])) return toValue_float64(call.ArgumentList[0].float64())
} }
result := toFloat(call.ArgumentList[0]) result := call.ArgumentList[0].float64()
if math.IsNaN(result) { if math.IsNaN(result) {
return NaNValue() return NaNValue()
} }
for _, value := range call.ArgumentList[1:] { for _, value := range call.ArgumentList[1:] {
value := toFloat(value) value := value.float64()
if math.IsNaN(value) { if math.IsNaN(value) {
return NaNValue() return NaNValue()
} }
@ -90,14 +90,14 @@ func builtinMath_min(call FunctionCall) Value {
case 0: case 0:
return positiveInfinityValue() return positiveInfinityValue()
case 1: case 1:
return toValue_float64(toFloat(call.ArgumentList[0])) return toValue_float64(call.ArgumentList[0].float64())
} }
result := toFloat(call.ArgumentList[0]) result := call.ArgumentList[0].float64()
if math.IsNaN(result) { if math.IsNaN(result) {
return NaNValue() return NaNValue()
} }
for _, value := range call.ArgumentList[1:] { for _, value := range call.ArgumentList[1:] {
value := toFloat(value) value := value.float64()
if math.IsNaN(value) { if math.IsNaN(value) {
return NaNValue() return NaNValue()
} }
@ -108,8 +108,8 @@ func builtinMath_min(call FunctionCall) Value {
func builtinMath_pow(call FunctionCall) Value { func builtinMath_pow(call FunctionCall) Value {
// TODO Make sure this works according to the specification (15.8.2.13) // TODO Make sure this works according to the specification (15.8.2.13)
x := toFloat(call.Argument(0)) x := call.Argument(0).float64()
y := toFloat(call.Argument(1)) y := call.Argument(1).float64()
if math.Abs(x) == 1 && math.IsInf(y, 0) { if math.Abs(x) == 1 && math.IsInf(y, 0) {
return NaNValue() return NaNValue()
} }
@ -121,7 +121,7 @@ func builtinMath_random(call FunctionCall) Value {
} }
func builtinMath_round(call FunctionCall) Value { func builtinMath_round(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
value := math.Floor(number + 0.5) value := math.Floor(number + 0.5)
if value == 0 { if value == 0 {
value = math.Copysign(0, number) value = math.Copysign(0, number)
@ -130,16 +130,16 @@ func builtinMath_round(call FunctionCall) Value {
} }
func builtinMath_sin(call FunctionCall) Value { func builtinMath_sin(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Sin(number)) return toValue_float64(math.Sin(number))
} }
func builtinMath_sqrt(call FunctionCall) Value { func builtinMath_sqrt(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Sqrt(number)) return toValue_float64(math.Sqrt(number))
} }
func builtinMath_tan(call FunctionCall) Value { func builtinMath_tan(call FunctionCall) Value {
number := toFloat(call.Argument(0)) number := call.Argument(0).float64()
return toValue_float64(math.Tan(number)) return toValue_float64(math.Tan(number))
} }

View File

@ -9,7 +9,7 @@ import (
func numberValueFromNumberArgumentList(argumentList []Value) Value { func numberValueFromNumberArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 { if len(argumentList) > 0 {
return toNumber(argumentList[0]) return argumentList[0].numberValue()
} }
return toValue_int(0) return toValue_int(0)
} }
@ -18,7 +18,7 @@ func builtinNumber(call FunctionCall) Value {
return numberValueFromNumberArgumentList(call.ArgumentList) return numberValueFromNumberArgumentList(call.ArgumentList)
} }
func builtinNewNumber(self *_object, _ Value, argumentList []Value) Value { func builtinNewNumber(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newNumber(numberValueFromNumberArgumentList(argumentList))) return toValue_object(self.runtime.newNumber(numberValueFromNumberArgumentList(argumentList)))
} }
@ -30,12 +30,12 @@ func builtinNumber_toString(call FunctionCall) Value {
if radixArgument.IsDefined() { if radixArgument.IsDefined() {
integer := toIntegerFloat(radixArgument) integer := toIntegerFloat(radixArgument)
if integer < 2 || integer > 36 { if integer < 2 || integer > 36 {
panic(newRangeError("RangeError: toString() radix must be between 2 and 36")) panic(call.runtime.panicRangeError("RangeError: toString() radix must be between 2 and 36"))
} }
radix = int(integer) radix = int(integer)
} }
if radix == 10 { if radix == 10 {
return toValue_string(toString(value)) return toValue_string(value.string())
} }
return toValue_string(numberToStringRadix(value, radix)) return toValue_string(numberToStringRadix(value, radix))
} }
@ -47,16 +47,16 @@ func builtinNumber_valueOf(call FunctionCall) Value {
func builtinNumber_toFixed(call FunctionCall) Value { func builtinNumber_toFixed(call FunctionCall) Value {
precision := toIntegerFloat(call.Argument(0)) precision := toIntegerFloat(call.Argument(0))
if 20 < precision || 0 > precision { if 20 < precision || 0 > precision {
panic(newRangeError("toFixed() precision must be between 0 and 20")) panic(call.runtime.panicRangeError("toFixed() precision must be between 0 and 20"))
} }
if call.This.IsNaN() { if call.This.IsNaN() {
return toValue_string("NaN") return toValue_string("NaN")
} }
value := toFloat(call.This) value := call.This.float64()
if math.Abs(value) >= 1e21 { if math.Abs(value) >= 1e21 {
return toValue_string(floatToString(value, 64)) return toValue_string(floatToString(value, 64))
} }
return toValue_string(strconv.FormatFloat(toFloat(call.This), 'f', int(precision), 64)) return toValue_string(strconv.FormatFloat(call.This.float64(), 'f', int(precision), 64))
} }
func builtinNumber_toExponential(call FunctionCall) Value { func builtinNumber_toExponential(call FunctionCall) Value {
@ -67,10 +67,10 @@ func builtinNumber_toExponential(call FunctionCall) Value {
if value := call.Argument(0); value.IsDefined() { if value := call.Argument(0); value.IsDefined() {
precision = toIntegerFloat(value) precision = toIntegerFloat(value)
if 0 > precision { if 0 > precision {
panic(newRangeError("RangeError: toExponential() precision must be greater than 0")) panic(call.runtime.panicRangeError("RangeError: toString() radix must be between 2 and 36"))
} }
} }
return toValue_string(strconv.FormatFloat(toFloat(call.This), 'e', int(precision), 64)) return toValue_string(strconv.FormatFloat(call.This.float64(), 'e', int(precision), 64))
} }
func builtinNumber_toPrecision(call FunctionCall) Value { func builtinNumber_toPrecision(call FunctionCall) Value {
@ -79,13 +79,13 @@ func builtinNumber_toPrecision(call FunctionCall) Value {
} }
value := call.Argument(0) value := call.Argument(0)
if value.IsUndefined() { if value.IsUndefined() {
return toValue_string(toString(call.This)) return toValue_string(call.This.string())
} }
precision := toIntegerFloat(value) precision := toIntegerFloat(value)
if 1 > precision { if 1 > precision {
panic(newRangeError("RangeError: toPrecision() precision must be greater than 1")) panic(call.runtime.panicRangeError("RangeError: toPrecision() precision must be greater than 1"))
} }
return toValue_string(strconv.FormatFloat(toFloat(call.This), 'g', int(precision), 64)) return toValue_string(strconv.FormatFloat(call.This.float64(), 'g', int(precision), 64))
} }
func builtinNumber_toLocaleString(call FunctionCall) Value { func builtinNumber_toLocaleString(call FunctionCall) Value {

View File

@ -8,7 +8,7 @@ import (
func builtinObject(call FunctionCall) Value { func builtinObject(call FunctionCall) Value {
value := call.Argument(0) value := call.Argument(0)
switch value._valueType { switch value.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
return toValue_object(call.runtime.newObject()) return toValue_object(call.runtime.newObject())
} }
@ -16,9 +16,9 @@ func builtinObject(call FunctionCall) Value {
return toValue_object(call.runtime.toObject(value)) return toValue_object(call.runtime.toObject(value))
} }
func builtinNewObject(self *_object, _ Value, argumentList []Value) Value { func builtinNewObject(self *_object, argumentList []Value) Value {
value := valueOfArrayIndex(argumentList, 0) value := valueOfArrayIndex(argumentList, 0)
switch value._valueType { switch value.kind {
case valueNull, valueUndefined: case valueNull, valueUndefined:
case valueNumber, valueString, valueBoolean: case valueNumber, valueString, valueBoolean:
return toValue_object(self.runtime.toObject(value)) return toValue_object(self.runtime.toObject(value))
@ -34,7 +34,7 @@ func builtinObject_valueOf(call FunctionCall) Value {
} }
func builtinObject_hasOwnProperty(call FunctionCall) Value { func builtinObject_hasOwnProperty(call FunctionCall) Value {
propertyName := toString(call.Argument(0)) propertyName := call.Argument(0).string()
thisObject := call.thisObject() thisObject := call.thisObject()
return toValue_bool(thisObject.hasOwnProperty(propertyName)) return toValue_bool(thisObject.hasOwnProperty(propertyName))
} }
@ -42,27 +42,27 @@ func builtinObject_hasOwnProperty(call FunctionCall) Value {
func builtinObject_isPrototypeOf(call FunctionCall) Value { func builtinObject_isPrototypeOf(call FunctionCall) Value {
value := call.Argument(0) value := call.Argument(0)
if !value.IsObject() { if !value.IsObject() {
return FalseValue() return falseValue
} }
prototype := call.toObject(value).prototype prototype := call.toObject(value).prototype
thisObject := call.thisObject() thisObject := call.thisObject()
for prototype != nil { for prototype != nil {
if thisObject == prototype { if thisObject == prototype {
return TrueValue() return trueValue
} }
prototype = prototype.prototype prototype = prototype.prototype
} }
return FalseValue() return falseValue
} }
func builtinObject_propertyIsEnumerable(call FunctionCall) Value { func builtinObject_propertyIsEnumerable(call FunctionCall) Value {
propertyName := toString(call.Argument(0)) propertyName := call.Argument(0).string()
thisObject := call.thisObject() thisObject := call.thisObject()
property := thisObject.getOwnProperty(propertyName) property := thisObject.getOwnProperty(propertyName)
if property != nil && property.enumerable() { if property != nil && property.enumerable() {
return TrueValue() return trueValue
} }
return FalseValue() return falseValue
} }
func builtinObject_toString(call FunctionCall) Value { func builtinObject_toString(call FunctionCall) Value {
@ -80,20 +80,20 @@ func builtinObject_toString(call FunctionCall) Value {
func builtinObject_toLocaleString(call FunctionCall) Value { func builtinObject_toLocaleString(call FunctionCall) Value {
toString := call.thisObject().get("toString") toString := call.thisObject().get("toString")
if !toString.isCallable() { if !toString.isCallable() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
return toString.call(call.This) return toString.call(call.runtime, call.This)
} }
func builtinObject_getPrototypeOf(call FunctionCall) Value { func builtinObject_getPrototypeOf(call FunctionCall) Value {
objectValue := call.Argument(0) objectValue := call.Argument(0)
object := objectValue._object() object := objectValue._object()
if object == nil { if object == nil {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
if object.prototype == nil { if object.prototype == nil {
return NullValue() return nullValue
} }
return toValue_object(object.prototype) return toValue_object(object.prototype)
@ -103,13 +103,13 @@ func builtinObject_getOwnPropertyDescriptor(call FunctionCall) Value {
objectValue := call.Argument(0) objectValue := call.Argument(0)
object := objectValue._object() object := objectValue._object()
if object == nil { if object == nil {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
name := toString(call.Argument(1)) name := call.Argument(1).string()
descriptor := object.getOwnProperty(name) descriptor := object.getOwnProperty(name)
if descriptor == nil { if descriptor == nil {
return UndefinedValue() return Value{}
} }
return toValue_object(call.runtime.fromPropertyDescriptor(*descriptor)) return toValue_object(call.runtime.fromPropertyDescriptor(*descriptor))
} }
@ -118,10 +118,10 @@ func builtinObject_defineProperty(call FunctionCall) Value {
objectValue := call.Argument(0) objectValue := call.Argument(0)
object := objectValue._object() object := objectValue._object()
if object == nil { if object == nil {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
name := toString(call.Argument(1)) name := call.Argument(1).string()
descriptor := toPropertyDescriptor(call.Argument(2)) descriptor := toPropertyDescriptor(call.runtime, call.Argument(2))
object.defineOwnProperty(name, descriptor, true) object.defineOwnProperty(name, descriptor, true)
return objectValue return objectValue
} }
@ -130,12 +130,12 @@ func builtinObject_defineProperties(call FunctionCall) Value {
objectValue := call.Argument(0) objectValue := call.Argument(0)
object := objectValue._object() object := objectValue._object()
if object == nil { if object == nil {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
properties := call.runtime.toObject(call.Argument(1)) properties := call.runtime.toObject(call.Argument(1))
properties.enumerate(false, func(name string) bool { properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(properties.get(name)) descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true) object.defineOwnProperty(name, descriptor, true)
return true return true
}) })
@ -146,7 +146,7 @@ func builtinObject_defineProperties(call FunctionCall) Value {
func builtinObject_create(call FunctionCall) Value { func builtinObject_create(call FunctionCall) Value {
prototypeValue := call.Argument(0) prototypeValue := call.Argument(0)
if !prototypeValue.IsNull() && !prototypeValue.IsObject() { if !prototypeValue.IsNull() && !prototypeValue.IsObject() {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
object := call.runtime.newObject() object := call.runtime.newObject()
@ -156,7 +156,7 @@ func builtinObject_create(call FunctionCall) Value {
if propertiesValue.IsDefined() { if propertiesValue.IsDefined() {
properties := call.runtime.toObject(propertiesValue) properties := call.runtime.toObject(propertiesValue)
properties.enumerate(false, func(name string) bool { properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(properties.get(name)) descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true) object.defineOwnProperty(name, descriptor, true)
return true return true
}) })
@ -170,7 +170,7 @@ func builtinObject_isExtensible(call FunctionCall) Value {
if object := object._object(); object != nil { if object := object._object(); object != nil {
return toValue_bool(object.extensible) return toValue_bool(object.extensible)
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinObject_preventExtensions(call FunctionCall) Value { func builtinObject_preventExtensions(call FunctionCall) Value {
@ -178,7 +178,7 @@ func builtinObject_preventExtensions(call FunctionCall) Value {
if object := object._object(); object != nil { if object := object._object(); object != nil {
object.extensible = false object.extensible = false
} else { } else {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
return object return object
} }
@ -199,7 +199,7 @@ func builtinObject_isSealed(call FunctionCall) Value {
}) })
return toValue_bool(result) return toValue_bool(result)
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinObject_seal(call FunctionCall) Value { func builtinObject_seal(call FunctionCall) Value {
@ -214,7 +214,7 @@ func builtinObject_seal(call FunctionCall) Value {
}) })
object.extensible = false object.extensible = false
} else { } else {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
return object return object
} }
@ -235,7 +235,7 @@ func builtinObject_isFrozen(call FunctionCall) Value {
}) })
return toValue_bool(result) return toValue_bool(result)
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinObject_freeze(call FunctionCall) Value { func builtinObject_freeze(call FunctionCall) Value {
@ -259,7 +259,7 @@ func builtinObject_freeze(call FunctionCall) Value {
}) })
object.extensible = false object.extensible = false
} else { } else {
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
return object return object
} }
@ -272,7 +272,7 @@ func builtinObject_keys(call FunctionCall) Value {
}) })
return toValue_object(call.runtime.newArrayOf(keys)) return toValue_object(call.runtime.newArrayOf(keys))
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }
func builtinObject_getOwnPropertyNames(call FunctionCall) Value { func builtinObject_getOwnPropertyNames(call FunctionCall) Value {
@ -285,5 +285,5 @@ func builtinObject_getOwnPropertyNames(call FunctionCall) Value {
}) })
return toValue_object(call.runtime.newArrayOf(propertyNames)) return toValue_object(call.runtime.newArrayOf(propertyNames))
} }
panic(newTypeError()) panic(call.runtime.panicTypeError())
} }

View File

@ -17,7 +17,7 @@ func builtinRegExp(call FunctionCall) Value {
return toValue_object(call.runtime.newRegExp(pattern, flags)) return toValue_object(call.runtime.newRegExp(pattern, flags))
} }
func builtinNewRegExp(self *_object, _ Value, argumentList []Value) Value { func builtinNewRegExp(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newRegExp( return toValue_object(self.runtime.newRegExp(
valueOfArrayIndex(argumentList, 0), valueOfArrayIndex(argumentList, 0),
valueOfArrayIndex(argumentList, 1), valueOfArrayIndex(argumentList, 1),
@ -26,15 +26,15 @@ func builtinNewRegExp(self *_object, _ Value, argumentList []Value) Value {
func builtinRegExp_toString(call FunctionCall) Value { func builtinRegExp_toString(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
source := toString(thisObject.get("source")) source := thisObject.get("source").string()
flags := []byte{} flags := []byte{}
if toBoolean(thisObject.get("global")) { if thisObject.get("global").bool() {
flags = append(flags, 'g') flags = append(flags, 'g')
} }
if toBoolean(thisObject.get("ignoreCase")) { if thisObject.get("ignoreCase").bool() {
flags = append(flags, 'i') flags = append(flags, 'i')
} }
if toBoolean(thisObject.get("multiline")) { if thisObject.get("multiline").bool() {
flags = append(flags, 'm') flags = append(flags, 'm')
} }
return toValue_string(fmt.Sprintf("/%s/%s", source, flags)) return toValue_string(fmt.Sprintf("/%s/%s", source, flags))
@ -42,17 +42,17 @@ func builtinRegExp_toString(call FunctionCall) Value {
func builtinRegExp_exec(call FunctionCall) Value { func builtinRegExp_exec(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
target := toString(call.Argument(0)) target := call.Argument(0).string()
match, result := execRegExp(thisObject, target) match, result := execRegExp(thisObject, target)
if !match { if !match {
return NullValue() return nullValue
} }
return toValue_object(execResultToArray(call.runtime, target, result)) return toValue_object(execResultToArray(call.runtime, target, result))
} }
func builtinRegExp_test(call FunctionCall) Value { func builtinRegExp_test(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
target := toString(call.Argument(0)) target := call.Argument(0).string()
match, _ := execRegExp(thisObject, target) match, _ := execRegExp(thisObject, target)
return toValue_bool(match) return toValue_bool(match)
} }
@ -61,5 +61,5 @@ func builtinRegExp_compile(call FunctionCall) Value {
// This (useless) function is deprecated, but is here to provide some // This (useless) function is deprecated, but is here to provide some
// semblance of compatibility. // semblance of compatibility.
// Caveat emptor: it may not be around for long. // Caveat emptor: it may not be around for long.
return UndefinedValue() return Value{}
} }

View File

@ -12,7 +12,7 @@ import (
func stringValueFromStringArgumentList(argumentList []Value) Value { func stringValueFromStringArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 { if len(argumentList) > 0 {
return toValue_string(toString(argumentList[0])) return toValue_string(argumentList[0].string())
} }
return toValue_string("") return toValue_string("")
} }
@ -21,7 +21,7 @@ func builtinString(call FunctionCall) Value {
return stringValueFromStringArgumentList(call.ArgumentList) return stringValueFromStringArgumentList(call.ArgumentList)
} }
func builtinNewString(self *_object, _ Value, argumentList []Value) Value { func builtinNewString(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newString(stringValueFromStringArgumentList(argumentList))) return toValue_object(self.runtime.newString(stringValueFromStringArgumentList(argumentList)))
} }
@ -41,8 +41,8 @@ func builtinString_fromCharCode(call FunctionCall) Value {
} }
func builtinString_charAt(call FunctionCall) Value { func builtinString_charAt(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
idx := int(toInteger(call.Argument(0)).value) idx := int(call.Argument(0).number().int64)
chr := stringAt(call.This._object().stringValue(), idx) chr := stringAt(call.This._object().stringValue(), idx)
if chr == utf8.RuneError { if chr == utf8.RuneError {
return toValue_string("") return toValue_string("")
@ -51,8 +51,8 @@ func builtinString_charAt(call FunctionCall) Value {
} }
func builtinString_charCodeAt(call FunctionCall) Value { func builtinString_charCodeAt(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
idx := int(toInteger(call.Argument(0)).value) idx := int(call.Argument(0).number().int64)
chr := stringAt(call.This._object().stringValue(), idx) chr := stringAt(call.This._object().stringValue(), idx)
if chr == utf8.RuneError { if chr == utf8.RuneError {
return NaNValue() return NaNValue()
@ -61,19 +61,19 @@ func builtinString_charCodeAt(call FunctionCall) Value {
} }
func builtinString_concat(call FunctionCall) Value { func builtinString_concat(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
var value bytes.Buffer var value bytes.Buffer
value.WriteString(toString(call.This)) value.WriteString(call.This.string())
for _, item := range call.ArgumentList { for _, item := range call.ArgumentList {
value.WriteString(toString(item)) value.WriteString(item.string())
} }
return toValue_string(value.String()) return toValue_string(value.String())
} }
func builtinString_indexOf(call FunctionCall) Value { func builtinString_indexOf(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
value := toString(call.This) value := call.This.string()
target := toString(call.Argument(0)) target := call.Argument(0).string()
if 2 > len(call.ArgumentList) { if 2 > len(call.ArgumentList) {
return toValue_int(strings.Index(value, target)) return toValue_int(strings.Index(value, target))
} }
@ -94,9 +94,9 @@ func builtinString_indexOf(call FunctionCall) Value {
} }
func builtinString_lastIndexOf(call FunctionCall) Value { func builtinString_lastIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
value := toString(call.This) value := call.This.string()
target := toString(call.Argument(0)) target := call.Argument(0).string()
if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() { if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
return toValue_int(strings.LastIndex(value, target)) return toValue_int(strings.LastIndex(value, target))
} }
@ -104,15 +104,15 @@ func builtinString_lastIndexOf(call FunctionCall) Value {
if length == 0 { if length == 0 {
return toValue_int(strings.LastIndex(value, target)) return toValue_int(strings.LastIndex(value, target))
} }
start := toInteger(call.ArgumentList[1]) start := call.ArgumentList[1].number()
if !start.valid() { if start.kind == numberInfinity { // FIXME
// startNumber is infinity, so start is the end of string (start = length) // startNumber is infinity, so start is the end of string (start = length)
return toValue_int(strings.LastIndex(value, target)) return toValue_int(strings.LastIndex(value, target))
} }
if 0 > start.value { if 0 > start.int64 {
start.value = 0 start.int64 = 0
} }
end := int(start.value) + len(target) end := int(start.int64) + len(target)
if end > length { if end > length {
end = length end = length
} }
@ -120,18 +120,18 @@ func builtinString_lastIndexOf(call FunctionCall) Value {
} }
func builtinString_match(call FunctionCall) Value { func builtinString_match(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
target := toString(call.This) target := call.This.string()
matcherValue := call.Argument(0) matcherValue := call.Argument(0)
matcher := matcherValue._object() matcher := matcherValue._object()
if !matcherValue.IsObject() || matcher.class != "RegExp" { if !matcherValue.IsObject() || matcher.class != "RegExp" {
matcher = call.runtime.newRegExp(matcherValue, UndefinedValue()) matcher = call.runtime.newRegExp(matcherValue, Value{})
} }
global := toBoolean(matcher.get("global")) global := matcher.get("global").bool()
if !global { if !global {
match, result := execRegExp(matcher, target) match, result := execRegExp(matcher, target)
if !match { if !match {
return NullValue() return nullValue
} }
return toValue_object(execResultToArray(call.runtime, target, result)) return toValue_object(execResultToArray(call.runtime, target, result))
} }
@ -141,7 +141,7 @@ func builtinString_match(call FunctionCall) Value {
matchCount := len(result) matchCount := len(result)
if result == nil { if result == nil {
matcher.put("lastIndex", toValue_int(0), true) matcher.put("lastIndex", toValue_int(0), true)
return UndefinedValue() // !match return Value{} // !match
} }
matchCount = len(result) matchCount = len(result)
valueArray := make([]Value, matchCount) valueArray := make([]Value, matchCount)
@ -189,8 +189,8 @@ func builtinString_findAndReplaceString(input []byte, lastIndex int, match []int
} }
func builtinString_replace(call FunctionCall) Value { func builtinString_replace(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
target := []byte(toString(call.This)) target := []byte(call.This.string())
searchValue := call.Argument(0) searchValue := call.Argument(0)
searchObject := searchValue._object() searchObject := searchValue._object()
@ -205,7 +205,7 @@ func builtinString_replace(call FunctionCall) Value {
find = -1 find = -1
} }
} else { } else {
search = regexp.MustCompile(regexp.QuoteMeta(toString(searchValue))) search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
} }
found := search.FindAllSubmatchIndex(target, find) found := search.FindAllSubmatchIndex(target, find)
@ -232,18 +232,18 @@ func builtinString_replace(call FunctionCall) Value {
if match[offset] != -1 { if match[offset] != -1 {
argumentList[index] = toValue_string(target[match[offset]:match[offset+1]]) argumentList[index] = toValue_string(target[match[offset]:match[offset+1]])
} else { } else {
argumentList[index] = UndefinedValue() argumentList[index] = Value{}
} }
} }
argumentList[matchCount+0] = toValue_int(match[0]) argumentList[matchCount+0] = toValue_int(match[0])
argumentList[matchCount+1] = toValue_string(target) argumentList[matchCount+1] = toValue_string(target)
replacement := toString(replace.Call(UndefinedValue(), argumentList)) replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
result = append(result, []byte(replacement)...) result = append(result, []byte(replacement)...)
lastIndex = match[1] lastIndex = match[1]
} }
} else { } else {
replace := []byte(toString(replaceValue)) replace := []byte(replaceValue.string())
for _, match := range found { for _, match := range found {
result = builtinString_findAndReplaceString(result, lastIndex, match, target, replace) result = builtinString_findAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1] lastIndex = match[1]
@ -260,17 +260,15 @@ func builtinString_replace(call FunctionCall) Value {
return toValue_string(string(result)) return toValue_string(string(result))
} }
return UndefinedValue()
} }
func builtinString_search(call FunctionCall) Value { func builtinString_search(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
target := toString(call.This) target := call.This.string()
searchValue := call.Argument(0) searchValue := call.Argument(0)
search := searchValue._object() search := searchValue._object()
if !searchValue.IsObject() || search.class != "RegExp" { if !searchValue.IsObject() || search.class != "RegExp" {
search = call.runtime.newRegExp(searchValue, UndefinedValue()) search = call.runtime.newRegExp(searchValue, Value{})
} }
result := search.regExpValue().regularExpression.FindStringIndex(target) result := search.regExpValue().regularExpression.FindStringIndex(target)
if result == nil { if result == nil {
@ -291,8 +289,8 @@ func stringSplitMatch(target string, targetLength int64, index uint, search stri
} }
func builtinString_split(call FunctionCall) Value { func builtinString_split(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
target := toString(call.This) target := call.This.string()
separatorValue := call.Argument(0) separatorValue := call.Argument(0)
limitValue := call.Argument(1) limitValue := call.Argument(1)
@ -343,7 +341,7 @@ func builtinString_split(call FunctionCall) Value {
captureCount := len(match) / 2 captureCount := len(match) / 2
for index := 1; index < captureCount; index++ { for index := 1; index < captureCount; index++ {
offset := index * 2 offset := index * 2
value := UndefinedValue() value := Value{}
if match[offset] != -1 { if match[offset] != -1 {
value = toValue_string(target[match[offset]:match[offset+1]]) value = toValue_string(target[match[offset]:match[offset+1]])
} }
@ -367,7 +365,7 @@ func builtinString_split(call FunctionCall) Value {
return toValue_object(call.runtime.newArrayOf(valueArray)) return toValue_object(call.runtime.newArrayOf(valueArray))
} else { } else {
separator := toString(separatorValue) separator := separatorValue.string()
splitLimit := limit splitLimit := limit
excess := false excess := false
@ -389,13 +387,11 @@ func builtinString_split(call FunctionCall) Value {
return toValue_object(call.runtime.newArrayOf(valueArray)) return toValue_object(call.runtime.newArrayOf(valueArray))
} }
return UndefinedValue()
} }
func builtinString_slice(call FunctionCall) Value { func builtinString_slice(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
target := toString(call.This) target := call.This.string()
length := int64(len(target)) length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, false) start, end := rangeStartEnd(call.ArgumentList, length, false)
@ -406,8 +402,8 @@ func builtinString_slice(call FunctionCall) Value {
} }
func builtinString_substring(call FunctionCall) Value { func builtinString_substring(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
target := toString(call.This) target := call.This.string()
length := int64(len(target)) length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, true) start, end := rangeStartEnd(call.ArgumentList, length, true)
@ -418,7 +414,7 @@ func builtinString_substring(call FunctionCall) Value {
} }
func builtinString_substr(call FunctionCall) Value { func builtinString_substr(call FunctionCall) Value {
target := toString(call.This) target := call.This.string()
size := int64(len(target)) size := int64(len(target))
start, length := rangeStartLength(call.ArgumentList, size) start, length := rangeStartLength(call.ArgumentList, size)
@ -443,42 +439,42 @@ func builtinString_substr(call FunctionCall) Value {
} }
func builtinString_toLowerCase(call FunctionCall) Value { func builtinString_toLowerCase(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
return toValue_string(strings.ToLower(toString(call.This))) return toValue_string(strings.ToLower(call.This.string()))
} }
func builtinString_toUpperCase(call FunctionCall) Value { func builtinString_toUpperCase(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
return toValue_string(strings.ToUpper(toString(call.This))) return toValue_string(strings.ToUpper(call.This.string()))
} }
// 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters // 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" 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 { func builtinString_trim(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
return toValue(strings.Trim(toString(call.This), return toValue(strings.Trim(call.This.string(),
builtinString_trim_whitespace)) builtinString_trim_whitespace))
} }
// Mozilla extension, not ECMAScript 5 // Mozilla extension, not ECMAScript 5
func builtinString_trimLeft(call FunctionCall) Value { func builtinString_trimLeft(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimLeft(toString(call.This), return toValue(strings.TrimLeft(call.This.string(),
builtinString_trim_whitespace)) builtinString_trim_whitespace))
} }
// Mozilla extension, not ECMAScript 5 // Mozilla extension, not ECMAScript 5
func builtinString_trimRight(call FunctionCall) Value { func builtinString_trimRight(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimRight(toString(call.This), return toValue(strings.TrimRight(call.This.string(),
builtinString_trim_whitespace)) builtinString_trim_whitespace))
} }
func builtinString_localeCompare(call FunctionCall) Value { func builtinString_localeCompare(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.runtime, call.This)
this := toString(call.This) this := call.This.string()
that := toString(call.Argument(0)) that := call.Argument(0).string()
if this < that { if this < that {
return toValue_int(-1) return toValue_int(-1)
} else if this == that { } else if this == that {
@ -491,7 +487,7 @@ func builtinString_localeCompare(call FunctionCall) Value {
An alternate version of String.trim An alternate version of String.trim
func builtinString_trim(call FunctionCall) Value { func builtinString_trim(call FunctionCall) Value {
checkObjectCoercible(call.This) checkObjectCoercible(call.This)
return toValue_string(strings.TrimFunc(toString(call.This), isWhiteSpaceOrLineTerminator)) return toValue_string(strings.TrimFunc(call.string(.This), isWhiteSpaceOrLineTerminator))
} }
*/ */

View File

@ -0,0 +1,155 @@
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{}
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
if value, valid := in.value.(Value); valid {
out.value = clone.value(value)
} else {
panic(fmt.Errorf("in.value.(Value) != true"))
}
return out
}
func (clone *_clone) dclProperty(in _dclProperty) _dclProperty {
out := in
out.value = clone.value(in.value)
return out
}

View File

@ -0,0 +1,24 @@
package otto
import (
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
)
type _file struct {
name string
src string
base int // This will always be 1 or greater
}
type _compiler struct {
file *file.File
program *ast.Program
}
func (cmpl *_compiler) parse() *_nodeProgram {
if cmpl.program != nil {
cmpl.file = cmpl.program.File
}
return cmpl._parse(cmpl.program)
}

View File

@ -4,13 +4,20 @@ import (
"strconv" "strconv"
) )
func (self *_runtime) cmpl_evaluate_nodeProgram(node *_nodeProgram) Value { 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_functionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList) self.cmpl_variableDeclaration(node.varList)
self.scope.frame.file = node.file
return self.cmpl_evaluate_nodeStatementList(node.body) return self.cmpl_evaluate_nodeStatementList(node.body)
} }
func (self *_runtime) cmpl_call_nodeFunction(function *_object, environment *_functionEnvironment, node *_nodeFunctionLiteral, this Value, argumentList []Value) Value { func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash, node *_nodeFunctionLiteral, this Value, argumentList []Value) Value {
indexOfParameterName := make([]string, len(argumentList)) indexOfParameterName := make([]string, len(argumentList))
// function(abc, def, ghi) // function(abc, def, ghi)
@ -24,19 +31,21 @@ func (self *_runtime) cmpl_call_nodeFunction(function *_object, environment *_fu
if name == "arguments" { if name == "arguments" {
argumentsFound = true argumentsFound = true
} }
value := UndefinedValue() value := Value{}
if index < len(argumentList) { if index < len(argumentList) {
value = argumentList[index] value = argumentList[index]
indexOfParameterName[index] = name indexOfParameterName[index] = name
} }
self.localSet(name, value) // strict = false
self.scope.lexical.setValue(name, value, false)
} }
if !argumentsFound { if !argumentsFound {
arguments := self.newArgumentsObject(indexOfParameterName, environment, len(argumentList)) arguments := self.newArgumentsObject(indexOfParameterName, stash, len(argumentList))
arguments.defineProperty("callee", toValue_object(function), 0101, false) arguments.defineProperty("callee", toValue_object(function), 0101, false)
environment.arguments = arguments stash.arguments = arguments
self.localSet("arguments", toValue_object(arguments)) // strict = false
self.scope.lexical.setValue("arguments", toValue_object(arguments), false)
for index, _ := range argumentList { for index, _ := range argumentList {
if index < len(node.parameterList) { if index < len(node.parameterList) {
continue continue
@ -50,38 +59,38 @@ func (self *_runtime) cmpl_call_nodeFunction(function *_object, environment *_fu
self.cmpl_variableDeclaration(node.varList) self.cmpl_variableDeclaration(node.varList)
result := self.cmpl_evaluate_nodeStatement(node.body) result := self.cmpl_evaluate_nodeStatement(node.body)
if result.isResult() { if result.kind == valueResult {
return result return result
} }
return UndefinedValue() return Value{}
} }
func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) { func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) {
executionContext := self._executionContext(0) executionContext := self.scope
eval := executionContext.eval eval := executionContext.eval
environment := executionContext.VariableEnvironment stash := executionContext.variable
for _, function := range list { for _, function := range list {
name := function.name name := function.name
value := self.cmpl_evaluate_nodeExpression(function) value := self.cmpl_evaluate_nodeExpression(function)
if !environment.HasBinding(name) { if !stash.hasBinding(name) {
environment.CreateMutableBinding(name, eval == true) stash.createBinding(name, eval == true, value)
} else {
// TODO 10.5.5.e
stash.setBinding(name, value, false) // TODO strict
} }
// TODO 10.5.5.e
environment.SetMutableBinding(name, value, false) // TODO strict
} }
} }
func (self *_runtime) cmpl_variableDeclaration(list []string) { func (self *_runtime) cmpl_variableDeclaration(list []string) {
executionContext := self._executionContext(0) executionContext := self.scope
eval := executionContext.eval eval := executionContext.eval
environment := executionContext.VariableEnvironment stash := executionContext.variable
for _, name := range list { for _, name := range list {
if !environment.HasBinding(name) { if !stash.hasBinding(name) {
environment.CreateMutableBinding(name, eval == true) stash.createBinding(name, eval == true, Value{}) // TODO strict?
environment.SetMutableBinding(name, UndefinedValue(), false) // TODO strict
} }
} }
} }

View File

@ -13,10 +13,10 @@ func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value {
// If the Interrupt channel is nil, then // If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any) // we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this // FIXME: Test this
if self.Otto.Interrupt != nil { if self.otto.Interrupt != nil {
runtime.Gosched() runtime.Gosched()
select { select {
case value := <-self.Otto.Interrupt: case value := <-self.otto.Interrupt:
value() value()
default: default:
} }
@ -50,15 +50,14 @@ func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value {
return self.cmpl_evaluate_nodeDotExpression(node) return self.cmpl_evaluate_nodeDotExpression(node)
case *_nodeFunctionLiteral: case *_nodeFunctionLiteral:
var local = self.LexicalEnvironment() var local = self.scope.lexical
if node.name != "" { if node.name != "" {
local = self.newDeclarativeEnvironment(local) local = self.newDeclarationStash(local)
} }
value := toValue_object(self.newNodeFunction(node, local)) value := toValue_object(self.newNodeFunction(node, local))
if node.name != "" { if node.name != "" {
local.CreateMutableBinding(node.name, false) local.createBinding(node.name, false, value)
local.SetMutableBinding(node.name, value, false)
} }
return value return value
@ -67,7 +66,7 @@ func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value {
// TODO Should be true or false (strictness) depending on context // TODO Should be true or false (strictness) depending on context
// getIdentifierReference should not return nil, but we check anyway and panic // getIdentifierReference should not return nil, but we check anyway and panic
// so as not to propagate the nil into something else // so as not to propagate the nil into something else
reference := getIdentifierReference(self.LexicalEnvironment(), name, false) reference := getIdentifierReference(self, self.scope.lexical, name, false, _at(node.idx))
if reference == nil { if reference == nil {
// Should never get here! // Should never get here!
panic(hereBeDragons("referenceError == nil: " + name)) panic(hereBeDragons("referenceError == nil: " + name))
@ -90,7 +89,7 @@ func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value {
return self.cmpl_evaluate_nodeSequenceExpression(node) return self.cmpl_evaluate_nodeSequenceExpression(node)
case *_nodeThisExpression: case *_nodeThisExpression:
return toValue_object(self._executionContext(0).this) return toValue_object(self.scope.this)
case *_nodeUnaryExpression: case *_nodeUnaryExpression:
return self.cmpl_evaluate_nodeUnaryExpression(node) return self.cmpl_evaluate_nodeUnaryExpression(node)
@ -108,9 +107,9 @@ func (self *_runtime) cmpl_evaluate_nodeArrayLiteral(node *_nodeArrayLiteral) Va
for _, node := range node.value { for _, node := range node.value {
if node == nil { if node == nil {
valueArray = append(valueArray, Value{}) valueArray = append(valueArray, emptyValue)
} else { } else {
valueArray = append(valueArray, self.GetValue(self.cmpl_evaluate_nodeExpression(node))) valueArray = append(valueArray, self.cmpl_evaluate_nodeExpression(node).resolve())
} }
} }
@ -123,14 +122,14 @@ func (self *_runtime) cmpl_evaluate_nodeAssignExpression(node *_nodeAssignExpres
left := self.cmpl_evaluate_nodeExpression(node.left) left := self.cmpl_evaluate_nodeExpression(node.left)
right := self.cmpl_evaluate_nodeExpression(node.right) right := self.cmpl_evaluate_nodeExpression(node.right)
rightValue := self.GetValue(right) rightValue := right.resolve()
result := rightValue result := rightValue
if node.operator != token.ASSIGN { if node.operator != token.ASSIGN {
result = self.calculateBinaryExpression(node.operator, left, rightValue) result = self.calculateBinaryExpression(node.operator, left, rightValue)
} }
self.PutValue(left.reference(), result) self.putValue(left.reference(), result)
return result return result
} }
@ -138,22 +137,22 @@ func (self *_runtime) cmpl_evaluate_nodeAssignExpression(node *_nodeAssignExpres
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpression) Value { func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left) left := self.cmpl_evaluate_nodeExpression(node.left)
leftValue := self.GetValue(left) leftValue := left.resolve()
switch node.operator { switch node.operator {
// Logical // Logical
case token.LOGICAL_AND: case token.LOGICAL_AND:
if !toBoolean(leftValue) { if !leftValue.bool() {
return leftValue return leftValue
} }
right := self.cmpl_evaluate_nodeExpression(node.right) right := self.cmpl_evaluate_nodeExpression(node.right)
return self.GetValue(right) return right.resolve()
case token.LOGICAL_OR: case token.LOGICAL_OR:
if toBoolean(leftValue) { if leftValue.bool() {
return leftValue return leftValue
} }
right := self.cmpl_evaluate_nodeExpression(node.right) right := self.cmpl_evaluate_nodeExpression(node.right)
return self.GetValue(right) return right.resolve()
} }
return self.calculateBinaryExpression(node.operator, leftValue, self.cmpl_evaluate_nodeExpression(node.right)) return self.calculateBinaryExpression(node.operator, leftValue, self.cmpl_evaluate_nodeExpression(node.right))
@ -161,57 +160,90 @@ func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpres
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression_comparison(node *_nodeBinaryExpression) Value { func (self *_runtime) cmpl_evaluate_nodeBinaryExpression_comparison(node *_nodeBinaryExpression) Value {
left := self.GetValue(self.cmpl_evaluate_nodeExpression(node.left)) left := self.cmpl_evaluate_nodeExpression(node.left).resolve()
right := self.GetValue(self.cmpl_evaluate_nodeExpression(node.right)) right := self.cmpl_evaluate_nodeExpression(node.right).resolve()
return toValue_bool(self.calculateComparison(node.operator, left, right)) return toValue_bool(self.calculateComparison(node.operator, left, right))
} }
func (self *_runtime) cmpl_evaluate_nodeBracketExpression(node *_nodeBracketExpression) Value { func (self *_runtime) cmpl_evaluate_nodeBracketExpression(node *_nodeBracketExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.left) target := self.cmpl_evaluate_nodeExpression(node.left)
targetValue := self.GetValue(target) targetValue := target.resolve()
member := self.cmpl_evaluate_nodeExpression(node.member) member := self.cmpl_evaluate_nodeExpression(node.member)
memberValue := self.GetValue(member) memberValue := member.resolve()
// TODO Pass in base value as-is, and defer toObject till later? // TODO Pass in base value as-is, and defer toObject till later?
return toValue(newPropertyReference(self.toObject(targetValue), toString(memberValue), false)) return toValue(newPropertyReference(self, self.toObject(targetValue), memberValue.string(), false, _at(node.idx)))
} }
func (self *_runtime) cmpl_evaluate_nodeCallExpression(node *_nodeCallExpression, withArgumentList []interface{}) Value { func (self *_runtime) cmpl_evaluate_nodeCallExpression(node *_nodeCallExpression, withArgumentList []interface{}) Value {
rt := self
this := Value{}
callee := self.cmpl_evaluate_nodeExpression(node.callee) callee := self.cmpl_evaluate_nodeExpression(node.callee)
calleeValue := self.GetValue(callee)
argumentList := []Value{} argumentList := []Value{}
if withArgumentList != nil { if withArgumentList != nil {
argumentList = self.toValueArray(withArgumentList...) argumentList = self.toValueArray(withArgumentList...)
} else { } else {
for _, argumentNode := range node.argumentList { for _, argumentNode := range node.argumentList {
argumentList = append(argumentList, self.GetValue(self.cmpl_evaluate_nodeExpression(argumentNode))) argumentList = append(argumentList, self.cmpl_evaluate_nodeExpression(argumentNode).resolve())
} }
} }
this := UndefinedValue()
calleeReference := callee.reference() rf := callee.reference()
evalHint := false vl := callee.resolve()
if calleeReference != nil {
if calleeReference.IsPropertyReference() { eval := false // Whether this call is a (candidate for) direct call to eval
calleeObject := calleeReference.GetBase().(*_object) name := ""
this = toValue_object(calleeObject) if rf != nil {
} else { switch rf := rf.(type) {
// TODO ImplictThisValue case *_propertyReference:
} name = rf.name
if calleeReference.GetName() == "eval" { object := rf.base
evalHint = true // Possible direct eval 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"))
} }
} }
if !calleeValue.IsFunction() {
panic(newTypeError("%v is not a function", calleeValue)) 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)
} }
return self.Call(calleeValue._object(), this, argumentList, evalHint)
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 { func (self *_runtime) cmpl_evaluate_nodeConditionalExpression(node *_nodeConditionalExpression) Value {
test := self.cmpl_evaluate_nodeExpression(node.test) test := self.cmpl_evaluate_nodeExpression(node.test)
testValue := self.GetValue(test) testValue := test.resolve()
if toBoolean(testValue) { if testValue.bool() {
return self.cmpl_evaluate_nodeExpression(node.consequent) return self.cmpl_evaluate_nodeExpression(node.consequent)
} }
return self.cmpl_evaluate_nodeExpression(node.alternate) return self.cmpl_evaluate_nodeExpression(node.alternate)
@ -219,27 +251,60 @@ func (self *_runtime) cmpl_evaluate_nodeConditionalExpression(node *_nodeConditi
func (self *_runtime) cmpl_evaluate_nodeDotExpression(node *_nodeDotExpression) Value { func (self *_runtime) cmpl_evaluate_nodeDotExpression(node *_nodeDotExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.left) target := self.cmpl_evaluate_nodeExpression(node.left)
targetValue := self.GetValue(target) targetValue := target.resolve()
// TODO Pass in base value as-is, and defer toObject till later? // TODO Pass in base value as-is, and defer toObject till later?
object, err := self.objectCoerce(targetValue) object, err := self.objectCoerce(targetValue)
if err != nil { if err != nil {
panic(newTypeError(fmt.Sprintf("Cannot access member '%s' of %s", node.identifier, err.Error()))) panic(self.panicTypeError("Cannot access member '%s' of %s", node.identifier, err.Error()))
} }
return toValue(newPropertyReference(object, node.identifier, false)) return toValue(newPropertyReference(self, object, node.identifier, false, _at(node.idx)))
} }
func (self *_runtime) cmpl_evaluate_nodeNewExpression(node *_nodeNewExpression) Value { func (self *_runtime) cmpl_evaluate_nodeNewExpression(node *_nodeNewExpression) Value {
rt := self
callee := self.cmpl_evaluate_nodeExpression(node.callee) callee := self.cmpl_evaluate_nodeExpression(node.callee)
calleeValue := self.GetValue(callee)
argumentList := []Value{} argumentList := []Value{}
for _, argumentNode := range node.argumentList { for _, argumentNode := range node.argumentList {
argumentList = append(argumentList, self.GetValue(self.cmpl_evaluate_nodeExpression(argumentNode))) argumentList = append(argumentList, self.cmpl_evaluate_nodeExpression(argumentNode).resolve())
} }
this := UndefinedValue()
if !calleeValue.IsFunction() { rf := callee.reference()
panic(newTypeError("%v is not a function", calleeValue)) 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"))
}
} }
return calleeValue._object().Construct(this, argumentList)
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 { func (self *_runtime) cmpl_evaluate_nodeObjectLiteral(node *_nodeObjectLiteral) Value {
@ -249,15 +314,15 @@ func (self *_runtime) cmpl_evaluate_nodeObjectLiteral(node *_nodeObjectLiteral)
for _, property := range node.value { for _, property := range node.value {
switch property.kind { switch property.kind {
case "value": case "value":
result.defineProperty(property.key, self.GetValue(self.cmpl_evaluate_nodeExpression(property.value)), 0111, false) result.defineProperty(property.key, self.cmpl_evaluate_nodeExpression(property.value).resolve(), 0111, false)
case "get": case "get":
getter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.LexicalEnvironment()) getter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical)
descriptor := _property{} descriptor := _property{}
descriptor.mode = 0211 descriptor.mode = 0211
descriptor.value = _propertyGetSet{getter, nil} descriptor.value = _propertyGetSet{getter, nil}
result.defineOwnProperty(property.key, descriptor, false) result.defineOwnProperty(property.key, descriptor, false)
case "set": case "set":
setter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.LexicalEnvironment()) setter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical)
descriptor := _property{} descriptor := _property{}
descriptor.mode = 0211 descriptor.mode = 0211
descriptor.value = _propertyGetSet{nil, setter} descriptor.value = _propertyGetSet{nil, setter}
@ -274,7 +339,7 @@ func (self *_runtime) cmpl_evaluate_nodeSequenceExpression(node *_nodeSequenceEx
var result Value var result Value
for _, node := range node.sequence { for _, node := range node.sequence {
result = self.cmpl_evaluate_nodeExpression(node) result = self.cmpl_evaluate_nodeExpression(node)
result = self.GetValue(result) result = result.resolve()
} }
return result return result
} }
@ -284,31 +349,31 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
target := self.cmpl_evaluate_nodeExpression(node.operand) target := self.cmpl_evaluate_nodeExpression(node.operand)
switch node.operator { switch node.operator {
case token.TYPEOF, token.DELETE: case token.TYPEOF, token.DELETE:
if target._valueType == valueReference && target.reference().IsUnresolvable() { if target.kind == valueReference && target.reference().invalid() {
if node.operator == token.TYPEOF { if node.operator == token.TYPEOF {
return toValue_string("undefined") return toValue_string("undefined")
} }
return TrueValue() return trueValue
} }
} }
switch node.operator { switch node.operator {
case token.NOT: case token.NOT:
targetValue := self.GetValue(target) targetValue := target.resolve()
if targetValue.toBoolean() { if targetValue.bool() {
return FalseValue() return falseValue
} }
return TrueValue() return trueValue
case token.BITWISE_NOT: case token.BITWISE_NOT:
targetValue := self.GetValue(target) targetValue := target.resolve()
integerValue := toInt32(targetValue) integerValue := toInt32(targetValue)
return toValue_int32(^integerValue) return toValue_int32(^integerValue)
case token.PLUS: case token.PLUS:
targetValue := self.GetValue(target) targetValue := target.resolve()
return toValue_float64(targetValue.toFloat()) return toValue_float64(targetValue.float64())
case token.MINUS: case token.MINUS:
targetValue := self.GetValue(target) targetValue := target.resolve()
value := targetValue.toFloat() value := targetValue.float64()
// TODO Test this // TODO Test this
sign := float64(-1) sign := float64(-1)
if math.Signbit(value) { if math.Signbit(value) {
@ -316,45 +381,45 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
} }
return toValue_float64(math.Copysign(value, sign)) return toValue_float64(math.Copysign(value, sign))
case token.INCREMENT: case token.INCREMENT:
targetValue := self.GetValue(target) targetValue := target.resolve()
if node.postfix { if node.postfix {
// Postfix++ // Postfix++
oldValue := targetValue.toFloat() oldValue := targetValue.float64()
newValue := toValue_float64(+1 + oldValue) newValue := toValue_float64(+1 + oldValue)
self.PutValue(target.reference(), newValue) self.putValue(target.reference(), newValue)
return toValue_float64(oldValue) return toValue_float64(oldValue)
} else { } else {
// ++Prefix // ++Prefix
newValue := toValue_float64(+1 + targetValue.toFloat()) newValue := toValue_float64(+1 + targetValue.float64())
self.PutValue(target.reference(), newValue) self.putValue(target.reference(), newValue)
return newValue return newValue
} }
case token.DECREMENT: case token.DECREMENT:
targetValue := self.GetValue(target) targetValue := target.resolve()
if node.postfix { if node.postfix {
// Postfix-- // Postfix--
oldValue := targetValue.toFloat() oldValue := targetValue.float64()
newValue := toValue_float64(-1 + oldValue) newValue := toValue_float64(-1 + oldValue)
self.PutValue(target.reference(), newValue) self.putValue(target.reference(), newValue)
return toValue_float64(oldValue) return toValue_float64(oldValue)
} else { } else {
// --Prefix // --Prefix
newValue := toValue_float64(-1 + targetValue.toFloat()) newValue := toValue_float64(-1 + targetValue.float64())
self.PutValue(target.reference(), newValue) self.putValue(target.reference(), newValue)
return newValue return newValue
} }
case token.VOID: case token.VOID:
self.GetValue(target) // FIXME Side effect? target.resolve() // FIXME Side effect?
return UndefinedValue() return Value{}
case token.DELETE: case token.DELETE:
reference := target.reference() reference := target.reference()
if reference == nil { if reference == nil {
return TrueValue() return trueValue
} }
return toValue_bool(target.reference().Delete()) return toValue_bool(target.reference().delete())
case token.TYPEOF: case token.TYPEOF:
targetValue := self.GetValue(target) targetValue := target.resolve()
switch targetValue._valueType { switch targetValue.kind {
case valueUndefined: case valueUndefined:
return toValue_string("undefined") return toValue_string("undefined")
case valueNull: case valueNull:
@ -366,7 +431,7 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
case valueString: case valueString:
return toValue_string("string") return toValue_string("string")
case valueObject: case valueObject:
if targetValue._object().functionValue().call != nil { if targetValue._object().isCall() {
return toValue_string("function") return toValue_string("function")
} }
return toValue_string("object") return toValue_string("object")
@ -381,11 +446,11 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
func (self *_runtime) cmpl_evaluate_nodeVariableExpression(node *_nodeVariableExpression) Value { func (self *_runtime) cmpl_evaluate_nodeVariableExpression(node *_nodeVariableExpression) Value {
if node.initializer != nil { if node.initializer != nil {
// FIXME If reference is nil // FIXME If reference is nil
left := getIdentifierReference(self.LexicalEnvironment(), node.name, false) left := getIdentifierReference(self, self.scope.lexical, node.name, false, _at(node.idx))
right := self.cmpl_evaluate_nodeExpression(node.initializer) right := self.cmpl_evaluate_nodeExpression(node.initializer)
rightValue := self.GetValue(right) rightValue := right.resolve()
self.PutValue(left, rightValue) self.putValue(left, rightValue)
} }
return toValue_string(node.name) return toValue_string(node.name)
} }

View File

@ -12,10 +12,10 @@ func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
// If the Interrupt channel is nil, then // If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any) // we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this // FIXME: Test this
if self.Otto.Interrupt != nil { if self.otto.Interrupt != nil {
runtime.Gosched() runtime.Gosched()
select { select {
case value := <-self.Otto.Interrupt: case value := <-self.otto.Interrupt:
value() value()
default: default:
} }
@ -24,8 +24,18 @@ func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
switch node := node.(type) { switch node := node.(type) {
case *_nodeBlockStatement: case *_nodeBlockStatement:
// FIXME If result is break, then return the empty value? labels := self.labels
return self.cmpl_evaluate_nodeStatementList(node.list) 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: case *_nodeBranchStatement:
target := node.label target := node.label
@ -37,13 +47,13 @@ func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
} }
case *_nodeDebuggerStatement: case *_nodeDebuggerStatement:
return Value{} // Nothing happens. return emptyValue // Nothing happens.
case *_nodeDoWhileStatement: case *_nodeDoWhileStatement:
return self.cmpl_evaluate_nodeDoWhileStatement(node) return self.cmpl_evaluate_nodeDoWhileStatement(node)
case *_nodeEmptyStatement: case *_nodeEmptyStatement:
return Value{} return emptyValue
case *_nodeExpressionStatement: case *_nodeExpressionStatement:
return self.cmpl_evaluate_nodeExpression(node.expression) return self.cmpl_evaluate_nodeExpression(node.expression)
@ -70,15 +80,15 @@ func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
case *_nodeReturnStatement: case *_nodeReturnStatement:
if node.argument != nil { if node.argument != nil {
return toValue(newReturnResult(self.GetValue(self.cmpl_evaluate_nodeExpression(node.argument)))) return toValue(newReturnResult(self.cmpl_evaluate_nodeExpression(node.argument).resolve()))
} }
return toValue(newReturnResult(UndefinedValue())) return toValue(newReturnResult(Value{}))
case *_nodeSwitchStatement: case *_nodeSwitchStatement:
return self.cmpl_evaluate_nodeSwitchStatement(node) return self.cmpl_evaluate_nodeSwitchStatement(node)
case *_nodeThrowStatement: case *_nodeThrowStatement:
value := self.GetValue(self.cmpl_evaluate_nodeExpression(node.argument)) value := self.cmpl_evaluate_nodeExpression(node.argument).resolve()
panic(newException(value)) panic(newException(value))
case *_nodeTryStatement: case *_nodeTryStatement:
@ -89,7 +99,7 @@ func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
for _, variable := range node.list { for _, variable := range node.list {
self.cmpl_evaluate_nodeVariableExpression(variable.(*_nodeVariableExpression)) self.cmpl_evaluate_nodeVariableExpression(variable.(*_nodeVariableExpression))
} }
return Value{} return emptyValue
case *_nodeWhileStatement: case *_nodeWhileStatement:
return self.cmpl_evaluate_nodeWhileStatement(node) return self.cmpl_evaluate_nodeWhileStatement(node)
@ -106,17 +116,17 @@ func (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Val
var result Value var result Value
for _, node := range list { for _, node := range list {
value := self.cmpl_evaluate_nodeStatement(node) value := self.cmpl_evaluate_nodeStatement(node)
switch value._valueType { switch value.kind {
case valueResult: case valueResult:
return value return value
case valueEmpty: case valueEmpty:
default: default:
// We have GetValue here to (for example) trigger a // We have getValue here to (for example) trigger a
// ReferenceError (of the not defined variety) // ReferenceError (of the not defined variety)
// Not sure if this is the best way to error out early // Not sure if this is the best way to error out early
// for such errors or if there is a better way // for such errors or if there is a better way
// TODO Do we still need this? // TODO Do we still need this?
result = self.GetValue(value) result = value.resolve()
} }
} }
return result return result
@ -129,12 +139,12 @@ func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileState
test := node.test test := node.test
result := Value{} result := emptyValue
resultBreak: resultBreak:
for { for {
for _, node := range node.body { for _, node := range node.body {
value := self.cmpl_evaluate_nodeStatement(node) value := self.cmpl_evaluate_nodeStatement(node)
switch value._valueType { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
case resultReturn: case resultReturn:
@ -150,7 +160,7 @@ resultBreak:
} }
} }
resultContinue: resultContinue:
if !self.GetValue(self.cmpl_evaluate_nodeExpression(test)).isTrue() { if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() {
// Stahp: do ... while (false) // Stahp: do ... while (false)
break break
} }
@ -164,11 +174,11 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
self.labels = nil self.labels = nil
source := self.cmpl_evaluate_nodeExpression(node.source) source := self.cmpl_evaluate_nodeExpression(node.source)
sourceValue := self.GetValue(source) sourceValue := source.resolve()
switch sourceValue._valueType { switch sourceValue.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
return emptyValue() return emptyValue
} }
sourceObject := self.toObject(sourceValue) sourceObject := self.toObject(sourceValue)
@ -176,22 +186,22 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
into := node.into into := node.into
body := node.body body := node.body
result := Value{} result := emptyValue
object := sourceObject object := sourceObject
for object != nil { for object != nil {
enumerateValue := Value{} enumerateValue := emptyValue
object.enumerate(false, func(name string) bool { object.enumerate(false, func(name string) bool {
into := self.cmpl_evaluate_nodeExpression(into) into := self.cmpl_evaluate_nodeExpression(into)
// In the case of: for (var abc in def) ... // In the case of: for (var abc in def) ...
if into.reference() == nil { if into.reference() == nil {
identifier := toString(into) identifier := into.string()
// TODO Should be true or false (strictness) depending on context // TODO Should be true or false (strictness) depending on context
into = toValue(getIdentifierReference(self.LexicalEnvironment(), identifier, false)) into = toValue(getIdentifierReference(self, self.scope.lexical, identifier, false, -1))
} }
self.PutValue(into.reference(), toValue_string(name)) self.putValue(into.reference(), toValue_string(name))
for _, node := range body { for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node) value := self.cmpl_evaluate_nodeStatement(node)
switch value._valueType { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
case resultReturn: case resultReturn:
@ -233,22 +243,22 @@ func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Va
if initializer != nil { if initializer != nil {
initialResult := self.cmpl_evaluate_nodeExpression(initializer) initialResult := self.cmpl_evaluate_nodeExpression(initializer)
self.GetValue(initialResult) // Side-effect trigger initialResult.resolve() // Side-effect trigger
} }
result := Value{} result := emptyValue
resultBreak: resultBreak:
for { for {
if test != nil { if test != nil {
testResult := self.cmpl_evaluate_nodeExpression(test) testResult := self.cmpl_evaluate_nodeExpression(test)
testResultValue := self.GetValue(testResult) testResultValue := testResult.resolve()
if toBoolean(testResultValue) == false { if testResultValue.bool() == false {
break break
} }
} }
for _, node := range body { for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node) value := self.cmpl_evaluate_nodeStatement(node)
switch value._valueType { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
case resultReturn: case resultReturn:
@ -266,7 +276,7 @@ resultBreak:
resultContinue: resultContinue:
if update != nil { if update != nil {
updateResult := self.cmpl_evaluate_nodeExpression(update) updateResult := self.cmpl_evaluate_nodeExpression(update)
self.GetValue(updateResult) // Side-effect trigger updateResult.resolve() // Side-effect trigger
} }
} }
return result return result
@ -274,14 +284,14 @@ resultBreak:
func (self *_runtime) cmpl_evaluate_nodeIfStatement(node *_nodeIfStatement) Value { func (self *_runtime) cmpl_evaluate_nodeIfStatement(node *_nodeIfStatement) Value {
test := self.cmpl_evaluate_nodeExpression(node.test) test := self.cmpl_evaluate_nodeExpression(node.test)
testValue := self.GetValue(test) testValue := test.resolve()
if toBoolean(testValue) { if testValue.bool() {
return self.cmpl_evaluate_nodeStatement(node.consequent) return self.cmpl_evaluate_nodeStatement(node.consequent)
} else if node.alternate != nil { } else if node.alternate != nil {
return self.cmpl_evaluate_nodeStatement(node.alternate) return self.cmpl_evaluate_nodeStatement(node.alternate)
} }
return Value{} return emptyValue
} }
func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStatement) Value { func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStatement) Value {
@ -302,18 +312,18 @@ func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStateme
} }
} }
result := Value{} result := emptyValue
if target != -1 { if target != -1 {
for _, clause := range node.body[target:] { for _, clause := range node.body[target:] {
for _, statement := range clause.consequent { for _, statement := range clause.consequent {
value := self.cmpl_evaluate_nodeStatement(statement) value := self.cmpl_evaluate_nodeStatement(statement)
switch value._valueType { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreak(labels) { switch value.evaluateBreak(labels) {
case resultReturn: case resultReturn:
return value return value
case resultBreak: case resultBreak:
return Value{} return emptyValue
} }
case valueEmpty: case valueEmpty:
default: default:
@ -332,14 +342,15 @@ func (self *_runtime) cmpl_evaluate_nodeTryStatement(node *_nodeTryStatement) Va
}) })
if exception && node.catch != nil { if exception && node.catch != nil {
outer := self.scope.lexical
lexicalEnvironment := self._executionContext(0).newDeclarativeEnvironment(self) self.scope.lexical = self.newDeclarationStash(outer)
defer func() { defer func() {
self._executionContext(0).LexicalEnvironment = lexicalEnvironment self.scope.lexical = outer
}() }()
// TODO If necessary, convert TypeError<runtime> => TypeError // TODO If necessary, convert TypeError<runtime> => TypeError
// That, is, such errors can be thrown despite not being JavaScript "native" // That, is, such errors can be thrown despite not being JavaScript "native"
self.localSet(node.catch.parameter, tryCatchValue) // strict = false
self.scope.lexical.setValue(node.catch.parameter, tryCatchValue, false)
// FIXME node.CatchParameter // FIXME node.CatchParameter
// FIXME node.Catch // FIXME node.Catch
@ -350,7 +361,7 @@ func (self *_runtime) cmpl_evaluate_nodeTryStatement(node *_nodeTryStatement) Va
if node.finally != nil { if node.finally != nil {
finallyValue := self.cmpl_evaluate_nodeStatement(node.finally) finallyValue := self.cmpl_evaluate_nodeStatement(node.finally)
if finallyValue.isResult() { if finallyValue.kind == valueResult {
return finallyValue return finallyValue
} }
} }
@ -369,16 +380,16 @@ func (self *_runtime) cmpl_evaluate_nodeWhileStatement(node *_nodeWhileStatement
labels := append(self.labels, "") labels := append(self.labels, "")
self.labels = nil self.labels = nil
result := Value{} result := emptyValue
resultBreakContinue: resultBreakContinue:
for { for {
if !self.GetValue(self.cmpl_evaluate_nodeExpression(test)).isTrue() { if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() {
// Stahp: while (false) ... // Stahp: while (false) ...
break break
} }
for _, node := range body { for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node) value := self.cmpl_evaluate_nodeStatement(node)
switch value._valueType { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
case resultReturn: case resultReturn:
@ -399,11 +410,11 @@ resultBreakContinue:
func (self *_runtime) cmpl_evaluate_nodeWithStatement(node *_nodeWithStatement) Value { func (self *_runtime) cmpl_evaluate_nodeWithStatement(node *_nodeWithStatement) Value {
object := self.cmpl_evaluate_nodeExpression(node.object) object := self.cmpl_evaluate_nodeExpression(node.object)
objectValue := self.GetValue(object) outer := self.scope.lexical
previousLexicalEnvironment, lexicalEnvironment := self._executionContext(0).newLexicalEnvironment(self.toObject(objectValue)) lexical := self.newObjectStash(self.toObject(object.resolve()), outer)
lexicalEnvironment.ProvideThis = true self.scope.lexical = lexical
defer func() { defer func() {
self._executionContext(0).LexicalEnvironment = previousLexicalEnvironment self.scope.lexical = outer
}() }()
return self.cmpl_evaluate_nodeStatement(node.body) return self.cmpl_evaluate_nodeStatement(node.body)

View File

@ -5,165 +5,170 @@ import (
"regexp" "regexp"
"github.com/robertkrimen/otto/ast" "github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token" "github.com/robertkrimen/otto/token"
) )
var trueLiteral = &_nodeLiteral{value: toValue_bool(true)} var trueLiteral = &_nodeLiteral{value: toValue_bool(true)}
var falseLiteral = &_nodeLiteral{value: toValue_bool(false)} var falseLiteral = &_nodeLiteral{value: toValue_bool(false)}
var nullLiteral = &_nodeLiteral{value: NullValue()} var nullLiteral = &_nodeLiteral{value: nullValue}
var emptyStatement = &_nodeEmptyStatement{} var emptyStatement = &_nodeEmptyStatement{}
func parseExpression(x ast.Expression) _nodeExpression { func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
if x == nil { if in == nil {
return nil return nil
} }
switch x := x.(type) { switch in := in.(type) {
case *ast.ArrayLiteral: case *ast.ArrayLiteral:
y := &_nodeArrayLiteral{ out := &_nodeArrayLiteral{
value: make([]_nodeExpression, len(x.Value)), value: make([]_nodeExpression, len(in.Value)),
} }
for i, value := range x.Value { for i, value := range in.Value {
y.value[i] = parseExpression(value) out.value[i] = cmpl.parseExpression(value)
} }
return y return out
case *ast.AssignExpression: case *ast.AssignExpression:
return &_nodeAssignExpression{ return &_nodeAssignExpression{
operator: x.Operator, operator: in.Operator,
left: parseExpression(x.Left), left: cmpl.parseExpression(in.Left),
right: parseExpression(x.Right), right: cmpl.parseExpression(in.Right),
} }
case *ast.BinaryExpression: case *ast.BinaryExpression:
return &_nodeBinaryExpression{ return &_nodeBinaryExpression{
operator: x.Operator, operator: in.Operator,
left: parseExpression(x.Left), left: cmpl.parseExpression(in.Left),
right: parseExpression(x.Right), right: cmpl.parseExpression(in.Right),
comparison: x.Comparison, comparison: in.Comparison,
} }
case *ast.BooleanLiteral: case *ast.BooleanLiteral:
if x.Value { if in.Value {
return trueLiteral return trueLiteral
} }
return falseLiteral return falseLiteral
case *ast.BracketExpression: case *ast.BracketExpression:
return &_nodeBracketExpression{ return &_nodeBracketExpression{
left: parseExpression(x.Left), idx: in.Left.Idx0(),
member: parseExpression(x.Member), left: cmpl.parseExpression(in.Left),
member: cmpl.parseExpression(in.Member),
} }
case *ast.CallExpression: case *ast.CallExpression:
y := &_nodeCallExpression{ out := &_nodeCallExpression{
callee: parseExpression(x.Callee), callee: cmpl.parseExpression(in.Callee),
argumentList: make([]_nodeExpression, len(x.ArgumentList)), argumentList: make([]_nodeExpression, len(in.ArgumentList)),
} }
for i, value := range x.ArgumentList { for i, value := range in.ArgumentList {
y.argumentList[i] = parseExpression(value) out.argumentList[i] = cmpl.parseExpression(value)
} }
return y return out
case *ast.ConditionalExpression: case *ast.ConditionalExpression:
return &_nodeConditionalExpression{ return &_nodeConditionalExpression{
test: parseExpression(x.Test), test: cmpl.parseExpression(in.Test),
consequent: parseExpression(x.Consequent), consequent: cmpl.parseExpression(in.Consequent),
alternate: parseExpression(x.Alternate), alternate: cmpl.parseExpression(in.Alternate),
} }
case *ast.DotExpression: case *ast.DotExpression:
return &_nodeDotExpression{ return &_nodeDotExpression{
left: parseExpression(x.Left), idx: in.Left.Idx0(),
identifier: x.Identifier.Name, left: cmpl.parseExpression(in.Left),
identifier: in.Identifier.Name,
} }
case *ast.FunctionLiteral: case *ast.FunctionLiteral:
name := "" name := ""
if x.Name != nil { if in.Name != nil {
name = x.Name.Name name = in.Name.Name
} }
y := &_nodeFunctionLiteral{ out := &_nodeFunctionLiteral{
name: name, name: name,
body: parseStatement(x.Body), body: cmpl.parseStatement(in.Body),
source: x.Source, source: in.Source,
file: cmpl.file,
} }
if x.ParameterList != nil { if in.ParameterList != nil {
list := x.ParameterList.List list := in.ParameterList.List
y.parameterList = make([]string, len(list)) out.parameterList = make([]string, len(list))
for i, value := range list { for i, value := range list {
y.parameterList[i] = value.Name out.parameterList[i] = value.Name
} }
} }
for _, value := range x.DeclarationList { for _, value := range in.DeclarationList {
switch value := value.(type) { switch value := value.(type) {
case *ast.FunctionDeclaration: case *ast.FunctionDeclaration:
y.functionList = append(y.functionList, parseExpression(value.Function).(*_nodeFunctionLiteral)) out.functionList = append(out.functionList, cmpl.parseExpression(value.Function).(*_nodeFunctionLiteral))
case *ast.VariableDeclaration: case *ast.VariableDeclaration:
for _, value := range value.List { for _, value := range value.List {
y.varList = append(y.varList, value.Name) out.varList = append(out.varList, value.Name)
} }
default: default:
panic(fmt.Errorf("Here be dragons: parseProgram.declaration(%T)", value)) panic(fmt.Errorf("Here be dragons: parseProgram.declaration(%T)", value))
} }
} }
return y return out
case *ast.Identifier: case *ast.Identifier:
return &_nodeIdentifier{ return &_nodeIdentifier{
name: x.Name, idx: in.Idx,
name: in.Name,
} }
case *ast.NewExpression: case *ast.NewExpression:
y := &_nodeNewExpression{ out := &_nodeNewExpression{
callee: parseExpression(x.Callee), callee: cmpl.parseExpression(in.Callee),
argumentList: make([]_nodeExpression, len(x.ArgumentList)), argumentList: make([]_nodeExpression, len(in.ArgumentList)),
} }
for i, value := range x.ArgumentList { for i, value := range in.ArgumentList {
y.argumentList[i] = parseExpression(value) out.argumentList[i] = cmpl.parseExpression(value)
} }
return y return out
case *ast.NullLiteral: case *ast.NullLiteral:
return nullLiteral return nullLiteral
case *ast.NumberLiteral: case *ast.NumberLiteral:
return &_nodeLiteral{ return &_nodeLiteral{
value: toValue(x.Value), value: toValue(in.Value),
} }
case *ast.ObjectLiteral: case *ast.ObjectLiteral:
y := &_nodeObjectLiteral{ out := &_nodeObjectLiteral{
value: make([]_nodeProperty, len(x.Value)), value: make([]_nodeProperty, len(in.Value)),
} }
for i, value := range x.Value { for i, value := range in.Value {
y.value[i] = _nodeProperty{ out.value[i] = _nodeProperty{
key: value.Key, key: value.Key,
kind: value.Kind, kind: value.Kind,
value: parseExpression(value.Value), value: cmpl.parseExpression(value.Value),
} }
} }
return y return out
case *ast.RegExpLiteral: case *ast.RegExpLiteral:
return &_nodeRegExpLiteral{ return &_nodeRegExpLiteral{
flags: x.Flags, flags: in.Flags,
pattern: x.Pattern, pattern: in.Pattern,
} }
case *ast.SequenceExpression: case *ast.SequenceExpression:
y := &_nodeSequenceExpression{ out := &_nodeSequenceExpression{
sequence: make([]_nodeExpression, len(x.Sequence)), sequence: make([]_nodeExpression, len(in.Sequence)),
} }
for i, value := range x.Sequence { for i, value := range in.Sequence {
y.sequence[i] = parseExpression(value) out.sequence[i] = cmpl.parseExpression(value)
} }
return y return out
case *ast.StringLiteral: case *ast.StringLiteral:
return &_nodeLiteral{ return &_nodeLiteral{
value: toValue_string(x.Value), value: toValue_string(in.Value),
} }
case *ast.ThisExpression: case *ast.ThisExpression:
@ -171,203 +176,211 @@ func parseExpression(x ast.Expression) _nodeExpression {
case *ast.UnaryExpression: case *ast.UnaryExpression:
return &_nodeUnaryExpression{ return &_nodeUnaryExpression{
operator: x.Operator, operator: in.Operator,
operand: parseExpression(x.Operand), operand: cmpl.parseExpression(in.Operand),
postfix: x.Postfix, postfix: in.Postfix,
} }
case *ast.VariableExpression: case *ast.VariableExpression:
return &_nodeVariableExpression{ return &_nodeVariableExpression{
name: x.Name, idx: in.Idx0(),
initializer: parseExpression(x.Initializer), name: in.Name,
initializer: cmpl.parseExpression(in.Initializer),
} }
} }
panic(fmt.Errorf("Here be dragons: parseExpression(%T)", x)) panic(fmt.Errorf("Here be dragons: cmpl.parseExpression(%T)", in))
} }
func parseStatement(x ast.Statement) _nodeStatement { func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
if x == nil { if in == nil {
return nil return nil
} }
switch x := x.(type) { switch in := in.(type) {
case *ast.BlockStatement: case *ast.BlockStatement:
y := &_nodeBlockStatement{ out := &_nodeBlockStatement{
list: make([]_nodeStatement, len(x.List)), list: make([]_nodeStatement, len(in.List)),
} }
for i, value := range x.List { for i, value := range in.List {
y.list[i] = parseStatement(value) out.list[i] = cmpl.parseStatement(value)
} }
return y return out
case *ast.BranchStatement: case *ast.BranchStatement:
y := &_nodeBranchStatement{ out := &_nodeBranchStatement{
branch: x.Token, branch: in.Token,
} }
if x.Label != nil { if in.Label != nil {
y.label = x.Label.Name out.label = in.Label.Name
} }
return y return out
case *ast.DebuggerStatement: case *ast.DebuggerStatement:
return &_nodeDebuggerStatement{} return &_nodeDebuggerStatement{}
case *ast.DoWhileStatement: case *ast.DoWhileStatement:
y := &_nodeDoWhileStatement{ out := &_nodeDoWhileStatement{
test: parseExpression(x.Test), test: cmpl.parseExpression(in.Test),
} }
body := parseStatement(x.Body) body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*_nodeBlockStatement); ok {
y.body = block.list out.body = block.list
} else { } else {
y.body = append(y.body, body) out.body = append(out.body, body)
} }
return y return out
case *ast.EmptyStatement: case *ast.EmptyStatement:
return emptyStatement return emptyStatement
case *ast.ExpressionStatement: case *ast.ExpressionStatement:
return &_nodeExpressionStatement{ return &_nodeExpressionStatement{
expression: parseExpression(x.Expression), expression: cmpl.parseExpression(in.Expression),
} }
case *ast.ForInStatement: case *ast.ForInStatement:
y := &_nodeForInStatement{ out := &_nodeForInStatement{
into: parseExpression(x.Into), into: cmpl.parseExpression(in.Into),
source: parseExpression(x.Source), source: cmpl.parseExpression(in.Source),
} }
body := parseStatement(x.Body) body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*_nodeBlockStatement); ok {
y.body = block.list out.body = block.list
} else { } else {
y.body = append(y.body, body) out.body = append(out.body, body)
} }
return y return out
case *ast.ForStatement: case *ast.ForStatement:
y := &_nodeForStatement{ out := &_nodeForStatement{
initializer: parseExpression(x.Initializer), initializer: cmpl.parseExpression(in.Initializer),
update: parseExpression(x.Update), update: cmpl.parseExpression(in.Update),
test: parseExpression(x.Test), test: cmpl.parseExpression(in.Test),
} }
body := parseStatement(x.Body) body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*_nodeBlockStatement); ok {
y.body = block.list out.body = block.list
} else { } else {
y.body = append(y.body, body) out.body = append(out.body, body)
} }
return y return out
case *ast.IfStatement: case *ast.IfStatement:
return &_nodeIfStatement{ return &_nodeIfStatement{
test: parseExpression(x.Test), test: cmpl.parseExpression(in.Test),
consequent: parseStatement(x.Consequent), consequent: cmpl.parseStatement(in.Consequent),
alternate: parseStatement(x.Alternate), alternate: cmpl.parseStatement(in.Alternate),
} }
case *ast.LabelledStatement: case *ast.LabelledStatement:
return &_nodeLabelledStatement{ return &_nodeLabelledStatement{
label: x.Label.Name, label: in.Label.Name,
statement: parseStatement(x.Statement), statement: cmpl.parseStatement(in.Statement),
} }
case *ast.ReturnStatement: case *ast.ReturnStatement:
return &_nodeReturnStatement{ return &_nodeReturnStatement{
argument: parseExpression(x.Argument), argument: cmpl.parseExpression(in.Argument),
} }
case *ast.SwitchStatement: case *ast.SwitchStatement:
y := &_nodeSwitchStatement{ out := &_nodeSwitchStatement{
discriminant: parseExpression(x.Discriminant), discriminant: cmpl.parseExpression(in.Discriminant),
default_: x.Default, default_: in.Default,
body: make([]*_nodeCaseStatement, len(x.Body)), body: make([]*_nodeCaseStatement, len(in.Body)),
} }
for i, p := range x.Body { for i, clause := range in.Body {
q := &_nodeCaseStatement{ out.body[i] = &_nodeCaseStatement{
test: parseExpression(p.Test), test: cmpl.parseExpression(clause.Test),
consequent: make([]_nodeStatement, len(p.Consequent)), consequent: make([]_nodeStatement, len(clause.Consequent)),
} }
for j, value := range p.Consequent { for j, value := range clause.Consequent {
q.consequent[j] = parseStatement(value) out.body[i].consequent[j] = cmpl.parseStatement(value)
} }
y.body[i] = q
} }
return y return out
case *ast.ThrowStatement: case *ast.ThrowStatement:
return &_nodeThrowStatement{ return &_nodeThrowStatement{
argument: parseExpression(x.Argument), argument: cmpl.parseExpression(in.Argument),
} }
case *ast.TryStatement: case *ast.TryStatement:
y := &_nodeTryStatement{ out := &_nodeTryStatement{
body: parseStatement(x.Body), body: cmpl.parseStatement(in.Body),
finally: parseStatement(x.Finally), finally: cmpl.parseStatement(in.Finally),
} }
if x.Catch != nil { if in.Catch != nil {
y.catch = &_nodeCatchStatement{ out.catch = &_nodeCatchStatement{
parameter: x.Catch.Parameter.Name, parameter: in.Catch.Parameter.Name,
body: parseStatement(x.Catch.Body), body: cmpl.parseStatement(in.Catch.Body),
} }
} }
return y return out
case *ast.VariableStatement: case *ast.VariableStatement:
y := &_nodeVariableStatement{ out := &_nodeVariableStatement{
list: make([]_nodeExpression, len(x.List)), list: make([]_nodeExpression, len(in.List)),
} }
for i, value := range x.List { for i, value := range in.List {
y.list[i] = parseExpression(value) out.list[i] = cmpl.parseExpression(value)
} }
return y return out
case *ast.WhileStatement: case *ast.WhileStatement:
y := &_nodeWhileStatement{ out := &_nodeWhileStatement{
test: parseExpression(x.Test), test: cmpl.parseExpression(in.Test),
} }
body := parseStatement(x.Body) body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*_nodeBlockStatement); ok {
y.body = block.list out.body = block.list
} else { } else {
y.body = append(y.body, body) out.body = append(out.body, body)
} }
return y return out
case *ast.WithStatement: case *ast.WithStatement:
return &_nodeWithStatement{ return &_nodeWithStatement{
object: parseExpression(x.Object), object: cmpl.parseExpression(in.Object),
body: parseStatement(x.Body), body: cmpl.parseStatement(in.Body),
} }
} }
panic(fmt.Errorf("Here be dragons: parseStatement(%T)", x)) panic(fmt.Errorf("Here be dragons: cmpl.parseStatement(%T)", in))
} }
func cmpl_parse(x *ast.Program) *_nodeProgram { func cmpl_parse(in *ast.Program) *_nodeProgram {
y := &_nodeProgram{ cmpl := _compiler{
body: make([]_nodeStatement, len(x.Body)), program: in,
} }
for i, value := range x.Body { return cmpl.parse()
y.body[i] = parseStatement(value) }
func (cmpl *_compiler) _parse(in *ast.Program) *_nodeProgram {
out := &_nodeProgram{
body: make([]_nodeStatement, len(in.Body)),
file: in.File,
} }
for _, value := range x.DeclarationList { for i, value := range in.Body {
out.body[i] = cmpl.parseStatement(value)
}
for _, value := range in.DeclarationList {
switch value := value.(type) { switch value := value.(type) {
case *ast.FunctionDeclaration: case *ast.FunctionDeclaration:
y.functionList = append(y.functionList, parseExpression(value.Function).(*_nodeFunctionLiteral)) out.functionList = append(out.functionList, cmpl.parseExpression(value.Function).(*_nodeFunctionLiteral))
case *ast.VariableDeclaration: case *ast.VariableDeclaration:
for _, value := range value.List { for _, value := range value.List {
y.varList = append(y.varList, value.Name) out.varList = append(out.varList, value.Name)
} }
default: default:
panic(fmt.Errorf("Here be dragons: parseProgram.DeclarationList(%T)", value)) panic(fmt.Errorf("Here be dragons: cmpl.parseProgram.DeclarationList(%T)", value))
} }
} }
return y return out
} }
type _nodeProgram struct { type _nodeProgram struct {
@ -377,6 +390,8 @@ type _nodeProgram struct {
functionList []*_nodeFunctionLiteral functionList []*_nodeFunctionLiteral
variableList []_nodeDeclaration variableList []_nodeDeclaration
file *file.File
} }
type _nodeDeclaration struct { type _nodeDeclaration struct {
@ -411,6 +426,7 @@ type (
} }
_nodeBracketExpression struct { _nodeBracketExpression struct {
idx file.Idx
left _nodeExpression left _nodeExpression
member _nodeExpression member _nodeExpression
} }
@ -427,6 +443,7 @@ type (
} }
_nodeDotExpression struct { _nodeDotExpression struct {
idx file.Idx
left _nodeExpression left _nodeExpression
identifier string identifier string
} }
@ -438,9 +455,11 @@ type (
parameterList []string parameterList []string
varList []string varList []string
functionList []*_nodeFunctionLiteral functionList []*_nodeFunctionLiteral
file *file.File
} }
_nodeIdentifier struct { _nodeIdentifier struct {
idx file.Idx
name string name string
} }
@ -483,6 +502,7 @@ type (
} }
_nodeVariableExpression struct { _nodeVariableExpression struct {
idx file.Idx
name string name string
initializer _nodeExpression initializer _nodeExpression
} }

View File

@ -15,7 +15,7 @@ func Test_cmpl(t *testing.T) {
is(err, nil) is(err, nil)
{ {
program := cmpl_parse(program) program := cmpl_parse(program)
value := vm.runtime.cmpl_evaluate_nodeProgram(program) value := vm.runtime.cmpl_evaluate_nodeProgram(program, false)
if len(expect) > 0 { if len(expect) > 0 {
is(value, expect[0]) is(value, expect[0])
} }

View File

@ -16,33 +16,33 @@ func formatForConsole(argumentList []Value) string {
func builtinConsole_log(call FunctionCall) Value { func builtinConsole_log(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList)) fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return UndefinedValue() return Value{}
} }
func builtinConsole_error(call FunctionCall) Value { func builtinConsole_error(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList)) fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return UndefinedValue() return Value{}
} }
// Nothing happens. // Nothing happens.
func builtinConsole_dir(call FunctionCall) Value { func builtinConsole_dir(call FunctionCall) Value {
return UndefinedValue() return Value{}
} }
func builtinConsole_time(call FunctionCall) Value { func builtinConsole_time(call FunctionCall) Value {
return UndefinedValue() return Value{}
} }
func builtinConsole_timeEnd(call FunctionCall) Value { func builtinConsole_timeEnd(call FunctionCall) Value {
return UndefinedValue() return Value{}
} }
func builtinConsole_trace(call FunctionCall) Value { func builtinConsole_trace(call FunctionCall) Value {
return UndefinedValue() return Value{}
} }
func builtinConsole_assert(call FunctionCall) Value { func builtinConsole_assert(call FunctionCall) Value {
return UndefinedValue() return Value{}
} }
func (runtime *_runtime) newConsole() *_object { func (runtime *_runtime) newConsole() *_object {

View File

@ -246,7 +246,10 @@ func TestDate_new(t *testing.T) {
// This is probably incorrect, due to differences in Go date/time handling // This is probably incorrect, due to differences in Go date/time handling
// versus ECMA date/time handling, but we'll leave this here for // versus ECMA date/time handling, but we'll leave this here for
// future reference // future reference
return
if true {
return
}
tt(t, func() { tt(t, func() {
test, _ := test() test, _ := test()

View File

@ -64,7 +64,7 @@ func ExampleSynopsis() {
// 16 // 16
// 16 // 16
// undefined // undefined
// ReferenceError: abcdefghijlmnopqrstuvwxyz is not defined // ReferenceError: 'abcdefghijlmnopqrstuvwxyz' is not defined
// Hello, Xyzzy. // Hello, Xyzzy.
// Hello, undefined. // Hello, undefined.
// 4 // 4

View File

@ -0,0 +1,245 @@
package otto
import (
"errors"
"fmt"
"strings"
"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
}
type _frame struct {
file *file.File
offset int
callee string
}
var (
nativeFrame = _frame{}
)
type _at int
func (fr _frame) location() string {
if fr.file == nil {
return "<unknown>"
}
path := fr.file.Name()
line, column := _position(fr.file, fr.offset)
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
}
func _position(file *file.File, offset int) (line, column int) {
{
offset := offset - file.Base()
if offset < 0 {
return -offset, -1
}
src := file.Source()
if offset >= len(src) {
return -offset, -len(src)
}
src = src[:offset]
line := 1 + strings.Count(src, "\n")
column := 0
if index := strings.LastIndex(src, "\n"); index >= 0 {
column = offset - index
} else {
column = 1 + len(src)
}
return line, column
}
}
// 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 {
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)
}
// 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 {
str := err.Error() + "\n"
for _, frame := range err.trace {
str += " at " + frame.location() + "\n"
}
return str
}
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, in ...interface{}) _error {
err := _error{
name: name,
offset: -1,
}
description := ""
length := len(in)
if rt != nil {
scope := rt.scope
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 -= 1
}
if length > 0 {
description, in = in[0].(string), in[1:]
}
}
limit := 10
err.trace = append(err.trace, frame)
if scope != nil {
for limit > 0 {
scope = scope.outer
if scope == nil {
break
}
if scope.frame.offset >= 0 {
err.trace = append(err.trace, scope.frame)
}
limit--
}
}
} 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", argumentList...),
}
}
func (rt *_runtime) panicReferenceError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "ReferenceError", argumentList...),
}
}
func (rt *_runtime) panicURIError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "URIError", argumentList...),
}
}
func (rt *_runtime) panicSyntaxError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "SyntaxError", argumentList...),
}
}
func (rt *_runtime) panicRangeError(argumentList ...interface{}) *_exception {
return &_exception{
value: newError(rt, "RangeError", 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 = &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
}

View File

@ -0,0 +1,192 @@
package otto
import (
"testing"
)
func TestError(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`
[ Error.prototype.name, Error.prototype.message, Error.prototype.hasOwnProperty("message") ];
`, "Error,,true")
})
}
func TestError_instanceof(t *testing.T) {
tt(t, func() {
test, _ := test()
test(`(new TypeError()) instanceof Error`, true)
})
}
func TestPanicValue(t *testing.T) {
tt(t, func() {
test, vm := test()
vm.Set("abc", func(call FunctionCall) Value {
value, err := call.Otto.Run(`({ def: 3.14159 })`)
is(err, nil)
panic(value)
})
test(`
try {
abc();
}
catch (err) {
error = err;
}
[ error instanceof Error, error.message, error.def ];
`, "false,,3.14159")
})
}
func Test_catchPanic(t *testing.T) {
tt(t, func() {
vm := New()
_, err := vm.Run(`
A syntax error that
does not define
var;
abc;
`)
is(err, "!=", nil)
_, err = vm.Call(`abc.def`, nil)
is(err, "!=", nil)
})
}
func TestErrorContext(t *testing.T) {
tt(t, func() {
vm := New()
_, err := vm.Run(`
undefined();
`)
{
err := err.(*Error)
is(err.message, "'undefined' is not a function")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:2:13")
}
_, err = vm.Run(`
({}).abc();
`)
{
err := err.(*Error)
is(err.message, "'abc' is not a function")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:2:14")
}
_, err = vm.Run(`
("abc").abc();
`)
{
err := err.(*Error)
is(err.message, "'abc' is not a function")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:2:14")
}
_, err = vm.Run(`
var ghi = "ghi";
ghi();
`)
{
err := err.(*Error)
is(err.message, "'ghi' is not a function")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:3:13")
}
_, err = vm.Run(`
function def() {
undefined();
}
function abc() {
def();
}
abc();
`)
{
err := err.(*Error)
is(err.message, "'undefined' is not a function")
is(len(err.trace), 3)
is(err.trace[0].location(), "def (<anonymous>:3:17)")
is(err.trace[1].location(), "abc (<anonymous>:6:17)")
is(err.trace[2].location(), "<anonymous>:8:13")
}
_, err = vm.Run(`
function abc() {
xyz();
}
abc();
`)
{
err := err.(*Error)
is(err.message, "'xyz' is not defined")
is(len(err.trace), 2)
is(err.trace[0].location(), "abc (<anonymous>:3:17)")
is(err.trace[1].location(), "<anonymous>:5:13")
}
_, err = vm.Run(`
mno + 1;
`)
{
err := err.(*Error)
is(err.message, "'mno' is not defined")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:2:13")
}
_, err = vm.Run(`
eval("xyz();");
`)
{
err := err.(*Error)
is(err.message, "'xyz' is not defined")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:1:1")
}
_, err = vm.Run(`
xyzzy = "Nothing happens."
eval("xyzzy();");
`)
{
err := err.(*Error)
is(err.message, "'xyzzy' is not a function")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:1:1")
}
_, err = vm.Run(`
throw Error("xyzzy");
`)
{
err := err.(*Error)
is(err.message, "xyzzy")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:2:19")
}
_, err = vm.Run(`
throw new Error("xyzzy");
`)
{
err := err.(*Error)
is(err.message, "xyzzy")
is(len(err.trace), 1)
is(err.trace[0].location(), "<anonymous>:2:23")
}
})
}

View File

@ -10,7 +10,7 @@ import (
func (self *_runtime) evaluateMultiply(left float64, right float64) Value { func (self *_runtime) evaluateMultiply(left float64, right float64) Value {
// TODO 11.5.1 // TODO 11.5.1
return UndefinedValue() return Value{}
} }
func (self *_runtime) evaluateDivide(left float64, right float64) Value { func (self *_runtime) evaluateDivide(left float64, right float64) Value {
@ -49,98 +49,98 @@ func (self *_runtime) evaluateDivide(left float64, right float64) Value {
func (self *_runtime) evaluateModulo(left float64, right float64) Value { func (self *_runtime) evaluateModulo(left float64, right float64) Value {
// TODO 11.5.3 // TODO 11.5.3
return UndefinedValue() return Value{}
} }
func (self *_runtime) calculateBinaryExpression(operator token.Token, left Value, right Value) Value { func (self *_runtime) calculateBinaryExpression(operator token.Token, left Value, right Value) Value {
leftValue := self.GetValue(left) leftValue := left.resolve()
switch operator { switch operator {
// Additive // Additive
case token.PLUS: case token.PLUS:
leftValue = toPrimitive(leftValue) leftValue = toPrimitive(leftValue)
rightValue := self.GetValue(right) rightValue := right.resolve()
rightValue = toPrimitive(rightValue) rightValue = toPrimitive(rightValue)
if leftValue.IsString() || rightValue.IsString() { if leftValue.IsString() || rightValue.IsString() {
return toValue_string(strings.Join([]string{leftValue.toString(), rightValue.toString()}, "")) return toValue_string(strings.Join([]string{leftValue.string(), rightValue.string()}, ""))
} else { } else {
return toValue_float64(leftValue.toFloat() + rightValue.toFloat()) return toValue_float64(leftValue.float64() + rightValue.float64())
} }
case token.MINUS: case token.MINUS:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_float64(leftValue.toFloat() - rightValue.toFloat()) return toValue_float64(leftValue.float64() - rightValue.float64())
// Multiplicative // Multiplicative
case token.MULTIPLY: case token.MULTIPLY:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_float64(leftValue.toFloat() * rightValue.toFloat()) return toValue_float64(leftValue.float64() * rightValue.float64())
case token.SLASH: case token.SLASH:
rightValue := self.GetValue(right) rightValue := right.resolve()
return self.evaluateDivide(leftValue.toFloat(), rightValue.toFloat()) return self.evaluateDivide(leftValue.float64(), rightValue.float64())
case token.REMAINDER: case token.REMAINDER:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_float64(math.Mod(leftValue.toFloat(), rightValue.toFloat())) return toValue_float64(math.Mod(leftValue.float64(), rightValue.float64()))
// Logical // Logical
case token.LOGICAL_AND: case token.LOGICAL_AND:
left := toBoolean(leftValue) left := leftValue.bool()
if !left { if !left {
return FalseValue() return falseValue
} }
return toValue_bool(toBoolean(self.GetValue(right))) return toValue_bool(right.resolve().bool())
case token.LOGICAL_OR: case token.LOGICAL_OR:
left := toBoolean(leftValue) left := leftValue.bool()
if left { if left {
return TrueValue() return trueValue
} }
return toValue_bool(toBoolean(self.GetValue(right))) return toValue_bool(right.resolve().bool())
// Bitwise // Bitwise
case token.AND: case token.AND:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) & toInt32(rightValue)) return toValue_int32(toInt32(leftValue) & toInt32(rightValue))
case token.OR: case token.OR:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) | toInt32(rightValue)) return toValue_int32(toInt32(leftValue) | toInt32(rightValue))
case token.EXCLUSIVE_OR: case token.EXCLUSIVE_OR:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) ^ toInt32(rightValue)) return toValue_int32(toInt32(leftValue) ^ toInt32(rightValue))
// Shift // Shift
// (Masking of 0x1f is to restrict the shift to a maximum of 31 places) // (Masking of 0x1f is to restrict the shift to a maximum of 31 places)
case token.SHIFT_LEFT: case token.SHIFT_LEFT:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) << (toUint32(rightValue) & 0x1f)) return toValue_int32(toInt32(leftValue) << (toUint32(rightValue) & 0x1f))
case token.SHIFT_RIGHT: case token.SHIFT_RIGHT:
rightValue := self.GetValue(right) rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f)) return toValue_int32(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.UNSIGNED_SHIFT_RIGHT: case token.UNSIGNED_SHIFT_RIGHT:
rightValue := self.GetValue(right) rightValue := right.resolve()
// Shifting an unsigned integer is a logical shift // Shifting an unsigned integer is a logical shift
return toValue_uint32(toUint32(leftValue) >> (toUint32(rightValue) & 0x1f)) return toValue_uint32(toUint32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.INSTANCEOF: case token.INSTANCEOF:
rightValue := self.GetValue(right) rightValue := right.resolve()
if !rightValue.IsObject() { if !rightValue.IsObject() {
panic(newTypeError("Expecting a function in instanceof check, but got: %v", rightValue)) panic(self.panicTypeError("Expecting a function in instanceof check, but got: %v", rightValue))
} }
return toValue_bool(rightValue._object().HasInstance(leftValue)) return toValue_bool(rightValue._object().hasInstance(leftValue))
case token.IN: case token.IN:
rightValue := self.GetValue(right) rightValue := right.resolve()
if !rightValue.IsObject() { if !rightValue.IsObject() {
panic(newTypeError()) panic(self.panicTypeError())
} }
return toValue_bool(rightValue._object().hasProperty(toString(leftValue))) return toValue_bool(rightValue._object().hasProperty(leftValue.string()))
} }
panic(hereBeDragons(operator)) panic(hereBeDragons(operator))
} }
func valueKindDispatchKey(left _valueType, right _valueType) int { func valueKindDispatchKey(left _valueKind, right _valueKind) int {
return (int(left) << 2) + int(right) return (int(left) << 2) + int(right)
} }
@ -150,10 +150,10 @@ func makeEqualDispatch() map[int](func(Value, Value) bool) {
key := valueKindDispatchKey key := valueKindDispatchKey
return map[int](func(Value, Value) bool){ return map[int](func(Value, Value) bool){
key(valueNumber, valueObject): func(x Value, y Value) bool { return x.toFloat() == y.toFloat() }, 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.toFloat() == y.toFloat() }, 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.toFloat() == y.toFloat() }, 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.toFloat() == y.toFloat() }, key(valueObject, valueString): func(x Value, y Value) bool { return x.float64() == y.float64() },
} }
} }
@ -167,7 +167,7 @@ const (
func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult { func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult {
x := UndefinedValue() x := Value{}
y := x y := x
if leftFirst { if leftFirst {
@ -179,14 +179,14 @@ func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult
} }
result := false result := false
if x._valueType != valueString || y._valueType != valueString { if x.kind != valueString || y.kind != valueString {
x, y := x.toFloat(), y.toFloat() x, y := x.float64(), y.float64()
if math.IsNaN(x) || math.IsNaN(y) { if math.IsNaN(x) || math.IsNaN(y) {
return lessThanUndefined return lessThanUndefined
} }
result = x < y result = x < y
} else { } else {
x, y := x.toString(), y.toString() x, y := x.string(), y.string()
result = x < y result = x < y
} }
@ -197,6 +197,7 @@ func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult
return lessThanFalse 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){ var lessThanTable [4](map[_lessThanResult]bool) = [4](map[_lessThanResult]bool){
// < // <
map[_lessThanResult]bool{ map[_lessThanResult]bool{
@ -231,8 +232,8 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
// FIXME Use strictEqualityComparison? // FIXME Use strictEqualityComparison?
// TODO This might be redundant now (with regards to evaluateComparison) // TODO This might be redundant now (with regards to evaluateComparison)
x := self.GetValue(left) x := left.resolve()
y := self.GetValue(right) y := right.resolve()
kindEqualKind := false kindEqualKind := false
result := true result := true
@ -251,7 +252,7 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
negate = true negate = true
fallthrough fallthrough
case token.STRICT_EQUAL: case token.STRICT_EQUAL:
if x._valueType != y._valueType { if x.kind != y.kind {
result = false result = false
} else { } else {
kindEqualKind = true kindEqualKind = true
@ -260,21 +261,21 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
negate = true negate = true
fallthrough fallthrough
case token.EQUAL: case token.EQUAL:
if x._valueType == y._valueType { if x.kind == y.kind {
kindEqualKind = true kindEqualKind = true
} else if x._valueType <= valueUndefined && y._valueType <= valueUndefined { } else if x.kind <= valueNull && y.kind <= valueNull {
result = true result = true
} else if x._valueType <= valueUndefined || y._valueType <= valueUndefined { } else if x.kind <= valueNull || y.kind <= valueNull {
result = false result = false
} else if x._valueType <= valueString && y._valueType <= valueString { } else if x.kind <= valueString && y.kind <= valueString {
result = x.toFloat() == y.toFloat() result = x.float64() == y.float64()
} else if x._valueType == valueBoolean { } else if x.kind == valueBoolean {
result = self.calculateComparison(token.EQUAL, toValue_float64(x.toFloat()), y) result = self.calculateComparison(token.EQUAL, toValue_float64(x.float64()), y)
} else if y._valueType == valueBoolean { } else if y.kind == valueBoolean {
result = self.calculateComparison(token.EQUAL, x, toValue_float64(y.toFloat())) result = self.calculateComparison(token.EQUAL, x, toValue_float64(y.float64()))
} else if x._valueType == valueObject { } else if x.kind == valueObject {
result = self.calculateComparison(token.EQUAL, toPrimitive(x), y) result = self.calculateComparison(token.EQUAL, toPrimitive(x), y)
} else if y._valueType == valueObject { } else if y.kind == valueObject {
result = self.calculateComparison(token.EQUAL, x, toPrimitive(y)) result = self.calculateComparison(token.EQUAL, x, toPrimitive(y))
} else { } else {
panic(hereBeDragons("Unable to test for equality: %v ==? %v", x, y)) panic(hereBeDragons("Unable to test for equality: %v ==? %v", x, y))
@ -284,21 +285,21 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
} }
if kindEqualKind { if kindEqualKind {
switch x._valueType { switch x.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
result = true result = true
case valueNumber: case valueNumber:
x := x.toFloat() x := x.float64()
y := y.toFloat() y := y.float64()
if math.IsNaN(x) || math.IsNaN(y) { if math.IsNaN(x) || math.IsNaN(y) {
result = false result = false
} else { } else {
result = x == y result = x == y
} }
case valueString: case valueString:
result = x.toString() == y.toString() result = x.string() == y.string()
case valueBoolean: case valueBoolean:
result = x.toBoolean() == y.toBoolean() result = x.bool() == y.bool()
case valueObject: case valueObject:
result = x._object() == y._object() result = x._object() == y._object()
default: default:
@ -313,5 +314,5 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
return result return result
ERROR: ERROR:
panic(hereBeDragons("%v (%v) %s %v (%v)", x, x._valueType, comparator, y, y._valueType)) panic(hereBeDragons("%v (%v) %s %v (%v)", x, x.kind, comparator, y, y.kind))
} }

View File

@ -229,7 +229,7 @@ func TestFunction_bind(t *testing.T) {
test(`raise: test(`raise:
Math.bind(); Math.bind();
`, "TypeError: undefined is not a function") `, "TypeError: 'bind' is not a function")
test(` test(`
function construct(fn, arguments) { function construct(fn, arguments) {
@ -251,6 +251,14 @@ func TestFunction_bind(t *testing.T) {
var result = new newFn(); var result = new newFn();
[ result.hasOwnProperty("abc"), result.hasOwnProperty("def"), result.abc, result.def ]; [ result.hasOwnProperty("abc"), result.hasOwnProperty("def"), result.abc, result.def ];
`, "true,true,abc,true") `, "true,true,abc,true")
test(`
abc = function(){
return "abc";
};
def = abc.bind();
def.toString();
`, "function () { [native code] }")
}) })
} }

View File

@ -7,28 +7,28 @@ import (
var ( var (
prototypeValueObject = interface{}(nil) prototypeValueObject = interface{}(nil)
prototypeValueFunction = _functionObject{ prototypeValueFunction = _nativeFunctionObject{
call: _nativeCallFunction{"", func(_ FunctionCall) Value { call: func(_ FunctionCall) Value {
return UndefinedValue() return Value{}
}}, },
} }
prototypeValueString = _stringASCII("") prototypeValueString = _stringASCII("")
// TODO Make this just false? // TODO Make this just false?
prototypeValueBoolean = Value{ prototypeValueBoolean = Value{
_valueType: valueBoolean, kind: valueBoolean,
value: false, value: false,
} }
prototypeValueNumber = Value{ prototypeValueNumber = Value{
_valueType: valueNumber, kind: valueNumber,
value: 0, value: 0,
} }
prototypeValueDate = _dateObject{ prototypeValueDate = _dateObject{
epoch: 0, epoch: 0,
isNaN: false, isNaN: false,
time: time.Unix(0, 0).UTC(), time: time.Unix(0, 0).UTC(),
value: Value{ value: Value{
_valueType: valueNumber, kind: valueNumber,
value: 0, value: 0,
}, },
} }
prototypeValueRegExp = _regExpObject{ prototypeValueRegExp = _regExpObject{
@ -45,16 +45,13 @@ func newContext() *_runtime {
self := &_runtime{} self := &_runtime{}
self.GlobalEnvironment = self.newObjectEnvironment(nil, nil) self.globalStash = self.newObjectStash(nil, nil)
self.GlobalObject = self.GlobalEnvironment.Object self.globalObject = self.globalStash.object
self.EnterGlobalExecutionContext()
_newContext(self) _newContext(self)
self.eval = self.GlobalObject.property["eval"].value.(Value).value.(*_object) self.eval = self.globalObject.property["eval"].value.(Value).value.(*_object)
self.GlobalObject.prototype = self.Global.ObjectPrototype self.globalObject.prototype = self.global.ObjectPrototype
//self.parser = ast.NewParser()
return self return self
} }
@ -94,13 +91,13 @@ func (self *_object) hasPrimitive() bool {
func (runtime *_runtime) newObject() *_object { func (runtime *_runtime) newObject() *_object {
self := runtime.newClassObject("Object") self := runtime.newClassObject("Object")
self.prototype = runtime.Global.ObjectPrototype self.prototype = runtime.global.ObjectPrototype
return self return self
} }
func (runtime *_runtime) newArray(length uint32) *_object { func (runtime *_runtime) newArray(length uint32) *_object {
self := runtime.newArrayObject(length) self := runtime.newArrayObject(length)
self.prototype = runtime.Global.ArrayPrototype self.prototype = runtime.global.ArrayPrototype
return self return self
} }
@ -117,19 +114,19 @@ func (runtime *_runtime) newArrayOf(valueArray []Value) *_object {
func (runtime *_runtime) newString(value Value) *_object { func (runtime *_runtime) newString(value Value) *_object {
self := runtime.newStringObject(value) self := runtime.newStringObject(value)
self.prototype = runtime.Global.StringPrototype self.prototype = runtime.global.StringPrototype
return self return self
} }
func (runtime *_runtime) newBoolean(value Value) *_object { func (runtime *_runtime) newBoolean(value Value) *_object {
self := runtime.newBooleanObject(value) self := runtime.newBooleanObject(value)
self.prototype = runtime.Global.BooleanPrototype self.prototype = runtime.global.BooleanPrototype
return self return self
} }
func (runtime *_runtime) newNumber(value Value) *_object { func (runtime *_runtime) newNumber(value Value) *_object {
self := runtime.newNumberObject(value) self := runtime.newNumberObject(value)
self.prototype = runtime.Global.NumberPrototype self.prototype = runtime.global.NumberPrototype
return self return self
} }
@ -139,17 +136,17 @@ func (runtime *_runtime) newRegExp(patternValue Value, flagsValue Value) *_objec
flags := "" flags := ""
if object := patternValue._object(); object != nil && object.class == "RegExp" { if object := patternValue._object(); object != nil && object.class == "RegExp" {
if flagsValue.IsDefined() { if flagsValue.IsDefined() {
panic(newTypeError("Cannot supply flags when constructing one RegExp from another")) panic(runtime.panicTypeError("Cannot supply flags when constructing one RegExp from another"))
} }
regExp := object.regExpValue() regExp := object.regExpValue()
pattern = regExp.source pattern = regExp.source
flags = regExp.flags flags = regExp.flags
} else { } else {
if patternValue.IsDefined() { if patternValue.IsDefined() {
pattern = toString(patternValue) pattern = patternValue.string()
} }
if flagsValue.IsDefined() { if flagsValue.IsDefined() {
flags = toString(flagsValue) flags = flagsValue.string()
} }
} }
@ -158,14 +155,14 @@ func (runtime *_runtime) newRegExp(patternValue Value, flagsValue Value) *_objec
func (runtime *_runtime) _newRegExp(pattern string, flags string) *_object { func (runtime *_runtime) _newRegExp(pattern string, flags string) *_object {
self := runtime.newRegExpObject(pattern, flags) self := runtime.newRegExpObject(pattern, flags)
self.prototype = runtime.Global.RegExpPrototype self.prototype = runtime.global.RegExpPrototype
return self return self
} }
// TODO Should (probably) be one argument, right? This is redundant // TODO Should (probably) be one argument, right? This is redundant
func (runtime *_runtime) newDate(epoch float64) *_object { func (runtime *_runtime) newDate(epoch float64) *_object {
self := runtime.newDateObject(epoch) self := runtime.newDateObject(epoch)
self.prototype = runtime.Global.DatePrototype self.prototype = runtime.global.DatePrototype
return self return self
} }
@ -186,8 +183,8 @@ func (runtime *_runtime) newError(name string, message Value) *_object {
return runtime.newURIError(message) return runtime.newURIError(message)
} }
self = runtime.newErrorObject(message) self = runtime.newErrorObject(name, message)
self.prototype = runtime.Global.ErrorPrototype self.prototype = runtime.global.ErrorPrototype
if name != "" { if name != "" {
self.defineProperty("name", toValue_string(name), 0111, false) self.defineProperty("name", toValue_string(name), 0111, false)
} }
@ -196,19 +193,29 @@ func (runtime *_runtime) newError(name string, message Value) *_object {
func (runtime *_runtime) newNativeFunction(name string, _nativeFunction _nativeFunction) *_object { func (runtime *_runtime) newNativeFunction(name string, _nativeFunction _nativeFunction) *_object {
self := runtime.newNativeFunctionObject(name, _nativeFunction, 0) self := runtime.newNativeFunctionObject(name, _nativeFunction, 0)
self.prototype = runtime.Global.FunctionPrototype self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject() prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false) self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false) prototype.defineProperty("constructor", toValue_object(self), 0100, false)
return self return self
} }
func (runtime *_runtime) newNodeFunction(node *_nodeFunctionLiteral, scopeEnvironment _environment) *_object { func (runtime *_runtime) newNodeFunction(node *_nodeFunctionLiteral, scopeEnvironment _stash) *_object {
// TODO Implement 13.2 fully // TODO Implement 13.2 fully
self := runtime.newNodeFunctionObject(node, scopeEnvironment) self := runtime.newNodeFunctionObject(node, scopeEnvironment)
self.prototype = runtime.Global.FunctionPrototype self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject() prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false) self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0101, false) prototype.defineProperty("constructor", toValue_object(self), 0101, false)
return self 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
}

View File

@ -31,35 +31,38 @@ func TestGlobal(t *testing.T) {
return value return value
} }
value := runtime.localGet("Object")._object().Call(UndefinedValue(), []Value{toValue(runtime.newObject())}) // FIXME enterGlobalScope
is(value.IsObject(), true) if false {
is(value, "[object Object]") value := runtime.scope.lexical.getBinding("Object", false)._object().call(UndefinedValue(), []Value{toValue(runtime.newObject())}, false, nativeFrame)
is(value._object().prototype == runtime.Global.ObjectPrototype, true) is(value.IsObject(), true)
is(value._object().prototype == runtime.Global.Object.get("prototype")._object(), true) is(value, "[object Object]")
is(value._object().get("toString"), "function toString() { [native code] }") is(value._object().prototype == runtime.global.ObjectPrototype, true)
is(call(value.Object(), "hasOwnProperty", "hasOwnProperty"), false) is(value._object().prototype == runtime.global.Object.get("prototype")._object(), true)
is(value._object().get("toString"), "function toString() { [native code] }")
is(call(value.Object(), "hasOwnProperty", "hasOwnProperty"), false)
is(call(value._object().get("toString")._object().prototype, "toString"), "function () { [native code] }") // TODO Is this right? is(call(value._object().get("toString")._object().prototype, "toString"), "function () { [native code] }") // TODO Is this right?
is(value._object().get("toString")._object().get("toString"), "function toString() { [native code] }") is(value._object().get("toString")._object().get("toString"), "function toString() { [native code] }")
is(value._object().get("toString")._object().get("toString")._object(), "function toString() { [native code] }") is(value._object().get("toString")._object().get("toString")._object(), "function toString() { [native code] }")
is(call(value._object(), "propertyIsEnumerable", "isPrototypeOf"), false) is(call(value._object(), "propertyIsEnumerable", "isPrototypeOf"), false)
value._object().put("xyzzy", toValue_string("Nothing happens."), false) value._object().put("xyzzy", toValue_string("Nothing happens."), false)
is(call(value, "propertyIsEnumerable", "isPrototypeOf"), false) is(call(value, "propertyIsEnumerable", "isPrototypeOf"), false)
is(call(value, "propertyIsEnumerable", "xyzzy"), true) is(call(value, "propertyIsEnumerable", "xyzzy"), true)
is(value._object().get("xyzzy"), "Nothing happens.") is(value._object().get("xyzzy"), "Nothing happens.")
is(call(runtime.localGet("Object"), "isPrototypeOf", value), false) is(call(runtime.scope.lexical.getBinding("Object", false), "isPrototypeOf", value), false)
is(call(runtime.localGet("Object")._object().get("prototype"), "isPrototypeOf", value), true) is(call(runtime.scope.lexical.getBinding("Object", false)._object().get("prototype"), "isPrototypeOf", value), true)
is(call(runtime.localGet("Function"), "isPrototypeOf", value), false) is(call(runtime.scope.lexical.getBinding("Function", false), "isPrototypeOf", value), false)
is(runtime.newObject().prototype == runtime.Global.Object.get("prototype")._object(), true) is(runtime.newObject().prototype == runtime.global.Object.get("prototype")._object(), true)
abc := runtime.newBoolean(toValue_bool(true)) abc := runtime.newBoolean(toValue_bool(true))
is(toValue_object(abc), "true") // TODO Call primitive? is(toValue_object(abc), "true") // TODO Call primitive?
def := runtime.localGet("Boolean")._object().Construct(UndefinedValue(), []Value{}) //def := runtime.localGet("Boolean")._object().Construct(UndefinedValue(), []Value{})
is(def, "false") // TODO Call primitive? //is(def, "false") // TODO Call primitive?
}
} }
test(`new Number().constructor == Number`, true) test(`new Number().constructor == Number`, true)

View File

@ -43,7 +43,7 @@ for (qw/int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float32 float
func toValue_$_(value $_) Value { func toValue_$_(value $_) Value {
return Value{ return Value{
_valueType: valueNumber, kind: valueNumber,
value: value, value: value,
} }
} }
@ -54,28 +54,28 @@ $fmt->print(<<_END_);
func toValue_string(value string) Value { func toValue_string(value string) Value {
return Value{ return Value{
_valueType: valueString, kind: valueString,
value: value, value: value,
} }
} }
func toValue_string16(value []uint16) Value { func toValue_string16(value []uint16) Value {
return Value{ return Value{
_valueType: valueString, kind: valueString,
value: value, value: value,
} }
} }
func toValue_bool(value bool) Value { func toValue_bool(value bool) Value {
return Value{ return Value{
_valueType: valueBoolean, kind: valueBoolean,
value: value, value: value,
} }
} }
func toValue_object(value *_object) Value { func toValue_object(value *_object) Value {
return Value{ return Value{
_valueType: valueObject, kind: valueObject,
value: value, value: value,
} }
} }
@ -642,9 +642,9 @@ sub newContext {
my $propertyOrder = $self->propertyOrder(@propertyMap); my $propertyOrder = $self->propertyOrder(@propertyMap);
$propertyOrder =~ s/^propertyOrder: //; $propertyOrder =~ s/^propertyOrder: //;
return return
"runtime.GlobalObject.property =", "runtime.globalObject.property =",
@propertyMap, @propertyMap,
"runtime.GlobalObject.propertyOrder =", "runtime.globalObject.propertyOrder =",
$propertyOrder, $propertyOrder,
; ;
}), }),
@ -666,7 +666,7 @@ sub block {
while (@input) { while (@input) {
local $_ = shift @input; local $_ = shift @input;
if (m/^\./) { if (m/^\./) {
$_ = "runtime.Global$_"; $_ = "runtime.global$_";
} }
if (m/ :?=$/) { if (m/ :?=$/) {
$_ .= shift @input; $_ .= shift @input;
@ -712,7 +712,7 @@ sub globalDeclare {
my @got; my @got;
while (@_) { while (@_) {
my $name = shift; my $name = shift;
push @got, $self->property($name, $self->objectValue("runtime.Global.$name"), "0101"), push @got, $self->property($name, $self->objectValue("runtime.global.$name"), "0101"),
} }
return @got; return @got;
} }
@ -743,7 +743,7 @@ sub globalObject {
runtime: runtime, runtime: runtime,
class: "$name", class: "$name",
objectClass: _classObject, objectClass: _classObject,
prototype: runtime.Global.ObjectPrototype, prototype: runtime.global.ObjectPrototype,
extensible: true, extensible: true,
$propertyMap $propertyMap
} }
@ -757,7 +757,7 @@ sub globalFunction {
my $builtin = "builtin${name}"; my $builtin = "builtin${name}";
my $builtinNew = "builtinNew${name}"; my $builtinNew = "builtinNew${name}";
my $prototype = "runtime.Global.${name}Prototype"; my $prototype = "runtime.global.${name}Prototype";
my $propertyMap = ""; my $propertyMap = "";
unshift @_, unshift @_,
$self->property("length", $self->numberValue($length), "0"), $self->property("length", $self->numberValue($length), "0"),
@ -772,7 +772,7 @@ sub globalFunction {
push @postblock, $self->statement( push @postblock, $self->statement(
"$prototype.property[\"constructor\"] =", "$prototype.property[\"constructor\"] =",
$self->property(undef, $self->objectValue("runtime.Global.${name}"), "0101"), $self->property(undef, $self->objectValue("runtime.global.${name}"), "0101"),
); );
return trim <<_END_; return trim <<_END_;
@ -780,9 +780,9 @@ sub globalFunction {
runtime: runtime, runtime: runtime,
class: "Function", class: "Function",
objectClass: _classObject, objectClass: _classObject,
prototype: runtime.Global.FunctionPrototype, prototype: runtime.global.FunctionPrototype,
extensible: true, extensible: true,
value: @{[ $self->functionOf($self->nativeCallFunction($name, $builtin), $builtinNew) ]}, value: @{[ $self->nativeFunctionOf($name, $builtin, $builtinNew) ]},
$propertyMap $propertyMap
} }
_END_ _END_
@ -813,7 +813,7 @@ sub globalPrototype {
} }
if ($prototype =~ m/^\./) { if ($prototype =~ m/^\./) {
$prototype = "runtime.Global$prototype"; $prototype = "runtime.global$prototype";
} }
my $propertyMap = ""; my $propertyMap = "";
@ -869,11 +869,11 @@ sub newFunction {
runtime: runtime, runtime: runtime,
class: "Function", class: "Function",
objectClass: _classObject, objectClass: _classObject,
prototype: runtime.Global.FunctionPrototype, prototype: runtime.global.FunctionPrototype,
extensible: true, extensible: true,
property: @{[ join "\n", $self->propertyMap(@propertyMap) ]}, property: @{[ join "\n", $self->propertyMap(@propertyMap) ]},
$propertyOrder $propertyOrder
value: @{[ $self->functionOf($self->nativeCallFunction($name, $func)) ]}, value: @{[ $self->nativeFunctionOf($name, $func) ]},
} }
_END_ _END_
); );
@ -892,7 +892,7 @@ sub newObject {
runtime: runtime, runtime: runtime,
class: "Object", class: "Object",
objectClass: _classObject, objectClass: _classObject,
prototype: runtime.Global.ObjectPrototype, prototype: runtime.global.ObjectPrototype,
extensible: true, extensible: true,
property: $propertyMap, property: $propertyMap,
$propertyOrder, $propertyOrder,
@ -917,7 +917,7 @@ sub newPrototypeObject {
runtime: runtime, runtime: runtime,
class: "$class", class: "$class",
objectClass: $objectClass, objectClass: $objectClass,
prototype: runtime.Global.ObjectPrototype, prototype: runtime.global.ObjectPrototype,
extensible: true, extensible: true,
property: $propertyMap, property: $propertyMap,
$propertyOrder, $propertyOrder,
@ -959,6 +959,26 @@ _functionObject{
_END_ _END_
} }
sub nativeFunctionOf {
my $self = shift;
my $name = shift;
my $call = shift;
my $construct = shift;
if ($construct) {
$construct = "construct: $construct,";
} else {
$construct = "";
}
return trim <<_END_
_nativeFunctionObject{
name: "$name",
call: $call,
$construct
}
_END_
}
sub nameProperty { sub nameProperty {
my $self = shift; my $self = shift;
my $name = shift; my $name = shift;
@ -977,7 +997,7 @@ sub numberValue {
my $value = shift; my $value = shift;
return trim <<_END_; return trim <<_END_;
Value{ Value{
_valueType: valueNumber, kind: valueNumber,
value: $value, value: $value,
} }
_END_ _END_
@ -1028,7 +1048,7 @@ sub objectValue {
my $value = shift; my $value = shift;
return trim <<_END_ return trim <<_END_
Value{ Value{
_valueType: valueObject, kind: valueObject,
value: $value, value: $value,
} }
_END_ _END_
@ -1039,7 +1059,7 @@ sub stringValue {
my $value = shift; my $value = shift;
return trim <<_END_ return trim <<_END_
Value{ Value{
_valueType: valueString, kind: valueString,
value: "$value", value: "$value",
} }
_END_ _END_
@ -1050,7 +1070,7 @@ sub booleanValue {
my $value = shift; my $value = shift;
return trim <<_END_ return trim <<_END_
Value{ Value{
_valueType: valueBoolean, kind: valueBoolean,
value: $value, value: $value,
} }
_END_ _END_
@ -1060,7 +1080,7 @@ sub undefinedValue {
my $self = shift; my $self = shift;
return trim <<_END_ return trim <<_END_
Value{ Value{
_valueType: valueUndefined, kind: valueUndefined,
} }
_END_ _END_
} }

View File

@ -144,15 +144,13 @@ func TestNumber_toLocaleString(t *testing.T) {
}) })
} }
func Test_toInteger(t *testing.T) { func TestValue_number(t *testing.T) {
tt(t, func() { tt(t, func() {
integer := toInteger(toValue(0.0)) nm := toValue(0.0).number()
is(integer.valid(), true) is(nm.kind, numberInteger)
is(integer.exact(), true)
integer = toInteger(toValue(3.14159)) nm = toValue(3.14159).number()
is(integer.valid(), true) is(nm.kind, numberFloat)
is(integer.exact(), false)
}) })
} }

View File

@ -85,20 +85,20 @@ func (self *_object) DefaultValue(hint _defaultValueHint) Value {
} }
for _, methodName := range methodSequence { for _, methodName := range methodSequence {
method := self.get(methodName) method := self.get(methodName)
// FIXME This is redundant...
if method.isCallable() { if method.isCallable() {
result := method._object().Call(toValue_object(self)) result := method._object().call(toValue_object(self), nil, false, nativeFrame)
if result.IsPrimitive() { if result.IsPrimitive() {
return result return result
} }
} }
} }
panic(newTypeError()) panic(self.runtime.panicTypeError())
return UndefinedValue()
} }
func (self *_object) String() string { func (self *_object) String() string {
return toString(self.DefaultValue(defaultValueHintString)) return self.DefaultValue(defaultValueHintString).string()
} }
func (self *_object) defineProperty(name string, value Value, mode _propertyMode, throw bool) bool { func (self *_object) defineProperty(name string, value Value, mode _propertyMode, throw bool) bool {
@ -130,7 +130,7 @@ func (self *_object) _read(name string) (_property, bool) {
func (self *_object) _write(name string, value interface{}, mode _propertyMode) { func (self *_object) _write(name string, value interface{}, mode _propertyMode) {
if value == nil { if value == nil {
value = UndefinedValue() value = Value{}
} }
_, exists := self.property[name] _, exists := self.property[name]
self.property[name] = _property{value, mode} self.property[name] = _property{value, mode}

View File

@ -193,7 +193,7 @@ func objectGet(self *_object, name string) Value {
if property != nil { if property != nil {
return property.get(self) return property.get(self)
} }
return UndefinedValue() return Value{}
} }
// 8.12.4 // 8.12.4
@ -214,7 +214,7 @@ func _objectCanPut(self *_object, name string) (canPut bool, property *_property
canPut = setter != nil canPut = setter != nil
return return
default: default:
panic(newTypeError()) panic(self.runtime.panicTypeError())
} }
} }
@ -238,10 +238,8 @@ func _objectCanPut(self *_object, name string) (canPut bool, property *_property
canPut = setter != nil canPut = setter != nil
return return
default: default:
panic(newTypeError()) panic(self.runtime.panicTypeError())
} }
return false, nil, nil
} }
// 8.12.5 // 8.12.5
@ -258,9 +256,9 @@ func objectPut(self *_object, name string, value Value, throw bool) {
// incompatible canPut routine // incompatible canPut routine
canPut, property, setter := _objectCanPut(self, name) canPut, property, setter := _objectCanPut(self, name)
if !canPut { if !canPut {
typeErrorResult(throw) self.runtime.typeErrorResult(throw)
} else if setter != nil { } else if setter != nil {
setter.callSet(toValue(self), value) setter.call(toValue(self), []Value{value}, false, nativeFrame)
} else if property != nil { } else if property != nil {
property.value = value property.value = value
self.defineOwnProperty(name, *property, throw) self.defineOwnProperty(name, *property, throw)
@ -274,7 +272,7 @@ func objectPut(self *_object, name string, value Value, throw bool) {
// //
// Right now, code should never get here, see above // Right now, code should never get here, see above
if !self.canPut(name) { if !self.canPut(name) {
typeErrorResult(throw) self.runtime.typeErrorResult(throw)
return return
} }
@ -283,7 +281,7 @@ func objectPut(self *_object, name string, value Value, throw bool) {
property = self.getProperty(name) property = self.getProperty(name)
if property != nil { if property != nil {
if getSet, isAccessor := property.value.(_propertyGetSet); isAccessor { if getSet, isAccessor := property.value.(_propertyGetSet); isAccessor {
getSet[1].callSet(toValue(self), value) getSet[1].call(toValue(self), []Value{value}, false, nativeFrame)
return return
} }
} }
@ -295,14 +293,14 @@ func objectPut(self *_object, name string, value Value, throw bool) {
self.defineOwnProperty(name, *property, throw) self.defineOwnProperty(name, *property, throw)
case _propertyGetSet: case _propertyGetSet:
if propertyValue[1] != nil { if propertyValue[1] != nil {
propertyValue[1].callSet(toValue(self), value) propertyValue[1].call(toValue(self), []Value{value}, false, nativeFrame)
return return
} }
if throw { if throw {
panic(newTypeError()) panic(self.runtime.panicTypeError())
} }
default: default:
panic(newTypeError()) panic(self.runtime.panicTypeError())
} }
} }
} }
@ -442,7 +440,7 @@ func objectDefineOwnProperty(self *_object, name string, descriptor _property, t
} }
Reject: Reject:
if throw { if throw {
panic(newTypeError()) panic(self.runtime.panicTypeError())
} }
return false return false
} }
@ -456,29 +454,40 @@ func objectDelete(self *_object, name string, throw bool) bool {
self._delete(name) self._delete(name)
return true return true
} }
return typeErrorResult(throw) return self.runtime.typeErrorResult(throw)
} }
func objectClone(self0 *_object, self1 *_object, clone *_clone) *_object { func objectClone(in *_object, out *_object, clone *_clone) *_object {
*self1 = *self0 *out = *in
self1.runtime = clone.runtime out.runtime = clone.runtime
if self1.prototype != nil { if out.prototype != nil {
self1.prototype = clone.object(self0.prototype) out.prototype = clone.object(in.prototype)
} }
self1.property = make(map[string]_property, len(self0.property)) out.property = make(map[string]_property, len(in.property))
self1.propertyOrder = make([]string, len(self0.propertyOrder)) out.propertyOrder = make([]string, len(in.propertyOrder))
copy(self1.propertyOrder, self0.propertyOrder) copy(out.propertyOrder, in.propertyOrder)
for index, property := range self0.property { for index, property := range in.property {
self1.property[index] = clone.property(property) out.property[index] = clone.property(property)
} }
switch value := self0.value.(type) { switch value := in.value.(type) {
case _functionObject: case _nativeFunctionObject:
self1.value = value.clone(clone) 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: case _argumentsObject:
self1.value = value.clone(clone) out.value = value.clone(clone)
} }
return self1 return out
} }

View File

@ -57,7 +57,7 @@ Set a Go function
vm.Set("sayHello", func(call otto.FunctionCall) otto.Value { vm.Set("sayHello", func(call otto.FunctionCall) otto.Value {
fmt.Printf("Hello, %s.\n", call.Argument(0).String()) fmt.Printf("Hello, %s.\n", call.Argument(0).String())
return otto.UndefinedValue() return otto.Value{}
}) })
Set a Go function that returns something useful Set a Go function that returns something useful
@ -131,7 +131,6 @@ Caveat Emptor
The following are some limitations with otto: The following are some limitations with otto:
* "use strict" will parse, but does nothing. * "use strict" will parse, but does nothing.
* Error reporting needs to be improved.
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification. * The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
Regular Expression Incompatibility Regular Expression Incompatibility
@ -190,16 +189,18 @@ If you want to stop long running executions (like third-party code), you can use
} }
fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration) fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration)
}() }()
vm := otto.New() vm := otto.New()
vm.Interrupt = make(chan func()) vm.Interrupt = make(chan func(), 1) // The buffer prevents blocking
go func() { go func() {
time.Sleep(2 * time.Second) // Stop after two seconds time.Sleep(2 * time.Second) // Stop after two seconds
vm.Interrupt <- func() { vm.Interrupt <- func() {
panic(halt) panic(halt)
} }
}() }()
vm.Run(unsafe) // Here be dragons (risky code) vm.Run(unsafe) // Here be dragons (risky code)
vm.Interrupt = nil
} }
Where is setTimeout/setInterval? Where is setTimeout/setInterval?
@ -242,7 +243,7 @@ func New() *Otto {
self := &Otto{ self := &Otto{
runtime: newContext(), runtime: newContext(),
} }
self.runtime.Otto = self self.runtime.otto = self
self.Set("console", self.runtime.newConsole()) self.Set("console", self.runtime.newConsole())
registry.Apply(func(entry registry.Entry) { registry.Apply(func(entry registry.Entry) {
@ -256,7 +257,7 @@ func (otto *Otto) clone() *Otto {
self := &Otto{ self := &Otto{
runtime: otto.runtime.clone(), runtime: otto.runtime.clone(),
} }
self.runtime.Otto = self self.runtime.otto = self
return self return self
} }
@ -272,7 +273,7 @@ func (otto *Otto) clone() *Otto {
// //
func Run(src interface{}) (*Otto, Value, error) { func Run(src interface{}) (*Otto, Value, error) {
otto := New() otto := New()
value, err := otto.Run(src) value, err := otto.Run(src) // This already does safety checking
return otto, value, err return otto, value, err
} }
@ -288,7 +289,11 @@ func Run(src interface{}) (*Otto, Value, error) {
// src may also be a Program, but if the AST has been modified, then runtime behavior is undefined. // src may also be a Program, but if the AST has been modified, then runtime behavior is undefined.
// //
func (self Otto) Run(src interface{}) (Value, error) { func (self Otto) Run(src interface{}) (Value, error) {
return self.runtime.cmpl_run(src) value, err := self.runtime.cmpl_run(src)
if !value.safe() {
value = Value{}
}
return value, err
} }
// Get the value of the top-level binding of the given name. // Get the value of the top-level binding of the given name.
@ -296,15 +301,18 @@ func (self Otto) Run(src interface{}) (Value, error) {
// If there is an error (like the binding does not exist), then the value // If there is an error (like the binding does not exist), then the value
// will be undefined. // will be undefined.
func (self Otto) Get(name string) (Value, error) { func (self Otto) Get(name string) (Value, error) {
value := UndefinedValue() value := Value{}
err := catchPanic(func() { err := catchPanic(func() {
value = self.getValue(name) value = self.getValue(name)
}) })
if !value.safe() {
value = Value{}
}
return value, err return value, err
} }
func (self Otto) getValue(name string) Value { func (self Otto) getValue(name string) Value {
return self.runtime.GlobalEnvironment.GetValue(name, false) return self.runtime.globalStash.getBinding(name, false)
} }
// Set the top-level binding of the given name to the given value. // Set the top-level binding of the given name to the given value.
@ -330,7 +338,7 @@ func (self Otto) Set(name string, value interface{}) error {
} }
func (self Otto) setValue(name string, value Value) { func (self Otto) setValue(name string, value Value) {
self.runtime.GlobalEnvironment.SetValue(name, value, false) self.runtime.globalStash.setValue(name, value, false)
} }
// Call the given JavaScript with a given this and arguments. // Call the given JavaScript with a given this and arguments.
@ -355,7 +363,7 @@ func (self Otto) setValue(name string, value Value) {
// //
func (self Otto) Call(source string, this interface{}, argumentList ...interface{}) (Value, error) { func (self Otto) Call(source string, this interface{}, argumentList ...interface{}) (Value, error) {
thisValue := UndefinedValue() thisValue := Value{}
construct := false construct := false
if strings.HasPrefix(source, "new ") { if strings.HasPrefix(source, "new ") {
@ -363,6 +371,12 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
construct = true construct = true
} }
// FIXME enterGlobalScope
self.runtime.enterGlobalScope()
defer func() {
self.runtime.leaveScope()
}()
if !construct && this == nil { if !construct && this == nil {
program, err := self.runtime.cmpl_parse("", source+"()") program, err := self.runtime.cmpl_parse("", source+"()")
if err == nil { if err == nil {
@ -373,7 +387,7 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
value = self.runtime.cmpl_evaluate_nodeCallExpression(node, argumentList) value = self.runtime.cmpl_evaluate_nodeCallExpression(node, argumentList)
}) })
if err != nil { if err != nil {
return UndefinedValue(), err return Value{}, err
} }
return value, nil return value, nil
} }
@ -382,7 +396,7 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
} else { } else {
value, err := self.ToValue(this) value, err := self.ToValue(this)
if err != nil { if err != nil {
return UndefinedValue(), err return Value{}, err
} }
thisValue = value thisValue = value
} }
@ -392,20 +406,20 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
fn, err := self.Run(source) fn, err := self.Run(source)
if err != nil { if err != nil {
return UndefinedValue(), err return Value{}, err
} }
if construct { if construct {
result, err := fn.constructSafe(this, argumentList...) result, err := fn.constructSafe(self.runtime, this, argumentList...)
if err != nil { if err != nil {
return UndefinedValue(), err return Value{}, err
} }
return result, nil return result, nil
} }
result, err := fn.Call(this, argumentList...) result, err := fn.Call(this, argumentList...)
if err != nil { if err != nil {
return UndefinedValue(), err return Value{}, err
} }
return result, nil return result, nil
} }
@ -441,23 +455,23 @@ func (self Otto) Object(source string) (*Object, error) {
// ToValue will convert an interface{} value to a value digestible by otto/JavaScript. // ToValue will convert an interface{} value to a value digestible by otto/JavaScript.
func (self Otto) ToValue(value interface{}) (Value, error) { func (self Otto) ToValue(value interface{}) (Value, error) {
return self.runtime.ToValue(value) return self.runtime.safeToValue(value)
} }
// Copy will create a copy/clone of the runtime. // Copy will create a copy/clone of the runtime.
// //
// Copy is useful for saving some processing time when creating many similar // Copy is useful for saving some time when creating many similar runtimes.
// runtimes.
// //
// This implementation is alpha-ish, and works by introspecting every part of the runtime // This method works by walking the original runtime and cloning each object, scope, stash,
// and reallocating and then relinking everything back together. Please report if you // etc. into a new runtime.
// notice any inadvertent sharing of data between copies. //
func (self *Otto) Copy() *Otto { // Be on the lookout for memory leaks or inadvertent sharing of resources.
otto := &Otto{ func (in *Otto) Copy() *Otto {
runtime: self.runtime.clone(), out := &Otto{
runtime: in.runtime.clone(),
} }
otto.runtime.Otto = otto out.runtime.otto = out
return otto return out
} }
// Object{} // Object{}
@ -495,7 +509,7 @@ func (self Object) Call(name string, argumentList ...interface{}) (Value, error)
function, err := self.Get(name) function, err := self.Get(name)
if err != nil { if err != nil {
return UndefinedValue(), err return Value{}, err
} }
return function.Call(self.Value(), argumentList...) return function.Call(self.Value(), argumentList...)
} }
@ -507,10 +521,13 @@ func (self Object) Value() Value {
// Get the value of the property with the given name. // Get the value of the property with the given name.
func (self Object) Get(name string) (Value, error) { func (self Object) Get(name string) (Value, error) {
value := UndefinedValue() value := Value{}
err := catchPanic(func() { err := catchPanic(func() {
value = self.object.get(name) value = self.object.get(name)
}) })
if !value.safe() {
value = Value{}
}
return value, err return value, err
} }
@ -520,7 +537,7 @@ func (self Object) Get(name string) (Value, error) {
// or there is an error during conversion of the given value. // or there is an error during conversion of the given value.
func (self Object) Set(name string, value interface{}) error { func (self Object) Set(name string, value interface{}) error {
{ {
value, err := self.object.runtime.ToValue(value) value, err := self.object.runtime.safeToValue(value)
if err != nil { if err != nil {
return err return err
} }

View File

@ -37,7 +37,12 @@ func main() {
return err return err
}() }()
if err != nil { if err != nil {
fmt.Println(err) switch err := err.(type) {
case *otto.Error:
fmt.Print(err.String())
default:
fmt.Println(err)
}
os.Exit(64) os.Exit(64)
} }
} }

View File

@ -67,13 +67,13 @@ func getValueOfArrayIndex(array []Value, index int) (Value, bool) {
return value, true return value, true
} }
} }
return UndefinedValue(), false return Value{}, false
} }
// A range index can be anything from 0 up to length. It is NOT safe to use as an index // 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. // to an array, but is useful for slicing and in some ECMA algorithms.
func valueToRangeIndex(indexValue Value, length int64, negativeIsZero bool) int64 { func valueToRangeIndex(indexValue Value, length int64, negativeIsZero bool) int64 {
index := toInteger(indexValue).value index := indexValue.number().int64
if negativeIsZero { if negativeIsZero {
if index < 0 { if index < 0 {
index = 0 index = 0
@ -129,7 +129,7 @@ func rangeStartLength(source []Value, size int64) (start, length int64) {
lengthValue := valueOfArrayIndex(source, 1) lengthValue := valueOfArrayIndex(source, 1)
if !lengthValue.IsUndefined() { if !lengthValue.IsUndefined() {
// Which it is not, so get the value as an array index // Which it is not, so get the value as an array index
length = toInteger(lengthValue).value length = lengthValue.number().int64
} }
return return
} }

View File

@ -18,14 +18,14 @@ func TestOttoError(t *testing.T) {
is(err, "TypeError: Nothing happens.") is(err, "TypeError: Nothing happens.")
_, err = ToValue([]byte{}) _, err = ToValue([]byte{})
is(err, "TypeError: Invalid value (slice): Missing runtime: [] ([]uint8)") is(err, "TypeError: invalid value (slice): missing runtime: [] ([]uint8)")
_, err = vm.Run(` _, err = vm.Run(`
(function(){ (function(){
return abcdef.length return abcdef.length
})() })()
`) `)
is(err, "ReferenceError: abcdef is not defined") is(err, "ReferenceError: 'abcdef' is not defined")
_, err = vm.Run(` _, err = vm.Run(`
function start() { function start() {
@ -35,14 +35,14 @@ func TestOttoError(t *testing.T) {
xyzzy() xyzzy()
`) `)
is(err, "ReferenceError: xyzzy is not defined") is(err, "ReferenceError: 'xyzzy' is not defined")
_, err = vm.Run(` _, err = vm.Run(`
// Just a comment // Just a comment
xyzzy xyzzy
`) `)
is(err, "ReferenceError: xyzzy is not defined") is(err, "ReferenceError: 'xyzzy' is not defined")
}) })
} }

View File

@ -649,7 +649,7 @@ func Test_PrimitiveValueObjectValue(t *testing.T) {
test, _ := test() test, _ := test()
Number11 := test(`new Number(11)`) Number11 := test(`new Number(11)`)
is(toFloat(Number11), 11) is(Number11.float64(), 11)
}) })
} }
@ -700,24 +700,22 @@ func Test_evalDirectIndirect(t *testing.T) {
tt(t, func() { tt(t, func() {
test, _ := test() test, _ := test()
// (function () {return this;}()).abc = "global";
test(` test(`
var abc = "global"; var abc = "global";
(function(){ (function(){
try { try {
var _eval = eval; var _eval = eval;
var abc = "function"; var abc = "function";
if ( return [
_eval("\'global\' === abc") === true && // eval (Indirect) _eval("\'global\' === abc"), // eval (Indirect)
eval("\'function\' === abc") === true // eval (Direct) eval("\'function\' === abc"), // eval (Direct)
) { ];
return true;
}
return false;
} finally { } finally {
delete this.abc; delete this.abc;
} }
})() })();
`, true) `, "true,true")
}) })
} }
@ -766,7 +764,7 @@ func TestShouldError(t *testing.T) {
test(`raise: test(`raise:
xyzzy xyzzy
throw new TypeError("Nothing happens.") throw new TypeError("Nothing happens.")
`, "ReferenceError: xyzzy is not defined") `, "ReferenceError: 'xyzzy' is not defined")
}) })
} }
@ -868,8 +866,8 @@ func TestDotMember(t *testing.T) {
func Test_stringToFloat(t *testing.T) { func Test_stringToFloat(t *testing.T) {
tt(t, func() { tt(t, func() {
is(stringToFloat("10e10000"), _Infinity) is(parseNumber("10e10000"), _Infinity)
is(stringToFloat("10e10_."), _NaN) is(parseNumber("10e10_."), _NaN)
}) })
} }
@ -1014,7 +1012,9 @@ func TestOttoCall_throw(t *testing.T) {
// Looks like this has been broken for a while... what // Looks like this has been broken for a while... what
// behavior do we want here? // behavior do we want here?
return if true {
return
}
tt(t, func() { tt(t, func() {
test, vm := test() test, vm := test()
@ -1024,7 +1024,7 @@ func TestOttoCall_throw(t *testing.T) {
call.Otto.Call(`throw eval`, nil, "({ def: 3.14159 })") call.Otto.Call(`throw eval`, nil, "({ def: 3.14159 })")
} }
call.Otto.Call(`throw Error`, nil, "abcdef") call.Otto.Call(`throw Error`, nil, "abcdef")
return UndefinedValue() return Value{}
}) })
// TODO try { abc(); } catch (err) { error = err } // TODO try { abc(); } catch (err) { error = err }
// Possible unrelated error case: // Possible unrelated error case:
@ -1099,6 +1099,54 @@ func TestOttoCopy(t *testing.T) {
`) `)
is(err, nil) is(err, nil)
is(value, "Xyzzy0[object Object]") is(value, "Xyzzy0[object Object]")
{
vm0 := New()
vm0.Run(`
var global = (function () {return this;}())
var abc = 0;
var vm = "vm0";
var def = (function(){
var jkl = 0;
var abc = function() {
global.abc += 1;
jkl += 1;
return 1;
};
return function() {
return [ vm, global.abc, jkl, abc() ];
};
})();
`)
value, err := vm0.Run(`
def();
`)
is(err, nil)
is(value, "vm0,0,0,1")
vm1 := vm0.Copy()
vm1.Set("vm", "vm1")
value, err = vm1.Run(`
def();
`)
is(err, nil)
is(value, "vm1,1,1,1")
value, err = vm0.Run(`
def();
`)
is(err, nil)
is(value, "vm0,1,1,1")
value, err = vm1.Run(`
def();
`)
is(err, nil)
is(value, "vm1,2,2,1")
}
}) })
} }
@ -1109,11 +1157,11 @@ func TestOttoCall_clone(t *testing.T) {
{ {
// FIXME terst, Check how this comparison is done // FIXME terst, Check how this comparison is done
is(rt.Global.Array.prototype, rt.Global.FunctionPrototype) is(rt.global.Array.prototype, rt.global.FunctionPrototype)
is(rt.Global.ArrayPrototype, "!=", nil) is(rt.global.ArrayPrototype, "!=", nil)
is(rt.Global.Array.runtime, rt) is(rt.global.Array.runtime, rt)
is(rt.Global.Array.prototype.runtime, rt) is(rt.global.Array.prototype.runtime, rt)
is(rt.Global.Array.get("prototype")._object().runtime, rt) is(rt.global.Array.get("prototype")._object().runtime, rt)
} }
{ {
@ -1128,14 +1176,14 @@ func TestOttoCall_clone(t *testing.T) {
is(value, "1,2,3") is(value, "1,2,3")
object := value._object() object := value._object()
is(object, "!=", nil) is(object, "!=", nil)
is(object.prototype, rt.Global.ArrayPrototype) is(object.prototype, rt.global.ArrayPrototype)
value, err = vm.Run(`Array.prototype`) value, err = vm.Run(`Array.prototype`)
is(err, nil) is(err, nil)
object = value._object() object = value._object()
is(object.runtime, rt) is(object.runtime, rt)
is(object, "!=", nil) is(object, "!=", nil)
is(object, rt.Global.ArrayPrototype) is(object, rt.global.ArrayPrototype)
} }
{ {

View File

@ -85,10 +85,10 @@ func (self _property) get(this *_object) Value {
return value return value
case _propertyGetSet: case _propertyGetSet:
if value[0] != nil { if value[0] != nil {
return value[0].callGet(toValue(this)) return value[0].call(toValue(this), nil, false, nativeFrame)
} }
} }
return UndefinedValue() return Value{}
} }
func (self _property) isAccessorDescriptor() bool { func (self _property) isAccessorDescriptor() bool {
@ -115,16 +115,16 @@ func (self _property) isEmpty() bool {
// _enumerableValue, _enumerableTrue, _enumerableFalse? // _enumerableValue, _enumerableTrue, _enumerableFalse?
// .enumerableValue() .enumerableExists() // .enumerableValue() .enumerableExists()
func toPropertyDescriptor(value Value) (descriptor _property) { func toPropertyDescriptor(rt *_runtime, value Value) (descriptor _property) {
objectDescriptor := value._object() objectDescriptor := value._object()
if objectDescriptor == nil { if objectDescriptor == nil {
panic(newTypeError()) panic(rt.panicTypeError())
} }
{ {
descriptor.mode = modeSetMask // Initially nothing is set descriptor.mode = modeSetMask // Initially nothing is set
if objectDescriptor.hasProperty("enumerable") { if objectDescriptor.hasProperty("enumerable") {
if objectDescriptor.get("enumerable").toBoolean() { if objectDescriptor.get("enumerable").bool() {
descriptor.enumerateOn() descriptor.enumerateOn()
} else { } else {
descriptor.enumerateOff() descriptor.enumerateOff()
@ -132,7 +132,7 @@ func toPropertyDescriptor(value Value) (descriptor _property) {
} }
if objectDescriptor.hasProperty("configurable") { if objectDescriptor.hasProperty("configurable") {
if objectDescriptor.get("configurable").toBoolean() { if objectDescriptor.get("configurable").bool() {
descriptor.configureOn() descriptor.configureOn()
} else { } else {
descriptor.configureOff() descriptor.configureOff()
@ -140,7 +140,7 @@ func toPropertyDescriptor(value Value) (descriptor _property) {
} }
if objectDescriptor.hasProperty("writable") { if objectDescriptor.hasProperty("writable") {
if objectDescriptor.get("writable").toBoolean() { if objectDescriptor.get("writable").bool() {
descriptor.writeOn() descriptor.writeOn()
} else { } else {
descriptor.writeOff() descriptor.writeOff()
@ -155,7 +155,7 @@ func toPropertyDescriptor(value Value) (descriptor _property) {
value := objectDescriptor.get("get") value := objectDescriptor.get("get")
if value.IsDefined() { if value.IsDefined() {
if !value.isCallable() { if !value.isCallable() {
panic(newTypeError()) panic(rt.panicTypeError())
} }
getter = value._object() getter = value._object()
getterSetter = true getterSetter = true
@ -169,7 +169,7 @@ func toPropertyDescriptor(value Value) (descriptor _property) {
value := objectDescriptor.get("set") value := objectDescriptor.get("set")
if value.IsDefined() { if value.IsDefined() {
if !value.isCallable() { if !value.isCallable() {
panic(newTypeError()) panic(rt.panicTypeError())
} }
setter = value._object() setter = value._object()
getterSetter = true getterSetter = true
@ -181,14 +181,14 @@ func toPropertyDescriptor(value Value) (descriptor _property) {
if getterSetter { if getterSetter {
if descriptor.writeSet() { if descriptor.writeSet() {
panic(newTypeError()) panic(rt.panicTypeError())
} }
descriptor.value = _propertyGetSet{getter, setter} descriptor.value = _propertyGetSet{getter, setter}
} }
if objectDescriptor.hasProperty("value") { if objectDescriptor.hasProperty("value") {
if getterSetter { if getterSetter {
panic(newTypeError()) panic(rt.panicTypeError())
} }
descriptor.value = objectDescriptor.get("value") descriptor.value = objectDescriptor.get("value")
} }
@ -203,11 +203,11 @@ func (self *_runtime) fromPropertyDescriptor(descriptor _property) *_object {
object.defineProperty("writable", toValue_bool(descriptor.writable()), 0111, false) object.defineProperty("writable", toValue_bool(descriptor.writable()), 0111, false)
} else if descriptor.isAccessorDescriptor() { } else if descriptor.isAccessorDescriptor() {
getSet := descriptor.value.(_propertyGetSet) getSet := descriptor.value.(_propertyGetSet)
get := UndefinedValue() get := Value{}
if getSet[0] != nil { if getSet[0] != nil {
get = toValue_object(getSet[0]) get = toValue_object(getSet[0])
} }
set := UndefinedValue() set := Value{}
if getSet[1] != nil { if getSet[1] != nil {
set = toValue_object(getSet[1]) set = toValue_object(getSet[1])
} }

View File

@ -6,43 +6,63 @@ import (
"testing" "testing"
) )
type testStruct struct { type _abcStruct struct {
Abc bool Abc bool
Def int Def int
Ghi string Ghi string
Jkl interface{} Jkl interface{}
Mno _mnoStruct
Pqr map[string]int8
} }
func (t *testStruct) FuncPointerReciever() string { func (abc _abcStruct) String() string {
return abc.Ghi
}
func (abc *_abcStruct) FuncPointer() string {
return "abc" return "abc"
} }
func (t testStruct) FuncNoArgsNoRet() { func (abc _abcStruct) Func() {
return return
} }
func (t testStruct) FuncNoArgs() string { func (abc _abcStruct) FuncReturn1() string {
return "abc" return "abc"
} }
func (t testStruct) FuncNoArgsMultRet() (string, error) { func (abc _abcStruct) FuncReturn2() (string, error) {
return "def", nil return "def", nil
} }
func (t testStruct) FuncOneArgs(a string) string { func (abc _abcStruct) Func1Return1(a string) string {
return a return a
} }
func (t testStruct) FuncMultArgs(a, b string) string { func (abc _abcStruct) Func2Return1(x, y string) string {
return a + b return x + y
} }
func (t testStruct) FuncVarArgs(as ...string) int { func (abc _abcStruct) FuncEllipsis(xyz ...string) int {
return len(as) return len(xyz)
}
func (abc _abcStruct) FuncReturnStruct() _mnoStruct {
return _mnoStruct{}
}
type _mnoStruct struct {
Ghi string
}
func (mno _mnoStruct) Func() string {
return "mno"
} }
func TestReflect(t *testing.T) { func TestReflect(t *testing.T) {
return if true {
return
}
tt(t, func() { tt(t, func() {
// Testing dbgf // Testing dbgf
// These should panic // These should panic
@ -55,15 +75,11 @@ func Test_reflectStruct(t *testing.T) {
tt(t, func() { tt(t, func() {
test, vm := test() test, vm := test()
// testStruct // _abcStruct
{ {
abc := &testStruct{} abc := &_abcStruct{}
vm.Set("abc", abc) vm.Set("abc", abc)
test(`
abc.FuncPointerReciever();
`, "abc")
test(` test(`
[ abc.Abc, abc.Ghi ]; [ abc.Abc, abc.Ghi ];
`, "false,") `, "false,")
@ -75,7 +91,7 @@ func Test_reflectStruct(t *testing.T) {
[ abc.Abc, abc.Ghi ]; [ abc.Abc, abc.Ghi ];
`, "true,Nothing happens.") `, "true,Nothing happens.")
*abc = testStruct{} *abc = _abcStruct{}
test(` test(`
[ abc.Abc, abc.Ghi ]; [ abc.Abc, abc.Ghi ];
@ -109,24 +125,40 @@ func Test_reflectStruct(t *testing.T) {
is(abc.Def, 451) is(abc.Def, 451)
test(` test(`
abc.FuncNoArgsNoRet(); abc.FuncPointer();
`, "abc")
test(`
abc.Func();
`, "undefined") `, "undefined")
test(` test(`
abc.FuncNoArgs(); abc.FuncReturn1();
`, "abc") `, "abc")
test(` test(`
abc.FuncOneArgs("abc"); abc.Func1Return1("abc");
`, "abc") `, "abc")
test(` test(`
abc.FuncMultArgs("abc", "def"); abc.Func2Return1("abc", "def");
`, "abcdef") `, "abcdef")
test(` test(`
abc.FuncVarArgs("abc", "def", "ghi"); abc.FuncEllipsis("abc", "def", "ghi");
`, 3) `, 3)
test(`raise: test(`raise:
abc.FuncNoArgsMultRet(); abc.FuncReturn2();
`, "TypeError") `, "TypeError")
test(`
abc.FuncReturnStruct();
`, "[object Object]")
test(`
abc.FuncReturnStruct().Func();
`, "mno")
} }
}) })
} }
@ -387,7 +419,7 @@ func Test_reflectMapInterface(t *testing.T) {
"jkl": "jkl", "jkl": "jkl",
} }
vm.Set("abc", abc) vm.Set("abc", abc)
vm.Set("mno", &testStruct{}) vm.Set("mno", &_abcStruct{})
test(` test(`
abc.xyz = "pqr"; abc.xyz = "pqr";
@ -402,10 +434,50 @@ func Test_reflectMapInterface(t *testing.T) {
is(abc["xyz"], "pqr") is(abc["xyz"], "pqr")
is(abc["ghi"], "[object Object]") is(abc["ghi"], "[object Object]")
is(abc["jkl"], float64(3.14159)) is(abc["jkl"], float64(3.14159))
mno, valid := abc["mno"].(*testStruct) mno, valid := abc["mno"].(*_abcStruct)
is(valid, true) is(valid, true)
is(mno.Abc, true) is(mno.Abc, true)
is(mno.Ghi, "Something happens.") is(mno.Ghi, "Something happens.")
} }
}) })
} }
func TestPassthrough(t *testing.T) {
tt(t, func() {
test, vm := test()
{
abc := &_abcStruct{
Mno: _mnoStruct{
Ghi: "<Mno.Ghi>",
},
}
vm.Set("abc", abc)
test(`
abc.Mno.Ghi;
`, "<Mno.Ghi>")
vm.Set("pqr", map[string]int8{
"xyzzy": 0,
"Nothing happens.": 1,
})
test(`
abc.Ghi = "abc";
abc.Pqr = pqr;
abc.Pqr["Nothing happens."];
`, 1)
mno := _mnoStruct{
Ghi: "<mno.Ghi>",
}
vm.Set("mno", mno)
test(`
abc.Mno = mno;
abc.Mno.Ghi;
`, "<mno.Ghi>")
}
})
}

View File

@ -160,7 +160,10 @@ func TestRegExp_toString(t *testing.T) {
} }
func TestRegExp_zaacbbbcac(t *testing.T) { func TestRegExp_zaacbbbcac(t *testing.T) {
return if true {
return
}
tt(t, func() { tt(t, func() {
test, _ := test() test, _ := test()

View File

@ -22,9 +22,9 @@ func newReturnResult(value Value) _result {
} }
func newContinueResult(target string) _result { func newContinueResult(target string) _result {
return _result{resultContinue, emptyValue(), target} return _result{resultContinue, emptyValue, target}
} }
func newBreakResult(target string) _result { func newBreakResult(target string) _result {
return _result{resultBreak, emptyValue(), target} return _result{resultBreak, emptyValue, target}
} }

View File

@ -3,6 +3,7 @@ package otto
import ( import (
"errors" "errors"
"reflect" "reflect"
"sync"
"github.com/robertkrimen/otto/ast" "github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/parser" "github.com/robertkrimen/otto/parser"
@ -45,100 +46,54 @@ type _global struct {
} }
type _runtime struct { type _runtime struct {
Stack [](*_executionContext) global _global
globalObject *_object
GlobalObject *_object globalStash *_objectStash
GlobalEnvironment *_objectEnvironment scope *_scope
otto *Otto
Global _global eval *_object // The builtin eval, for determine indirect versus direct invocation
eval *_object // The builtin eval, for determine indirect versus direct invocation
Otto *Otto
labels []string // FIXME labels []string // FIXME
lck sync.Mutex
} }
func (self *_runtime) EnterGlobalExecutionContext() { func (self *_runtime) enterScope(scope *_scope) {
self.EnterExecutionContext(newExecutionContext(self.GlobalEnvironment, self.GlobalEnvironment, self.GlobalObject)) scope.outer = self.scope
self.scope = scope
} }
func (self *_runtime) EnterExecutionContext(scope *_executionContext) { func (self *_runtime) leaveScope() {
self.Stack = append(self.Stack, scope) self.scope = self.scope.outer
} }
func (self *_runtime) LeaveExecutionContext() { // FIXME This is used in two places (cloning)
self.Stack = self.Stack[:len(self.Stack)-1] func (self *_runtime) enterGlobalScope() {
self.enterScope(newScope(self.globalStash, self.globalStash, self.globalObject))
} }
func (self *_runtime) _executionContext(depth int) *_executionContext { func (self *_runtime) enterFunctionScope(outer _stash, this Value) *_fnStash {
if depth == 0 { if outer == nil {
return self.Stack[len(self.Stack)-1] outer = self.globalStash
} }
if len(self.Stack)-1+depth >= 0 { stash := self.newFunctionStash(outer)
return self.Stack[len(self.Stack)-1+depth]
}
return nil
}
func (self *_runtime) EnterFunctionExecutionContext(function *_object, this Value) *_functionEnvironment {
scopeEnvironment := function.functionValue().call.ScopeEnvironment()
if scopeEnvironment == nil {
scopeEnvironment = self.GlobalEnvironment
}
environment := self.newFunctionEnvironment(scopeEnvironment)
var thisObject *_object var thisObject *_object
switch this._valueType { switch this.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
thisObject = self.GlobalObject thisObject = self.globalObject
default: default:
thisObject = self.toObject(this) thisObject = self.toObject(this)
} }
self.EnterExecutionContext(newExecutionContext(environment, environment, thisObject)) self.enterScope(newScope(stash, stash, thisObject))
return environment return stash
} }
func (self *_runtime) EnterEvalExecutionContext(call FunctionCall) { func (self *_runtime) putValue(reference _reference, value Value) {
// Skip the current function lexical/variable environment, which is of the function execution context call name := reference.putValue(value)
// to eval (the global execution context). Instead, execute in the context of where the eval was called, if name != "" {
// which is essentially dynamic scoping // Why? -- If reference.base == nil
parent := self._executionContext(-1) // strict = false
new := newExecutionContext(parent.LexicalEnvironment, parent.VariableEnvironment, parent.this) self.globalObject.defineProperty(name, value, 0111, false)
// FIXME Make passing through of self.GlobalObject more general? Whenever newExecutionContext is passed a nil object?
new.eval = true
self.EnterExecutionContext(new)
}
func (self *_runtime) GetValue(value Value) Value {
if value.isReference() {
return value.reference().GetValue()
} }
return value
}
func (self *_runtime) PutValue(reference _reference, value Value) {
if !reference.PutValue(value) {
// Why? -- If reference.Base == nil
strict := false
self.GlobalObject.defineProperty(reference.GetName(), value, 0111, strict)
}
}
func (self *_runtime) Call(function *_object, this Value, argumentList []Value, evalHint bool) Value {
// Pass eval boolean through to EnterFunctionExecutionContext for further testing
_functionEnvironment := self.EnterFunctionExecutionContext(function, this)
defer func() {
self.LeaveExecutionContext()
}()
if evalHint {
evalHint = function == self.eval // If evalHint is true, then it IS a direct eval
}
callValue := function.functionValue().call.Dispatch(function, _functionEnvironment, self, this, argumentList, evalHint)
if value, valid := callValue.value.(_result); valid {
return value.value
}
return callValue
} }
func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exception bool) { func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exception bool) {
@ -155,10 +110,7 @@ func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exce
switch caught := caught.(type) { switch caught := caught.(type) {
case _error: case _error:
exception = true exception = true
tryValue = toValue_object(self.newError(caught.Name, caught.MessageValue())) tryValue = toValue_object(self.newError(caught.name, caught.messageValue()))
//case *_syntaxError:
// exception = true
// tryValue = toValue_object(self.newError("SyntaxError", toValue_string(caught.Message)))
case Value: case Value:
exception = true exception = true
tryValue = caught tryValue = caught
@ -172,30 +124,12 @@ func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exce
return return
} }
// _executionContext Proxy
func (self *_runtime) localGet(name string) Value {
return self._executionContext(0).getValue(name)
}
func (self *_runtime) localSet(name string, value Value) {
self._executionContext(0).setValue(name, value, false)
}
func (self *_runtime) VariableEnvironment() _environment {
return self._executionContext(0).VariableEnvironment
}
func (self *_runtime) LexicalEnvironment() _environment {
return self._executionContext(0).LexicalEnvironment
}
// toObject // toObject
func (self *_runtime) toObject(value Value) *_object { func (self *_runtime) toObject(value Value) *_object {
switch value._valueType { switch value.kind {
case valueEmpty, valueUndefined, valueNull: case valueEmpty, valueUndefined, valueNull:
panic(newTypeError()) panic(self.panicTypeError())
case valueBoolean: case valueBoolean:
return self.newBoolean(value) return self.newBoolean(value)
case valueString: case valueString:
@ -205,11 +139,11 @@ func (self *_runtime) toObject(value Value) *_object {
case valueObject: case valueObject:
return value._object() return value._object()
} }
panic(newTypeError()) panic(self.panicTypeError())
} }
func (self *_runtime) objectCoerce(value Value) (*_object, error) { func (self *_runtime) objectCoerce(value Value) (*_object, error) {
switch value._valueType { switch value.kind {
case valueUndefined: case valueUndefined:
return nil, errors.New("undefined") return nil, errors.New("undefined")
case valueNull: case valueNull:
@ -223,20 +157,20 @@ func (self *_runtime) objectCoerce(value Value) (*_object, error) {
case valueObject: case valueObject:
return value._object(), nil return value._object(), nil
} }
panic(newTypeError()) panic(self.panicTypeError())
} }
func checkObjectCoercible(value Value) { func checkObjectCoercible(rt *_runtime, value Value) {
isObject, mustCoerce := testObjectCoercible(value) isObject, mustCoerce := testObjectCoercible(value)
if !isObject && !mustCoerce { if !isObject && !mustCoerce {
panic(newTypeError()) panic(rt.panicTypeError())
} }
} }
// testObjectCoercible // testObjectCoercible
func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) { func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
switch value._valueType { switch value.kind {
case valueReference, valueEmpty, valueNull, valueUndefined: case valueReference, valueEmpty, valueNull, valueUndefined:
return false, false return false, false
case valueNumber, valueString, valueBoolean: case valueNumber, valueString, valueBoolean:
@ -249,8 +183,8 @@ func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
return return
} }
func (self *_runtime) ToValue(value interface{}) (Value, error) { func (self *_runtime) safeToValue(value interface{}) (Value, error) {
result := UndefinedValue() result := Value{}
err := catchPanic(func() { err := catchPanic(func() {
result = self.toValue(value) result = self.toValue(value)
}) })
@ -267,7 +201,8 @@ func (self *_runtime) toValue(value interface{}) Value {
return toValue_object(self.newNativeFunction("", value)) return toValue_object(self.newNativeFunction("", value))
case Object, *Object, _object, *_object: case Object, *Object, _object, *_object:
// Nothing happens. // Nothing happens.
// FIXME // FIXME We should really figure out what can come here.
// This catch-all is ugly.
default: default:
{ {
value := reflect.ValueOf(value) value := reflect.ValueOf(value)
@ -280,19 +215,21 @@ func (self *_runtime) toValue(value interface{}) Value {
return toValue_object(self.newGoArray(value)) return toValue_object(self.newGoArray(value))
} }
case reflect.Func: case reflect.Func:
// TODO Maybe cache this?
return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value { return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value {
args := make([]reflect.Value, len(call.ArgumentList)) in := make([]reflect.Value, len(call.ArgumentList))
for i, a := range call.ArgumentList { for i, value := range call.ArgumentList {
args[i] = reflect.ValueOf(a.export()) in[i] = reflect.ValueOf(value.export())
} }
retvals := value.Call(args) out := value.Call(in)
if len(retvals) > 1 { if len(out) == 1 {
panic(newTypeError()) return self.toValue(out[0].Interface())
} else if len(retvals) == 1 { } else if len(out) == 0 {
return toValue(retvals[0].Interface()) return Value{}
} }
return UndefinedValue()
panic(call.runtime.panicTypeError())
})) }))
case reflect.Struct: case reflect.Struct:
return toValue_object(self.newGoStructObject(value)) return toValue_object(self.newGoStructObject(value))
@ -310,13 +247,13 @@ func (self *_runtime) toValue(value interface{}) Value {
func (runtime *_runtime) newGoSlice(value reflect.Value) *_object { func (runtime *_runtime) newGoSlice(value reflect.Value) *_object {
self := runtime.newGoSliceObject(value) self := runtime.newGoSliceObject(value)
self.prototype = runtime.Global.ArrayPrototype self.prototype = runtime.global.ArrayPrototype
return self return self
} }
func (runtime *_runtime) newGoArray(value reflect.Value) *_object { func (runtime *_runtime) newGoArray(value reflect.Value) *_object {
self := runtime.newGoArrayObject(value) self := runtime.newGoArrayObject(value)
self.prototype = runtime.Global.ArrayPrototype self.prototype = runtime.global.ArrayPrototype
return self return self
} }
@ -344,7 +281,7 @@ func (self *_runtime) parseSource(src interface{}) (*_nodeProgram, *ast.Program,
} }
func (self *_runtime) cmpl_run(src interface{}) (Value, error) { func (self *_runtime) cmpl_run(src interface{}) (Value, error) {
result := UndefinedValue() result := Value{}
cmpl_program, program, err := self.parseSource(src) cmpl_program, program, err := self.parseSource(src)
if err != nil { if err != nil {
return result, err return result, err
@ -353,13 +290,13 @@ func (self *_runtime) cmpl_run(src interface{}) (Value, error) {
cmpl_program = cmpl_parse(program) cmpl_program = cmpl_parse(program)
} }
err = catchPanic(func() { err = catchPanic(func() {
result = self.cmpl_evaluate_nodeProgram(cmpl_program) result = self.cmpl_evaluate_nodeProgram(cmpl_program, false)
}) })
switch result._valueType { switch result.kind {
case valueEmpty: case valueEmpty:
result = UndefinedValue() result = Value{}
case valueReference: case valueReference:
result = self.GetValue(result) result = result.resolve()
} }
return result, err return result, err
} }
@ -373,12 +310,12 @@ func (self *_runtime) parseThrow(err error) {
{ {
err := err[0] err := err[0]
if err.Message == "Invalid left-hand side in assignment" { if err.Message == "Invalid left-hand side in assignment" {
panic(newReferenceError(err.Message)) panic(self.panicReferenceError(err.Message))
} }
panic(newSyntaxError(err.Message)) panic(self.panicSyntaxError(err.Message))
} }
} }
panic(newSyntaxError(err.Error())) panic(self.panicSyntaxError(err.Error()))
} }
func (self *_runtime) parseOrThrow(source string) *ast.Program { func (self *_runtime) parseOrThrow(source string) *ast.Program {

View File

@ -359,7 +359,7 @@ func TestComparison(t *testing.T) {
test("1 == 'Hello, World.'", false) test("1 == 'Hello, World.'", false)
is(stringToFloat("-1"), -1) is(parseNumber("-1"), -1)
test("0+Object", "0function Object() { [native code] }") test("0+Object", "0function Object() { [native code] }")
}) })

View File

@ -0,0 +1,34 @@
package otto
// _scope:
// entryFile
// entryIdx
// top?
// outer => nil
// _stash:
// lexical
// variable
//
// _thisStash (ObjectEnvironment)
// _fnStash
// _dclStash
// An ECMA-262 ExecutionContext
type _scope struct {
lexical _stash
variable _stash
this *_object
eval bool // Replace this with kind?
outer *_scope
frame _frame
}
func newScope(lexical _stash, variable _stash, this *_object) *_scope {
return &_scope{
lexical: lexical,
variable: variable,
this: this,
}
}

View File

@ -6,8 +6,6 @@ import (
func TestScript(t *testing.T) { func TestScript(t *testing.T) {
tt(t, func() { tt(t, func() {
return
vm := New() vm := New()
script, err := vm.Compile("xyzzy", `var abc; if (!abc) abc = 0; abc += 2; abc;`) script, err := vm.Compile("xyzzy", `var abc; if (!abc) abc = 0; abc += 2; abc;`)
@ -20,6 +18,10 @@ func TestScript(t *testing.T) {
is(err, nil) is(err, nil)
is(value, 2) is(value, 2)
if true {
return
}
tmp, err := script.marshalBinary() tmp, err := script.marshalBinary()
is(err, nil) is(err, nil)
is(len(tmp), 1228) is(len(tmp), 1228)

View File

@ -0,0 +1,275 @@
package otto
import (
"fmt"
)
// ======
// _stash
// ======
type _stash interface {
hasBinding(string) bool //
createBinding(string, bool, Value) // CreateMutableBinding
setBinding(string, Value, bool) // SetMutableBinding
getBinding(string, bool) Value // GetBindingValue
deleteBinding(string) bool //
setValue(string, Value, bool) // createBinding + setBinding
outer() _stash
runtime() *_runtime
newReference(string, bool, _at) _reference
clone(clone *_clone) _stash
}
// ==========
// _objectStash
// ==========
type _objectStash struct {
_runtime *_runtime
_outer _stash
object *_object
}
func (self *_objectStash) runtime() *_runtime {
return self._runtime
}
func (runtime *_runtime) newObjectStash(object *_object, outer _stash) *_objectStash {
if object == nil {
object = runtime.newBaseObject()
object.class = "environment"
}
return &_objectStash{
_runtime: runtime,
_outer: outer,
object: object,
}
}
func (in *_objectStash) clone(clone *_clone) _stash {
out, exists := clone.objectStash(in)
if exists {
return out
}
*out = _objectStash{
clone.runtime,
clone.stash(in._outer),
clone.object(in.object),
}
return out
}
func (self *_objectStash) hasBinding(name string) bool {
return self.object.hasProperty(name)
}
func (self *_objectStash) createBinding(name string, deletable bool, value Value) {
if self.object.hasProperty(name) {
panic(hereBeDragons())
}
mode := _propertyMode(0111)
if !deletable {
mode = _propertyMode(0110)
}
// TODO False?
self.object.defineProperty(name, value, mode, false)
}
func (self *_objectStash) setBinding(name string, value Value, strict bool) {
self.object.put(name, value, strict)
}
func (self *_objectStash) setValue(name string, value Value, throw bool) {
if !self.hasBinding(name) {
self.createBinding(name, true, value) // Configurable by default
} else {
self.setBinding(name, value, throw)
}
}
func (self *_objectStash) getBinding(name string, throw bool) Value {
if self.object.hasProperty(name) {
return self.object.get(name)
}
if throw { // strict?
panic(self._runtime.panicReferenceError("Not Defined", name))
}
return Value{}
}
func (self *_objectStash) deleteBinding(name string) bool {
return self.object.delete(name, false)
}
func (self *_objectStash) outer() _stash {
return self._outer
}
func (self *_objectStash) newReference(name string, strict bool, at _at) _reference {
return newPropertyReference(self._runtime, self.object, name, strict, at)
}
// =========
// _dclStash
// =========
type _dclStash struct {
_runtime *_runtime
_outer _stash
property map[string]_dclProperty
}
type _dclProperty struct {
value Value
mutable bool
deletable bool
readable bool
}
func (runtime *_runtime) newDeclarationStash(outer _stash) *_dclStash {
return &_dclStash{
_runtime: runtime,
_outer: outer,
property: map[string]_dclProperty{},
}
}
func (in *_dclStash) clone(clone *_clone) _stash {
out, exists := clone.dclStash(in)
if exists {
return out
}
property := make(map[string]_dclProperty, len(in.property))
for index, value := range in.property {
property[index] = clone.dclProperty(value)
}
*out = _dclStash{
clone.runtime,
clone.stash(in._outer),
property,
}
return out
}
func (self *_dclStash) hasBinding(name string) bool {
_, exists := self.property[name]
return exists
}
func (self *_dclStash) runtime() *_runtime {
return self._runtime
}
func (self *_dclStash) createBinding(name string, deletable bool, value Value) {
_, exists := self.property[name]
if exists {
panic(fmt.Errorf("createBinding: %s: already exists", name))
}
self.property[name] = _dclProperty{
value: value,
mutable: true,
deletable: deletable,
readable: false,
}
}
func (self *_dclStash) setBinding(name string, value Value, strict bool) {
property, exists := self.property[name]
if !exists {
panic(fmt.Errorf("setBinding: %s: missing", name))
}
if property.mutable {
property.value = value
self.property[name] = property
} else {
self._runtime.typeErrorResult(strict)
}
}
func (self *_dclStash) setValue(name string, value Value, throw bool) {
if !self.hasBinding(name) {
self.createBinding(name, false, value) // NOT deletable by default
} else {
self.setBinding(name, value, throw)
}
}
// FIXME This is called a __lot__
func (self *_dclStash) getBinding(name string, throw bool) Value {
property, exists := self.property[name]
if !exists {
panic(fmt.Errorf("getBinding: %s: missing", name))
}
if !property.mutable && !property.readable {
if throw { // strict?
panic(self._runtime.panicTypeError())
}
return Value{}
}
return property.value
}
func (self *_dclStash) deleteBinding(name string) bool {
property, exists := self.property[name]
if !exists {
return true
}
if !property.deletable {
return false
}
delete(self.property, name)
return true
}
func (self *_dclStash) outer() _stash {
return self._outer
}
func (self *_dclStash) newReference(name string, strict bool, _ _at) _reference {
return &_stashReference{
name: name,
base: self,
}
}
// ========
// _fnStash
// ========
type _fnStash struct {
_dclStash
arguments *_object
indexOfArgumentName map[string]string
}
func (runtime *_runtime) newFunctionStash(outer _stash) *_fnStash {
return &_fnStash{
_dclStash: _dclStash{
_runtime: runtime,
_outer: outer,
property: map[string]_dclProperty{},
},
}
}
func (in *_fnStash) clone(clone *_clone) _stash {
out, exists := clone.fnStash(in)
if exists {
return out
}
dclStash := in._dclStash.clone(clone).(*_dclStash)
index := make(map[string]string, len(in.indexOfArgumentName))
for name, value := range in.indexOfArgumentName {
index[name] = value
}
*out = _fnStash{
_dclStash: *dclStash,
arguments: clone.object(in.arguments),
indexOfArgumentName: index,
}
return out
}

Some files were not shown because too many files have changed in this diff Show More