about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-05-10 11:38:18 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-05-10 11:40:33 -0700
commit4071055aeed22737366b3cb863b24a59f0625a28 (patch)
tree671bfd7bee9d5d75f5890bc78d1106719568fcde
parent6b16a2ef6b12eedc14f2a7652bf8d977c8192b6e (diff)
downloadmu-4071055aeed22737366b3cb863b24a59f0625a28.tar.gz
1327 - better error handling in chessboard
Also a bugfix in break to label, because I noticed the screen wasn't
being cleaned up on quit.
-rw-r--r--020run.cc3
-rw-r--r--023jump.cc1
-rw-r--r--026assert.cc3
-rw-r--r--037call_reply.cc42
-rw-r--r--047jump_label.cc26
-rw-r--r--050scenario.cc13
-rw-r--r--chessboard.mu142
-rw-r--r--mu.vim2
-rw-r--r--termbox/termbox.c12
9 files changed, 187 insertions, 57 deletions
diff --git a/020run.cc b/020run.cc
index f92e2775..4a36b379 100644
--- a/020run.cc
+++ b/020run.cc
@@ -130,7 +130,8 @@ if (argc > 1) {
 :(before "End Main")
 if (!Run_tests) {
   setup();
-  Trace_stream = new trace_stream;
+//?   Trace_file = "interactive"; //? 1
+  START_TRACING_UNTIL_END_OF_SCOPE;
 //?   Trace_stream->dump_layer = "all"; //? 2
   transform_all();
   recipe_number r = Recipe_number[string("main")];
diff --git a/023jump.cc b/023jump.cc
index 807113f3..8349f44e 100644
--- a/023jump.cc
+++ b/023jump.cc
@@ -42,6 +42,7 @@ Recipe_number["jump-if"] = JUMP_IF;
 :(before "End Primitive Recipe Implementations")
 case JUMP_IF: {
   assert(current_instruction().ingredients.at(1).initialized);
+  trace("AAA") << current_instruction().to_string() << '\n';
   assert(ingredients.size() == 2);
   assert(ingredients.at(0).size() == 1);  // scalar
   if (!ingredients.at(0).at(0)) {
diff --git a/026assert.cc b/026assert.cc
index 7217d5c2..50456001 100644
--- a/026assert.cc
+++ b/026assert.cc
@@ -15,9 +15,8 @@ case ASSERT: {
   assert(ingredients.at(0).size() == 1);  // scalar
   if (!ingredients.at(0).at(0)) {
     assert(isa_literal(current_instruction().ingredients.at(1)));
-//?     tb_shutdown(); //? 1
+    tb_shutdown();
     raise << current_instruction().ingredients.at(1).name << '\n' << die();
-//?     exit(0); //? 1
   }
   break;
 }
diff --git a/037call_reply.cc b/037call_reply.cc
index 81f9c7c2..e44fe205 100644
--- a/037call_reply.cc
+++ b/037call_reply.cc
@@ -62,6 +62,7 @@ recipe f [
 //: ingredients unless they're also products. The /same-as-ingredient inside
 //: the recipe's 'reply' will help catch accidental misuse of such
 //: 'ingredient-results' (sometimes called in-out parameters in other languages).
+
 :(scenario reply_same_as_ingredient)
 % Hide_warnings = true;
 recipe main [
@@ -90,3 +91,44 @@ string to_string(const vector<long long int>& in) {
   out << "]";
   return out.str();
 }
+
+//: Conditional reply.
+
+:(scenario reply_if)
+recipe main [
+  1:integer <- test1
+]
+recipe test1 [
+  reply-if 0:literal, 34:literal
+  reply 35:literal
+]
++mem: storing 35 in location 1
+
+:(scenario reply_if2)
+recipe main [
+  1:integer <- test1
+]
+recipe test1 [
+  reply-if 1:literal, 34:literal
+  reply 35:literal
+]
++mem: storing 34 in location 1
+
+:(before "End Rewrite Instruction(curr)")
+// rewrite `reply-if a, b, c, ...` to
+//   ```
+//   jump-unless a, 1:offset
+//   reply b, c, ...
+//   ```
+if (curr.name == "reply-if") {
+  assert(curr.products.empty());
+  curr.operation = Recipe_number["jump-unless"];
+  vector<reagent> results;
+  copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
+  curr.ingredients.resize(1);
+  curr.ingredients.push_back(reagent("1:offset"));
+  result.steps.push_back(curr);
+  curr.clear();
+  curr.operation = Recipe_number["reply"];
+  curr.ingredients.swap(results);
+}
diff --git a/047jump_label.cc b/047jump_label.cc
index 9bc0c442..c9dcb4f5 100644
--- a/047jump_label.cc
+++ b/047jump_label.cc
@@ -24,25 +24,25 @@ void transform_labels(const recipe_number r) {
     instruction& inst = Recipe[r].steps.at(i);
     if (inst.operation == Recipe_number["jump"]) {
 //?       cerr << inst.to_string() << '\n'; //? 1
-      replace_offset(inst.ingredients.at(0), offset, r);
+      replace_offset(inst.ingredients.at(0), offset, i, r);
     }
     if (inst.operation == Recipe_number["jump-if"] || inst.operation == Recipe_number["jump-unless"]) {
-      replace_offset(inst.ingredients.at(1), offset, r);
+      replace_offset(inst.ingredients.at(1), offset, i, r);
     }
     if ((inst.operation == Recipe_number["loop"] || inst.operation == Recipe_number["break"])
         && inst.ingredients.size() == 1) {
-      replace_offset(inst.ingredients.at(0), offset, r);
+      replace_offset(inst.ingredients.at(0), offset, i, r);
     }
     if ((inst.operation == Recipe_number["loop-if"] || inst.operation == Recipe_number["loop-unless"]
             || inst.operation == Recipe_number["break-if"] || inst.operation == Recipe_number["break-unless"])
         && inst.ingredients.size() == 2) {
-      replace_offset(inst.ingredients.at(1), offset, r);
+      replace_offset(inst.ingredients.at(1), offset, i, r);
     }
   }
 }
 
 :(code)
-void replace_offset(reagent& x, /*const*/ map<string, index_t>& offset, const recipe_number r) {
+void replace_offset(reagent& x, /*const*/ map<string, index_t>& offset, const index_t current_offset, const recipe_number r) {
 //?   cerr << "AAA " << x.to_string() << '\n'; //? 1
   assert(isa_literal(x));
 //?   cerr << "BBB " << x.to_string() << '\n'; //? 1
@@ -52,7 +52,7 @@ void replace_offset(reagent& x, /*const*/ map<string, index_t>& offset, const re
 //?   cerr << "DDD " << x.to_string() << '\n'; //? 1
   if (offset.find(x.name) == offset.end())
     raise << "can't find label " << x.name << " in routine " << Recipe[r].name << '\n';
-  x.set_value(offset[x.name]);
+  x.set_value(offset[x.name]-current_offset);
 }
 
 :(scenario break_to_label)
@@ -91,3 +91,17 @@ recipe main [
   +target
 ]
 -mem: storing 0 in location 1
+
+:(scenario jump_runs_code_after_label)
+recipe main [
+  # first a few lines of padding to exercise the offset computation
+  1:integer <- copy 0:literal
+  2:integer <- copy 0:literal
+  3:integer <- copy 0:literal
+  jump +target:offset
+  4:integer <- copy 0:literal
+  +target
+  5:integer <- copy 0:literal
+]
++mem: storing 0 in location 5
+-mem: storing 0 in location 4
diff --git a/050scenario.cc b/050scenario.cc
index d950b7ae..af97b34a 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -88,7 +88,7 @@ time_t mu_time; time(&mu_time);
 cerr << "\nMu tests: " << ctime(&mu_time);
 for (index_t i = 0; i < Scenarios.size(); ++i) {
 //?   cerr << Passed << '\n'; //? 1
-//?   cerr << i << ": " << Scenarios.at(i).name << '\n'; //? 1
+//?   cerr << i << ": " << Scenarios.at(i).name << '\n'; //? 2
   run_mu_scenario(Scenarios.at(i));
   if (Passed) cerr << ".";
 }
@@ -294,6 +294,10 @@ recipe main [
 // Like runs of contiguous '+' lines, order is important. The trace checks
 // that the lines are present *and* in the specified sequence. (There can be
 // other lines in between.)
+//
+// Be careful not to mix setting Hide_warnings and checking the trace in .mu
+// files. It'll work in C++ scenarios, but the test failure gets silently
+// hidden in mu scenarios.
 
 :(scenario trace_check_warns_on_failure)
 % Hide_warnings = true;
@@ -319,8 +323,10 @@ case TRACE_SHOULD_CONTAIN: {
 // simplified version of check_trace_contents() that emits warnings rather
 // than just printing to stderr
 bool check_trace(const string& expected) {
+//?   cerr << "AAA " << expected << '\n'; //? 1
   Trace_stream->newline();
   vector<pair<string, string> > expected_lines = parse_trace(expected);
+//?   cerr << "BBB " << expected_lines.size() << '\n'; //? 1
   if (expected_lines.empty()) return true;
   index_t curr_expected_line = 0;
   for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
@@ -328,7 +334,10 @@ bool check_trace(const string& expected) {
     if (expected_lines.at(curr_expected_line).second != p->second.second) continue;
     // match
     ++curr_expected_line;
-    if (curr_expected_line == expected_lines.size()) return true;
+    if (curr_expected_line == expected_lines.size()) {
+//?       cerr << "ZZZ\n"; //? 1
+      return true;
+    }
   }
 
   raise << "missing [" << expected_lines.at(curr_expected_line).second << "] "
diff --git a/chessboard.mu b/chessboard.mu
index 5cd2deb1..a5ba2627 100644
--- a/chessboard.mu
+++ b/chessboard.mu
@@ -152,100 +152,148 @@ container move [
   to-rank:integer
 ]
 
-# result:address:move <- read-move stdin:address:channel
+# result:address:move, quit?:boolean, error?:boolean <- read-move stdin:address:channel, screen:address
+# prints only error messages to screen
 recipe read-move [
   default-space:address:array:location <- new location:type, 30:literal
   stdin:address:channel <- next-ingredient
-  from-file:integer <- read-file stdin:address:channel
+  screen:address <- next-ingredient
+#?   $print screen:address #? 1
+  from-file:integer, quit?:boolean, error?:boolean <- read-file stdin:address:channel, screen:address
+  reply-if quit?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
+  reply-if error?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
 #?   return-to-console #? 1
-  {
-    q-pressed?:boolean <- lesser-than from-file:integer, 0:literal
-    break-unless q-pressed?:boolean
-    reply 0:literal
-  }
   # construct the move object
   result:address:move <- new move:literal
   x:address:integer <- get-address result:address:move/deref, from-file:offset
   x:address:integer/deref <- copy from-file:integer
   x:address:integer <- get-address result:address:move/deref, from-rank:offset
-  x:address:integer/deref <- read-rank stdin:address:channel
-  expect-from-channel stdin:address:channel, 45:literal  # '-'
+  x:address:integer/deref, quit?:boolean, error?:boolean <- read-rank stdin:address:channel, screen:address
+  reply-if quit?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
+  reply-if error?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
+  error?:boolean <- expect-from-channel stdin:address:channel, 45:literal/dash, screen:address
+  reply-if error?:boolean, 0:literal/dummy, 0:literal/quit, error?:boolean
   x:address:integer <- get-address result:address:move/deref, to-file:offset
-  x:address:integer/deref <- read-file stdin:address:channel
+  x:address:integer/deref, quit?:boolean, error?:boolean <- read-file stdin:address:channel, screen:address
+  reply-if quit?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
+  reply-if error?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
   x:address:integer <- get-address result:address:move/deref, to-rank:offset
-  x:address:integer/deref <- read-rank stdin:address:channel
+  x:address:integer/deref, quit?:boolean, error?:boolean <- read-rank stdin:address:channel, screen:address
+  reply-if quit?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
+  reply-if error?:boolean, 0:literal/dummy, quit?:boolean, error?:boolean
 #?   $exit #? 1
-  expect-from-channel stdin:address:channel, 13:literal  # newline
-  reply result:address:move
+  error?:boolean <- expect-from-channel stdin:address:channel, 13:literal/newline, screen:address
+  reply-if error?:boolean, 0:literal/dummy, 0:literal/quit, error?:boolean
+  reply result:address:move, quit?:boolean, error?:boolean
 ]
 
+# file:integer, quit:boolean, error:boolean <- read-file stdin:address:channel, screen:address
+# valid values for file: 0-7
 recipe read-file [
   default-space:address:array:location <- new location:type, 30:literal
   stdin:address:channel <- next-ingredient
+  screen:address <- next-ingredient
   c:character, stdin:address:channel <- read stdin:address:channel
   {
     q-pressed?:boolean <- equal c:character, 81:literal  # 'Q'
     break-unless q-pressed?:boolean
-    reply -1:literal
+    reply 0:literal/dummy, 1:literal/quit, 0:literal/error
   }
   {
     q-pressed?:boolean <- equal c:character, 113:literal  # 'q'
     break-unless q-pressed?:boolean
-    reply -1:literal
+    reply 0:literal/dummy, 1:literal/quit, 0:literal/error
   }
   file:integer <- subtract c:character, 97:literal  # 'a'
 #?   $print file:integer, [ #? 1
 #? ] #? 1
   # 'a' <= file <= 'h'
-  above-min:boolean <- greater-or-equal file:integer, 0:literal
-  assert above-min:boolean [file too low]
-  below-max:boolean <- lesser-than file:integer, 8:literal
-  assert below-max:boolean [file too high]
-  reply file:integer
+  {
+    above-min:boolean <- greater-or-equal file:integer, 0:literal
+    break-if above-min:boolean
+    error-message:address:array:character <- new [file too low: ]
+    print-string screen:address, error-message:address:array:character
+    print-character screen:address, c:character
+    cursor-to-next-line screen:address
+    reply 0:literal/dummy, 0:literal/quit, 1:literal/error
+  }
+  {
+    below-max:boolean <- lesser-than file:integer, 8:literal
+    break-if below-max:boolean
+    error-message:address:array:character <- new [file too high: ]
+    print-string screen:address, error-message:address:array:character
+    print-character screen:address, c:character
+    reply 0:literal/dummy, 0:literal/quit, 1:literal/error
+  }
+  reply file:integer, 0:literal/quit, 0:literal/error
 ]
 
+# rank:integer <- read-rank stdin:address:channel, screen:address
+# valid values: 0-7, -1 (quit), -2 (error)
 recipe read-rank [
   default-space:address:array:location <- new location:type, 30:literal
   stdin:address:channel <- next-ingredient
+  screen:address <- next-ingredient
   c:character, stdin:address:channel <- read stdin:address:channel
   {
     q-pressed?:boolean <- equal c:character, 81:literal  # 'Q'
     break-unless q-pressed?:boolean
-    reply -1:literal
+    reply 0:literal/dummy, 1:literal/quit, 0:literal/error
   }
   {
     q-pressed?:boolean <- equal c:character, 113:literal  # 'q'
     break-unless q-pressed?:boolean
-    reply -1:literal
+    reply 0:literal/dummy, 1:literal/quit, 0:literal/error
   }
   rank:integer <- subtract c:character, 49:literal  # '1'
 #?   $print rank:integer, [ #? 1
 #? ] #? 1
   # assert'1' <= rank <= '8'
-  above-min:boolean <- greater-or-equal rank:integer 0:literal
-  assert above-min:boolean [rank too low]
-  below-max:boolean <- lesser-or-equal rank:integer 7:literal
-  assert below-max:boolean [rank too high]
-  reply rank:integer
+  {
+    above-min:boolean <- greater-or-equal rank:integer 0:literal
+    break-if above-min:boolean
+    error-message:address:array:character <- new [rank too low: ]
+    print-string screen:address, error-message:address:array:character
+    print-character screen:address, c:character
+    reply 0:literal/dummy, 0:literal/quit, 1:literal/error
+  }
+  {
+    below-max:boolean <- lesser-or-equal rank:integer 7:literal
+    break-if below-max:boolean
+    error-message:address:array:character <- new [rank too high: ]
+    print-string screen:address, error-message:address:array:character
+    print-character screen:address, c:character
+    reply 0:literal/dummy, 0:literal/quit, 1:literal/error
+  }
+  reply rank:integer, 0:literal/quit, 0:literal/error
 ]
 
 # read a character from the given channel and check that it's what we expect
+# return true on error
 recipe expect-from-channel [
   default-space:address:array:location <- new location:type, 30:literal
   stdin:address:channel <- next-ingredient
   expected:character <- next-ingredient
+  screen:address <- next-ingredient
   c:character, stdin:address:channel <- read stdin:address:channel
   match?:boolean <- equal c:character, expected:character
-  assert match?:boolean [expected character not found]
+  {
+    break-if match?:boolean
+    s:address:array:character <- new [expected character not found]
+    print-string screen:address, s:address:array:character
+  }
+  result:boolean <- not match?:boolean
+  reply result:boolean
 ]
 
 scenario read-move-blocking [
+  assume-screen 20:literal/width, 2:literal/height
   run [
 #?     $start-tracing #? 1
     1:address:channel <- init-channel 2:literal
 #?     $print [aaa channel address: ], 1:address:channel, [ #? 1
 #? ] #? 1
-    2:integer/routine <- start-running read-move:recipe, 1:address:channel
+    2:integer/routine <- start-running read-move:recipe, 1:address:channel, screen:address
     # 'read-move' is waiting for input
     wait-for-routine 2:integer
 #?     $print [bbb channel address: ], 1:address:channel, [ #? 1
@@ -333,9 +381,10 @@ F read-move-blocking: routine failed to terminate on newline]
 ]
 
 scenario read-move-quit [
+  assume-screen 20:literal/width, 2:literal/height
   run [
     1:address:channel <- init-channel 2:literal
-    2:integer/routine <- start-running read-move:recipe, 1:address:channel
+    2:integer/routine <- start-running read-move:recipe, 1:address:channel, screen:address
     # 'read-move' is waiting for input
     wait-for-routine 2:integer
     3:integer <- routine-state 2:integer/id
@@ -359,10 +408,10 @@ F read-move-quit: routine failed to terminate on 'q']
 ]
 
 scenario read-move-illegal-file [
+  assume-screen 20:literal/width, 2:literal/height
   run [
-    hide-warnings
     1:address:channel <- init-channel 2:literal
-    2:integer/routine <- start-running read-move:recipe, 1:address:channel
+    2:integer/routine <- start-running read-move:recipe, 1:address:channel, screen:address
     # 'read-move' is waiting for input
     wait-for-routine 2:integer
     3:integer <- routine-state 2:integer/id
@@ -373,16 +422,17 @@ F read-move-file: routine failed to pause after coming up (before any keys were
     restart 2:integer/routine
     wait-for-routine 2:integer
   ]
-  trace-should-contain [
-    warn: file too low
+  screen-should-contain [
+    .file too low: 2     .
+    .                    .
   ]
 ]
 
 scenario read-move-illegal-rank [
+  assume-screen 20:literal/width, 2:literal/height
   run [
-    hide-warnings
     1:address:channel <- init-channel 2:literal
-    2:integer/routine <- start-running read-move:recipe, 1:address:channel
+    2:integer/routine <- start-running read-move:recipe, 1:address:channel, screen:address
     # 'read-move' is waiting for input
     wait-for-routine 2:integer
     3:integer <- routine-state 2:integer/id
@@ -394,8 +444,9 @@ F read-move-file: routine failed to pause after coming up (before any keys were
     restart 2:integer/routine
     wait-for-routine 2:integer
   ]
-  trace-should-contain [
-    warn: rank too high
+  screen-should-contain [
+    .rank too high: a    .
+    .                    .
   ]
 ]
 
@@ -496,15 +547,20 @@ recipe chessboard [
     msg:address:array:character <- new [Hit 'q' to exit.
 ]
     print-string 0:literal/screen, msg:address:array:character
-    cursor-to-next-line 0:literal/screen
-    msg:address:array:character <- new [move: ]
-    print-string 0:literal/screen, msg:address:array:character
-    m:address:move <- read-move buffered-stdin:address:channel
-    break-unless m:address:move
+    {
+      cursor-to-next-line 0:literal/screen
+      msg:address:array:character <- new [move: ]
+      print-string 0:literal/screen, msg:address:array:character
+      m:address:move, quit:boolean, error:boolean <- read-move buffered-stdin:address:channel, 0:literal/screen
+      break-if quit:boolean, +quit:offset
+      loop-if error:boolean
+    }
     board:address:array:address:array:character <- make-move board:address:array:address:array:character, m:address:move
     clear-screen 0:literal/screen
     loop
   }
+  +quit
+#?   $print [aaa] #? 1
   return-to-console
 ]
 
diff --git a/mu.vim b/mu.vim
index 5c120676..a973fc1a 100644
--- a/mu.vim
+++ b/mu.vim
@@ -35,7 +35,7 @@ syntax match muDelimiter "[{}\[\]]" | highlight link muDelimiter Delimiter
 syntax match muLabel " [^a-zA-Z0-9 \[][a-zA-Z0-9-]\+" | highlight link muLabel Function
 syntax match muAssign " <- " | highlight link muAssign SpecialChar
 syntax match muAssign "\<raw\>"
-syntax keyword muControl reply jump jump-if jump-unless loop loop-if loop-unless break-if break-unless | highlight link muControl Function
+syntax keyword muControl reply reply-if reply-unless jump jump-if jump-unless loop loop-if loop-unless break-if break-unless | highlight link muControl Function
 " common keywords
 syntax keyword muFunc recipe default-space next-ingredient ingredient before after scenario run memory trace screen keyboard stalled finished | highlight link muFunc Statement
 
diff --git a/termbox/termbox.c b/termbox/termbox.c
index 2fbbf5b4..80aeb3a9 100644
--- a/termbox/termbox.c
+++ b/termbox/termbox.c
@@ -158,6 +158,8 @@ void tb_present(void)
   int x,y,w,i;
   struct tb_cell *back, *front;
 
+  assert(termw != -1);
+
   /* invalidate cursor position */
   lastx = LAST_COORD_INIT;
   lasty = LAST_COORD_INIT;
@@ -203,12 +205,11 @@ void tb_present(void)
 
 void tb_set_cursor(int cx, int cy)
 {
+  assert(termw != -1);
   if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
     bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
-
   if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
     bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
-
   cursor_x = cx;
   cursor_y = cy;
   if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
@@ -217,6 +218,7 @@ void tb_set_cursor(int cx, int cy)
 
 void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg)
 {
+  assert(termw != -1);
   if ((unsigned)x >= (unsigned)back_buffer.width)
     return;
   if ((unsigned)y >= (unsigned)back_buffer.height)
@@ -232,6 +234,7 @@ struct tb_cell *tb_cell_buffer()
 
 int tb_poll_event(struct tb_event *event)
 {
+  assert(termw != -1);
   return wait_fill_event(event, 0);
 }
 
@@ -240,21 +243,25 @@ int tb_peek_event(struct tb_event *event, int timeout)
   struct timeval tv;
   tv.tv_sec = timeout / 1000;
   tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
+  assert(termw != -1);
   return wait_fill_event(event, &tv);
 }
 
 int tb_width(void)
 {
+  assert(termw != -1);
   return termw;
 }
 
 int tb_height(void)
 {
+  assert(termw != -1);
   return termh;
 }
 
 void tb_clear(void)
 {
+  assert(termw != -1);
   if (buffer_size_change_request) {
     update_size();
     buffer_size_change_request = 0;
@@ -264,6 +271,7 @@ void tb_clear(void)
 
 void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
 {
+  assert(termw != -1);
   foreground = fg;
   background = bg;
 }