1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-12 20:27:30 +08:00
otto/otto.go
2013-05-19 21:08:32 -07:00

384 lines
10 KiB
Go

/*
Package otto is a JavaScript parser and interpreter written natively in Go.
// Create a new runtime
Otto := otto.New()
Otto.Run(`
abc = 2 + 2
console.log("The value of abc is " + abc)
// The value of abc is 4
`)
value, err := Otto.Get("abc")
{
// value is an int64 with a value of 4
value, _ := value.ToInteger()
}
Otto.Set("def", 11)
Otto.Run(`
console.log("The value of def is " + def)
// The value of def is 11
`)
Otto.Set("xyzzy", "Nothing happens.")
Otto.Run(`
console.log(xyzzy.length) // 16
`)
value, _ = Otto.Run("xyzzy.length")
{
// value is an int64 with a value of 16
value, _ := value.ToInteger()
}
value, err = Otto.Run("abcdefghijlmnopqrstuvwxyz.length")
if err != nil {
// err = ReferenceError: abcdefghijlmnopqrstuvwxyz is not defined
// If there is an error, then value.IsUndefined() is true
...
}
Embedding a Go function in JavaScript:
Otto.Set("sayHello", func(call otto.FunctionCall) otto.Value {
fmt.Printf("Hello, %s.\n", call.Argument(0).String())
return otto.UndefinedValue()
})
Otto.Set("twoPlus", func(call otto.FunctionCall) otto.Value {
right, _ := call.Argument(0).ToInteger()
result, _ := Otto.ToValue(2 + right)
return result
})
result, _ = Otto.Run(`
// First, say a greeting
sayHello("Xyzzy") // Hello, Xyzzy.
sayHello() // Hello, undefined
result = twoPlus(2.0) // 4
`)
You can run (Go) JavaScript from the commandline with: http://github.com/robertkrimen/otto/tree/master/otto
$ go get -v github.com/robertkrimen/otto/otto
Run JavaScript by entering some source on stdin or by giving otto a filename:
$ otto example.js
Optionally include the JavaScript utility-belt library, underscore, with this import:
import (
"github.com/robertkrimen/otto"
_ "github.com/robertkrimen/otto/underscore"
)
// Now every otto runtime will come loaded with underscore
For more information: http://github.com/robertkrimen/otto/tree/master/underscore
Caveat Emptor
* For now, otto is a hybrid ECMA3/ECMA5 interpreter. Parts of the specification are still works in progress.
* For example, "use strict" will parse, but does nothing.
* Error reporting needs to be improved.
* Does not support the (?!) or (?=) regular expression syntax (because Go does not)
* JavaScript considers a vertical tab (\000B <VT>) to be part of the whitespace class (\s), while RE2 does not.
* Really, error reporting could use some improvement.
Regular Expression Syntax
Go translates JavaScript-style regular expressions into something that is "regexp" package compatible.
Unfortunately, JavaScript has positive lookahead, negative lookahead, and backreferencing,
all of which are not supported by Go's RE2-like engine: https://code.google.com/p/re2/wiki/Syntax
A brief discussion of these limitations: "Regexp (?!re)" https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/7qgSDWPIh_E
More information about RE2: https://code.google.com/p/re2/
JavaScript considers a vertical tab (\000B <VT>) to be part of the whitespace class (\s), while RE2 does not.
*/
package otto
import (
"fmt"
"github.com/robertkrimen/otto/registry"
"strings"
)
// Otto is the representation of the JavaScript runtime. Each instance of Otto has a self-contained namespace.
type Otto struct {
runtime *_runtime
}
// New will allocate a new JavaScript runtime
func New() *Otto {
self := &Otto{
runtime: newContext(),
}
self.runtime.Otto = self
self.Set("console", self.runtime.newConsole())
registry.Apply(func(entry registry.Entry) {
self.Run(entry.Source())
})
return self
}
// Run will allocate a new JavaScript runtime, run the given source
// on the allocated runtime, and return the runtime, resulting value, and
// error (if any).
func Run(source string) (*Otto, Value, error) {
otto := New()
value, err := otto.Run(source)
return otto, value, err
}
// Run will run the given source (parsing it first), returning the resulting value and error (if any)
//
// If the runtime is unable to parse the source, then this function will return undefined and the parse error (nothing
// will be evaluated in this case).
func (self Otto) Run(source string) (Value, error) {
return self.runtime.runSafe(source)
}
// Get the value of the top-level binding of the given name.
//
// If there is an error (like the binding not existing), then the value
// will be undefined.
func (self Otto) Get(name string) (Value, error) {
value := UndefinedValue()
err := catchPanic(func() {
value = self.getValue(name)
})
return value, err
}
func (self Otto) getValue(name string) Value {
return self.runtime.GlobalEnvironment.GetValue(name, false)
}
// Set the top-level binding of the given name to the given value.
//
// Set will automatically apply ToValue to the given value in order
// to convert it to a JavaScript value (type Value).
//
// If there is an error (like the binding being read-only, or the ToValue conversion
// failing), then an error is returned.
//
// If the top-level binding does not exist, it will be created.
func (self Otto) Set(name string, value interface{}) error {
{
value, err := self.ToValue(value)
if err != nil {
return err
}
err = catchPanic(func() {
self.setValue(name, value)
})
return err
}
}
func (self Otto) setValue(name string, value Value) {
self.runtime.GlobalEnvironment.SetValue(name, value, false)
}
// Call the given JavaScript with a given this and arguments.
//
// WARNING: 2013-05-19: This function is rough, and is in beta.
//
// If this is nil, then some special handling takes place to determine the proper
// this value, falling back to a "standard" invocation if necessary (calling the
// function which is the result of evaluating the source with a this of undefined).
//
// If source begins with "new " (A lowercase new followed by a space), then
// Call will invoke the function constructor rather than performing a function call.
// In this case, the this argument has no effect.
//
// // value is a String object
// value, _ := Otto.Call("Object", nil, "Hello, World.")
//
// // Likewise...
// value, _ := Otto.Call("new Object", nil, "Hello, World.")
//
// // This will perform a concat on the given array and return the result
// // value is [ 1, 2, 3, undefined, 4, 5, 6, 7, "abc" ]
// value, _ := Otto.Call(`[ 1, 2, 3, undefined, 4 ].concat`, nil, 5, 6, 7, "abc")
//
func (self Otto) Call(source string, this interface{}, argumentList ...interface{}) (Value, error) {
thisValue := UndefinedValue()
new_ := false
switch {
case strings.HasPrefix(source, "new "):
source = source[4:]
new_ = true
}
if !new_ && this == nil {
value := UndefinedValue()
fallback := false
err := catchPanic(func() {
programNode := mustParse(source + "()")
if callNode, valid := programNode.Body[0].(*_callNode); valid {
value = self.runtime.evaluateCall(callNode, argumentList)
} else {
fallback = true
}
})
if !fallback && err == nil {
return value, nil
}
} else {
value, err := self.ToValue(this)
if err != nil {
return UndefinedValue(), err
}
thisValue = value
}
fnValue, err := self.Run(source)
if err != nil {
return UndefinedValue(), err
}
value := UndefinedValue()
if new_ {
value, err = fnValue.constructSafe(thisValue, argumentList...)
if err != nil {
return UndefinedValue(), err
}
} else {
value, err = fnValue.Call(thisValue, argumentList...)
if err != nil {
return UndefinedValue(), err
}
}
return value, nil
}
// Object will run the given source and return the result as an object.
//
// For example, accessing an existing object:
//
// object, _ := Otto.Object(`Number`)
//
// Or, creating a new object:
//
// object, _ := Otto.Object(`{ xyzzy: "Nothing happens." }`)
//
// Or, creating and assigning an object:
//
// object, _ := Otto.Object(`xyzzy = {}`)
// object.Set("volume", 11)
//
// If there is an error (like the source does not result in an object), then
// nil and an error is returned.
func (self Otto) Object(source string) (*Object, error) {
value, err := self.runtime.runSafe(source)
if err != nil {
return nil, err
}
if value.IsObject() {
return value.Object(), nil
}
return nil, fmt.Errorf("value is not an object")
}
// ToValue will convert an interface{} value to a value digestible by otto/JavaScript.
func (self Otto) ToValue(value interface{}) (Value, error) {
return self.runtime.ToValue(value)
}
// Object{}
// Object is the representation of a JavaScript object.
type Object struct {
object *_object
value Value
}
func _newObject(object *_object, value Value) *Object {
// value MUST contain object!
return &Object{
object: object,
value: value,
}
}
// Call the method specified by the given name, using self as the this value.
// It is essentially equivalent to:
//
// return self.Get(name).Call(self, argumentList)
//
// An undefined value and an error will result if:
//
// 1. There is an error during conversion of the argument list
// 2. The property is not actually a function
// 3. An (uncaught) exception is thrown
//
func (self Object) Call(name string, argumentList ...interface{}) (Value, error) {
function, err := self.Get(name)
if err != nil {
return UndefinedValue(), err
}
return function.Call(self.Value(), argumentList...)
}
// Value will return self as a value.
func (self Object) Value() Value {
return self.value
}
// Get the value of the property with the given name.
func (self Object) Get(name string) (Value, error) {
value := UndefinedValue()
err := catchPanic(func() {
value = self.object.get(name)
})
return value, err
}
// Set the property of the given name to the given value.
//
// An error will result if the setting the property triggers an exception (i.e. read-only),
// or there is an error during conversion of the given value.
func (self Object) Set(name string, value interface{}) error {
{
value, err := self.object.runtime.ToValue(value)
if err != nil {
return err
}
err = catchPanic(func() {
self.object.put(name, value, true)
})
return err
}
}
// Class will return the class string of the object.
//
// The return value will (generally) be one of:
//
// Object
// Function
// Array
// String
// Number
// Boolean
// Date
// RegExp
//
func (self Object) Class() string {
return self.object.class
}