goodguide-gibbon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'http://rubygems.org/'
2
+ gemspec
3
+
4
+ gem 'minitest', '~> 4.0'
5
+ gem 'wrong'
6
+ gem 'pry'
7
+
8
+ gem 'rake'
@@ -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,7 @@
1
+ module GoodGuide
2
+ module Gibbon
3
+ def self.version
4
+ '0.1.0'
5
+ end
6
+ end
7
+ 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