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