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 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 [interratron.rb](https://github.com/andrewvc/interrotron/blob/master/lib/interrotron.rb)
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))", {}, 4)
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
- [:datetime, /#dt\{([^\{]+)\}/, {capture: 1}],
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.vm_eval(pred) ? t_clause : f_clause },
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.vm_eval(pred)
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.vm_eval(a)} ? args.last : qvar('false') },
76
- 'or' => Macro.new {|i,*args| args.detect {|a| i.vm_eval(a) } || qvar('false') },
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
- mlen = matches[0].length
134
- str = str[mlen..-1]
135
- m = matches[opts[:capture] || 0]
136
- tokens << Token.new(name, m) unless opts[:discard] == true
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
- t = tokens.shift
163
- if t.type == :lpar
164
- while t = tokens[0]
165
- if t.type == :lpar
166
- expr << parse(tokens)
167
- else
168
- tokens.shift
169
- break if t.type == :rpar
170
- expr << cast(t)
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
- case token.type
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 += 1
195
- raise OpsThresholdError, "Exceeded max ops(#{@max_ops}) allowed!" if @op_count && @op_count > @max_ops
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 vm_eval(expr,max_ops=nil)
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 = vm_eval(expr[0])
191
+ head = iro_eval(expr[0])
204
192
  if head.is_a?(Macro)
205
193
  expanded = head.call(self, *expr[1..-1])
206
- vm_eval(expanded)
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
- args = expr[1..-1].map {|e|vm_eval(e)}
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
- vm_eval(ast)
214
+ #iro_eval(ast)
215
+ ast.map {|expr| iro_eval(expr)}.last
226
216
  }
227
217
  end
228
218
 
@@ -1,3 +1,3 @@
1
1
  class Interrotron
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -122,9 +122,13 @@ describe "running" do
122
122
  run("(length (array 1 2 3 'bob'))").should == 4
123
123
  end
124
124
 
125
- it "should implement detect correctly in the positive case" do
126
- pending "not now"
127
- #run("(detect (> 10 n) (array 1 5 30 1))").should
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))", {}, 4)
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.3
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: &70115221983600 !ruby/object:Gem::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: *70115221983600
24
+ version_requirements: *70187372115860
25
25
  description: A tiny, embeddable, lisp VM
26
26
  email:
27
27
  - andrew@andrewvc.com