h8 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -2
- data/benchmark/big.txt +121706 -0
- data/benchmark/knightsmove.coffee +48 -0
- data/benchmark/knigthsmove.rb +70 -0
- data/benchmark/process_text.coffee +12 -0
- data/benchmark/text_process.rb +51 -0
- data/benchmark/tools.rb +21 -0
- data/bin/h8 +12 -0
- data/ext/h8/h8.cpp +4 -2
- data/ext/h8/h8.h +8 -3
- data/ext/h8/js_gate.h +25 -1
- data/ext/h8/main.cpp +12 -5
- data/ext/h8/object_wrap.h +96 -102
- data/ext/h8/ruby_gate.cpp +0 -1
- data/ext/h8/ruby_gate.h +5 -3
- data/lib/h8/coffee.rb +19 -3
- data/lib/h8/command.rb +128 -0
- data/lib/h8/context.rb +14 -21
- data/lib/h8/errors.rb +9 -8
- data/lib/h8/pargser.rb +133 -0
- data/lib/h8/version.rb +1 -1
- data/lib/{h8 → scripts}/coffee-script.js +0 -0
- data/lib/scripts/globals.coffee +4 -0
- data/spec/coffee/cli_tests.coffee +50 -0
- data/spec/coffee_spec.rb +50 -0
- data/spec/command_spec.rb +72 -0
- data/spec/context_spec.rb +21 -1
- data/spec/js_gate_spec.rb +53 -1
- data/spec/pargser_spec.rb +67 -0
- data/spec/ruby_gate_spec.rb +17 -0
- metadata +21 -4
data/ext/h8/ruby_gate.cpp
CHANGED
@@ -14,7 +14,6 @@ static void* unblock_caller(void *param) {
|
|
14
14
|
}
|
15
15
|
|
16
16
|
static void with_gvl(RubyGate *gate, const std::function<void(void)> &block) {
|
17
|
-
// v8::Unlocker unlock(gate->isolate());
|
18
17
|
H8 *h8 = gate->getH8();
|
19
18
|
if (h8->isGvlReleased()) {
|
20
19
|
h8->setGvlReleased(false);
|
data/ext/h8/ruby_gate.h
CHANGED
@@ -41,9 +41,7 @@ public:
|
|
41
41
|
}
|
42
42
|
|
43
43
|
virtual void free() {
|
44
|
-
|
45
|
-
persistent().ClearWeak();
|
46
|
-
persistent().Reset();
|
44
|
+
// printf("RG::FREE(%p)\n", this);
|
47
45
|
delete this;
|
48
46
|
}
|
49
47
|
|
@@ -52,6 +50,10 @@ public:
|
|
52
50
|
}
|
53
51
|
|
54
52
|
virtual ~RubyGate() {
|
53
|
+
// puts("~RG()");
|
54
|
+
persistent().ClearWeak();
|
55
|
+
persistent().Reset();
|
56
|
+
// The rest is done by the base classes
|
55
57
|
}
|
56
58
|
|
57
59
|
Isolate* isolate() const noexcept {
|
data/lib/h8/coffee.rb
CHANGED
@@ -36,13 +36,15 @@ module H8
|
|
36
36
|
# Create compiler instance.
|
37
37
|
def initialize
|
38
38
|
@context = H8::Context.new
|
39
|
-
@context.eval
|
39
|
+
@context.eval read_script 'coffee-script.js'
|
40
|
+
eval read_script('globals.coffee')
|
40
41
|
end
|
41
42
|
|
42
43
|
# compile coffeescript source and return compiled javascript
|
43
|
-
def compile src, **kwargs
|
44
|
+
def compile src, file_name: nil, **kwargs
|
44
45
|
@context[:cs] = src
|
45
|
-
|
46
|
+
@context[:filename] = file_name
|
47
|
+
res = @context.eval('CoffeeScript.compile(cs,{filename: filename})')
|
46
48
|
@context[:cs] = nil # Sources can be big...
|
47
49
|
res
|
48
50
|
end
|
@@ -52,6 +54,20 @@ module H8
|
|
52
54
|
def eval src, **kwargs
|
53
55
|
@context.eval compile(src), **kwargs
|
54
56
|
end
|
57
|
+
|
58
|
+
# Provide context with CoffeeScrip compiler loaded
|
59
|
+
def context
|
60
|
+
@context
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
@@base = File.expand_path File.join(File.dirname(__FILE__), '../scripts')
|
66
|
+
@@cache = {}
|
67
|
+
|
68
|
+
def read_script name
|
69
|
+
@@cache[name] ||= open(File.join(@@base, name), 'r').read
|
70
|
+
end
|
55
71
|
end
|
56
72
|
|
57
73
|
|
data/lib/h8/command.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'pargser'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'h8'
|
5
|
+
|
6
|
+
module H8
|
7
|
+
|
8
|
+
class Command
|
9
|
+
|
10
|
+
def initialize *args, out: STDOUT, err: STDERR
|
11
|
+
@out = out
|
12
|
+
@err = err
|
13
|
+
|
14
|
+
run *args if args.length > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def run *args
|
18
|
+
count = 0
|
19
|
+
|
20
|
+
@parser = Pargser.new(args)
|
21
|
+
|
22
|
+
@parser.key('-e', default: false, doc: 'inline coffee code to execute') { |script|
|
23
|
+
if script
|
24
|
+
@file = '-e'
|
25
|
+
context.coffee (@script=script)
|
26
|
+
count += 1
|
27
|
+
end
|
28
|
+
}
|
29
|
+
.key('-x', default: nil, doc: 'inline js code to execute') { |script|
|
30
|
+
if script
|
31
|
+
@file = '-e'
|
32
|
+
context.eval (@script=script)
|
33
|
+
count += 1
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
@parser.parse { |file|
|
38
|
+
count += 1
|
39
|
+
@script = open(file, 'r').read
|
40
|
+
file.downcase.end_with?('.coffee') and @script = H8::Coffee.compile(@script)
|
41
|
+
@file = file
|
42
|
+
context.eval @script
|
43
|
+
}
|
44
|
+
|
45
|
+
count > 0 or raise 'Must provide at least one file'
|
46
|
+
end
|
47
|
+
|
48
|
+
def context
|
49
|
+
@context ||= begin
|
50
|
+
cxt = H8::Context.new
|
51
|
+
console = Console.new out: @out, err: @err
|
52
|
+
cxt[:console] = console
|
53
|
+
print = -> (*args) { console.debug *args }
|
54
|
+
cxt[:print] = print
|
55
|
+
cxt[:puts] = print
|
56
|
+
cxt[:open] = -> (name, mode='r', block=nil) { Stream.new(name, mode, block) }
|
57
|
+
cxt['__FILE__'] = @file ? @file.to_s : '<inline>'
|
58
|
+
cxt[:File] = FileProxy.new
|
59
|
+
cxt
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def usage
|
64
|
+
"\nh8 #{H8::VERSION} CLI inteface\n\n" +
|
65
|
+
"Usage: h8 <file.js/file.coffe>\n\n" +
|
66
|
+
@parser.keys_doc
|
67
|
+
end
|
68
|
+
|
69
|
+
class FileProxy
|
70
|
+
def dirname str
|
71
|
+
File.dirname str
|
72
|
+
end
|
73
|
+
|
74
|
+
def extname str
|
75
|
+
File.extname str
|
76
|
+
end
|
77
|
+
|
78
|
+
def basename str
|
79
|
+
File.basename str
|
80
|
+
end
|
81
|
+
|
82
|
+
def expand_path str
|
83
|
+
File.expand_path str
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class Stream
|
89
|
+
def initialize name, mode, block=nil
|
90
|
+
@file = open(name, mode)
|
91
|
+
if block
|
92
|
+
block.call self
|
93
|
+
@file.close
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def read(count=nil)
|
98
|
+
if count
|
99
|
+
@file.read(count)
|
100
|
+
else
|
101
|
+
@file.read
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
class Console
|
108
|
+
def initialize out: STDOUT, err: STDERR
|
109
|
+
@out, @err = out, err
|
110
|
+
end
|
111
|
+
|
112
|
+
def debug *args
|
113
|
+
@out.puts args.join(' ')
|
114
|
+
end
|
115
|
+
|
116
|
+
def log *args
|
117
|
+
debug *args
|
118
|
+
end
|
119
|
+
|
120
|
+
def error *args
|
121
|
+
@err.puts args.join(' ')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/lib/h8/context.rb
CHANGED
@@ -25,7 +25,7 @@ module H8
|
|
25
25
|
# * ruby Class - creating a javascript constructor function that creates ruby
|
26
26
|
# class instance (any arguments) and gates it to use with js.
|
27
27
|
def []= name, value
|
28
|
-
set_all name => value
|
28
|
+
set_all name.to_sym => value
|
29
29
|
end
|
30
30
|
|
31
31
|
# Execute a given script on the current context with optionally limited execution time.
|
@@ -37,16 +37,25 @@ module H8
|
|
37
37
|
#
|
38
38
|
# @return [Value] wrapped object returned by the script
|
39
39
|
# @raise [H8::TimeoutError] if the timeout was set and expired
|
40
|
-
def eval script, max_time: 0, timeout: 0
|
40
|
+
def eval script, max_time: 0, timeout: 0, file_name: nil
|
41
41
|
timeout = max_time * 1000 if max_time > 0
|
42
42
|
yield(self) if block_given?
|
43
|
-
_eval script, timeout.to_i
|
43
|
+
_eval script, timeout.to_i, file_name
|
44
44
|
end
|
45
45
|
|
46
|
+
# Compile and execute coffeescript, taking same arguments as #eval.
|
47
|
+
#
|
48
|
+
# If you need to execute same script more than once consider first H8::Coffee.compile
|
49
|
+
# and cache compiled script.
|
50
|
+
def coffee script, ** kwargs
|
51
|
+
eval Coffee.compile script, **kwargs
|
52
|
+
end
|
53
|
+
|
54
|
+
|
46
55
|
# Execute script in a new context with optionally set vars. @see H8#set_all
|
47
56
|
# @return [Value] wrapped object returned by the script
|
48
|
-
def self.eval script, **kwargs
|
49
|
-
Context.new(** kwargs).eval script
|
57
|
+
def self.eval script, file_name: nil, **kwargs
|
58
|
+
Context.new(** kwargs).eval script, file_name: file_name
|
50
59
|
end
|
51
60
|
|
52
61
|
# Secure gate for JS to securely access ruby class properties (methods with no args)
|
@@ -111,20 +120,4 @@ module H8
|
|
111
120
|
end
|
112
121
|
end
|
113
122
|
|
114
|
-
# class RacerContext < Context
|
115
|
-
# protected
|
116
|
-
# def set_var name, value
|
117
|
-
# if value.is_a?(Proc)
|
118
|
-
# n1 = "___compat_#{name}"
|
119
|
-
# _eval "function #{name}() { return #{n1}( this, arguments); }", 0
|
120
|
-
# super n1, -> (this, arguments) {
|
121
|
-
# args = [this] + H8::arguments_to_a(arguments)
|
122
|
-
# value.call *args
|
123
|
-
# }
|
124
|
-
# else
|
125
|
-
# super name, value
|
126
|
-
# end
|
127
|
-
# end
|
128
|
-
# end
|
129
|
-
|
130
123
|
end
|
data/lib/h8/errors.rb
CHANGED
@@ -8,26 +8,27 @@ module H8
|
|
8
8
|
# The general error caused by the script execution, e.g. uncaught javascript exceptinos and like.
|
9
9
|
# Check #message to see the cause.
|
10
10
|
class JsError < Error
|
11
|
-
# Error message
|
12
|
-
attr :message
|
13
11
|
|
14
12
|
# Javascript Error object. May be nil
|
15
13
|
attr :javascript_error
|
16
14
|
|
17
|
-
def to_s
|
18
|
-
message
|
19
|
-
end
|
20
|
-
|
21
15
|
# Error name
|
22
16
|
def name
|
23
|
-
@javascript_error.name ? @javascript_error.name : message
|
17
|
+
@javascript_error.name ? @javascript_error.name : @message
|
24
18
|
end
|
25
19
|
|
26
20
|
# String that represents stack trace if any as multiline string (\n separated)
|
27
21
|
def javascript_backtrace
|
28
|
-
@javascript_error ? @javascript_error.stack : message
|
22
|
+
@javascript_error ? @javascript_error.stack : @message
|
29
23
|
end
|
30
24
|
|
25
|
+
def to_s
|
26
|
+
javascript_backtrace
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
to_s
|
31
|
+
end
|
31
32
|
end
|
32
33
|
|
33
34
|
# Script execution is timed out (see H8::Context#eval timeout parameter)
|
data/lib/h8/pargser.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module H8
|
4
|
+
class Pargser
|
5
|
+
|
6
|
+
class Error < ArgumentError; end
|
7
|
+
|
8
|
+
# Create parser instance with a list of arguments. Otherwise, arguments can
|
9
|
+
# be passed to #parse call.
|
10
|
+
#
|
11
|
+
# @param [Array] args arguments
|
12
|
+
def initialize args=[]
|
13
|
+
@args = args
|
14
|
+
@keys = {}
|
15
|
+
@required = Set.new
|
16
|
+
@docs = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Register key handler.
|
20
|
+
# When #parse handler blocks will be called in order of appearance in
|
21
|
+
# arguments array
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# @param [String] name key name
|
25
|
+
#
|
26
|
+
# @param [Array(String)] aliases for the key
|
27
|
+
#
|
28
|
+
# @param [Boolean] :needs_value if set then the parser wants a value argument after the
|
29
|
+
# key which will be passed to block as an argument. if default param is not set and
|
30
|
+
# the key will not be detected, Pargser::Error will be raised
|
31
|
+
#
|
32
|
+
# @param [String] :default value. if set, needs_value parameter can be omitted - the handler
|
33
|
+
# block will be passed either with this value or with one specified in args.
|
34
|
+
#
|
35
|
+
# @param [String] :doc optional documentation string that will be used in #keys_doc
|
36
|
+
#
|
37
|
+
# @yield block if the key is found with optional value argument
|
38
|
+
#
|
39
|
+
def key name, *aliases, needs_value: false, doc: nil, **kwargs, &block
|
40
|
+
k = name.to_s
|
41
|
+
|
42
|
+
default_set = false
|
43
|
+
default = nil
|
44
|
+
if kwargs.include?(:default)
|
45
|
+
default = kwargs[:default]
|
46
|
+
needs_value = true
|
47
|
+
default_set = true
|
48
|
+
end
|
49
|
+
|
50
|
+
@keys.include?(k) and raise Error, "Duplicate key registration #{k}"
|
51
|
+
data = @keys[k] = OpenStruct.new required: false,
|
52
|
+
needs_value: needs_value,
|
53
|
+
block: block,
|
54
|
+
doc: doc,
|
55
|
+
key: k,
|
56
|
+
aliases: aliases,
|
57
|
+
default: default,
|
58
|
+
default_set: default_set
|
59
|
+
@docs << data
|
60
|
+
aliases.each { |a| @keys[a.to_s] = data }
|
61
|
+
@required.add(data) if needs_value
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Process command line and call key handlers in the order of
|
66
|
+
# appearance. Then call handlers that keys which need values
|
67
|
+
# and were not called and have defaults, or raise error.
|
68
|
+
#
|
69
|
+
# The rest of arguments (non-keys) are either yielded or returned
|
70
|
+
# as an array.
|
71
|
+
#
|
72
|
+
# You can optionally set other arguments than specified in constructor
|
73
|
+
#
|
74
|
+
# @param [Array] args to parse. If specified, arguments passed to constructor
|
75
|
+
# will be ignored and lost
|
76
|
+
# @return [Array] non-keys arguments (keys afer '--' or other arguments)
|
77
|
+
# @yield [String] non keys argumenrs (same as returned)
|
78
|
+
def parse args=nil
|
79
|
+
@args = args if args
|
80
|
+
no_more_keys = false
|
81
|
+
rest = []
|
82
|
+
while !@args.empty?
|
83
|
+
a = @args.shift
|
84
|
+
case
|
85
|
+
when no_more_keys
|
86
|
+
rest << a
|
87
|
+
when (data = @keys[a])
|
88
|
+
@required.delete data
|
89
|
+
if data.needs_value
|
90
|
+
value = @args.shift or raise "Value needed for key #{a}"
|
91
|
+
data.block.call value
|
92
|
+
else
|
93
|
+
data.block.call
|
94
|
+
end
|
95
|
+
when a == '--'
|
96
|
+
no_more_keys = true
|
97
|
+
when a[0] == '-'
|
98
|
+
raise Error, "Unknown key #{a}"
|
99
|
+
else
|
100
|
+
rest << a
|
101
|
+
end
|
102
|
+
end
|
103
|
+
@required.each { |data|
|
104
|
+
raise Error, "Required key is missing: #{data.key}" if !data.default_set
|
105
|
+
data.block.call data.default
|
106
|
+
}
|
107
|
+
block_given? and rest.each { |a| yield a }
|
108
|
+
rest
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generate keys documentation multiline text
|
112
|
+
def keys_doc
|
113
|
+
res = []
|
114
|
+
@docs.each { |d|
|
115
|
+
keys = [d.key] + d.aliases
|
116
|
+
str = "\t#{keys.join(',')}"
|
117
|
+
if d.needs_value
|
118
|
+
str += " value"
|
119
|
+
if d.default
|
120
|
+
str += " (default: #{d.default})" if d.default
|
121
|
+
else
|
122
|
+
str += ' (optional)'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
res << str
|
126
|
+
d.doc and d.doc.split("\n").each{ |l| res << "\t\t#{l}" }
|
127
|
+
}
|
128
|
+
res.join("\n")
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
data/lib/h8/version.rb
CHANGED
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
errorsCount = 0
|
2
|
+
|
3
|
+
class TestFailed extends Error
|
4
|
+
|
5
|
+
fail = (msg) ->
|
6
|
+
msg = "Failed: #{msg}\n"
|
7
|
+
errorsCount++
|
8
|
+
try
|
9
|
+
throw Error()
|
10
|
+
catch e
|
11
|
+
msg += e.stack.split("\n")[3]+' '+__FILE__
|
12
|
+
console.error msg
|
13
|
+
|
14
|
+
ok = (condition, message='') ->
|
15
|
+
condition or fail "condition is not true"
|
16
|
+
|
17
|
+
gt = (a, b, message = '') ->
|
18
|
+
unless a > b
|
19
|
+
fail "#{a} > #{b} #{message}"
|
20
|
+
|
21
|
+
ge = (a, b, message = '') ->
|
22
|
+
unless a >= b
|
23
|
+
fail "#{a} >= #{b} #{message}"
|
24
|
+
|
25
|
+
lt = (a, b, message = '') ->
|
26
|
+
unless a < b
|
27
|
+
fail "#{a} < #{b} #{message}"
|
28
|
+
|
29
|
+
le = (a, b, message = '') ->
|
30
|
+
unless a <= b
|
31
|
+
fail "#{a} <= #{b} #{message}"
|
32
|
+
|
33
|
+
eq = (a, b, message = '') ->
|
34
|
+
unless a == b
|
35
|
+
fail "#{a} == #{b} #{message}"
|
36
|
+
|
37
|
+
ne = (a, b, message = '') ->
|
38
|
+
unless a != b
|
39
|
+
fail "#{a} != #{b} #{message}"
|
40
|
+
|
41
|
+
|
42
|
+
ok File.dirname(__FILE__).match(/spec\/coffee$/)
|
43
|
+
eq File.extname(__FILE__), '.coffee'
|
44
|
+
|
45
|
+
eq File.basename(__FILE__), 'cli_tests.coffee'
|
46
|
+
|
47
|
+
if errorsCount > 0
|
48
|
+
print "#{errorsCount} tests failed"
|
49
|
+
else
|
50
|
+
print "All tests passed"
|