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