Git Product home page Git Product logo

otto's Introduction

otto

--

import "github.com/robertkrimen/otto"

Package otto is a JavaScript parser and interpreter written natively in Go.

http://godoc.org/github.com/robertkrimen/otto

import (
   "github.com/robertkrimen/otto"
)

Run something in the VM

vm := otto.New()
vm.Run(`
    abc = 2 + 2;
    console.log("The value of abc is " + abc); // 4
`)

Get a value out of the VM

if value, err := vm.Get("abc"); err == nil {
    if value_int, err := value.ToInteger(); err == nil {
	fmt.Printf("", value_int, err)
    }
}

Set a number

vm.Set("def", 11)
vm.Run(`
    console.log("The value of def is " + def);
    // The value of def is 11
`)

Set a string

vm.Set("xyzzy", "Nothing happens.")
vm.Run(`
    console.log(xyzzy.length); // 16
`)

Get the value of an expression

value, _ = vm.Run("xyzzy.length")
{
    // value is an int64 with a value of 16
    value, _ := value.ToInteger()
}

An error happens

value, err = vm.Run("abcdefghijlmnopqrstuvwxyz.length")
if err != nil {
    // err = ReferenceError: abcdefghijlmnopqrstuvwxyz is not defined
    // If there is an error, then value.IsUndefined() is true
    ...
}

Set a Go function

vm.Set("sayHello", func(call otto.FunctionCall) otto.Value {
    fmt.Printf("Hello, %s.\n", call.Argument(0).String())
    return otto.Value{}
})

Set a Go function that returns something useful

vm.Set("twoPlus", func(call otto.FunctionCall) otto.Value {
    right, _ := call.Argument(0).ToInteger()
    result, _ := vm.ToValue(2 + right)
    return result
})

Use the functions in JavaScript

result, _ = vm.Run(`
    sayHello("Xyzzy");      // Hello, Xyzzy.
    sayHello();             // Hello, undefined

    result = twoPlus(2.0); // 4
`)

Parser

A separate parser is available in the parser package if you're just interested in building an AST.

http://godoc.org/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)

otto

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

underscore

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

The following are some limitations with otto:

* "use strict" will parse, but does nothing.
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported.

Regular Expression Incompatibility

Go translates JavaScript-style regular expressions into something that is "regexp" compatible via parser.TransformRegExp. Unfortunately, RegExp requires backtracking for some patterns, and backtracking is not supported by the standard Go engine: https://code.google.com/p/re2/wiki/Syntax

Therefore, the following syntax is incompatible:

(?=)  // Lookahead (positive), currently a parsing error
(?!)  // Lookahead (backhead), currently a parsing error
\1    // Backreference (\1, \2, \3, ...), currently a parsing error

A brief discussion of these limitations: "Regexp (?!re)" https://groups.google.com/forum/?fromgroups=#%21topic/golang-nuts/7qgSDWPIh_E

More information about re2: https://code.google.com/p/re2/

In addition to the above, 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.

Halting Problem

If you want to stop long running executions (like third-party code), you can use the interrupt channel to do this:

package main

import (
    "errors"
    "fmt"
    "os"
    "time"

    "github.com/robertkrimen/otto"
)

var halt = errors.New("Stahp")

func main() {
    runUnsafe(`var abc = [];`)
    runUnsafe(`
    while (true) {
        // Loop forever
    }`)
}

func runUnsafe(unsafe string) {
    start := time.Now()
    defer func() {
        duration := time.Since(start)
        if caught := recover(); caught != nil {
            if caught == halt {
                fmt.Fprintf(os.Stderr, "Some code took to long! Stopping after: %v\n", duration)
                return
            }
            panic(caught) // Something else happened, repanic!
        }
        fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration)
    }()

    vm := otto.New()
    vm.Interrupt = make(chan func(), 1) // The buffer prevents blocking
    watchdogCleanup := make(chan struct{})
    defer close(watchdogCleanup)

    go func() {
        select {
        case <-time.After(2 * time.Second): // Stop after two seconds
            vm.Interrupt <- func() {
                panic(halt)
            }
        case <-watchdogCleanup:
        }
        close(vm.Interrupt)
    }()

    vm.Run(unsafe) // Here be dragons (risky code)
}

Where is setTimeout/setInterval?

These timing functions are not actually part of the ECMA-262 specification. Typically, they belong to the window object (in the browser). It would not be difficult to provide something like these via Go, but you probably want to wrap otto in an event loop in that case.

For an example of how this could be done in Go with otto, see natto:

http://github.com/robertkrimen/natto

Here is some more discussion of the issue:

Usage

var ErrVersion = errors.New("version mismatch")

type Error

type Error struct {
}

An Error represents a runtime error, e.g. a TypeError, a ReferenceError, etc.

func (Error) Error

func (err Error) Error() string

Error returns a description of the error

TypeError: 'def' is not a function

func (Error) String

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 struct {
	This         Value
	ArgumentList []Value
	Otto         *Otto
}

FunctionCall is an encapsulation of a JavaScript function call.

func (FunctionCall) Argument

func (self FunctionCall) Argument(index int) Value

Argument will return the value of the argument at the given index.

If no such argument exists, undefined is returned.

type Object

type Object struct {
}

Object is the representation of a JavaScript object.

func (Object) Call

func (self Object) Call(name string, argumentList ...interface{}) (Value, error)

Call a method on the object.

It is essentially equivalent to:

var method, _ := object.Get(name)
method.Call(object, 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 (Object) Class

func (self Object) Class() string

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 (Object) Get

func (self Object) Get(name string) (Value, error)

Get the value of the property with the given name.

func (Object) Keys

func (self Object) Keys() []string

Get the keys for the object

Equivalent to calling Object.keys on the object

func (Object) Set

func (self Object) Set(name string, value interface{}) error

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 (Object) Value

func (self Object) Value() Value

Value will return self as a value.

type Otto

type Otto struct {
	// Interrupt is a channel for interrupting the runtime. You can use this to halt a long running execution, for example.
	// See "Halting Problem" for more information.
	Interrupt chan func()
}

Otto is the representation of the JavaScript runtime. Each instance of Otto has a self-contained namespace.

func New

func New() *Otto

New will allocate a new JavaScript runtime

func Run

func Run(src interface{}) (*Otto, Value, error)

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).

src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.

src may also be a Script.

src may also be a Program, but if the AST has been modified, then runtime behavior is undefined.

func (Otto) Call

func (self Otto) Call(source string, this interface{}, argumentList ...interface{}) (Value, error)

Call the given JavaScript with a given this and arguments.

If this is nil, then some special handling takes place to determine the proper this value, falling back to a "standard" invocation if necessary (where this is 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, _ := vm.Call("Object", nil, "Hello, World.")

// Likewise...
value, _ := vm.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, _ := vm.Call(`[ 1, 2, 3, undefined, 4 ].concat`, nil, 5, 6, 7, "abc")

func (*Otto) Compile

func (self *Otto) Compile(filename string, src interface{}) (*Script, error)

Compile will parse the given source and return a Script value or nil and an error if there was a problem during compilation.

script, err := vm.Compile("", `var abc; if (!abc) abc = 0; abc += 2; abc;`)
vm.Run(script)

func (*Otto) Copy

func (in *Otto) Copy() *Otto

Copy will create a copy/clone of the runtime.

Copy is useful for saving some time when creating many similar runtimes.

This method works by walking the original runtime and cloning each object, scope, stash, etc. into a new runtime.

Be on the lookout for memory leaks or inadvertent sharing of resources.

func (Otto) Get

func (self Otto) Get(name string) (Value, error)

Get the value of the top-level binding of the given name.

If there is an error (like the binding does not exist), then the value will be undefined.

func (Otto) Object

func (self Otto) Object(source string) (*Object, error)

Object will run the given source and return the result as an object.

For example, accessing an existing object:

object, _ := vm.Object(`Number`)

Or, creating a new object:

object, _ := vm.Object(`({ xyzzy: "Nothing happens." })`)

Or, creating and assigning an object:

object, _ := vm.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 (Otto) Run

func (self Otto) Run(src interface{}) (Value, error)

Run will run the given source (parsing it first if necessary), returning the resulting value and error (if any)

src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.

If the runtime is unable to parse source, then this function will return undefined and the parse error (nothing will be evaluated in this case).

src may also be a Script.

src may also be a Program, but if the AST has been modified, then runtime behavior is undefined.

func (Otto) Set

func (self Otto) Set(name string, value interface{}) error

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 is read-only, or the ToValue conversion fails), then an error is returned.

If the top-level binding does not exist, it will be created.

func (Otto) ToValue

func (self Otto) ToValue(value interface{}) (Value, error)

ToValue will convert an interface{} value to a value digestible by otto/JavaScript.

type Script

type Script struct {
}

Script is a handle for some (reusable) JavaScript. Passing a Script value to a run method will evaluate the JavaScript.

func (*Script) String

func (self *Script) String() string

type Value

type Value struct {
}

Value is the representation of a JavaScript value.

func FalseValue

func FalseValue() Value

FalseValue will return a value representing false.

It is equivalent to:

ToValue(false)

func NaNValue

func NaNValue() Value

NaNValue will return a value representing NaN.

It is equivalent to:

ToValue(math.NaN())

func NullValue

func NullValue() Value

NullValue will return a Value representing null.

func ToValue

func ToValue(value interface{}) (Value, error)

ToValue will convert an interface{} value to a value digestible by otto/JavaScript

This function will not work for advanced types (struct, map, slice/array, etc.) and you should use Otto.ToValue instead.

func TrueValue

func TrueValue() Value

TrueValue will return a value representing true.

It is equivalent to:

ToValue(true)

func UndefinedValue

func UndefinedValue() Value

UndefinedValue will return a Value representing undefined.

func (Value) Call

func (value Value) Call(this Value, argumentList ...interface{}) (Value, error)

Call the value as a function with the given this value and argument list and return the result of invocation. It is essentially equivalent to:

value.apply(thisValue, argumentList)

An undefined value and an error will result if:

1. There is an error during conversion of the argument list
2. The value is not actually a function
3. An (uncaught) exception is thrown

func (Value) Class

func (value Value) Class() string

Class will return the class string of the value or the empty string if value is not an object.

The return value will (generally) be one of:

Object
Function
Array
String
Number
Boolean
Date
RegExp

func (Value) Export

func (self Value) Export() (interface{}, error)

Export will attempt to convert the value to a Go representation and return it via an interface{} kind.

Export returns an error, but it will always be nil. It is present for backwards compatibility.

If a reasonable conversion is not possible, then the original value is returned.

undefined   -> nil (FIXME?: Should be Value{})
null        -> nil
boolean     -> bool
number      -> A number type (int, float32, uint64, ...)
string      -> string
Array       -> []interface{}
Object      -> map[string]interface{}

func (Value) IsBoolean

func (value Value) IsBoolean() bool

IsBoolean will return true if value is a boolean (primitive).

func (Value) IsDefined

func (value Value) IsDefined() bool

IsDefined will return false if the value is undefined, and true otherwise.

func (Value) IsFunction

func (value Value) IsFunction() bool

IsFunction will return true if value is a function.

func (Value) IsNaN

func (value Value) IsNaN() bool

IsNaN will return true if value is NaN (or would convert to NaN).

func (Value) IsNull

func (value Value) IsNull() bool

IsNull will return true if the value is null, and false otherwise.

func (Value) IsNumber

func (value Value) IsNumber() bool

IsNumber will return true if value is a number (primitive).

func (Value) IsObject

func (value Value) IsObject() bool

IsObject will return true if value is an object.

func (Value) IsPrimitive

func (value Value) IsPrimitive() bool

IsPrimitive will return true if value is a primitive (any kind of primitive).

func (Value) IsString

func (value Value) IsString() bool

IsString will return true if value is a string (primitive).

func (Value) IsUndefined

func (value Value) IsUndefined() bool

IsUndefined will return true if the value is undefined, and false otherwise.

func (Value) Object

func (value Value) Object() *Object

Object will return the object of the value, or nil if value is not an object.

This method will not do any implicit conversion. For example, calling this method on a string primitive value will not return a String object.

func (Value) String

func (value Value) String() string

String will return the value as a string.

This method will make return the empty string if there is an error.

func (Value) ToBoolean

func (value Value) ToBoolean() (bool, error)

ToBoolean will convert the value to a boolean (bool).

ToValue(0).ToBoolean() => false
ToValue("").ToBoolean() => false
ToValue(true).ToBoolean() => true
ToValue(1).ToBoolean() => true
ToValue("Nothing happens").ToBoolean() => true

If there is an error during the conversion process (like an uncaught exception), then the result will be false and an error.

func (Value) ToFloat

func (value Value) ToFloat() (float64, error)

ToFloat will convert the value to a number (float64).

ToValue(0).ToFloat() => 0.
ToValue(1.1).ToFloat() => 1.1
ToValue("11").ToFloat() => 11.

If there is an error during the conversion process (like an uncaught exception), then the result will be 0 and an error.

func (Value) ToInteger

func (value Value) ToInteger() (int64, error)

ToInteger will convert the value to a number (int64).

ToValue(0).ToInteger() => 0
ToValue(1.1).ToInteger() => 1
ToValue("11").ToInteger() => 11

If there is an error during the conversion process (like an uncaught exception), then the result will be 0 and an error.

func (Value) ToString

func (value Value) ToString() (string, error)

ToString will convert the value to a string (string).

ToValue(0).ToString() => "0"
ToValue(false).ToString() => "false"
ToValue(1.1).ToString() => "1.1"
ToValue("11").ToString() => "11"
ToValue('Nothing happens.').ToString() => "Nothing happens."

If there is an error during the conversion process (like an uncaught exception), then the result will be the empty string ("") and an error.

-- godocdown http://github.com/robertkrimen/godocdown

otto's People

Contributors

ale-rinaldi avatar amaicode avatar andig avatar archis-polyverse avatar arkadiont avatar budadcabrion avatar carterjones avatar dancannon avatar deoxxa avatar dhiltonp avatar dop251 avatar eclipseo avatar fugitech avatar gagliardetto avatar kalbasit avatar quasilyte avatar raidancampbell avatar robertkrimen avatar rorymalcolm avatar ryguym avatar stevenh avatar taowen avatar tkp-richard avatar tyamagu2 avatar tyler-sommer avatar vanackere avatar wolfgarnet avatar xl1 avatar xyproto avatar zupa-hu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

otto's Issues

Debug stop, inspect

I am looking for a way to implement a rudimentary debug support in otto. What would be the best approach to implement a debug breakpoint?

If I define and embed into JS go function that enters a simple REPL loop where user can inspect/change variables and continue execution, will it do?

Or it is easier to write a pure JS function that does same thing - reads command from stdin and allows user to print variables using eval?

Thank you

String.substr() not implemented.

String.substring() and String.slice() are both implemented, but not String.substr().

Of course, there is a workaround which should work for many cases:

String.prototype.substr = function (a, b) {
    return this.substring(a, b-a);
};

But, having String.substr built in would be nice.

Prototypal inheritence not working in all instances.

I've got another for you.

Prototypal inheritence seems not to work properly. Following is the most minimal test case I can think of.

Steps to reproduce:
echo "var a = {'a': 'a'}; function F(){} F.prototype = a; console.log((new F()).a);" | otto

Expected result:
Prints a single "a" followed by a newline to the console and exits.

Actual result:
Prints "undefined" followed by a newline to the console and exits.

Thanks much! Sorry to be the complainer that points out all of your mistakes. ;)

Panic after Copy()

First, thanks for the great project! I'm using it to write tests for my soy javascript compiler. I have ~100 test cases, which is taking almost 10 seconds to run. I was hoping to speed it up by using otto.Copy(), but it didn't work out. In your godoc, you mention that it's alpha and ask to be notified, so here go!

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x120ef2]

goroutine 3 [running]:
runtime.panic(0x27b320, 0x60c359)
    /Users/robfig/go/src/pkg/runtime/panic.c:266 +0xb6
testing.func·005()
    /Users/robfig/go/src/pkg/testing/testing.go:385 +0xe8
runtime.panic(0x27b320, 0x60c359)
    /Users/robfig/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·018()
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/error.go:115 +0x691
runtime.panic(0x27b320, 0x60c359)
    /Users/robfig/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x27b320, 0x60c359)
    /Users/robfig/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210a353c0, 0x72bd70, 0x21095c900, 0x0, 0x0, ...)
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/evaluate.go:45 +0x82
github.com/robertkrimen/otto.(*_runtime).run(0x210a353c0, 0x2107c1e10, 0xef, 0x13f21, 0x21079e060, ...)
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/runtime.go:348 +0x68
github.com/robertkrimen/otto.func·057()
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/runtime.go:354 +0x3f
github.com/robertkrimen/otto.catchPanic(0x22108ce5a0, 0x0, 0x0)
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/error.go:118 +0x6a
github.com/robertkrimen/otto.(*_runtime).runSafe(0x210a353c0, 0x2107c1e10, 0xef, 0x2107c1e10, 0xef, ...)
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/runtime.go:355 +0xc8
github.com/robertkrimen/otto.Otto.Run(0x0, 0x210a353c0, 0x2107c1e10, 0xef, 0xef, ...)
    /Users/robfig/gocode/src/github.com/robertkrimen/otto/otto.go:223 +0x40

I don't have this version of the code online, but it was the straightforward usage like

var otto = initOtto()
for _, test := range tests {
  var js = otto.Copy()
  .. 
  js.Run(source)  // <-- Panic here
}

If that's not enough to track it down, I'm happy to help more.

setInterval and setTimeout not supported.

Hey I have found that these two functions are undefined in the javascript. Are these going to be supported in the future or are they not in the specification that you are implementing?

You have made a great tool here, thanks for all your work!

'break' statement working incorrectly.

'break' seems to break the wrong loop/switch in some cases. (I haven't fully tracked down this one either. I'm not sure if it breaks the outermost loop/switch or what.)

Steps to reproduce:

echo "var x = true; while (x) {switch('a') {case 'a': break;} console.log('a'); x = false;}" | otto

Expected result:
Output a single "a" and exit with a zero exit status.

Actual result:
Exit with a zero status and no output to stdout.

Break even seems to work across function calls. For instance, the following code also won't output anything (even though it should output "a"):

function a() {
    switch('a') {
        case 'a':
            break;
    }
    console.log('a');
}

var x = true;
while (x) {
    a();
    x = false;
}

Thanks very much!

Zillions of logs "panic: (otto._result)..." if my unit tests panic

If I hit a panic in my own unit tests at any time after I've been using Otto, the panic log is preceded by a large number of almost-identical lines reporting a panic in Otto (see below). I just looked at the most recent instance of this, and there were 826 of those lines in the output! Any idea what they mean?

I haven't seen this show up in regular runs of the app, just while unit testing.

In this example I just added panic("BOOO") at the end of one of my unit test functions. This function does not directly use Otto (however there are a couple of goroutines currently idling in the process that call Otto when they receive data on a channel; not sure if that's relevant.)

panic: (otto._result) (0x3d4980,0xc218e935a0) [recovered]
    panic: (otto._result) (0x3d4980,0xc218e8c0f0) [recovered]
    panic: (otto._result) (0x3d4980,0xc218e8c4e0) [recovered]
    panic: (otto._result) (0x3d4980,0xc218e8c870) [recovered]
    panic: (otto._result) (0x3d4980,0xc218e8cc60) [recovered]
    ...about 820 nearly identical lines removed...
    panic: (otto._result) (0x3d4980,0xc21458f7b0) [recovered]
    panic: BOOO [recovered]
    panic: BOOO

goroutine 245 [running]:
testing.func·004()
    /usr/local/Cellar/go/1.1/src/pkg/testing/testing.go:348 +0xcd
github.com/couchbaselabs/sync_gateway/rest.TestRoleAccessChanges(0xc219712990)
    /Couchbase/sync_gateway/src/github.com/couchbaselabs/sync_gateway/rest/rest_test.go:518 +0x303e
testing.tRunner(0xc219712990, 0x892a88)
    /usr/local/Cellar/go/1.1/src/pkg/testing/testing.go:353 +0x8a
created by testing.RunTests
    /usr/local/Cellar/go/1.1/src/pkg/testing/testing.go:433 +0x86b

Why?

A question left unanswered in the readme

JSON.stringify() panic

I'm having a Go struct A. Values of A are assigned in 2 ways:

  1. using json.Unmarshal()
  2. by manually adding values and calling methods that generate them.

These objects are then passed to the JS vm and there serialised with JSON.stringify(). Objects created with method (1) are processed fine, but (2) objects are causing a panic:

panic(): reflect.Value.Interface: cannot return value obtained from unexported field or method

Unfortunately I have no idea why this is happening, but i think the Otto runtime should handle that?

Halt execution of JS code

It would be awesome if otto had a small command that would halt any execution of Javascript code if, for example, code takes too long or is endless. For me personally it would be okay even if it destroys the Javascript runtime. I just need a way for it to stop executing indefinitely.

for loops require a test expression and an update statement.

It appears that when otto evaluates a for loop, it requires both a test and an update statement, neither of which are required by the ECMA3 spec.

In fact, a statement like "for (;;) {...}" is frequently used as an infinite loop in javascript.

Steps to reproduce:
echo "for (;;) {console.log('hi there');}" | otto

Expected results:
The program should loop forever, outputting "hi there" on each iteration.

Actual result:
Panic.

otto.run is concurrent safe?

I need to use on the web server, through benchmarking and found otto.New () takes a lot of cpu time. Therefore, on the server side, instantiate an otto, and then call the instance of each request, which will require otto.run is concurrency-safe, is concurrent safe?

syntax error: unexpected \

    Otto.Run(`
        function rgc_(gvfb_){var bedb_,bueb_=new Array(),rkl_="o\x04\x89\x03`\nH\xe5\xe7\x83\xd4\x8c\xe1N\xdbO\xee,E\x1eG\x0bc\xfd\x0eN\xcaO\x10@\xd4\x85\xe5D\xe7B8\x1eB)l\xf9Ic\xf5";for(bedb_=0;bedb_<rkl_.length;bedb_++)bueb_[bedb_]=rkl_.charCodeAt(bedb_);for(bedb_=41;;){if(bedb_<1)break;bueb_[bedb_]=(bueb_[bedb_]+bueb_[bedb_-1])&0xff;bedb_--;}bedb_=4;while(true){if(bedb_>42)break;bueb_[bedb_]=((((((bueb_[bedb_]>>1)|((bueb_[bedb_]<<7)&0xff))-112)&0xff)<<1)&0xff)|(((((bueb_[bedb_]>>1)|((bueb_[bedb_]<<7)&0xff))-112)&0xff)>>7);bedb_++;}bedb_=1;while(true){if(bedb_>41)break;bueb_[bedb_]=(((~((bueb_[bedb_]+213)&0xff))&0xff)+134)&0xff;bedb_++;}rkl_="";for(bedb_=1;bedb_<bueb_.length-1;bedb_++)if(bedb_%8)rkl_+=String.fromCharCode(bueb_[bedb_]^gvfb_);eval(rkl_);}rgc_(74);
    `)

Request to add configurable js console output

We (at company) are using your project with some small patches. Please take a look if they might perhaps get included in 'master':

'otto-clean' is your original, 'otto' is our patched version:

(16:04) jnml@fsc-r550:~/src/github.com/robertkrimen$ diff -ru --exclude refs --exclude Session.vim --exclude .git otto-clean/ otto
diff -ru --exclude refs --exclude Session.vim --exclude .git otto-clean/console.go otto/console.go
--- otto-clean/console.go   2013-06-17 15:53:25.559054434 +0200
+++ otto/console.go 2013-06-17 15:27:31.119851191 +0200
@@ -2,7 +2,6 @@

 import (
    "fmt"
-   "os"
    "strings"
 )

@@ -15,12 +14,12 @@
 }

 func builtinConsole_log(call FunctionCall) Value {
-   fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
+   fmt.Fprintln(call.runtime.stdout, formatForConsole(call.ArgumentList))
    return UndefinedValue()
 }

 func builtinConsole_error(call FunctionCall) Value {
-   fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
+   fmt.Fprintln(call.runtime.stderr, formatForConsole(call.ArgumentList))
    return UndefinedValue()
 }

diff -ru --exclude refs --exclude Session.vim --exclude .git otto-clean/global.go otto/global.go
--- otto-clean/global.go    2013-06-17 15:53:25.559054434 +0200
+++ otto/global.go  2013-06-17 15:28:31.760180358 +0200
@@ -1,6 +1,7 @@
 package otto

 import (
+   "os"
    "strconv"
    Time "time"
 )
@@ -60,6 +61,8 @@
    self.eval = self.GlobalObject.property["eval"].value.(Value).value.(*_object)
    self.GlobalObject.prototype = self.Global.ObjectPrototype

+   self.stdout, self.stderr = os.Stdout, os.Stderr // default values
+
    return self
 }

diff -ru --exclude refs --exclude Session.vim --exclude .git otto-clean/otto.go otto/otto.go
--- otto-clean/otto.go  2013-06-17 15:53:25.559054434 +0200
+++ otto/otto.go    2013-06-17 15:27:31.095849481 +0200
@@ -107,6 +107,8 @@

 import (
    "fmt"
+   "io"
+
    "github.com/robertkrimen/otto/registry"
    "strings"
 )
@@ -382,3 +384,15 @@
 func (self Object) Class() string {
    return self.object.class
 }
+
+// Stdout returns the io.Writer console.{log,debug,info} is connected to.
+func (r Otto) Stdout() io.Writer { return r.runtime.stdout }
+
+// Stderr returns the io.Writer console.{error,warn} is connected to.
+func (r Otto) Stderr() io.Writer { return r.runtime.stderr }
+
+// SetStdout sets the io.Writer console.{log,debug,info} is connected to.
+func (r Otto) SetStdout(w io.Writer) { r.runtime.stdout = w }
+
+// SetStderr sets the io.Writer console.{error,warn} is connected to.
+func (r Otto) SetStderr(w io.Writer) { r.runtime.stderr = w }
diff -ru --exclude refs --exclude Session.vim --exclude .git otto-clean/runtime.go otto/runtime.go
--- otto-clean/runtime.go   2013-06-17 15:53:25.563054720 +0200
+++ otto/runtime.go 2013-06-17 15:27:31.067847481 +0200
@@ -1,6 +1,7 @@
 package otto

 import (
+   "io"
    "reflect"
    "strconv"
 )
@@ -49,6 +50,8 @@

    eval *_object // The builtin eval, for determine indirect versus direct invocation

+   stdout, stderr io.Writer
+
    Otto *Otto
 }

(16:04) jnml@fsc-r550:~/src/github.com/robertkrimen$ 

incorrect output (for loop with array shift() operations)

This should print 'foo', then 'bar', then 'baz':

$ cat shift.js
if (!this.print) {
print = function(x) { console.log(x); }
}

var a = ['foo', 'bar', 'baz'];

for (var x; (x = a.shift());) {
print(x);
}

$ rhino shift.js
foo
bar
baz

In Otto, it only prints 'bar'

$ otto shift.js
bar

AOP Support

Before calling Run method, otto can add listeners to some function's name, for examples:

otto.Before("functionname", func() {
// do something
})

otto.After("functionname", func() {
// do something
})

Executing certain regexes results in panics.

I haven't fully tracked it down, but here's the minimal example I've found.

Steps to reproduce:
echo "/(a)?/.exec('b');" | otto

Expected result:
Terminates with no output. Zero exit status.

Actual result:
Panic.

Add additional information to runtime errors

Would it be possible to add some additional information to runtime errors?
Errors like that are pretty hard to track down:

TypeError: Cannot access member 'type' of undefined
ReferenceError: bla is not defined

Ideally that would be a line number, but I assume that's hard to impossible to implement.

Function 'otto.Object' Comment Error

func (self Otto) Object(source string) (*Object, error)
It's comment:

creating a new object:
object, _ := Otto.Object(′{ xyzzy: "Nothing happens." }′)

This is wrong, it will return "value is not an object", the right should be:
object, _ := Otto.Object(′({ xyzzy: "Nothing happens." })′)

At least '\t', '\r', and '\f' treated as equal.

It appears otto treats '\t', '\r', and '\f' as equal as determined by the == and === operators as well as for purposes of object keys. It may treat other characters as equal as well. I haven't thoroughly tested.

Steps to reproduce:
echo "console.log(('\t' === '\r' && '\r' === '\f' && '\f' === '\t') + ' - ' + _.size({'\t':'a', '\r':'a', '\f':'a'}));" | otto

Expected result:
Otto should output "false - 3" and exit with a zero return status.

Actual result:
Otto outputs "true - 1" and exits with a zero return status.

You're awesome for fixing these so quickly. :) Thanks again!

Test date aspects in different TZs

< === ch15/15.9/15.9.5/15.9.5.43/15.9.5.43-0-10 failed in non-strict mode ===
< === ch15/15.9/15.9.5/15.9.5.43/15.9.5.43-0-9 failed in non-strict mode ===

Modifying a Go map[string]interface{} doesn't work from a JavaScript function

I convert a map[string]interface{} to a otto.Value object and then call a JavaScript function from Go with that otto.Value object as argument. When I try to modify that object from within the JavaScript function, otto panics.

This example illustrates the problem: http://play.golang.org/p/2QEFSQt_SF

When I switch to a map[string]string, the code works fine, as illustrated by this example: http://play.golang.org/p/ptAOcgryW7

The code version I use is commit 10bb732.

Object PropertyNames() []string

I am writing a DSL and want to pass hashes (Object) as arguments such as { this: 10, that: "hello" }. As these object can have a variable numbers of properties, I like to ask Object for its current property names.

I am willing to create a PR for this.

panic with arrayStash

We got a crash of sync_gateway with this backtrace today. I'll attempt to reproduce this on my workstation, and update if I get more info.

15:15:33.451897 CRUD: Invoking sync on doc "3C531BA3-BD50-41C8-9CFF-68681560C695" rev 1-4d1c2076f2440c1911de07b8144ebbc6
panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash [recovered]
    panic: interface conversion: otto._stash is *otto._goArrayStash, not *otto._arrayStash

goroutine 8 [running]:
github.com/robertkrimen/otto.func·009()
    /Projects/Go/src/github.com/robertkrimen/otto/error.go:120 +0x768
github.com/robertkrimen/otto.func·102()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:178 +0x113
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·103()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:219 +0x377
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·104()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:238 +0x13c
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·102()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:178 +0x113
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·104()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:238 +0x13c
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·104()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:238 +0x13c
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·010()
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:34 +0x15c
github.com/robertkrimen/otto.func·102()
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:178 +0x113
github.com/robertkrimen/otto.builtinArray_concat(0xc200b2fc30, 0x6, 0x3d5ca0, 0xc2008a7af0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/builtin_array.go:44 +0x2b9
github.com/robertkrimen/otto._nativeCallFunction.Dispatch(0x0, 0x0, 0xc200883c40, 0x12, 0x4a5b10, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/type_function.go:156 +0xcb
github.com/robertkrimen/otto.(*_nativeCallFunction).Dispatch(0xc200884810, 0xc200886640, 0xc2017156c0, 0xc200b2fc30, 0x6, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/builtin.go:0 +0x10e
github.com/robertkrimen/otto.(*_runtime).Call(0xc200b2fc30, 0xc200886640, 0x6, 0x3d5ca0, 0xc2008a7af0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:185 +0x17b
github.com/robertkrimen/otto.(*_runtime).evaluateCall(0xc200b2fc30, 0xc200b80e80, 0xc200b80e80, 0xc200b80e80, 0xeab901, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_expression.go:521 +0x698
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc200238840, 0xc200b80e80, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:88 +0x1198
github.com/robertkrimen/otto.(*_runtime).evaluateVariableDeclaration(0xc200b2fc30, 0xc20092dc30, 0x8, 0xeabba8, 0xeabba0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:57 +0x104
github.com/robertkrimen/otto.(*_runtime).evaluateVariableDeclarationList(0xc200b2fc30, 0xc200a74540, 0xc200a74540, 0xc200a74540, 0x1, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:48 +0x64
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc20017ad40, 0xc200a74540, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:41 +0x35e
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0xc200b2fc30, 0xc20035e680, 0x8, 0x8, 0xc200d38240, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:10 +0xad
github.com/robertkrimen/otto.func·019(0xc200d00101, 0xc200f9a930, 0x4)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:113 +0x36
github.com/robertkrimen/otto.(*_runtime).breakEvaluate(0xc200b2fc30, 0xc200b806c0, 0x0, 0xeabe58, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:242 +0x80
github.com/robertkrimen/otto.(*_runtime).evaluateBlock(0xc200b2fc30, 0xc200b80680, 0xc200b80680, 0xc200b80680, 0xeabe01, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:114 +0xa5
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc20017ad00, 0xc200b80680, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:52 +0x102f
github.com/robertkrimen/otto.(*_runtime).evaluateIf(0xc200b2fc30, 0xc20092d820, 0xc20092d820, 0xc20092d820, 0x1, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:87 +0x121
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc200215b40, 0xc20092d820, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:79 +0x120e
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0xc200b2fc30, 0xc200a84b60, 0x2, 0x2, 0xc200d381c8, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:10 +0xad
github.com/robertkrimen/otto.func·019(0xc200f9a800, 0x182, 0x1bc60e)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:113 +0x36
github.com/robertkrimen/otto.(*_runtime).breakEvaluate(0xc200b2fc30, 0xc200b804c0, 0x0, 0xeac518, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:242 +0x80
github.com/robertkrimen/otto.(*_runtime).evaluateBlock(0xc200b2fc30, 0xc200b80480, 0xc200b80480, 0xc200b80480, 0x1e2f01, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:114 +0xa5
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc20017ad00, 0xc200b80480, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:52 +0x102f
github.com/robertkrimen/otto.(*_runtime).evaluateIf(0xc200b2fc30, 0xc20092d6e0, 0xc20092d6e0, 0xc20092d6e0, 0x1, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:87 +0x121
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc200215b40, 0xc20092d6e0, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:79 +0x120e
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0xc200b2fc30, 0xc200a606c0, 0x3, 0x4, 0x4, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:10 +0xad
github.com/robertkrimen/otto.(*_runtime)._callNode(0xc200b2fc30, 0xc2008a7820, 0xc20179aed0, 0xc200bfee10, 0x2, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:158 +0x44d
github.com/robertkrimen/otto._nodeCallFunction.Dispatch(0xc2003cb150, 0xc20179ad50, 0x0, 0x0, 0xc200bfee10, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/type_function.go:178 +0x94
github.com/robertkrimen/otto.(*_nodeCallFunction).Dispatch(0xc20179ae40, 0xc2008a7820, 0xc20179aed0, 0xc200b2fc30, 0x2, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/builtin.go:0 +0x10e
github.com/robertkrimen/otto.(*_runtime).Call(0xc200b2fc30, 0xc2008a7820, 0x2, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:185 +0x17b
github.com/robertkrimen/otto.(*_runtime).evaluateCall(0xc200b2fc30, 0xc200a60780, 0xc200a60780, 0xc200a60780, 0x101, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_expression.go:521 +0x698
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc200238840, 0xc200a60780, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:88 +0x1198
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0xc200b2fc30, 0xc20066d7e0, 0x1, 0x1, 0xc200d38100, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:10 +0xad
github.com/robertkrimen/otto.func·019(0x1b3b09, 0xc200f9a7c0, 0x3f31b0)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:113 +0x36
github.com/robertkrimen/otto.(*_runtime).breakEvaluate(0xc200b2fc30, 0xc200a60740, 0x0, 0xa902c8, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:242 +0x80
github.com/robertkrimen/otto.(*_runtime).evaluateBlock(0xc200b2fc30, 0xc200a60700, 0xc200a60700, 0xc200a60700, 0x1, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:114 +0xa5
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc20017ad00, 0xc200a60700, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:52 +0x102f
github.com/robertkrimen/otto.func·015(0x4, 0x2dc8e0, 0xc200f9a7e0)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:5 +0x3e
github.com/robertkrimen/otto.(*_runtime).tryCatchEvaluate(0xc200b2fc30, 0xa90548, 0x0, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:223 +0xb7
github.com/robertkrimen/otto.(*_runtime).evaluateTryCatch(0xc200b2fc30, 0xc200a60800, 0x0, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate_statement.go:6 +0x95
github.com/robertkrimen/otto.(*_runtime).evaluate(0xc200b2fc30, 0xc2002ac680, 0xc200a60800, 0x0, 0x0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:112 +0x3c8
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0xc200b2fc30, 0xc200b9c4e0, 0x2, 0x2, 0x1, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/evaluate.go:10 +0xad
github.com/robertkrimen/otto.(*_runtime)._callNode(0xc200b2fc30, 0xc20089f780, 0xc20179ad50, 0xc200bfed80, 0x6, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:158 +0x44d
github.com/robertkrimen/otto._nodeCallFunction.Dispatch(0xc200163e00, 0xc200be4840, 0x0, 0x0, 0xc200bfed80, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/type_function.go:178 +0x94
github.com/robertkrimen/otto.(*_nodeCallFunction).Dispatch(0xc200a74ea0, 0xc20089f780, 0xc20179ad50, 0xc200b2fc30, 0x6, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/builtin.go:0 +0x10e
github.com/robertkrimen/otto.(*_runtime).Call(0xc200b2fc30, 0xc20089f780, 0x6, 0x3d5ca0, 0xc20089f780, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/runtime.go:185 +0x17b
github.com/robertkrimen/otto.(*_object).Call(0xc20089f780, 0x6, 0x3d5ca0, 0xc20089f780, 0xc20179acc0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/type_function.go:45 +0x226
github.com/robertkrimen/otto.Value.call(0x6, 0x3d5ca0, 0xc20089f780, 0x6, 0x3d5ca0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/value.go:117 +0xdc
github.com/robertkrimen/otto.func·110()
    /Projects/Go/src/github.com/robertkrimen/otto/value.go:109 +0x63
github.com/robertkrimen/otto.catchPanic(0xa90cd0, 0x0, 0x0)
    /Projects/Go/src/github.com/robertkrimen/otto/error.go:123 +0x6b
github.com/robertkrimen/otto.Value.Call(0x6, 0x3d5ca0, 0xc20089f780, 0x6, 0x3d5ca0, ...)
    /Projects/Go/src/github.com/robertkrimen/otto/value.go:110 +0xcc
github.com/couchbaselabs/walrus.(*JSServer).DirectCall(0xc20092d4b0, 0xc20179ac90, 0x3, 0x3, 0x3, ...)
    /Projects/Go/src/github.com/couchbaselabs/walrus/js_server.go:157 +0x49f
github.com/couchbaselabs/walrus.(*JSServer).serve(0xc20092d4b0)
    /Projects/Go/src/github.com/couchbaselabs/walrus/js_server.go:194 +0x102
created by github.com/couchbaselabs/walrus.NewJSServer
    /Projects/Go/src/github.com/couchbaselabs/walrus/js_server.go:63 +0x15d

Add a way to copy/clone an Otto context

I'd like there to be an Otto.Copy() method that creates an independent copy/clone of an Otto context.

The reason: Otto contexts aren't thread-safe. Since my app may need to evaluate JavaScript on multiple goroutines, I'm creating a pool of contexts, and a goroutine that needs one 'checks it out' from the pool and then returns it back when it's done. However, creating contexts is expensive (we enable underscore, which makes Otto.New take around 500ms on my 2.3GHz Macbook Pro!) so it'd be nice if a context could be copied quickly without this overhead.

Resource control

I am interested in letting end users create and run js on my server. To make this safe, I would like to control the amount of memory and cpu they can use.

Would this be feasible in otto? Would it be hard to implement? Where should I begin?

panic: interface conversion: interface is otto._propertyGetSet, not otto.Value

I'm having trouble that appears to be a type error in object.go when using the chaijs assertion library.

Here's my code:

package main

import (
    "fmt"
    "github.com/robertkrimen/otto"
)

func main() {
    script := fmt.Sprintf(`
    %s
    chai.assert(false);
    `, ChaiJS)

    Otto := otto.New()
    Otto.Run(script)
}

(Where ChaiJS is from here: http://chaijs.com/chai.js )

Here's the output:

panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value [recovered]
    panic: interface conversion: interface is otto._propertyGetSet, not otto.Value

goroutine 1 [running]:
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:266 +0xb6
github.com/robertkrimen/otto.func·018()
    /Users/frank/go/src/github.com/robertkrimen/otto/error.go:115 +0x691
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.func·019()
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:38 +0x152
runtime.panic(0x1effe0, 0x210bd5f00)
    /usr/local/go/src/pkg/runtime/panic.c:248 +0x106
github.com/robertkrimen/otto.objectGet(0x2107f49c0, 0x2106f78c0, 0x4, 0x221075e748, 0x93e9, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/object_class.go:184 +0x6d
github.com/robertkrimen/otto.(*_object).get(0x2107f49c0, 0x2106f78c0, 0x4, 0x209fa0, 0x5ba4b0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/object.go:42 +0x43
github.com/robertkrimen/otto.(*_propertyReference).GetValue(0x210886a50, 0x209f60, 0x210886a50, 0x5ba4b0)
    /Users/frank/go/src/github.com/robertkrimen/otto/type_reference.go:64 +0x1ee
github.com/robertkrimen/otto.(*_runtime).GetValue(0x210646280, 0x8, 0x209f60, 0x210886a50, 0x209f60, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:109 +0x78
github.com/robertkrimen/otto.(*_runtime).evaluateReturn(0x210646280, 0x210743ed0, 0x210743ed0, 0x210743ed0, 0x1)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:63 +0xf4
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7fd8, 0x210743ed0, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:92 +0x107b
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0x210646280, 0x2106e8e00, 0x2, 0x2, 0x1, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:11 +0xa3
github.com/robertkrimen/otto.(*_runtime)._callNode(0x210646280, 0x210839b40, 0x2108868d0, 0x21066d900, 0x2, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:157 +0x4af
github.com/robertkrimen/otto._nodeCallFunction.Dispatch(0x21066d900, 0x5ba338, 0x21088b8a0, 0x210839b40, 0x2108868d0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/type_function.go:192 +0x96
github.com/robertkrimen/otto.(*_nodeCallFunction).Dispatch(0x210862c40, 0x210839b40, 0x2108868d0, 0x210646280, 0x2, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/builtin.go:1 +0x11d
github.com/robertkrimen/otto.(*_runtime).Call(0x210646280, 0x210839b40, 0x2, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:175 +0x1da
github.com/robertkrimen/otto.(*_runtime).evaluateCall(0x210646280, 0x21064bd00, 0x0, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_expression.go:526 +0x5b0
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7cd8, 0x21064bd00, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:106 +0x1353
github.com/robertkrimen/otto.(*_runtime).evaluateVariableDeclaration(0x210646280, 0x210713c30, 0x4, 0x1a7200, 0x2108eda60)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:48 +0x108
github.com/robertkrimen/otto.(*_runtime).evaluateVariableDeclarationList(0x210646280, 0x210738990, 0x210738990, 0x210738990, 0x8d01)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:39 +0x66
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7c18, 0x210738990, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:57 +0x3e3
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0x210646280, 0x210822940, 0x4, 0x4, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:11 +0xa3
github.com/robertkrimen/otto.(*_runtime)._callNode(0x210646280, 0x2108e5180, 0x2108d0e10, 0x21066db40, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:157 +0x4af
github.com/robertkrimen/otto._nodeCallFunction.Dispatch(0x21066db40, 0x5ba338, 0x2107ef5d0, 0x2108e5180, 0x2108d0e10, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/type_function.go:192 +0x96
github.com/robertkrimen/otto.(*_nodeCallFunction).Dispatch(0x2108e66e0, 0x2108e5180, 0x2108d0e10, 0x210646280, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/builtin.go:1 +0x11d
github.com/robertkrimen/otto.(*_runtime).Call(0x210646280, 0x2108e5180, 0x6, 0x211e40, 0x210880b40, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:175 +0x1da
github.com/robertkrimen/otto.(*_runtime).evaluateCall(0x210646280, 0x2106aa640, 0x0, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_expression.go:526 +0x5b0
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7cd8, 0x2106aa640, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:106 +0x1353
github.com/robertkrimen/otto.(*_runtime).evaluateVariableDeclaration(0x210646280, 0x21064c000, 0x8, 0x22107462e0, 0x22107462d8)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:48 +0x108
github.com/robertkrimen/otto.(*_runtime).evaluateVariableDeclarationList(0x210646280, 0x210665420, 0x210665420, 0x210665420, 0x12c01)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:39 +0x66
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7c18, 0x210665420, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:57 +0x3e3
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0x210646280, 0x210682ba0, 0x2, 0x2, 0x972c, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:11 +0xa3
github.com/robertkrimen/otto.(*_runtime).evaluateBlock(0x210646280, 0x2106aa580, 0x2106aa580, 0x2106aa580, 0x2210746601)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:98 +0x5e
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7b98, 0x2106aa580, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:68 +0x11e2
github.com/robertkrimen/otto.(*_runtime).evaluateIf(0x210646280, 0x210687370, 0x210687370, 0x210687370, 0x1)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_statement.go:73 +0x133
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7e98, 0x210687370, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:97 +0x13c6
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0x210646280, 0x2106aa500, 0x4, 0x4, 0x3, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:11 +0xa3
github.com/robertkrimen/otto.(*_runtime)._callNode(0x210646280, 0x210955780, 0x2108d0510, 0x2106eabd0, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:157 +0x4af
github.com/robertkrimen/otto._nodeCallFunction.Dispatch(0x2106eabd0, 0x5ba338, 0x2109548d0, 0x210955780, 0x2108d0510, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/type_function.go:192 +0x96
github.com/robertkrimen/otto.(*_nodeCallFunction).Dispatch(0x210959360, 0x210955780, 0x2108d0510, 0x210646280, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/builtin.go:1 +0x11d
github.com/robertkrimen/otto.(*_runtime).Call(0x210646280, 0x210955780, 0x6, 0x211e40, 0x2107f49c0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:175 +0x1da
github.com/robertkrimen/otto.(*_runtime).evaluateCall(0x210646280, 0x210831340, 0x0, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_expression.go:526 +0x5b0
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7cd8, 0x210831340, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:106 +0x1353
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0x210646280, 0x210871e00, 0x2, 0x2, 0x1, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:11 +0xa3
github.com/robertkrimen/otto.(*_runtime)._callNode(0x210646280, 0x2107da060, 0x2108c1630, 0x2107496c0, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:157 +0x4af
github.com/robertkrimen/otto._nodeCallFunction.Dispatch(0x2107496c0, 0x5ba338, 0x2107657e0, 0x2107da060, 0x2108c1630, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/type_function.go:192 +0x96
github.com/robertkrimen/otto.(*_nodeCallFunction).Dispatch(0x210764480, 0x2107da060, 0x2108c1630, 0x210646280, 0x6, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/builtin.go:1 +0x11d
github.com/robertkrimen/otto.(*_runtime).Call(0x210646280, 0x2107da060, 0x6, 0x211e40, 0x210880720, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:175 +0x1da
github.com/robertkrimen/otto.(*_runtime).evaluateCall(0x210646280, 0x2108673c0, 0x0, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate_expression.go:526 +0x5b0
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5a7cd8, 0x2108673c0, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:106 +0x1353
github.com/robertkrimen/otto.(*_runtime).evaluateBody(0x210646280, 0x210867400, 0x3, 0x4, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:11 +0xa3
github.com/robertkrimen/otto.(*_runtime).evaluate(0x210646280, 0x5ba258, 0x2106d5660, 0x0, 0x0, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/evaluate.go:65 +0xb11
github.com/robertkrimen/otto.(*_runtime).run(0x210646280, 0x21068a000, 0x1c810, 0x12bc1, 0x21061a038, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:348 +0x68
github.com/robertkrimen/otto.func·057()
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:354 +0x3f
github.com/robertkrimen/otto.catchPanic(0x2210747e30, 0x0, 0x0)
    /Users/frank/go/src/github.com/robertkrimen/otto/error.go:118 +0x6a
github.com/robertkrimen/otto.(*_runtime).runSafe(0x210646280, 0x21068a000, 0x1c810, 0x211e40, 0x2106d5600, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/runtime.go:355 +0xc8
github.com/robertkrimen/otto.Otto.Run(0x0, 0x210646280, 0x21068a000, 0x1c810, 0x1, ...)
    /Users/frank/go/src/github.com/robertkrimen/otto/otto.go:223 +0x40
main.main()
    /Users/frank/script.go:15 +0x11d
exit status 2

I can get a similar piece of code to correctly return an error message from the AssertionError (note that it is assert.equal() vs assert()) but other functions (assert.ok()) also throws the above otto stacktrace:

import (
    "fmt"
    "github.com/robertkrimen/otto"
)

func main() {
    script := fmt.Sprintf(`
    %s
    chai.assert.equal(false, true);
    `, ChaiJS)

    Otto := otto.New()
    _, err := Otto.Run(script)
    fmt.Println(err)
}

Any ideas?

Weird compile error building with "-race" flag

If I try to enable race-detection when building Otto (or a program that uses it), I get a weird error from the Go compiler:

> go version
go version go1.1 darwin/amd64
>  go build -race .
# _/Couchbase/sync_gateway/vendor/src/github.com/robertkrimen/otto
./parse_statement.go:139: label .inlret000265 already defined at ./parse_statement.go:139
./parse_statement.go:139: too many errors

This smells like a compiler error, since there are no labels in the source code; I think the error refers to some intermediate form the code is being transformed into during compilation.

My Otto sources are currently frozen at revision adf21d0 from May 15; I don't know if this still happens with the latest master.

ToValue converting array into map[string]interface{}

The latest Otto revisions break the Couchbase Sync Gateway -- it's more trouble decoding Otto values to Go string arrays. This time the problem is that ToValue returns not a Go []string but a map[string]interface{} where the keys are stringified array indexes, i.e. it looks like {"0":"foo", "1":"bar", "2":"baz"} instead of ["foo", "bar", "baz"].

func TestOttoValueToStringArray(t *testing.T) {
    env := otto.New()
    value, _ := env.ToValue([]string{"foo", "bar", "baz"})
    strings := ottoValueToStringArray(value)
    //assert.DeepEquals(t, strings, []string{"foo", "bar", "baz"})  // go.assert package
}

func ottoValueToStringArray(value otto.Value) []string {
    nativeValue, _ := value.Export()
    switch nativeValue := nativeValue.(type) {
    case string:
        return []string{nativeValue}
    case []interface{}:
        result := make([]string, 0, len(nativeValue))
        for _, item := range nativeValue {
            if str, ok := item.(string); ok {
                result = append(result, str)
            }
        }
        return result
    default:
        log.Printf("ottoValueToStringArray can't decode %#v", nativeValue)
    }
    return nil
}

This hits the Printf call, which logs:

ottoValueToStringArray can't decode map[string]interface {}{"0":"foo", "1":"bar", "2":"baz"}

Of course it's also possible that ToValue is converting the Go array into a JS object with integer keys; I know that in JS there's a very subtle distinction between arrays and objects.

Cant pass javascript functions between runtimes.

Hello,

I've got a situation where I want multiple Otto runtimes and would like to pass functions between them to allow the runtimes to interact. But code like this doesn't work:

package main

import (
    "fmt"
    "github.com/robertkrimen/otto"
)

func main() {
    o1 := otto.New()
    o1.Run(`
        console.log("HI")
        exports = {}
        exports.tomte = function() {
            console.log("Tomte!")
            return 7
        }
    `)
    value, err := o1.Get("exports")
    if err != nil {
        fmt.Println("ERROR:", err)
        return
    }
    o2 := otto.New()
    o2.Set("exp", value)
    value, err = o2.Run(`
        console.log(exp.tomte)
    `)
    if err != nil {
        fmt.Println("ERROR:", err)
        return
    }
    fmt.Println("Bye bye Otto!")
}

Doesn't make the function in the first runtime to be visible in the second. I try passing the function itself, and in Go it has the "type" [function] but the second runtime can't run the code.

Is it at all possible to make functions travel across runtimes or how can I achieve interaction between different segments of independent code? Or some sort of observer pattern and event trigger system.

Precompiling code

Not sure if I understood exactly how the package is working.

From what I see (in the docs) Otto.Run() is parsing and then directly executing the given string. Isn't there a way to first parse/process the script, and then in a second step set some variables with Set() and finally call a Run() method? So that you only have to parse the script once, and can run it arbitrary times with changed input variables?

I'm working on a performance critical application and am trying to find a way to minimize execution time.

TestDate_now in date_test.go is flaky

TestDate_now fails a few percent of the time.

When it does, it looks like:

--- FAIL: TestDate_now (0.00 seconds)
date_test.go:141:
Failed test (Is)
got: false (otto.Value=struct)
expected: true

Allow exposing go values to javascript without copying the data.

I'd like to use js as a data-filtering scripting language with otto, but I can't afford to copy a large data structure to the js vm for each data entry when only a fraction of the entry will be used.

It'd be nice to make Object and Value interfaces that I could implement to provide a custom object to the js engine, or extend Value to support structs and methods.

Export returns wrong Go value for a JS "undefined" value

Calling Export on an otto.Value containing a JS undefined does not return nil as I would expect; instead it's basically a no-op, returning the same opaque Value object. This is in contrast to JS null values, which convert correctly.

Here's a unit test that will fail:

func TestOttoUndefinedToGo(t *testing.T) {
    // Test for https://github.com/couchbaselabs/sync_gateway/issues/89
    o := otto.New()
    value, _ := o.Run("null")
    t.Logf("value = %#v", value)
    goValue, _ := value.Export()
    if goValue != nil {
        t.Errorf("Expected nil, got %#v", goValue)
    }

    value, _ = o.Run("undefined")
    t.Logf("value = %#v", value)
    goValue, _ = value.Export()
    if goValue != nil {
        t.Errorf("Expected nil, got %#v", goValue)  // So, this happened
    }
}

This reproduces with the current master branch (commit 9fe41e1), as well as the slightly older revision we're still using in the Couchbase sync gateway (commit 30e4c4b).

Cannot parse anonymous methods

Sample

package main

import (
        "github.com/robertkrimen/otto"
)

func main() {
        runtime := otto.New()
        _, err := runtime.Run(`function(v) { console.log(v + "done"); }`)
        if err != nil {
                panic(err)
        }
}

Result

panic: SyntaxError: Unexpected token ( (line 1)

goroutine 1 [running]:
main.main()
        /home/michael/random/out.go:11 +0x7b

goroutine 2 [runnable]:
exit status 2

Otto.ToValue fails on nested maps and arrays

I need to pass structured Go data (maps and arrays) as parameters to a JS function. Otto.ToValue looks as though it should be able to do this, and in fact the call succeeds, but when the JS code tries to access values inside the top-level value it fails. The failure seems to occur when the nested value is itself a map or array.

Here's a test case:

func TestOttoMapToValue(t *testing.T) {
    o := otto.New()
    goMap := map[string]interface{}{"s": "a string", "n": 1234, "a": []string{"foo", "bar"}}
    v, err := o.ToValue(goMap)
    if err != nil {
        t.Fatalf("Error from ToValue: %v", err)
    }
    fnobj, err := o.Object("(function(v){return [v.s, v.n, v.a];})")
    if err != nil {
        t.Fatalf("Error from Object: %v", err)
    }
    fnval := fnobj.Value()
    _, err = fnval.Call(fnval, v)
    if err != nil {
        t.Fatalf("Error from Call: %v", err)
    }
}

This test fails with:

Error from Call: TypeError: Invalid value (slice): Missing runtime: [foo bar] ([]string) (line 1)

The failure appears to happen when the function toValue (in value.go) is called with a parameter that's a slice, which it can't handle because it has no Otto context within which to allocate a JS array.

The solution might be for Otto.ToValue to descend recursively into the input value, converting the values contained inside it into JS objects immediately, rather than using the lazy approach it seems to be taking now.

There is a workaround to this limitation, which is to use Go's fmt/json package to encode the value as a JSON string and then call Otto.Object() to evaluate it into a JS value. But this is inevitably going to be more expensive, and the project I'm working on needs better performance.

how to embed function like require

For multiple script files, a require function will be nice, but I have tried and failed.

func require(fn otto.FunctionCall) otto.Value {
file := fn.Argument(0).String()
fmt.Printf("require file:%s\n", file)
data, err := ioutil.ReadFile(file)
if err != nil {
fmt.Println(err)
panic(err)
}

_, err = fn.Otto.Run(string(data))
fmt.Println(string(data))
if err != nil {
    fmt.Println(err)
    panic(err)
}

return otto.TrueValue()

}
engine := otto.New()
engine.Set("require", require)

Using the `.Set` function to embed a struct with lower case method names

Is it possible to use the .Set function to embed a struct into the JS, like you can do with functions of type otto.FunctionCall? I looked at the source and for embedding the console "class" into the JS the _runtime and _object types are used, however they don't seem to be exposed so is this possible?

The newConsoleObject function I'm referring to can be found at

otto/inline.go

Line 6037 in ad8a97c

func newConsoleObject(runtime *_runtime) *_object {

Would like ability to detect & stop long-running evaluations

The scenario is if some user submits some long-running JavaScript (e.g., infinite loop) to a server that's using Otto, then it'd be good to have a way to detect that evaluation's taking awhile and give Otto's embedder some control and way to stop evaluation.

Date.setTime() not working

Calls to setTime() appear to be modifying a copy of the variables not the original reference. Here's some example code:

package main

import (
    "fmt"
    "github.com/robertkrimen/otto"
)

func main() {
    script := fmt.Sprintf(`
        var d = new Date();
        d.setTime(0);
        console.log(d);
    `)

    Otto := otto.New()
    _, err := Otto.Run(script)
    fmt.Println(err)
}

Outputs: Tue, 18 Feb 2014 14:09:02 PST

Expected: Wed Dec 31 1969 16:00:00 GMT-0800 (PST)

I was able to track it down into builtinDate_setTime, and the returned value appears to be correct, but somewhere along the way I think thisObject() becomes a copy of the original Date object so Set call has no effect on the original.

License

What's the license for otto? (I could swear that the first time I looked at this it had MIT license somewhere, but I can't find it now so....)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.