rVM 0.0.3 → 0.0.4
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 +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
|
|