Skip to content

Commit

Permalink
Merge pull request #8 from jessedoyle/runtime
Browse files Browse the repository at this point in the history
runtime interface implementation
  • Loading branch information
jessedoyle committed Jan 3, 2016
2 parents 655f0cb + 2a0ce42 commit 4fc7b5e
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
language: crystal
before_install: make libduktape
script: make spec
sudo: false
sudo: false
email: false
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# v0.6.3 - Jan 2, 2016

- Rework the internal require order and `duktape/base`.
- Add a `Duktape::Runtime` class that lessens the need for low-level API calls for many use-cases. This must be required using `require "duktape/runtime"`.

# v0.6.2 - November 30, 2015

- Update Duktape version to `v1.3.1`. See [release info](https://github.com/svaarala/duktape/blob/master/RELEASES.rst).
Expand Down
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ version: 1.0.0 # your project's version
dependencies:
duktape:
github: jessedoyle/duktape.cr
version: ~> 0.6.2
version: ~> 0.6.3
```
then execute:
Expand Down Expand Up @@ -115,6 +115,39 @@ sbx = Duktape::Sandbox.new 500 # 500ms execution time limit
sbx.eval! "while (true) {}" # => RangeError
```

## Duktape::Runtime

A alternative interface for evaluating JS code is available via the `Duktape::Runtime` class. This class provides a streamlined evaluation API (similar to ExecJS) that allows easier access to javascript values without the need to call many low-level Duktape API functions.

The entire `Runtime` API is as follows:

* `call(property, *args)` - Call the property or function with the given arguments and return the result.
* `call([properties], *args)` - Call the property that is nested within an array of string property names.
* `eval(source)` - Evaluate the javascript source and return the last value.
* `exec(source)` - Evaluate the javascript source and always return `nil`.

`Duktape::Runtime` instances can also be provided an initialization block when created.

Here's an example:

```crystal
require "duktape/runtime"
# A Runtime (optionally) accepts an initialization block
rt = Duktape::Runtime.new do |sbx|
sbx.eval! <<-JS
function test(a, b, c) { return a + b + c; }
JS
end
rt.call("test", 3, 4, 5) # => 12 (same as test(3, 4, 5);)
rt.call(["Math", "PI"]) # => 3.14159
rt.eval("1 + 1") # => 2
rt.exec("1 + 1") # => nil
```

Note that `duktape/runtime` is not loaded by the base `duktape` require, and may be used standalone if necessary (ie. replace your `require "duktape"` calls with `require "duktape/runtime"` if you want this functionality).

## Calling Crystal Code from Javascript

Note: This functionality is considered experimental and syntax/functionality may change dramatically between releases.
Expand Down
150 changes: 150 additions & 0 deletions spec/duktape/runtime_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
require "../spec_helper"
require "../../src/duktape/runtime"

describe Duktape::Runtime do
describe "initialize" do
context "without arguments" do
it "should create a Runtime instance" do
rt = Duktape::Runtime.new

rt.should be_a(Duktape::Runtime)
end
end

context "with a block argument" do
it "should pass a Duktape::Sandbox to init JS code with" do
rt = Duktape::Runtime.new do |sbx|
sbx.should be_a(Duktape::Sandbox)
sbx.eval!("function add(num){ return num + num; }")
end

rt.eval("add(9);").should eq(18)
rt.should be_a(Duktape::Runtime)
end
end
end

describe "call" do
context "with a single property name" do
it "should call the property with the args" do
rt = Duktape::Runtime.new do |sbx|
sbx.eval!(";function test(num) { return num - 1; }")
end

val = rt.call("test", 123)

val.should eq(122)
val.should be_a(Float64)
end

it "should call a key without arguments" do
rt = Duktape::Runtime.new
val = rt.call("Math.PI") as Float64

val.should_not be_nil
val.floor.should eq(3)
end
end

context "with multiple property names" do
it "should call the nested property with arguments" do
rt = Duktape::Runtime.new
val = rt.call(["JSON", "stringify"], 123) as String

val.should eq("123")
end

it "should handle multiple arguments" do
rt = Duktape::Runtime.new do |sbx|
sbx.eval!("function t(a, b, c) { return a + b + c; }")
end

val = rt.call(["t"], 2, 3, 4)

val.should eq(9)
val.should be_a(Float64)
end

it "should return nil for the empty array" do
rt = Duktape::Runtime.new
val = rt.call([] of String, 123)

val.should be_nil
end

it "should work without any arguments passed" do
rt = Duktape::Runtime.new
val = rt.call(["Math", "E"]) as Float64

val.floor.should eq(2)
end

it "should return a ComplexObject on error" do
rt = Duktape::Runtime.new
val = rt.call(["JSON", "invalid"], 123) as Duktape::Runtime::ComplexObject

val.should be_a(Duktape::Runtime::ComplexObject)
val.string.should eq("TypeError: not callable")
end
end
end

describe "eval" do
it "should return the last value evaluated" do
rt = Duktape::Runtime.new do |sbx|
sbx.eval!("; function bool() { return true; }")
end

val = rt.eval("bool();")

val.should be_true
val.should be_a(Bool)
end

it "should return a float evaluated" do
rt = Duktape::Runtime.new
val = rt.eval("1 + 1;")

val.should eq(2.0)
val.should be_a(Float64)
end

it "should raise a ReferenceError on invalid syntax" do
rt = Duktape::Runtime.new

expect_raises(Duktape::Error, /ReferenceError/) do
rt.eval("__abc__;")
end
end
end

describe "exec" do
it "should return nil for all evaluation" do
rt = Duktape::Runtime.new
val = rt.exec("1 + 1")

val.should be_nil
end

it "should execute code" do
rt = Duktape::Runtime.new do |sbx|
sbx.eval!("var a = 1; function add() { a = a + 1; }")
end

val = rt.exec("add();")
after = rt.eval("a;")

val.should be_nil
after.should be_a(Float64)
after.should eq(2)
end

it "should raise on invalid syntax" do
rt = Duktape::Runtime.new

expect_raises(Duktape::Error, /SyntaxError/) do
rt.exec("\"missing")
end
end
end
end
1 change: 0 additions & 1 deletion spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ ENV["VERIFY"] = "1"
require "spec"
require "../src/lib_duktape"
require "../src/duktape"
require "../src/duktape/**"
require "./support/**"

# Disable logging
Expand Down
4 changes: 1 addition & 3 deletions src/duktape.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
#
# This is free software. Please see LICENSE for details.

require "./lib_duktape"
require "./duktape/support/*"
require "./duktape/*"
require "./duktape/base"
7 changes: 7 additions & 0 deletions src/duktape/base.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "../lib_duktape"
require "./support/*"
require "./context"
require "./error"
require "./logger"
require "./sandbox"
require "./version"
Loading

0 comments on commit 4fc7b5e

Please sign in to comment.