about summary refs log tree commit diff stats
path: root/sandbox/011-errors.mu
blob: 2f59d1fe747214d869e8f57e81151f3ffc2c52e7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
[Literate Programming](https://en.wikipedia.org/wiki/Literate_programming)
tool to convert Mu's layers into compilable form.

Mu's tangling directives differ from Knuth's classic implementation. The
classical approach starts out with labeled subsystems that are initially
empty, and adds code to them using two major directives:

```
<name> ≡
<code>
```

```
<name> +≡
<code>
```

_(`<code>` can span multiple lines.)_

This approach is best suited for top-down exposition.

On the other hand, Mu's tangling directives are better suited for a cleaned-up
history of a codebase. Subsystems start out with a simple skeleton of the core
of the program. Later versions then tell a story of the evolution of the
program, with each version colocating all the code related to new features.

Read more:
* http://akkartik.name/post/wart-layers
* http://akkartik.name/post/literate-programming
* https://github.com/akkartik/mu/blob/master/000organization.cc

## directives

Add code to a project:

```
:(code)
<code>
```

Insert code before a specific line:

```
:(before <waypoint>)
<code>
```

Here `<waypoint>` is a substring matching a single line in the codebase. (We
never use regular expressions.) Surround the substring in `"` quotes if it
spans multiple words.

Insert code _after_ a specific line:

```
:(after <waypoint>)
<code>
```

Delete a specific previously-added line (because it's not needed in a newer
version).

```
:(delete <line>)
```

Delete a block of code starting with a given header and surrounded by `{` and
`}`:

```
:(delete{} <header>)
```

_(Caveat: doesn't directly support C's `do`..`while` loops.)_

Replace a specific line with new code:

```
:(replace <line>)
<code>
```

This is identical to:
```
:(before <line>)
<code>
:(delete <line>)
```
_(Assuming `<code>` did not insert a new line matching the substring `<line>`.)_

Replace a block of code with another:

```
:(replace{} <header>)
<code>
```

Insert code before or after a substring pattern that isn't quite a unique
waypoint in the whole codebase:

```
:(before <line> following <waypoint>)
<code>
:(after <line> following <waypoint>)
<code>
```

```
:(before <waypoint> then <line>)
<code>
:(after <waypoint> then <line>)
<code>
```
' href='#n568'>568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
## handling malformed programs

container environment [
  recipe-errors:text
]

# load code from disk, save any errors
def! update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, screen:&:screen [
  local-scope
  load-inputs
  in:text <- slurp resources, [lesson/recipes.mu]
  recipe-errors:text <- reload in
  *env <- put *env, recipe-errors:offset, recipe-errors
  # if recipe editor has errors, stop
  {
    break-unless recipe-errors
    update-status screen, [errors found     ], 1/red
    errors-found? <- copy true
    return
  }
  errors-found? <- copy false
]

before <end-render-components> [
  trace 11, [app], [render status]
  recipe-errors:text <- get *env, recipe-errors:offset
  {
    break-unless recipe-errors
    update-status screen, [errors found     ], 1/red
  }
]

container environment [
  error-index:num  # index of first sandbox with an error (or -1 if none)
]

after <programming-environment-initialization> [
  *result <- put *result, error-index:offset, -1
]

after <begin-run-sandboxes> [
  *env <- put *env, error-index:offset, -1
]

before <end-run-sandboxes> [
  {
    error-index:num <- get *env, error-index:offset
    sandboxes-completed-successfully?:bool <- equal error-index, -1
    break-if sandboxes-completed-successfully?
    errors-found? <- copy true
  }
]

before <end-render-components> [
  {
    break-if recipe-errors
    error-index:num <- get *env, error-index:offset
    sandboxes-completed-successfully?:bool <- equal error-index, -1
    break-if sandboxes-completed-successfully?
    error-index-text:text <- to-text error-index
    status:text <- interpolate [errors found (_)    ], error-index-text
    update-status screen, status, 1/red
  }
]

container sandbox [
  errors:text
]

def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
  local-scope
  load-inputs
  {
    recipe-errors:text <- get *env, recipe-errors:offset
    break-unless recipe-errors
    *sandbox <- put *sandbox, errors:offset, recipe-errors
    return
  }
  data:text <- get *sandbox, data:offset
  response:text, errors:text, fake-screen:&:screen, trace:text, completed?:bool <- run-sandboxed data
  *sandbox <- put *sandbox, response:offset, response
  *sandbox <- put *sandbox, errors:offset, errors
  *sandbox <- put *sandbox, screen:offset, fake-screen
  *sandbox <- put *sandbox, trace:offset, trace
  {
    break-if errors
    break-if completed?
    errors <- new [took too long!
]
    *sandbox <- put *sandbox, errors:offset, errors
  }
  {
    break-unless errors
    error-index:num <- get *env, error-index:offset
    error-not-set?:bool <- equal error-index, -1
    break-unless error-not-set?
    *env <- put *env, error-index:offset, idx
  }
]

# make sure we render any trace
after <render-sandbox-trace-done> [
  {
    sandbox-errors:text <- get *sandbox, errors:offset
    break-unless sandbox-errors
    *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0  # no response
    {
      break-unless env
      recipe-errors:text <- get *env, recipe-errors:offset
      row, screen <- render-text screen, recipe-errors, left, right, 1/red, row
    }
    row, screen <- render-text screen, sandbox-errors, left, right, 1/red, row
    # don't try to print anything more for this sandbox
    jump +render-sandbox-end
  }
]

scenario run-shows-errors-in-get [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo [|
      |  get 123:num, foo:offset|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo]
  render-all screen, env, render
  screen-should-contain [
    .                               run (F4)           .
    .foo                                               .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .foo: unknown element 'foo' in container 'number'  .
    .foo: first ingredient of 'get' should be a contai.
    .ner, but got '123:num'                            .
  ]
  screen-should-contain-in-color 1/red, [
    .  errors found                                    .
    .                                                  .
  ]
]

scenario run-updates-status-with-first-erroneous-sandbox [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
  ]
  env:&:environment <- new-programming-environment resources, screen, []
  render-all screen, env, render
  assume-console [
    # create invalid sandbox 1
    type [get foo, x:offset]
    press F4
    # create invalid sandbox 0
    type [get foo, x:offset]
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # status line shows that error is in first sandbox
  screen-should-contain [
    .  errors found (0)             run (F4)           .
  ]
]

scenario run-updates-status-with-first-erroneous-sandbox-2 [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
  ]
  env:&:environment <- new-programming-environment resources, screen, []
  render-all screen, env, render
  assume-console [
    # create invalid sandbox 2
    type [get foo, x:offset]
    press F4
    # create invalid sandbox 1
    type [get foo, x:offset]
    press F4
    # create valid sandbox 0
    type [add 2, 2]
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # status line shows that error is in second sandbox
  screen-should-contain [
    .  errors found (1)             run (F4)           .
  ]
]

scenario run-hides-errors-from-past-sandboxes [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
  ]
  env:&:environment <- new-programming-environment resources, screen, [get foo, x:offset]  # invalid
  render-all screen, env, render
  assume-console [
    press F4  # generate error
  ]
  event-loop screen, console, env, resources
  assume-console [
    left-click 3, 10
    press ctrl-k
    type [add 2, 2]  # valid code
    press F4  # update sandbox
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # error should disappear
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

scenario run-updates-errors-for-shape-shifting-recipes [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # define a shape-shifting recipe with an error
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo x:_elem -> z:_elem [|
      |  local-scope|
      |  load-ingredients|
      |  y:&:num <- copy null|
      |  z <- add x, y|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo 2]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  event-loop screen, console, env, resources
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo 2                                             .
    .foo_2: 'add' requires number ingredients, but got.
    . 'y'                                              .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # now rerun everything
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # error should remain unchanged
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo 2                                             .
    .foo_3: 'add' requires number ingredients, but got.
    . 'y'                                              .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

scenario run-avoids-spurious-errors-on-reloading-shape-shifting-recipes [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # overload a well-known shape-shifting recipe
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe length l:&:list:_elem -> n:num [|
      |]|
    ]
  ]
  # call code that uses other variants of it, but not it itself
  test-sandbox:text <- new [x:&:list:num <- copy null
to-text x]
  env:&:environment <- new-programming-environment resources, screen, test-sandbox
  render-all screen, env, render
  # run it once
  assume-console [
    press F4
  ]
  event-loop screen, console, env, resources
  # no errors anywhere on screen (can't check anything else, since to-text will return an address)
  screen-should-contain-in-color 1/red, [
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
    .             <-                                   .
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
  ]
  # rerun everything
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # still no errors
  screen-should-contain-in-color 1/red, [
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
    .             <-                                   .
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
  ]
]

scenario run-shows-missing-type-errors [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo [|
      |  x <- copy 0|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .foo: missing type for 'x' in 'x <- copy 0'        .
  ]
]

scenario run-shows-unbalanced-bracket-errors [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # recipe is incomplete (unbalanced '[')
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo \\\[|
      |  x <- copy 0|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .9: unbalanced '\\[' for recipe                      .
    .9: unbalanced '\\[' for recipe                      .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

scenario run-shows-get-on-non-container-errors [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo [|
      |  local-scope|
      |  x:&:point <- new point:type|
      |  get x:&:point, 1:offset|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .foo: first ingredient of 'get' should be a contai.
    .ner, but got 'x:&:point'                          .
  ]
]

scenario run-shows-non-literal-get-argument-errors [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo [|
      |  local-scope|
      |  x:num <- copy 0|
      |  y:&:point <- new point:type|
      |  get *y:&:point, x:num|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .foo: second ingredient of 'get' should have type .
    .'offset', but got 'x:num'                         .
  ]
]

scenario run-shows-errors-every-time [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # try to run a file with an error
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo [|
      |  local-scope|
      |  x:num <- copy y:num|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  event-loop screen, console, env, resources
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .foo: tried to read ingredient 'y' in 'x:num <- co.
    .py y:num' but it hasn't been written to yet       .
  ]
  # rerun the file, check for the same error
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found                 run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .foo: tried to read ingredient 'y' in 'x:num <- co.
    .py y:num' but it hasn't been written to yet       .
  ]
]

scenario run-instruction-and-print-errors [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 15/height
  assume-resources [
  ]
  # editor contains an illegal instruction
  env:&:environment <- new-programming-environment resources, screen, [get 1:&:point, 1:offset]
  render-all screen, env, render
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # check that screen prints error message in red
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .get 1:&:point, 1:offset                           .
    .first ingredient of 'get' should be a container, .
    .but got '1:&:point'                               .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  screen-should-contain-in-color 1/red, [
    .  errors found (0)                                .
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
    .first ingredient of 'get' should be a container,  .
    .but got '1:&:point'                               .
    .                                                  .
    .                                                  .
  ]
]

scenario run-instruction-and-print-errors-only-once [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 10/height
  assume-resources [
  ]
  # editor contains an illegal instruction
  env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset]
  render-all screen, env, render
  # run the code in the editors multiple times
  assume-console [
    press F4
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # check that screen prints error message just once
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .get 1234:num, foo:offset                          .
    .unknown element 'foo' in container 'number'       .
    .first ingredient of 'get' should be a container, .
    .but got '1234:num'                                .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

scenario sandbox-can-handle-infinite-loop [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  assume-resources [
  ]
  # editor contains an infinite loop
  test-sandbox:text <- new [{
loop
}]
  env:&:environment <- new-programming-environment resources, screen, test-sandbox
  render-all screen, env, render
  # run the sandbox
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .{                                                 .
    .loop                                              .
    .}                                                 .
    .took too long!                                    .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

scenario sandbox-with-errors-shows-trace [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # generate a stash and a error
  assume-resources [
    [lesson/recipes.mu] <- [
      |recipe foo [|
      |  local-scope|
      |  a:num <- next-ingredient|
      |  b:num <- next-ingredient|
      |  stash [dividing by], b|
      |  _, c:num <- divide-with-remainder a, b|
      |  reply b|
      |]|
    ]
  ]
  env:&:environment <- new-programming-environment resources, screen, [foo 4, 0]
  render-all screen, env, render
  # run
  assume-console [
    press F4
  ]
  event-loop screen, console, env, resources
  # screen prints error message
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo 4, 0                                          .
    .foo: divide by zero in '_, c:num <- divide-with-r.
    .emainder a, b'                                    .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # click on the call in the sandbox
  assume-console [
    left-click 4, 15
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # screen should expand trace
  screen-should-contain [
    .  errors found (0)             run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo 4, 0                                          .
    .dividing by 0                                     .
    .14 instructions run                               .
    .foo: divide by zero in '_, c:num <- divide-with-r.
    .emainder a, b'                                    .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]