flor 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/LICENSE.txt +1 -1
  3. data/Makefile +66 -0
  4. data/README.md +57 -0
  5. data/fail.txt +7 -0
  6. data/flor.gemspec +12 -9
  7. data/intercepted.txt +123 -0
  8. data/lib/flor/colours.rb +140 -0
  9. data/lib/flor/conf.rb +88 -0
  10. data/lib/flor/core/executor.rb +473 -0
  11. data/lib/flor/core/node.rb +397 -0
  12. data/lib/flor/core/procedure.rb +600 -0
  13. data/lib/flor/core/texecutor.rb +209 -0
  14. data/lib/flor/core.rb +93 -0
  15. data/lib/flor/dollar.rb +248 -0
  16. data/lib/flor/errors.rb +36 -0
  17. data/lib/flor/flor.rb +556 -0
  18. data/lib/flor/log.rb +336 -0
  19. data/lib/flor/migrations/0001_tables.rb +122 -0
  20. data/lib/flor/parser.rb +414 -0
  21. data/lib/flor/pcore/_arr.rb +49 -0
  22. data/lib/flor/pcore/_atom.rb +43 -0
  23. data/lib/flor/pcore/_att.rb +160 -0
  24. data/lib/flor/pcore/_dump.rb +60 -0
  25. data/lib/flor/pcore/_err.rb +30 -0
  26. data/lib/flor/pcore/_happly.rb +73 -0
  27. data/lib/flor/pcore/_obj.rb +65 -0
  28. data/lib/flor/pcore/_skip.rb +63 -0
  29. data/lib/flor/pcore/apply.rb +60 -0
  30. data/lib/flor/pcore/arith.rb +46 -0
  31. data/lib/flor/pcore/break.rb +71 -0
  32. data/lib/flor/pcore/cmp.rb +72 -0
  33. data/lib/flor/pcore/cond.rb +57 -0
  34. data/lib/flor/pcore/cursor.rb +223 -0
  35. data/lib/flor/pcore/define.rb +96 -0
  36. data/lib/flor/pcore/fail.rb +45 -0
  37. data/lib/flor/pcore/ife.rb +56 -0
  38. data/lib/flor/pcore/loop.rb +53 -0
  39. data/lib/flor/pcore/map.rb +75 -0
  40. data/lib/flor/pcore/match.rb +70 -0
  41. data/lib/flor/pcore/move.rb +65 -0
  42. data/lib/flor/pcore/noeval.rb +46 -0
  43. data/lib/flor/pcore/noret.rb +47 -0
  44. data/lib/flor/pcore/push.rb +69 -0
  45. data/lib/flor/pcore/sequence.rb +39 -0
  46. data/lib/flor/pcore/set.rb +76 -0
  47. data/lib/flor/pcore/stall.rb +35 -0
  48. data/lib/flor/pcore/until.rb +122 -0
  49. data/lib/flor/pcore/val.rb +40 -0
  50. data/lib/flor/punit/cancel.rb +69 -0
  51. data/lib/flor/punit/cmap.rb +76 -0
  52. data/lib/flor/punit/concurrence.rb +149 -0
  53. data/lib/flor/punit/every.rb +46 -0
  54. data/lib/flor/punit/on.rb +81 -0
  55. data/lib/flor/punit/schedule.rb +68 -0
  56. data/lib/flor/punit/signal.rb +47 -0
  57. data/lib/flor/punit/sleep.rb +53 -0
  58. data/lib/flor/punit/task.rb +109 -0
  59. data/lib/flor/punit/trace.rb +51 -0
  60. data/lib/flor/punit/trap.rb +100 -0
  61. data/lib/flor/to_string.rb +81 -0
  62. data/lib/flor/tools/env.rb +103 -0
  63. data/lib/flor/tools/repl.rb +231 -0
  64. data/lib/flor/unit/executor.rb +260 -0
  65. data/lib/flor/unit/hooker.rb +186 -0
  66. data/lib/flor/unit/journal.rb +52 -0
  67. data/lib/flor/unit/loader.rb +181 -0
  68. data/lib/flor/unit/logger.rb +181 -0
  69. data/lib/flor/unit/models/execution.rb +105 -0
  70. data/lib/flor/unit/models/pointer.rb +31 -0
  71. data/lib/flor/unit/models/timer.rb +52 -0
  72. data/lib/flor/unit/models/trace.rb +31 -0
  73. data/lib/flor/unit/models/trap.rb +130 -0
  74. data/lib/flor/unit/models.rb +106 -0
  75. data/lib/flor/unit/scheduler.rb +419 -0
  76. data/lib/flor/unit/storage.rb +633 -0
  77. data/lib/flor/unit/tasker.rb +191 -0
  78. data/lib/flor/unit/waiter.rb +146 -0
  79. data/lib/flor/unit/wlist.rb +77 -0
  80. data/lib/flor/unit.rb +50 -0
  81. data/lib/flor.rb +40 -3
  82. metadata +152 -22
  83. checksums.yaml +0 -7
  84. data/Rakefile +0 -52
@@ -0,0 +1,209 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Flor
27
+
28
+ class TransientExecutor < Executor
29
+
30
+ class TransientTasker
31
+
32
+ def has_tasker?(exid, tname); false; end
33
+ end
34
+
35
+ class TransientUnit
36
+
37
+ attr_accessor :conf, :opts
38
+ attr_reader :journal, :tasker, :loader
39
+ attr_accessor :archive
40
+
41
+ def initialize(conf)
42
+
43
+ @conf = conf
44
+ @opts = {}
45
+ @journal = []
46
+ @tasker = TransientTasker.new
47
+ @archive = nil
48
+ end
49
+
50
+ def notify(executor, o)
51
+
52
+ return [] if o['consumed']
53
+
54
+ Flor.log_message(executor, o) \
55
+ if @conf['log_msg']
56
+
57
+ @journal << o
58
+
59
+ []
60
+ end
61
+
62
+ def remove_node(exid, n)
63
+
64
+ (@archive[exid] ||= {})[n['nid']] = Flor.dup(n) if @archive
65
+ end
66
+ end
67
+
68
+ def initialize(conf={})
69
+
70
+ conf.merge!(Flor::Conf.read_env) unless conf['conf'] == true
71
+ # don't read FLOR_DEBUG if this executor is only meant to read the conf
72
+
73
+ super(
74
+ TransientUnit.new(conf),
75
+ [], # no traps
76
+ {
77
+ 'exid' => Flor.generate_exid('eval', 'u0'),
78
+ 'nodes' => {}, 'errors' => [], 'counters' => {},
79
+ #'ashes' => {},
80
+ 'start' => Flor.tstamp
81
+ })
82
+ end
83
+
84
+ def journal; @unit.journal; end
85
+ def archive; @unit.archive[exid]; end
86
+
87
+ def launch(tree, opts={})
88
+
89
+ @unit.opts = opts
90
+ @unit.archive = {} if opts[:archive]
91
+
92
+ Flor.print_src(tree, opts) if conf['log_src']
93
+
94
+ messages = [ Flor.make_launch_msg(@execution['exid'], tree, opts) ]
95
+
96
+ Flor.print_tree(messages.first['tree']) if conf['log_tree']
97
+
98
+ walk(messages, opts)
99
+ end
100
+
101
+ def walk(messages, opts={})
102
+
103
+ loop do
104
+
105
+ message = messages.shift
106
+ return nil unless message
107
+
108
+ if message['point'] == 'terminated' && messages.any?
109
+ #
110
+ # try to handle 'terminated' last
111
+ #
112
+ messages << message
113
+ message = messages.shift
114
+ end
115
+
116
+ msgs = process(message)
117
+
118
+ messages.concat(msgs)
119
+
120
+ return messages if message_match?(message, opts[:until_after])
121
+ return messages if message_match?(messages, opts[:until])
122
+
123
+ return message \
124
+ if message['point'] == 'terminated'
125
+ return message \
126
+ if message['point'] == 'failed' && message['on_error'] == nil
127
+ end
128
+ end
129
+
130
+ def step(message)
131
+
132
+ process(message)
133
+ end
134
+
135
+ # Used in specs when testing multiple message arrival order on
136
+ # a "suite" of transient executors
137
+ #
138
+ def clone
139
+
140
+ c = TransientExecutor.allocate
141
+
142
+ c.instance_variable_set(:@unit, @unit)
143
+ c.instance_variable_set(:@traps, []) # not useful for a TransientEx clone
144
+ c.instance_variable_set(:@execution, Flor.dup(@execution))
145
+
146
+ c
147
+ end
148
+
149
+ protected
150
+
151
+ # TODO eventually merge with Waiter.parse_serie
152
+ #
153
+ def message_match?(msg_s, ountil)
154
+
155
+ return false unless ountil
156
+
157
+ ms = msg_s; ms = [ ms ] if ms.is_a?(Hash)
158
+
159
+ nid, point = ountil.split(' ')
160
+
161
+ ms.find { |m| m['nid'] == nid && m['point'] == point }
162
+ end
163
+ end
164
+
165
+ class ConfExecutor < TransientExecutor
166
+
167
+ def self.interpret(path)
168
+
169
+ s = path
170
+
171
+ s = File.read(s).strip unless s.match(/[\r\n]/)
172
+ s = "{\n#{s}\n}"
173
+
174
+ vs = Hash.new { |h, k| k }
175
+ class << vs
176
+ def has_key?(k); ! Flor::Procedure[k]; end
177
+ end
178
+
179
+ vs['root'] = determine_root(path)
180
+
181
+ vs['ruby_version'] = RUBY_VERSION
182
+ vs['ruby_platform'] = RUBY_PLATFORM
183
+
184
+ c = (ENV['FLOR_DEBUG'] || '').match(/conf/) ? false : true
185
+ r = (self.new('conf' => c)).launch(s, vars: vs)
186
+
187
+ unless r['point'] == 'terminated'
188
+ ae = ArgumentError.new("error while reading conf: #{r['error']['msg']}")
189
+ ae.set_backtrace(r['error']['trc'])
190
+ fail ae
191
+ end
192
+
193
+ h = Flor.dup(r['payload']['ret'])
194
+
195
+ h.merge!('_path' => path) unless path.match(/[\r\n]/)
196
+
197
+ h
198
+ end
199
+
200
+ def self.determine_root(path)
201
+
202
+ dir = File.absolute_path(File.dirname(path))
203
+ ps = dir.split(File::SEPARATOR)
204
+
205
+ ps.last == 'etc' ? File.absolute_path(File.join(dir, '..')) : dir
206
+ end
207
+ end
208
+ end
209
+
data/lib/flor/core.rb ADDED
@@ -0,0 +1,93 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ module Flor
26
+
27
+ def self.generate_exid(domain, unit)
28
+
29
+ @exid_counter ||= 0
30
+ @exid_mutex ||= Mutex.new
31
+
32
+ t = Time.now.utc
33
+
34
+ sus =
35
+ @exid_mutex.synchronize do
36
+
37
+ sus = t.sec * 100000000 + t.usec * 100 + @exid_counter
38
+
39
+ @exid_counter = @exid_counter + 1
40
+ @exid_counter = 0 if @exid_counter > 99
41
+
42
+ Munemo.to_s(sus)
43
+ end
44
+
45
+ t = t.strftime('%Y%m%d.%H%M')
46
+
47
+ "#{domain}-#{unit}-#{t}.#{sus}"
48
+ end
49
+
50
+ def self.make_launch_msg(exid, tree, opts)
51
+
52
+ t =
53
+ tree.is_a?(String) ?
54
+ Flor::Lang.parse(tree, opts[:fname], opts) :
55
+ tree
56
+
57
+ unless t
58
+ #h = opts.merge(prune: false, rewrite: false)
59
+ #p Flor::Lang.parse(tree, h[:fname], h)
60
+ # TODO re-parse and indicate what went wrong...
61
+ fail ArgumentError.new('flor parse failure')
62
+ end
63
+
64
+ pl = opts[:payload] || opts[:fields] || {}
65
+ vs = opts[:variables] || opts[:vars] || {}
66
+
67
+ msg =
68
+ { 'point' => 'execute',
69
+ 'exid' => exid,
70
+ 'nid' => '0',
71
+ 'tree' => t,
72
+ 'payload' => pl,
73
+ 'vars' => vs }
74
+
75
+ msg['vdomain'] = opts[:vdomain] \
76
+ if opts.has_key?(:vdomain)
77
+
78
+ msg
79
+ end
80
+
81
+ def self.load_procedures(dir)
82
+
83
+ dirpath =
84
+ if dir.match(/\A[.\/]/)
85
+ File.join(dir, '*.rb')
86
+ else
87
+ File.join(File.dirname(__FILE__), dir, '*.rb')
88
+ end
89
+
90
+ Dir[dirpath].each { |path| require(path) }
91
+ end
92
+ end
93
+
@@ -0,0 +1,248 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Flor
27
+
28
+ class Dollar
29
+
30
+ module Parser include Raabro
31
+
32
+ #static fabr_tree *_str(fabr_input *i)
33
+ #{
34
+ # return fabr_rex("s", i,
35
+ # "("
36
+ # "\\\\\\)" "|"
37
+ # "[^\\$\\)]" "|"
38
+ # "\\$[^\\(]"
39
+ # ")+");
40
+ #}
41
+ def istr(i)
42
+ rex(:str, i, %r{
43
+ ( \\\) | [^\$)] | \$[^(] )+
44
+ }x)
45
+ end
46
+
47
+ #static fabr_tree *_outerstr(fabr_input *i)
48
+ #{
49
+ # return fabr_rex("s", i,
50
+ # "("
51
+ # "[^\\$]" "|" // doesn't mind ")"
52
+ # "\\$[^\\(]"
53
+ # ")+");
54
+ #}
55
+ def ostr(i)
56
+ rex(:str, i, %r{
57
+ ( [^\$] | \$(?!\() )+
58
+ }x)
59
+ end
60
+ #
61
+ # ( [^\$] | \$(?!\() )+
62
+ # one or more of (not a dollar or a dollar followed by sthing else
63
+ # than a parenthesis opening)
64
+
65
+ def pe(i); str(nil, i, ')'); end
66
+ def dois(i); alt(nil, i, :dollar, :istr); end
67
+ def span(i); rep(:span, i, :dois, 0); end
68
+ def dps(i); str(nil, i, '$('); end
69
+ def dollar(i); seq(:dollar, i, :dps, :span, :pe); end
70
+ def doos(i); alt(nil, i, :dollar, :ostr); end
71
+ def outer(i); rep(:span, i, :doos, 0); end
72
+
73
+ def rewrite_str(t)
74
+ t.string
75
+ end
76
+ def rewrite_dollar(t)
77
+ cn = rewrite(t.children[1])
78
+ c = cn.first
79
+ if cn.size == 1 && c.is_a?(String)
80
+ [ :dol, c ]
81
+ else
82
+ [ :dol, cn ]
83
+ end
84
+ end
85
+ def rewrite_span(t)
86
+ t.children.collect { |c| rewrite(c) }
87
+ end
88
+ end
89
+
90
+ module PipeParser include Raabro
91
+
92
+ def elt(i); rex(:elt, i, /[^|]+/); end
93
+ def pipe(i); rex(:pipe, i, /\|\|?/); end
94
+ def elts(i); jseq(:elts, i, :elt, :pipe); end
95
+
96
+ def rewrite_elt(t); t.string; end
97
+ def rewrite_pipe(t); t.string == '|' ? :pipe : :dpipe; end
98
+ def rewrite_elts(t); t.children.collect { |e| rewrite(e) }; end
99
+ end
100
+
101
+ #def lookup(s)
102
+ # # ...
103
+ #end
104
+ #
105
+ # the signature
106
+
107
+ # Called when joining multiple results in a string. Easily overwritable.
108
+ #
109
+ def stringify(v)
110
+
111
+ case v
112
+ when Array, Hash then JSON.dump(v)
113
+ else v.to_s
114
+ end
115
+ end
116
+
117
+ def quote(s, force)
118
+
119
+ return s if force == false && s[0, 1] == '"' && s[-1, 1] == '"'
120
+
121
+ JSON.dump([ s ])[1..-2]
122
+ end
123
+
124
+ def match(rex, s)
125
+
126
+ s.match(rex) ? s : false
127
+ end
128
+
129
+ def substitute(pat, rpl, gix, s)
130
+
131
+ ops =
132
+ (gix.index('i') ? Regexp::IGNORECASE : 0) |
133
+ (gix.index('x') ? Regexp::EXTENDED : 0)
134
+
135
+ rex = Regexp.new(pat, ops)
136
+
137
+ gix.index('g') ? s.gsub(rex, rpl) : s.sub(rex, rpl)
138
+ end
139
+
140
+ def lfilter(s, cmp, len)
141
+
142
+ l = s.length
143
+
144
+ case cmp
145
+ when '>' then l > len
146
+ when '>=' then l >= len
147
+ when '<' then l < len
148
+ when '<=' then l <= len
149
+ when '=', '==' then l == len
150
+ when '!=', '<>' then l != len
151
+ else false
152
+ end
153
+ end
154
+
155
+ def to_json(o)
156
+
157
+ case o
158
+ when Array, Hash then JSON.dump(o)
159
+ else JSON.dump([ o ])[1..-2]
160
+ end
161
+ end
162
+
163
+ def call(fun, o)
164
+
165
+ # NB: yes, $1..$9 are thread-safe (and local, not global)
166
+
167
+ case fun
168
+
169
+ when 'u' then o.to_s.upcase
170
+ when 'd' then o.to_s.downcase
171
+ when 'r' then o.reverse
172
+ when 'c' then o.to_s.capitalize.gsub(/\s[a-z]/) { |c| c.upcase }
173
+ when 'q' then quote(o, false)
174
+ when 'Q' then quote(o, true)
175
+
176
+ when 'json' then to_json(o)
177
+
178
+ when /^j(.+)/
179
+ o.respond_to?(:join) ? o.join($1) : o
180
+
181
+ when /\Am\/(.+)\/\z/
182
+ match($1, o.to_s)
183
+ when /\As\/(.*[^\\]\/)(.+)\/([gix]*)\z/
184
+ substitute($1.chop, $2, $3, o.to_s)
185
+
186
+ when /\A-?\d+\z/ then o.to_s[fun.to_i]
187
+ when /\A(-?\d+), *(-?\d+)\z/ then o.to_s[$1.to_i, $2.to_i]
188
+ when /\A(-?\d+)\.\.(-?\d+)\z/ then o.to_s[$1.to_i..$2.to_i]
189
+
190
+ when /\A\s*l\s*([><=!]=?|<>)\s*(\d+)\z/
191
+ lfilter(o.to_s, $1, $2.to_i) ? o.to_s : nil
192
+
193
+ else o
194
+ end
195
+ end
196
+
197
+ def unescape(s)
198
+
199
+ s.gsub(/\\[\$)]/) { |m| m[1, 1] }
200
+ end
201
+
202
+ def do_eval(t)
203
+
204
+ #return t if t.is_a?(String)
205
+ return unescape(t) if t.is_a?(String)
206
+
207
+ return t.collect { |c| stringify(do_eval(c)) }.join if t[0] != :dol
208
+
209
+ k = do_eval(t[1])
210
+ ks = PipeParser.parse(k)
211
+
212
+ result = nil
213
+ mode = :lookup # vs :call
214
+
215
+ ks.each do |k|
216
+
217
+ if k == :pipe then mode = :call; next; end
218
+ if k == :dpipe && result then break; end
219
+ if k == :dpipe then mode = :lookup; next; end
220
+
221
+ result =
222
+ if mode == :lookup
223
+ #k[0, 1] == "'" ? k[1..-1] : do_lookup(k)
224
+ k[0, 1] == "'" ? k[1..-1] : lookup(k)
225
+ else # :call
226
+ call(k, result)
227
+ end
228
+ end
229
+
230
+ result
231
+ end
232
+
233
+ def expand(s)
234
+
235
+ return s unless s.index('$')
236
+
237
+ #Raabro.pp(Parser.parse(s, debug: 2))
238
+ t = Parser.parse(s)
239
+
240
+ return s unless t
241
+
242
+ t = t.first if t.size == 1
243
+
244
+ do_eval(t)
245
+ end
246
+ end
247
+ end
248
+
@@ -0,0 +1,36 @@
1
+ #--
2
+ # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ class Flor::FlorError < StandardError
27
+
28
+ attr_reader :node
29
+
30
+ def initialize(message, node=nil)
31
+
32
+ super(message)
33
+ @node = node
34
+ end
35
+ end
36
+