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.
- checksums.yaml +4 -4
- data/CLAUDE.md +1 -0
- data/docs/application.md +3 -1
- data/docs/iterators.md +39 -3
- data/docs/language_syntax.md +53 -5
- data/docs/objects.md +58 -1
- data/docs/operators.md +16 -0
- data/docs/plugins.md +9 -1
- data/docs/verbs.md +4 -2
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +20 -0
- data/lib/gloo/app/engine.rb +64 -8
- data/lib/gloo/app/log.rb +47 -6
- data/lib/gloo/convert/converter.rb +63 -2
- data/lib/gloo/convert/string_to_date.rb +14 -0
- data/lib/gloo/convert/string_to_datetime.rb +16 -0
- data/lib/gloo/convert/string_to_decimal.rb +13 -0
- data/lib/gloo/convert/string_to_integer.rb +9 -2
- data/lib/gloo/convert/string_to_time.rb +28 -0
- data/lib/gloo/core/error.rb +11 -2
- data/lib/gloo/core/event_manager.rb +7 -4
- data/lib/gloo/core/factory.rb +16 -11
- data/lib/gloo/core/here.rb +6 -1
- data/lib/gloo/core/invoker.rb +2 -3
- data/lib/gloo/core/not_found.rb +42 -0
- data/lib/gloo/core/obj.rb +28 -2
- data/lib/gloo/core/op.rb +2 -0
- data/lib/gloo/core/parser.rb +1 -1
- data/lib/gloo/core/pn.rb +33 -12
- data/lib/gloo/core/tokens.rb +38 -0
- data/lib/gloo/core/verb.rb +54 -0
- data/lib/gloo/exec/dispatch.rb +5 -6
- data/lib/gloo/exec/exec_env.rb +13 -0
- data/lib/gloo/exec/runner.rb +10 -2
- data/lib/gloo/exec/script.rb +13 -1
- data/lib/gloo/expr/expression.rb +39 -1
- data/lib/gloo/expr/op_plus.rb +3 -0
- data/lib/gloo/objs/basic/boolean.rb +16 -0
- data/lib/gloo/objs/basic/container.rb +154 -6
- data/lib/gloo/objs/basic/decimal.rb +1 -0
- data/lib/gloo/objs/basic/integer.rb +1 -0
- data/lib/gloo/objs/basic/string.rb +0 -1
- data/lib/gloo/objs/basic/string_msgs.rb +68 -32
- data/lib/gloo/objs/ctrl/each.rb +22 -3
- data/lib/gloo/objs/ctrl/each_dir.rb +20 -7
- data/lib/gloo/objs/ctrl/each_file.rb +78 -9
- data/lib/gloo/objs/ctrl/function.rb +6 -5
- data/lib/gloo/objs/dt/date.rb +28 -11
- data/lib/gloo/objs/dt/datetime.rb +72 -21
- data/lib/gloo/objs/dt/dt_tools.rb +46 -2
- data/lib/gloo/objs/dt/time.rb +28 -11
- data/lib/gloo/objs/str_utils/cipher.rb +17 -4
- data/lib/gloo/objs/str_utils/password.rb +6 -2
- data/lib/gloo/objs/system/erb.rb +9 -3
- data/lib/gloo/objs/system/file_handle.rb +57 -21
- data/lib/gloo/objs/system/system.rb +6 -2
- data/lib/gloo/objs/web/http_get.rb +12 -2
- data/lib/gloo/objs/web/http_post.rb +6 -2
- data/lib/gloo/objs/web/json.rb +38 -18
- data/lib/gloo/objs/web/uri.rb +36 -16
- data/lib/gloo/persist/file_loader.rb +72 -11
- data/lib/gloo/persist/file_saver.rb +2 -1
- data/lib/gloo/persist/indent_stack.rb +49 -33
- data/lib/gloo/persist/line_splitter.rb +40 -3
- data/lib/gloo/persist/persist_man.rb +2 -4
- data/lib/gloo/plugin/ext_manager.rb +6 -9
- data/lib/gloo/plugin/lib_manager.rb +2 -2
- data/lib/gloo/verbs/break.rb +1 -0
- data/lib/gloo/verbs/check.rb +1 -1
- data/lib/gloo/verbs/cls.rb +1 -0
- data/lib/gloo/verbs/create.rb +32 -4
- data/lib/gloo/verbs/execute.rb +1 -1
- data/lib/gloo/verbs/exists.rb +27 -15
- data/lib/gloo/verbs/files.rb +2 -1
- data/lib/gloo/verbs/help.rb +1 -0
- data/lib/gloo/verbs/if.rb +1 -1
- data/lib/gloo/verbs/invoke.rb +1 -1
- data/lib/gloo/verbs/list.rb +4 -3
- data/lib/gloo/verbs/load.rb +3 -3
- data/lib/gloo/verbs/move.rb +10 -8
- data/lib/gloo/verbs/put.rb +20 -8
- data/lib/gloo/verbs/quit.rb +1 -0
- data/lib/gloo/verbs/redirect.rb +8 -2
- data/lib/gloo/verbs/reload.rb +9 -1
- data/lib/gloo/verbs/run.rb +2 -2
- data/lib/gloo/verbs/save.rb +4 -2
- data/lib/gloo/verbs/tell.rb +1 -1
- data/lib/gloo/verbs/unless.rb +1 -1
- data/lib/gloo/verbs/unload.rb +8 -0
- data/test.gloo/ctrl/each.test.gloo +88 -0
- data/test.gloo/lang/declaration.test.gloo +27 -0
- data/test.gloo/lang/exceptions.test.gloo +60 -0
- data/test.gloo/lang/failures.test.gloo +139 -0
- data/test.gloo/lang/it.test.gloo +133 -0
- data/test.gloo/lang/naming.test.gloo +11 -0
- data/test.gloo/lang/ops.test.gloo +30 -0
- data/test.gloo/objs/can.test.gloo +101 -0
- data/test.gloo/objs/string.test.gloo +24 -0
- data/test.gloo/string/str.test.gloo +28 -0
- data/test.gloo/verbs/create.test.gloo +36 -0
- data/test.gloo/verbs/exists.test.gloo +33 -1
- data/test.gloo/verbs/unload.test.gloo +34 -0
- metadata +4 -1
data/lib/gloo/core/verb.rb
CHANGED
|
@@ -29,6 +29,60 @@ module Gloo
|
|
|
29
29
|
Dictionary.instance.register_verb( subclass )
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
#
|
|
33
|
+
# Report any syntax problem in the command's tokens or params
|
|
34
|
+
# (see Tokens#syntax_problem) as a syntax error. The verb still
|
|
35
|
+
# runs afterward, with the tokens' best guess.
|
|
36
|
+
#
|
|
37
|
+
def check_syntax
|
|
38
|
+
[ @tokens, @params ].each do |t|
|
|
39
|
+
problem = t.syntax_problem if t.respond_to?( :syntax_problem )
|
|
40
|
+
@engine.syntax_err problem if problem
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# For a verb that takes no object: whatever was written after it,
|
|
46
|
+
# as one string (empty if nothing was).
|
|
47
|
+
#
|
|
48
|
+
def extra_words
|
|
49
|
+
words = @tokens.respond_to?( :params ) ? @tokens.params.to_a : []
|
|
50
|
+
words += @params.tokens.to_a if @params.respond_to?( :tokens )
|
|
51
|
+
return words.join( ' ' )
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# The verb as written in the command (keyword or shortcut).
|
|
56
|
+
#
|
|
57
|
+
def written_verb
|
|
58
|
+
written = @tokens.verb if @tokens.respond_to?( :verb )
|
|
59
|
+
return written || self.class.keyword
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# For a harmless verb that takes no object: warn if anything was
|
|
64
|
+
# written after it. The verb still runs.
|
|
65
|
+
#
|
|
66
|
+
def warn_extra_words
|
|
67
|
+
extra = extra_words
|
|
68
|
+
return if extra.empty?
|
|
69
|
+
|
|
70
|
+
@engine.warn "#{written_verb} takes no object; ignoring '#{extra}'."
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# For a destructive verb that takes no object: if anything was
|
|
75
|
+
# written after it, report an error and return true; the verb
|
|
76
|
+
# should not run. The detail says what didn't happen and what to
|
|
77
|
+
# do instead.
|
|
78
|
+
#
|
|
79
|
+
def extra_words_err?( detail )
|
|
80
|
+
return false if extra_words.empty?
|
|
81
|
+
|
|
82
|
+
@engine.err "#{written_verb} takes no object, so #{detail}."
|
|
83
|
+
return true
|
|
84
|
+
end
|
|
85
|
+
|
|
32
86
|
#
|
|
33
87
|
# Run the verb.
|
|
34
88
|
#
|
data/lib/gloo/exec/dispatch.rb
CHANGED
|
@@ -12,7 +12,6 @@ module Gloo
|
|
|
12
12
|
module Exec
|
|
13
13
|
class Dispatch
|
|
14
14
|
|
|
15
|
-
OBJ_NOT_FOUND_ERR = 'Object was not found: '.freeze
|
|
16
15
|
|
|
17
16
|
#
|
|
18
17
|
# Send a message to an object of a given name (and path).
|
|
@@ -20,10 +19,14 @@ module Gloo
|
|
|
20
19
|
#
|
|
21
20
|
def self.send_message( engine, msg, to_obj_pn, params = nil )
|
|
22
21
|
pn = Gloo::Core::Pn.new( engine, to_obj_pn )
|
|
22
|
+
return if pn.it_target_err?( 'sent messages' )
|
|
23
|
+
|
|
23
24
|
target_obj = pn.resolve
|
|
24
25
|
|
|
25
26
|
unless target_obj
|
|
26
|
-
|
|
27
|
+
# The message never ran, so there is no result.
|
|
28
|
+
engine.err Gloo::Core::NotFound.object( to_obj_pn )
|
|
29
|
+
engine.heap.it.set_to false
|
|
27
30
|
return
|
|
28
31
|
end
|
|
29
32
|
Gloo::Exec::Dispatch.message( engine, msg, target_obj, params )
|
|
@@ -44,10 +47,6 @@ module Gloo
|
|
|
44
47
|
# Dispatch an action.
|
|
45
48
|
#
|
|
46
49
|
def self.action( engine, action )
|
|
47
|
-
unless action.valid?
|
|
48
|
-
engine.log.warn "Object #{action.to.name} does not respond to #{action.msg}"
|
|
49
|
-
end
|
|
50
|
-
|
|
51
50
|
engine.exec_env.push_action action
|
|
52
51
|
engine.log.debug "Sending message #{action.msg} to #{action.to.name}"
|
|
53
52
|
action.dispatch
|
data/lib/gloo/exec/exec_env.rb
CHANGED
|
@@ -12,6 +12,9 @@ module Gloo
|
|
|
12
12
|
attr_accessor :verbs, :actions, :scripts, :here
|
|
13
13
|
attr_reader :running_script
|
|
14
14
|
|
|
15
|
+
# The file and line being loaded, if a file is being loaded.
|
|
16
|
+
attr_accessor :load_location
|
|
17
|
+
|
|
15
18
|
VERB_STACK = 'verbs'.freeze
|
|
16
19
|
ACTION_STACK = 'actions'.freeze
|
|
17
20
|
SCRIPT_STACK = 'scripts'.freeze
|
|
@@ -31,6 +34,16 @@ module Gloo
|
|
|
31
34
|
@here = Gloo::Exec::Stack.new( @engine, HERE_STACK )
|
|
32
35
|
end
|
|
33
36
|
|
|
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.
|
|
40
|
+
#
|
|
41
|
+
def current_location
|
|
42
|
+
return @running_script.location if @running_script
|
|
43
|
+
|
|
44
|
+
return @load_location
|
|
45
|
+
end
|
|
46
|
+
|
|
34
47
|
#
|
|
35
48
|
# Get the here object.
|
|
36
49
|
#
|
data/lib/gloo/exec/runner.rb
CHANGED
|
@@ -20,8 +20,12 @@ module Gloo
|
|
|
20
20
|
engine.heap.error.start_tracking
|
|
21
21
|
engine.exec_env.verbs.push verb
|
|
22
22
|
begin
|
|
23
|
+
verb&.check_syntax
|
|
23
24
|
verb&.run
|
|
24
|
-
rescue => ex
|
|
25
|
+
rescue StandardError, ScriptError => ex
|
|
26
|
+
# ScriptError (a SyntaxError from eval'd code, a LoadError
|
|
27
|
+
# from a missing gem) isn't a StandardError, and would
|
|
28
|
+
# otherwise stop gloo altogether.
|
|
25
29
|
engine.handle_exception( ex )
|
|
26
30
|
ensure
|
|
27
31
|
engine.exec_env.verbs.pop
|
|
@@ -36,12 +40,16 @@ module Gloo
|
|
|
36
40
|
def self.run( engine, path_name )
|
|
37
41
|
engine.log.debug "running script at #{path_name}"
|
|
38
42
|
pn = Gloo::Core::Pn.new( engine, path_name )
|
|
43
|
+
return if pn.it_target_err?( 'run' )
|
|
44
|
+
|
|
39
45
|
o = pn.resolve
|
|
40
46
|
|
|
41
47
|
if o
|
|
42
48
|
o.send_message 'run'
|
|
43
49
|
else
|
|
44
|
-
|
|
50
|
+
# The message never ran, so there is no result.
|
|
51
|
+
engine.err Gloo::Core::NotFound.object( path_name )
|
|
52
|
+
engine.heap.it.set_to false
|
|
45
53
|
end
|
|
46
54
|
end
|
|
47
55
|
|
data/lib/gloo/exec/script.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Gloo
|
|
|
16
16
|
@engine = engine
|
|
17
17
|
@obj = obj
|
|
18
18
|
@break_out = false
|
|
19
|
+
@line = 0
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
#
|
|
@@ -27,10 +28,13 @@ module Gloo
|
|
|
27
28
|
@engine.exec_env.push_script self
|
|
28
29
|
|
|
29
30
|
if @obj.value.is_a? String
|
|
31
|
+
@line = 1
|
|
30
32
|
@engine.parser.run @obj.value
|
|
31
33
|
elsif @obj.value.is_a? Array
|
|
32
|
-
@obj.value.
|
|
34
|
+
@obj.value.each_with_index do |line, i|
|
|
33
35
|
break if @break_out
|
|
36
|
+
|
|
37
|
+
@line = i + 1
|
|
34
38
|
@engine.parser.run line
|
|
35
39
|
end
|
|
36
40
|
end
|
|
@@ -38,6 +42,14 @@ module Gloo
|
|
|
38
42
|
@engine.exec_env.pop_script
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
#
|
|
46
|
+
# Where this script is in its run: its path and the line now
|
|
47
|
+
# running (counting from 1), for error messages.
|
|
48
|
+
#
|
|
49
|
+
def location
|
|
50
|
+
return "#{@obj.pn}, line #{@line}"
|
|
51
|
+
end
|
|
52
|
+
|
|
41
53
|
#
|
|
42
54
|
# Generic function to get display value.
|
|
43
55
|
# Can be used for debugging, etc.
|
data/lib/gloo/expr/expression.rb
CHANGED
|
@@ -8,6 +8,8 @@ module Gloo
|
|
|
8
8
|
module Expr
|
|
9
9
|
class Expression
|
|
10
10
|
|
|
11
|
+
DIVIDE_BY_ZERO_ERR = 'Division by zero.'.freeze
|
|
12
|
+
|
|
11
13
|
# ---------------------------------------------------------------------
|
|
12
14
|
# Constructor
|
|
13
15
|
# ---------------------------------------------------------------------
|
|
@@ -36,6 +38,7 @@ module Gloo
|
|
|
36
38
|
|
|
37
39
|
@symbols.each do |sym|
|
|
38
40
|
if sym.is_a? Gloo::Core::Op
|
|
41
|
+
missing_value( @op ) if @op
|
|
39
42
|
@op = sym
|
|
40
43
|
elsif @left.nil?
|
|
41
44
|
@left = sym
|
|
@@ -45,6 +48,7 @@ module Gloo
|
|
|
45
48
|
|
|
46
49
|
perform_op if @left && @right
|
|
47
50
|
end
|
|
51
|
+
missing_value( @op ) if @op
|
|
48
52
|
|
|
49
53
|
return @left.value if @left.is_a? Gloo::Core::Literal
|
|
50
54
|
return @left.value if @left.is_a? Gloo::Expr::Call
|
|
@@ -66,11 +70,39 @@ module Gloo
|
|
|
66
70
|
@op ||= Gloo::Core::Op.default_op
|
|
67
71
|
l = evaluate_sym @left
|
|
68
72
|
r = evaluate_sym @right
|
|
69
|
-
@left = @op
|
|
73
|
+
@left = apply_op( @op, l, r )
|
|
70
74
|
@right = nil
|
|
71
75
|
@op = nil
|
|
72
76
|
end
|
|
73
77
|
|
|
78
|
+
#
|
|
79
|
+
# Apply the operator. An operator that can't be used with the value
|
|
80
|
+
# (eg. 'a' / 2), or a division by zero, is a runtime error, and the
|
|
81
|
+
# result is no value. A missing value (nil) was already reported.
|
|
82
|
+
#
|
|
83
|
+
def apply_op( op, left, right )
|
|
84
|
+
result = op.perform( left, right )
|
|
85
|
+
if op.is_a?( Gloo::Expr::OpDiv ) && result.is_a?( Float ) &&
|
|
86
|
+
( result.nan? || result.infinite? )
|
|
87
|
+
raise ZeroDivisionError
|
|
88
|
+
end
|
|
89
|
+
return result unless result.nil? && !left.nil?
|
|
90
|
+
|
|
91
|
+
@engine.err "Operator '#{op.class::SYMBOL}' can't be used with '#{left}'."
|
|
92
|
+
return nil
|
|
93
|
+
rescue ZeroDivisionError
|
|
94
|
+
@engine.err DIVIDE_BY_ZERO_ERR
|
|
95
|
+
return nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# An operator without a value on one side (eg. 'show 1 +') is a
|
|
100
|
+
# syntax error; the best guess is to leave the operator out.
|
|
101
|
+
#
|
|
102
|
+
def missing_value( op )
|
|
103
|
+
@engine.syntax_err "Operator '#{op.class::SYMBOL}' is missing a value."
|
|
104
|
+
end
|
|
105
|
+
|
|
74
106
|
#
|
|
75
107
|
# Evaluate the symbol and get a simple value.
|
|
76
108
|
#
|
|
@@ -88,8 +120,14 @@ module Gloo
|
|
|
88
120
|
def resolve_ref( ref )
|
|
89
121
|
return ref.src if ref.named_color?
|
|
90
122
|
|
|
123
|
+
name = ref.src
|
|
91
124
|
ob = ref.resolve
|
|
92
125
|
return ob.value if ob
|
|
126
|
+
|
|
127
|
+
# The object is being used as a value, so a missing object is
|
|
128
|
+
# a runtime error (the lookup itself doesn't report it).
|
|
129
|
+
@engine.err Gloo::Core::NotFound.object( name )
|
|
130
|
+
return nil
|
|
93
131
|
end
|
|
94
132
|
|
|
95
133
|
#
|
data/lib/gloo/expr/op_plus.rb
CHANGED
|
@@ -9,6 +9,9 @@ module Gloo
|
|
|
9
9
|
class OpPlus < Gloo::Core::Op
|
|
10
10
|
|
|
11
11
|
SYMBOL = '+'.freeze
|
|
12
|
+
# An alternate spelling that reads better when joining strings:
|
|
13
|
+
# put first and ' ' and last into full_name
|
|
14
|
+
ALT_SYMBOL = 'and'.freeze
|
|
12
15
|
|
|
13
16
|
#
|
|
14
17
|
# Perform the operation and return the result.
|
|
@@ -12,6 +12,7 @@ module Gloo
|
|
|
12
12
|
KEYWORD_SHORT = 'bool'.freeze
|
|
13
13
|
TRUE = 'true'.freeze
|
|
14
14
|
FALSE = 'false'.freeze
|
|
15
|
+
BOOLEAN_STRINGS = [ TRUE, FALSE, 't', 'f' ].freeze
|
|
15
16
|
|
|
16
17
|
#
|
|
17
18
|
# The name of the object type.
|
|
@@ -32,6 +33,20 @@ module Gloo
|
|
|
32
33
|
#
|
|
33
34
|
def set_value( new_value )
|
|
34
35
|
self.value = Gloo::Objs::Boolean.coerse_to_bool( new_value )
|
|
36
|
+
return unless Gloo::Objs::Boolean.bad_value?( new_value )
|
|
37
|
+
|
|
38
|
+
@engine.warn "'#{new_value}' is not true or false; using #{self.value}."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Is the value a string that isn't a boolean? A blank value is
|
|
43
|
+
# no value (false).
|
|
44
|
+
#
|
|
45
|
+
def self.bad_value?( new_value )
|
|
46
|
+
return false unless new_value.is_a?( ::String )
|
|
47
|
+
return false if new_value.strip.empty?
|
|
48
|
+
|
|
49
|
+
return !BOOLEAN_STRINGS.include?( new_value.strip.downcase )
|
|
35
50
|
end
|
|
36
51
|
|
|
37
52
|
#
|
|
@@ -132,6 +147,7 @@ module Gloo
|
|
|
132
147
|
'true — Set the boolean to true.',
|
|
133
148
|
'false — Set the boolean to false.'
|
|
134
149
|
],
|
|
150
|
+
:notes => 'Recognized values are true, false, t and f (in any case), and integers (0 is false). Any other string is false, with a warning: "\'maybe\' is not true or false; using false." A blank value is false.',
|
|
135
151
|
:examples => <<~EXAMPLES.strip
|
|
136
152
|
b [can] :
|
|
137
153
|
flag [boolean] : true
|
|
@@ -10,6 +10,8 @@ module Gloo
|
|
|
10
10
|
|
|
11
11
|
KEYWORD = 'container'.freeze
|
|
12
12
|
KEYWORD_SHORT = 'can'.freeze
|
|
13
|
+
MISSING_INDEX_MSG = 'Missing index!'.freeze
|
|
14
|
+
MISSING_NAME_MSG = 'Missing child name!'.freeze
|
|
13
15
|
|
|
14
16
|
#
|
|
15
17
|
# The name of the object type.
|
|
@@ -33,7 +35,8 @@ module Gloo
|
|
|
33
35
|
# Get a list of message names that this object receives.
|
|
34
36
|
#
|
|
35
37
|
def self.messages
|
|
36
|
-
return super + %w[count delete_children child_exists show_key_value_table
|
|
38
|
+
return super + %w[count delete_children child_exists show_key_value_table
|
|
39
|
+
child_value_at child_path_at random_child_value random_child_path]
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
#
|
|
@@ -65,17 +68,138 @@ module Gloo
|
|
|
65
68
|
# Check to see if there is a child with the given name.
|
|
66
69
|
#
|
|
67
70
|
def msg_child_exists
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
unless @params&.token_count&.positive?
|
|
72
|
+
@engine.syntax_err MISSING_NAME_MSG
|
|
73
|
+
return put_it( false )
|
|
71
74
|
end
|
|
72
|
-
|
|
75
|
+
|
|
76
|
+
expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
|
|
77
|
+
data = expr.evaluate
|
|
78
|
+
return put_it( false ) if data.nil?
|
|
73
79
|
|
|
74
80
|
val = self.contains_child?( data )
|
|
75
81
|
@engine.heap.it.set_to val
|
|
76
82
|
return val
|
|
77
83
|
end
|
|
78
84
|
|
|
85
|
+
#
|
|
86
|
+
# Get the value of the child at the given 0-based index.
|
|
87
|
+
#
|
|
88
|
+
def msg_child_value_at
|
|
89
|
+
child = child_at_param
|
|
90
|
+
return child if child == false
|
|
91
|
+
|
|
92
|
+
return put_child_value( child )
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Get the path of the child at the given 0-based index.
|
|
97
|
+
#
|
|
98
|
+
def msg_child_path_at
|
|
99
|
+
child = child_at_param
|
|
100
|
+
return child if child == false
|
|
101
|
+
|
|
102
|
+
return put_it( child.pn )
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Get the value of a random child.
|
|
107
|
+
#
|
|
108
|
+
def msg_random_child_value
|
|
109
|
+
child = random_child
|
|
110
|
+
return child if child == false
|
|
111
|
+
|
|
112
|
+
return put_child_value( child )
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
#
|
|
116
|
+
# Get the path of a random child.
|
|
117
|
+
#
|
|
118
|
+
def msg_random_child_path
|
|
119
|
+
child = random_child
|
|
120
|
+
return child if child == false
|
|
121
|
+
|
|
122
|
+
return put_it( child.pn )
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------
|
|
126
|
+
# Child Access Helpers
|
|
127
|
+
# ---------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
#
|
|
130
|
+
# Find the child at the index given as the message parameter.
|
|
131
|
+
# Reports the problem, puts false into it, and returns false
|
|
132
|
+
# if there is no index or no child at that index.
|
|
133
|
+
#
|
|
134
|
+
def child_at_param
|
|
135
|
+
unless @params&.token_count&.positive?
|
|
136
|
+
@engine.syntax_err MISSING_INDEX_MSG
|
|
137
|
+
return put_it( false )
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
|
|
141
|
+
data = expr.evaluate
|
|
142
|
+
index = Container.to_index( data )
|
|
143
|
+
if index.nil?
|
|
144
|
+
@engine.err "Index '#{data}' is not a number!"
|
|
145
|
+
return put_it( false )
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
if index.negative? || index >= child_count
|
|
149
|
+
@engine.err "Index #{index} is out of range (0 to #{child_count - 1})!"
|
|
150
|
+
return put_it( false )
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
return children[ index ]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
#
|
|
157
|
+
# Get a whole number index from an evaluated parameter.
|
|
158
|
+
# Returns nil if it isn't one.
|
|
159
|
+
#
|
|
160
|
+
def self.to_index( data )
|
|
161
|
+
return data if data.is_a?( ::Integer )
|
|
162
|
+
return data.strip.to_i if data.is_a?( ::String ) && data.strip.match?( /\A-?\d+\z/ )
|
|
163
|
+
|
|
164
|
+
return nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
#
|
|
168
|
+
# Pick a random child.
|
|
169
|
+
# Reports the problem, puts false into it, and returns false
|
|
170
|
+
# if the container is empty.
|
|
171
|
+
#
|
|
172
|
+
def random_child
|
|
173
|
+
if child_count.zero?
|
|
174
|
+
@engine.err "Container '#{pn}' is empty!"
|
|
175
|
+
return put_it( false )
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
return children.sample
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
#
|
|
182
|
+
# Put the value of the child into it.
|
|
183
|
+
# A container child has no value of its own; use its path instead.
|
|
184
|
+
#
|
|
185
|
+
def put_child_value( child )
|
|
186
|
+
child = Gloo::Objs::Alias.resolve_alias( @engine, child )
|
|
187
|
+
if child.is_container?
|
|
188
|
+
@engine.err "Child '#{child.pn}' is a container, not a value! Use its path instead."
|
|
189
|
+
return put_it( false )
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
return put_it( child.value )
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
#
|
|
196
|
+
# Put the value into it and return it.
|
|
197
|
+
#
|
|
198
|
+
def put_it( val )
|
|
199
|
+
@engine.heap.it.set_to val
|
|
200
|
+
return val
|
|
201
|
+
end
|
|
202
|
+
|
|
79
203
|
# ---------------------------------------------------------------------
|
|
80
204
|
# Object Documentation
|
|
81
205
|
# ---------------------------------------------------------------------
|
|
@@ -99,8 +223,17 @@ module Gloo
|
|
|
99
223
|
'count — Count the number of children objects in the container. The result is put in it.',
|
|
100
224
|
'delete_children — Delete all children objects from the container.',
|
|
101
225
|
'show_key_value_table — Show a table with key (name) and values for all children in the container.',
|
|
102
|
-
'child_exists ({name}) — Check to see if there is a child with the given name. A parameter is required. It will have a boolean.'
|
|
226
|
+
'child_exists ({name}) — Check to see if there is a child with the given name. A parameter is required. It will have a boolean.',
|
|
227
|
+
'child_value_at ({index}) — Get the value of the child at position {index}. Positions are 0-based and in the order the children were added, so after split_list, index 0 is the child named 1. A parameter is required. It will have the value; an out-of-range or non-numeric index, or a child that is a container, is an error, and it will have false.',
|
|
228
|
+
'child_path_at ({index}) — Get the path (from root) of the child at position {index}, 0-based. Works for any child, including a container; put it into an alias (put it into ptr*) to reach the child and its fields. A parameter is required. It will have the path; an out-of-range or non-numeric index is an error, and it will have false.',
|
|
229
|
+
'random_child_value — Get the value of a randomly chosen child. It will have the value; an empty container, or picking a child that is a container, is an error, and it will have false.',
|
|
230
|
+
'random_child_path — Get the path (from root) of a randomly chosen child. It will have the path; an empty container is an error, and it will have false.'
|
|
103
231
|
],
|
|
232
|
+
:notes => 'The random_child messages pick with replacement: two ' \
|
|
233
|
+
'calls can pick the same child. For distinct picks, pick a ' \
|
|
234
|
+
'random index with an integer\'s randomize message, keep the ' \
|
|
235
|
+
'indexes already used as children of another container, and ' \
|
|
236
|
+
'check it with child_exists before using child_path_at.',
|
|
104
237
|
:examples => <<~EXAMPLES.strip
|
|
105
238
|
can [can] :
|
|
106
239
|
data [can] :
|
|
@@ -109,6 +242,21 @@ module Gloo
|
|
|
109
242
|
3 : three
|
|
110
243
|
on_load [script] :
|
|
111
244
|
tell can.data to show_key_value_table
|
|
245
|
+
|
|
246
|
+
#
|
|
247
|
+
# Reach a container child's fields through an alias.
|
|
248
|
+
#
|
|
249
|
+
books [can] :
|
|
250
|
+
list [can] :
|
|
251
|
+
a [can] :
|
|
252
|
+
title [string] : Walden
|
|
253
|
+
b [can] :
|
|
254
|
+
title [string] : Emma
|
|
255
|
+
ptr [alias] :
|
|
256
|
+
on_load [script] :
|
|
257
|
+
tell books.list to child_path_at (1)
|
|
258
|
+
put it into books.ptr*
|
|
259
|
+
show books.ptr.title
|
|
112
260
|
EXAMPLES
|
|
113
261
|
}
|
|
114
262
|
end
|
|
@@ -109,6 +109,7 @@ module Gloo
|
|
|
109
109
|
'an optional parameter is included, round to the precision specified.',
|
|
110
110
|
'format ({fmt}) — With no parameter, adds comma separators to the whole part (e.g. 1000.5 -> 1,000.5). With a parameter, uses it as a sprintf-style format string (e.g. \'%.2f\'). It will have the formatted string.'
|
|
111
111
|
],
|
|
112
|
+
:notes => 'A value that isn\'t a number (eg. \'x\' or \'1,5\') is used as a best guess, with a warning: "\'1,5\' is not a decimal number; using 1.0." A blank value is 0.0.',
|
|
112
113
|
:examples => <<~EXAMPLES.strip
|
|
113
114
|
d [can] :
|
|
114
115
|
x [decimal] : 100
|
|
@@ -140,6 +140,7 @@ module Gloo
|
|
|
140
140
|
'range to 6 and add 1 to the result.',
|
|
141
141
|
'format ({fmt}) — With no parameter, adds comma separators (e.g. 1000 -> 1,000). With a parameter, uses it as a sprintf-style format string (e.g. \'%05d\'). It will have the formatted string.'
|
|
142
142
|
],
|
|
143
|
+
:notes => 'A value that isn\'t a whole number (eg. \'x\', \'12abc\' or \'3.7\') is used as a best guess, with a warning: "\'x\' is not an integer; using 0." A blank value is 0.',
|
|
143
144
|
:examples => <<~EXAMPLES.strip
|
|
144
145
|
#
|
|
145
146
|
# Integer object.
|