rVM 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +38 -0
- data/lib/rvm/classes/string.rb +1 -1
- data/lib/rvm/interpreter.rb +56 -10
- data/lib/rvm/languages/math/compiler.rb +63 -29
- data/lib/rvm/languages/math/tokenizer.rb +62 -44
- data/lib/rvm/languages/math/tree.rb +97 -88
- metadata +2 -2
data/README
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
= rVM
|
2
|
+
|
3
|
+
rVM is the implementation of a VirtualMachine in pure Ruby. To clarify, no virtual machine as in VMWare that lets you virtualize a computer. But rather like ruby 1.9 or JRuby that both implement a virtual machine (written in C or Java) to execute Ruby code.
|
4
|
+
Just that rVM takes it the other way around, it is written in ruby and lets you execute code within ruby.
|
5
|
+
|
6
|
+
= So what is so cool about that?
|
7
|
+
|
8
|
+
For once, the challenge to pull this off ;P. Then it has on huge advantage. It makes it pretty easy to give ruby programs a interface for scripting.
|
9
|
+
|
10
|
+
Now you might think 'wow cool but I could just use ruby!' that is right, and wrong. Imagine just short code snippets to make a query, perhaps you want to implement a SQL based language to let a user query data in your program? Also consider you want your website to allow user side scripts. I would not want to have ruby code executed for random visitors. Then again I might be more tempted to let them execute code within a VM that has no access to anything outside it's VM neither variables nor hardware nor memory.
|
11
|
+
|
12
|
+
= Example
|
13
|
+
|
14
|
+
|
15
|
+
The following example implements a pretty easy calculator using the the math language shipped with rVM. Set variables and are not used outside one line
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'rvm' # This loads the rVM gem
|
19
|
+
require 'rvm/languages/math' #This loads the math plugin as well as the math function library
|
20
|
+
s =''
|
21
|
+
compiler = RVM::Languages[:math] #Lets get us the compiler
|
22
|
+
while s!= 'QUIT'
|
23
|
+
s=gets.chomp
|
24
|
+
puts compiler.compile(s).execute(RVM::Interpreter.env) # We pass an fresh enviroment as we don't need an environment.
|
25
|
+
end
|
26
|
+
|
27
|
+
Here one example that keeps set variables due to using one enviroment for all the calls.
|
28
|
+
|
29
|
+
require 'rubygems'
|
30
|
+
require 'rvm' # This loads the rVM gem
|
31
|
+
require 'rvm/languages/math' #This loads the math plugin as well as the math function library
|
32
|
+
s =''
|
33
|
+
compiler = RVM::Languages[:math] #Lets get us the compiler
|
34
|
+
env = RVM::Interpreter.env
|
35
|
+
while s!= 'QUIT'
|
36
|
+
s=gets.chomp
|
37
|
+
puts compiler.compile(s).execute(env) # We pass an fresh enviroment as we don't need an environment.
|
38
|
+
end
|
data/lib/rvm/classes/string.rb
CHANGED
data/lib/rvm/interpreter.rb
CHANGED
@@ -39,12 +39,13 @@ module RVM
|
|
39
39
|
RVM::debug "data: #{data}\noldenv:#{oldenv}"
|
40
40
|
@data = {
|
41
41
|
:locals => {},
|
42
|
+
:functions => {},
|
42
43
|
:caller => nil,
|
43
44
|
:self => nil,
|
44
45
|
:evaldeepth => 0,
|
45
46
|
:params => []
|
46
47
|
}.merge(data)
|
47
|
-
@prev = oldenv
|
48
|
+
@prev = oldenv || {}
|
48
49
|
RVM::debug "Creating new enviroment with: #{@data.inspect} as child of #{@prev}"
|
49
50
|
|
50
51
|
end
|
@@ -86,7 +87,7 @@ module RVM
|
|
86
87
|
# the given name it tries the privious enviroments.
|
87
88
|
def [] k
|
88
89
|
r = @data[:locals][k] || @prev[k]
|
89
|
-
RVM::debug "Getting
|
90
|
+
RVM::debug "Getting variable #{k} (#{r})"
|
90
91
|
r
|
91
92
|
end
|
92
93
|
|
@@ -97,6 +98,38 @@ module RVM
|
|
97
98
|
@data[:locals][k] = v
|
98
99
|
end
|
99
100
|
|
101
|
+
#Returns a function for the current enviroment
|
102
|
+
def function k
|
103
|
+
r = @data[:functions][k]
|
104
|
+
if not r and @prev.is_a? Enviroment
|
105
|
+
r = @prev.function(k)
|
106
|
+
end
|
107
|
+
RVM::debug "Getting functon #{k} (#{r})"
|
108
|
+
r
|
109
|
+
end
|
110
|
+
|
111
|
+
#Returns a function for the current enviroment
|
112
|
+
def def_function k, b
|
113
|
+
@data[:functions][k] = b
|
114
|
+
RVM::debug "Setting functon #{k} (#{b})"
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class FunctionDefinition
|
120
|
+
def initialize name, body, override = true
|
121
|
+
@name = name
|
122
|
+
@body = body
|
123
|
+
@override = override
|
124
|
+
end
|
125
|
+
|
126
|
+
def execute env
|
127
|
+
if (not @override) and env.function(@name)
|
128
|
+
raise "Function #{@name} already defined!"
|
129
|
+
end
|
130
|
+
env.def_function(@name.execute(env),@body.execute(env))
|
131
|
+
nil
|
132
|
+
end
|
100
133
|
end
|
101
134
|
|
102
135
|
class Loop
|
@@ -284,7 +317,7 @@ module RVM
|
|
284
317
|
def execute env
|
285
318
|
RVM::debug "Executing Parameter..."
|
286
319
|
r = env.param(@num.execute(env).to_i)
|
287
|
-
@type = r.data_type
|
320
|
+
@type = r.data_type if r
|
288
321
|
r
|
289
322
|
end
|
290
323
|
end
|
@@ -303,6 +336,10 @@ module RVM
|
|
303
336
|
r
|
304
337
|
end
|
305
338
|
|
339
|
+
def + v
|
340
|
+
Sequence.new(super)
|
341
|
+
end
|
342
|
+
|
306
343
|
def data_type
|
307
344
|
:any
|
308
345
|
end
|
@@ -322,11 +359,7 @@ module RVM
|
|
322
359
|
#
|
323
360
|
# Arguments is a list of the arguments to the function.
|
324
361
|
def initialize function, arguments
|
325
|
-
|
326
|
-
@function = function
|
327
|
-
else
|
328
|
-
@function = RVM::Functions[function]
|
329
|
-
end
|
362
|
+
@function = function
|
330
363
|
@arguments = arguments
|
331
364
|
end
|
332
365
|
|
@@ -345,12 +378,25 @@ module RVM
|
|
345
378
|
def execute env
|
346
379
|
RVM::debug "Executing FunctionCall..."
|
347
380
|
args = @arguments
|
348
|
-
if @function.
|
381
|
+
if @function.is_a? RVM::Classes[:block]
|
382
|
+
args = @arguments.map do |arg|
|
383
|
+
arg.execute env
|
384
|
+
end
|
385
|
+
@function.call(args, env)
|
386
|
+
elsif (fun = env.function(@function))
|
349
387
|
args = @arguments.map do |arg|
|
350
388
|
arg.execute env
|
351
389
|
end
|
390
|
+
fun.call(args, env)
|
391
|
+
else
|
392
|
+
fun = RVM::Functions[@function]
|
393
|
+
if fun.execargs
|
394
|
+
args = @arguments.map do |arg|
|
395
|
+
arg.execute env
|
396
|
+
end
|
397
|
+
end
|
398
|
+
fun.call(args, env)
|
352
399
|
end
|
353
|
-
@function.call(args, env)
|
354
400
|
end
|
355
401
|
end
|
356
402
|
end
|
@@ -1,36 +1,70 @@
|
|
1
1
|
module RVM
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
module Languages
|
3
|
+
module Math
|
4
|
+
class Compiler
|
5
|
+
include RVM::Interpreter
|
6
|
+
FUNCTION_MAP = {
|
7
7
|
'+' => :add,
|
8
8
|
'-' => :sub,
|
9
9
|
'/' => :div,
|
10
10
|
'*' => :mul,
|
11
11
|
'^' => :power
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
12
|
+
}
|
13
|
+
def Compiler.compile tree
|
14
|
+
if tree.is_a? Array
|
15
|
+
compile(tree.first)
|
16
|
+
elsif tree.is_a? Hash
|
17
|
+
case tree[:type]
|
18
|
+
when :number
|
19
|
+
Interpreter.const(:number, tree[:number])
|
20
|
+
when :function
|
21
|
+
params = tree[:params].map {|p| compile(p)}
|
22
|
+
case tree[:op]
|
23
|
+
when '-'
|
24
|
+
FunctionCall.new :neg, params
|
25
|
+
else
|
26
|
+
FunctionCall.new tree[:op], params
|
27
|
+
end
|
28
|
+
when :ident
|
29
|
+
Interpreter::Variable.new(Interpreter.const(:string, tree[:ident]))
|
30
|
+
when :op
|
31
|
+
case tree[:op]
|
32
|
+
when ';'
|
33
|
+
s = Interpreter::Sequence.new()
|
34
|
+
if (tree[:left][:type] == :op) and (tree[:left][:op] == ';')
|
35
|
+
s += compile(tree[:left])
|
36
|
+
else
|
37
|
+
s << compile(tree[:left])
|
38
|
+
end
|
39
|
+
if (tree[:right][:type] == :op) and (tree[:right][:op] == ';')
|
40
|
+
s += compile(tree[:right])
|
41
|
+
else
|
42
|
+
s << compile(tree[:right])
|
43
|
+
end
|
44
|
+
when '='
|
45
|
+
case tree[:left][:type]
|
46
|
+
when :ident
|
47
|
+
Interpreter::Assignment.new(Interpreter.const(:string, tree[:left][:ident]),compile(tree[:right]))
|
48
|
+
when :function
|
49
|
+
body = Interpreter::Sequence.new()
|
50
|
+
i = 0
|
51
|
+
tree[:left][:params].each do |p|
|
52
|
+
raise ArgumentError, "Bad ype for a function parameter: #{p[:type]} " if p[:type] != :ident
|
53
|
+
body << Interpreter::Assignment.new(Interpreter.const(:string, p[:ident]), Interpreter::Parameter.new(Interpreter.const(:number, i)))
|
54
|
+
end
|
55
|
+
body << compile(tree[:right])
|
56
|
+
Interpreter::FunctionDefinition.new(Interpreter.const(:string, tree[:left][:op]), Interpreter.const(:block,body))
|
57
|
+
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Bad name for a variable!"
|
60
|
+
end
|
61
|
+
else
|
62
|
+
FunctionCall.new(FUNCTION_MAP[tree[:op]], [compile(tree[:left]),compile(tree[:right])])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
36
70
|
end
|
@@ -1,47 +1,65 @@
|
|
1
1
|
require 'strscan'
|
2
2
|
module RVM
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
3
|
+
module Languages
|
4
|
+
module Math
|
5
|
+
class Tokenizer
|
6
|
+
def Tokenizer.tokenize str
|
7
|
+
s = StringScanner.new str
|
8
|
+
tokens = []
|
9
|
+
state = :number
|
10
|
+
paren_deepth = 0
|
11
|
+
while not s.eos?
|
12
|
+
s.skip(/\s*/)
|
13
|
+
if state == :number
|
14
|
+
if s.scan(/[+-]+/)
|
15
|
+
t = s.matched.gsub('+','').gsub('--','')
|
16
|
+
if t == '-'
|
17
|
+
tokens << [t, :function]
|
18
|
+
end
|
19
|
+
elsif s.scan(/[a-z][a-z0-9]*/i)
|
20
|
+
r = s.matched
|
21
|
+
if s.scan(/\(/)
|
22
|
+
tokens << [r, :function]
|
23
|
+
paren_deepth += 1
|
24
|
+
else
|
25
|
+
tokens << [r, :ident]
|
26
|
+
state = :opperator
|
27
|
+
end
|
28
|
+
elsif s.scan(/[\d]+(\.\d+)?/i)
|
29
|
+
tokens << [s.matched, :number]
|
30
|
+
state = :opperator
|
31
|
+
elsif s.scan(/\(/)
|
32
|
+
paren_deepth += 1
|
33
|
+
tokens << [s.matched, :paren_open]
|
34
|
+
else
|
35
|
+
raise "Unknown literal in term at positin #{s.pos}."
|
36
|
+
end
|
37
|
+
else
|
38
|
+
if s.scan(/\)/)
|
39
|
+
paren_deepth -= 1
|
40
|
+
raise "Unmatched parenthes at #{s.pos}" if paren_deepth < 0
|
41
|
+
tokens << [s.matched, :paren_close]
|
42
|
+
elsif s.scan(/,/)
|
43
|
+
tokens << [s.matched, :function_sep]
|
44
|
+
state = :number
|
45
|
+
elsif s.scan(/[*+\/^=-]/)
|
46
|
+
tokens << [s.matched, :opperator]
|
47
|
+
state = :number
|
48
|
+
elsif s.scan(/;/)
|
49
|
+
if paren_deepth == 0
|
50
|
+
tokens << [s.matched, :opperator]
|
51
|
+
state = :number
|
52
|
+
else
|
53
|
+
raise "Expression sepperator enclosed in parenthes at #{s.pos}"
|
54
|
+
end
|
55
|
+
else
|
56
|
+
raise "Unknown literal in term at positin #{s.pos}."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
tokens
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
47
65
|
end
|
@@ -1,100 +1,109 @@
|
|
1
1
|
module RVM
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
module Languages
|
3
|
+
module Math
|
4
|
+
class Tree
|
5
|
+
PRIORITIES = {
|
6
|
+
';' => -1,
|
7
|
+
'=' => 0,
|
6
8
|
'(' => 0,
|
7
9
|
'+' => 1,
|
8
10
|
'-' => 1,
|
9
11
|
'*' => 2,
|
10
12
|
'/' => 2,
|
11
13
|
'^' => 3,
|
12
|
-
|
13
|
-
|
14
|
+
}
|
15
|
+
ASSOSICATIONS = {
|
16
|
+
';' => :right,
|
14
17
|
'+' => :left,
|
15
18
|
'-' => :left,
|
16
19
|
'*' => :left,
|
17
20
|
'/' => :left,
|
18
|
-
'^' => :right
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
21
|
+
'^' => :right,
|
22
|
+
'=' => :right
|
23
|
+
}
|
24
|
+
def Tree.generate tokens
|
25
|
+
output = []
|
26
|
+
dbgoutput = []
|
27
|
+
stack = []
|
28
|
+
dbgstack = []
|
29
|
+
while not tokens.empty?
|
30
|
+
token = tokens.shift
|
31
|
+
case token[1]
|
32
|
+
when :function
|
33
|
+
fun = token[0]
|
34
|
+
stack << {:type => :function, :op => fun}
|
35
|
+
output << {:type => :function_end}
|
36
|
+
dbgoutput << ')'
|
37
|
+
dbgstack << fun
|
38
|
+
when :function_sep
|
39
|
+
while stack.last && (stack.last[:op] != '(')
|
40
|
+
output << stack.pop
|
41
|
+
dbgoutput << dbgstack.pop
|
42
|
+
end
|
43
|
+
when :paren_open
|
44
|
+
op = token[0]
|
45
|
+
stack << {:type => :op, :op => op}
|
46
|
+
dbgstack << op
|
47
|
+
when :paren_close
|
48
|
+
while stack.last && (stack.last[:op] != '(')
|
49
|
+
output << stack.pop
|
50
|
+
dbgoutput << dbgstack.pop
|
51
|
+
end
|
52
|
+
stack.pop
|
53
|
+
dbgstack.pop
|
54
|
+
if stack.last && (stack.last[:type] == :function)
|
55
|
+
output << stack.pop
|
56
|
+
dbgoutput << dbgstack.pop
|
57
|
+
end
|
58
|
+
when :number
|
59
|
+
output << {:type => :number, :number => token[0]}
|
60
|
+
dbgoutput << token[0]
|
61
|
+
when :ident
|
62
|
+
output << {:type => :ident, :ident => token[0]}
|
63
|
+
dbgoutput << token[0]
|
64
|
+
when :opperator
|
65
|
+
op = token[0]
|
66
|
+
ass = ASSOSICATIONS[op]
|
67
|
+
o1p = PRIORITIES[op]
|
68
|
+
while stack.last &&
|
69
|
+
(((ass == :left) && (o1p <= PRIORITIES[stack.last[:op]])) ||
|
70
|
+
((ass == :right) && (o1p < PRIORITIES[stack.last[:op]])))
|
71
|
+
output << stack.pop
|
72
|
+
dbgoutput << dbgstack.pop
|
73
|
+
end
|
74
|
+
stack << {:type => :op, :op => op}
|
75
|
+
dbgstack << op
|
76
|
+
else
|
77
|
+
end
|
78
|
+
end
|
79
|
+
while not stack.empty?
|
80
|
+
output << stack.pop
|
81
|
+
end
|
82
|
+
p output
|
83
|
+
gen_tree output
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def Tree.gen_tree data
|
88
|
+
p data
|
89
|
+
item = data.pop
|
90
|
+
case item[:type]
|
91
|
+
when :number, :ident
|
92
|
+
item
|
93
|
+
when :op
|
94
|
+
item[:right] = gen_tree(data)
|
95
|
+
item[:left] = gen_tree(data)
|
96
|
+
item
|
97
|
+
when :function
|
98
|
+
item[:params] = []
|
99
|
+
while data.last && data.last[:type] != :function_end
|
100
|
+
item[:params] << gen_tree(data)
|
101
|
+
end
|
102
|
+
data.pop
|
103
|
+
item
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
100
109
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rVM
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Heinz N. Gies
|
@@ -9,7 +9,7 @@ autorequire: ""
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-03-
|
12
|
+
date: 2008-03-05 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|