gloo 6.7.2 → 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 (103) 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 +20 -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/here.rb +6 -1
  24. data/lib/gloo/core/invoker.rb +2 -3
  25. data/lib/gloo/core/not_found.rb +42 -0
  26. data/lib/gloo/core/obj.rb +28 -2
  27. data/lib/gloo/core/op.rb +2 -0
  28. data/lib/gloo/core/parser.rb +1 -1
  29. data/lib/gloo/core/pn.rb +33 -12
  30. data/lib/gloo/core/tokens.rb +38 -0
  31. data/lib/gloo/core/verb.rb +54 -0
  32. data/lib/gloo/exec/dispatch.rb +5 -6
  33. data/lib/gloo/exec/exec_env.rb +13 -0
  34. data/lib/gloo/exec/runner.rb +10 -2
  35. data/lib/gloo/exec/script.rb +13 -1
  36. data/lib/gloo/expr/expression.rb +39 -1
  37. data/lib/gloo/expr/op_plus.rb +3 -0
  38. data/lib/gloo/objs/basic/boolean.rb +16 -0
  39. data/lib/gloo/objs/basic/container.rb +154 -6
  40. data/lib/gloo/objs/basic/decimal.rb +1 -0
  41. data/lib/gloo/objs/basic/integer.rb +1 -0
  42. data/lib/gloo/objs/basic/string.rb +0 -1
  43. data/lib/gloo/objs/basic/string_msgs.rb +68 -32
  44. data/lib/gloo/objs/ctrl/each.rb +22 -3
  45. data/lib/gloo/objs/ctrl/each_dir.rb +20 -7
  46. data/lib/gloo/objs/ctrl/each_file.rb +78 -9
  47. data/lib/gloo/objs/ctrl/function.rb +6 -5
  48. data/lib/gloo/objs/dt/date.rb +28 -11
  49. data/lib/gloo/objs/dt/datetime.rb +72 -21
  50. data/lib/gloo/objs/dt/dt_tools.rb +46 -2
  51. data/lib/gloo/objs/dt/time.rb +28 -11
  52. data/lib/gloo/objs/str_utils/cipher.rb +17 -4
  53. data/lib/gloo/objs/str_utils/password.rb +6 -2
  54. data/lib/gloo/objs/system/erb.rb +9 -3
  55. data/lib/gloo/objs/system/file_handle.rb +57 -21
  56. data/lib/gloo/objs/system/system.rb +6 -2
  57. data/lib/gloo/objs/web/http_get.rb +12 -2
  58. data/lib/gloo/objs/web/http_post.rb +6 -2
  59. data/lib/gloo/objs/web/json.rb +38 -18
  60. data/lib/gloo/objs/web/uri.rb +36 -16
  61. data/lib/gloo/persist/file_loader.rb +72 -11
  62. data/lib/gloo/persist/file_saver.rb +2 -1
  63. data/lib/gloo/persist/indent_stack.rb +49 -33
  64. data/lib/gloo/persist/line_splitter.rb +40 -3
  65. data/lib/gloo/persist/persist_man.rb +2 -4
  66. data/lib/gloo/plugin/ext_manager.rb +6 -9
  67. data/lib/gloo/plugin/lib_manager.rb +2 -2
  68. data/lib/gloo/verbs/break.rb +1 -0
  69. data/lib/gloo/verbs/check.rb +1 -1
  70. data/lib/gloo/verbs/cls.rb +1 -0
  71. data/lib/gloo/verbs/create.rb +32 -4
  72. data/lib/gloo/verbs/execute.rb +1 -1
  73. data/lib/gloo/verbs/exists.rb +27 -15
  74. data/lib/gloo/verbs/files.rb +2 -1
  75. data/lib/gloo/verbs/help.rb +1 -0
  76. data/lib/gloo/verbs/if.rb +1 -1
  77. data/lib/gloo/verbs/invoke.rb +1 -1
  78. data/lib/gloo/verbs/list.rb +4 -3
  79. data/lib/gloo/verbs/load.rb +3 -3
  80. data/lib/gloo/verbs/move.rb +10 -8
  81. data/lib/gloo/verbs/put.rb +20 -8
  82. data/lib/gloo/verbs/quit.rb +1 -0
  83. data/lib/gloo/verbs/redirect.rb +8 -2
  84. data/lib/gloo/verbs/reload.rb +9 -1
  85. data/lib/gloo/verbs/run.rb +2 -2
  86. data/lib/gloo/verbs/save.rb +4 -2
  87. data/lib/gloo/verbs/tell.rb +1 -1
  88. data/lib/gloo/verbs/unless.rb +1 -1
  89. data/lib/gloo/verbs/unload.rb +8 -0
  90. data/test.gloo/ctrl/each.test.gloo +88 -0
  91. data/test.gloo/lang/declaration.test.gloo +27 -0
  92. data/test.gloo/lang/exceptions.test.gloo +60 -0
  93. data/test.gloo/lang/failures.test.gloo +139 -0
  94. data/test.gloo/lang/it.test.gloo +133 -0
  95. data/test.gloo/lang/naming.test.gloo +11 -0
  96. data/test.gloo/lang/ops.test.gloo +30 -0
  97. data/test.gloo/objs/can.test.gloo +101 -0
  98. data/test.gloo/objs/string.test.gloo +24 -0
  99. data/test.gloo/string/str.test.gloo +28 -0
  100. data/test.gloo/verbs/create.test.gloo +36 -0
  101. data/test.gloo/verbs/exists.test.gloo +33 -1
  102. data/test.gloo/verbs/unload.test.gloo +34 -0
  103. metadata +4 -1
@@ -12,10 +12,8 @@ module Gloo
12
12
 
13
13
  attr_reader :maps, :mech
14
14
 
15
- OBJ_NOT_FOUND_ERR = 'Could not resolve object to save: '.freeze
16
15
  PATH_EXISTS_ERR = 'Will not overwrite a file not already saved there: '.freeze
17
16
  RELOAD_DIRTY_WARNING = 'Reloading will discard unsaved changes in: '.freeze
18
- FILE_NOT_FOUND_ERR = 'File not found: '.freeze
19
17
 
20
18
  #
21
19
  # Constructor for the persistence manager.
@@ -92,7 +90,7 @@ module Gloo
92
90
  def load( name )
93
91
  pns = get_full_path_names name
94
92
  if pns.nil? || pns.empty?
95
- @engine.err( "#{FILE_NOT_FOUND_ERR}#{name}" ) unless name.to_s.strip.empty?
93
+ @engine.err( Gloo::Core::NotFound.file( name ) ) unless name.to_s.strip.empty?
96
94
  return false
97
95
  end
98
96
 
@@ -239,7 +237,7 @@ module Gloo
239
237
  #
240
238
  def resolve_for_save( name )
241
239
  obj = Gloo::Core::Pn.new( @engine, name ).resolve
242
- @engine.err( "#{OBJ_NOT_FOUND_ERR}#{name}" ) unless obj
240
+ @engine.err( Gloo::Core::NotFound.object( name ) ) unless obj
243
241
  return obj
244
242
  end
245
243
 
@@ -35,13 +35,13 @@ module Gloo
35
35
  root = @engine.settings.ext_path
36
36
 
37
37
  unless File.exist?( root )
38
- @engine.log.error "Extension directory does not exist: #{root}"
38
+ @engine.err Gloo::Core::NotFound.folder( root )
39
39
  return nil
40
40
  end
41
41
 
42
42
  f = File.join( root, name, name + EXT_FILE )
43
43
  unless File.exist?( f )
44
- @engine.log.error "Extension start file does not exist: #{f}"
44
+ @engine.err Gloo::Core::NotFound.file( f )
45
45
  return nil
46
46
  end
47
47
 
@@ -62,10 +62,8 @@ module Gloo
62
62
  @engine.log.debug "Loading extension: #{name}"
63
63
  fn = ext_start_file name
64
64
 
65
- unless fn
66
- @engine.log.error "Extension start file not found for: #{name}"
67
- return
68
- end
65
+ # ext_start_file has already reported what's missing.
66
+ return unless fn
69
67
 
70
68
  @extensions[name] = fn
71
69
  register_extension name, fn
@@ -85,9 +83,8 @@ module Gloo
85
83
  inst = plugin_class.new
86
84
  ext_cb = Callback.new( @engine )
87
85
  inst.register( ext_cb )
88
- rescue NameError => ex
89
- @engine.log_exception ex
90
- @engine.log.error "Could not find class #{class_name} in file #{full_path}"
86
+ rescue NameError
87
+ @engine.err "Could not find class '#{class_name}' in extension file '#{full_path}'."
91
88
  end
92
89
  end
93
90
 
@@ -93,8 +93,8 @@ module Gloo
93
93
  inst = plugin_class.new
94
94
  lib_cb = Callback.new( @engine )
95
95
  inst.register( lib_cb )
96
- rescue NameError => ex
97
- @engine.log.error "Warning: Could not find class #{class_name}", ex
96
+ rescue NameError
97
+ @engine.err "Could not find class '#{class_name}' in library '#{name}'."
98
98
  end
99
99
  end
100
100
 
@@ -16,6 +16,7 @@ module Gloo
16
16
  # Stop the execution of the current script.
17
17
  #
18
18
  def run
19
+ warn_extra_words
19
20
  @engine.exec_env.running_script.break_out
20
21
  end
21
22
 
@@ -21,7 +21,7 @@ module Gloo
21
21
  msg = @tokens.after_token( FOR )
22
22
 
23
23
  unless msg
24
- @engine.err( UNKNOWN_MSG_ERR )
24
+ @engine.syntax_err( UNKNOWN_MSG_ERR )
25
25
  return
26
26
  end
27
27
 
@@ -15,6 +15,7 @@ module Gloo
15
15
  # Run the verb.
16
16
  #
17
17
  def run
18
+ warn_extra_words
18
19
  @engine.platform&.clear_screen
19
20
  end
20
21
 
@@ -13,6 +13,10 @@ module Gloo
13
13
  AS = 'as'.freeze
14
14
  VAL = ':'.freeze
15
15
  NO_NAME_ERR = 'Object name is missing!'.freeze
16
+ BAD_NAME_CHARS = /[()"']/.freeze
17
+ BAD_NAME_ERR = "'%s' isn't a name! To create an object at a " \
18
+ 'computed path, put the path into an alias, then create ' \
19
+ 'the alias: create slot* as string.'.freeze
16
20
 
17
21
  #
18
22
  # Run the verb.
@@ -23,9 +27,10 @@ module Gloo
23
27
  value = @tokens.after_token( VAL )
24
28
 
25
29
  unless name
26
- @engine.err NO_NAME_ERR
27
- return
30
+ @engine.syntax_err NO_NAME_ERR
31
+ return @engine.heap.it.set_to( false )
28
32
  end
33
+ return @engine.heap.it.set_to( false ) if bad_name?( name )
29
34
  create name, type, value
30
35
  end
31
36
 
@@ -49,6 +54,22 @@ module Gloo
49
54
 
50
55
  private
51
56
 
57
+ #
58
+ # Is the name something create can't use? A name is taken as
59
+ # written, so expression characters -- parentheses or quotes -- or
60
+ # extra words between the name and 'as' / ':' mean the script was
61
+ # trying to compute it. Report that and return true.
62
+ #
63
+ def bad_name?( name )
64
+ after = @tokens.token_count > 2 ? @tokens.tokens[ 2 ] : nil
65
+ return false unless name.match?( BAD_NAME_CHARS ) ||
66
+ !( after.nil? || after.downcase == AS || after.start_with?( VAL ) )
67
+
68
+ written = @tokens.tokens[ 1..].take_while { |t| t.downcase != AS && !t.start_with?( VAL ) }
69
+ @engine.syntax_err format( BAD_NAME_ERR, written.join( ' ' ) )
70
+ return true
71
+ end
72
+
52
73
  #
53
74
  # Create an object with given name of given type with
54
75
  # the given initial value.
@@ -60,12 +81,18 @@ module Gloo
60
81
 
61
82
  # Check to see if this is an alias
62
83
  pn = Gloo::Core::Pn.new( @engine, name )
84
+ return if pn.it_target_err?( 'created',
85
+ 'it is reserved for the result of the last command, so use another name' )
86
+
63
87
  obj = pn.resolve if pn
64
88
  name = obj.value if obj&.is_alias?
65
89
 
66
90
  obj = @engine.factory.create( { name: name, type: type, value: value } )
67
91
 
68
- obj.add_default_children if obj&.add_children_on_create?
92
+ # Couldn't be created (already reported).
93
+ return @engine.heap.it.set_to( false ) unless obj
94
+
95
+ obj.add_default_children if obj.add_children_on_create?
69
96
  @engine.heap.it.set_to value
70
97
  end
71
98
 
@@ -96,7 +123,8 @@ module Gloo
96
123
  "to the existing object's current value.",
97
124
  :errors => [
98
125
  "#{NO_NAME_ERR} — The name of the object was not specified and the object cannot be created.",
99
- 'Could not create object. Bad path: {name} — The parent container named in the path does not exist. The path is root-relative and every container above the new object must already exist.'
126
+ "Could not create '{path}': Object '{parent.path}' was not found. — The parent container named in the path does not exist. The path is root-relative and every container above the new object must already exist. Nothing is created, and it is false.",
127
+ "#{format( BAD_NAME_ERR, '{name}' )} — The name has parentheses or quotes, or there are extra words between the name and as / : — create takes a name as written. Nothing is created, and it is false."
100
128
  ],
101
129
  :examples => <<~EXAMPLES.strip
102
130
  # Basic examples of creating an object from the gloo shell:
@@ -17,7 +17,7 @@ module Gloo
17
17
  #
18
18
  def run
19
19
  if @tokens.token_count < 2
20
- @engine.err MISSING_EXPR_ERR
20
+ @engine.syntax_err MISSING_EXPR_ERR
21
21
  return
22
22
  end
23
23
 
@@ -1,7 +1,8 @@
1
1
  # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
2
  # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
3
  #
4
- # Check to see if a verb or object type has been defined (or loaded).
4
+ # Check to see if a verb or object type has been defined (or loaded),
5
+ # or if an object instance exists.
5
6
  #
6
7
 
7
8
  module Gloo
@@ -14,7 +15,9 @@ module Gloo
14
15
  OBJ_TYPE = 'object'.freeze
15
16
  ANY_TYPE = 'any'.freeze
16
17
  INSTANCE = 'instance'.freeze
18
+ KINDS = [ ANY_TYPE, OBJ_TYPE, VERB_TYPE, INSTANCE ].freeze
17
19
  WRONG_NUM_ARGS_ERR = 'Wrong number of arguments! 1 or 2 expected.'.freeze
20
+ UNKNOWN_KIND_ERR = "Unknown kind '%s'! Use any, object, verb or instance.".freeze
18
21
 
19
22
  #
20
23
  # Run the verb.
@@ -23,12 +26,16 @@ module Gloo
23
26
  if @tokens.token_count == 3
24
27
  type = @tokens.second.strip.downcase
25
28
  keyword = @tokens.last
29
+ unless KINDS.include?( type )
30
+ @engine.syntax_err format( UNKNOWN_KIND_ERR, @tokens.second )
31
+ return @engine.heap.it.set_to( false )
32
+ end
26
33
  elsif @tokens.token_count == 2
27
34
  type = ANY_TYPE
28
35
  keyword = @tokens.second
29
36
  else
30
- @engine.err WRONG_NUM_ARGS_ERR
31
- return
37
+ @engine.syntax_err WRONG_NUM_ARGS_ERR
38
+ return @engine.heap.it.set_to( false )
32
39
  end
33
40
 
34
41
  @engine.heap.it.set_to lookup_keyword(keyword, type)
@@ -64,7 +71,9 @@ module Gloo
64
71
  elsif type == OBJ_TYPE
65
72
  return @engine.dictionary.obj?(keyword)
66
73
  elsif type == ANY_TYPE
67
- return @engine.dictionary.verb?(keyword) || @engine.dictionary.obj?(keyword)
74
+ return @engine.dictionary.verb?(keyword) ||
75
+ @engine.dictionary.obj?(keyword) ||
76
+ instance_exists?(keyword)
68
77
  elsif type == INSTANCE
69
78
  return instance_exists?(keyword)
70
79
  end
@@ -94,26 +103,29 @@ module Gloo
94
103
  :name => KEYWORD,
95
104
  :shortcut => KEYWORD_SHORT,
96
105
  :description => 'Check to see if a verb or object type has ' \
97
- 'been defined (or loaded). This verb can be used to check ' \
98
- 'if a core library or extension has been loaded.',
106
+ 'been defined (or loaded), or if an object instance exists ' \
107
+ 'in the heap. With no kind, any of the three counts.',
99
108
  :syntax => [ 'exists? |any,object,verb,instance| {keyword}' ],
100
109
  :parameters => [
101
- "Kind of Keyword — Check for the presence of a keyword of a kind. " \
102
- "any (default, optional) checks for either an object or a verb; " \
103
- "object checks for an object matching the keyword or keyword " \
110
+ "Kind of Keyword — Optional; any is the default. " \
111
+ "any checks for a verb, an object type, or an object instance; " \
112
+ "object checks for an object type matching the keyword or keyword " \
104
113
  "shortcut; verb checks for a verb matching the keyword or " \
105
114
  "keyword shortcut; instance checks to see if an object " \
106
115
  "instance exists at the pathname represented by keyword. " \
107
116
  "`exists? any markdown` is identical to `exists? markdown`.",
108
- '{keyword} — The verb or object keyword or keyword shortcut. ' \
109
- 'Must be specified. For the instance option, the keyword is ' \
110
- 'the pathname to an object instance.'
117
+ '{keyword} — A verb or object type keyword or keyword shortcut, ' \
118
+ 'or the pathname to an object instance. Must be specified.'
111
119
  ],
112
- :result => 'If the object or verb has been defined, then true, ' \
113
- 'otherwise false. The result will be in it.',
120
+ :result => 'If the verb, object type or instance exists, then ' \
121
+ 'true, otherwise false. The result will be in it.',
114
122
  :errors => [
115
- "#{WRONG_NUM_ARGS_ERR} The kind of keyword is optional."
123
+ "#{WRONG_NUM_ARGS_ERR} The kind of keyword is optional.",
124
+ "#{format( UNKNOWN_KIND_ERR, '{kind}' )} The kind is not one of the four."
116
125
  ],
126
+ :notes => 'To check that a core library or extension has been ' \
127
+ 'loaded, use object: exists? object markdown. With no kind, ' \
128
+ 'an object instance with the same name would also count.',
117
129
  :examples => <<~EXAMPLES.strip
118
130
  #
119
131
  # Example of exists? keyword.
@@ -15,7 +15,8 @@ module Gloo
15
15
  # Run the verb.
16
16
  #
17
17
  def run
18
- return unless @engine.persist_man.maps
18
+ warn_extra_words
19
+ return @engine.heap.it.set_to( 0 ) unless @engine.persist_man.maps
19
20
 
20
21
  @engine.persist_man.maps.each do |map|
21
22
  @engine.log.show "#{map.obj.name} - #{map.pn}"
@@ -20,6 +20,7 @@ module Gloo
20
20
  # Takes no arguments - always enters the interactive help shell.
21
21
  #
22
22
  def run
23
+ warn_extra_words
23
24
  @engine.log.show BANNER
24
25
  build_shell.start
25
26
  end
data/lib/gloo/verbs/if.rb CHANGED
@@ -65,7 +65,7 @@ module Gloo
65
65
  # The first token is the verb, so we drop it.
66
66
  value = value[ 1..-1 ]
67
67
  else
68
- @engine.err MISSING_EXPR_ERR
68
+ @engine.syntax_err MISSING_EXPR_ERR
69
69
  end
70
70
 
71
71
  return value
@@ -67,7 +67,7 @@ module Gloo
67
67
  'directly rather than going through it.',
68
68
  :errors => [
69
69
  "#{Gloo::Core::Invoker::NO_TARGET_ERR} — No function reference was given.",
70
- "#{Gloo::Core::Invoker::NOT_FOUND_ERR}{path.to.function} — The path doesn't resolve to any object.",
70
+ "Object '{path.to.function}' was not found. — The path doesn't resolve to any object.",
71
71
  "#{Gloo::Core::Invoker::NOT_FUNCTION_ERR}{path.to.function} — The path resolves to an object that isn't a function.",
72
72
  "#{Gloo::Core::Invoker::PARAM_COUNT_ERR}{path.to.function} (expected N, got N) — The number of params given doesn't match what the function declares."
73
73
  ],
@@ -10,7 +10,6 @@ module Gloo
10
10
 
11
11
  KEYWORD = 'list'.freeze
12
12
  KEYWORD_SHORT = '.'.freeze
13
- TARGET_MISSING_ERR = 'Object does not exist: '.freeze
14
13
 
15
14
  #
16
15
  # Run the verb.
@@ -20,11 +19,13 @@ module Gloo
20
19
  target = self.determine_target
21
20
  indent = self.determine_indent
22
21
 
22
+ return if target.it_target_err?( 'listed' )
23
+
23
24
  obj = target.resolve
24
25
  if obj
25
26
  show_target( obj, levels, indent )
26
27
  else
27
- @engine.err "#{TARGET_MISSING_ERR} #{target}"
28
+ @engine.err Gloo::Core::NotFound.object( @tokens.second )
28
29
  end
29
30
  end
30
31
 
@@ -191,7 +192,7 @@ module Gloo
191
192
  :result => 'Object and children are listed out in the CLI. ' \
192
193
  'Doc lines are included when the list_docs setting is on.',
193
194
  :errors => [
194
- "#{TARGET_MISSING_ERR}{path.to.object} — The object specified that is to be listed could not be found."
195
+ "Object '{path.to.object}' was not found. — The object specified that is to be listed could not be found."
195
196
  ],
196
197
  :examples => <<~EXAMPLES.strip
197
198
  > list
@@ -30,12 +30,12 @@ module Gloo
30
30
  opt = @tokens.second.strip.downcase
31
31
  fn = @tokens.last
32
32
  else
33
- @engine.err WRONG_NUM_ARGS_ERR
33
+ @engine.syntax_err WRONG_NUM_ARGS_ERR
34
34
  return
35
35
  end
36
36
 
37
37
  if fn.blank?
38
- @engine.err MISSING_EXPR_ERR
38
+ @engine.syntax_err MISSING_EXPR_ERR
39
39
  elsif opt == FILE_OPT
40
40
  load_gloo_file fn
41
41
  elsif opt == EXT_OPT
@@ -43,7 +43,7 @@ module Gloo
43
43
  elsif opt == LIB_OPT
44
44
  load_library fn
45
45
  else
46
- @engine.err UNKNOWN_OPT_ERR
46
+ @engine.syntax_err UNKNOWN_OPT_ERR
47
47
  end
48
48
  end
49
49
 
@@ -12,9 +12,7 @@ module Gloo
12
12
  KEYWORD_SHORT = 'mv'.freeze
13
13
  TO = 'to'.freeze
14
14
  MISSING_SRC_ERR = 'Object to move was not specified!'.freeze
15
- MISSING_SRC_OBJ_ERR = 'Could not find object to move: '.freeze
16
15
  MISSING_DST_ERR = "'Move' must include 'to' parent object!".freeze
17
- MISSING_DST_OBJ_ERR = 'Could not resolve target: '.freeze
18
16
 
19
17
  #
20
18
  # Run the verb.
@@ -56,15 +54,17 @@ module Gloo
56
54
  def lookup_obj
57
55
  arr = @tokens.before_token( TO )
58
56
  if arr.count == 1
59
- @engine.err MISSING_SRC_ERR
57
+ @engine.syntax_err MISSING_SRC_ERR
60
58
  return
61
59
  end
62
60
 
63
61
  name = arr[ 1 ]
64
62
  pn = Gloo::Core::Pn.new( @engine, name )
63
+ return nil if pn.it_target_err?( 'moved' )
64
+
65
65
  o = pn.resolve
66
66
 
67
- @engine.err( "#{MISSING_SRC_OBJ_ERR} #{name}" ) unless o
67
+ @engine.err( Gloo::Core::NotFound.object( name ) ) unless o
68
68
  return o
69
69
  end
70
70
 
@@ -74,13 +74,15 @@ module Gloo
74
74
  def lookup_dst
75
75
  dst = @tokens.after_token( TO )
76
76
  unless dst
77
- @engine.err MISSING_DST_ERR
77
+ @engine.syntax_err MISSING_DST_ERR
78
78
  return nil
79
79
  end
80
80
 
81
81
  pn = Gloo::Core::Pn.new( @engine, dst )
82
+ return nil if pn.it_target_err?( 'moved into' )
83
+
82
84
  o = pn.resolve
83
- @engine.err( "#{MISSING_DST_OBJ_ERR} '#{dst}'" ) unless o
85
+ @engine.err( Gloo::Core::NotFound.object( dst ) ) unless o
84
86
  return o
85
87
  end
86
88
 
@@ -104,9 +106,9 @@ module Gloo
104
106
  :result => 'The object will now be in the new location.',
105
107
  :errors => [
106
108
  "#{MISSING_SRC_ERR} The {path.to.object} is not specified.",
107
- "#{MISSING_SRC_OBJ_ERR}{path.to.object} — The {path.to.object} cannot be resolved.",
109
+ "Object '{path.to.object}' was not found. — The {path.to.object} cannot be resolved.",
108
110
  "#{MISSING_DST_ERR} The {new.parent} is not specified.",
109
- "#{MISSING_DST_OBJ_ERR}{new.parent} — The {new.parent} cannot be resolved."
111
+ "Object '{new.parent}' was not found. — The {new.parent} cannot be resolved."
110
112
  ],
111
113
  :examples => <<~EXAMPLES.strip
112
114
  can [can] :
@@ -13,17 +13,16 @@ module Gloo
13
13
  INTO = 'into'.freeze
14
14
  MISSING_EXPR_ERR = 'Missing Expression!'.freeze
15
15
  INTO_MISSING_ERR = 'Target (into) missing!'.freeze
16
- TARGET_ERR = 'Target could not be resolved: '.freeze
17
16
 
18
17
  #
19
18
  # Run the verb.
20
19
  #
21
20
  def run
22
21
  value = fetch_value_tokens
23
- return if value.nil?
22
+ return @engine.heap.it.set_to( false ) if value.nil?
24
23
 
25
24
  target = lookup_target
26
- return if target.nil?
25
+ return @engine.heap.it.set_to( false ) if target.nil?
27
26
 
28
27
  update_target target, value
29
28
  end
@@ -54,7 +53,7 @@ module Gloo
54
53
  def fetch_value_tokens
55
54
  value = @tokens.before_token( INTO )
56
55
  if value.nil? || ( value.count <= 1 )
57
- @engine.err MISSING_EXPR_ERR
56
+ @engine.syntax_err MISSING_EXPR_ERR
58
57
  return nil
59
58
  end
60
59
 
@@ -69,7 +68,7 @@ module Gloo
69
68
  target = @tokens.after_token( INTO )
70
69
  return target if target
71
70
 
72
- @engine.err INTO_MISSING_ERR
71
+ @engine.syntax_err INTO_MISSING_ERR
73
72
  return nil
74
73
  end
75
74
 
@@ -78,12 +77,22 @@ module Gloo
78
77
  #
79
78
  def update_target( target, value )
80
79
  pn = Gloo::Core::Pn.new( @engine, target )
80
+ return if pn.it_target_err?( 'put into',
81
+ 'put the value into an object instead' )
82
+
81
83
  o = pn.resolve
82
84
  if o.nil?
83
- @engine.err "#{TARGET_ERR} #{target}"
85
+ @engine.err Gloo::Core::NotFound.object( target )
86
+ @engine.heap.it.set_to false
84
87
  elsif value.count.positive?
88
+ errors_before = @engine.heap.error.error_count
85
89
  expr = Gloo::Expr::Expression.new( @engine, value )
86
90
  result = expr.evaluate
91
+
92
+ # The value couldn't be evaluated (already reported); leave
93
+ # the target with its last good value rather than nil.
94
+ return @engine.heap.it.set_to( false ) if @engine.heap.error.error_count > errors_before
95
+
87
96
  o.set_value result
88
97
  @engine.heap.it.set_to result
89
98
  end
@@ -108,11 +117,14 @@ module Gloo
108
117
  '{dst.path} — The path to the destination object.'
109
118
  ],
110
119
  :result => 'The destination object has the result of the ' \
111
- 'evaluated expression. It will also be set into it.',
120
+ 'evaluated expression. It will also be set into it. If ' \
121
+ 'evaluating the expression reports an error (eg. it uses ' \
122
+ 'an object that does not exist), the destination is left ' \
123
+ 'unchanged. If the put fails for any reason, it is false.',
112
124
  :errors => [
113
125
  "#{MISSING_EXPR_ERR} — The into keyword is missing, or no source expression is provided.",
114
126
  "#{INTO_MISSING_ERR} — The destination is not specified.",
115
- "#{TARGET_ERR}{dst.path} — The destination of the put cannot be resolved."
127
+ "Object '{dst.path}' was not found. — The destination of the put cannot be resolved."
116
128
  ],
117
129
  :examples => <<~EXAMPLES.strip
118
130
  > put 'one' into str
@@ -18,6 +18,7 @@ module Gloo
18
18
  # engine stop gracefully next time through the loop.
19
19
  #
20
20
  def run
21
+ warn_extra_words
21
22
  @engine.stop_running
22
23
  end
23
24
 
@@ -24,14 +24,19 @@ module Gloo
24
24
  #
25
25
  def run
26
26
  if @tokens.token_count < 2
27
- @engine.err MISSING_EXPR_ERR
27
+ @engine.syntax_err MISSING_EXPR_ERR
28
28
  return
29
29
  end
30
30
 
31
31
  if is_hard_redirect?
32
32
  redirect_hard
33
33
  else
34
+ pn = Gloo::Core::Pn.new( @engine, @tokens.second )
35
+ return if pn.it_target_err?( 'redirected to' )
36
+
34
37
  determine_target
38
+ return @engine.err( Gloo::Core::NotFound.object( @tokens.second ) ) unless @target_obj
39
+
35
40
  redirect_to_target
36
41
  end
37
42
  end
@@ -163,7 +168,8 @@ module Gloo
163
168
  'the target page.',
164
169
  :errors => [
165
170
  "#{MISSING_EXPR_ERR} — Redirect requires the target page or script to redirect to.",
166
- "#{BAD_TARGET_ERR} — The target object does not exist or cannot receive the redirect.",
171
+ "Object '{target}' was not found. — The target object does not exist.",
172
+ "#{BAD_TARGET_ERR} — The target object cannot receive the redirect.",
167
173
  "#{APP_NOT_RUNING_ERR} — Page cannot redirect because the application is not running."
168
174
  ],
169
175
  :examples => <<~EXAMPLES.strip
@@ -17,6 +17,8 @@ module Gloo
17
17
  # Then the engine will restart with the original parameters.
18
18
  #
19
19
  def run
20
+ return if extra_words_err?( 'nothing was reloaded; to reload one object, use tell x to reload' )
21
+
20
22
  @engine.persist_man.reload_all
21
23
  end
22
24
 
@@ -61,9 +63,15 @@ module Gloo
61
63
  'loaded again. Note that re-load does not trigger the ' \
62
64
  'on_load script to run. There is an on_reload message sent ' \
63
65
  'to all open files.',
66
+ :errors => [
67
+ 'reload takes no object, so nothing was reloaded — anything ' \
68
+ 'written after reload is an error, and nothing is reloaded. ' \
69
+ 'To reload one object, use tell x to reload.'
70
+ ],
64
71
  :notes => 'If a file has changes that have not been saved, ' \
65
72
  'reloading discards them (a warning is logged, but the ' \
66
- 'reload still proceeds).',
73
+ 'reload still proceeds). reload takes no object; to reload ' \
74
+ 'just one object\'s file, send it the reload message.',
67
75
  :examples => <<~EXAMPLES.strip
68
76
  > reload
69
77
  EXAMPLES
@@ -19,7 +19,7 @@ module Gloo
19
19
  #
20
20
  def run
21
21
  if @tokens.token_count < 2
22
- @engine.err MISSING_EXPR_ERR
22
+ @engine.syntax_err MISSING_EXPR_ERR
23
23
  return
24
24
  end
25
25
 
@@ -62,7 +62,7 @@ module Gloo
62
62
  #
63
63
  def run_expression
64
64
  unless @tokens.token_count > 2
65
- @engine.err MISSING_EXPR_ERR
65
+ @engine.syntax_err MISSING_EXPR_ERR
66
66
  return
67
67
  end
68
68
 
@@ -17,7 +17,9 @@ module Gloo
17
17
  # Run the verb.
18
18
  #
19
19
  def run
20
- return @engine.persist_man.save @tokens.second unless @tokens.index_of( TO )
20
+ name = @tokens.second
21
+ return if name && Gloo::Core::Pn.new( @engine, name ).it_target_err?( 'saved' )
22
+ return @engine.persist_man.save name unless @tokens.index_of( TO )
21
23
 
22
24
  run_save_to
23
25
  end
@@ -49,7 +51,7 @@ module Gloo
49
51
  name = @tokens.before_token( TO )[ 1 ]
50
52
  path = @tokens.after_token( TO )
51
53
  unless path
52
- @engine.err MISSING_PATH_ERR
54
+ @engine.syntax_err MISSING_PATH_ERR
53
55
  return
54
56
  end
55
57
 
@@ -22,7 +22,7 @@ module Gloo
22
22
  msg = @tokens.after_token( TO )
23
23
 
24
24
  unless msg
25
- @engine.err( UNKNOWN_MSG_ERR )
25
+ @engine.syntax_err( UNKNOWN_MSG_ERR )
26
26
  return
27
27
  end
28
28
 
@@ -65,7 +65,7 @@ module Gloo
65
65
  # The first token is the verb, so we drop it.
66
66
  value = value[ 1..-1 ]
67
67
  else
68
- @engine.err MISSING_EXPR_ERR
68
+ @engine.syntax_err MISSING_EXPR_ERR
69
69
  end
70
70
 
71
71
  return value