flor 0.0.1 → 0.9.0

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