goodguide-gibbon 0.1.0
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.
- data/Gemfile +8 -0
- data/goodguide-gibbon.gemspec +24 -0
- data/lib/goodguide/gibbon/version.rb +7 -0
- data/lib/goodguide/gibbon.rb +189 -0
- data/vendor/gibbon/lib/gibbon.browser.js +2860 -0
- data/vendor/gibbon/package.json +25 -0
- metadata +68 -0
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require './lib/goodguide/gibbon/version'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "goodguide-gibbon"
|
6
|
+
s.version = GoodGuide::Gibbon.version
|
7
|
+
s.authors = ["Jay Adkisson"]
|
8
|
+
s.email = ["jjmadkisson@gmail.com"]
|
9
|
+
s.summary = "Ruby bindings for the gibbon data language"
|
10
|
+
s.description = <<-desc.strip.gsub(/\s+/, ' ')
|
11
|
+
Run and analyze gibbon code from ruby or a browser (via a ruby app).
|
12
|
+
desc
|
13
|
+
s.homepage = "http://github.com/GoodGuide/goodguide-gibbon"
|
14
|
+
s.rubyforge_project = "goodguide-gibbon"
|
15
|
+
s.files = FileList[
|
16
|
+
'Gemfile',
|
17
|
+
'goodguide-gibbon.gemspec',
|
18
|
+
'lib/**/*.rb',
|
19
|
+
'vendor/gibbon/package.json',
|
20
|
+
'vendor/gibbon/lib/gibbon.browser.js'
|
21
|
+
]
|
22
|
+
|
23
|
+
s.add_dependency 'therubyracer'
|
24
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'v8' # therubyracer
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module GoodGuide
|
6
|
+
module Gibbon
|
7
|
+
def self.root
|
8
|
+
Pathname.new(__FILE__).dirname.parent.parent
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.js_lib
|
12
|
+
root.join('vendor/gibbon/lib/gibbon.browser.js')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.js_source
|
16
|
+
@js_source ||= File.read(js_lib)
|
17
|
+
end
|
18
|
+
|
19
|
+
class SemanticError < StandardError
|
20
|
+
def initialize(errors)
|
21
|
+
@errors = errors
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class RuntimeError < StandardError; end
|
26
|
+
|
27
|
+
class Program
|
28
|
+
attr_reader :source
|
29
|
+
def initialize(global, context, source)
|
30
|
+
@global = global
|
31
|
+
@context = context
|
32
|
+
@source = source
|
33
|
+
end
|
34
|
+
|
35
|
+
def syntax
|
36
|
+
@syntax ||= @context.parse(@source)
|
37
|
+
end
|
38
|
+
|
39
|
+
def semantics
|
40
|
+
@semantics ||= begin
|
41
|
+
semantics, errors = @context.analyze(self.syntax, @global)
|
42
|
+
raise SemanticError.new(errors) if errors
|
43
|
+
semantics
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(entity_id)
|
48
|
+
values, error = @context.eval_gibbon(
|
49
|
+
self.semantics, @global, entity_id
|
50
|
+
)
|
51
|
+
|
52
|
+
raise RuntimeError.new(error) if error
|
53
|
+
values
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Client
|
58
|
+
def self.parse(global, source)
|
59
|
+
self.new.parse(global, source)
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse(global, source)
|
63
|
+
Program.new(global, context, source)
|
64
|
+
end
|
65
|
+
|
66
|
+
def context
|
67
|
+
@context ||= Context.new(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
def lookup_type(id, name)
|
71
|
+
raise 'abstract'
|
72
|
+
end
|
73
|
+
|
74
|
+
def lookup_value(id, annotations)
|
75
|
+
raise 'abstract'
|
76
|
+
end
|
77
|
+
|
78
|
+
def proc_for(method)
|
79
|
+
lambda do |this, *args|
|
80
|
+
cb = args.pop
|
81
|
+
err, val = begin
|
82
|
+
[nil, send(method, *args)]
|
83
|
+
rescue => e
|
84
|
+
[e.to_s, nil]
|
85
|
+
end
|
86
|
+
|
87
|
+
cb.call(err, val)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_js
|
92
|
+
{
|
93
|
+
getType: proc_for(:lookup_type),
|
94
|
+
getValue: proc_for(:lookup_value),
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Context < V8::Context
|
100
|
+
class Console
|
101
|
+
def log(*a)
|
102
|
+
puts a.map(&:to_s).join(' ')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_reader :client
|
107
|
+
def initialize(client)
|
108
|
+
super()
|
109
|
+
@client = client.to_js
|
110
|
+
self[:console] = Console.new
|
111
|
+
load GoodGuide::Gibbon.js_lib
|
112
|
+
end
|
113
|
+
|
114
|
+
def gibbon
|
115
|
+
self[:Gibbon]
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse(str)
|
119
|
+
obj_to_ruby gibbon.parse(str).asJSON
|
120
|
+
end
|
121
|
+
|
122
|
+
def analyze(syntax, global_table)
|
123
|
+
semantics, error = capture do |&callback|
|
124
|
+
gibbon.analyze(syntax, global_table, self.client, callback)
|
125
|
+
end
|
126
|
+
|
127
|
+
[hash_to_ruby(semantics), obj_to_ruby(error)]
|
128
|
+
end
|
129
|
+
|
130
|
+
def eval_gibbon(semantics, global_table, id)
|
131
|
+
semantics = hash_to_js(semantics)
|
132
|
+
values, error = capture do |&callback|
|
133
|
+
gibbon.eval(semantics, global_table, id, self.client, callback)
|
134
|
+
end
|
135
|
+
|
136
|
+
[hash_to_ruby(values), obj_to_ruby(error)]
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def capture(&b)
|
141
|
+
output = nil
|
142
|
+
error = nil
|
143
|
+
b.call do |this, error_, output_|
|
144
|
+
output, error = output_, error_
|
145
|
+
end
|
146
|
+
|
147
|
+
[output, error]
|
148
|
+
end
|
149
|
+
|
150
|
+
def hash_to_ruby(js_hash)
|
151
|
+
return nil unless js_hash
|
152
|
+
|
153
|
+
ruby_hash = {}
|
154
|
+
iterator = lambda { |this, k, v|
|
155
|
+
ruby_hash[k] = obj_to_ruby(v)
|
156
|
+
}
|
157
|
+
|
158
|
+
js_hash[:each].methodcall js_hash, iterator
|
159
|
+
|
160
|
+
ruby_hash
|
161
|
+
end
|
162
|
+
|
163
|
+
def hash_to_js(ruby_hash)
|
164
|
+
js_hash = gibbon[:Hash].new
|
165
|
+
ruby_hash.each do |k, v|
|
166
|
+
js_hash.set(k, v)
|
167
|
+
end
|
168
|
+
js_hash
|
169
|
+
end
|
170
|
+
|
171
|
+
def obj_to_ruby(o)
|
172
|
+
case o
|
173
|
+
when V8::Array
|
174
|
+
o.map { |x| obj_to_ruby(x) }
|
175
|
+
when V8::Object
|
176
|
+
o = o.asJSON if o.respond_to? :asJSON
|
177
|
+
|
178
|
+
out = {}
|
179
|
+
o.each do |k, v|
|
180
|
+
out[k] = obj_to_ruby(v)
|
181
|
+
end
|
182
|
+
out
|
183
|
+
else
|
184
|
+
o
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|