feelin 7.0.2 → 7.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a2aab7f50ce55a02f7c45876a113972cf952c23ab505a2a95f527c213015591
4
- data.tar.gz: 68ffbc5239c2f1387ea5982e6af9b7c9fc619a93ab43e42c2750a981cfb6813c
3
+ metadata.gz: 1d42d010d26029134be39b2bcdbd097683a00b0866820f1d816f69607b5bd053
4
+ data.tar.gz: cbee6c7dc98e5c2e078ebdecdbe3abac6dee702966841ffa3e5a3df399e69853
5
5
  SHA512:
6
- metadata.gz: da2fea8b67b029c34710ee51f1c0e3f491eb974e2270a83eaed1ac6d88935148882e56bbde7a7709e4543ffada442181f3b02ed14d407b9c0671087d59e46f95
7
- data.tar.gz: 3cf684bb66042782c7415b16d2cca27608ed45f2dc9eb2a83b6e25a4cfab1806cbd1c4c04148417651060164137491d654e67e21361ba79e4ca5ba47ff7d608c
6
+ metadata.gz: 247e1e7cce0bf1a66df682268273282000c3ef98ba755af7ac213f5b9c978cb30aa9f68bef46e4ab49ff0c2573cf25838ed11e0da51aca861d0b30ccd268f7cf
7
+ data.tar.gz: 5ff326f4111002f0f65fef3e84f31bf307d1fea982372c72805936ada9484e8b579a57fe853a3a88c1cf2f35b4b116271996327498853b8ef608969a3483050b
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [7.0.3] - 2026-10-05
9
+
10
+ ### Changed
11
+
12
+ * **Breaking:** `FEELIN.parse` is `FEELIN.parse_expression`, after feelin's own `parseExpression`.
13
+ * **Breaking:** errors are the gem's own. A failure inside V8 is raised as `FEELIN::Error` — `FEELIN::SyntaxError` for an expression that does not parse, `FEELIN::TimeoutError` and `FEELIN::MemoryError` for a context's limits — instead of `MiniRacer::RuntimeError`, `MiniRacer::ScriptTerminatedError` and `MiniRacer::V8OutOfMemoryError`. The message names the expression, also available as `error.expression`; `error.reason` is the cause without it. An exception raised by a custom function still passes unwrapped.
14
+ * **Breaking:** a custom function receives a date, a time and a duration as their ISO 8601 strings — the form a result has — instead of a hash of the internal fields of the JavaScript object.
15
+
8
16
  ## [7.0.2] - 2026-10-05
9
17
 
10
18
  ### Added
@@ -18,10 +26,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
26
  * **Breaking:** a result is JSON-compatible. A date, a time and a duration are now the ISO 8601 strings FEEL's `string()` gives them (`"2020-01-02"`, `"10:00:00+03:00"`, `"2020-01-02T03:04:05Z"`, `"P1DT2H"`) instead of a hash of the internal fields of the JavaScript object. A function is a hash of its `parameterNames`.
19
27
  * The context is passed to V8 as an argument rather than written into the evaluated script, so V8 no longer compiles and keeps a script per distinct context. Measured speed is the same as before.
20
28
 
21
- ### Not updated
22
-
23
- * `feelin` 8.2.0 was tried and held back: evaluating an expression that names a variable takes about twice as long there (8.2.0 looks every variable name up among the built-ins on each `evaluate`), and it produces enough garbage to run a context with `max_memory: 64_000_000` out of memory after ~10 000 evaluations.
24
-
25
29
  ## [7.0.1] - 2026-06-22
26
30
 
27
31
  ### Changed
data/README.md CHANGED
@@ -43,11 +43,11 @@ FEELIN.unary_test('[1..end]', 1, { 'end' => 10 }) # true
43
43
 
44
44
  ### Syntax tree
45
45
 
46
- `parse` and `parse_unary_tests` answer the syntax tree of an expression without evaluating it — for
46
+ `parse_expression` and `parse_unary_tests` answer the syntax tree of an expression without evaluating it — for
47
47
  translating FEEL into something else (SQL, for one) or inspecting what an expression refers to.
48
48
 
49
49
  ```ruby
50
- FEELIN.parse('price > 10')
50
+ FEELIN.parse_expression('price > 10')
51
51
  # { "type" => "Expression", "from" => 0, "to" => 10, "text" => "price > 10", "children" => [
52
52
  # { "type" => "Comparison", "from" => 0, "to" => 10, "text" => "price > 10", "children" => [
53
53
  # { "type" => "VariableName", "from" => 0, "to" => 5, "text" => "price", "children" => [
@@ -68,10 +68,10 @@ The optional second argument is a context, of which only the names matter: it is
68
68
  spaces in it be read as one name.
69
69
 
70
70
  ```ruby
71
- FEELIN.parse("Mike's daughter.name + 1", { "Mike's daughter.name" => nil })
71
+ FEELIN.parse_expression("Mike's daughter.name + 1", { "Mike's daughter.name" => nil })
72
72
  ```
73
73
 
74
- An expression that does not parse raises `MiniRacer::RuntimeError`, with the same message `evaluate` gives.
74
+ An expression that does not parse raises `FEELIN::SyntaxError`, with the same message `evaluate` gives.
75
75
 
76
76
  ### Custom functions
77
77
 
@@ -80,6 +80,18 @@ FEELIN.add_function('rates', proc { [10, 20] })
80
80
  FEELIN.evaluate('every rate in rates() satisfies rate < 10') # false
81
81
  ```
82
82
 
83
+ Arguments reach the function in the form a result has: a date, a time and a duration as their ISO 8601
84
+ strings, wherever they are among the arguments. What the function returns is plain data to FEEL — an ISO
85
+ string it returns is a string there, and `date(...)` makes a date of it.
86
+
87
+ ```ruby
88
+ FEELIN.add_function('last day of month', proc do |date|
89
+ day = Date.iso8601(date)
90
+ Date.new(day.year, day.month, -1).iso8601
91
+ end)
92
+ FEELIN.evaluate('date(last day of month(date("2024-02-10"))) + duration("P1D")') # "2024-03-01"
93
+ ```
94
+
83
95
  ### A context of one's own
84
96
 
85
97
  The methods above work on one shared V8 context, which has no limits. `FEELIN::Context` is a separate
@@ -91,14 +103,38 @@ context = FEELIN::Context.new(timeout: 1_000, max_memory: 64_000_000) # ms, byte
91
103
 
92
104
  context.add_function('rate', proc { 0.2 })
93
105
  context.evaluate('price * rate()', { 'price' => 100 }) # 20
94
- context.parse('price * rate()')
106
+ context.parse_expression('price * rate()')
95
107
 
96
108
  context.dispose
97
109
  ```
98
110
 
99
- Past its timeout an evaluation raises `MiniRacer::ScriptTerminatedError`, past its memory
100
- `MiniRacer::V8OutOfMemoryError`. Contexts are created from one snapshot of the bundle, so a new one does
101
- not load feelin again.
111
+ Past its timeout an evaluation raises `FEELIN::TimeoutError`, past its memory `FEELIN::MemoryError`.
112
+ Contexts are created from one snapshot of the bundle, so a new one does not load feelin again.
113
+
114
+ ### Errors
115
+
116
+ Whatever goes wrong inside V8 is raised as a `FEELIN::Error`, never as an error of MiniRacer. Its message
117
+ names the expression, which is also there as `error.expression`; `error.reason` is what went wrong without
118
+ it.
119
+
120
+ | error | when |
121
+ |---|---|
122
+ | `FEELIN::SyntaxError` | the expression does not parse |
123
+ | `FEELIN::TimeoutError` | the evaluation ran past the context's `timeout` |
124
+ | `FEELIN::MemoryError` | the evaluation ran past the context's `max_memory` |
125
+ | `FEELIN::Error` | anything else, and the parent of the three above |
126
+
127
+ ```ruby
128
+ begin
129
+ FEELIN.evaluate('1 +')
130
+ rescue FEELIN::SyntaxError => e
131
+ e.message # "1 +" is not a FEEL expression: Incomplete <ArithmeticExpression>
132
+ e.expression # 1 +
133
+ e.reason # Incomplete <ArithmeticExpression>
134
+ end
135
+ ```
136
+
137
+ An exception raised by a custom function is not wrapped: it reaches the caller as it was raised.
102
138
 
103
139
  ## Development
104
140
 
@@ -13687,14 +13687,26 @@ var feel = (() => {
13687
13687
  const context = (contextJson == null ? null : JSON.parse(contextJson)) || {};
13688
13688
  return hasFunctions ? Object.assign(context, functions) : context;
13689
13689
  }
13690
+ function guarded(fn2) {
13691
+ return (...args) => {
13692
+ try {
13693
+ return fn2(...args);
13694
+ } catch (error) {
13695
+ if (error instanceof SyntaxError2) {
13696
+ error.name = "FeelSyntaxError";
13697
+ }
13698
+ throw error;
13699
+ }
13700
+ };
13701
+ }
13690
13702
  var json = {
13691
- evaluate: (expression, contextJson) => toJson(evaluate(expression, readContext(contextJson)).value),
13692
- unaryTest: (expression, contextJson) => toJson(unaryTest(expression, readContext(contextJson)).value),
13693
- parseExpression: (expression, contextJson) => JSON.stringify(toAst(parseExpression(expression, readContext(contextJson)), expression)),
13694
- parseUnaryTests: (expression, contextJson) => JSON.stringify(toAst(parseUnaryTests(expression, readContext(contextJson)), expression)),
13703
+ evaluate: guarded((expression, contextJson) => toJson(evaluate(expression, readContext(contextJson)).value)),
13704
+ unaryTest: guarded((expression, contextJson) => toJson(unaryTest(expression, readContext(contextJson)).value)),
13705
+ parseExpression: guarded((expression, contextJson) => JSON.stringify(toAst(parseExpression(expression, readContext(contextJson)), expression))),
13706
+ parseUnaryTests: guarded((expression, contextJson) => JSON.stringify(toAst(parseUnaryTests(expression, readContext(contextJson)), expression))),
13695
13707
  addFunction: (name2) => {
13696
13708
  functions[name2] = function(...args) {
13697
- return globalThis[name2](...args);
13709
+ return globalThis[name2](...JSON.parse(toJson(args)));
13698
13710
  };
13699
13711
  hasFunctions = true;
13700
13712
  }
@@ -72,6 +72,9 @@ const toJson = (value) => JSON.stringify(value === undefined ? null : value, rep
72
72
  // Custom functions, by name. Each forwards to the global the Ruby side attached under that name; the
73
73
  // `(...args)` forwarder is deliberate: feelin reads a function's source to learn its parameters, so a bare
74
74
  // reference to the (native) attached global would be seen as taking no arguments.
75
+ //
76
+ // The arguments go to Ruby in the form a result does — through the same JSON, so a date, time or duration
77
+ // among them, however deep, is its ISO 8601 string there too and not the fields of a luxon object.
75
78
  const functions = {};
76
79
  let hasFunctions = false;
77
80
 
@@ -81,13 +84,29 @@ function readContext(contextJson) {
81
84
  return hasFunctions ? Object.assign(context, functions) : context;
82
85
  }
83
86
 
87
+ // A syntax error leaves under a name of its own: across the V8 boundary an error is only its name and its
88
+ // message, and feelin's SyntaxError keeps the name `Error`.
89
+ function guarded(fn) {
90
+ return (...args) => {
91
+ try {
92
+ return fn(...args);
93
+ } catch (error) {
94
+ if (error instanceof SyntaxError) {
95
+ error.name = 'FeelSyntaxError';
96
+ }
97
+
98
+ throw error;
99
+ }
100
+ };
101
+ }
102
+
84
103
  export const json = {
85
- evaluate: (expression, contextJson) => toJson(evaluate(expression, readContext(contextJson)).value),
86
- unaryTest: (expression, contextJson) => toJson(unaryTest(expression, readContext(contextJson)).value),
87
- parseExpression: (expression, contextJson) => JSON.stringify(toAst(parseExpression(expression, readContext(contextJson)), expression)),
88
- parseUnaryTests: (expression, contextJson) => JSON.stringify(toAst(parseUnaryTests(expression, readContext(contextJson)), expression)),
104
+ evaluate: guarded((expression, contextJson) => toJson(evaluate(expression, readContext(contextJson)).value)),
105
+ unaryTest: guarded((expression, contextJson) => toJson(unaryTest(expression, readContext(contextJson)).value)),
106
+ parseExpression: guarded((expression, contextJson) => JSON.stringify(toAst(parseExpression(expression, readContext(contextJson)), expression))),
107
+ parseUnaryTests: guarded((expression, contextJson) => JSON.stringify(toAst(parseUnaryTests(expression, readContext(contextJson)), expression))),
89
108
  addFunction: (name) => {
90
- functions[name] = function(...args) { return globalThis[name](...args); };
109
+ functions[name] = function(...args) { return globalThis[name](...JSON.parse(toJson(args))); };
91
110
  hasFunctions = true;
92
111
  }
93
112
  };
@@ -1,3 +1,3 @@
1
1
  module FEELIN
2
- VERSION = "7.0.2"
2
+ VERSION = "7.0.3"
3
3
  end
data/lib/feelin.rb CHANGED
@@ -9,12 +9,35 @@ module FEELIN
9
9
  # assembled at load time.
10
10
  BUNDLE_PATH = File.expand_path("feelin/js/dist/feelin.js", __dir__)
11
11
 
12
+ # What goes wrong inside V8 is raised as one of these, never as a MiniRacer
13
+ # error: the message names the expression, which is also kept in `expression`,
14
+ # and `reason` is what went wrong without it. An exception raised by a custom
15
+ # function is not one of them and passes as it is.
16
+ class Error < StandardError
17
+ attr_reader :expression, :reason
18
+
19
+ def initialize(message, expression = nil, reason = message)
20
+ super(message)
21
+ @expression = expression
22
+ @reason = reason
23
+ end
24
+ end
25
+
26
+ # the expression does not parse
27
+ class SyntaxError < Error; end
28
+
29
+ # the evaluation ran past the context's `timeout`
30
+ class TimeoutError < Error; end
31
+
32
+ # the evaluation ran past the context's `max_memory`
33
+ class MemoryError < Error; end
34
+
12
35
  # A V8 context with feelin loaded. The module-level methods below work on one
13
36
  # shared context without limits; a context of one's own is for an expression
14
37
  # that is not trusted to end or to stay small — `timeout` (ms) and `max_memory`
15
- # (bytes) are MiniRacer's limits, and going over one raises
16
- # MiniRacer::ScriptTerminatedError or MiniRacer::V8OutOfMemoryError — and for
17
- # custom functions that the shared context should not see.
38
+ # (bytes) are its limits, and going over one raises TimeoutError or
39
+ # MemoryError — and for custom functions that the shared context should not
40
+ # see.
18
41
  #
19
42
  # Every context starts from one snapshot of the bundle, so creating one does
20
43
  # not load and compile feelin again.
@@ -33,6 +56,7 @@ module FEELIN
33
56
  end
34
57
 
35
58
  def initialize(timeout: nil, max_memory: nil)
59
+ @timeout = timeout
36
60
  limits = { timeout: timeout, max_memory: max_memory }.compact
37
61
  @context = MiniRacer::Context.new(snapshot: self.class.snapshot, **limits)
38
62
  end
@@ -50,8 +74,8 @@ module FEELIN
50
74
  # counted in UTF-16 code units), `text` (that span) and `children`. Tokens are
51
75
  # nodes too. The context only supplies the names of the variables, which is
52
76
  # what lets a name with spaces in it be read as one. An expression that does
53
- # not parse raises, as it does in `evaluate`.
54
- def parse(expression, context = nil)
77
+ # not parse raises SyntaxError, as it does in `evaluate`.
78
+ def parse_expression(expression, context = nil)
55
79
  call("parseExpression", expression, context)
56
80
  end
57
81
 
@@ -70,8 +94,24 @@ module FEELIN
70
94
 
71
95
  private
72
96
 
97
+ SYNTAX_ERROR = /\AFeelSyntaxError: /
98
+
73
99
  def call(function, expression, context)
74
100
  JSON.parse(@context.call("feel.json.#{function}", expression, context.nil? ? nil : JSON.generate(context)))
101
+ rescue MiniRacer::ScriptTerminatedError
102
+ raise failure(TimeoutError, expression, "took longer than #{@timeout} ms", "")
103
+ rescue MiniRacer::V8OutOfMemoryError
104
+ raise failure(MemoryError, expression, "ran out of memory", "")
105
+ rescue MiniRacer::Error => e
106
+ reason = e.message.lines.first.to_s.strip
107
+
108
+ raise failure(SyntaxError, expression, reason.sub(SYNTAX_ERROR, ''), " is not a FEEL expression:") if reason.match?(SYNTAX_ERROR)
109
+
110
+ raise failure(Error, expression, reason.sub(/\AError: /, ''), ":")
111
+ end
112
+
113
+ def failure(error, expression, reason, link)
114
+ error.new("#{expression.inspect}#{link} #{reason}", expression, reason)
75
115
  end
76
116
  end
77
117
 
@@ -84,8 +124,8 @@ module FEELIN
84
124
  shared.unary_test(expression, value, context)
85
125
  end
86
126
 
87
- def parse(expression, context = nil)
88
- shared.parse(expression, context)
127
+ def parse_expression(expression, context = nil)
128
+ shared.parse_expression(expression, context)
89
129
  end
90
130
 
91
131
  def parse_unary_tests(expression, context = nil)
@@ -36,6 +36,26 @@ RSpec.describe FEELIN do
36
36
  end
37
37
  end
38
38
 
39
+ describe "custom function arguments" do
40
+ it "passes a date, a time and a duration as their ISO 8601 strings" do
41
+ seen = nil
42
+ FEELIN.add_function('seen', proc { |*args| seen = args; nil })
43
+
44
+ FEELIN.evaluate('seen(date("2020-01-02"), time("10:00:00+03:00"), date and time("2020-01-02T03:04:05Z"), duration("P1DT2H"))')
45
+
46
+ expect(seen).to eq [ '2020-01-02', '10:00:00+03:00', '2020-01-02T03:04:05Z', 'P1DT2H' ]
47
+ end
48
+
49
+ it "does so inside a list and a context, and leaves the rest as it is" do
50
+ seen = nil
51
+ FEELIN.add_function('seen', proc { |*args| seen = args; nil })
52
+
53
+ FEELIN.evaluate('seen([ @"2020-01-02" ], { on: @"2020-01-03", n: 1.5 }, "text", null, true)')
54
+
55
+ expect(seen).to eq [ [ '2020-01-02' ], { 'on' => '2020-01-03', 'n' => 1.5 }, 'text', nil, true ]
56
+ end
57
+ end
58
+
39
59
  describe "temporal results" do
40
60
  it "answers a date, a time and a duration in their ISO 8601 form" do
41
61
  expect(FEELIN.evaluate('date("2020-01-02")')).to eq '2020-01-02'
@@ -70,7 +90,7 @@ RSpec.describe FEELIN do
70
90
  context = FEELIN::Context.new(timeout: 50)
71
91
 
72
92
  expect { context.evaluate('count(for a in 1..3000, b in 1..3000 return a * b)') }
73
- .to raise_error(MiniRacer::ScriptTerminatedError)
93
+ .to raise_error(FEELIN::TimeoutError, /took longer than 50 ms/)
74
94
  ensure
75
95
  context&.dispose
76
96
  end
@@ -89,13 +109,35 @@ RSpec.describe FEELIN do
89
109
  end
90
110
  end
91
111
 
92
- describe "syntax errors" do
93
- it "raises on an expression that does not parse" do
94
- expect { FEELIN.evaluate('1 +') }.to raise_error(MiniRacer::RuntimeError, /Incomplete <ArithmeticExpression>/)
112
+ describe "errors" do
113
+ it "raises a syntax error that names the expression" do
114
+ expect { FEELIN.evaluate('1 +') }.to raise_error(FEELIN::SyntaxError, '"1 +" is not a FEEL expression: Incomplete <ArithmeticExpression>') do |error|
115
+ expect(error.expression).to eq '1 +'
116
+ expect(error.reason).to eq 'Incomplete <ArithmeticExpression>'
117
+ end
118
+ expect { FEELIN.unary_test('[1..', 1) }.to raise_error(FEELIN::SyntaxError)
119
+ end
120
+
121
+ it "raises nothing of MiniRacer's" do
122
+ expect { FEELIN.evaluate('1 +') }.to raise_error(FEELIN::Error) { |error| expect(error).not_to be_a(MiniRacer::Error) }
123
+ end
124
+
125
+ it "raises a memory error past the context's limit" do
126
+ context = FEELIN::Context.new(max_memory: 4_000_000)
127
+
128
+ expect { context.evaluate('for a in 1..300000 return { n: a, s: string(a) }') }.to raise_error(FEELIN::MemoryError, /ran out of memory/)
129
+ ensure
130
+ context&.dispose
131
+ end
132
+
133
+ it "lets an exception of a custom function through as it is" do
134
+ FEELIN.add_function('fails', proc { raise ArgumentError, 'no' })
135
+
136
+ expect { FEELIN.evaluate('fails()') }.to raise_error(ArgumentError, 'no')
95
137
  end
96
138
  end
97
139
 
98
- describe "#parse" do
140
+ describe "#parse_expression" do
99
141
  # the types of a tree, nested as the tree is, tokens left out
100
142
  def shape(node)
101
143
  children = node['children'].reject { |child| child['children'].empty? && child['type'] !~ /\A[A-Z]/ }
@@ -103,7 +145,7 @@ RSpec.describe FEELIN do
103
145
  end
104
146
 
105
147
  it "answers the syntax tree without evaluating the expression" do
106
- tree = FEELIN.parse('a.b + 1 > 2')
148
+ tree = FEELIN.parse_expression('a.b + 1 > 2')
107
149
 
108
150
  expect(shape(tree)).to eq(
109
151
  'Expression' => [ {
@@ -121,33 +163,33 @@ RSpec.describe FEELIN do
121
163
  end
122
164
 
123
165
  it "gives every node its span and its text" do
124
- comparison = FEELIN.parse('a.b + 1 > 2')['children'].first
166
+ comparison = FEELIN.parse_expression('a.b + 1 > 2')['children'].first
125
167
 
126
168
  expect(comparison.slice('type', 'from', 'to', 'text')).to eq('type' => 'Comparison', 'from' => 0, 'to' => 11, 'text' => 'a.b + 1 > 2')
127
169
  expect(comparison['children'].map { |child| child['text'] }).to eq [ 'a.b + 1', '>', '2' ]
128
170
  end
129
171
 
130
172
  it "reads a name with spaces as one name when the context has it" do
131
- tree = FEELIN.parse("Mike's daughter.name + 1", { "Mike's daughter.name" => 'Lisa' })
173
+ tree = FEELIN.parse_expression("Mike's daughter.name + 1", { "Mike's daughter.name" => 'Lisa' })
132
174
  name = tree['children'].first['children'].first
133
175
 
134
176
  expect(name.slice('type', 'text')).to eq('type' => 'VariableName', 'text' => "Mike's daughter.name")
135
177
  end
136
178
 
137
- it "raises on an expression that does not parse, as evaluate does" do
138
- expect { FEELIN.parse('1 +') }.to raise_error(MiniRacer::RuntimeError, /Incomplete <ArithmeticExpression>/)
139
- expect { FEELIN.parse('1 + ) 2') }.to raise_error(MiniRacer::RuntimeError, /Unrecognized token/)
179
+ it "raises on an expression that does not parse" do
180
+ expect { FEELIN.parse_expression('1 +') }.to raise_error(FEELIN::SyntaxError, /Incomplete <ArithmeticExpression>/)
181
+ expect { FEELIN.parse_expression('1 + ) 2') }.to raise_error(FEELIN::SyntaxError, /Unrecognized token/)
140
182
  end
141
183
 
142
184
  it "reports a syntax error as evaluate reports it" do
143
185
  [ '1 +', '{ a: 1', 'if x then', '1 + ) 2', '1 2', '[1, 2' ].each do |expression|
144
186
  expected = begin
145
187
  FEELIN.evaluate(expression)
146
- rescue MiniRacer::RuntimeError => e
188
+ rescue FEELIN::SyntaxError => e
147
189
  e.message
148
190
  end
149
191
 
150
- expect { FEELIN.parse(expression) }.to raise_error(MiniRacer::RuntimeError, expected), expression
192
+ expect { FEELIN.parse_expression(expression) }.to raise_error(FEELIN::SyntaxError, expected), expression
151
193
  end
152
194
  end
153
195
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feelin
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.2
4
+ version: 7.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Arkhipov