plasma 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +1 -0
- data/Rakefile +28 -0
- data/bin/plasma +18 -0
- data/lib/extras/plasma_view.rb +18 -0
- data/lib/plasma.rb +11 -0
- data/lib/plasma/include/core.plasma +3 -0
- data/lib/plasma/include/plasma_core.rb +27 -0
- data/lib/plasma/interpreter.rb +9 -0
- data/lib/plasma/interpreter/class_mods.rb +43 -0
- data/lib/plasma/interpreter/plasma_grammar.rb +1996 -0
- data/lib/plasma/interpreter/plasma_grammar.treetop +97 -0
- data/lib/plasma/interpreter/plasma_grammarnode.rb +331 -0
- data/lib/plasma/interpreter/plasma_interpreter.rb +104 -0
- data/lib/plasma/template.rb +3 -0
- data/lib/plasma/template/plasma_template.rb +57 -0
- data/test/import_test.plasma +26 -0
- data/test/plasmatest.rb +37 -0
- data/test/plasmic.plasma +12 -0
- metadata +83 -0
@@ -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
|