plasma 0.0.1

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.
@@ -0,0 +1,97 @@
1
+ module Plasma
2
+ module Interpreter
3
+ grammar PlasmaGrammar
4
+ rule plasma
5
+ decl / seq / quote / defun / def / fun / if / apply / bool / sym / hash / list / date / time / str / num
6
+ end
7
+
8
+ rule decl
9
+ '|' x first:plasma? rest:(s '|' s plasma)* x '|' <DeclNode>
10
+ end
11
+
12
+ rule seq
13
+ '(' x first:plasma? rest:(s '|' s plasma)* x ')' <SeqNode>
14
+ end
15
+
16
+ rule quote
17
+ "'" plasma <QuoteNode>
18
+ end
19
+
20
+ rule defun
21
+ '(' x 'defun' s params s plasma x ')' <DefunNode>
22
+ end
23
+
24
+ rule def
25
+ '(' x 'def' s sym s plasma x ')' <DefNode>
26
+ end
27
+
28
+ rule fun
29
+ '(' x 'fun' s params s plasma x ')' <FunNode>
30
+ end
31
+
32
+ rule params
33
+ '(' x first:sym rest:(s sym)* x ')' <ParamsNode>
34
+ end
35
+
36
+ rule if
37
+ '(' x 'if' s pred:plasma s 'then' s body:plasma maybe:(s 'else' s other:plasma)? x ')' <IfNode>
38
+ end
39
+
40
+ rule apply
41
+ '(' x applied:(fun / sym) s first:plasma rest:(s plasma)* x ')' <ApplyNode>
42
+ end
43
+
44
+ rule bool
45
+ 'true' <TrueNode> / 'false' <FalseNode>
46
+ end
47
+
48
+ rule sym
49
+ [a-z+!/\^&@?*%<>=_-]+ <SymNode>
50
+ end
51
+
52
+ rule hash
53
+ '{' x first:relation? rest:(s relation)* x '}' <HashNode>
54
+ end
55
+
56
+ rule relation
57
+ sym x ':' x plasma <RelationNode>
58
+ end
59
+
60
+ rule list
61
+ '[' x first:plasma? rest:(s plasma)* x ']' <ListNode>
62
+ end
63
+
64
+ rule date
65
+ year [-] month:dual [-] day:dual (s time)? <DateNode>
66
+ end
67
+
68
+ rule year
69
+ [0-9] [0-9] [0-9] [0-9]
70
+ end
71
+
72
+ rule dual
73
+ [0-9] [0-9]
74
+ end
75
+
76
+ rule time
77
+ hours:dual ':' minutes:dual ':' seconds:dual <TimeNode>
78
+ end
79
+
80
+ rule str
81
+ '"' first:([^"])? rest:([^"])* '"' <StrNode>
82
+ end
83
+
84
+ rule num
85
+ whole:([0-9]+) expansion:('.' [0-9]+)? <NumNode>
86
+ end
87
+
88
+ rule x
89
+ s?
90
+ end
91
+
92
+ rule s
93
+ [ \n]+
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,331 @@
1
+ module Plasma
2
+ module Interpreter
3
+ class Env
4
+ attr_accessor :state
5
+ attr_accessor :default
6
+
7
+ def initialize(init={}, default=nil)
8
+ @state = [init]
9
+ @default = default
10
+ end
11
+
12
+ def to_s
13
+ @state.map {|s| s.inspect}.join("\n")
14
+ end
15
+
16
+ def to_plasma
17
+ @state.to_plasma
18
+ end
19
+
20
+ def bind!(key, value)
21
+ @state.last[key] = value
22
+ self
23
+ end
24
+
25
+ def merge!(hash)
26
+ @state.last.merge!(hash)
27
+ self
28
+ end
29
+
30
+ def scope!(inner={})
31
+ @state.push(inner)
32
+ self
33
+ end
34
+
35
+ def release!()
36
+ @state.pop
37
+ self
38
+ end
39
+
40
+ def resolve(key)
41
+ @state.reverse_each do |layer|
42
+ return layer[key] if layer.include?(key)
43
+ end
44
+
45
+ # nothing to find
46
+ return default
47
+ end
48
+ end
49
+
50
+ class Quote
51
+ attr_reader :unevaluated
52
+
53
+ def initialize(plasma)
54
+ @unevaluated = plasma
55
+ end
56
+
57
+ def unquote(env)
58
+ @unevaluated.eval(env)
59
+ end
60
+
61
+ def to_plasma
62
+ "'#{@unevaluated.text_value}"
63
+ end
64
+ end
65
+
66
+ class Closure
67
+ attr_reader :env, :params, :body
68
+
69
+ def initialize(env, params, body)
70
+ @env = env.dup
71
+ @params = params
72
+ @body = body
73
+ @proc = nil
74
+ end
75
+
76
+ def apply(*args)
77
+ left = params.dup
78
+ args = args.slice(0...left.length) if left.length < args.length
79
+ zipped = args.inject({}){|hash, arg| hash.merge(left.shift => arg)}
80
+ zipped.merge!(:env => @env)
81
+
82
+ if left.empty?
83
+ @env.scope!(zipped)
84
+ value = @body.evaluate(@env)
85
+ @env.release!()
86
+
87
+ return value
88
+ else
89
+ return Closure.new(@env.dup.merge!(zipped), left, body)
90
+ end
91
+ end
92
+
93
+ def to_proc
94
+ @proc ||= Proc.new do |*args|
95
+ self.apply *args
96
+ end
97
+ end
98
+
99
+ def to_plasma
100
+ p = params.map{|p| p.to_s}.join(' ')
101
+ "fun (#{p}) #{@body.text_value}"
102
+ end
103
+ end
104
+
105
+ class RubyClosure
106
+ def initialize(name, interp, &body)
107
+ @name = name
108
+ @interp = interp
109
+ @body = Proc.new(&body)
110
+ end
111
+
112
+ def apply(*args)
113
+ args = args.slice(0..@body.arity) if @body.arity < args.length
114
+ diff = @body.arity - args.length
115
+ if diff == 0
116
+ value = @body.call(*args)
117
+ else
118
+ fresh = {}
119
+ args.each_with_index {|arg, index| fresh.merge(index => arg)}
120
+ params = (args.length..args.length+diff).join(' ')
121
+ keys = fresh.keys.join(' ')
122
+ env = Env.new(fresh.merge(@name => self))
123
+
124
+ interp.interpret("fun (#{params}) ((#{@name} #{keys} #{params}))", env)
125
+ end
126
+ end
127
+ end
128
+
129
+ class UnresolvedSymbolException < Exception
130
+ attr_accessor :symbol
131
+
132
+ def initialize(symbol)
133
+ @symbol = symbol
134
+ end
135
+ end
136
+
137
+ class NoSuchSourceException < Exception
138
+ attr_accessor :plasma
139
+
140
+ def initialize(plasma)
141
+ @plasma = plasma
142
+ end
143
+ end
144
+
145
+ class TooManyArgumentsException < Exception
146
+ end
147
+
148
+ class FailedToParseException < Exception
149
+ end
150
+
151
+ class PlasmaNode < Treetop::Runtime::SyntaxNode
152
+ def evaluate(env)
153
+ orb.evaluate(env)
154
+ end
155
+
156
+ def empty?
157
+ false
158
+ end
159
+ end
160
+
161
+ class ColNode < PlasmaNode
162
+ def col
163
+ if first.empty?
164
+ []
165
+ else
166
+ if rest.empty?
167
+ [first]
168
+ else
169
+ rest.elements.map do |el|
170
+ el.elements.select{|subel| subel.is_a? PlasmaNode}
171
+ end.flatten.unshift(first)
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ class DeclNode < ColNode
178
+ def evaluate(env)
179
+ value = col.inject(nil) do |value, statement|
180
+ statement.evaluate(env)
181
+ end
182
+ value
183
+ end
184
+ end
185
+
186
+ class SeqNode < ColNode
187
+ def evaluate(env)
188
+ env.scope!
189
+ value = col.inject(nil) do |value, statement|
190
+ statement.evaluate(env)
191
+ end
192
+ env.release!
193
+
194
+ value
195
+ end
196
+ end
197
+
198
+ class QuoteNode < PlasmaNode
199
+ def evaluate(env)
200
+ plasma
201
+ end
202
+ end
203
+
204
+ class DefunNode < PlasmaNode
205
+ def evaluate(env)
206
+ syms = params.syms
207
+ closure = Closure.new(env, syms.slice(1..syms.length), plasma)
208
+ closure.env.merge!(syms[0] => closure)
209
+ env.bind!(syms[0], closure)
210
+ closure
211
+ end
212
+ end
213
+
214
+ class DefNode < PlasmaNode
215
+ def evaluate(env)
216
+ value = plasma.evaluate(env)
217
+ env.bind!(sym.text_value.to_sym, value)
218
+ value
219
+ end
220
+ end
221
+
222
+ class FunNode < PlasmaNode
223
+ def evaluate(env)
224
+ Closure.new(env, params.syms, plasma)
225
+ end
226
+ end
227
+
228
+ class ParamsNode < ColNode
229
+ def syms
230
+ col.map{|node| node.text_value.to_sym}
231
+ end
232
+ end
233
+
234
+ class IfNode < PlasmaNode
235
+ def evaluate(env)
236
+ if pred.evaluate(env)
237
+ body.evaluate(env)
238
+ elsif respond_to?(:maybe)
239
+ maybe.other.evaluate(env)
240
+ else
241
+ nil
242
+ end
243
+ end
244
+ end
245
+
246
+ class ApplyNode < ColNode
247
+ def evaluate(env)
248
+ args = col.map{|arg| arg.evaluate(env)}
249
+
250
+ begin
251
+ closure = applied.evaluate(env)
252
+ return closure.apply(*args)
253
+ rescue UnresolvedSymbolException => detail
254
+ begin
255
+ return args.first.send(detail.symbol, *args.slice(1..args.length))
256
+ rescue ArgumentError => arg
257
+ begin
258
+ slice = args.slice(1...args.length-1)
259
+ proc = args.last.to_proc
260
+
261
+ return args.first.send(detail.symbol, *slice, &proc)
262
+ rescue Exception => brg
263
+ raise TooManyArgumentsException.new(self.text_value), "too many arguments in #{self.text_value}", caller
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ class TrueNode < PlasmaNode
271
+ def evaluate(env)
272
+ true
273
+ end
274
+ end
275
+
276
+ class FalseNode < PlasmaNode
277
+ def evaluate(env)
278
+ false
279
+ end
280
+ end
281
+
282
+ class SymNode < PlasmaNode
283
+ def evaluate(env)
284
+ result = env.resolve(self.text_value.to_sym)
285
+ raise UnresolvedSymbolException.new(self.text_value), "unresolved symbol #{self.text_value}", caller if result.nil?
286
+ result
287
+ end
288
+ end
289
+
290
+ class HashNode < ColNode
291
+ def evaluate(env)
292
+ col.inject({}) {|hash, el| hash.merge(el.evaluate(env))}
293
+ end
294
+ end
295
+
296
+ class RelationNode < PlasmaNode
297
+ def evaluate(env)
298
+ {sym.text_value.to_sym => plasma.evaluate(env)}
299
+ end
300
+ end
301
+
302
+ class ListNode < ColNode
303
+ def evaluate(env)
304
+ col.map{|el| el.evaluate(env)}
305
+ end
306
+ end
307
+
308
+ class DateNode < PlasmaNode
309
+ def evaluate(env)
310
+ end
311
+ end
312
+
313
+ class TimeNode < PlasmaNode
314
+ def evaluate(env)
315
+ end
316
+ end
317
+
318
+ class StrNode < ColNode
319
+ def evaluate(env)
320
+ self.text_value.slice(1...self.text_value.length-1)
321
+ end
322
+ end
323
+
324
+ class NumNode < PlasmaNode
325
+ def evaluate(env)
326
+ text_value.to_f
327
+ end
328
+ end
329
+ end
330
+ end
331
+
@@ -0,0 +1,104 @@
1
+ module Plasma
2
+ module Interpreter
3
+ class PlasmaInterpreter
4
+ attr_accessor :env
5
+
6
+ def initialize
7
+ @prompt = "-----| "
8
+ @dir = File.dirname(__FILE__)
9
+ @load_path = [File.join(PLASMA_ROOT, 'include'), PLASMA_PACKAGE_ROOT, @dir]
10
+
11
+ @env = Env.new
12
+ @env.bind!(:mu, self)
13
+ @env.bind!(:env, @env)
14
+
15
+ @plasma = PlasmaGrammarParser.new
16
+
17
+ import 'plasma_core'
18
+ merge 'core'
19
+ end
20
+
21
+ def to_s
22
+ "plasma -- #{@env.keys}"
23
+ end
24
+
25
+ def to_plasma
26
+ "eval"
27
+ end
28
+
29
+ def path
30
+ @load_path
31
+ end
32
+
33
+ def import(rb)
34
+ name = rb.split('/').last
35
+ file = "#{rb}.rb"
36
+ @load_path.each do |p|
37
+ package = File.join(p, file)
38
+ if File.exist? package
39
+ load package
40
+ @env.merge!(Plasma::Interpreter.const_get(name.classify).plasma(self))
41
+
42
+ return true
43
+ end
44
+ end
45
+
46
+ return false
47
+ end
48
+
49
+ def merge(plasma)
50
+ name = plasma.split('/').last
51
+ file = "#{plasma}.plasma"
52
+ found = false
53
+ value = nil
54
+
55
+ @load_path.each do |p|
56
+ package = File.join(p, file)
57
+ if File.exist? package
58
+ source = File.open(package, 'r')
59
+ code = source.read.strip
60
+
61
+ value = self.interpret(code)
62
+ found = true
63
+ end
64
+ end
65
+
66
+ return value if found
67
+ raise NoSuchSourceException.new(plasma), "no such source #{plasma}", caller
68
+ end
69
+
70
+ def parse(code)
71
+ tree = @plasma.parse(code)
72
+ raise FailedToParseException.new, "failed to parse #{code}", caller if tree.nil?
73
+
74
+ return tree
75
+ end
76
+
77
+ def evaluate(tree, env=nil)
78
+ env = @env if env.nil?
79
+ tree.evaluate(env)
80
+ end
81
+
82
+ def interpret(code, env=nil)
83
+ tree = parse(code)
84
+ evaluate(tree, env)
85
+ end
86
+
87
+ def repl
88
+ while true
89
+ begin
90
+ STDOUT.write(@prompt)
91
+ input = STDIN.readline.strip
92
+ break if input == 'quit' or input == 'exit'
93
+
94
+ value = interpret input
95
+
96
+ STDOUT.write(" #{value.to_plasma}\n")
97
+ rescue Exception => e
98
+ STDOUT.write(" #{e.to_s}\n")
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end