gloo 6.7.1 → 7.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +1 -0
  3. data/docs/application.md +3 -1
  4. data/docs/iterators.md +39 -3
  5. data/docs/language_syntax.md +53 -5
  6. data/docs/objects.md +58 -1
  7. data/docs/operators.md +16 -0
  8. data/docs/plugins.md +9 -1
  9. data/docs/verbs.md +4 -2
  10. data/lib/VERSION +1 -1
  11. data/lib/VERSION_NOTES +24 -0
  12. data/lib/gloo/app/engine.rb +65 -9
  13. data/lib/gloo/app/log.rb +47 -6
  14. data/lib/gloo/convert/converter.rb +63 -2
  15. data/lib/gloo/convert/string_to_date.rb +14 -0
  16. data/lib/gloo/convert/string_to_datetime.rb +16 -0
  17. data/lib/gloo/convert/string_to_decimal.rb +13 -0
  18. data/lib/gloo/convert/string_to_integer.rb +9 -2
  19. data/lib/gloo/convert/string_to_time.rb +28 -0
  20. data/lib/gloo/core/dictionary.rb +30 -21
  21. data/lib/gloo/core/error.rb +11 -2
  22. data/lib/gloo/core/event_manager.rb +7 -4
  23. data/lib/gloo/core/factory.rb +16 -11
  24. data/lib/gloo/core/here.rb +6 -1
  25. data/lib/gloo/core/invoker.rb +2 -3
  26. data/lib/gloo/core/not_found.rb +42 -0
  27. data/lib/gloo/core/obj.rb +28 -2
  28. data/lib/gloo/core/op.rb +2 -0
  29. data/lib/gloo/core/parser.rb +1 -1
  30. data/lib/gloo/core/pn.rb +33 -12
  31. data/lib/gloo/core/tokens.rb +38 -0
  32. data/lib/gloo/core/verb.rb +54 -0
  33. data/lib/gloo/exec/dispatch.rb +5 -6
  34. data/lib/gloo/exec/exec_env.rb +13 -0
  35. data/lib/gloo/exec/runner.rb +10 -2
  36. data/lib/gloo/exec/script.rb +13 -1
  37. data/lib/gloo/expr/expression.rb +39 -1
  38. data/lib/gloo/expr/op_plus.rb +3 -0
  39. data/lib/gloo/objs/basic/boolean.rb +16 -0
  40. data/lib/gloo/objs/basic/container.rb +154 -6
  41. data/lib/gloo/objs/basic/decimal.rb +1 -0
  42. data/lib/gloo/objs/basic/integer.rb +1 -0
  43. data/lib/gloo/objs/basic/string.rb +0 -1
  44. data/lib/gloo/objs/basic/string_msgs.rb +68 -32
  45. data/lib/gloo/objs/ctrl/each.rb +22 -3
  46. data/lib/gloo/objs/ctrl/each_dir.rb +20 -7
  47. data/lib/gloo/objs/ctrl/each_file.rb +78 -9
  48. data/lib/gloo/objs/ctrl/function.rb +6 -5
  49. data/lib/gloo/objs/dt/date.rb +28 -11
  50. data/lib/gloo/objs/dt/datetime.rb +72 -21
  51. data/lib/gloo/objs/dt/dt_tools.rb +46 -2
  52. data/lib/gloo/objs/dt/time.rb +28 -11
  53. data/lib/gloo/objs/str_utils/cipher.rb +17 -4
  54. data/lib/gloo/objs/str_utils/password.rb +6 -2
  55. data/lib/gloo/objs/system/erb.rb +9 -3
  56. data/lib/gloo/objs/system/file_handle.rb +57 -21
  57. data/lib/gloo/objs/system/system.rb +6 -2
  58. data/lib/gloo/objs/web/http_get.rb +12 -2
  59. data/lib/gloo/objs/web/http_post.rb +6 -2
  60. data/lib/gloo/objs/web/json.rb +38 -18
  61. data/lib/gloo/objs/web/uri.rb +36 -16
  62. data/lib/gloo/persist/file_loader.rb +72 -11
  63. data/lib/gloo/persist/file_saver.rb +2 -1
  64. data/lib/gloo/persist/indent_stack.rb +49 -33
  65. data/lib/gloo/persist/line_splitter.rb +40 -3
  66. data/lib/gloo/persist/persist_man.rb +2 -4
  67. data/lib/gloo/plugin/callback.rb +2 -2
  68. data/lib/gloo/plugin/ext_manager.rb +6 -9
  69. data/lib/gloo/plugin/lib_manager.rb +2 -2
  70. data/lib/gloo/verbs/break.rb +1 -0
  71. data/lib/gloo/verbs/check.rb +1 -1
  72. data/lib/gloo/verbs/cls.rb +1 -0
  73. data/lib/gloo/verbs/create.rb +32 -4
  74. data/lib/gloo/verbs/execute.rb +1 -1
  75. data/lib/gloo/verbs/exists.rb +27 -15
  76. data/lib/gloo/verbs/files.rb +2 -1
  77. data/lib/gloo/verbs/help.rb +1 -0
  78. data/lib/gloo/verbs/if.rb +1 -1
  79. data/lib/gloo/verbs/invoke.rb +1 -1
  80. data/lib/gloo/verbs/list.rb +4 -3
  81. data/lib/gloo/verbs/load.rb +3 -3
  82. data/lib/gloo/verbs/move.rb +10 -8
  83. data/lib/gloo/verbs/put.rb +20 -8
  84. data/lib/gloo/verbs/quit.rb +1 -0
  85. data/lib/gloo/verbs/redirect.rb +8 -2
  86. data/lib/gloo/verbs/reload.rb +9 -1
  87. data/lib/gloo/verbs/run.rb +2 -2
  88. data/lib/gloo/verbs/save.rb +4 -2
  89. data/lib/gloo/verbs/tell.rb +1 -1
  90. data/lib/gloo/verbs/unless.rb +1 -1
  91. data/lib/gloo/verbs/unload.rb +8 -0
  92. data/test.gloo/ctrl/each.test.gloo +88 -0
  93. data/test.gloo/lang/declaration.test.gloo +27 -0
  94. data/test.gloo/lang/exceptions.test.gloo +60 -0
  95. data/test.gloo/lang/failures.test.gloo +139 -0
  96. data/test.gloo/lang/it.test.gloo +133 -0
  97. data/test.gloo/lang/naming.test.gloo +11 -0
  98. data/test.gloo/lang/ops.test.gloo +30 -0
  99. data/test.gloo/objs/can.test.gloo +101 -0
  100. data/test.gloo/objs/string.test.gloo +24 -0
  101. data/test.gloo/string/str.test.gloo +28 -0
  102. data/test.gloo/verbs/create.test.gloo +36 -0
  103. data/test.gloo/verbs/exists.test.gloo +33 -1
  104. data/test.gloo/verbs/unload.test.gloo +34 -0
  105. metadata +6 -6
@@ -8,6 +8,16 @@ module Gloo
8
8
  module Convert
9
9
  class Converter
10
10
 
11
+ # How each type reads in a warning: "'x' is not an integer".
12
+ TYPE_NAMES = {
13
+ 'Integer' => 'an integer',
14
+ 'Decimal' => 'a decimal number',
15
+ 'Date' => 'a date',
16
+ 'Time' => 'a time',
17
+ 'DateTime' => 'a date and time'
18
+ }.freeze
19
+ NO_CONVERSION_ERR = 'There is no conversion to '.freeze
20
+
11
21
  #
12
22
  # Initializer.
13
23
  #
@@ -23,12 +33,30 @@ module Gloo
23
33
  # Convert the given value to the specified type,
24
34
  # or if no conversion is available, revert to default.
25
35
  #
36
+ # If the value isn't really of that type (eg. 'x' for an
37
+ # integer), the conversion is still the best guess, and a warning
38
+ # says what was guessed. A kind of value there's no converter for
39
+ # is also warned about, using the default. Converting to a type
40
+ # that isn't known is an error.
41
+ #
26
42
  def convert( value, to_type, default = nil )
27
43
  begin
28
44
  name = "Gloo::Convert::#{value.class}To#{to_type}"
29
- clazz = name.split( '::' ).inject( Object ) { |o, c| o.const_get c }
45
+ clazz = find_converter( name )
46
+ unless TYPE_NAMES.key?( to_type )
47
+ @engine.err "#{NO_CONVERSION_ERR}'#{to_type}'."
48
+ return default
49
+ end
50
+ unless clazz
51
+ @engine.warn "'#{value}' is not #{TYPE_NAMES[ to_type ]}; " \
52
+ "using #{default.nil? ? 'no value' : default}."
53
+ return default
54
+ end
55
+
30
56
  o = clazz.new
31
- return o.convert( value )
57
+ result = o.convert( value )
58
+ warn_bad_value( o, value, result, to_type )
59
+ return result
32
60
  rescue => e
33
61
  @engine.log_exception e
34
62
  end
@@ -36,6 +64,39 @@ module Gloo
36
64
  return default
37
65
  end
38
66
 
67
+ private
68
+
69
+ #
70
+ # Find the converter class by name, or nil if there isn't one for
71
+ # that kind of value.
72
+ #
73
+ def find_converter( name )
74
+ return name.split( '::' ).inject( Object ) { |o, c| o.const_get( c, false ) }
75
+ rescue NameError
76
+ return nil
77
+ end
78
+
79
+ #
80
+ # Warn if the converter says the value isn't valid for its type.
81
+ #
82
+ def warn_bad_value( converter, value, result, to_type )
83
+ return unless converter.respond_to?( :valid? )
84
+ return if converter.valid?( value, result )
85
+
86
+ @engine.warn "'#{value}' is not #{TYPE_NAMES[ to_type ]}; " \
87
+ "using #{describe( converter, result )}."
88
+ end
89
+
90
+ #
91
+ # Describe the guess for a warning.
92
+ #
93
+ def describe( converter, result )
94
+ return 'no value' if result.nil?
95
+ return converter.describe( result ) if converter.respond_to?( :describe )
96
+
97
+ return result.to_s
98
+ end
99
+
39
100
  end
40
101
  end
41
102
  end
@@ -21,6 +21,20 @@ module Gloo
21
21
  return Chronic.parse( value )
22
22
  end
23
23
 
24
+ #
25
+ # Is the value really a date? A blank value is no value.
26
+ #
27
+ def valid?( value, result )
28
+ return value.blank? || !result.nil?
29
+ end
30
+
31
+ #
32
+ # Describe the guess for a warning.
33
+ #
34
+ def describe( result )
35
+ return result.strftime( Gloo::Objs::Date::DEFAULT_FORMAT )
36
+ end
37
+
24
38
  end
25
39
  end
26
40
  end
@@ -16,6 +16,22 @@ module Gloo
16
16
  return Chronic.parse( value )
17
17
  end
18
18
 
19
+ #
20
+ # Is the value really a date and time? A blank value is no value.
21
+ #
22
+ def valid?( value, result )
23
+ return true if value.blank?
24
+
25
+ return !result.nil? && !StringToTime.clock_out_of_range?( value )
26
+ end
27
+
28
+ #
29
+ # Describe the guess for a warning.
30
+ #
31
+ def describe( result )
32
+ return result.strftime( Gloo::Objs::DateTime::DEFAULT_FORMAT )
33
+ end
34
+
19
35
  end
20
36
  end
21
37
  end
@@ -15,6 +15,19 @@ module Gloo
15
15
  return value.to_f
16
16
  end
17
17
 
18
+ #
19
+ # Is the value really a decimal number? A blank value is no
20
+ # value (0.0).
21
+ #
22
+ def valid?( value, _result )
23
+ return true if value.blank?
24
+
25
+ Float( value.strip )
26
+ return true
27
+ rescue ArgumentError
28
+ return false
29
+ end
30
+
18
31
  end
19
32
  end
20
33
  end
@@ -12,11 +12,18 @@ module Gloo
12
12
  # Convert the given string value to an integer.
13
13
  #
14
14
  def convert( value )
15
- return nil if value.blank?
16
-
15
+ return 0 if value.blank?
16
+
17
17
  return value.to_i
18
18
  end
19
19
 
20
+ #
21
+ # Is the value really an integer? A blank value is no value (0).
22
+ #
23
+ def valid?( value, _result )
24
+ return value.blank? || value.match?( /\A\s*[-+]?\d+\s*\z/ )
25
+ end
26
+
20
27
  end
21
28
  end
22
29
  end
@@ -16,6 +16,34 @@ module Gloo
16
16
  return Chronic.parse( value )
17
17
  end
18
18
 
19
+ #
20
+ # Is the value really a time? A blank value is no value. A clock
21
+ # time out of range (eg. 25:99) is parsed, but rolls over.
22
+ #
23
+ def valid?( value, result )
24
+ return true if value.blank?
25
+
26
+ return !result.nil? && !StringToTime.clock_out_of_range?( value )
27
+ end
28
+
29
+ #
30
+ # Describe the guess for a warning.
31
+ #
32
+ def describe( result )
33
+ return result.strftime( Gloo::Objs::Time::DEFAULT_FORMAT )
34
+ end
35
+
36
+ #
37
+ # Does the value have a clock time (h:mm or h:mm:ss) with an hour,
38
+ # minute or second out of range?
39
+ #
40
+ def self.clock_out_of_range?( value )
41
+ m = value.match( /(\d{1,2}):(\d{2})(?::(\d{2}))?/ )
42
+ return false unless m
43
+
44
+ return m[ 1 ].to_i > 24 || m[ 2 ].to_i > 59 || m[ 3 ].to_i > 59
45
+ end
46
+
19
47
  end
20
48
  end
21
49
  end
@@ -27,10 +27,14 @@ module Gloo
27
27
  #
28
28
  # Get the singleton Dictionary and Initialize
29
29
  # if this is the first time.
30
+ # The engine is optional -- verbs/objects self-register at class
31
+ # load time, before any engine (and its log) exists. When an
32
+ # engine is given, it's used to report a duplicate-registration
33
+ # error; without one, duplicates are just silently skipped.
30
34
  #
31
- def self.get
35
+ def self.get( engine=nil )
32
36
  o = Gloo::Core::Dictionary.instance
33
- o.init if o.verbs.count == 0
37
+ o.init( engine ) if o.verbs.count == 0
34
38
  return o
35
39
  end
36
40
 
@@ -51,9 +55,9 @@ module Gloo
51
55
  #
52
56
  # Initialize verbs and objects in the dictionary.
53
57
  #
54
- def init
55
- init_verbs
56
- init_objs
58
+ def init( engine=nil )
59
+ init_verbs( engine )
60
+ init_objs( engine )
57
61
  end
58
62
 
59
63
  #
@@ -145,15 +149,15 @@ module Gloo
145
149
  #
146
150
  # Register a verb after start up.
147
151
  #
148
- def register_verb_post_start( verb_class )
149
- add_verb verb_class
152
+ def register_verb_post_start( verb_class, engine=nil )
153
+ add_verb verb_class, engine
150
154
  end
151
155
 
152
156
  #
153
157
  # Register an object type after start up.
154
158
  #
155
- def register_obj_post_start( obj_class )
156
- add_object obj_class
159
+ def register_obj_post_start( obj_class, engine=nil )
160
+ add_object obj_class, engine
157
161
  end
158
162
 
159
163
  #
@@ -200,28 +204,32 @@ module Gloo
200
204
  #
201
205
  # Init the list of objects.
202
206
  #
203
- def init_objs
207
+ def init_objs( engine=nil )
204
208
  @obj_references.each do |o|
205
- add_object o
209
+ add_object o, engine
206
210
  end
207
211
  end
208
212
 
209
213
  #
210
214
  # Init the list of verbs.
211
215
  #
212
- def init_verbs
216
+ def init_verbs( engine=nil )
213
217
  @verb_references.each do |v|
214
- add_verb v
218
+ add_verb v, engine
215
219
  end
216
220
  end
217
221
 
218
- #
219
- # Add an object to the dictionary
220
- #
221
- def add_object( obj )
222
+ #
223
+ # Add an object to the dictionary.
224
+ # The engine is optional and is only used to report a
225
+ # duplicate-registration error -- when it's not given (e.g.
226
+ # self-registration at class load time), duplicates are just
227
+ # silently skipped.
228
+ #
229
+ def add_object( obj, engine=nil )
222
230
  # Make sure it hasn't already been registered
223
231
  if ( @objs.key?(obj.typename) || @objs.key?(obj.short_typename) )
224
- @engine.err "duplicate object type '#{obj.typename}' or '#{obj.short_typename}'"
232
+ engine&.err "duplicate object type '#{obj.typename}' or '#{obj.short_typename}'"
225
233
  return
226
234
  end
227
235
 
@@ -232,12 +240,13 @@ module Gloo
232
240
  end
233
241
 
234
242
  #
235
- # Add a verb to the dictionary
243
+ # Add a verb to the dictionary.
244
+ # The engine is optional -- see add_object.
236
245
  #
237
- def add_verb( verb )
246
+ def add_verb( verb, engine=nil )
238
247
  # Make sure it hasn't already been registered
239
248
  if ( verb?( verb.keyword ) || verb?( verb.keyword_shortcut ) )
240
- @engine.err "duplicate verb keyword '#{verb.keyword}' or '#{verb.keyword_shortcut}'"
249
+ engine&.err "duplicate verb keyword '#{verb.keyword}' or '#{verb.keyword_shortcut}'"
241
250
  return
242
251
  end
243
252
 
@@ -8,7 +8,12 @@ module Gloo
8
8
  module Core
9
9
  class Error
10
10
 
11
- attr_accessor :value, :error_count
11
+ # The kind of error: the command couldn't be understood (syntax),
12
+ # or it was understood but couldn't be done (runtime).
13
+ SYNTAX = 'syntax'.freeze
14
+ RUNTIME = 'runtime'.freeze
15
+
16
+ attr_accessor :value, :error_count, :kind, :location
12
17
 
13
18
  #
14
19
  # Set up the error object.
@@ -23,14 +28,18 @@ module Gloo
23
28
  def clear
24
29
  @error_count = 0
25
30
  @value = nil
31
+ @kind = nil
32
+ @location = nil
26
33
  end
27
34
 
28
35
  #
29
36
  # Set the value of error.
30
37
  #
31
- def set_to( new_value )
38
+ def set_to( new_value, kind = RUNTIME, location = nil )
32
39
  @error_count += 1
33
40
  @value = new_value
41
+ @kind = kind
42
+ @location = location
34
43
  end
35
44
 
36
45
  #
@@ -75,16 +75,19 @@ module Gloo
75
75
  #
76
76
  # Run the on_error scripts in any open objects.
77
77
  # For each on_error script found, look for the error_data container
78
- # and set the error message and backtrace.
78
+ # and set the error message, backtrace, kind (syntax or runtime)
79
+ # and location -- each only if error_data has that child.
79
80
  #
80
- def on_error msg, backtrace
81
+ def on_error msg, backtrace, kind = Gloo::Core::Error::RUNTIME, location = nil
81
82
  @engine.log.debug 'on_error event'
82
83
  arr = Gloo::Core::ObjFinder.by_name( @engine, 'on_error' )
83
84
  arr.each do |o|
84
85
  data = o.parent.find_child 'error_data'
85
86
  if data
86
- data.find_child( 'message' ).set_value msg
87
- data.find_child( 'backtrace' ).set_value backtrace
87
+ data.find_child( 'message' )&.set_value msg
88
+ data.find_child( 'backtrace' )&.set_value backtrace
89
+ data.find_child( 'kind' )&.set_value kind
90
+ data.find_child( 'location' )&.set_value location
88
91
  end
89
92
 
90
93
  Gloo::Exec::Dispatch.message( @engine, 'run', o )
@@ -8,6 +8,9 @@ module Gloo
8
8
  module Core
9
9
  class Factory < Baseo
10
10
 
11
+ UNTYPED = 'untyped'.freeze
12
+ UNKNOWN_TYPE_ERR = 'Unknown type '.freeze
13
+
11
14
  # ---------------------------------------------------------------------
12
15
  # Initializer
13
16
  # ---------------------------------------------------------------------
@@ -155,6 +158,12 @@ module Gloo
155
158
  return self.update_existing pn, params[ :value ]
156
159
  end
157
160
 
161
+ unless parent
162
+ missing = pn.elements[ 0..-2 ].join( '.' )
163
+ @engine.err "Could not create '#{params[ :name ]}': #{Gloo::Core::NotFound.object( missing )}"
164
+ return nil
165
+ end
166
+
158
167
  @engine.log.debug "Creating new object: #{obj_name}"
159
168
  return create_new obj_name, params[ :value ], objtype, parent
160
169
  end
@@ -163,11 +172,6 @@ module Gloo
163
172
  # Create a new object.
164
173
  #
165
174
  def create_new( name, value, type, parent )
166
- unless parent
167
- @engine.err "Could not create object. Bad path: #{name}"
168
- return nil
169
- end
170
-
171
175
  # Check to see if the object exists already
172
176
  return parent.find_child( name ) if parent.contains_child?( name )
173
177
 
@@ -189,20 +193,21 @@ module Gloo
189
193
 
190
194
  #
191
195
  # Find the object type by name.
192
- # Return nil if the object type cannot be found or
193
- # cannot be created.
196
+ # An unknown type is a syntax error; the best guess is untyped,
197
+ # so the object (and anything nested under it) is still created.
198
+ # Return nil if the object type cannot be created.
194
199
  #
195
200
  def find_type( type_name )
196
- type_name = 'untyped' if type_name.nil? || type_name.strip.empty?
201
+ type_name = UNTYPED if type_name.nil? || type_name.strip.empty?
197
202
  t = @engine.dictionary.find_obj( type_name )
198
203
 
199
204
  if t.nil?
200
- @engine.log.warn "Could not find type, '#{type_name}'"
201
- return nil
205
+ @engine.syntax_err "#{UNKNOWN_TYPE_ERR}'#{type_name}'; using untyped."
206
+ t = @engine.dictionary.find_obj( UNTYPED )
202
207
  end
203
208
 
204
209
  unless t.can_create?
205
- @engine.err "'#{type_name}' cannot be created."
210
+ @engine.syntax_err "'#{type_name}' cannot be created."
206
211
  return nil
207
212
  end
208
213
 
@@ -23,11 +23,16 @@ module Gloo
23
23
  #
24
24
  def self.expand_here( engine, pn )
25
25
  target = engine.exec_env.here_obj
26
+ # Outside a running script there's nothing for ^ to be
27
+ # relative to; leave the path as is, so the lookup finds nothing.
28
+ return unless target
26
29
 
27
30
  here = pn.elements.first
28
31
  remainder = pn.elements[ 1..-1 ].join( '.' )
29
32
 
30
- here.length.times { target = target.parent }
33
+ here.length.times { target = target&.parent }
34
+ return unless target
35
+
31
36
  full_path = target.pn.empty? ? remainder : "#{target.pn}.#{remainder}"
32
37
  pn.set_to full_path
33
38
  end
@@ -12,7 +12,6 @@ module Gloo
12
12
  class Invoker
13
13
 
14
14
  NO_TARGET_ERR = 'Missing function reference!'.freeze
15
- NOT_FOUND_ERR = 'Object was not found: '.freeze
16
15
  NOT_FUNCTION_ERR = 'Not a function: '.freeze
17
16
  PARAM_COUNT_ERR = 'Wrong number of parameters for function: '.freeze
18
17
 
@@ -26,7 +25,7 @@ module Gloo
26
25
  #
27
26
  def self.invoke( engine, target, arg_tokens )
28
27
  if target.nil? || target.to_s.strip.empty?
29
- engine.err NO_TARGET_ERR
28
+ engine.syntax_err NO_TARGET_ERR
30
29
  return nil
31
30
  end
32
31
 
@@ -49,7 +48,7 @@ module Gloo
49
48
  func = pn.resolve
50
49
 
51
50
  unless func
52
- engine.err "#{NOT_FOUND_ERR}#{target}"
51
+ engine.err Gloo::Core::NotFound.object( target )
53
52
  return nil
54
53
  end
55
54
 
@@ -0,0 +1,42 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
+ #
4
+ # The one wording for "not found" errors, so every command reports
5
+ # a missing object, file, folder or verb the same way.
6
+ #
7
+
8
+ module Gloo
9
+ module Core
10
+ module NotFound
11
+
12
+ #
13
+ # An object path that doesn't resolve, shown as written.
14
+ #
15
+ def self.object( path )
16
+ return "Object '#{path}' was not found."
17
+ end
18
+
19
+ #
20
+ # A file that doesn't exist.
21
+ #
22
+ def self.file( name )
23
+ return "File '#{name}' was not found."
24
+ end
25
+
26
+ #
27
+ # A folder that doesn't exist.
28
+ #
29
+ def self.folder( name )
30
+ return "Folder '#{name}' was not found."
31
+ end
32
+
33
+ #
34
+ # A verb that isn't in the dictionary.
35
+ #
36
+ def self.verb( name )
37
+ return "Verb '#{name}' was not found."
38
+ end
39
+
40
+ end
41
+ end
42
+ end
data/lib/gloo/core/obj.rb CHANGED
@@ -241,7 +241,7 @@ module Gloo
241
241
  #
242
242
  def contains_child?( name )
243
243
  @children.each do |o|
244
- return true if name.downcase == o.name.downcase
244
+ return true if name.to_s.downcase == o.name.downcase
245
245
  end
246
246
  return false
247
247
  end
@@ -261,7 +261,7 @@ module Gloo
261
261
  if is_alias?
262
262
  ln = Gloo::Core::Pn.new( @engine, self.value )
263
263
  redirect = ln.resolve
264
- return redirect.find_child( name )
264
+ return redirect&.find_child( name )
265
265
  end
266
266
  return nil
267
267
  end
@@ -349,6 +349,29 @@ module Gloo
349
349
  return msgs.include?( msg.strip.downcase )
350
350
  end
351
351
 
352
+ #
353
+ # Run a step of a message that can fail on ordinary bad input or
354
+ # the outside world (invalid JSON, an unknown host, a wrong key).
355
+ # Returns true if the block ran. If it raises one of the given
356
+ # exception classes, report it as a gloo error ("Could not
357
+ # {action}: {reason}"), put false into it (unless set_it is
358
+ # false, for an action that doesn't set it), and return false.
359
+ # Any other exception is left alone, so a real bug still shows
360
+ # up as one.
361
+ #
362
+ # return false unless attempt( 'parse the JSON', JSON::ParserError ) do
363
+ # json = JSON.parse( value )
364
+ # end
365
+ #
366
+ def attempt( action, *errors, set_it: true )
367
+ yield
368
+ return true
369
+ rescue *errors => e
370
+ @engine.err "Could not #{action}: #{e.message}"
371
+ @engine.heap.it.set_to false if set_it
372
+ return false
373
+ end
374
+
352
375
  #
353
376
  # Sent this object the given message.
354
377
  #
@@ -356,7 +379,9 @@ module Gloo
356
379
  @params = params
357
380
  return self.dispatch msg if self.can_receive_message? msg
358
381
 
382
+ # The message never ran, so there is no result.
359
383
  @engine.err "Object #{self.name} cannot receive message #{msg}"
384
+ @engine.heap.it.set_to false
360
385
  return false
361
386
  end
362
387
 
@@ -370,6 +395,7 @@ module Gloo
370
395
  return true
371
396
  else
372
397
  @engine.err "Message #{msg} not implemented"
398
+ @engine.heap.it.set_to false
373
399
  return false
374
400
  end
375
401
  end
data/lib/gloo/core/op.rb CHANGED
@@ -15,6 +15,7 @@ module Gloo
15
15
  def self.op?( token )
16
16
  return [
17
17
  Gloo::Expr::OpPlus::SYMBOL,
18
+ Gloo::Expr::OpPlus::ALT_SYMBOL,
18
19
  Gloo::Expr::OpMinus::SYMBOL,
19
20
  Gloo::Expr::OpMult::SYMBOL,
20
21
  Gloo::Expr::OpDiv::SYMBOL,
@@ -34,6 +35,7 @@ module Gloo
34
35
  def self.create_op( token )
35
36
  case token
36
37
  when Gloo::Expr::OpPlus::SYMBOL then Gloo::Expr::OpPlus.new
38
+ when Gloo::Expr::OpPlus::ALT_SYMBOL then Gloo::Expr::OpPlus.new
37
39
  when Gloo::Expr::OpMinus::SYMBOL then Gloo::Expr::OpMinus.new
38
40
  when Gloo::Expr::OpMult::SYMBOL then Gloo::Expr::OpMult.new
39
41
  when Gloo::Expr::OpDiv::SYMBOL then Gloo::Expr::OpDiv.new
@@ -35,7 +35,7 @@ module Gloo
35
35
  verb = dic.find_verb( tokens.verb )
36
36
  return verb.new( @engine, tokens, params ) if verb
37
37
 
38
- @engine.err "Verb '#{tokens.verb}' was not found."
38
+ @engine.syntax_err Gloo::Core::NotFound.verb( tokens.verb )
39
39
  return nil
40
40
  end
41
41