gloo 7.1 → 7.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 211368f27a7bbea86aed449d545c28f05a62857e92e8a3942394261a336e597b
4
- data.tar.gz: 98ea31342225ac55d1ae499727fe2e71bf1679a8c4f1523651e57ac50829a714
3
+ metadata.gz: d25bb60fb25bb1b42e44eeac62b0ec3bae86a9f3770c696caf4ab39b7f1c9784
4
+ data.tar.gz: 260731b8d5f1cd1c6f3718be0d26f17d39139795e7e5c13ff8908a4fc3f1c453
5
5
  SHA512:
6
- metadata.gz: c2457159e21684fbdb72367753c52204de25393f74ee715622ad9879a6b228477f580440e516fee5c0d47ea6c198f8568bd109912abb2cbb01704e1e84d94d79
7
- data.tar.gz: 0ba0bd0264f57f0334312c897a07b3023bd2019e835c8ec88e6e9bd827aa6af80c4a7610c1f1198958bb8948da8e2ed4981c56b725b371086df2e8950661ab07
6
+ metadata.gz: 3729efcd7dcfa5964a408fb28750bd2ac7caea3af323b1ea8c8b75503446f51805db7f20b05bfd705a9ac020bd06fe6fba5135013137e937f7dd6f499e134f40
7
+ data.tar.gz: d69918fdaf8ab491ac5bc21cd7a838048ef4eaf2299d8a867eea017b14c0f5ca3a314e7106e6fc0bf00fad8e949550efaae897121d8ae11de4d964a1a0231218
data/docs/application.md CHANGED
@@ -158,6 +158,8 @@ Gloo writes to the `gloo.log` and `error.log` files as well as to the console.
158
158
 
159
159
  Debug messages are written to the log only, but other messages are also written to the console unless the application is running in quiet mode.
160
160
 
161
+ On the console, errors and warnings go to stderr, while a script's own output (`show`) goes to stdout. So the two can be redirected separately: `gloo my_script.gloo 2>/dev/null` shows just the script's output.
162
+
161
163
  Only error and warning level messages are written to the error log.
162
164
 
163
165
  In test mode (`--test`), errors and warnings go to the log files only, not the console, so the test output is just the test results. An error a test expects (or one from a test file that deliberately contains a problem) is still in `error.log`. The summary at the end of a test run says how many errors and warnings were logged, and by which tests, leaving out those a test expects (`expect_errors`). See Testing.
data/docs/iterators.md CHANGED
@@ -9,6 +9,7 @@
9
9
  - Each Line
10
10
  - Each Word
11
11
  - Repeat
12
+ - Ending a Loop Early
12
13
 
13
14
  ## Each
14
15
 
@@ -328,3 +329,26 @@ repeat [can] :
328
329
  ```
329
330
 
330
331
  See also: Object Base.
332
+
333
+ ## Ending a Loop Early
334
+
335
+ `break` in a loop's `do` script ends the whole loop, not just that pass. This works for `repeat` and every kind of `each`. The script that ran the loop carries on with its next line:
336
+
337
+ ```gloo
338
+ early [can] :
339
+ on_load [script] :
340
+ tell early.r to run
341
+ show 'after the loop'
342
+ r [repeat] :
343
+ times [int] : 5
344
+ index [int] :
345
+ do [script] :
346
+ if early.r.index = 2 then break
347
+ show early.r.index
348
+ ```
349
+
350
+ This shows `0`, `1`, then `after the loop`.
351
+
352
+ `break` ends the script it's written in. So in nested loops, a `break` in the inner loop's `do` ends only the inner loop, and a `break` in a script the `do` script runs (with `run`, or a function it invokes) ends only that script, not the loop. An `each` with `group_by` still runs `on_group_end` for the group that was open when the loop ended.
353
+
354
+ See also: Break.
@@ -75,7 +75,7 @@ Gloo makes a best guess and keeps going when it can, but it never does so silent
75
75
  - **Warning** — the command was done, but probably not the way it was meant: a value that isn't really of its type, used as a best guess (`'x' is not an integer; using 0.`), or indentation that doesn't line up. A warning is only logged; it isn't an error.
76
76
  - **Just a result** — a question whose answer is "no" is not an error at all: `exists?`, `contains?`, `substring?`, and `index_of` (which gives -1) answer in `it`.
77
77
 
78
- Errors are logged with where they happened in front of the message: the file and line while a file is loading (`app.gloo:12: Unknown type 'strng'; using untyped.`), or the script and its line (`app.on_load, line 3: Object 'app.nme' was not found.`). "Not found" errors always read the same way: `Object 'x' was not found.`, `File 'x' was not found.`, `Folder 'x' was not found.`, `Verb 'x' was not found.`
78
+ Errors and warnings are logged with where they happened in front of the message: the file and line while a file is loading (`app.gloo:12: Unknown type 'strng'; using untyped.`), even when a script started the load; otherwise the script and its line (`app.on_load, line 3: Object 'app.nme' was not found.`). On the console both go to stderr, apart from a script's own output. "Not found" errors always read the same way: `Object 'x' was not found.`, `File 'x' was not found.`, `Folder 'x' was not found.`, `Verb 'x' was not found.`
79
79
 
80
80
  What happens after an error:
81
81
 
data/docs/plugins.md CHANGED
@@ -372,7 +372,7 @@ Register it from `stats_ext.rb` with `callback.register_obj( Stats )`.
372
372
 
373
373
  ### Custom `each` iterators
374
374
 
375
- An extension's object can also plug into the built-in `each` verb by providing its own iterator. `extensions/git/src/each_repo.rb` shows the shape: a plain Ruby class (not a `Gloo::Core::Obj`) with `self.use_for?( iterator_obj )` — which returns `true` when the `each` loop's iterator object looks like a match (here, when it has a `repo` child) — and a `run` method that walks whatever it's iterating over, setting the loop's child value and calling `@iterator_obj.run_do` for each item. See the git extension for the full pattern; this hook only makes sense for extensions that also register a matching object type (here, `git_repo`).
375
+ An extension's object can also plug into the built-in `each` verb by providing its own iterator. `extensions/git/src/each_repo.rb` shows the shape: a plain Ruby class (not a `Gloo::Core::Obj`) with `self.use_for?( iterator_obj )` — which returns `true` when the `each` loop's iterator object looks like a match (here, when it has a `repo` child) — and a `run` method that walks whatever it's iterating over, setting the loop's child value and calling `@iterator_obj.run_do` for each item. `run_do` returns true when a `break` in the `do` script ended it, so stop the walk then: `break if @iterator_obj.run_do`. See the git extension for the full pattern; this hook only makes sense for extensions that also register a matching object type (here, `git_repo`).
376
376
 
377
377
  ### Documenting the extension
378
378
 
data/lib/VERSION CHANGED
@@ -1 +1 @@
1
- 7.1
1
+ 7.1.1
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,10 @@
1
+ 7.1.1 - 2026.10.26
2
+ - Fixes issues with the value in it after a failed command.
3
+ - Warnings go to stderr, like errors.
4
+ - Break in a loop's do script now ends the whole loop.
5
+ - Cipher doc and test updated.
6
+
7
+
1
8
  7.1 - 2026.10.23
2
9
  - Loading an empty gloo file is now allowed and only gives a warning.
3
10
  - Adds configuration setting for tmp directory.
data/lib/gloo/app/log.rb CHANGED
@@ -168,13 +168,13 @@ module Gloo
168
168
 
169
169
  #
170
170
  # Write a warning message to the log.
171
- # Also write to the console unless quiet.
171
+ # Also write to the console (on stderr, like errors) unless quiet.
172
172
  #
173
173
  def warn( msg )
174
174
  @warning_count += 1
175
175
  @logger.warn msg
176
176
  @error.warn msg
177
- puts @theme.warn( msg ) if errors_to_console?
177
+ $stderr.puts @theme.warn( msg ) if errors_to_console?
178
178
  end
179
179
 
180
180
  #
@@ -18,26 +18,39 @@ module Gloo
18
18
  #
19
19
  # Resolve, validate and invoke the function at the given
20
20
  # target path, with the given raw (unevaluated) arg tokens.
21
- # Reports an error and returns nil for any failure - including
22
- # a failure inside the function itself (see Function#invoke,
23
- # which leaves `it` alone rather than setting it to an
24
- # unreliable result when that happens).
21
+ # For any failure, reports an error, puts false in `it` and
22
+ # returns nil - before the call (see failed), or inside the
23
+ # function itself (see Function#invoke).
25
24
  #
26
25
  def self.invoke( engine, target, arg_tokens )
27
26
  if target.nil? || target.to_s.strip.empty?
28
27
  engine.syntax_err NO_TARGET_ERR
29
- return nil
28
+ return failed( engine )
30
29
  end
31
30
 
32
31
  func = resolve_function( engine, target )
33
- return nil unless func
32
+ return failed( engine ) unless func
34
33
 
34
+ # An arg that couldn't be worked out was already reported, but
35
+ # the function's own error tracking would clear it if the call
36
+ # went ahead.
37
+ errors_before = engine.heap.error.error_count
35
38
  args = evaluate_arg_tokens( engine, arg_tokens )
36
- return nil unless params_count_ok?( engine, func, args )
39
+ return failed( engine ) if engine.heap.error.error_count > errors_before
40
+ return failed( engine ) unless params_count_ok?( engine, func, args )
37
41
 
38
42
  return invoke_function( engine, func, args )
39
43
  end
40
44
 
45
+ #
46
+ # The function couldn't be called (the problem was already
47
+ # reported): put false in `it`, and return nil.
48
+ #
49
+ def self.failed( engine )
50
+ engine.heap.it.set_to false
51
+ return nil
52
+ end
53
+
41
54
  #
42
55
  # Resolve the function object at the target path. Reports an
43
56
  # error and returns nil if the path doesn't resolve to
@@ -94,15 +94,26 @@ module Gloo
94
94
  def take_evaluated_arg
95
95
  return true unless @params
96
96
 
97
- errors_before = @engine.heap.error.error_count
98
- value = Gloo::Expr::Expression.new( @engine, @params.tokens ).evaluate
99
- return false if @engine.heap.error.error_count > errors_before
97
+ value, ok = evaluate_expr( @params.tokens )
98
+ return false unless ok
100
99
 
101
100
  @tokens = @tokens.with_token( value.to_s )
102
101
  @params = nil
103
102
  return true
104
103
  end
105
104
 
105
+ #
106
+ # Work out an expression from the given tokens. Returns the value
107
+ # and whether it worked: false if the expression reported a
108
+ # problem (already reported), in which case the value is whatever
109
+ # it had worked out so far.
110
+ #
111
+ def evaluate_expr( tokens )
112
+ errors_before = @engine.heap.error.error_count
113
+ value = Gloo::Expr::Expression.new( @engine, tokens ).evaluate
114
+ return value, @engine.heap.error.error_count == errors_before
115
+ end
116
+
106
117
  #
107
118
  # Run the verb.
108
119
  #
@@ -35,13 +35,16 @@ module Gloo
35
35
  end
36
36
 
37
37
  #
38
- # Where the line now running is, for error messages: the running
39
- # script and its line, or the file and line being loaded.
38
+ # Where the line now running is, for error messages: the file and
39
+ # line being loaded, or the running script and its line. While a
40
+ # file is being read, a problem is in that file, even when a
41
+ # script started the load.
40
42
  #
41
43
  def current_location
44
+ return @load_location if @load_location
42
45
  return @running_script.location if @running_script
43
46
 
44
- return @load_location
47
+ return nil
45
48
  end
46
49
 
47
50
  #
@@ -65,6 +65,13 @@ module Gloo
65
65
  @break_out = true
66
66
  end
67
67
 
68
+ #
69
+ # Did a break end this script's run?
70
+ #
71
+ def broke_out?
72
+ return @break_out
73
+ end
74
+
68
75
  end
69
76
  end
70
77
  end
@@ -115,6 +115,15 @@ module Gloo
115
115
  def msg_run
116
116
  s = Gloo::Exec::Script.new( @engine, self )
117
117
  s.run
118
+ @broke_out = s.broke_out?
119
+ end
120
+
121
+ #
122
+ # Did a break end this script's last run? A loop uses this to
123
+ # stop when its do script breaks.
124
+ #
125
+ def broke_out?
126
+ return @broke_out == true
118
127
  end
119
128
 
120
129
  # ---------------------------------------------------------------------
@@ -43,13 +43,15 @@ module Gloo
43
43
  end
44
44
 
45
45
  #
46
- # Run the do script once.
46
+ # Run the do script once. Returns true if a break in it ended the
47
+ # run, so the loop should stop.
47
48
  #
48
49
  def run_do
49
50
  o = find_child DO
50
- return unless o
51
+ return false unless o
51
52
 
52
53
  Gloo::Exec::Dispatch.message( @engine, 'run', o )
54
+ return o.respond_to?( :broke_out? ) && o.broke_out?
53
55
  end
54
56
 
55
57
 
@@ -130,7 +130,7 @@ module Gloo
130
130
  end
131
131
 
132
132
  set_child child
133
- @iterator_obj.run_do
133
+ break if @iterator_obj.run_do
134
134
  end
135
135
 
136
136
  if group_mode && !first_time
@@ -58,7 +58,7 @@ module Gloo
58
58
  next unless File.directory?( f )
59
59
 
60
60
  set_dir f
61
- @iterator_obj.run_do
61
+ break if @iterator_obj.run_do
62
62
  end
63
63
  end
64
64
 
@@ -60,7 +60,7 @@ module Gloo
60
60
  next unless include?( f )
61
61
 
62
62
  set_file f
63
- @iterator_obj.run_do
63
+ break if @iterator_obj.run_do
64
64
  end
65
65
  end
66
66
 
@@ -48,7 +48,7 @@ module Gloo
48
48
 
49
49
  str.each_line do |line|
50
50
  set_line line
51
- @iterator_obj.run_do
51
+ break if @iterator_obj.run_do
52
52
  end
53
53
  end
54
54
 
@@ -48,7 +48,7 @@ module Gloo
48
48
 
49
49
  str.split( ' ' ).each do |word|
50
50
  set_word word
51
- @iterator_obj.run_do
51
+ break if @iterator_obj.run_do
52
52
  end
53
53
  end
54
54
 
@@ -37,13 +37,15 @@ module Gloo
37
37
  end
38
38
 
39
39
  #
40
- # Run the do script once.
40
+ # Run the do script once. Returns true if a break in it ended the
41
+ # run, so the loop should stop.
41
42
  #
42
43
  def run_do
43
44
  o = find_child DO
44
- return unless o
45
+ return false unless o
45
46
 
46
47
  Gloo::Exec::Dispatch.message( @engine, 'run', o )
48
+ return o.respond_to?( :broke_out? ) && o.broke_out?
47
49
  end
48
50
 
49
51
  #
@@ -98,7 +100,7 @@ module Gloo
98
100
  def msg_run
99
101
  times.times do |index|
100
102
  set_index index
101
- run_do
103
+ break if run_do
102
104
  end
103
105
  end
104
106
 
@@ -209,7 +209,8 @@ module Gloo
209
209
  {
210
210
  :name => KEYWORD,
211
211
  :shortcut => KEYWORD_SHORT,
212
- :description => 'Tool to encrypt and decrypt data.',
212
+ :description => 'Tool to encrypt and decrypt data, with ' \
213
+ 'AES-256-CBC.',
213
214
  :children => [
214
215
  'key (string) — The encryption key. Base64 encoded.',
215
216
  'init_vector (string) — The initialization vector. Base64 encoded.',
@@ -218,8 +219,15 @@ module Gloo
218
219
  :messages => [
219
220
  'generate_keys — Generate an encryption key and initialization vector. The keys are base64 encoded.',
220
221
  'encrypt — Encrypt the data. The data child is changed. It will have the encrypted data.',
221
- 'decrypt — Decrypt the data. The data child is changed. It will have the decrypted data.'
222
+ 'decrypt — Decrypt the data. The data child is changed. It will have the decrypted data. With the wrong key or init_vector, decrypt usually reports an error and it is false; but not always (see notes).'
222
223
  ],
224
+ :notes => 'AES-CBC can\'t always tell that a key is wrong. A ' \
225
+ 'wrong key usually leaves the decrypted data with padding ' \
226
+ 'that doesn\'t check out, which is reported as an error. But ' \
227
+ 'about 1 time in 256 the padding happens to look right: then ' \
228
+ 'decrypt reports nothing, and the data is garbage rather than ' \
229
+ 'the original. To be sure the key is right, keep a value you ' \
230
+ 'can check, such as encrypting a known word alongside the data.',
223
231
  :examples => <<~EXAMPLES.strip
224
232
  #
225
233
  # Encrypt and Decrypt a string.
@@ -14,11 +14,10 @@ module Gloo
14
14
  attr_reader :roots
15
15
 
16
16
  #
17
- # Set up a ledger for the file at pn.
17
+ # Set up a ledger for one file's load.
18
18
  #
19
- def initialize( engine, pn )
19
+ def initialize( engine )
20
20
  @engine = engine
21
- @pn = pn
22
21
  @roots = []
23
22
  @mine = {}.compare_by_identity
24
23
  end
@@ -43,15 +42,16 @@ module Gloo
43
42
  # `value`. If another file created it and the values differ, the
44
43
  # new value is being ignored -- warn so it isn't a surprise. A
45
44
  # duplicate name inside one file is left alone (a documented
46
- # "first one wins").
45
+ # "first one wins"). The warning's location (this file and line)
46
+ # comes from the load.
47
47
  #
48
48
  def clash( prior, name, value )
49
49
  return if @mine.key?( prior )
50
50
  return if value.nil? || value.to_s.strip.empty?
51
51
  return if prior.value.to_s == value.to_s
52
52
 
53
- @engine.log.warn(
54
- "#{@pn}: '#{name}' is already declared in another loaded file with a " \
53
+ @engine.warn(
54
+ "'#{name}' is already declared in another loaded file with a " \
55
55
  "different value (#{prior.value.inspect} kept, #{value.inspect} ignored)."
56
56
  )
57
57
  end
@@ -53,7 +53,7 @@ module Gloo
53
53
  @trivia = Gloo::Persist::TriviaBuffer.new
54
54
  @body = Gloo::Persist::ScriptBodyCollector.new
55
55
  @shorthand = Gloo::Persist::ShorthandExpander.new( engine )
56
- @ledger = Gloo::Persist::DeclarationLedger.new( engine, pn )
56
+ @ledger = Gloo::Persist::DeclarationLedger.new( engine )
57
57
  @in_block = false
58
58
  @block_value = ''
59
59
  @body_started = false
@@ -396,7 +396,7 @@ module Gloo
396
396
  #
397
397
  def warn_if_dirty( fs )
398
398
  saver = Gloo::Persist::FileSaver.new( @engine, fs.pn, fs.obj, fs.source_doc )
399
- @engine.log.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
399
+ @engine.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
400
400
  end
401
401
 
402
402
  end
@@ -51,11 +51,16 @@ module Gloo
51
51
  {
52
52
  :name => KEYWORD,
53
53
  :shortcut => KEYWORD_SHORT,
54
- :description => 'Break out of the current script, stop running.',
54
+ :description => 'Break out of the current script, stop running. ' \
55
+ 'If that script is the do script of a loop (repeat, or any ' \
56
+ 'kind of each), the whole loop ends.',
55
57
  :syntax => [ 'break' ],
56
58
  :result => 'No more commands are executed in the currently ' \
57
59
  'running script. If there are other scripts in the stack, ' \
58
- 'they will resume normally.',
60
+ 'they will resume normally. In a loop\'s do script, the loop ' \
61
+ 'ends too and runs no more passes; the script that ran the ' \
62
+ 'loop carries on. A break in a script the do script runs ' \
63
+ '(eg. with run) ends only that script, not the loop.',
59
64
  :examples => <<~EXAMPLES.strip
60
65
  #
61
66
  # Break out of a script.
@@ -30,9 +30,8 @@ module Gloo
30
30
  #
31
31
  def run
32
32
  if @tokens.token_count > 1
33
- expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
34
- result = expr.evaluate
35
- @engine.heap.it.set_to result
33
+ result, ok = evaluate_expr( @tokens.params )
34
+ @engine.heap.it.set_to( ok ? result : false )
36
35
  else
37
36
  @engine.heap.it.set_to true
38
37
  end
@@ -61,7 +60,9 @@ module Gloo
61
60
  '{expression} — The expression that will be evaluated. The expression is optional. If not provided, eval will result in true.'
62
61
  ],
63
62
  :result => 'The result of the expression evaluation is put ' \
64
- 'into it. No other action is performed.',
63
+ 'into it. No other action is performed. If the expression ' \
64
+ 'fails (eg. it names a missing object), the error is ' \
65
+ 'reported and it is false.',
65
66
  :examples => <<~EXAMPLES.strip,
66
67
  > eval "me"
67
68
  > eval "hello " "world"
@@ -60,7 +60,8 @@ module Gloo
60
60
  '{path.to.function} — The function we want to invoke.',
61
61
  '{params} — The list of parameters to the function.'
62
62
  ],
63
- :result => 'The result of the function is put into it. The ' \
63
+ :result => 'The result of the function is put into it; if ' \
64
+ 'anything goes wrong, it is false. The ' \
64
65
  'inline forms (invoke(...), ~>(...)) can be used anywhere ' \
65
66
  'an expression is evaluated - show, put ... into, if, ' \
66
67
  'unless, eval, and more - and evaluate to the result ' \
@@ -69,13 +70,13 @@ module Gloo
69
70
  "#{Gloo::Core::Invoker::NO_TARGET_ERR} — No function reference was given.",
70
71
  "Object '{path.to.function}' was not found. — The path doesn't resolve to any object.",
71
72
  "#{Gloo::Core::Invoker::NOT_FUNCTION_ERR}{path.to.function} — The path resolves to an object that isn't a function.",
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
+ "#{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.",
74
+ "Object '{param}' was not found. — A param names an object that doesn't exist. The function isn't run."
73
75
  ],
74
76
  :notes => 'If the function itself fails during invocation ' \
75
- '(its on_invoke script hits an error), it is left ' \
76
- 'unchanged rather than being set to an unreliable result. ' \
77
- 'The underlying error is whatever was reported by the ' \
78
- 'failure inside the function. ' \
77
+ '(its on_invoke script hits an error), it is false rather ' \
78
+ 'than an unreliable result. The underlying error is ' \
79
+ 'whatever was reported by the failure inside the function. ' \
79
80
  'Inline call params are space-separated, same as the ' \
80
81
  'standalone verb - each one is evaluated as a single ' \
81
82
  'token (a literal or object reference), not a multi-token ' \
@@ -50,11 +50,12 @@ module Gloo
50
50
  # Write to the specified logger.
51
51
  #
52
52
  def write_to_log
53
- expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
54
- result = expr.evaluate
53
+ # A failed expression is still logged as far as it got, but it
54
+ # is false.
55
+ result, ok = evaluate_expr( @tokens.params )
55
56
  level = log_level_specified( result )
56
57
  @engine.log.write result, level
57
- @engine.heap.it.set_to result
58
+ @engine.heap.it.set_to( ok ? result : false )
58
59
  end
59
60
 
60
61
  #
@@ -106,7 +107,10 @@ module Gloo
106
107
  'not specified, the message is written as DEBUG level.'
107
108
  ],
108
109
  :result => 'The message is written to the log at the ' \
109
- 'specified level. It will contain the log message.',
110
+ 'specified level. It will contain the log message. If the ' \
111
+ 'expression fails, the error is reported and what was ' \
112
+ 'worked out so far is still logged, but it is false. A ' \
113
+ 'level that can\'t be worked out falls back to debug.',
110
114
  :notes => "Use `log clear` from the CLI to clear out both the " \
111
115
  "standard log and the error log.\n\n" \
112
116
  "> log clear",
@@ -16,10 +16,11 @@ module Gloo
16
16
  #
17
17
  def run
18
18
  if @tokens.token_count > 1
19
- expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
20
- result = expr.evaluate
19
+ # A failed expression is still shown as far as it got (a best
20
+ # guess, and handy when debugging), but it is false.
21
+ result, ok = evaluate_expr( @tokens.params )
21
22
  @engine.log.show get_formatted_string( result )
22
- @engine.heap.it.set_to result
23
+ @engine.heap.it.set_to( ok ? result : false )
23
24
  else
24
25
  @engine.log.show ''
25
26
  end
@@ -46,14 +47,16 @@ module Gloo
46
47
  private
47
48
 
48
49
  #
49
- # Get the formatted string.
50
+ # Get the formatted string: in the color given in the params, if
51
+ # any. A color that can't be worked out (already reported) shows
52
+ # the string plain.
50
53
  #
51
54
  def get_formatted_string( str )
52
55
  if @params&.token_count&.positive?
53
- expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
54
- val = expr.evaluate
55
- color = val.to_sym
56
- return @engine.platform.get_colorized_string( str, color )
56
+ val, ok = evaluate_expr( @params.tokens )
57
+ return str unless ok && val
58
+
59
+ return @engine.platform.get_colorized_string( str, val.to_sym )
57
60
  end
58
61
  return str
59
62
  end
@@ -79,11 +82,13 @@ module Gloo
79
82
  '{target} — The object that we want to see. The target ' \
80
83
  'might be the path to an object, a literal or an ' \
81
84
  'expression to be evaluated before being shown.',
82
- '{color} — Optional color for the text.'
85
+ '{color} — Optional color for the text. If the color can\'t be worked out, the error is reported and the text is shown plain.'
83
86
  ],
84
87
  :result => "The object's value is shown, or the literal, or " \
85
88
  'the result of the evaluated expression is shown. It will ' \
86
- "contain the object's value.",
89
+ "contain the object's value. If the expression fails (eg. " \
90
+ 'it names a missing object), the error is reported and ' \
91
+ 'what was worked out so far is still shown, but it is false.',
87
92
  :examples => <<~EXAMPLES.strip,
88
93
  > show "me"
89
94
  > show "hello " "world"
@@ -23,6 +23,18 @@ tests.lang.exceptions [can] :
23
23
  kind [string] :
24
24
  location [string] :
25
25
 
26
+ # A scratch file with a mistake in it, for the location of an error in
27
+ # a loaded file.
28
+ broken_file [file] :
29
+ broken_name [string] : /gloo_exceptions_location.gloo
30
+ broken_src [text] : BEGIN
31
+ broken_location_demo [can] :
32
+ count [int : 3
33
+ END
34
+
35
+ on_load [script] :
36
+ put $.gloo_tmp ^.broken_name into ^.broken_file
37
+
26
38
  confirm_throw_fires_on_exception [test] :
27
39
  description [string] : Confirm throw fires on_exception and populates exception_data, but does not fire on_error.
28
40
  expect_errors [bool] : true
@@ -127,3 +139,13 @@ tests.lang.exceptions [can] :
127
139
  eval ^^.num = 0
128
140
  assert "expected the best guess, 0"
129
141
 
142
+ confirm_error_location_in_a_loaded_file [test] :
143
+ description [string] : an error in a file a script loads names that file and line, not the script.
144
+ expect_errors [bool] : true
145
+ on_test [script] :
146
+ tell ^^.broken_file to write ( ^^.broken_src )
147
+ load ( ^^.broken_file )
148
+ check ^^.error_data.location for ends_with? ( 'gloo_exceptions_location.gloo:2' )
149
+ assert "expected the location to be the loaded file and line"
150
+ tell broken_location_demo to unload
151
+ tell ^^.broken_file to delete
@@ -41,3 +41,30 @@ tests.objs.cipher [can] :
41
41
  tell tests.objs.cipher.b to decrypt
42
42
  eval tests.objs.cipher.b.data = ^^.plaintext
43
43
  assert "expected the second cipher to decrypt with the shared key"
44
+
45
+ # Fixed values, so the results never vary: 'hello' encrypted with key
46
+ # ('k' x 32) and iv ('i' x 16), and a wrong key ('0' x 32) whose
47
+ # garbage fails the padding check. (AES-CBC can't always tell a key is
48
+ # wrong: about 1 wrong key in 256 gives garbage with no error.)
49
+ fixed_iv [string] : aWlpaWlpaWlpaWlpaWlpaQ==
50
+ hello_encrypted [string] : aRyXOIIhJzBhh+BthfFstw==
51
+ wrong_key [string] : MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=
52
+ err_fired [bool] : false
53
+
54
+ on_error [script] :
55
+ put true into ^.err_fired
56
+
57
+ wrong_key_is_an_error [test] :
58
+ description [string] : decrypt with a wrong key that fails the padding check reports an error.
59
+ expect_errors [bool] : true
60
+ on_test [script] :
61
+ put false into ^^.err_fired
62
+ create tests.objs.cipher.w as cipher
63
+ put ^^.wrong_key into tests.objs.cipher.w.key
64
+ put ^^.fixed_iv into tests.objs.cipher.w.init_vector
65
+ put ^^.hello_encrypted into tests.objs.cipher.w.data
66
+ tell tests.objs.cipher.w to decrypt
67
+ eval it = false
68
+ assert "expected it to be false"
69
+ eval ^^.err_fired = true
70
+ assert "expected on_error to fire for the wrong key"
@@ -48,10 +48,13 @@ tests.objs.int [can] :
48
48
  eval ^^.a <= 100
49
49
  assert "expected it to be less than or equal to 100"
50
50
 
51
+ # A random value is only checked for its range: two random values
52
+ # are equal now and then, so 'not equal' would fail at random.
51
53
  tell ^^.a to randomize (10000)
52
- tell ^^.b to randomize (10000)
53
- eval ^^.a = ^^.b
54
- refute "expected them to not be equal"
54
+ eval ^^.a >= 0
55
+ assert "expected it to be greater than or equal to 0"
56
+ eval ^^.a < 10000
57
+ assert "expected it to be less than 10000"
55
58
 
56
59
  format_int [test] :
57
60
  description [string] : Format an integer
@@ -18,3 +18,69 @@ tests.verbs.break [can] :
18
18
  tell ^^.s to run
19
19
  eval ^^.b
20
20
  assert "expected b to be true"
21
+
22
+ # Loops whose do script breaks on its second pass.
23
+ passes [int] : 0
24
+ after [int] : 0
25
+
26
+ rep [repeat] :
27
+ times [int] : 4
28
+ index [int] :
29
+ do [script] :
30
+ put tests.verbs.break.passes + 1 into tests.verbs.break.passes
31
+ if tests.verbs.break.passes = 2 then break
32
+ put tests.verbs.break.after + 1 into tests.verbs.break.after
33
+
34
+ kids [can] :
35
+ a [string] : a
36
+ b [string] : b
37
+ c [string] : c
38
+
39
+ each_kid [each] :
40
+ child [alias] :
41
+ in [alias] : tests.verbs.break.kids
42
+ do [script] :
43
+ put tests.verbs.break.passes + 1 into tests.verbs.break.passes
44
+ if tests.verbs.break.passes = 2 then break
45
+ put tests.verbs.break.after + 1 into tests.verbs.break.after
46
+
47
+ inner [repeat] :
48
+ times [int] : 3
49
+ index [int] :
50
+ do [script] :
51
+ if tests.verbs.break.inner.index = 1 then break
52
+ put tests.verbs.break.after + 1 into tests.verbs.break.after
53
+
54
+ outer [repeat] :
55
+ times [int] : 2
56
+ index [int] :
57
+ do [script] :
58
+ tell tests.verbs.break.inner to run
59
+
60
+ break_ends_a_repeat [test] :
61
+ description [string] : break in a repeat's do script ends the loop.
62
+ on_test [script] :
63
+ put 0 into ^^.passes
64
+ put 0 into ^^.after
65
+ tell ^^.rep to run
66
+ eval ^^.passes = 2
67
+ assert "expected the loop to stop on the pass that broke"
68
+ eval ^^.after = 1
69
+ assert "expected only the first pass to get past the break"
70
+
71
+ break_ends_an_each [test] :
72
+ description [string] : break in an each's do script ends the loop.
73
+ on_test [script] :
74
+ put 0 into ^^.passes
75
+ put 0 into ^^.after
76
+ tell ^^.each_kid to run
77
+ eval ^^.passes = 2
78
+ assert "expected the loop to stop on the pass that broke"
79
+
80
+ break_ends_only_the_inner_loop [test] :
81
+ description [string] : break in an inner loop ends that loop, not the outer one.
82
+ on_test [script] :
83
+ put 0 into ^^.after
84
+ tell ^^.outer to run
85
+ eval ^^.after = 2
86
+ assert "expected each of the 2 inner loops to get past its break once"
@@ -15,3 +15,11 @@ tests.verbs.eval [can] :
15
15
 
16
16
  eval false
17
17
  refute "expected false to be false"
18
+
19
+ failed_expression_puts_false_in_it [test] :
20
+ description [string] : eval of an expression that fails puts false in it.
21
+ expect_errors [bool] : true
22
+ on_test [script] :
23
+ eval 1 + no_such_obj_xyz
24
+ eval it = false
25
+ assert "expected it to be false after a failed expression"
@@ -90,3 +90,53 @@ tests.verbs.invoke [can] :
90
90
  show invoke( tests.verbs.invoke.greet "Bob Smith" )
91
91
  eval it = "Hi, Bob Smith"
92
92
  assert "expected it to be 'Hi, Bob Smith'"
93
+
94
+ wrong_param_count_sets_it_false [test] :
95
+ description [string] : invoke with the wrong number of params puts false in it.
96
+ expect_errors [bool] : true
97
+ on_test [script] :
98
+ eval 'Hi'
99
+ invoke tests.verbs.invoke.add 1
100
+ eval it = false
101
+ assert "expected it to be false after a failed invoke"
102
+
103
+ shortcut_wrong_param_count_sets_it_false [test] :
104
+ description [string] : ~> with the wrong number of params puts false in it.
105
+ expect_errors [bool] : true
106
+ on_test [script] :
107
+ eval 'Hi'
108
+ ~> tests.verbs.invoke.add 1
109
+ eval it = false
110
+ assert "expected it to be false after a failed ~>"
111
+
112
+ not_a_function_sets_it_false [test] :
113
+ description [string] : invoke of something that isn't a function puts false in it.
114
+ expect_errors [bool] : true
115
+ on_test [script] :
116
+ eval 'Hi'
117
+ invoke tests.verbs.invoke.total
118
+ eval it = false
119
+ assert "expected it to be false after invoking a non-function"
120
+
121
+ missing_arg_object_is_an_error [test] :
122
+ description [string] : an argument naming a missing object fires on_error and puts false in it.
123
+ expect_errors [bool] : true
124
+ on_test [script] :
125
+ put false into ^^.err_fired
126
+ eval 'Hi'
127
+ invoke tests.verbs.invoke.add no_such_obj_xyz 4
128
+ eval it = false
129
+ assert "expected it to be false"
130
+ eval ^^.err_fired = true
131
+ assert "expected on_error to fire for the missing argument"
132
+
133
+ inline_failure_in_put_sets_it_false [test] :
134
+ description [string] : a failed invoke( ... ) inside put puts false in it.
135
+ expect_errors [bool] : true
136
+ on_test [script] :
137
+ put 5 into ^^.total
138
+ put invoke( tests.verbs.invoke.add 1 ) into ^^.total
139
+ eval it = false
140
+ assert "expected it to be false"
141
+ eval ^^.total = 5
142
+ assert "expected the target to keep its value"
@@ -10,3 +10,19 @@ tests.verbs.log [can] :
10
10
  log 'debug' (debug)
11
11
  eval it = 'debug'
12
12
  assert "expected it to be 'debug'"
13
+
14
+ failed_expression_puts_false_in_it [test] :
15
+ description [string] : log of an expression that fails puts false in it.
16
+ expect_errors [bool] : true
17
+ on_test [script] :
18
+ log 1 + no_such_obj_xyz
19
+ eval it = false
20
+ assert "expected it to be false after a failed expression"
21
+
22
+ failed_level_puts_the_value_in_it [test] :
23
+ description [string] : log with a level that fails still puts the value in it.
24
+ expect_errors [bool] : true
25
+ on_test [script] :
26
+ log 'plain' (no_such_obj_xyz)
27
+ eval it = 'plain'
28
+ assert "expected the value in it when only the level fails"
@@ -33,3 +33,35 @@ tests.verbs.show [can] :
33
33
  show 'a' + 'b'
34
34
  eval it = 'ab'
35
35
  assert "expected it to be 'ab'"
36
+
37
+ add [ƒ] :
38
+ on_invoke [script] :
39
+ put ^.params.x + ^.params.y into ^.result
40
+ params [can] :
41
+ x [int] :
42
+ y [int] :
43
+ result [int] :
44
+
45
+ failed_expression_puts_false_in_it [test] :
46
+ description [string] : show of an expression that fails puts false in it.
47
+ expect_errors [bool] : true
48
+ on_test [script] :
49
+ show 1 + no_such_obj_xyz
50
+ eval it = false
51
+ assert "expected it to be false after a failed expression"
52
+
53
+ failed_inline_invoke_puts_false_in_it [test] :
54
+ description [string] : show of an inline invoke that fails puts false in it.
55
+ expect_errors [bool] : true
56
+ on_test [script] :
57
+ show invoke( tests.verbs.show.add 1 )
58
+ eval it = false
59
+ assert "expected it to be false after a failed inline invoke"
60
+
61
+ failed_color_puts_the_value_in_it [test] :
62
+ description [string] : show with a color that fails still puts the value in it.
63
+ expect_errors [bool] : true
64
+ on_test [script] :
65
+ show 'plain' (no_such_obj_xyz)
66
+ eval it = 'plain'
67
+ assert "expected the value in it when only the color fails"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloo
3
3
  version: !ruby/object:Gem::Version
4
- version: '7.1'
4
+ version: 7.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Crane