interrotron 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.md +10 -4
- data/lib/interrotron.rb +42 -52
- data/lib/interrotron/version.rb +1 -1
- data/spec/interrotron_spec.rb +24 -4
- metadata +3 -3
data/README.md
CHANGED
@@ -11,7 +11,7 @@ A simple non-turing complete lisp meant to be embedded in apps as a rules engine
|
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
14
|
-
Either add the `interrotron` gem, or just copy and paste [
|
14
|
+
Either add the `interrotron` gem, or just copy and paste [interrotron.rb](https://github.com/andrewvc/interrotron/blob/master/lib/interrotron.rb)
|
15
15
|
|
16
16
|
## Usage
|
17
17
|
|
@@ -46,7 +46,7 @@ Interrotron.run("(if false
|
|
46
46
|
|
47
47
|
# Additionally, it is possible to constrain execution to a maximum number of
|
48
48
|
# operations by passing in a third argument
|
49
|
-
Interrotron.run("str (+ 1 2) (+ 3 4) (+ 5 7))", {},
|
49
|
+
Interrotron.run("(str (+ 1 2) (+ 3 4) (+ 5 7))", {}, 3)
|
50
50
|
# => raises Interrotron::OpsThresholdError since 4 operations were executed
|
51
51
|
|
52
52
|
```
|
@@ -64,14 +64,20 @@ The following functions and variables are built in to Interrotron (and more are
|
|
64
64
|
(floor expr) ; equiv to num.floor
|
65
65
|
(ceil expr) ; equiv to num.ceil
|
66
66
|
(round expr) ; equiv to num.round
|
67
|
-
(max lst) ; returns the largest element in a list
|
68
|
-
(min lst) ; returns the smallest element in a list
|
69
67
|
(to_i expr) ; int conversion
|
70
68
|
(to_f expr) ; float conversion
|
71
69
|
(rand) ; returns a random float between 0 and 1
|
72
70
|
(upcase str) ; uppercases a string
|
73
71
|
(downcase) ; lowercases a string
|
74
72
|
(now) ; returns the current DateTime
|
73
|
+
(array e1, e2, ...) ; creates an array
|
74
|
+
(max arr) ; returns the largest element of an array
|
75
|
+
(min arr) ; returns the smallest element of an array
|
76
|
+
(length arr) ; get the length of an array
|
77
|
+
(first arr) ; get arr head
|
78
|
+
(last arr) ; get arr tail
|
79
|
+
(nth pos arr) ; get array at index
|
80
|
+
(member? val arr) ; check if the array has a member with value 'val'
|
75
81
|
```
|
76
82
|
|
77
83
|
## Contributing
|
data/lib/interrotron.rb
CHANGED
@@ -47,9 +47,11 @@ class Interrotron
|
|
47
47
|
[:lpar, /\(/],
|
48
48
|
[:rpar, /\)/],
|
49
49
|
[:fn, /fn/],
|
50
|
-
[:var, /[A-Za-z_
|
51
|
-
[:num, /(\-?[0-9]+(\.[0-9]+)?)
|
52
|
-
|
50
|
+
[:var, /[A-Za-z_><\+\>\<\!\=\*\/\%\-\?]+/],
|
51
|
+
[:num, /(\-?[0-9]+(\.[0-9]+)?)/,
|
52
|
+
{cast: proc {|v| v =~ /\./ ? v.to_f : v.to_i }}],
|
53
|
+
[:datetime, /#dt\{([^\{]+)\}/,
|
54
|
+
{capture: 1, cast: proc {|v| DateTime.parse(v) }}],
|
53
55
|
[:spc, /\s+/, {discard: true}],
|
54
56
|
[:str, /"([^"\\]*(\\.[^"\\]*)*)"/, {capture: 1}],
|
55
57
|
[:str, /'([^'\\]*(\\.[^'\\]*)*)'/, {capture: 1}]
|
@@ -61,19 +63,19 @@ class Interrotron
|
|
61
63
|
end
|
62
64
|
|
63
65
|
DEFAULT_VARS = Hashie::Mash.new({
|
64
|
-
'if' => Macro.new {|i,pred,t_clause,f_clause| i.
|
66
|
+
'if' => Macro.new {|i,pred,t_clause,f_clause| i.iro_eval(pred) ? t_clause : f_clause },
|
65
67
|
'cond' => Macro.new {|i,*args|
|
66
68
|
raise InterroArgumentError, "Cond requires at least 3 args" unless args.length >= 3
|
67
69
|
raise InterroArgumentError, "Cond requires an even # of args!" unless args.length.even?
|
68
70
|
res = qvar('nil')
|
69
71
|
args.each_slice(2).any? {|slice|
|
70
72
|
pred, expr = slice
|
71
|
-
res = expr if i.
|
73
|
+
res = expr if i.iro_eval(pred)
|
72
74
|
}
|
73
75
|
res
|
74
76
|
},
|
75
|
-
'and' => Macro.new {|i,*args| args.all? {|a| i.
|
76
|
-
'or' => Macro.new {|i,*args| args.detect {|a| i.
|
77
|
+
'and' => Macro.new {|i,*args| args.all? {|a| i.iro_eval(a)} ? args.last : qvar('false') },
|
78
|
+
'or' => Macro.new {|i,*args| args.detect {|a| i.iro_eval(a) } || qvar('false') },
|
77
79
|
'array' => proc {|*args| args},
|
78
80
|
'identity' => proc {|a| a},
|
79
81
|
'not' => proc {|a| !a},
|
@@ -99,7 +101,9 @@ class Interrotron
|
|
99
101
|
'min' => proc {|arr| arr.min},
|
100
102
|
'first' => proc {|arr| arr.first},
|
101
103
|
'last' => proc {|arr| arr.last},
|
104
|
+
'nth' => proc {|pos, arr| arr[pos]},
|
102
105
|
'length' => proc {|arr| arr.length},
|
106
|
+
'member?' => proc {|v,arr| arr.member? v},
|
103
107
|
'to_i' => proc {|a| a.to_i},
|
104
108
|
'to_f' => proc {|a| a.to_f},
|
105
109
|
'rand' => proc { rand },
|
@@ -130,10 +134,12 @@ class Interrotron
|
|
130
134
|
if !matches || !matches.pre_match.empty?
|
131
135
|
false
|
132
136
|
else
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
+
str = str[matches[0].length..-1]
|
138
|
+
unless opts[:discard] == true
|
139
|
+
val = matches[opts[:capture] || 0]
|
140
|
+
val = opts[:cast].call(val) if opts[:cast]
|
141
|
+
tokens << Token.new(name, val)
|
142
|
+
end
|
137
143
|
true
|
138
144
|
end
|
139
145
|
}
|
@@ -142,45 +148,26 @@ class Interrotron
|
|
142
148
|
tokens
|
143
149
|
end
|
144
150
|
|
145
|
-
# Transforms token values to ruby types
|
146
|
-
def cast(t)
|
147
|
-
new_val = case t.type
|
148
|
-
when :num
|
149
|
-
t.value =~ /\./ ? t.value.to_f : t.value.to_i
|
150
|
-
when :datetime
|
151
|
-
DateTime.parse(t.value)
|
152
|
-
else
|
153
|
-
t.value
|
154
|
-
end
|
155
|
-
t.value = new_val
|
156
|
-
t
|
157
|
-
end
|
158
|
-
|
159
151
|
def parse(tokens)
|
160
|
-
return [] if tokens.empty?
|
152
|
+
return [] if !tokens || tokens.empty?
|
153
|
+
|
161
154
|
expr = []
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
end
|
155
|
+
while !tokens.empty?
|
156
|
+
t = tokens.shift
|
157
|
+
case t.type
|
158
|
+
when :lpar
|
159
|
+
expr << parse(tokens)
|
160
|
+
when :rpar
|
161
|
+
return expr
|
162
|
+
else
|
163
|
+
expr << t
|
172
164
|
end
|
173
|
-
elsif t.type != :rpar
|
174
|
-
tokens.shift
|
175
|
-
expr << cast(t)
|
176
|
-
#raise SyntaxError, "Expected :lparen, got #{t} while parsing #{tokens}"
|
177
165
|
end
|
178
166
|
expr
|
179
167
|
end
|
180
168
|
|
181
169
|
def resolve_token(token)
|
182
|
-
|
183
|
-
when :var
|
170
|
+
if token.type == :var
|
184
171
|
frame = @stack.reverse.find {|frame| frame.has_key?(token.value) }
|
185
172
|
raise UndefinedVarError, "Var '#{token.value}' is undefined!" unless frame
|
186
173
|
frame[token.value]
|
@@ -190,24 +177,26 @@ class Interrotron
|
|
190
177
|
end
|
191
178
|
|
192
179
|
def register_op
|
193
|
-
return unless @max_ops
|
194
|
-
@op_count
|
195
|
-
|
180
|
+
return unless @max_ops # noop when op counting disabled
|
181
|
+
if (@op_count+=1) > @max_ops
|
182
|
+
raise OpsThresholdError, "Exceeded max ops(#{@max_ops}) allowed!"
|
183
|
+
end
|
196
184
|
end
|
197
185
|
|
198
|
-
def
|
186
|
+
def iro_eval(expr,max_ops=nil)
|
199
187
|
return resolve_token(expr) if expr.is_a?(Token)
|
200
188
|
return nil if expr.empty?
|
201
189
|
register_op
|
202
190
|
|
203
|
-
head =
|
191
|
+
head = iro_eval(expr[0])
|
204
192
|
if head.is_a?(Macro)
|
205
193
|
expanded = head.call(self, *expr[1..-1])
|
206
|
-
|
194
|
+
iro_eval(expanded)
|
195
|
+
elsif head.is_a?(Proc)
|
196
|
+
args = expr[1..-1].map {|e| iro_eval(e) }
|
197
|
+
head.call(*args)
|
207
198
|
else
|
208
|
-
|
209
|
-
|
210
|
-
head.is_a?(Proc) ? head.call(*args) : head
|
199
|
+
raise InterroArgumentError, "Non FN/macro Value in head position!"
|
211
200
|
end
|
212
201
|
end
|
213
202
|
|
@@ -222,7 +211,8 @@ class Interrotron
|
|
222
211
|
reset!
|
223
212
|
@max_ops = max_ops
|
224
213
|
@stack = [@instance_default_vars.merge(vars)]
|
225
|
-
|
214
|
+
#iro_eval(ast)
|
215
|
+
ast.map {|expr| iro_eval(expr)}.last
|
226
216
|
}
|
227
217
|
end
|
228
218
|
|
data/lib/interrotron/version.rb
CHANGED
data/spec/interrotron_spec.rb
CHANGED
@@ -122,9 +122,13 @@ describe "running" do
|
|
122
122
|
run("(length (array 1 2 3 'bob'))").should == 4
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
125
|
+
describe "checking membership" do
|
126
|
+
it "should work in the false case" do
|
127
|
+
run("(member? 5 (array 10 20 30))").should be_false
|
128
|
+
end
|
129
|
+
it "should work in the true case" do
|
130
|
+
run("(member? 5 (array 10 5 30))").should be_true
|
131
|
+
end
|
128
132
|
end
|
129
133
|
end
|
130
134
|
|
@@ -143,7 +147,7 @@ describe "running" do
|
|
143
147
|
|
144
148
|
describe "op counter" do
|
145
149
|
it "should not stop scripts under or at the threshold" do
|
146
|
-
run("(str (+ 1 2) (+ 3 4) (+ 5 7))", {},
|
150
|
+
run("(str (+ 1 2) (+ 3 4) (+ 5 7))", {}, 5)
|
147
151
|
end
|
148
152
|
it "should terminate with the proper exception if over the threshold" do
|
149
153
|
proc {
|
@@ -151,4 +155,20 @@ describe "running" do
|
|
151
155
|
}.should raise_exception(Interrotron::OpsThresholdError)
|
152
156
|
end
|
153
157
|
end
|
158
|
+
|
159
|
+
describe "multiple forms at the root" do
|
160
|
+
it "should return the second form" do
|
161
|
+
run("(+ 1 1) (+ 2 2)").should == 4
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should raise an exception if a number is in a fn pos" do
|
165
|
+
proc { run("(1)") }.should raise_exception(Interrotron::InterroArgumentError)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "empty input" do
|
170
|
+
it "should return nil" do
|
171
|
+
run("").should be_nil
|
172
|
+
end
|
173
|
+
end
|
154
174
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interrotron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-05-27 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70187372115860 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '2.6'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70187372115860
|
25
25
|
description: A tiny, embeddable, lisp VM
|
26
26
|
email:
|
27
27
|
- andrew@andrewvc.com
|