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