gloo 7.1.2 → 7.2
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/Rakefile +23 -0
- data/docs/application.md +47 -2
- data/docs/development.md +10 -0
- data/docs/iterators.md +22 -5
- data/docs/language_objects.md +20 -15
- data/docs/language_scripting.md +6 -2
- data/docs/plugins.md +1 -1
- data/docs/testing.md +4 -7
- data/docs/verbs.md +23 -3
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +13 -0
- data/lib/dependencies.rb +4 -1
- data/lib/gloo/app/engine.rb +18 -6
- data/lib/gloo/core/obj.rb +0 -23
- data/lib/gloo/exec/script.rb +12 -2
- data/lib/gloo/objs/basic/script.rb +5 -28
- data/lib/gloo/objs/ctrl/each.rb +4 -3
- data/lib/gloo/objs/ctrl/repeat.rb +4 -3
- data/lib/gloo/persist/declaration_ledger.rb +9 -9
- data/lib/gloo/persist/declaration_writer.rb +208 -0
- data/lib/gloo/persist/disc_mech.rb +70 -39
- data/lib/gloo/persist/file_loader.rb +125 -66
- data/lib/gloo/persist/file_saver.rb +130 -198
- data/lib/gloo/persist/file_storage.rb +15 -4
- data/lib/gloo/persist/file_style.rb +89 -0
- data/lib/gloo/persist/line_splitter.rb +16 -1
- data/lib/gloo/persist/persist_man.rb +112 -30
- data/lib/gloo/persist/save_index.rb +211 -0
- data/lib/gloo/persist/script_body_collector.rb +16 -21
- data/lib/gloo/persist/source/blank_node.rb +10 -3
- data/lib/gloo/persist/source/comment_node.rb +10 -2
- data/lib/gloo/persist/source/directive_node.rb +10 -2
- data/lib/gloo/persist/source/obj_node.rb +174 -9
- data/lib/gloo/persist/source/source_doc.rb +54 -0
- data/lib/gloo/persist/subtree_extractor.rb +9 -37
- data/lib/gloo/persist/trivia_buffer.rb +7 -7
- data/lib/gloo/verbs/break.rb +99 -10
- data/lib/gloo/verbs/load.rb +27 -21
- data/lib/gloo/verbs/move.rb +42 -3
- data/lib/gloo/verbs/save.rb +26 -4
- data/test.gloo/app/exit_status.test.gloo +34 -0
- data/test.gloo/app/fixtures/exit_after_error.gloo +9 -0
- data/test.gloo/lang/declaration.test.gloo +36 -0
- data/test.gloo/lang/fixtures/mixed_indent.gloo +5 -0
- data/test.gloo/lang/naming.test.gloo +44 -6
- data/test.gloo/verbs/break.test.gloo +104 -11
- data/test.gloo/verbs/fixtures/load_beside.gloo +6 -0
- data/test.gloo/verbs/fixtures/lossless_text.gloo +7 -0
- data/test.gloo/verbs/fixtures/lossless_text_saved.gloo +9 -0
- data/test.gloo/verbs/load.test.gloo +18 -0
- data/test.gloo/verbs/move.test.gloo +39 -0
- data/test.gloo/verbs/save.test.gloo +239 -0
- metadata +10 -1
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
#
|
|
4
4
|
# One object declaration in a source file, eg.
|
|
5
5
|
# name [type] : value
|
|
6
|
-
# Carries the
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Carries the declaration's exact source text, plus a back-reference
|
|
7
|
+
# to the heap object it created (or named again), so a save can tell
|
|
8
|
+
# whether the object is still there and its value unchanged and, if
|
|
9
|
+
# so, write the text back exactly as it was read.
|
|
10
10
|
#
|
|
11
11
|
|
|
12
12
|
module Gloo
|
|
@@ -19,8 +19,23 @@ module Gloo
|
|
|
19
19
|
# :begin_end -- name [type] : BEGIN ... END
|
|
20
20
|
# :body -- name [type] :, followed by an indented,
|
|
21
21
|
# unparsed body (script commands)
|
|
22
|
+
#
|
|
23
|
+
# raw is the declaration's own exact text: its line (with any
|
|
24
|
+
# continuation lines), and its BEGIN/END block or script body,
|
|
25
|
+
# line endings included -- not its leading_doc or children.
|
|
26
|
+
# head is the declaration line up to its value (indent, name and
|
|
27
|
+
# [type], as written), raw_tail what follows it.
|
|
28
|
+
#
|
|
29
|
+
# repaired_head is set when gloo had to guess at the head (a
|
|
30
|
+
# missing name or ']'): a save writes the head as understood.
|
|
31
|
+
#
|
|
32
|
+
# home is the object's parent and snapshot a copy of its value,
|
|
33
|
+
# both taken when the file finished loading: the object is still
|
|
34
|
+
# in its place if its parent is home, and unchanged if its value
|
|
35
|
+
# equals the snapshot.
|
|
22
36
|
attr_accessor :name, :raw_type, :raw_indent, :raw_tail, :block_style,
|
|
23
|
-
:raw_value, :
|
|
37
|
+
:raw_value, :leading_doc, :obj, :raw, :head,
|
|
38
|
+
:home, :snapshot, :repaired_head
|
|
24
39
|
attr_reader :children
|
|
25
40
|
|
|
26
41
|
#
|
|
@@ -35,15 +50,80 @@ module Gloo
|
|
|
35
50
|
@raw_tail = ''
|
|
36
51
|
@block_style = :inline
|
|
37
52
|
@raw_value = nil
|
|
38
|
-
# a begin_end block's closing END is not necessarily indented
|
|
39
|
-
# to match its declaration -- kept separately, raw.
|
|
40
|
-
@raw_end_indent = ''
|
|
41
53
|
@leading_doc = nil
|
|
42
|
-
@trailing_comment = nil
|
|
43
54
|
@obj = nil
|
|
55
|
+
@raw = ''
|
|
56
|
+
@head = ''
|
|
57
|
+
@home = nil
|
|
58
|
+
@snapshot = nil
|
|
59
|
+
@repaired_head = nil
|
|
44
60
|
@children = []
|
|
45
61
|
end
|
|
46
62
|
|
|
63
|
+
#
|
|
64
|
+
# The exact source text of the declaration, its leading_doc and
|
|
65
|
+
# everything under it, as read.
|
|
66
|
+
#
|
|
67
|
+
def text
|
|
68
|
+
str = "#{@leading_doc}#{@raw}"
|
|
69
|
+
@children.each { |child| str << child.text }
|
|
70
|
+
return str
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# A copy of this declaration with no children, and with the
|
|
75
|
+
# given text in place of its own (for a save's new document;
|
|
76
|
+
# this node is left as it is).
|
|
77
|
+
#
|
|
78
|
+
def detached( raw = @raw )
|
|
79
|
+
copy = dup
|
|
80
|
+
copy.raw = raw
|
|
81
|
+
copy.reset_children
|
|
82
|
+
return copy
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# A copy with the head gloo understood in place of the one
|
|
87
|
+
# written (see repaired_head); this node if nothing was repaired.
|
|
88
|
+
#
|
|
89
|
+
def repaired
|
|
90
|
+
return self unless @repaired_head
|
|
91
|
+
|
|
92
|
+
copy = detached( @raw.sub( @head, @repaired_head ) )
|
|
93
|
+
copy.head = @repaired_head
|
|
94
|
+
copy.repaired_head = nil
|
|
95
|
+
return copy
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Record where the object is and what its value is, now that the
|
|
100
|
+
# file has finished loading.
|
|
101
|
+
#
|
|
102
|
+
def remember
|
|
103
|
+
return unless @obj
|
|
104
|
+
|
|
105
|
+
@home = @obj.parent
|
|
106
|
+
@snapshot = copy_of( @obj.value )
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#
|
|
110
|
+
# Is the object still under the parent it was loaded under?
|
|
111
|
+
#
|
|
112
|
+
def in_place?
|
|
113
|
+
return false unless @obj && @home
|
|
114
|
+
|
|
115
|
+
return @obj.parent.equal?( @home ) && @home.children.any? { |c| c.equal?( @obj ) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#
|
|
119
|
+
# Has the object's value changed since the file was loaded?
|
|
120
|
+
#
|
|
121
|
+
def changed?
|
|
122
|
+
return false unless @obj
|
|
123
|
+
|
|
124
|
+
return @obj.value != @snapshot
|
|
125
|
+
end
|
|
126
|
+
|
|
47
127
|
#
|
|
48
128
|
# The cleaned-up leading_doc: each line's leading whitespace and
|
|
49
129
|
# its '#' marker (plus one following space, if any) stripped,
|
|
@@ -59,8 +139,93 @@ module Gloo
|
|
|
59
139
|
return dedent( lines ).join( "\n" )
|
|
60
140
|
end
|
|
61
141
|
|
|
142
|
+
#
|
|
143
|
+
# Give the declaration a new name as written (eg. a dotted path
|
|
144
|
+
# when it moves into a file of its own). Only the name on its
|
|
145
|
+
# first line changes.
|
|
146
|
+
#
|
|
147
|
+
def rename( new_name )
|
|
148
|
+
@raw = @raw.sub( /\A([ \t]*)#{Regexp.escape( @name )}/ ) { "#{$1}#{new_name}" }
|
|
149
|
+
@head = @head.sub( /\A([ \t]*)#{Regexp.escape( @name )}/ ) { "#{$1}#{new_name}" }
|
|
150
|
+
@name = new_name
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# Move the declaration (and everything under it) to a new
|
|
155
|
+
# indentation. Its own lines and its script body shift by the
|
|
156
|
+
# same amount; a BEGIN/END block's content is the value, so it
|
|
157
|
+
# stays exactly as it is, and END lines up with the declaration.
|
|
158
|
+
# A leading_doc's lines are set to the new indentation.
|
|
159
|
+
#
|
|
160
|
+
def reindent( new_indent )
|
|
161
|
+
old = @raw_indent
|
|
162
|
+
@leading_doc = reindent_doc( @leading_doc, new_indent ) if @leading_doc
|
|
163
|
+
@raw = reindent_own( @raw, old, new_indent )
|
|
164
|
+
@head = shift( @head, old, new_indent )
|
|
165
|
+
@raw_indent = new_indent
|
|
166
|
+
@children.each do |child|
|
|
167
|
+
next unless child.is_a?( ObjNode )
|
|
168
|
+
|
|
169
|
+
child.reindent( shift( child.raw_indent, old, new_indent ) )
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
protected
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# Start with no children (for a detached copy).
|
|
177
|
+
#
|
|
178
|
+
def reset_children
|
|
179
|
+
@children = []
|
|
180
|
+
end
|
|
181
|
+
|
|
62
182
|
private
|
|
63
183
|
|
|
184
|
+
#
|
|
185
|
+
# A copy of a value that can't be changed through the original
|
|
186
|
+
# (a script's lines are an Array that grows in place).
|
|
187
|
+
#
|
|
188
|
+
def copy_of( value )
|
|
189
|
+
return Marshal.load( Marshal.dump( value ) )
|
|
190
|
+
rescue TypeError
|
|
191
|
+
return value.dup
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# The declaration's own lines at a new indentation.
|
|
196
|
+
#
|
|
197
|
+
def reindent_own( text, old, new_indent )
|
|
198
|
+
lines = text.lines
|
|
199
|
+
return text if lines.empty?
|
|
200
|
+
|
|
201
|
+
if @block_style == :begin_end
|
|
202
|
+
ending = lines.last[ /\r?\n\z/ ].to_s
|
|
203
|
+
lines[ 0 ] = shift( lines[ 0 ], old, new_indent )
|
|
204
|
+
lines[ -1 ] = "#{new_indent}END#{ending}" if lines.size > 1
|
|
205
|
+
return lines.join
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
return lines.map { |l| l.strip.empty? ? l : shift( l, old, new_indent ) }.join
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
#
|
|
212
|
+
# Replace a line's old indentation prefix with the new one; a
|
|
213
|
+
# line that doesn't start with it gets the new one added.
|
|
214
|
+
#
|
|
215
|
+
def shift( line, old, new_indent )
|
|
216
|
+
return "#{new_indent}#{line.delete_prefix( old )}" if line.start_with?( old )
|
|
217
|
+
|
|
218
|
+
return "#{new_indent}#{line.lstrip}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
#
|
|
222
|
+
# Re-indent a leading_doc's lines to new_indent, replacing
|
|
223
|
+
# whatever leading whitespace each '#' line had.
|
|
224
|
+
#
|
|
225
|
+
def reindent_doc( raw, new_indent )
|
|
226
|
+
return raw.lines.map { |line| "#{new_indent}#{line.lstrip}" }.join
|
|
227
|
+
end
|
|
228
|
+
|
|
64
229
|
#
|
|
65
230
|
# One raw comment line -> its text past the '#': leading
|
|
66
231
|
# whitespace dropped, then the '#' and at most one space after
|
|
@@ -19,11 +19,53 @@ module Gloo
|
|
|
19
19
|
|
|
20
20
|
attr_reader :children
|
|
21
21
|
|
|
22
|
+
# A file whose last line has no line ending: every node's text
|
|
23
|
+
# ends with one anyway (so new text can follow any node), and
|
|
24
|
+
# the document leaves the last one off.
|
|
25
|
+
attr_accessor :no_final_newline
|
|
26
|
+
|
|
22
27
|
#
|
|
23
28
|
# Set up an empty source document.
|
|
24
29
|
#
|
|
25
30
|
def initialize
|
|
26
31
|
@children = []
|
|
32
|
+
@no_final_newline = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
# The file's exact text: every node's text, in order. Right
|
|
37
|
+
# after a load this is the file, byte for byte.
|
|
38
|
+
#
|
|
39
|
+
def text
|
|
40
|
+
str = @children.map( &:text ).join
|
|
41
|
+
str = str.delete_suffix( "\n" ) if @no_final_newline
|
|
42
|
+
return str
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Note that the file's last line had no line ending, and give it
|
|
47
|
+
# one in the node that holds it (see no_final_newline).
|
|
48
|
+
#
|
|
49
|
+
def missing_final_newline!
|
|
50
|
+
leaf = last_text_node( @children )
|
|
51
|
+
return unless leaf
|
|
52
|
+
|
|
53
|
+
leaf.raw << "\n"
|
|
54
|
+
@no_final_newline = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# Every object declaration in the document, at any depth, in
|
|
59
|
+
# file order.
|
|
60
|
+
#
|
|
61
|
+
def obj_nodes( nodes = @children, found = [] )
|
|
62
|
+
nodes.each do |node|
|
|
63
|
+
next unless node.is_a?( Source::ObjNode )
|
|
64
|
+
|
|
65
|
+
found << node
|
|
66
|
+
obj_nodes( node.children, found )
|
|
67
|
+
end
|
|
68
|
+
return found
|
|
27
69
|
end
|
|
28
70
|
|
|
29
71
|
#
|
|
@@ -51,6 +93,18 @@ module Gloo
|
|
|
51
93
|
|
|
52
94
|
private
|
|
53
95
|
|
|
96
|
+
#
|
|
97
|
+
# The node whose text comes last in the file: the last node, or
|
|
98
|
+
# its last child (at any depth) if it has children.
|
|
99
|
+
#
|
|
100
|
+
def last_text_node( nodes )
|
|
101
|
+
last = nodes.last
|
|
102
|
+
return nil unless last
|
|
103
|
+
return last unless last.is_a?( Source::ObjNode ) && last.children.any?
|
|
104
|
+
|
|
105
|
+
return last_text_node( last.children )
|
|
106
|
+
end
|
|
107
|
+
|
|
54
108
|
#
|
|
55
109
|
# Depth-first search of nodes (and their children) for the one
|
|
56
110
|
# linked to obj; removes it from whichever children array it's
|
|
@@ -46,11 +46,13 @@ module Gloo
|
|
|
46
46
|
return err( SPANS_MULTIPLE_FILES_ERR ) if owners.count > 1
|
|
47
47
|
|
|
48
48
|
source_fs = owners.first
|
|
49
|
-
|
|
49
|
+
return [ fresh_node( obj ), nil ] unless source_fs
|
|
50
|
+
|
|
51
|
+
node = remove_node( obj, source_fs )
|
|
50
52
|
return err( NO_OWN_DECLARATION_ERR ) unless node
|
|
51
53
|
|
|
52
|
-
node.
|
|
53
|
-
reindent(
|
|
54
|
+
node.rename( dotted_name( obj ) )
|
|
55
|
+
node.reindent( '' )
|
|
54
56
|
return [ node, source_fs ]
|
|
55
57
|
end
|
|
56
58
|
|
|
@@ -110,13 +112,12 @@ module Gloo
|
|
|
110
112
|
|
|
111
113
|
#
|
|
112
114
|
# obj has no file of its own (never loaded, or created at
|
|
113
|
-
# runtime since) --
|
|
114
|
-
#
|
|
115
|
-
# way it already does for any brand-new child.
|
|
115
|
+
# runtime since) -- write it, and everything under it, from
|
|
116
|
+
# scratch, named by its dotted path.
|
|
116
117
|
#
|
|
117
118
|
def fresh_node( obj )
|
|
118
|
-
node =
|
|
119
|
-
node.
|
|
119
|
+
node = FileSaver.new( @engine, nil, obj ).new_node( obj, dotted_name( obj ) )
|
|
120
|
+
Source::SourceDoc.new.obj_nodes( [ node ] ).each( &:remember )
|
|
120
121
|
return node
|
|
121
122
|
end
|
|
122
123
|
|
|
@@ -150,35 +151,6 @@ module Gloo
|
|
|
150
151
|
return names.join( '.' )
|
|
151
152
|
end
|
|
152
153
|
|
|
153
|
-
#
|
|
154
|
-
# Recompute raw_indent (and, for a BEGIN/END node, raw_end_indent
|
|
155
|
-
# to match) from scratch for the moved node and every descendant
|
|
156
|
-
# ObjNode. Their old indentation depth described nesting inside
|
|
157
|
-
# the file they came from; here obj itself is depth 0, in a
|
|
158
|
-
# brand-new file. Floating trivia (a blank line or comment not
|
|
159
|
-
# attached as some declaration's leading_doc) travels along
|
|
160
|
-
# unindented-adjusted -- purely cosmetic, since indentation never
|
|
161
|
-
# affects how a comment/blank line is recognized on reload.
|
|
162
|
-
#
|
|
163
|
-
def reindent( node, depth )
|
|
164
|
-
new_indent = "\t" * depth
|
|
165
|
-
node.raw_indent = new_indent
|
|
166
|
-
node.raw_end_indent = new_indent if node.block_style == :begin_end
|
|
167
|
-
node.leading_doc = reindent_doc( node.leading_doc, new_indent ) if node.leading_doc
|
|
168
|
-
node.children.each do |child|
|
|
169
|
-
reindent( child, depth + 1 ) if child.is_a?( Source::ObjNode )
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
#
|
|
174
|
-
# Re-indent a raw leading_doc's lines to new_indent, replacing
|
|
175
|
-
# whatever leading whitespace each '#'-prefixed line had in the
|
|
176
|
-
# file it came from.
|
|
177
|
-
#
|
|
178
|
-
def reindent_doc( raw, new_indent )
|
|
179
|
-
return raw.split( "\n" ).map { |line| "#{new_indent}#{line.lstrip}" }.join( "\n" )
|
|
180
|
-
end
|
|
181
|
-
|
|
182
154
|
end
|
|
183
155
|
end
|
|
184
156
|
end
|
|
@@ -29,16 +29,16 @@ module Gloo
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
#
|
|
32
|
-
# Buffer one comment line. raw is the full source line
|
|
33
|
-
#
|
|
32
|
+
# Buffer one comment line. raw is the full source line, with its
|
|
33
|
+
# line ending; tabs is its indentation level.
|
|
34
34
|
#
|
|
35
35
|
def push_comment( raw, tabs )
|
|
36
36
|
@pending << { :kind => :comment, :raw => raw, :tabs => tabs }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
#
|
|
40
|
-
# Buffer one blank line. raw is its exact source text
|
|
41
|
-
#
|
|
40
|
+
# Buffer one blank line. raw is its exact source text, with its
|
|
41
|
+
# line ending and any trailing whitespace -- a blank line always
|
|
42
42
|
# breaks a comment run's association with whatever declaration
|
|
43
43
|
# follows, the same as it always has.
|
|
44
44
|
#
|
|
@@ -61,8 +61,8 @@ module Gloo
|
|
|
61
61
|
#
|
|
62
62
|
# If the run since the last blank (or the whole run, if it has no
|
|
63
63
|
# blank in it) is one or more comment lines all at the same
|
|
64
|
-
# indent as line_tabs, claim it as a leading_doc (raw,
|
|
65
|
-
#
|
|
64
|
+
# indent as line_tabs, claim it as a leading_doc (the raw lines,
|
|
65
|
+
# line endings included). Anything before that -- an earlier blank, and
|
|
66
66
|
# whatever preceded it -- floats into children first, in order,
|
|
67
67
|
# since a blank always breaks the association. If the trailing
|
|
68
68
|
# run doesn't qualify (wrong indent, or empty), it floats into
|
|
@@ -74,7 +74,7 @@ module Gloo
|
|
|
74
74
|
flush_list( before, children )
|
|
75
75
|
|
|
76
76
|
if after.any? && after.last[ :tabs ] == line_tabs
|
|
77
|
-
doc = after.map { |t| t[ :raw ] }.join
|
|
77
|
+
doc = after.map { |t| t[ :raw ] }.join
|
|
78
78
|
@pending = []
|
|
79
79
|
return doc
|
|
80
80
|
end
|
data/lib/gloo/verbs/break.rb
CHANGED
|
@@ -10,14 +10,23 @@ module Gloo
|
|
|
10
10
|
|
|
11
11
|
KEYWORD = 'break'.freeze
|
|
12
12
|
KEYWORD_SHORT = 'stop'.freeze
|
|
13
|
+
LOOP = 'loop'.freeze
|
|
14
|
+
NOT_IN_A_LOOP = 'Could not break loop: not in a loop.'.freeze
|
|
13
15
|
|
|
14
16
|
#
|
|
15
17
|
# Run the verb.
|
|
16
|
-
# Stop the execution of the current script.
|
|
18
|
+
# Stop the execution of the current script. With loop (break
|
|
19
|
+
# loop), also end the loop whose do script this is.
|
|
17
20
|
#
|
|
18
21
|
def run
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
words = extra_words.split( ' ' )
|
|
23
|
+
if words.first&.downcase == LOOP
|
|
24
|
+
warn_words_after_loop( words.drop( 1 ) )
|
|
25
|
+
break_loop
|
|
26
|
+
else
|
|
27
|
+
warn_unknown_words
|
|
28
|
+
@engine.exec_env.running_script.break_out
|
|
29
|
+
end
|
|
21
30
|
end
|
|
22
31
|
|
|
23
32
|
#
|
|
@@ -40,6 +49,54 @@ module Gloo
|
|
|
40
49
|
|
|
41
50
|
private
|
|
42
51
|
|
|
52
|
+
#
|
|
53
|
+
# End the running script and its loop. Only valid directly in a
|
|
54
|
+
# loop's do script; anywhere else (a script the do runs, a group
|
|
55
|
+
# script, outside any loop) it's an error and nothing stops.
|
|
56
|
+
#
|
|
57
|
+
def break_loop
|
|
58
|
+
script = @engine.exec_env.running_script
|
|
59
|
+
unless loop_do?( script&.obj )
|
|
60
|
+
@engine.err NOT_IN_A_LOOP
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
script.break_out( loop: true )
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#
|
|
68
|
+
# Is the object the do script of a loop (a repeat or an each)?
|
|
69
|
+
#
|
|
70
|
+
def loop_do?( obj )
|
|
71
|
+
parent = obj&.parent
|
|
72
|
+
return false unless parent.is_a?( Gloo::Objs::Repeat ) ||
|
|
73
|
+
parent.is_a?( Gloo::Objs::Each )
|
|
74
|
+
|
|
75
|
+
return parent.find_child( Gloo::Objs::Each::DO ).equal?( obj )
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
# Warn about anything written after break other than loop.
|
|
80
|
+
# The break still happens.
|
|
81
|
+
#
|
|
82
|
+
def warn_unknown_words
|
|
83
|
+
extra = extra_words
|
|
84
|
+
return if extra.empty?
|
|
85
|
+
|
|
86
|
+
@engine.warn "#{written_verb} takes only '#{LOOP}'; ignoring '#{extra}'."
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# Warn about anything written after break loop. The loop still
|
|
91
|
+
# ends.
|
|
92
|
+
#
|
|
93
|
+
def warn_words_after_loop( words )
|
|
94
|
+
return if words.empty?
|
|
95
|
+
|
|
96
|
+
@engine.warn "#{written_verb} #{LOOP} takes no object; " \
|
|
97
|
+
"ignoring '#{words.join( ' ' )}'."
|
|
98
|
+
end
|
|
99
|
+
|
|
43
100
|
# ---------------------------------------------------------------------
|
|
44
101
|
# Verb Documentation
|
|
45
102
|
# ---------------------------------------------------------------------
|
|
@@ -52,15 +109,26 @@ module Gloo
|
|
|
52
109
|
:name => KEYWORD,
|
|
53
110
|
:shortcut => KEYWORD_SHORT,
|
|
54
111
|
:description => 'Break out of the current script, stop running. ' \
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
:syntax => [ 'break' ],
|
|
112
|
+
'In a loop\'s do script, break skips the rest of that pass ' \
|
|
113
|
+
'and the loop goes on; break loop ends the whole loop.',
|
|
114
|
+
:syntax => [ 'break', 'break loop' ],
|
|
115
|
+
:parameters => [
|
|
116
|
+
'loop — Optional. Also end the loop whose do script this is. ' \
|
|
117
|
+
'Only valid directly in a loop\'s do script (repeat, or any ' \
|
|
118
|
+
'kind of each).'
|
|
119
|
+
],
|
|
58
120
|
:result => 'No more commands are executed in the currently ' \
|
|
59
121
|
'running script. If there are other scripts in the stack, ' \
|
|
60
|
-
'they will resume normally. In a loop\'s do script,
|
|
61
|
-
'ends
|
|
62
|
-
'
|
|
63
|
-
'
|
|
122
|
+
'they will resume normally. In a loop\'s do script, a plain ' \
|
|
123
|
+
'break ends that pass and the loop goes on with the next ' \
|
|
124
|
+
'item. With break loop, the loop ends too and the script ' \
|
|
125
|
+
'that ran it carries on.',
|
|
126
|
+
:errors => [
|
|
127
|
+
"#{NOT_IN_A_LOOP} — break loop in a script that isn't a " \
|
|
128
|
+
'loop\'s do script: one the do script runs (with run, or a ' \
|
|
129
|
+
'function), a loop\'s on_group_start or on_group_end, or a ' \
|
|
130
|
+
'script outside any loop. Nothing stops.'
|
|
131
|
+
],
|
|
64
132
|
:examples => <<~EXAMPLES.strip
|
|
65
133
|
#
|
|
66
134
|
# Break out of a script.
|
|
@@ -73,6 +141,27 @@ module Gloo
|
|
|
73
141
|
put true into ^.x
|
|
74
142
|
if ^.x then break
|
|
75
143
|
show 'should not see this'
|
|
144
|
+
|
|
145
|
+
#
|
|
146
|
+
# In a loop: break skips an item, break loop ends the loop.
|
|
147
|
+
# Shows 0, 1, 3, then 0, 1.
|
|
148
|
+
#
|
|
149
|
+
loops [can] :
|
|
150
|
+
on_load [script] :
|
|
151
|
+
tell loops.skip to run
|
|
152
|
+
tell loops.early to run
|
|
153
|
+
skip [repeat] :
|
|
154
|
+
times [int] : 4
|
|
155
|
+
index [int] :
|
|
156
|
+
do [script] :
|
|
157
|
+
if loops.skip.index = 2 then break
|
|
158
|
+
show loops.skip.index
|
|
159
|
+
early [repeat] :
|
|
160
|
+
times [int] : 4
|
|
161
|
+
index [int] :
|
|
162
|
+
do [script] :
|
|
163
|
+
if loops.early.index = 2 then break loop
|
|
164
|
+
show loops.early.index
|
|
76
165
|
EXAMPLES
|
|
77
166
|
}
|
|
78
167
|
end
|
data/lib/gloo/verbs/load.rb
CHANGED
|
@@ -63,12 +63,15 @@ module Gloo
|
|
|
63
63
|
return KEYWORD_SHORT
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
# Load a gloo file
|
|
68
|
-
#
|
|
66
|
+
#
|
|
67
|
+
# Load a gloo file. A relative name is looked for in the
|
|
68
|
+
# persist manager's search folders (next to this file, the
|
|
69
|
+
# app or projects folder, the gloo home).
|
|
70
|
+
#
|
|
69
71
|
def load_gloo_file( fn )
|
|
70
72
|
@engine.log.debug "Getting ready to load gloo file: #{fn}"
|
|
71
|
-
@engine.persist_man
|
|
73
|
+
pm = @engine.persist_man
|
|
74
|
+
pm.load( fn, pm.search_folders )
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
#
|
|
@@ -98,16 +101,17 @@ module Gloo
|
|
|
98
101
|
{
|
|
99
102
|
:name => KEYWORD,
|
|
100
103
|
:shortcut => KEYWORD_SHORT,
|
|
101
|
-
:description => 'Load an object file.
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
104
|
+
:description => 'Load an object file. Give a full path, one ' \
|
|
105
|
+
'starting with ~, or a relative name; the .gloo extension is ' \
|
|
106
|
+
'optional. A relative name is looked for in a few places, in ' \
|
|
107
|
+
'order. In App mode (--app): the app folder, then next to the ' \
|
|
108
|
+
'file the loading script came from, then the gloo home. ' \
|
|
109
|
+
'Otherwise: next to the loading file, then the projects ' \
|
|
110
|
+
'folder (the project_path setting), then the gloo home. A ' \
|
|
111
|
+
'command typed at the prompt has no file, so the working ' \
|
|
112
|
+
'folder stands in for it. Using * instead of a file name ' \
|
|
113
|
+
'loads all gloo files in the folder. The load verb is also ' \
|
|
114
|
+
'used to import an extension or library at runtime.',
|
|
111
115
|
:syntax => [
|
|
112
116
|
'load {file_name}',
|
|
113
117
|
'load ext {my_ext}',
|
|
@@ -116,11 +120,12 @@ module Gloo
|
|
|
116
120
|
],
|
|
117
121
|
:parameters => [
|
|
118
122
|
'{file_name} — Name of the object file that is to be loaded: ' \
|
|
119
|
-
'relative
|
|
123
|
+
'a relative name (see the description for where it is ' \
|
|
124
|
+
'looked for), a full path, or starting with ~. ' \
|
|
120
125
|
'The .gloo extension is optional. ' \
|
|
121
|
-
'End it with * to load every .gloo file in a folder
|
|
122
|
-
'
|
|
123
|
-
'
|
|
126
|
+
'End it with * to load every .gloo file in a folder, found ' \
|
|
127
|
+
'the same way; the first place with matching files is ' \
|
|
128
|
+
'used. Anything before the * is the start of the file names.',
|
|
124
129
|
'reference type — By default, the type is file. File can be ' \
|
|
125
130
|
'specified, but does not need to be. Use ext to load a ' \
|
|
126
131
|
'User Extension. Use lib to load a Core Library.',
|
|
@@ -136,8 +141,8 @@ module Gloo
|
|
|
136
141
|
"#{UNKNOWN_OPT_ERR} — The reference type given isn't file, ext, or lib.",
|
|
137
142
|
"#{WRONG_NUM_ARGS_ERR} — load expects 2 or 3 arguments (the verb, an optional reference type, and the file/ext/lib name).",
|
|
138
143
|
'File not found: {name} — The file name given could not be ' \
|
|
139
|
-
'resolved to a gloo file
|
|
140
|
-
'
|
|
144
|
+
'resolved to a gloo file in any of the places it is ' \
|
|
145
|
+
'looked for.'
|
|
141
146
|
],
|
|
142
147
|
:notes => 'Several files can contribute to one container: ' \
|
|
143
148
|
'declare it in each file and add different children, and ' \
|
|
@@ -156,7 +161,8 @@ module Gloo
|
|
|
156
161
|
'is declared in more than one loaded file, the first ' \
|
|
157
162
|
'non-empty doc wins, same as the first value.',
|
|
158
163
|
:examples => <<~EXAMPLES.strip
|
|
159
|
-
|
|
164
|
+
# Next to this file (or the app folder, in App mode)
|
|
165
|
+
> load parts/helpers
|
|
160
166
|
> load my/app/*
|
|
161
167
|
> load ~/.my_app/settings.gloo
|
|
162
168
|
> load ~/.my_app/plugins/*
|