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