gloo 6.7.2 → 7.0.1

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 (117) 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 +59 -2
  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 +64 -8
  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/error.rb +11 -2
  21. data/lib/gloo/core/event_manager.rb +7 -4
  22. data/lib/gloo/core/factory.rb +16 -11
  23. data/lib/gloo/core/gloo_system.rb +3 -2
  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 +30 -3
  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/ext_manager.rb +6 -9
  68. data/lib/gloo/plugin/lib_manager.rb +2 -2
  69. data/lib/gloo/verbs/break.rb +1 -0
  70. data/lib/gloo/verbs/check.rb +1 -1
  71. data/lib/gloo/verbs/cls.rb +1 -0
  72. data/lib/gloo/verbs/create.rb +32 -4
  73. data/lib/gloo/verbs/execute.rb +1 -1
  74. data/lib/gloo/verbs/exists.rb +27 -15
  75. data/lib/gloo/verbs/files.rb +2 -1
  76. data/lib/gloo/verbs/help.rb +1 -0
  77. data/lib/gloo/verbs/if.rb +1 -1
  78. data/lib/gloo/verbs/invoke.rb +1 -1
  79. data/lib/gloo/verbs/list.rb +4 -3
  80. data/lib/gloo/verbs/load.rb +3 -3
  81. data/lib/gloo/verbs/move.rb +10 -8
  82. data/lib/gloo/verbs/put.rb +20 -8
  83. data/lib/gloo/verbs/quit.rb +1 -0
  84. data/lib/gloo/verbs/redirect.rb +8 -2
  85. data/lib/gloo/verbs/reload.rb +9 -1
  86. data/lib/gloo/verbs/run.rb +2 -2
  87. data/lib/gloo/verbs/save.rb +4 -2
  88. data/lib/gloo/verbs/tell.rb +1 -1
  89. data/lib/gloo/verbs/unless.rb +1 -1
  90. data/lib/gloo/verbs/unload.rb +8 -0
  91. data/test.gloo/ctrl/each.test.gloo +88 -0
  92. data/test.gloo/dt/datetime.test.gloo +5 -3
  93. data/test.gloo/lang/convert.test.gloo +1 -0
  94. data/test.gloo/lang/declaration.test.gloo +38 -0
  95. data/test.gloo/lang/exceptions.test.gloo +68 -0
  96. data/test.gloo/lang/failures.test.gloo +143 -0
  97. data/test.gloo/lang/gloo_sys.test.gloo +14 -9
  98. data/test.gloo/lang/it.test.gloo +139 -0
  99. data/test.gloo/lang/naming.test.gloo +21 -0
  100. data/test.gloo/lang/ops.test.gloo +30 -0
  101. data/test.gloo/math/div.test.gloo +2 -2
  102. data/test.gloo/math/mult.test.gloo +4 -4
  103. data/test.gloo/objs/can.test.gloo +104 -0
  104. data/test.gloo/objs/function.test.gloo +1 -0
  105. data/test.gloo/objs/string.test.gloo +24 -0
  106. data/test.gloo/string/str.test.gloo +28 -0
  107. data/test.gloo/verbs/check.test.gloo +10 -1
  108. data/test.gloo/verbs/create.test.gloo +37 -0
  109. data/test.gloo/verbs/exists.test.gloo +34 -1
  110. data/test.gloo/verbs/invoke.test.gloo +1 -0
  111. data/test.gloo/verbs/list.test.gloo +1 -0
  112. data/test.gloo/verbs/load.test.gloo +2 -0
  113. data/test.gloo/verbs/reload.test.gloo +1 -0
  114. data/test.gloo/verbs/save.test.gloo +2 -0
  115. data/test.gloo/verbs/throw.test.gloo +2 -0
  116. data/test.gloo/verbs/unload.test.gloo +35 -0
  117. metadata +4 -1
@@ -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
@@ -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
 
@@ -246,7 +246,7 @@ module Gloo
246
246
 
247
247
  # Is the platform Linux?
248
248
  def msg_platform_linux?
249
- return OS.posix?
249
+ return OS.linux?
250
250
  end
251
251
 
252
252
  # Is the platform Mac?
@@ -322,7 +322,8 @@ module Gloo
322
322
  'platform_windows? — Is the platform Windows?',
323
323
  'platform_unix? — Is the platform Unix?',
324
324
  'platform_linux? — Is the platform Linux?',
325
- 'platform_mac? — Is the platform Mac?'
325
+ 'platform_mac? — Is the platform Mac?',
326
+ 'platform_wsl? — Is the platform WSL (Linux on Windows)?'
326
327
  ],
327
328
  :examples => <<~EXAMPLES.strip
328
329
  > show gloo.user
@@ -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
@@ -420,7 +446,8 @@ module Gloo
420
446
  end
421
447
 
422
448
  #
423
- # Check to see if the value is blank.
449
+ # Check to see if the simple value is blank.
450
+ # Children aren't considered; use contains? for those.
424
451
  #
425
452
  def msg_blank?
426
453
  val_blank = value.blank?
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
 
data/lib/gloo/core/pn.rb CHANGED
@@ -14,6 +14,8 @@ module Gloo
14
14
  ERROR = 'error'.freeze
15
15
  CONTEXT = '@'.freeze
16
16
  NAMED_COLORS = %w[red blue green white black yellow].freeze
17
+ IT_NOT_AN_OBJECT = "it isn't an object and can't be %s; %s.".freeze
18
+ IT_HINT = 'put it into an object first (put it into x)'.freeze
17
19
 
18
20
  attr_reader :src, :elements
19
21
 
@@ -61,6 +63,19 @@ module Gloo
61
63
  return @src.downcase == IT
62
64
  end
63
65
 
66
+ #
67
+ # it is a read-only result value, not an object, so it can't
68
+ # be the target of a verb or message. If the path refers to it,
69
+ # report that (what it can't be: 'sent messages', 'run', ...,
70
+ # and what to do instead) and return true.
71
+ #
72
+ def it_target_err?( what, hint = IT_HINT )
73
+ return false unless self.it?
74
+
75
+ @engine.err format( IT_NOT_AN_OBJECT, what, hint )
76
+ return true
77
+ end
78
+
64
79
  #
65
80
  # Does the pathname reference refer to error?
66
81
  #
@@ -141,19 +156,29 @@ module Gloo
141
156
  self.set_to( "#{@engine.heap.context}#{@src[1..-1]}" )
142
157
  end
143
158
 
159
+ #
160
+ # Expand any here (^) or context (@) reference into the
161
+ # full path, so the lookup below starts from the heap root.
162
+ #
163
+ def expand_refs
164
+ Here.expand_here( @engine, self ) if Here.includes_here_ref?( @elements )
165
+ expand_context if self.includes_context?
166
+ end
167
+
144
168
  #
145
169
  # Get the parent that contains the object referenced.
146
170
  #
171
+ # This is a lookup, not a use of the object: a missing element
172
+ # returns nil without reporting an error. A caller that needs the
173
+ # object reports its own error.
174
+ #
147
175
  def get_parent
148
176
  o = @engine.heap.root
149
177
 
150
178
  if self.includes_path?
151
179
  @elements[ 0..-2 ].each do |e|
152
180
  o = o.find_child( e )
153
- if o.nil?
154
- @engine.err "Object '#{e}' was not found."
155
- return nil
156
- end
181
+ return nil if o.nil?
157
182
  end
158
183
  end
159
184
 
@@ -162,12 +187,14 @@ module Gloo
162
187
 
163
188
  #
164
189
  # Does the object at the path exist?
190
+ # A question, so a missing object is an answer, not an error.
165
191
  #
166
192
  def exists?
167
193
  return true if self.root?
168
194
  return true if self.it?
169
195
  return true if self.error?
170
196
 
197
+ expand_refs
171
198
  parent = self.get_parent
172
199
  return false unless parent
173
200
 
@@ -186,6 +213,7 @@ module Gloo
186
213
  #
187
214
  # Resolve the pathname reference.
188
215
  # Find the object referenced or return nil if it is not found.
216
+ # Like get_parent, a missing object is not reported here.
189
217
  #
190
218
  def resolve
191
219
  return @engine.heap.root if self.root?
@@ -194,14 +222,7 @@ module Gloo
194
222
  return Gloo::Core::GlooSystem.new(
195
223
  @engine, self ) if self.gloo_sys?
196
224
 
197
- if Here.includes_here_ref?( @elements )
198
- Here.expand_here( @engine, self )
199
- end
200
-
201
- if self.includes_context?
202
- expand_context
203
- end
204
-
225
+ expand_refs
205
226
  parent = self.get_parent
206
227
  return nil unless parent
207
228