gloo 6.2.1 → 6.3.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.mailmap +1 -0
  3. data/docs/language_syntax.md +18 -2
  4. data/docs/objects.md +4 -0
  5. data/docs/plugins.md +12 -4
  6. data/docs/verbs.md +8 -0
  7. data/lib/VERSION +1 -1
  8. data/lib/VERSION_NOTES +4 -0
  9. data/lib/gloo/objs/basic/untyped.rb +14 -2
  10. data/lib/gloo/persist/file_loader.rb +27 -1
  11. data/lib/gloo/verbs/check.rb +16 -4
  12. data/lib/gloo/verbs/create.rb +8 -3
  13. data/lib/gloo/verbs/eval.rb +16 -2
  14. data/lib/gloo/verbs/tell.rb +15 -3
  15. data/lib/gloo/verbs/throw.rb +29 -1
  16. data/test.gloo/lang/continuation.test.gloo +39 -0
  17. data/test.gloo/lang/convert.test.gloo +70 -0
  18. data/test.gloo/lang/here.test.gloo +41 -0
  19. data/test.gloo/lang/it.test.gloo +41 -0
  20. data/test.gloo/lang/literal.test.gloo +61 -0
  21. data/test.gloo/lang/load_lib_directive.test.gloo +33 -0
  22. data/test.gloo/lang/naming.test.gloo +44 -0
  23. data/test.gloo/objs/cipher.test.gloo +45 -0
  24. data/test.gloo/objs/erb.test.gloo +18 -0
  25. data/test.gloo/objs/file.test.gloo +77 -0
  26. data/test.gloo/objs/function.test.gloo +54 -0
  27. data/test.gloo/objs/json.test.gloo +70 -0
  28. data/test.gloo/objs/outline.test.gloo +72 -0
  29. data/test.gloo/objs/password.test.gloo +40 -0
  30. data/test.gloo/objs/repeat.test.gloo +48 -0
  31. data/test.gloo/objs/untyped.test.gloo +20 -2
  32. data/test.gloo/objs/uri.test.gloo +43 -0
  33. data/test.gloo/verbs/context.test.gloo +19 -0
  34. data/test.gloo/verbs/list.test.gloo +34 -0
  35. data/test.gloo/verbs/load.test.gloo +32 -0
  36. data/test.gloo/verbs/show.test.gloo +37 -0
  37. data/test.gloo/verbs/throw.test.gloo +44 -0
  38. data/test.gloo/verbs/unload.test.gloo +21 -0
  39. metadata +23 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dee07bac4d22ecf1656cd257c1f3ce3ad12ae334377263faf5918d7f1433e20
4
- data.tar.gz: 6310ec46fcf0f0da75b874b984f169566c5d7d52ce4d21c2646134c5036e2205
3
+ metadata.gz: 8b2250e28df1b29cef0afb4bb989a7631b421b61aff2a6a4ca7a4a99784ea6f5
4
+ data.tar.gz: d1aada2c93dee25e57a9a99770637ac1bf5c76c7e5064ebfd6de0a9779c041e2
5
5
  SHA512:
6
- metadata.gz: 1bd139bbe32577a3811247d7d04b0b270796a447b1179c4b62a337526d0c57b5610e0636fa08e8a6aad920d8435e372d7763038d6e759637adae92d9c6b7a3af
7
- data.tar.gz: e4edde70f119ab15b319910ea1b128b6a862f8be723b27f7d9e8d7acf4c6cf98f8f6a62d2114203aedcaa9e89625b530d88334be65bd8d6ecf3d1221bc775ad9
6
+ metadata.gz: 0d6b391cda5acf241e452eadee0533a0929622dde953eb44fd32cadfd1a1c8c469a0b820370af157eca3f5128636127d6171fc57a6002e532e03e24d03982656
7
+ data.tar.gz: 97806263c82dfb98af8105c19e017dde5b3d4a426c85384c2368d7b2df1b4e9f396bc9236844b7fff77e3fd264c0aa77ccb17e10fa96d5fa4a658f7e39ec2475
data/.mailmap ADDED
@@ -0,0 +1 @@
1
+ Eric Crane <eric.crane@mac.com> <encrane@datacor.com>
@@ -88,7 +88,10 @@ The following events are application and file-level events:
88
88
  - `on_quit` — event triggered when gloo is quitting
89
89
  - `on_save` — when an object is saved, this event is triggered
90
90
  - `on_reload` — event triggered when an object receives message to reload
91
- - `on_error` — event triggered when there is an error in the application
91
+ - `on_error` — event triggered when gloo hits a checked error condition (a bad path, a missing object, a verb used wrongly)
92
+ - `on_exception` — event triggered when gloo's safety net catches an unanticipated Ruby exception (rare in normal code; use the `throw` verb to exercise it)
93
+
94
+ `on_error` and `on_exception` are independent channels — a checked gloo error fires `on_error` only, an unhandled Ruby exception fires `on_exception` only, and neither triggers the other. In both cases the line that failed is abandoned and execution continues with the next line; a handler is a place to log or react, not a way to retry. Each handler reads its details from a sibling data container (`error_data` / `exception_data`) that the engine populates before running the script; the handler script and its data container can sit at the root of a file or be nested together inside a container.
92
95
 
93
96
  Some objects also have events that are triggered as part of their lifecycle. Here are some examples:
94
97
 
@@ -143,9 +146,22 @@ on_error [script] :
143
146
  error_data [can] :
144
147
  message [string] :
145
148
  backtrace [string] :
149
+
150
+
151
+ #
152
+ # Global Exception Handler.
153
+ # Same shape as on_error, with a sibling exception_data container.
154
+ # 'throw' deliberately raises a Ruby exception to exercise this.
155
+ #
156
+ on_exception [script] :
157
+ tell audit_exception.write to run
158
+
159
+ exception_data [can] :
160
+ message [string] :
161
+ backtrace [string] :
146
162
  ```
147
163
 
148
- See also: Load, Reload, Unload, Save, Quit.
164
+ See also: Load, Reload, Unload, Save, Quit, and the `throw` verb reference.
149
165
 
150
166
  ## Function Calls
151
167
 
data/docs/objects.md CHANGED
@@ -4,6 +4,8 @@ Everything in gloo is an object. Strings, numbers, containers, scripts, function
4
4
 
5
5
  Gloo ships with a large set of built-in object types, and core libraries and extensions can add more. This page doesn't try to cover them all — it walks through three common ones to get a feel for how objects work. For the complete list of object types, and every message each one supports, use the in-app help: enter `help` (or `?`), then `objects` to list them all, or `object {name}` for detail on one (see Application, Help).
6
6
 
7
+ An object's **type declares which messages it can receive** — `up` and `trim` for a `string`, `inc` for an `integer`, `run` for a `script`. An object doesn't have to have a type: with none it is `untyped` (short `any`), declared with a bare colon (`slot :`), with `[any]`, or by `create` with no `as`. An untyped object still takes the messages every object understands (`blank?`, `contains?`, `responds_to?`, `reload`, `unload`) — just not the type-specific ones. Untyped is the right choice when you only need to hold, compare, or show a value: a generic result slot, a config value passed straight through, or a field whose kind of value changes over its life.
8
+
7
9
  **Contents**
8
10
 
9
11
  - String
@@ -27,6 +29,8 @@ s [can] :
27
29
 
28
30
  Sending `up` to the string converts it to uppercase, in place. Sending `size` puts the character count into `it`. There are messages for lowercasing, counting words and lines, checking prefixes/suffixes, encoding, and generating random strings (UUIDs, hex, alphanumeric) — see the in-app help for the full list.
29
31
 
32
+ For yes/no messages that inspect state (`blank?`, `starts_with?`, `ends_with?`), the `check` verb reads better than `tell` — `check s.msg for starts_with? ("Hello")` — but it does the same thing (see Verbs, Tell).
33
+
30
34
  ## Container
31
35
 
32
36
  A container holds other objects — it's the closest thing gloo has to a folder, a hash, or a struct. Any object nested inside a container is reachable through a dotted pathname:
data/docs/plugins.md CHANGED
@@ -9,10 +9,6 @@
9
9
 
10
10
  Core Libraries extend gloo functionality, primarily by adding object types and potentially verbs.
11
11
 
12
- Be sure to load a core library (or extension) prior to loading a gloo file that includes object types defined in the library.
13
-
14
- Use the Load Verb to use an extension.
15
-
16
12
  A core library ships as its own gem (`gloo-<name>`). `load lib <name>` requires the gem, installing it first via `gem install` if it isn't already present — so the explicit `gem install` step below is optional, but doing it yourself ahead of time is recommended so the install doesn't happen mid-script. Once loaded, a library's objects and verbs show up in the `help`/`?` shell exactly like built-ins:
17
13
 
18
14
  ```gloo
@@ -21,6 +17,18 @@ A core library ships as its own gem (`gloo-<name>`). `load lib <name>` requires
21
17
  help> object yaml
22
18
  ```
23
19
 
20
+ A library's object types must be registered before any file that declares one is parsed. To use a library type in a script file, put the `load lib` at the **top of the file**, before the first object:
21
+
22
+ ```gloo
23
+ load lib yaml
24
+
25
+ settings [can] :
26
+ file [yaml] : ~/.myapp/settings.yml
27
+ ...
28
+ ```
29
+
30
+ The loader runs these top-of-file `load lib` / `load ext` lines before it builds the object tree, so declarations below them can use the library's types. Only `load lib` and `load ext` are recognized this way, and only above the first object — a `load lib` in a script still runs when that script runs, as before.
31
+
24
32
  ### Available Core Libraries
25
33
 
26
34
  - **CLI** — Use the `gloo-cli` gem when building CLI applications.
data/docs/verbs.md CHANGED
@@ -42,6 +42,14 @@ tell {path.to.object} to {message}
42
42
  > tell the.container to count
43
43
  ```
44
44
 
45
+ `check` is the same verb under another name — it sends a message exactly the way `tell` does. The two spellings exist so code reads like natural communication: use `tell` to trigger an action (`up`, `run`, `unload`), and `check` to investigate state with a yes/no question (`blank?`, `contains?`, `starts_with?`). The answer to a `check` lands in `it`, so it pairs naturally with `if` / `unless`.
46
+
47
+ ```gloo
48
+ > tell my.str to up
49
+ > check my.str for starts_with? ("HELLO")
50
+ > if it then show 'it does'
51
+ ```
52
+
45
53
  ## Put
46
54
 
47
55
  `put` evaluates an expression and stores the result in an object.
data/lib/VERSION CHANGED
@@ -1 +1 @@
1
- 6.2.1
1
+ 6.3.0
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,7 @@
1
+ 6.3.0 - 2026.08.31
2
+ - Adds option to load a library at the top of a script that includes types from that lib.
3
+
4
+
1
5
  6.2.1 - 2026.08.25
2
6
  - Fixes bug starting in app mode.
3
7
 
@@ -47,12 +47,24 @@ module Gloo
47
47
  {
48
48
  :name => KEYWORD,
49
49
  :shortcut => KEYWORD_SHORT,
50
- :description => 'An untyped object. If no type is specified ' \
51
- 'when an object is created it will be of this type.',
50
+ :description => 'An object with no type. A type declares ' \
51
+ 'which messages an object can receive; an untyped object ' \
52
+ 'receives only the base object messages (blank?, ' \
53
+ 'contains?, responds_to?, reload, unload) but can hold a ' \
54
+ 'value of any kind. If no type is specified when an object ' \
55
+ 'is created, it is untyped. Prefer untyped when you only ' \
56
+ 'need to hold, compare, or show a value and do not need ' \
57
+ 'type-specific behaviour.',
52
58
  :examples => <<~EXAMPLES.strip
53
59
  > create x
54
60
  > put 1 into x
55
61
  > put 'string' into x
62
+
63
+ #
64
+ # Declared with a bare colon, or with [any]:
65
+ #
66
+ slot :
67
+ flag [any] :
56
68
  EXAMPLES
57
69
  }
58
70
  end
@@ -12,6 +12,14 @@ module Gloo
12
12
  END_BLOCK = 'END'.freeze
13
13
  SPACE_CNT = 2
14
14
 
15
+ # A 'load lib {name}' (or 'load ext {name}') statement at the top
16
+ # of a file, before the first object declaration. It makes a core
17
+ # library's object types available to the declarations that
18
+ # follow. Same syntax as the load verb, but the loader runs it
19
+ # before building the object tree instead of a script running it
20
+ # afterward.
21
+ LIB_DIRECTIVE = /\A(?:load|ld)\s+(?:lib|ext)\s+\S+\s*\z/i.freeze
22
+
15
23
  attr_reader :obj
16
24
 
17
25
  #
@@ -27,6 +35,7 @@ module Gloo
27
35
  @exiting_multiline = false
28
36
  @in_block = false
29
37
  @block_value = ''
38
+ @body_started = false
30
39
  @debug = false
31
40
  end
32
41
 
@@ -51,11 +60,28 @@ module Gloo
51
60
  f.each_line do |line|
52
61
  next if skip_line? line
53
62
 
63
+ if !@body_started && line =~ LIB_DIRECTIVE
64
+ run_lib_directive line
65
+ next
66
+ end
67
+ @body_started = true
68
+
54
69
  handle_one_line line
55
70
  end
56
71
  end
57
72
 
58
- #
73
+ #
74
+ # Run a top-of-file 'load lib {name}' directive. It goes through
75
+ # the same load verb a script would use, so libraries are loaded
76
+ # and their object types registered before the declarations that
77
+ # depend on them are parsed.
78
+ #
79
+ def run_lib_directive( line )
80
+ @engine.log.debug "Loading file directive: #{line.strip}"
81
+ @engine.parser.run line.strip
82
+ end
83
+
84
+ #
59
85
  # Join continuation lines.
60
86
  #
61
87
  def join_continuations( data )
@@ -60,9 +60,13 @@ module Gloo
60
60
  {
61
61
  :name => KEYWORD,
62
62
  :shortcut => KEYWORD_SHORT,
63
- :description => 'Send a message to an object to check for an ' \
64
- 'object condition. This is the same as the tell verb, but ' \
65
- 'gives better syntactical naming.',
63
+ :description => 'Send a message to an object to investigate ' \
64
+ 'its state typically a yes/no question such as blank?, ' \
65
+ 'contains? or starts_with?. Mechanically identical to the ' \
66
+ 'tell verb; by convention check is used to investigate ' \
67
+ 'state while tell is used to trigger an action. Using the ' \
68
+ 'verb that matches intent makes the code read like ' \
69
+ 'natural communication.',
66
70
  :syntax => [ 'check {path.to.object} for {condition_message}' ],
67
71
  :parameters => [
68
72
  '{path.to.object} — The object that we want to send a condition_message to.',
@@ -74,7 +78,7 @@ module Gloo
74
78
  "#{UNKNOWN_MSG_ERR} — No message was specified, or the 'for' keyword is missing.",
75
79
  'Object was not found — The target of the message was not found.'
76
80
  ],
77
- :examples => <<~EXAMPLES.strip
81
+ :examples => <<~EXAMPLES.strip,
78
82
  #
79
83
  # Base object checks.
80
84
  #
@@ -95,6 +99,14 @@ module Gloo
95
99
  check obj.msg for blank?
96
100
  show it
97
101
  EXAMPLES
102
+ :notes => <<~NOTES.strip
103
+ check and tell send the same message the same way — the two
104
+ spellings exist for readability. Use check for state questions
105
+ (blank?, contains?, responds_to?, starts_with?) and tell for
106
+ action messages (up, inc, run, unload). The answer to a check
107
+ lands in it, so it pairs naturally with if / unless:
108
+ 'check my.str for blank?' then 'if it then ...'.
109
+ NOTES
98
110
  }
99
111
  end
100
112
 
@@ -88,10 +88,15 @@ module Gloo
88
88
  '{type} — The type of the new object. Optional; if not provided the object will be untyped.',
89
89
  "{value} — The initial value for the new object. Optional; if not provided the object will have the default value for the type."
90
90
  ],
91
- :result => "The new object will be created and added to the " \
92
- "object heap. It will be set to the new object's initial value.",
91
+ :result => "If nothing exists at the path, a new object is " \
92
+ "created with the given (or type-default) value and added " \
93
+ "to the object tree, and it is set to that value. If an " \
94
+ "object already exists at the path it is left unchanged — " \
95
+ "the type and value given here are ignored — and it is set " \
96
+ "to the existing object's current value.",
93
97
  :errors => [
94
- "#{NO_NAME_ERR} — The name of the object was not specified and the object cannot be created."
98
+ "#{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.'
95
100
  ],
96
101
  :examples => <<~EXAMPLES.strip
97
102
  # Basic examples of creating an object from the gloo shell:
@@ -49,19 +49,33 @@ module Gloo
49
49
  {
50
50
  :name => KEYWORD,
51
51
  :shortcut => KEYWORD_SHORT,
52
- :description => 'Evaluate an expression and put the result into it.',
52
+ :description => 'Evaluate an expression and put the result ' \
53
+ 'into it, without any other action. Unlike put it does ' \
54
+ 'not store the result in a named object, and unlike show ' \
55
+ 'it does not print it. Primarily used in tests: assert ' \
56
+ 'and refute check the value of it, so eval is the bridge ' \
57
+ 'for expressions and comparisons that do not already ' \
58
+ 'leave a boolean there.',
53
59
  :syntax => [ 'eval {expression}' ],
54
60
  :parameters => [
55
61
  '{expression} — The expression that will be evaluated. The expression is optional. If not provided, eval will result in true.'
56
62
  ],
57
63
  :result => 'The result of the expression evaluation is put ' \
58
64
  'into it. No other action is performed.',
59
- :examples => <<~EXAMPLES.strip
65
+ :examples => <<~EXAMPLES.strip,
60
66
  > eval "me"
61
67
  > eval "hello " "world"
62
68
  > eval 132 * 23
63
69
  > eval path.to.num = 1
64
70
  EXAMPLES
71
+ :notes => <<~NOTES.strip
72
+ eval was added for the gloo-test framework. assert and refute
73
+ only look at it; the common pattern is to run a verb or
74
+ message, then 'eval it = {expected}' to compare the result to
75
+ an expected value, then assert or refute. Bare eval (or its
76
+ shortcut noop) sets it to true, which pairs with assert for
77
+ 'this state should hold' checks.
78
+ NOTES
65
79
  }
66
80
  end
67
81
 
@@ -61,8 +61,12 @@ module Gloo
61
61
  {
62
62
  :name => KEYWORD,
63
63
  :shortcut => KEYWORD_SHORT,
64
- :description => 'Send a message to an object. Ask the object ' \
65
- 'to perform an action.',
64
+ :description => 'Send a message to an object, asking it to ' \
65
+ 'perform an action. Mechanically identical to the check ' \
66
+ 'verb; by convention tell is used to trigger an action ' \
67
+ 'or change state, while check is used to investigate ' \
68
+ 'state. Using the verb that matches intent makes the ' \
69
+ 'code read like natural communication.',
66
70
  :syntax => [ 'tell {path.to.object} to {message}' ],
67
71
  :parameters => [
68
72
  '{path.to.object} — The object that we want to send a message to.',
@@ -73,12 +77,20 @@ module Gloo
73
77
  "#{UNKNOWN_MSG_ERR} — No message was specified, or the `to` keyword is missing.",
74
78
  'Object was not found — The target of the message was not found.'
75
79
  ],
76
- :examples => <<~EXAMPLES.strip
80
+ :examples => <<~EXAMPLES.strip,
77
81
  > tell an.obj to unload
78
82
  > tell the.script to run
79
83
  > tell my.str to up
80
84
  > tell the.container to count
81
85
  EXAMPLES
86
+ :notes => <<~NOTES.strip
87
+ tell and check send the same message the same way — the two
88
+ spellings exist for readability. Use tell for action messages
89
+ (up, inc, run, unload, randomize) and check for state questions
90
+ (blank?, contains?, starts_with?). Either verb will accept
91
+ either kind of message, but mixing them reads wrong:
92
+ 'tell my.str for blank?' works but should be a check.
93
+ NOTES
82
94
  }
83
95
  end
84
96
 
@@ -72,10 +72,38 @@ module Gloo
72
72
  :result => "Fires the app's on_exception handler, if one is " \
73
73
  'defined, with exception_data.message set to the thrown ' \
74
74
  'message. Does not fire on_error.',
75
- :examples => <<~EXAMPLES.strip
75
+ :examples => <<~EXAMPLES.strip,
76
76
  > throw
77
77
  > throw "custom message"
78
+
79
+ #
80
+ # Execution continues with the line after the throw.
81
+ #
82
+ demo [container] :
83
+ on_exception [script] :
84
+ show 'caught: ' + ^.exception_data.message
85
+ exception_data [can] :
86
+ message [string] :
87
+ backtrace [string] :
88
+ on_load [script] :
89
+ throw "boom"
90
+ show 'still running'
78
91
  EXAMPLES
92
+ :notes => <<~NOTES.strip
93
+ on_error and on_exception are independent channels: a checked
94
+ gloo error (bad path, missing object) fires on_error only; a
95
+ Ruby exception (from throw, or a real bug caught by gloo's
96
+ safety net) fires on_exception only. Neither triggers the
97
+ other, and in both cases the failing line is abandoned and
98
+ execution continues with the next line.
99
+
100
+ gloo logs every caught exception, so a throw during a script
101
+ run also prints the message and a truncated backtrace to the
102
+ console — that is the logging working, not a crash.
103
+
104
+ See gloo_sample_code/lang/exceptions.gloo for a runnable
105
+ walk-through.
106
+ NOTES
79
107
  }
80
108
  end
81
109
 
@@ -0,0 +1,39 @@
1
+ #
2
+ # Line continuation tests
3
+ #
4
+ # A trailing \ continues a statement on the next line.
5
+ #
6
+
7
+ tests [can] :
8
+ lang [can] :
9
+ continuation [can] :
10
+
11
+ one [string] : Hello
12
+ two [string] : World
13
+ joined [string] :
14
+ split [string] :
15
+ flag [bool] : false
16
+
17
+ split_statement_matches_single_line [test] :
18
+ description [string] : a statement broken with \ produces the same result as one line.
19
+ on_test [script] :
20
+ put ^^.one and ' ' and ^^.two into ^^.joined
21
+
22
+ put ^^.one and \
23
+ ' ' and \
24
+ ^^.two into ^^.split
25
+
26
+ eval ^^.split = ^^.joined
27
+ assert "expected the continued statement to match the single-line one"
28
+
29
+ eval ^^.split = 'Hello World'
30
+ assert "expected the joined value"
31
+
32
+ continuation_in_an_if [test] :
33
+ description [string] : \ works inside an if statement.
34
+ on_test [script] :
35
+ put false into ^^.flag
36
+ if ^^.one = 'Hello' \
37
+ then put true into ^^.flag
38
+ eval ^^.flag = true
39
+ assert "expected the continued if to run its then branch"
@@ -0,0 +1,70 @@
1
+ #
2
+ # Value conversion tests
3
+ #
4
+ # 'put' converts the value to the target object's type.
5
+ #
6
+
7
+ tests [can] :
8
+ lang [can] :
9
+ convert [can] :
10
+
11
+ i [integer] : 0
12
+ d [decimal] : 0
13
+ s [string] : x
14
+ b [boolean] : false
15
+
16
+ string_to_number [test] :
17
+ description [string] : a string is converted to the leading number for an int or decimal target.
18
+ on_test [script] :
19
+ put '3 third time' into ^^.i
20
+ eval ^^.i = 3
21
+ assert "expected the leading integer to be parsed"
22
+
23
+ put '3.12 more' into ^^.d
24
+ eval ^^.d = 3.12
25
+ assert "expected the leading decimal to be parsed"
26
+
27
+ put 'notanumber' into ^^.i
28
+ eval ^^.i = 0
29
+ assert "expected a non-numeric string to convert to 0"
30
+
31
+ number_to_number [test] :
32
+ description [string] : int and decimal convert to each other; decimal to int truncates.
33
+ on_test [script] :
34
+ put 5 into ^^.d
35
+ eval ^^.d = 5.0
36
+ assert "expected the integer to widen to a decimal"
37
+
38
+ put 9.99 into ^^.i
39
+ eval ^^.i = 9
40
+ assert "expected the decimal to truncate toward zero"
41
+
42
+ anything_to_string [test] :
43
+ description [string] : a number or boolean put into a string becomes its text form.
44
+ on_test [script] :
45
+ put 123 into ^^.s
46
+ eval ^^.s = '123'
47
+ assert "expected the number to become a string"
48
+
49
+ put true into ^^.s
50
+ eval ^^.s = 'true'
51
+ assert "expected the boolean to become a string"
52
+
53
+ to_boolean [test] :
54
+ description [string] : strings (case-insensitive) and numbers convert to a boolean.
55
+ on_test [script] :
56
+ put 'false' into ^^.b
57
+ eval ^^.b = false
58
+ assert "expected 'false' to convert to false"
59
+
60
+ put 'TRUE' into ^^.b
61
+ eval ^^.b = true
62
+ assert "expected 'TRUE' to convert to true, case-insensitively"
63
+
64
+ put 0 into ^^.b
65
+ eval ^^.b = false
66
+ assert "expected 0 to convert to false"
67
+
68
+ put 42 into ^^.b
69
+ eval ^^.b = true
70
+ assert "expected a non-zero number to convert to true"
@@ -0,0 +1,41 @@
1
+ #
2
+ # Here / parent reference tests
3
+ #
4
+ # From inside a script, ^ is the script's containing object, ^^ its
5
+ # parent, ^^^ its grandparent, and so on.
6
+ #
7
+
8
+ tests [can] :
9
+ lang [can] :
10
+ here [can] :
11
+
12
+ s [string] : level 0
13
+
14
+ r1 [string] :
15
+ r2 [string] :
16
+ r3 [string] :
17
+
18
+ a [can] :
19
+ s [string] : level 1
20
+ b [can] :
21
+ s [string] : level 2
22
+ c [can] :
23
+ s [string] : level 3
24
+ capture [script] :
25
+ put ^.s into tests.lang.here.r1
26
+ put ^^.s into tests.lang.here.r2
27
+ put ^^^.s into tests.lang.here.r3
28
+
29
+ caret_walks_up_the_tree [test] :
30
+ description [string] : ^, ^^, ^^^ resolve relative to the running script's location.
31
+ on_test [script] :
32
+ tell tests.lang.here.a.b.c.capture to run
33
+
34
+ eval ^^.r1 = 'level 3'
35
+ assert "expected ^ to be the script's own container"
36
+
37
+ eval ^^.r2 = 'level 2'
38
+ assert "expected ^^ to be the parent container"
39
+
40
+ eval ^^.r3 = 'level 1'
41
+ assert "expected ^^^ to be the grandparent container"
@@ -0,0 +1,41 @@
1
+ #
2
+ # 'it' tests
3
+ #
4
+ # 'it' holds the result of the last statement that produced one.
5
+ #
6
+
7
+ tests [can] :
8
+ lang [can] :
9
+ it [can] :
10
+
11
+ n [int] : 5
12
+ r [string] :
13
+
14
+ set_by_the_previous_statement [test] :
15
+ description [string] : show, eval, tell and put all leave their result in it.
16
+ on_test [script] :
17
+ show 3 + 4
18
+ eval it = 7
19
+ assert "expected show to leave the evaluated value in it"
20
+
21
+ eval 10 * 2
22
+ eval it = 20
23
+ assert "expected eval to leave its result in it"
24
+
25
+ put 'hello' into ^^.r
26
+ eval it = 'hello'
27
+ assert "expected put to leave the stored value in it"
28
+
29
+ put 5 into ^^.n
30
+ tell ^^.n to inc
31
+ eval it = 6
32
+ assert "expected tell to leave the message result in it"
33
+
34
+ persists_across_statements_that_produce_nothing [test] :
35
+ description [string] : statements like a bare show or list leave it untouched.
36
+ on_test [script] :
37
+ eval 42
38
+ show
39
+ list ^^.n
40
+ eval it = 42
41
+ assert "expected it to survive statements that don't produce a result"
@@ -0,0 +1,61 @@
1
+ #
2
+ # Literal value tests
3
+ #
4
+
5
+ tests [can] :
6
+ lang [can] :
7
+ literal [can] :
8
+
9
+ s [string] :
10
+ i [integer] :
11
+ d [decimal] :
12
+ b [boolean] :
13
+
14
+ string_quote_styles [test] :
15
+ description [string] : single and double quotes both make a string; the other quote can appear inside.
16
+ on_test [script] :
17
+ put 'single quoted' into ^^.s
18
+ eval ^^.s = 'single quoted'
19
+ assert "expected single quotes to work"
20
+
21
+ put "double quoted" into ^^.s
22
+ eval ^^.s = 'double quoted'
23
+ assert "expected double quotes to work"
24
+
25
+ put "you're fine" into ^^.s
26
+ eval ^^.s = "you're fine"
27
+ assert "expected an apostrophe inside double quotes to be literal"
28
+
29
+ number_literals [test] :
30
+ description [string] : integer and decimal literals, including negatives.
31
+ on_test [script] :
32
+ put -5 into ^^.i
33
+ eval ^^.i = -5
34
+ assert "expected a negative integer literal"
35
+
36
+ put 3.14159 into ^^.d
37
+ eval ^^.d = 3.14159
38
+ assert "expected a decimal literal"
39
+
40
+ put -2.5 into ^^.d
41
+ eval ^^.d = -2.5
42
+ assert "expected a negative decimal literal"
43
+
44
+ boolean_literals_are_case_insensitive [test] :
45
+ description [string] : TRUE, true, False, FALSE all parse.
46
+ on_test [script] :
47
+ put TRUE into ^^.b
48
+ eval ^^.b = true
49
+ assert "expected TRUE to parse"
50
+
51
+ put true into ^^.b
52
+ eval ^^.b = true
53
+ assert "expected true to parse"
54
+
55
+ put False into ^^.b
56
+ eval ^^.b = false
57
+ assert "expected False to parse"
58
+
59
+ put FALSE into ^^.b
60
+ eval ^^.b = false
61
+ assert "expected FALSE to parse"