From c603cd6cef43100aa83a62e15f96fd54c9fb987e Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 13 Nov 2015 10:08:57 -0800 Subject: 2430 - make room for more transforms --- html/060string.mu.html | 1356 --------------------------------- html/061channel.mu.html | 412 ---------- html/062array.mu.html | 77 -- html/063list.mu.html | 100 --- html/064random.cc.html | 100 --- html/065duplex_list.mu.html | 593 -------------- html/066stream.mu.html | 80 -- html/070display.cc.html | 534 ------------- html/070string.mu.html | 1356 +++++++++++++++++++++++++++++++++ html/071channel.mu.html | 412 ++++++++++ html/071print.mu.html | 722 ------------------ html/072array.mu.html | 77 ++ html/072scenario_screen.cc.html | 394 ---------- html/073list.mu.html | 100 +++ html/073scenario_screen_test.mu.html | 63 -- html/074console.mu.html | 150 ---- html/074random.cc.html | 100 +++ html/075duplex_list.mu.html | 593 ++++++++++++++ html/075scenario_console.cc.html | 323 -------- html/076scenario_console_test.mu.html | 59 -- html/076stream.mu.html | 80 ++ html/080display.cc.html | 534 +++++++++++++ html/080trace_browser.cc.html | 248 ------ html/081print.mu.html | 722 ++++++++++++++++++ html/081run_interactive.cc.html | 489 ------------ html/082persist.cc.html | 157 ---- html/082scenario_screen.cc.html | 394 ++++++++++ html/083scenario_screen_test.mu.html | 63 ++ html/084console.mu.html | 150 ++++ html/085scenario_console.cc.html | 323 ++++++++ html/086scenario_console_test.mu.html | 59 ++ html/090trace_browser.cc.html | 248 ++++++ html/091run_interactive.cc.html | 489 ++++++++++++ html/092persist.cc.html | 157 ++++ html/098check_type_pointers.cc.html | 67 -- html/998check_type_pointers.cc.html | 67 ++ html/edit/006-sandbox-edit.mu.html | 1 - html/example1.mu.html | 37 + html/factorial.mu.html | 7 +- html/tangle.mu.html | 7 +- 40 files changed, 5967 insertions(+), 5933 deletions(-) delete mode 100644 html/060string.mu.html delete mode 100644 html/061channel.mu.html delete mode 100644 html/062array.mu.html delete mode 100644 html/063list.mu.html delete mode 100644 html/064random.cc.html delete mode 100644 html/065duplex_list.mu.html delete mode 100644 html/066stream.mu.html delete mode 100644 html/070display.cc.html create mode 100644 html/070string.mu.html create mode 100644 html/071channel.mu.html delete mode 100644 html/071print.mu.html create mode 100644 html/072array.mu.html delete mode 100644 html/072scenario_screen.cc.html create mode 100644 html/073list.mu.html delete mode 100644 html/073scenario_screen_test.mu.html delete mode 100644 html/074console.mu.html create mode 100644 html/074random.cc.html create mode 100644 html/075duplex_list.mu.html delete mode 100644 html/075scenario_console.cc.html delete mode 100644 html/076scenario_console_test.mu.html create mode 100644 html/076stream.mu.html create mode 100644 html/080display.cc.html delete mode 100644 html/080trace_browser.cc.html create mode 100644 html/081print.mu.html delete mode 100644 html/081run_interactive.cc.html delete mode 100644 html/082persist.cc.html create mode 100644 html/082scenario_screen.cc.html create mode 100644 html/083scenario_screen_test.mu.html create mode 100644 html/084console.mu.html create mode 100644 html/085scenario_console.cc.html create mode 100644 html/086scenario_console_test.mu.html create mode 100644 html/090trace_browser.cc.html create mode 100644 html/091run_interactive.cc.html create mode 100644 html/092persist.cc.html delete mode 100644 html/098check_type_pointers.cc.html create mode 100644 html/998check_type_pointers.cc.html create mode 100644 html/example1.mu.html (limited to 'html') diff --git a/html/060string.mu.html b/html/060string.mu.html deleted file mode 100644 index ae7a3e01..00000000 --- a/html/060string.mu.html +++ /dev/null @@ -1,1356 +0,0 @@ - - - - -Mu - 060string.mu - - - - - - - - - - -
-# Some useful helpers for dealing with strings.
-
-recipe string-equal [
-  local-scope
-  a:address:array:character <- next-ingredient
-  a-len:number <- length *a
-  b:address:array:character <- next-ingredient
-  b-len:number <- length *b
-  # compare lengths
-  {
-    trace 99, [string-equal], [comparing lengths]
-    length-equal?:boolean <- equal a-len, b-len
-    break-if length-equal?
-    reply 0
-  }
-  # compare each corresponding character
-  trace 99, [string-equal], [comparing characters]
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, a-len
-    break-if done?
-    a2:character <- index *a, i
-    b2:character <- index *b, i
-    {
-      chars-match?:boolean <- equal a2, b2
-      break-if chars-match?
-      reply 0
-    }
-    i <- add i, 1
-    loop
-  }
-  reply 1
-]
-
-scenario string-equal-reflexive [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    3:boolean/raw <- string-equal x, x
-  ]
-  memory-should-contain [
-    3 <- 1  # x == x for all x
-  ]
-]
-
-scenario string-equal-identical [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    y:address:array:character <- new [abc]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 1  # abc == abc
-  ]
-]
-
-scenario string-equal-distinct-lengths [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    y:address:array:character <- new [abcd]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 0  # abc != abcd
-  ]
-  trace-should-contain [
-    string-equal: comparing lengths
-  ]
-  trace-should-not-contain [
-    string-equal: comparing characters
-  ]
-]
-
-scenario string-equal-with-empty [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new []
-    y:address:array:character <- new [abcd]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 0  # "" != abcd
-  ]
-]
-
-scenario string-equal-common-lengths-but-distinct [
-  run [
-    default-space:address:array:location <- new location:type, 30
-    x:address:array:character <- new [abc]
-    y:address:array:character <- new [abd]
-    3:boolean/raw <- string-equal x, y
-  ]
-  memory-should-contain [
-    3 <- 0  # abc != abd
-  ]
-]
-
-# A new type to help incrementally construct strings.
-container buffer [
-  length:number
-  data:address:array:character
-]
-
-recipe new-buffer [
-  local-scope
-  result:address:buffer <- new buffer:type
-  len:address:number <- get-address *result, length:offset
-  *len:address:number <- copy 0
-  s:address:address:array:character <- get-address *result, data:offset
-  capacity:number, found?:boolean <- next-ingredient
-  assert found?, [new-buffer must get a capacity argument]
-  *s <- new character:type, capacity
-  reply result
-]
-
-recipe grow-buffer [
-  local-scope
-  in:address:buffer <- next-ingredient
-  # double buffer size
-  x:address:address:array:character <- get-address *in, data:offset
-  oldlen:number <- length **x
-  newlen:number <- multiply oldlen, 2
-  olddata:address:array:character <- copy *x
-  *x <- new character:type, newlen
-  # copy old contents
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, oldlen
-    break-if done?
-    src:character <- index *olddata, i
-    dest:address:character <- index-address **x, i
-    *dest <- copy src
-    i <- add i, 1
-    loop
-  }
-  reply in
-]
-
-recipe buffer-full? [
-  local-scope
-  in:address:buffer <- next-ingredient
-  len:number <- get *in, length:offset
-  s:address:array:character <- get *in, data:offset
-  capacity:number <- length *s
-  result:boolean <- greater-or-equal len, capacity
-  reply result
-]
-
-# in <- buffer-append in:address:buffer, c:character
-recipe buffer-append [
-  local-scope
-  in:address:buffer <- next-ingredient
-  c:character <- next-ingredient
-  len:address:number <- get-address *in, length:offset
-  {
-    # backspace? just drop last character if it exists and return
-    backspace?:boolean <- equal c, 8/backspace
-    break-unless backspace?
-    empty?:boolean <- lesser-or-equal *len, 0
-    reply-if empty?, in/same-as-ingredient:0
-    *len <- subtract *len, 1
-    reply in/same-as-ingredient:0
-  }
-  {
-    # grow buffer if necessary
-    full?:boolean <- buffer-full? in
-    break-unless full?
-    in <- grow-buffer in
-  }
-  s:address:array:character <- get *in, data:offset
-  dest:address:character <- index-address *s, *len
-  *dest <- copy c
-  *len <- add *len, 1
-  reply in/same-as-ingredient:0
-]
-
-scenario buffer-append-works [
-  run [
-    local-scope
-    x:address:buffer <- new-buffer 3
-    s1:address:array:character <- get *x:address:buffer, data:offset
-    x:address:buffer <- buffer-append x:address:buffer, 97  # 'a'
-    x:address:buffer <- buffer-append x:address:buffer, 98  # 'b'
-    x:address:buffer <- buffer-append x:address:buffer, 99  # 'c'
-    s2:address:array:character <- get *x:address:buffer, data:offset
-    1:boolean/raw <- equal s1:address:array:character, s2:address:array:character
-    2:array:character/raw <- copy *s2:address:array:character
-    +buffer-filled
-    x:address:buffer <- buffer-append x:address:buffer, 100  # 'd'
-    s3:address:array:character <- get *x:address:buffer, data:offset
-    10:boolean/raw <- equal s1:address:array:character, s3:address:array:character
-    11:number/raw <- get *x:address:buffer, length:offset
-    12:array:character/raw <- copy *s3:address:array:character
-  ]
-  memory-should-contain [
-    # before +buffer-filled
-    1 <- 1   # no change in data pointer
-    2 <- 3   # size of data
-    3 <- 97  # data
-    4 <- 98
-    5 <- 99
-    # in the end
-    10 <- 0   # data pointer has grown
-    11 <- 4   # final length
-    12 <- 6   # but data's capacity has doubled
-    13 <- 97  # data
-    14 <- 98
-    15 <- 99
-    16 <- 100
-    17 <- 0
-    18 <- 0
-  ]
-]
-
-scenario buffer-append-handles-backspace [
-  run [
-    local-scope
-    x:address:buffer <- new-buffer 3
-    x <- buffer-append x, 97  # 'a'
-    x <- buffer-append x, 98  # 'b'
-    x <- buffer-append x, 8/backspace
-    s:address:array:character <- buffer-to-array x
-    1:array:character/raw <- copy *s
-  ]
-  memory-should-contain [
-    1 <- 1   # length
-    2 <- 97  # contents
-    3 <- 0
-  ]
-]
-
-# result:address:array:character <- integer-to-decimal-string n:number
-recipe integer-to-decimal-string [
-  local-scope
-  n:number <- next-ingredient
-  # is it zero?
-  {
-    break-if n
-    result:address:array:character <- new [0]
-    reply result
-  }
-  # save sign
-  negate-result:boolean <- copy 0
-  {
-    negative?:boolean <- lesser-than n, 0
-    break-unless negative?
-    negate-result <- copy 1
-    n <- multiply n, -1
-  }
-  # add digits from right to left into intermediate buffer
-  tmp:address:buffer <- new-buffer 30
-  digit-base:number <- copy 48  # '0'
-  {
-    done?:boolean <- equal n, 0
-    break-if done?
-    n, digit:number <- divide-with-remainder n, 10
-    c:character <- add digit-base, digit
-    tmp:address:buffer <- buffer-append tmp, c
-    loop
-  }
-  # add sign
-  {
-    break-unless negate-result:boolean
-    tmp <- buffer-append tmp, 45  # '-'
-  }
-  # reverse buffer into string result
-  len:number <- get *tmp, length:offset
-  buf:address:array:character <- get *tmp, data:offset
-  result:address:array:character <- new character:type, len
-  i:number <- subtract len, 1  # source index, decreasing
-  j:number <- copy 0  # destination index, increasing
-  {
-    # while i >= 0
-    done?:boolean <- lesser-than i, 0
-    break-if done?
-    # result[j] = tmp[i]
-    src:character <- index *buf, i
-    dest:address:character <- index-address *result, j
-    *dest <- copy src
-    i <- subtract i, 1
-    j <- add j, 1
-    loop
-  }
-  reply result
-]
-
-recipe buffer-to-array [
-  local-scope
-  in:address:buffer <- next-ingredient
-  {
-    # propagate null buffer
-    break-if in
-    reply 0
-  }
-  len:number <- get *in, length:offset
-  s:address:array:character <- get *in, data:offset
-  # we can't just return s because it is usually the wrong length
-  result:address:array:character <- new character:type, len
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, len
-    break-if done?
-    src:character <- index *s, i
-    dest:address:character <- index-address *result, i
-    *dest <- copy src
-    i <- add i, 1
-    loop
-  }
-  reply result
-]
-
-scenario integer-to-decimal-digit-zero [
-  run [
-    1:address:array:character/raw <- integer-to-decimal-string 0
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [0]
-  ]
-]
-
-scenario integer-to-decimal-digit-positive [
-  run [
-    1:address:array:character/raw <- integer-to-decimal-string 234
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [234]
-  ]
-]
-
-scenario integer-to-decimal-digit-negative [
-  run [
-    1:address:array:character/raw <- integer-to-decimal-string -1
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2 <- 2
-    3 <- 45  # '-'
-    4 <- 49  # '1'
-  ]
-]
-
-# result:address:array:character <- string-append a:address:array:character, b:address:array:character
-recipe string-append [
-  local-scope
-  # result = new character[a.length + b.length]
-  a:address:array:character <- next-ingredient
-  a-len:number <- length *a
-  b:address:array:character <- next-ingredient
-  b-len:number <- length *b
-  result-len:number <- add a-len, b-len
-  result:address:array:character <- new character:type, result-len
-  # copy a into result
-  result-idx:number <- copy 0
-  i:number <- copy 0
-  {
-    # while i < a.length
-    a-done?:boolean <- greater-or-equal i, a-len
-    break-if a-done?
-    # result[result-idx] = a[i]
-    out:address:character <- index-address *result, result-idx
-    in:character <- index *a, i
-    *out <- copy in
-    i <- add i, 1
-    result-idx <- add result-idx, 1
-    loop
-  }
-  # copy b into result
-  i <- copy 0
-  {
-    # while i < b.length
-    b-done?:boolean <- greater-or-equal i, b-len
-    break-if b-done?
-    # result[result-idx] = a[i]
-    out:address:character <- index-address *result, result-idx
-    in:character <- index *b, i
-    *out <- copy in
-    i <- add i, 1
-    result-idx <- add result-idx, 1
-    loop
-  }
-  reply result
-]
-
-scenario string-append-1 [
-  run [
-    1:address:array:character/raw <- new [hello,]
-    2:address:array:character/raw <- new [ world!]
-    3:address:array:character/raw <- string-append 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [hello, world!]
-  ]
-]
-
-scenario replace-character-in-string [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 98/b, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [azc]
-  ]
-]
-
-recipe string-replace [
-  local-scope
-  s:address:array:character <- next-ingredient
-  oldc:character <- next-ingredient
-  newc:character <- next-ingredient
-  from:number, _ <- next-ingredient  # default to 0
-  len:number <- length *s
-  i:number <- find-next s, oldc, from
-  done?:boolean <- greater-or-equal i, len
-  reply-if done?, s/same-as-ingredient:0
-  dest:address:character <- index-address *s, i
-  *dest <- copy newc
-  i <- add i, 1
-  s <- string-replace s, oldc, newc, i
-  reply s/same-as-ingredient:0
-]
-
-scenario replace-character-at-start [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 97/a, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [zbc]
-  ]
-]
-
-scenario replace-character-at-end [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 99/c, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [abz]
-  ]
-]
-
-scenario replace-character-missing [
-  run [
-    1:address:array:character/raw <- new [abc]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 100/d, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [abc]
-  ]
-]
-
-scenario replace-all-characters [
-  run [
-    1:address:array:character/raw <- new [banana]
-    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 97/a, 122/z
-    2:array:character/raw <- copy *1:address:array:character/raw
-  ]
-  memory-should-contain [
-    2:string <- [bznznz]
-  ]
-]
-
-# replace underscores in first with remaining args
-# result:address:array:character <- interpolate template:address:array:character, ...
-recipe interpolate [
-  local-scope
-  template:address:array:character <- next-ingredient
-  # compute result-len, space to allocate for result
-  tem-len:number <- length *template
-  result-len:number <- copy tem-len
-  {
-    # while arg received
-    a:address:array:character, arg-received?:boolean <- next-ingredient
-    break-unless arg-received?
-    # result-len = result-len + arg.length - 1 (for the 'underscore' being replaced)
-    a-len:number <- length *a
-    result-len <- add result-len, a-len
-    result-len <- subtract result-len, 1
-    loop
-  }
-  rewind-ingredients
-  _ <- next-ingredient  # skip template
-  result:address:array:character <- new character:type, result-len
-  # repeatedly copy sections of template and 'holes' into result
-  result-idx:number <- copy 0
-  i:number <- copy 0
-  {
-    # while arg received
-    a:address:array:character, arg-received?:boolean <- next-ingredient
-    break-unless arg-received?
-    # copy template into result until '_'
-    {
-      # while i < template.length
-      tem-done?:boolean <- greater-or-equal i, tem-len
-      break-if tem-done?, +done:label
-      # while template[i] != '_'
-      in:character <- index *template, i
-      underscore?:boolean <- equal in, 95/_
-      break-if underscore?
-      # result[result-idx] = template[i]
-      out:address:character <- index-address *result, result-idx
-      *out <- copy in
-      i <- add i, 1
-      result-idx <- add result-idx, 1
-      loop
-    }
-    # copy 'a' into result
-    j:number <- copy 0
-    {
-      # while j < a.length
-      arg-done?:boolean <- greater-or-equal j, a-len
-      break-if arg-done?
-      # result[result-idx] = a[j]
-      in:character <- index *a, j
-      out:address:character <- index-address *result, result-idx
-      *out <- copy in
-      j <- add j, 1
-      result-idx <- add result-idx, 1
-      loop
-    }
-    # skip '_' in template
-    i <- add i, 1
-    loop  # interpolate next arg
-  }
-  +done
-  # done with holes; copy rest of template directly into result
-  {
-    # while i < template.length
-    tem-done?:boolean <- greater-or-equal i, tem-len
-    break-if tem-done?
-    # result[result-idx] = template[i]
-    in:character <- index *template, i
-    out:address:character <- index-address *result, result-idx:number
-    *out <- copy in
-    i <- add i, 1
-    result-idx <- add result-idx, 1
-    loop
-  }
-  reply result
-]
-
-scenario interpolate-works [
-  run [
-    1:address:array:character/raw <- new [abc _]
-    2:address:array:character/raw <- new [def]
-    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [abc def]
-  ]
-]
-
-scenario interpolate-at-start [
-  run [
-    1:address:array:character/raw <- new [_, hello!]
-    2:address:array:character/raw <- new [abc]
-    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [abc, hello!]
-    16 <- 0  # out of bounds
-  ]
-]
-
-scenario interpolate-at-end [
-  run [
-    1:address:array:character/raw <- new [hello, _]
-    2:address:array:character/raw <- new [abc]
-    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
-    4:array:character/raw <- copy *3:address:array:character/raw
-  ]
-  memory-should-contain [
-    4:string <- [hello, abc]
-  ]
-]
-
-# result:boolean <- space? c:character
-recipe space? [
-  local-scope
-  c:character <- next-ingredient
-  # most common case first
-  result:boolean <- equal c, 32/space
-  reply-if result, result
-  result <- equal c, 10/newline
-  reply-if result, result
-  result <- equal c, 9/tab
-  reply-if result, result
-  result <- equal c, 13/carriage-return
-  reply-if result, result
-  # remaining uncommon cases in sorted order
-  # http://unicode.org code-points in unicode-set Z and Pattern_White_Space
-  result <- equal c, 11/ctrl-k
-  reply-if result, result
-  result <- equal c, 12/ctrl-l
-  reply-if result, result
-  result <- equal c, 133/ctrl-0085
-  reply-if result, result
-  result <- equal c, 160/no-break-space
-  reply-if result, result
-  result <- equal c, 5760/ogham-space-mark
-  reply-if result, result
-  result <- equal c, 8192/en-quad
-  reply-if result, result
-  result <- equal c, 8193/em-quad
-  reply-if result, result
-  result <- equal c, 8194/en-space
-  reply-if result, result
-  result <- equal c, 8195/em-space
-  reply-if result, result
-  result <- equal c, 8196/three-per-em-space
-  reply-if result, result
-  result <- equal c, 8197/four-per-em-space
-  reply-if result, result
-  result <- equal c, 8198/six-per-em-space
-  reply-if result, result
-  result <- equal c, 8199/figure-space
-  reply-if result, result
-  result <- equal c, 8200/punctuation-space
-  reply-if result, result
-  result <- equal c, 8201/thin-space
-  reply-if result, result
-  result <- equal c, 8202/hair-space
-  reply-if result, result
-  result <- equal c, 8206/left-to-right
-  reply-if result, result
-  result <- equal c, 8207/right-to-left
-  reply-if result, result
-  result <- equal c, 8232/line-separator
-  reply-if result, result
-  result <- equal c, 8233/paragraph-separator
-  reply-if result, result
-  result <- equal c, 8239/narrow-no-break-space
-  reply-if result, result
-  result <- equal c, 8287/medium-mathematical-space
-  reply-if result, result
-  result <- equal c, 12288/ideographic-space
-  reply result
-]
-
-# result:address:array:character <- trim s:address:array:character
-recipe trim [
-  local-scope
-  s:address:array:character <- next-ingredient
-  len:number <- length *s
-  # left trim: compute start
-  start:number <- copy 0
-  {
-    {
-      at-end?:boolean <- greater-or-equal start, len
-      break-unless at-end?
-      result:address:array:character <- new character:type, 0
-      reply result
-    }
-    curr:character <- index *s, start
-    whitespace?:boolean <- space? curr
-    break-unless whitespace?
-    start <- add start, 1
-    loop
-  }
-  # right trim: compute end
-  end:number <- subtract len, 1
-  {
-    not-at-start?:boolean <- greater-than end, start
-    assert not-at-start?, [end ran up against start]
-    curr:character <- index *s, end
-    whitespace?:boolean <- space? curr
-    break-unless whitespace?
-    end <- subtract end, 1
-    loop
-  }
-  # result = new character[end+1 - start]
-  new-len:number <- subtract end, start, -1
-  result:address:array:character <- new character:type, new-len
-  # copy the untrimmed parts between start and end
-  i:number <- copy start
-  j:number <- copy 0
-  {
-    # while i <= end
-    done?:boolean <- greater-than i, end
-    break-if done?
-    # result[j] = s[i]
-    src:character <- index *s, i
-    dest:address:character <- index-address *result, j
-    *dest <- copy src
-    i <- add i, 1
-    j <- add j, 1
-    loop
-  }
-  reply result
-]
-
-scenario trim-unmodified [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-left [
-  run [
-    1:address:array:character <- new [  abc]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-right [
-  run [
-    1:address:array:character <- new [abc  ]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-left-right [
-  run [
-    1:address:array:character <- new [  abc   ]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-scenario trim-newline-tab [
-  run [
-    1:address:array:character <- new [  abc
-]
-    2:address:array:character <- trim 1:address:array:character
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [abc]
-  ]
-]
-
-# next-index:number <- find-next text:address:array:character, pattern:character, idx:number
-recipe find-next [
-  local-scope
-  text:address:array:character <- next-ingredient
-  pattern:character <- next-ingredient
-  idx:number <- next-ingredient
-  len:number <- length *text
-  {
-    eof?:boolean <- greater-or-equal idx, len
-    break-if eof?
-    curr:character <- index *text, idx
-    found?:boolean <- equal curr, pattern
-    break-if found?
-    idx <- add idx, 1
-    loop
-  }
-  reply idx
-]
-
-scenario string-find-next [
-  run [
-    1:address:array:character <- new [a/b]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 1
-  ]
-]
-
-scenario string-find-next-empty [
-  run [
-    1:address:array:character <- new []
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 0
-  ]
-]
-
-scenario string-find-next-initial [
-  run [
-    1:address:array:character <- new [/abc]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 0  # prefix match
-  ]
-]
-
-scenario string-find-next-final [
-  run [
-    1:address:array:character <- new [abc/]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 3  # suffix match
-  ]
-]
-
-scenario string-find-next-missing [
-  run [
-    1:address:array:character <- new [abc]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 3  # no match
-  ]
-]
-
-scenario string-find-next-invalid-index [
-  run [
-    1:address:array:character <- new [abc]
-    2:number <- find-next 1:address:array:character, 47/slash, 4/start-index
-  ]
-  memory-should-contain [
-    2 <- 4  # no change
-  ]
-]
-
-scenario string-find-next-first [
-  run [
-    1:address:array:character <- new [ab/c/]
-    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
-  ]
-  memory-should-contain [
-    2 <- 2  # first '/' of multiple
-  ]
-]
-
-scenario string-find-next-second [
-  run [
-    1:address:array:character <- new [ab/c/]
-    2:number <- find-next 1:address:array:character, 47/slash, 3/start-index
-  ]
-  memory-should-contain [
-    2 <- 4  # second '/' of multiple
-  ]
-]
-
-# next-index:number <- find-substring text:address:array:character, pattern:address:array:character, idx:number
-# like find-next, but searches for multiple characters
-# fairly dumb algorithm
-recipe find-substring [
-  local-scope
-  text:address:array:character <- next-ingredient
-  pattern:address:array:character <- next-ingredient
-  idx:number <- next-ingredient
-  first:character <- index *pattern, 0
-  # repeatedly check for match at current idx
-  len:number <- length *text
-  {
-    # does some unnecessary work checking for substrings even when there isn't enough of text left
-    done?:boolean <- greater-or-equal idx, len
-    break-if done?
-    found?:boolean <- match-at text, pattern, idx
-    break-if found?
-    idx <- add idx, 1
-    # optimization: skip past indices that definitely won't match
-    idx <- find-next text, first, idx
-    loop
-  }
-  reply idx
-]
-
-scenario find-substring-1 [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bc]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1
-  ]
-]
-
-scenario find-substring-2 [
-  run [
-    1:address:array:character <- new [abcd]
-    2:address:array:character <- new [bc]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 1
-  ]
-  memory-should-contain [
-    3 <- 1
-  ]
-]
-
-scenario find-substring-no-match [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bd]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 3  # not found
-  ]
-]
-
-scenario find-substring-suffix-match [
-  run [
-    1:address:array:character <- new [abcd]
-    2:address:array:character <- new [cd]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 2
-  ]
-]
-
-scenario find-substring-suffix-match-2 [
-  run [
-    1:address:array:character <- new [abcd]
-    2:address:array:character <- new [cde]
-    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 4  # not found
-  ]
-]
-
-# result:boolean <- match-at text:address:array:character, pattern:address:array:character, idx:number
-# checks if substring matches at index 'idx'
-recipe match-at [
-  local-scope
-  text:address:array:character <- next-ingredient
-  pattern:address:array:character <- next-ingredient
-  idx:number <- next-ingredient
-  pattern-len:number <- length *pattern
-  # check that there's space left for the pattern
-  {
-    x:number <- length *text
-    x <- subtract x, pattern-len
-    enough-room?:boolean <- lesser-or-equal idx, x
-    break-if enough-room?
-    reply 0/not-found
-  }
-  # check each character of pattern
-  pattern-idx:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal pattern-idx, pattern-len
-    break-if done?
-    c:character <- index *text, idx
-    exp:character <- index *pattern, pattern-idx
-    {
-      match?:boolean <- equal c, exp
-      break-if match?
-      reply 0/not-found
-    }
-    idx <- add idx, 1
-    pattern-idx <- add pattern-idx, 1
-    loop
-  }
-  reply 1/found
-]
-
-scenario match-at-checks-substring-at-index [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [ab]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # match found
-  ]
-]
-
-scenario match-at-reflexive [
-  run [
-    1:address:array:character <- new [abc]
-    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # match found
-  ]
-]
-
-scenario match-at-outside-bounds [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [a]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
-  ]
-  memory-should-contain [
-    3 <- 0  # never matches
-  ]
-]
-
-scenario match-at-empty-pattern [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new []
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # always matches empty pattern given a valid index
-  ]
-]
-
-scenario match-at-empty-pattern-outside-bound [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new []
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
-  ]
-  memory-should-contain [
-    3 <- 0  # no match
-  ]
-]
-
-scenario match-at-empty-text [
-  run [
-    1:address:array:character <- new []
-    2:address:array:character <- new [abc]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 0  # no match
-  ]
-]
-
-scenario match-at-empty-against-empty [
-  run [
-    1:address:array:character <- new []
-    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 1  # matches because pattern is also empty
-  ]
-]
-
-scenario match-at-inside-bounds [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bc]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 1
-  ]
-  memory-should-contain [
-    3 <- 1  # matches inner substring
-  ]
-]
-
-scenario match-at-inside-bounds-2 [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- new [bc]
-    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
-  ]
-  memory-should-contain [
-    3 <- 0  # no match
-  ]
-]
-
-# result:address:array:address:array:character <- split s:address:array:character, delim:character
-recipe split [
-  local-scope
-  s:address:array:character <- next-ingredient
-  delim:character <- next-ingredient
-  # empty string? return empty array
-  len:number <- length *s
-  {
-    empty?:boolean <- equal len, 0
-    break-unless empty?
-    result:address:array:address:array:character <- new location:type, 0
-    reply result
-  }
-  # count #pieces we need room for
-  count:number <- copy 1  # n delimiters = n+1 pieces
-  idx:number <- copy 0
-  {
-    idx <- find-next s, delim, idx
-    done?:boolean <- greater-or-equal idx, len
-    break-if done?
-    idx <- add idx, 1
-    count <- add count, 1
-    loop
-  }
-  # allocate space
-  result:address:array:address:array:character <- new location:type, count
-  # repeatedly copy slices start..end until delimiter into result[curr-result]
-  curr-result:number <- copy 0
-  start:number <- copy 0
-  {
-    # while next delim exists
-    done?:boolean <- greater-or-equal start, len
-    break-if done?
-    end:number <- find-next s, delim, start
-    # copy start..end into result[curr-result]
-    dest:address:address:array:character <- index-address *result, curr-result
-    *dest <- string-copy s, start, end
-    # slide over to next slice
-    start <- add end, 1
-    curr-result <- add curr-result, 1
-    loop
-  }
-  reply result
-]
-
-scenario string-split-1 [
-  run [
-    1:address:array:character <- new [a/b]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    5:address:array:character <- index *2:address:array:address:array:character, 1
-    10:array:character <- copy *4:address:array:character
-    20:array:character <- copy *5:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 2  # length of result
-    10:string <- [a]
-    20:string <- [b]
-  ]
-]
-
-scenario string-split-2 [
-  run [
-    1:address:array:character <- new [a/b/c]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    5:address:array:character <- index *2:address:array:address:array:character, 1
-    6:address:array:character <- index *2:address:array:address:array:character, 2
-    10:array:character <- copy *4:address:array:character
-    20:array:character <- copy *5:address:array:character
-    30:array:character <- copy *6:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 3  # length of result
-    10:string <- [a]
-    20:string <- [b]
-    30:string <- [c]
-  ]
-]
-
-scenario string-split-missing [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    10:array:character <- copy *4:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 1  # length of result
-    10:string <- [abc]
-  ]
-]
-
-scenario string-split-empty [
-  run [
-    1:address:array:character <- new []
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 0  # empty result
-  ]
-]
-
-scenario string-split-empty-piece [
-  run [
-    1:address:array:character <- new [a/b//c]
-    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
-    3:number <- length *2:address:array:address:array:character
-    4:address:array:character <- index *2:address:array:address:array:character, 0
-    5:address:array:character <- index *2:address:array:address:array:character, 1
-    6:address:array:character <- index *2:address:array:address:array:character, 2
-    7:address:array:character <- index *2:address:array:address:array:character, 3
-    10:array:character <- copy *4:address:array:character
-    20:array:character <- copy *5:address:array:character
-    30:array:character <- copy *6:address:array:character
-    40:array:character <- copy *7:address:array:character
-  ]
-  memory-should-contain [
-    3 <- 4  # length of result
-    10:string <- [a]
-    20:string <- [b]
-    30:string <- []
-    40:string <- [c]
-  ]
-]
-
-# x:address:array:character, y:address:array:character <- split-first text:address:array:character, delim:character
-recipe split-first [
-  local-scope
-  text:address:array:character <- next-ingredient
-  delim:character <- next-ingredient
-  # empty string? return empty strings
-  len:number <- length *text
-  {
-    empty?:boolean <- equal len, 0
-    break-unless empty?
-    x:address:array:character <- new []
-    y:address:array:character <- new []
-    reply x, y
-  }
-  idx:number <- find-next text, delim, 0
-  x:address:array:character <- string-copy text, 0, idx
-  idx <- add idx, 1
-  y:address:array:character <- string-copy text, idx, len
-  reply x, y
-]
-
-scenario string-split-first [
-  run [
-    1:address:array:character <- new [a/b]
-    2:address:array:character, 3:address:array:character <- split-first 1:address:array:character, 47/slash
-    10:array:character <- copy *2:address:array:character
-    20:array:character <- copy *3:address:array:character
-  ]
-  memory-should-contain [
-    10:string <- [a]
-    20:string <- [b]
-  ]
-]
-
-# result:address:array:character <- string-copy buf:address:array:character, start:number, end:number
-# todo: make this generic
-recipe string-copy [
-  local-scope
-  buf:address:array:character <- next-ingredient
-  start:number <- next-ingredient
-  end:number <- next-ingredient
-  # if end is out of bounds, trim it
-  len:number <- length *buf
-  end:number <- min len, end
-  # allocate space for result
-  len <- subtract end, start
-  result:address:array:character <- new character:type, len
-  # copy start..end into result[curr-result]
-  src-idx:number <- copy start
-  dest-idx:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal src-idx, end
-    break-if done?
-    src:character <- index *buf, src-idx
-    dest:address:character <- index-address *result, dest-idx
-    *dest <- copy src
-    src-idx <- add src-idx, 1
-    dest-idx <- add dest-idx, 1
-    loop
-  }
-  reply result
-]
-
-scenario string-copy-copies-substring [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- string-copy 1:address:array:character, 1, 3
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [bc]
-  ]
-]
-
-scenario string-copy-out-of-bounds [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- string-copy 1:address:array:character, 2, 4
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- [c]
-  ]
-]
-
-scenario string-copy-out-of-bounds-2 [
-  run [
-    1:address:array:character <- new [abc]
-    2:address:array:character <- string-copy 1:address:array:character, 3, 3
-    3:array:character <- copy *2:address:array:character
-  ]
-  memory-should-contain [
-    3:string <- []
-  ]
-]
-
-recipe min [
-  local-scope
-  x:number <- next-ingredient
-  y:number <- next-ingredient
-  {
-    return-x?:boolean <- lesser-than x, y
-    break-if return-x?
-    reply y
-  }
-  reply x
-]
-
-recipe max [
-  local-scope
-  x:number <- next-ingredient
-  y:number <- next-ingredient
-  {
-    return-x?:boolean <- greater-than x, y
-    break-if return-x?
-    reply y
-  }
-  reply x
-]
-
- - - diff --git a/html/061channel.mu.html b/html/061channel.mu.html deleted file mode 100644 index 47a11e0f..00000000 --- a/html/061channel.mu.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - -Mu - 061channel.mu - - - - - - - - - - -
-# Mu synchronizes using channels rather than locks, like Erlang and Go.
-#
-# The two ends of a channel will usually belong to different routines, but
-# each end should only be used by a single one. Don't try to read from or
-# write to it from multiple routines at once.
-#
-# The key property of channels is that writing to a full channel or reading
-# from an empty one will put the current routine in 'waiting' state until the
-# operation can be completed.
-
-scenario channel [
-  run [
-    1:address:channel <- new-channel 3/capacity
-    1:address:channel <- write 1:address:channel, 34
-    2:character, 1:address:channel <- read 1:address:channel
-  ]
-  memory-should-contain [
-    2 <- 34
-  ]
-]
-
-container channel [
-  # To avoid locking, writer and reader will never write to the same location.
-  # So channels will include fields in pairs, one for the writer and one for the
-  # reader.
-  first-full:number  # for write
-  first-free:number  # for read
-  # A circular buffer contains values from index first-full up to (but not
-  # including) index first-empty. The reader always modifies it at first-full,
-  # while the writer always modifies it at first-empty.
-  data:address:array:character
-]
-
-# result:address:channel <- new-channel capacity:number
-recipe new-channel [
-  local-scope
-  # result = new channel
-  result:address:channel <- new channel:type
-  # result.first-full = 0
-  full:address:number <- get-address *result, first-full:offset
-  *full <- copy 0
-  # result.first-free = 0
-  free:address:number <- get-address *result, first-free:offset
-  *free <- copy 0
-  # result.data = new location[ingredient+1]
-  capacity:number <- next-ingredient
-  capacity <- add capacity, 1  # unused slot for 'full?' below
-  dest:address:address:array:character <- get-address *result, data:offset
-  *dest <- new character:type, capacity
-  reply result
-]
-
-# chan <- write chan:address:channel, val:character
-recipe write [
-  local-scope
-  chan:address:channel <- next-ingredient
-  val:character <- next-ingredient
-  {
-    # block if chan is full
-    full:boolean <- channel-full? chan
-    break-unless full
-    full-address:address:number <- get-address *chan, first-full:offset
-    wait-for-location *full-address
-  }
-  # store val
-  circular-buffer:address:array:character <- get *chan, data:offset
-  free:address:number <- get-address *chan, first-free:offset
-  dest:address:character <- index-address *circular-buffer, *free
-  *dest <- copy val
-  # mark its slot as filled
-  *free <- add *free, 1
-  {
-    # wrap free around to 0 if necessary
-    len:number <- length *circular-buffer
-    at-end?:boolean <- greater-or-equal *free, len
-    break-unless at-end?
-    *free <- copy 0
-  }
-  reply chan/same-as-ingredient:0
-]
-
-# result:character, chan <- read chan:address:channel
-recipe read [
-  local-scope
-  chan:address:channel <- next-ingredient
-  {
-    # block if chan is empty
-    empty?:boolean <- channel-empty? chan
-    break-unless empty?
-    free-address:address:number <- get-address *chan, first-free:offset
-    wait-for-location *free-address
-  }
-  # read result
-  full:address:number <- get-address *chan, first-full:offset
-  circular-buffer:address:array:character <- get *chan, data:offset
-  result:character <- index *circular-buffer, *full
-  # mark its slot as empty
-  *full <- add *full, 1
-  {
-    # wrap full around to 0 if necessary
-    len:number <- length *circular-buffer
-    at-end?:boolean <- greater-or-equal *full, len
-    break-unless at-end?
-    *full <- copy 0
-  }
-  reply result, chan/same-as-ingredient:0
-]
-
-recipe clear-channel [
-  local-scope
-  chan:address:channel <- next-ingredient
-  {
-    empty?:boolean <- channel-empty? chan
-    break-if empty?
-    _, chan <- read chan
-  }
-  reply chan/same-as-ingredient:0
-]
-
-scenario channel-initialization [
-  run [
-    1:address:channel <- new-channel 3/capacity
-    2:number <- get *1:address:channel, first-full:offset
-    3:number <- get *1:address:channel, first-free:offset
-  ]
-  memory-should-contain [
-    2 <- 0  # first-full
-    3 <- 0  # first-free
-  ]
-]
-
-scenario channel-write-increments-free [
-  run [
-    1:address:channel <- new-channel 3/capacity
-    1:address:channel <- write 1:address:channel, 34
-    2:number <- get *1:address:channel, first-full:offset
-    3:number <- get *1:address:channel, first-free:offset
-  ]
-  memory-should-contain [
-    2 <- 0  # first-full
-    3 <- 1  # first-free
-  ]
-]
-
-scenario channel-read-increments-full [
-  run [
-    1:address:channel <- new-channel 3/capacity
-    1:address:channel <- write 1:address:channel, 34
-    _, 1:address:channel <- read 1:address:channel
-    2:number <- get *1:address:channel, first-full:offset
-    3:number <- get *1:address:channel, first-free:offset
-  ]
-  memory-should-contain [
-    2 <- 1  # first-full
-    3 <- 1  # first-free
-  ]
-]
-
-scenario channel-wrap [
-  run [
-    # channel with just 1 slot
-    1:address:channel <- new-channel 1/capacity
-    # write and read a value
-    1:address:channel <- write 1:address:channel, 34
-    _, 1:address:channel <- read 1:address:channel
-    # first-free will now be 1
-    2:number <- get *1:address:channel, first-free:offset
-    3:number <- get *1:address:channel, first-free:offset
-    # write second value, verify that first-free wraps
-    1:address:channel <- write 1:address:channel, 34
-    4:number <- get *1:address:channel, first-free:offset
-    # read second value, verify that first-full wraps
-    _, 1:address:channel <- read 1:address:channel
-    5:number <- get *1:address:channel, first-full:offset
-  ]
-  memory-should-contain [
-    2 <- 1  # first-free after first write
-    3 <- 1  # first-full after first read
-    4 <- 0  # first-free after second write, wrapped
-    5 <- 0  # first-full after second read, wrapped
-  ]
-]
-
-## helpers
-
-# An empty channel has first-empty and first-full both at the same value.
-recipe channel-empty? [
-  local-scope
-  chan:address:channel <- next-ingredient
-  # return chan.first-full == chan.first-free
-  full:number <- get *chan, first-full:offset
-  free:number <- get *chan, first-free:offset
-  result:boolean <- equal full, free
-  reply result
-]
-
-# A full channel has first-empty just before first-full, wasting one slot.
-# (Other alternatives: https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction)
-recipe channel-full? [
-  local-scope
-  chan:address:channel <- next-ingredient
-  # tmp = chan.first-free + 1
-  tmp:number <- get *chan, first-free:offset
-  tmp <- add tmp, 1
-  {
-    # if tmp == chan.capacity, tmp = 0
-    len:number <- channel-capacity chan
-    at-end?:boolean <- greater-or-equal tmp, len
-    break-unless at-end?
-    tmp <- copy 0
-  }
-  # return chan.first-full == tmp
-  full:number <- get *chan, first-full:offset
-  result:boolean <- equal full, tmp
-  reply result
-]
-
-# result:number <- channel-capacity chan:address:channel
-recipe channel-capacity [
-  local-scope
-  chan:address:channel <- next-ingredient
-  q:address:array:character <- get *chan, data:offset
-  result:number <- length *q
-  reply result
-]
-
-scenario channel-new-empty-not-full [
-  run [
-    1:address:channel <- new-channel 3/capacity
-    2:boolean <- channel-empty? 1:address:channel
-    3:boolean <- channel-full? 1:address:channel
-  ]
-  memory-should-contain [
-    2 <- 1  # empty?
-    3 <- 0  # full?
-  ]
-]
-
-scenario channel-write-not-empty [
-  run [
-    1:address:channel <- new-channel 3/capacity
-    1:address:channel <- write 1:address:channel, 34
-    2:boolean <- channel-empty? 1:address:channel
-    3:boolean <- channel-full? 1:address:channel
-  ]
-  memory-should-contain [
-    2 <- 0  # empty?
-    3 <- 0  # full?
-  ]
-]
-
-scenario channel-write-full [
-  run [
-    1:address:channel <- new-channel 1/capacity
-    1:address:channel <- write 1:address:channel, 34
-    2:boolean <- channel-empty? 1:address:channel
-    3:boolean <- channel-full? 1:address:channel
-  ]
-  memory-should-contain [
-    2 <- 0  # empty?
-    3 <- 1  # full?
-  ]
-]
-
-scenario channel-read-not-full [
-  run [
-    1:address:channel <- new-channel 1/capacity
-    1:address:channel <- write 1:address:channel, 34
-    _, 1:address:channel <- read 1:address:channel
-    2:boolean <- channel-empty? 1:address:channel
-    3:boolean <- channel-full? 1:address:channel
-  ]
-  memory-should-contain [
-    2 <- 1  # empty?
-    3 <- 0  # full?
-  ]
-]
-
-# helper for channels of characters in particular
-# out <- buffer-lines in:address:channel, out:address:channel
-recipe buffer-lines [
-  local-scope
-  in:address:channel <- next-ingredient
-  out:address:channel <- next-ingredient
-  # repeat forever
-  {
-    line:address:buffer <- new-buffer, 30
-    # read characters from 'in' until newline, copy into line
-    {
-      +next-character
-      c:character, in <- read in
-      # drop a character on backspace
-      {
-        # special-case: if it's a backspace
-        backspace?:boolean <- equal c, 8
-        break-unless backspace?
-        # drop previous character
-        {
-          buffer-length:address:number <- get-address *line, length:offset
-          buffer-empty?:boolean <- equal *buffer-length, 0
-          break-if buffer-empty?
-          *buffer-length <- subtract *buffer-length, 1
-        }
-        # and don't append this one
-        loop +next-character:label
-      }
-      # append anything else
-      line <- buffer-append line, c
-      line-done?:boolean <- equal c, 10/newline
-      break-if line-done?
-      # stop buffering on eof (currently only generated by fake console)
-      eof?:boolean <- equal c, 0/eof
-      break-if eof?
-      loop
-    }
-    # copy line into 'out'
-    i:number <- copy 0
-    line-contents:address:array:character <- get *line, data:offset
-    max:number <- get *line, length:offset
-    {
-      done?:boolean <- greater-or-equal i, max
-      break-if done?
-      c:character <- index *line-contents, i
-      out <- write out, c
-      i <- add i, 1
-      loop
-    }
-    loop
-  }
-  reply out/same-as-ingredient:1
-]
-
-scenario buffer-lines-blocks-until-newline [
-  run [
-    1:address:channel/stdin <- new-channel 10/capacity
-    2:address:channel/buffered-stdin <- new-channel 10/capacity
-    3:boolean <- channel-empty? 2:address:channel/buffered-stdin
-    assert 3:boolean, [
-F buffer-lines-blocks-until-newline: channel should be empty after init]
-    # buffer stdin into buffered-stdin, try to read from buffered-stdin
-    4:number/buffer-routine <- start-running buffer-lines:recipe, 1:address:channel/stdin, 2:address:channel/buffered-stdin
-    wait-for-routine 4:number/buffer-routine
-    5:boolean <- channel-empty? 2:address:channel/buffered-stdin
-    assert 5:boolean, [
-F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up]
-    # write 'a'
-    1:address:channel <- write 1:address:channel, 97/a
-    restart 4:number/buffer-routine
-    wait-for-routine 4:number/buffer-routine
-    6:boolean <- channel-empty? 2:address:channel/buffered-stdin
-    assert 6:boolean, [
-F buffer-lines-blocks-until-newline: channel should be empty after writing 'a']
-    # write 'b'
-    1:address:channel <- write 1:address:channel, 98/b
-    restart 4:number/buffer-routine
-    wait-for-routine 4:number/buffer-routine
-    7:boolean <- channel-empty? 2:address:channel/buffered-stdin
-    assert 7:boolean, [
-F buffer-lines-blocks-until-newline: channel should be empty after writing 'b']
-    # write newline
-    1:address:channel <- write 1:address:channel, 10/newline
-    restart 4:number/buffer-routine
-    wait-for-routine 4:number/buffer-routine
-    8:boolean <- channel-empty? 2:address:channel/buffered-stdin
-    9:boolean/completed? <- not 8:boolean
-    assert 9:boolean/completed?, [
-F buffer-lines-blocks-until-newline: channel should contain data after writing newline]
-    trace 1, [test], [reached end]
-  ]
-  trace-should-contain [
-    test: reached end
-  ]
-]
-
- - - diff --git a/html/062array.mu.html b/html/062array.mu.html deleted file mode 100644 index 4f800098..00000000 --- a/html/062array.mu.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - -Mu - 062array.mu - - - - - - - - - - -
-scenario array-from-args [
-  run [
-    1:address:array:character <- new-array 0, 1, 2
-    2:array:character <- copy *1:address:array:character
-  ]
-  memory-should-contain [
-    2 <- 3  # array length
-    3 <- 0
-    4 <- 1
-    5 <- 2
-  ]
-]
-
-# create an array out of a list of scalar args
-recipe new-array [
-  local-scope
-  capacity:number <- copy 0
-  {
-    # while read curr-value
-    curr-value:character, exists?:boolean <- next-ingredient
-    break-unless exists?
-    capacity <- add capacity, 1
-    loop
-  }
-  result:address:array:character <- new character:type, capacity
-  rewind-ingredients
-  i:number <- copy 0
-  {
-    # while read curr-value
-    done?:boolean <- greater-or-equal i, capacity
-    break-if done?
-    curr-value:character, exists?:boolean <- next-ingredient
-    assert exists?, [error in rewinding ingredients to new-array]
-    tmp:address:character <- index-address *result, i
-    *tmp <- copy curr-value
-    i <- add i, 1
-    loop
-  }
-  reply result
-]
-
- - - diff --git a/html/063list.mu.html b/html/063list.mu.html deleted file mode 100644 index a3122c41..00000000 --- a/html/063list.mu.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - -Mu - 063list.mu - - - - - - - - - - -
-# A list links up multiple objects together to make them easier to manage.
-#
-# The objects must be of the same type. If you want to store multiple types in
-# a single list, use an exclusive-container.
-
-container list:_elem [
-  value:_elem
-  next:address:list:_elem
-]
-
-recipe push x:_elem, in:address:list:_elem -> result:address:list:_elem [
-  local-scope
-  load-ingredients
-  result <- new {(list _elem): type}
-  val:address:_elem <- get-address *result, value:offset
-  *val <- copy x
-  next:address:address:list:_elem <- get-address *result, next:offset
-  *next <- copy in
-  reply result
-]
-
-recipe first in:address:list:_elem -> result:_elem [
-  local-scope
-  load-ingredients
-  result <- get *in, value:offset
-]
-
-# result:address:list <- rest in:address:list
-recipe rest in:address:list:_elem -> result:address:list:_elem [
-  local-scope
-  load-ingredients
-  result <- get *in, next:offset
-]
-
-recipe force-specialization-list-number [
-  1:address:list:number <- push 2:number, 1:address:list:number
-  2:number <- first 1:address:list:number
-  1:address:list:number <- rest 1:address:list:number
-]
-
-# todo: automatically specialize code in scenarios
-scenario list-handling [
-  run [
-    1:address:list:number <- copy 0
-    2:number <- copy 3
-    1:address:list:number <- push 2:number, 1:address:list:number
-    1:address:list:number <- push 4, 1:address:list:number
-    1:address:list:number <- push 5, 1:address:list:number
-    2:number <- first 1:address:list:number
-    1:address:list:number <- rest 1:address:list:number
-    3:number <- first 1:address:list:number
-    1:address:list:number <- rest 1:address:list:number
-    4:number <- first 1:address:list:number
-    1:address:list:number <- rest 1:address:list:number
-  ]
-  memory-should-contain [
-    1 <- 0  # empty to empty, dust to dust..
-    2 <- 5
-    3 <- 4
-    4 <- 3
-  ]
-]
-
- - - diff --git a/html/064random.cc.html b/html/064random.cc.html deleted file mode 100644 index bd176af3..00000000 --- a/html/064random.cc.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - -Mu - 064random.cc - - - - - - - - - - -
-:(before "End Primitive Recipe Declarations")
-RANDOM,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "random", RANDOM);
-:(before "End Primitive Recipe Checks")
-case RANDOM: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RANDOM: {
-  // todo: limited range of numbers, might be imperfectly random
-  // todo: thread state in extra ingredients and products
-  products.resize(1);
-  products.at(0).push_back(rand());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-MAKE_RANDOM_NONDETERMINISTIC,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "make-random-nondeterministic", MAKE_RANDOM_NONDETERMINISTIC);
-:(before "End Primitive Recipe Checks")
-case MAKE_RANDOM_NONDETERMINISTIC: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case MAKE_RANDOM_NONDETERMINISTIC: {
-  srand(time(NULL));
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-ROUND,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "round", ROUND);
-:(before "End Primitive Recipe Checks")
-case ROUND: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise_error << maybe(get(Recipe, r).name) << "'round' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'round' should be a number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case ROUND: {
-  products.resize(1);
-  products.at(0).push_back(rint(ingredients.at(0).at(0)));
-  break;
-}
-
-:(scenario round_to_nearest_integer)
-recipe main [
-  1:number <- round 12.2
-]
-+mem: storing 12 in location 1
-
-:(before "End Includes")
-#include<math.h>
-
- - - diff --git a/html/065duplex_list.mu.html b/html/065duplex_list.mu.html deleted file mode 100644 index 4a9437ce..00000000 --- a/html/065duplex_list.mu.html +++ /dev/null @@ -1,593 +0,0 @@ - - - - -Mu - 065duplex_list.mu - - - - - - - - - - -
-# A doubly linked list permits bidirectional traversal.
-
-container duplex-list:_elem [
-  value:_elem
-  next:address:duplex-list:_elem
-  prev:address:duplex-list:_elem
-]
-
-recipe push-duplex x:_elem, in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  result <- new {(duplex-list _elem): type}
-  val:address:_elem <- get-address *result, value:offset
-  *val <- copy x
-  next:address:address:duplex-list:_elem <- get-address *result, next:offset
-  *next <- copy in
-  reply-unless in
-  prev:address:address:duplex-list:_elem <- get-address *in, prev:offset
-  *prev <- copy result
-]
-
-recipe first-duplex in:address:duplex-list:_elem -> result:_elem [
-  local-scope
-  load-ingredients
-  reply-unless in, 0
-  result <- get *in, value:offset
-]
-
-recipe next-duplex in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  reply-unless in, 0
-  result <- get *in, next:offset
-]
-
-recipe prev-duplex in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  reply-unless in, 0
-  result <- get *in, prev:offset
-  reply result
-]
-
-scenario duplex-list-handling [
-  run [
-    # reserve locations 0, 1 and 2 to check for missing null check
-    1:number <- copy 34
-    2:number <- copy 35
-    3:address:duplex-list:character <- copy 0
-    3:address:duplex-list:character <- push-duplex 3, 3:address:duplex-list:character
-    3:address:duplex-list:character <- push-duplex 4, 3:address:duplex-list:character
-    3:address:duplex-list:character <- push-duplex 5, 3:address:duplex-list:character
-    4:address:duplex-list:character <- copy 3:address:duplex-list:character
-    5:character <- first-duplex 4:address:duplex-list:character
-    4:address:duplex-list:character <- next-duplex 4:address:duplex-list:character
-    6:character <- first-duplex 4:address:duplex-list:character
-    4:address:duplex-list:character <- next-duplex 4:address:duplex-list:character
-    7:character <- first-duplex 4:address:duplex-list:character
-    8:address:duplex-list:character <- next-duplex 4:address:duplex-list:character
-    9:character <- first-duplex 8:address:duplex-list:character
-    10:address:duplex-list:character <- next-duplex 8:address:duplex-list:character
-    11:address:duplex-list:character <- prev-duplex 8:address:duplex-list:character
-    4:address:duplex-list:character <- prev-duplex 4:address:duplex-list:character
-    12:character <- first-duplex 4:address:duplex-list:character
-    4:address:duplex-list:character <- prev-duplex 4:address:duplex-list:character
-    13:character <- first-duplex 4:address:duplex-list:character
-    14:boolean <- equal 3:address:duplex-list:character, 4:address:duplex-list:character
-  ]
-  memory-should-contain [
-    0 <- 0  # no modifications to null pointers
-    1 <- 34
-    2 <- 35
-    5 <- 5  # scanning next
-    6 <- 4
-    7 <- 3
-    8 <- 0  # null
-    9 <- 0  # first of null
-    10 <- 0  # next of null
-    11 <- 0  # prev of null
-    12 <- 4  # then start scanning prev
-    13 <- 5
-    14 <- 1  # list back at start
-  ]
-]
-
-# Inserts 'x' after 'in'. Returns some pointer into the list.
-recipe insert-duplex x:_elem, in:address:duplex-list:_elem -> new-node:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  new-node <- new {(duplex-list _elem): type}
-  val:address:_elem <- get-address *new-node, value:offset
-  *val <- copy x
-  next-node:address:duplex-list:_elem <- get *in, next:offset
-  # in.next = new-node
-  y:address:address:duplex-list:_elem <- get-address *in, next:offset
-  *y <- copy new-node
-  # new-node.prev = in
-  y <- get-address *new-node, prev:offset
-  *y <- copy in
-  # new-node.next = next-node
-  y <- get-address *new-node, next:offset
-  *y <- copy next-node
-  # if next-node is not null
-  reply-unless next-node, new-node
-  # next-node.prev = new-node
-  y <- get-address *next-node, prev:offset
-  *y <- copy new-node
-  reply new-node  # just signalling something changed; don't rely on the result
-]
-
-scenario inserting-into-duplex-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to head of list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character  # 2 points inside list
-    2:address:duplex-list:character <- insert-duplex 6, 2:address:duplex-list:character
-    # check structure like before
-    2:address:duplex-list:character <- copy 1:address:duplex-list:character
-    3:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    4:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    5:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    6:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    7:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    8:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    9:character <- first-duplex 2:address:duplex-list:character
-    10:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
-  ]
-  memory-should-contain [
-    3 <- 5  # scanning next
-    4 <- 4
-    5 <- 6  # inserted element
-    6 <- 3
-    7 <- 6  # then prev
-    8 <- 4
-    9 <- 5
-    10 <- 1  # list back at start
-  ]
-]
-
-scenario inserting-at-end-of-duplex-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to head of list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character  # 2 points inside list
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character  # now at end of list
-    2:address:duplex-list:character <- insert-duplex 6, 2:address:duplex-list:character
-    # check structure like before
-    2:address:duplex-list:character <- copy 1:address:duplex-list:character
-    3:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    4:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    5:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    6:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    7:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    8:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    9:character <- first-duplex 2:address:duplex-list:character
-    10:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
-  ]
-  memory-should-contain [
-    3 <- 5  # scanning next
-    4 <- 4
-    5 <- 3
-    6 <- 6  # inserted element
-    7 <- 3  # then prev
-    8 <- 4
-    9 <- 5
-    10 <- 1  # list back at start
-  ]
-]
-
-scenario inserting-after-start-of-duplex-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to head of list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
-    2:address:duplex-list:character <- insert-duplex 6, 1:address:duplex-list:character
-    # check structure like before
-    2:address:duplex-list:character <- copy 1:address:duplex-list:character
-    3:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    4:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    5:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    6:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    7:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    8:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    9:character <- first-duplex 2:address:duplex-list:character
-    10:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
-  ]
-  memory-should-contain [
-    3 <- 5  # scanning next
-    4 <- 6  # inserted element
-    5 <- 4
-    6 <- 3
-    7 <- 4  # then prev
-    8 <- 6
-    9 <- 5
-    10 <- 1  # list back at start
-  ]
-]
-
-# Removes 'in' from its surrounding list. Returns some valid pointer into the
-# rest of the list.
-#
-# Returns null if and only if list is empty. Beware: in that case any pointers
-# to the head are now invalid.
-recipe remove-duplex in:address:duplex-list:_elem -> next-node:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  # if 'in' is null, return
-  reply-unless in, in
-  next-node:address:duplex-list:_elem <- get *in, next:offset
-  prev-node:address:duplex-list:_elem <- get *in, prev:offset
-  # null in's pointers
-  x:address:address:duplex-list:_elem <- get-address *in, next:offset
-  *x <- copy 0
-  x <- get-address *in, prev:offset
-  *x <- copy 0
-  {
-    # if next-node is not null
-    break-unless next-node
-    # next-node.prev = prev-node
-    x <- get-address *next-node, prev:offset
-    *x <- copy prev-node
-  }
-  {
-    # if prev-node is not null
-    break-unless prev-node
-    # prev-node.next = next-node
-    x <- get-address *prev-node, next:offset
-    *x <- copy next-node
-    reply prev-node
-  }
-  reply next-node
-]
-
-scenario removing-from-duplex-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to head of list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character  # 2 points at second element
-    2:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character
-    3:boolean <- equal 2:address:duplex-list:character, 0
-    # check structure like before
-    2:address:duplex-list:character <- copy 1:address:duplex-list:character
-    4:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    5:character <- first-duplex 2:address:duplex-list:character
-    6:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    7:character <- first-duplex 2:address:duplex-list:character
-    8:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
-  ]
-  memory-should-contain [
-    3 <- 0  # remove returned non-null
-    4 <- 5  # scanning next, skipping deleted element
-    5 <- 3
-    6 <- 0  # no more elements
-    7 <- 5  # prev of final element
-    8 <- 1  # list back at start
-  ]
-]
-
-scenario removing-from-start-of-duplex-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to head of list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
-    # removing from head? return value matters.
-    1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character
-    # check structure like before
-    2:address:duplex-list:character <- copy 1:address:duplex-list:character
-    3:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    4:character <- first-duplex 2:address:duplex-list:character
-    5:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    6:character <- first-duplex 2:address:duplex-list:character
-    7:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
-  ]
-  memory-should-contain [
-    3 <- 4  # scanning next, skipping deleted element
-    4 <- 3
-    5 <- 0  # no more elements
-    6 <- 4  # prev of final element
-    7 <- 1  # list back at start
-  ]
-]
-
-scenario removing-from-end-of-duplex-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to head of list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
-    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
-    # delete last element
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character
-    3:boolean <- equal 2:address:duplex-list:character, 0
-    # check structure like before
-    2:address:duplex-list:character <- copy 1:address:duplex-list:character
-    4:character <- first-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    5:character <- first-duplex 2:address:duplex-list:character
-    6:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
-    7:character <- first-duplex 2:address:duplex-list:character
-    8:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
-  ]
-  memory-should-contain [
-    3 <- 0  # remove returned non-null
-    4 <- 5  # scanning next, skipping deleted element
-    5 <- 4
-    6 <- 0  # no more elements
-    7 <- 5  # prev of final element
-    8 <- 1  # list back at start
-  ]
-]
-
-scenario removing-from-singleton-list [
-  run [
-    1:address:duplex-list:character <- copy 0  # 1 points to singleton list
-    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
-    2:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character
-    3:address:duplex-list:character <- get *1:address:duplex-list:character, next:offset
-    4:address:duplex-list:character <- get *1:address:duplex-list:character, prev:offset
-  ]
-  memory-should-contain [
-    2 <- 0  # remove returned null
-    3 <- 0  # removed node is also detached
-    4 <- 0
-  ]
-]
-
-# l:address:duplex-list <- remove-duplex-between start:address:duplex-list, end:address:duplex-list
-# Remove values between 'start' and 'end' (both exclusive). Returns some valid
-# pointer into the rest of the list.
-# Also clear pointers back out from start/end for hygiene.
-recipe remove-duplex-between start:address:duplex-list:_elem, end:address:duplex-list:_elem -> start:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  reply-unless start
-  # start->next->prev = 0
-  # start->next = end
-  next:address:address:duplex-list:_elem <- get-address *start, next:offset
-  nothing-to-delete?:boolean <- equal *next, end
-  reply-if nothing-to-delete?
-  prev:address:address:duplex-list:_elem <- get-address **next, prev:offset
-  *prev <- copy 0
-  *next <- copy end
-  reply-unless end
-  # end->prev->next = 0
-  # end->prev = start
-  prev <- get-address *end, prev:offset
-  next <- get-address **prev, next:offset
-  *next <- copy 0
-  *prev <- copy start
-]
-
-scenario remove-range [
-  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
-  1:address:duplex-list:character <- copy 0  # 1 points to singleton list
-  1:address:duplex-list:character <- push-duplex 18, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 17, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 16, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 15, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 14, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 13, 1:address:duplex-list:character
-  run [
-    # delete 16 onwards
-    # first pointer: to the third element
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    2:address:duplex-list:character <- remove-duplex-between 2:address:duplex-list:character, 0
-    # now check the list
-    4:character <- get *1:address:duplex-list:character, value:offset
-    5:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    6:character <- get *5:address:duplex-list:character, value:offset
-    7:address:duplex-list:character <- next-duplex 5:address:duplex-list:character
-    8:character <- get *7:address:duplex-list:character, value:offset
-    9:address:duplex-list:character <- next-duplex 7:address:duplex-list:character
-  ]
-  memory-should-contain [
-    4 <- 13
-    6 <- 14
-    8 <- 15
-    9 <- 0
-  ]
-]
-
-scenario remove-range-to-end [
-  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
-  1:address:duplex-list:character <- copy 0  # 1 points to singleton list
-  1:address:duplex-list:character <- push-duplex 18, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 17, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 16, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 15, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 14, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 13, 1:address:duplex-list:character
-  run [
-    # delete 15, 16 and 17
-    # first pointer: to the third element
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    # second pointer: to the fifth element
-    3:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
-    3:address:duplex-list:character <- next-duplex 3:address:duplex-list:character
-    3:address:duplex-list:character <- next-duplex 3:address:duplex-list:character
-    3:address:duplex-list:character <- next-duplex 3:address:duplex-list:character
-    remove-duplex-between 2:address:duplex-list:character, 3:address:duplex-list:character
-    # now check the list
-    4:character <- get *1:address:duplex-list:character, value:offset
-    5:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    6:character <- get *5:address:duplex-list:character, value:offset
-    7:address:duplex-list:character <- next-duplex 5:address:duplex-list:character
-    8:character <- get *7:address:duplex-list:character, value:offset
-    9:address:duplex-list:character <- next-duplex 7:address:duplex-list:character
-  ]
-  memory-should-contain [
-    4 <- 13
-    6 <- 14
-    8 <- 18
-    9 <- 0
-  ]
-]
-
-scenario remove-range-empty [
-  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
-  1:address:duplex-list:character <- copy 0  # 1 points to singleton list
-  1:address:duplex-list:character <- push-duplex 14, 1:address:duplex-list:character
-  1:address:duplex-list:character <- push-duplex 13, 1:address:duplex-list:character
-  run [
-    # delete 16 onwards
-    # first pointer: to the third element
-    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    remove-duplex-between 1:address:duplex-list:character, 2:address:duplex-list:character
-    # now check the list
-    4:character <- get *1:address:duplex-list:character, value:offset
-    5:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-    6:character <- get *5:address:duplex-list:character, value:offset
-    7:address:duplex-list:character <- next-duplex 5:address:duplex-list:character
-  ]
-  memory-should-contain [
-    4 <- 13
-    6 <- 14
-    7 <- 0
-  ]
-]
-
-# Inserts list beginning at 'new' after 'in'. Returns some pointer into the list.
-recipe insert-duplex-range in:address:duplex-list:_elem, start:address:duplex-list:_elem -> in:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  reply-unless in
-  reply-unless start
-  end:address:duplex-list:_elem <- copy start
-  {
-    next:address:duplex-list:_elem <- next-duplex end/insert-range
-    break-unless next
-    end <- copy next
-    loop
-  }
-  next:address:duplex-list:_elem <- next-duplex in
-  dest:address:address:duplex-list:_elem <- get-address *end, next:offset
-  *dest <- copy next
-  {
-    break-unless next
-    dest <- get-address *next, prev:offset
-    *dest <- copy end
-  }
-  dest <- get-address *in, next:offset
-  *dest <- copy start
-  dest <- get-address *start, prev:offset
-  *dest <- copy in
-]
-
-recipe append-duplex in:address:duplex-list:_elem, new:address:duplex-list:_elem -> in:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  last:address:duplex-list:_elem <- last-duplex in
-  dest:address:address:duplex-list:_elem <- get-address *last, next:offset
-  *dest <- copy new
-  reply-unless new
-  dest <- get-address *new, prev:offset
-  *dest <- copy last
-]
-
-recipe last-duplex in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  result <- copy in
-  {
-    next:address:duplex-list:_elem <- next-duplex result
-    break-unless next
-    result <- copy next
-    loop
-  }
-]
-
-# helper for debugging
-recipe dump-duplex-from x:address:duplex-list:_elem [
-  local-scope
-  load-ingredients
-  $print x, [: ]
-  {
-    break-unless x
-    c:_elem <- get *x, value:offset
-    $print c, [ ]
-    x <- next-duplex x
-    {
-      is-newline?:boolean <- equal c, 10/newline
-      break-unless is-newline?
-      $print 10/newline
-      $print x, [: ]
-    }
-    loop
-  }
-  $print 10/newline, [---], 10/newline
-]
-
-recipe force-specialization-duplex-list-character [
-  1:address:duplex-list:character <- push-duplex 2:character, 1:address:duplex-list:character
-  2:character <- first-duplex 1:address:duplex-list:character
-  1:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
-  1:address:duplex-list:character <- prev-duplex 1:address:duplex-list:character
-  1:address:duplex-list:character <- insert-duplex 2:character, 1:address:duplex-list:character
-  1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character
-  1:address:duplex-list:character <- remove-duplex-between 1:address:duplex-list:character, 1:address:duplex-list:character
-  1:address:duplex-list:character <- insert-duplex-range 1:address:duplex-list:character, 1:address:duplex-list:character
-  1:address:duplex-list:character <- append-duplex 1:address:duplex-list:character, 1:address:duplex-list:character
-  1:address:duplex-list:character <- last-duplex 1:address:duplex-list:character
-]
-
- - - diff --git a/html/066stream.mu.html b/html/066stream.mu.html deleted file mode 100644 index 54e65159..00000000 --- a/html/066stream.mu.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - -Mu - 066stream.mu - - - - - - - - - - -
-# new type to help incrementally read strings
-container stream [
-  index:number
-  data:address:array:character
-]
-
-recipe new-stream [
-  local-scope
-  result:address:stream <- new stream:type
-  i:address:number <- get-address *result, index:offset
-  *i <- copy 0
-  d:address:address:array:character <- get-address *result, data:offset
-  *d <- next-ingredient
-  reply result
-]
-
-recipe rewind-stream [
-  local-scope
-  in:address:stream <- next-ingredient
-  x:address:number <- get-address *in, index:offset
-  *x <- copy 0
-  reply in/same-as-arg:0
-]
-
-recipe read-line [
-  local-scope
-  in:address:stream <- next-ingredient
-  idx:address:number <- get-address *in, index:offset
-  s:address:array:character <- get *in, data:offset
-  next-idx:number <- find-next s, 10/newline, *idx
-  result:address:array:character <- string-copy s, *idx, next-idx
-  *idx <- add next-idx, 1  # skip newline
-  reply result
-]
-
-recipe end-of-stream? [
-  local-scope
-  in:address:stream <- next-ingredient
-  idx:number <- get *in, index:offset
-  s:address:array:character <- get *in, data:offset
-  len:number <- length *s
-  result:boolean <- greater-or-equal idx, len
-  reply result
-]
-
- - - diff --git a/html/070display.cc.html b/html/070display.cc.html deleted file mode 100644 index 8fb46c4d..00000000 --- a/html/070display.cc.html +++ /dev/null @@ -1,534 +0,0 @@ - - - - -Mu - 070display.cc - - - - - - - - - - -
-//: Take charge of the text-mode display and console.
-
-//:: Display management
-
-:(before "End Globals")
-long long int Display_row = 0, Display_column = 0;
-bool Autodisplay = true;
-
-:(before "End Primitive Recipe Declarations")
-OPEN_CONSOLE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "open-console", OPEN_CONSOLE);
-:(before "End Primitive Recipe Checks")
-case OPEN_CONSOLE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case OPEN_CONSOLE: {
-  tb_init();
-  Display_row = Display_column = 0;
-  long long int width = tb_width();
-  long long int height = tb_height();
-  if (width > 222 || height > 222) tb_shutdown();
-  if (width > 222)
-    raise_error << "sorry, mu doesn't support windows wider than 222 characters. Please resize your window.\n" << end();
-  if (height > 222)
-    raise_error << "sorry, mu doesn't support windows taller than 222 characters. Please resize your window.\n" << end();
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-CLOSE_CONSOLE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "close-console", CLOSE_CONSOLE);
-:(before "End Primitive Recipe Checks")
-case CLOSE_CONSOLE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case CLOSE_CONSOLE: {
-  tb_shutdown();
-  break;
-}
-
-:(before "End Teardown")
-tb_shutdown();
-
-:(before "End Primitive Recipe Declarations")
-CLEAR_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "clear-display", CLEAR_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case CLEAR_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case CLEAR_DISPLAY: {
-  tb_clear();
-  Display_row = Display_column = 0;
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SYNC_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "sync-display", SYNC_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case SYNC_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SYNC_DISPLAY: {
-  tb_sync();
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-CLEAR_LINE_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "clear-line-on-display", CLEAR_LINE_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case CLEAR_LINE_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case CLEAR_LINE_ON_DISPLAY: {
-  long long int width = tb_width();
-  for (long long int x = Display_column; x < width; ++x) {
-    tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_BLACK);
-  }
-  tb_set_cursor(Display_column, Display_row);
-  if (Autodisplay) tb_present();
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-PRINT_CHARACTER_TO_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "print-character-to-display", PRINT_CHARACTER_TO_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case PRINT_CHARACTER_TO_DISPLAY: {
-  if (inst.ingredients.empty()) {
-    raise_error << maybe(get(Recipe, r).name) << "'print-character-to-display' requires at least one ingredient, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'print-character-to-display' should be a character, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-    break;
-  }
-  if (SIZE(inst.ingredients) > 1) {
-    if (!is_mu_number(inst.ingredients.at(1))) {
-      raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'print-character-to-display' should be a foreground color number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
-      break;
-    }
-  }
-  if (SIZE(inst.ingredients) > 2) {
-    if (!is_mu_number(inst.ingredients.at(2))) {
-      raise_error << maybe(get(Recipe, r).name) << "third ingredient of 'print-character-to-display' should be a background color number, but got " << inst.ingredients.at(2).original_string << '\n' << end();
-      break;
-    }
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case PRINT_CHARACTER_TO_DISPLAY: {
-  int h=tb_height(), w=tb_width();
-  long long int height = (h >= 0) ? h : 0;
-  long long int width = (w >= 0) ? w : 0;
-  long long int c = ingredients.at(0).at(0);
-  int color = TB_BLACK;
-  if (SIZE(ingredients) > 1) {
-    color = ingredients.at(1).at(0);
-  }
-  int bg_color = TB_BLACK;
-  if (SIZE(ingredients) > 2) {
-    bg_color = ingredients.at(2).at(0);
-    if (bg_color == 0) bg_color = TB_BLACK;
-  }
-  tb_change_cell(Display_column, Display_row, c, color, bg_color);
-  if (c == '\n' || c == '\r') {
-    if (Display_row < height-1) {
-      Display_column = 0;
-      ++Display_row;
-      tb_set_cursor(Display_column, Display_row);
-      if (Autodisplay) tb_present();
-    }
-    break;
-  }
-  if (c == '\b') {
-    if (Display_column > 0) {
-      tb_change_cell(Display_column-1, Display_row, ' ', color, bg_color);
-      --Display_column;
-      tb_set_cursor(Display_column, Display_row);
-      if (Autodisplay) tb_present();
-    }
-    break;
-  }
-  if (Display_column < width-1) {
-    ++Display_column;
-    tb_set_cursor(Display_column, Display_row);
-  }
-  if (Autodisplay) tb_present();
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-CURSOR_POSITION_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "cursor-position-on-display", CURSOR_POSITION_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case CURSOR_POSITION_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case CURSOR_POSITION_ON_DISPLAY: {
-  products.resize(2);
-  products.at(0).push_back(Display_row);
-  products.at(1).push_back(Display_column);
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-MOVE_CURSOR_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "move-cursor-on-display", MOVE_CURSOR_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case MOVE_CURSOR_ON_DISPLAY: {
-  if (SIZE(inst.ingredients) != 2) {
-    raise_error << maybe(get(Recipe, r).name) << "'move-cursor-on-display' requires two ingredients, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(0))) {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-    break;
-  }
-  if (!is_mu_number(inst.ingredients.at(1))) {
-    raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case MOVE_CURSOR_ON_DISPLAY: {
-  Display_row = ingredients.at(0).at(0);
-  Display_column = ingredients.at(1).at(0);
-  tb_set_cursor(Display_column, Display_row);
-  if (Autodisplay) tb_present();
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-MOVE_CURSOR_DOWN_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "move-cursor-down-on-display", MOVE_CURSOR_DOWN_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case MOVE_CURSOR_DOWN_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case MOVE_CURSOR_DOWN_ON_DISPLAY: {
-  int h=tb_height();
-  long long int height = (h >= 0) ? h : 0;
-  if (Display_row < height-1) {
-    Display_row++;
-    tb_set_cursor(Display_column, Display_row);
-    if (Autodisplay) tb_present();
-  }
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-MOVE_CURSOR_UP_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "move-cursor-up-on-display", MOVE_CURSOR_UP_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case MOVE_CURSOR_UP_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case MOVE_CURSOR_UP_ON_DISPLAY: {
-  if (Display_row > 0) {
-    Display_row--;
-    tb_set_cursor(Display_column, Display_row);
-    if (Autodisplay) tb_present();
-  }
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-MOVE_CURSOR_RIGHT_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "move-cursor-right-on-display", MOVE_CURSOR_RIGHT_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
-  int w=tb_width();
-  long long int width = (w >= 0) ? w : 0;
-  if (Display_column < width-1) {
-    Display_column++;
-    tb_set_cursor(Display_column, Display_row);
-    if (Autodisplay) tb_present();
-  }
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-MOVE_CURSOR_LEFT_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "move-cursor-left-on-display", MOVE_CURSOR_LEFT_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case MOVE_CURSOR_LEFT_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case MOVE_CURSOR_LEFT_ON_DISPLAY: {
-  if (Display_column > 0) {
-    Display_column--;
-    tb_set_cursor(Display_column, Display_row);
-    if (Autodisplay) tb_present();
-  }
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-DISPLAY_WIDTH,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "display-width", DISPLAY_WIDTH);
-:(before "End Primitive Recipe Checks")
-case DISPLAY_WIDTH: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case DISPLAY_WIDTH: {
-  products.resize(1);
-  products.at(0).push_back(tb_width());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-DISPLAY_HEIGHT,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "display-height", DISPLAY_HEIGHT);
-:(before "End Primitive Recipe Checks")
-case DISPLAY_HEIGHT: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case DISPLAY_HEIGHT: {
-  products.resize(1);
-  products.at(0).push_back(tb_height());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-HIDE_CURSOR_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "hide-cursor-on-display", HIDE_CURSOR_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case HIDE_CURSOR_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case HIDE_CURSOR_ON_DISPLAY: {
-  tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR);
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SHOW_CURSOR_ON_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "show-cursor-on-display", SHOW_CURSOR_ON_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case SHOW_CURSOR_ON_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SHOW_CURSOR_ON_DISPLAY: {
-  tb_set_cursor(Display_row, Display_column);
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-HIDE_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "hide-display", HIDE_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case HIDE_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case HIDE_DISPLAY: {
-  Autodisplay = false;
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SHOW_DISPLAY,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "show-display", SHOW_DISPLAY);
-:(before "End Primitive Recipe Checks")
-case SHOW_DISPLAY: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SHOW_DISPLAY: {
-  Autodisplay = true;
-  tb_present();
-  break;
-}
-
-//:: Keyboard/mouse management
-
-:(before "End Primitive Recipe Declarations")
-WAIT_FOR_SOME_INTERACTION,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "wait-for-some-interaction", WAIT_FOR_SOME_INTERACTION);
-:(before "End Primitive Recipe Checks")
-case WAIT_FOR_SOME_INTERACTION: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case WAIT_FOR_SOME_INTERACTION: {
-  tb_event event;
-  tb_poll_event(&event);
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-CHECK_FOR_INTERACTION,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "check-for-interaction", CHECK_FOR_INTERACTION);
-:(before "End Primitive Recipe Checks")
-case CHECK_FOR_INTERACTION: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case CHECK_FOR_INTERACTION: {
-  products.resize(2);  // result and status
-  tb_event event;
-  int event_type = tb_peek_event(&event, 5/*ms*/);
-  if (event_type == TB_EVENT_KEY && event.ch) {
-    products.at(0).push_back(/*text event*/0);
-    products.at(0).push_back(event.ch);
-    products.at(0).push_back(0);
-    products.at(0).push_back(0);
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  // treat keys within ascii as unicode characters
-  if (event_type == TB_EVENT_KEY && event.key < 0xff) {
-    products.at(0).push_back(/*text event*/0);
-    if (event.key == TB_KEY_CTRL_C) tb_shutdown(), exit(1);
-    if (event.key == TB_KEY_BACKSPACE2) event.key = TB_KEY_BACKSPACE;
-    if (event.key == TB_KEY_CARRIAGE_RETURN) event.key = TB_KEY_NEWLINE;
-    products.at(0).push_back(event.key);
-    products.at(0).push_back(0);
-    products.at(0).push_back(0);
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  // keys outside ascii aren't unicode characters but arbitrary termbox inventions
-  if (event_type == TB_EVENT_KEY) {
-    products.at(0).push_back(/*keycode event*/1);
-    products.at(0).push_back(event.key);
-    products.at(0).push_back(0);
-    products.at(0).push_back(0);
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  if (event_type == TB_EVENT_MOUSE) {
-    products.at(0).push_back(/*touch event*/2);
-    products.at(0).push_back(event.key);  // which button, etc.
-    products.at(0).push_back(event.y);  // row
-    products.at(0).push_back(event.x);  // column
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  if (event_type == TB_EVENT_RESIZE) {
-    products.at(0).push_back(/*resize event*/3);
-    products.at(0).push_back(event.w);  // width
-    products.at(0).push_back(event.h);  // height
-    products.at(0).push_back(0);
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  assert(event_type == 0);
-  products.at(0).push_back(0);
-  products.at(0).push_back(0);
-  products.at(0).push_back(0);
-  products.at(0).push_back(0);
-  products.at(1).push_back(/*found*/false);
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-INTERACTIONS_LEFT,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "interactions-left?", INTERACTIONS_LEFT);
-:(before "End Primitive Recipe Checks")
-case INTERACTIONS_LEFT: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case INTERACTIONS_LEFT: {
-  products.resize(1);
-  products.at(0).push_back(tb_event_ready());
-  break;
-}
-
-//: a hack to make edit.mu more responsive
-
-:(before "End Primitive Recipe Declarations")
-CLEAR_DISPLAY_FROM,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "clear-display-from", CLEAR_DISPLAY_FROM);
-:(before "End Primitive Recipe Checks")
-case CLEAR_DISPLAY_FROM: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case CLEAR_DISPLAY_FROM: {
-  // todo: error checking
-  int row = ingredients.at(0).at(0);
-  int column = ingredients.at(1).at(0);
-  int left = ingredients.at(2).at(0);
-  int right = ingredients.at(3).at(0);
-  int height=tb_height();
-  for (; row < height; ++row, column=left) {  // start column from left in every inner loop except first
-    for (; column <= right; ++column) {
-      tb_change_cell(column, row, ' ', TB_WHITE, TB_BLACK);
-    }
-  }
-  if (Autodisplay) tb_present();
-  break;
-}
-
- - - diff --git a/html/070string.mu.html b/html/070string.mu.html new file mode 100644 index 00000000..08edff08 --- /dev/null +++ b/html/070string.mu.html @@ -0,0 +1,1356 @@ + + + + +Mu - 070string.mu + + + + + + + + + + +
+# Some useful helpers for dealing with strings.
+
+recipe string-equal [
+  local-scope
+  a:address:array:character <- next-ingredient
+  a-len:number <- length *a
+  b:address:array:character <- next-ingredient
+  b-len:number <- length *b
+  # compare lengths
+  {
+    trace 99, [string-equal], [comparing lengths]
+    length-equal?:boolean <- equal a-len, b-len
+    break-if length-equal?
+    reply 0
+  }
+  # compare each corresponding character
+  trace 99, [string-equal], [comparing characters]
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, a-len
+    break-if done?
+    a2:character <- index *a, i
+    b2:character <- index *b, i
+    {
+      chars-match?:boolean <- equal a2, b2
+      break-if chars-match?
+      reply 0
+    }
+    i <- add i, 1
+    loop
+  }
+  reply 1
+]
+
+scenario string-equal-reflexive [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    3:boolean/raw <- string-equal x, x
+  ]
+  memory-should-contain [
+    3 <- 1  # x == x for all x
+  ]
+]
+
+scenario string-equal-identical [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    y:address:array:character <- new [abc]
+    3:boolean/raw <- string-equal x, y
+  ]
+  memory-should-contain [
+    3 <- 1  # abc == abc
+  ]
+]
+
+scenario string-equal-distinct-lengths [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    y:address:array:character <- new [abcd]
+    3:boolean/raw <- string-equal x, y
+  ]
+  memory-should-contain [
+    3 <- 0  # abc != abcd
+  ]
+  trace-should-contain [
+    string-equal: comparing lengths
+  ]
+  trace-should-not-contain [
+    string-equal: comparing characters
+  ]
+]
+
+scenario string-equal-with-empty [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new []
+    y:address:array:character <- new [abcd]
+    3:boolean/raw <- string-equal x, y
+  ]
+  memory-should-contain [
+    3 <- 0  # "" != abcd
+  ]
+]
+
+scenario string-equal-common-lengths-but-distinct [
+  run [
+    default-space:address:array:location <- new location:type, 30
+    x:address:array:character <- new [abc]
+    y:address:array:character <- new [abd]
+    3:boolean/raw <- string-equal x, y
+  ]
+  memory-should-contain [
+    3 <- 0  # abc != abd
+  ]
+]
+
+# A new type to help incrementally construct strings.
+container buffer [
+  length:number
+  data:address:array:character
+]
+
+recipe new-buffer [
+  local-scope
+  result:address:buffer <- new buffer:type
+  len:address:number <- get-address *result, length:offset
+  *len:address:number <- copy 0
+  s:address:address:array:character <- get-address *result, data:offset
+  capacity:number, found?:boolean <- next-ingredient
+  assert found?, [new-buffer must get a capacity argument]
+  *s <- new character:type, capacity
+  reply result
+]
+
+recipe grow-buffer [
+  local-scope
+  in:address:buffer <- next-ingredient
+  # double buffer size
+  x:address:address:array:character <- get-address *in, data:offset
+  oldlen:number <- length **x
+  newlen:number <- multiply oldlen, 2
+  olddata:address:array:character <- copy *x
+  *x <- new character:type, newlen
+  # copy old contents
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, oldlen
+    break-if done?
+    src:character <- index *olddata, i
+    dest:address:character <- index-address **x, i
+    *dest <- copy src
+    i <- add i, 1
+    loop
+  }
+  reply in
+]
+
+recipe buffer-full? [
+  local-scope
+  in:address:buffer <- next-ingredient
+  len:number <- get *in, length:offset
+  s:address:array:character <- get *in, data:offset
+  capacity:number <- length *s
+  result:boolean <- greater-or-equal len, capacity
+  reply result
+]
+
+# in <- buffer-append in:address:buffer, c:character
+recipe buffer-append [
+  local-scope
+  in:address:buffer <- next-ingredient
+  c:character <- next-ingredient
+  len:address:number <- get-address *in, length:offset
+  {
+    # backspace? just drop last character if it exists and return
+    backspace?:boolean <- equal c, 8/backspace
+    break-unless backspace?
+    empty?:boolean <- lesser-or-equal *len, 0
+    reply-if empty?, in/same-as-ingredient:0
+    *len <- subtract *len, 1
+    reply in/same-as-ingredient:0
+  }
+  {
+    # grow buffer if necessary
+    full?:boolean <- buffer-full? in
+    break-unless full?
+    in <- grow-buffer in
+  }
+  s:address:array:character <- get *in, data:offset
+  dest:address:character <- index-address *s, *len
+  *dest <- copy c
+  *len <- add *len, 1
+  reply in/same-as-ingredient:0
+]
+
+scenario buffer-append-works [
+  run [
+    local-scope
+    x:address:buffer <- new-buffer 3
+    s1:address:array:character <- get *x:address:buffer, data:offset
+    x:address:buffer <- buffer-append x:address:buffer, 97  # 'a'
+    x:address:buffer <- buffer-append x:address:buffer, 98  # 'b'
+    x:address:buffer <- buffer-append x:address:buffer, 99  # 'c'
+    s2:address:array:character <- get *x:address:buffer, data:offset
+    1:boolean/raw <- equal s1:address:array:character, s2:address:array:character
+    2:array:character/raw <- copy *s2:address:array:character
+    +buffer-filled
+    x:address:buffer <- buffer-append x:address:buffer, 100  # 'd'
+    s3:address:array:character <- get *x:address:buffer, data:offset
+    10:boolean/raw <- equal s1:address:array:character, s3:address:array:character
+    11:number/raw <- get *x:address:buffer, length:offset
+    12:array:character/raw <- copy *s3:address:array:character
+  ]
+  memory-should-contain [
+    # before +buffer-filled
+    1 <- 1   # no change in data pointer
+    2 <- 3   # size of data
+    3 <- 97  # data
+    4 <- 98
+    5 <- 99
+    # in the end
+    10 <- 0   # data pointer has grown
+    11 <- 4   # final length
+    12 <- 6   # but data's capacity has doubled
+    13 <- 97  # data
+    14 <- 98
+    15 <- 99
+    16 <- 100
+    17 <- 0
+    18 <- 0
+  ]
+]
+
+scenario buffer-append-handles-backspace [
+  run [
+    local-scope
+    x:address:buffer <- new-buffer 3
+    x <- buffer-append x, 97  # 'a'
+    x <- buffer-append x, 98  # 'b'
+    x <- buffer-append x, 8/backspace
+    s:address:array:character <- buffer-to-array x
+    1:array:character/raw <- copy *s
+  ]
+  memory-should-contain [
+    1 <- 1   # length
+    2 <- 97  # contents
+    3 <- 0
+  ]
+]
+
+# result:address:array:character <- integer-to-decimal-string n:number
+recipe integer-to-decimal-string [
+  local-scope
+  n:number <- next-ingredient
+  # is it zero?
+  {
+    break-if n
+    result:address:array:character <- new [0]
+    reply result
+  }
+  # save sign
+  negate-result:boolean <- copy 0
+  {
+    negative?:boolean <- lesser-than n, 0
+    break-unless negative?
+    negate-result <- copy 1
+    n <- multiply n, -1
+  }
+  # add digits from right to left into intermediate buffer
+  tmp:address:buffer <- new-buffer 30
+  digit-base:number <- copy 48  # '0'
+  {
+    done?:boolean <- equal n, 0
+    break-if done?
+    n, digit:number <- divide-with-remainder n, 10
+    c:character <- add digit-base, digit
+    tmp:address:buffer <- buffer-append tmp, c
+    loop
+  }
+  # add sign
+  {
+    break-unless negate-result:boolean
+    tmp <- buffer-append tmp, 45  # '-'
+  }
+  # reverse buffer into string result
+  len:number <- get *tmp, length:offset
+  buf:address:array:character <- get *tmp, data:offset
+  result:address:array:character <- new character:type, len
+  i:number <- subtract len, 1  # source index, decreasing
+  j:number <- copy 0  # destination index, increasing
+  {
+    # while i >= 0
+    done?:boolean <- lesser-than i, 0
+    break-if done?
+    # result[j] = tmp[i]
+    src:character <- index *buf, i
+    dest:address:character <- index-address *result, j
+    *dest <- copy src
+    i <- subtract i, 1
+    j <- add j, 1
+    loop
+  }
+  reply result
+]
+
+recipe buffer-to-array [
+  local-scope
+  in:address:buffer <- next-ingredient
+  {
+    # propagate null buffer
+    break-if in
+    reply 0
+  }
+  len:number <- get *in, length:offset
+  s:address:array:character <- get *in, data:offset
+  # we can't just return s because it is usually the wrong length
+  result:address:array:character <- new character:type, len
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    src:character <- index *s, i
+    dest:address:character <- index-address *result, i
+    *dest <- copy src
+    i <- add i, 1
+    loop
+  }
+  reply result
+]
+
+scenario integer-to-decimal-digit-zero [
+  run [
+    1:address:array:character/raw <- integer-to-decimal-string 0
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [0]
+  ]
+]
+
+scenario integer-to-decimal-digit-positive [
+  run [
+    1:address:array:character/raw <- integer-to-decimal-string 234
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [234]
+  ]
+]
+
+scenario integer-to-decimal-digit-negative [
+  run [
+    1:address:array:character/raw <- integer-to-decimal-string -1
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2 <- 2
+    3 <- 45  # '-'
+    4 <- 49  # '1'
+  ]
+]
+
+# result:address:array:character <- string-append a:address:array:character, b:address:array:character
+recipe string-append [
+  local-scope
+  # result = new character[a.length + b.length]
+  a:address:array:character <- next-ingredient
+  a-len:number <- length *a
+  b:address:array:character <- next-ingredient
+  b-len:number <- length *b
+  result-len:number <- add a-len, b-len
+  result:address:array:character <- new character:type, result-len
+  # copy a into result
+  result-idx:number <- copy 0
+  i:number <- copy 0
+  {
+    # while i < a.length
+    a-done?:boolean <- greater-or-equal i, a-len
+    break-if a-done?
+    # result[result-idx] = a[i]
+    out:address:character <- index-address *result, result-idx
+    in:character <- index *a, i
+    *out <- copy in
+    i <- add i, 1
+    result-idx <- add result-idx, 1
+    loop
+  }
+  # copy b into result
+  i <- copy 0
+  {
+    # while i < b.length
+    b-done?:boolean <- greater-or-equal i, b-len
+    break-if b-done?
+    # result[result-idx] = a[i]
+    out:address:character <- index-address *result, result-idx
+    in:character <- index *b, i
+    *out <- copy in
+    i <- add i, 1
+    result-idx <- add result-idx, 1
+    loop
+  }
+  reply result
+]
+
+scenario string-append-1 [
+  run [
+    1:address:array:character/raw <- new [hello,]
+    2:address:array:character/raw <- new [ world!]
+    3:address:array:character/raw <- string-append 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:string <- [hello, world!]
+  ]
+]
+
+scenario replace-character-in-string [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 98/b, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [azc]
+  ]
+]
+
+recipe string-replace [
+  local-scope
+  s:address:array:character <- next-ingredient
+  oldc:character <- next-ingredient
+  newc:character <- next-ingredient
+  from:number, _ <- next-ingredient  # default to 0
+  len:number <- length *s
+  i:number <- find-next s, oldc, from
+  done?:boolean <- greater-or-equal i, len
+  reply-if done?, s/same-as-ingredient:0
+  dest:address:character <- index-address *s, i
+  *dest <- copy newc
+  i <- add i, 1
+  s <- string-replace s, oldc, newc, i
+  reply s/same-as-ingredient:0
+]
+
+scenario replace-character-at-start [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 97/a, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [zbc]
+  ]
+]
+
+scenario replace-character-at-end [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 99/c, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [abz]
+  ]
+]
+
+scenario replace-character-missing [
+  run [
+    1:address:array:character/raw <- new [abc]
+    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 100/d, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [abc]
+  ]
+]
+
+scenario replace-all-characters [
+  run [
+    1:address:array:character/raw <- new [banana]
+    1:address:array:character/raw <- string-replace 1:address:array:character/raw, 97/a, 122/z
+    2:array:character/raw <- copy *1:address:array:character/raw
+  ]
+  memory-should-contain [
+    2:string <- [bznznz]
+  ]
+]
+
+# replace underscores in first with remaining args
+# result:address:array:character <- interpolate template:address:array:character, ...
+recipe interpolate [
+  local-scope
+  template:address:array:character <- next-ingredient
+  # compute result-len, space to allocate for result
+  tem-len:number <- length *template
+  result-len:number <- copy tem-len
+  {
+    # while arg received
+    a:address:array:character, arg-received?:boolean <- next-ingredient
+    break-unless arg-received?
+    # result-len = result-len + arg.length - 1 (for the 'underscore' being replaced)
+    a-len:number <- length *a
+    result-len <- add result-len, a-len
+    result-len <- subtract result-len, 1
+    loop
+  }
+  rewind-ingredients
+  _ <- next-ingredient  # skip template
+  result:address:array:character <- new character:type, result-len
+  # repeatedly copy sections of template and 'holes' into result
+  result-idx:number <- copy 0
+  i:number <- copy 0
+  {
+    # while arg received
+    a:address:array:character, arg-received?:boolean <- next-ingredient
+    break-unless arg-received?
+    # copy template into result until '_'
+    {
+      # while i < template.length
+      tem-done?:boolean <- greater-or-equal i, tem-len
+      break-if tem-done?, +done:label
+      # while template[i] != '_'
+      in:character <- index *template, i
+      underscore?:boolean <- equal in, 95/_
+      break-if underscore?
+      # result[result-idx] = template[i]
+      out:address:character <- index-address *result, result-idx
+      *out <- copy in
+      i <- add i, 1
+      result-idx <- add result-idx, 1
+      loop
+    }
+    # copy 'a' into result
+    j:number <- copy 0
+    {
+      # while j < a.length
+      arg-done?:boolean <- greater-or-equal j, a-len
+      break-if arg-done?
+      # result[result-idx] = a[j]
+      in:character <- index *a, j
+      out:address:character <- index-address *result, result-idx
+      *out <- copy in
+      j <- add j, 1
+      result-idx <- add result-idx, 1
+      loop
+    }
+    # skip '_' in template
+    i <- add i, 1
+    loop  # interpolate next arg
+  }
+  +done
+  # done with holes; copy rest of template directly into result
+  {
+    # while i < template.length
+    tem-done?:boolean <- greater-or-equal i, tem-len
+    break-if tem-done?
+    # result[result-idx] = template[i]
+    in:character <- index *template, i
+    out:address:character <- index-address *result, result-idx:number
+    *out <- copy in
+    i <- add i, 1
+    result-idx <- add result-idx, 1
+    loop
+  }
+  reply result
+]
+
+scenario interpolate-works [
+  run [
+    1:address:array:character/raw <- new [abc _]
+    2:address:array:character/raw <- new [def]
+    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:string <- [abc def]
+  ]
+]
+
+scenario interpolate-at-start [
+  run [
+    1:address:array:character/raw <- new [_, hello!]
+    2:address:array:character/raw <- new [abc]
+    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:string <- [abc, hello!]
+    16 <- 0  # out of bounds
+  ]
+]
+
+scenario interpolate-at-end [
+  run [
+    1:address:array:character/raw <- new [hello, _]
+    2:address:array:character/raw <- new [abc]
+    3:address:array:character/raw <- interpolate 1:address:array:character/raw, 2:address:array:character/raw
+    4:array:character/raw <- copy *3:address:array:character/raw
+  ]
+  memory-should-contain [
+    4:string <- [hello, abc]
+  ]
+]
+
+# result:boolean <- space? c:character
+recipe space? [
+  local-scope
+  c:character <- next-ingredient
+  # most common case first
+  result:boolean <- equal c, 32/space
+  reply-if result, result
+  result <- equal c, 10/newline
+  reply-if result, result
+  result <- equal c, 9/tab
+  reply-if result, result
+  result <- equal c, 13/carriage-return
+  reply-if result, result
+  # remaining uncommon cases in sorted order
+  # http://unicode.org code-points in unicode-set Z and Pattern_White_Space
+  result <- equal c, 11/ctrl-k
+  reply-if result, result
+  result <- equal c, 12/ctrl-l
+  reply-if result, result
+  result <- equal c, 133/ctrl-0085
+  reply-if result, result
+  result <- equal c, 160/no-break-space
+  reply-if result, result
+  result <- equal c, 5760/ogham-space-mark
+  reply-if result, result
+  result <- equal c, 8192/en-quad
+  reply-if result, result
+  result <- equal c, 8193/em-quad
+  reply-if result, result
+  result <- equal c, 8194/en-space
+  reply-if result, result
+  result <- equal c, 8195/em-space
+  reply-if result, result
+  result <- equal c, 8196/three-per-em-space
+  reply-if result, result
+  result <- equal c, 8197/four-per-em-space
+  reply-if result, result
+  result <- equal c, 8198/six-per-em-space
+  reply-if result, result
+  result <- equal c, 8199/figure-space
+  reply-if result, result
+  result <- equal c, 8200/punctuation-space
+  reply-if result, result
+  result <- equal c, 8201/thin-space
+  reply-if result, result
+  result <- equal c, 8202/hair-space
+  reply-if result, result
+  result <- equal c, 8206/left-to-right
+  reply-if result, result
+  result <- equal c, 8207/right-to-left
+  reply-if result, result
+  result <- equal c, 8232/line-separator
+  reply-if result, result
+  result <- equal c, 8233/paragraph-separator
+  reply-if result, result
+  result <- equal c, 8239/narrow-no-break-space
+  reply-if result, result
+  result <- equal c, 8287/medium-mathematical-space
+  reply-if result, result
+  result <- equal c, 12288/ideographic-space
+  reply result
+]
+
+# result:address:array:character <- trim s:address:array:character
+recipe trim [
+  local-scope
+  s:address:array:character <- next-ingredient
+  len:number <- length *s
+  # left trim: compute start
+  start:number <- copy 0
+  {
+    {
+      at-end?:boolean <- greater-or-equal start, len
+      break-unless at-end?
+      result:address:array:character <- new character:type, 0
+      reply result
+    }
+    curr:character <- index *s, start
+    whitespace?:boolean <- space? curr
+    break-unless whitespace?
+    start <- add start, 1
+    loop
+  }
+  # right trim: compute end
+  end:number <- subtract len, 1
+  {
+    not-at-start?:boolean <- greater-than end, start
+    assert not-at-start?, [end ran up against start]
+    curr:character <- index *s, end
+    whitespace?:boolean <- space? curr
+    break-unless whitespace?
+    end <- subtract end, 1
+    loop
+  }
+  # result = new character[end+1 - start]
+  new-len:number <- subtract end, start, -1
+  result:address:array:character <- new character:type, new-len
+  # copy the untrimmed parts between start and end
+  i:number <- copy start
+  j:number <- copy 0
+  {
+    # while i <= end
+    done?:boolean <- greater-than i, end
+    break-if done?
+    # result[j] = s[i]
+    src:character <- index *s, i
+    dest:address:character <- index-address *result, j
+    *dest <- copy src
+    i <- add i, 1
+    j <- add j, 1
+    loop
+  }
+  reply result
+]
+
+scenario trim-unmodified [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [abc]
+  ]
+]
+
+scenario trim-left [
+  run [
+    1:address:array:character <- new [  abc]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [abc]
+  ]
+]
+
+scenario trim-right [
+  run [
+    1:address:array:character <- new [abc  ]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [abc]
+  ]
+]
+
+scenario trim-left-right [
+  run [
+    1:address:array:character <- new [  abc   ]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [abc]
+  ]
+]
+
+scenario trim-newline-tab [
+  run [
+    1:address:array:character <- new [  abc
+]
+    2:address:array:character <- trim 1:address:array:character
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [abc]
+  ]
+]
+
+# next-index:number <- find-next text:address:array:character, pattern:character, idx:number
+recipe find-next [
+  local-scope
+  text:address:array:character <- next-ingredient
+  pattern:character <- next-ingredient
+  idx:number <- next-ingredient
+  len:number <- length *text
+  {
+    eof?:boolean <- greater-or-equal idx, len
+    break-if eof?
+    curr:character <- index *text, idx
+    found?:boolean <- equal curr, pattern
+    break-if found?
+    idx <- add idx, 1
+    loop
+  }
+  reply idx
+]
+
+scenario string-find-next [
+  run [
+    1:address:array:character <- new [a/b]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 1
+  ]
+]
+
+scenario string-find-next-empty [
+  run [
+    1:address:array:character <- new []
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 0
+  ]
+]
+
+scenario string-find-next-initial [
+  run [
+    1:address:array:character <- new [/abc]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 0  # prefix match
+  ]
+]
+
+scenario string-find-next-final [
+  run [
+    1:address:array:character <- new [abc/]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 3  # suffix match
+  ]
+]
+
+scenario string-find-next-missing [
+  run [
+    1:address:array:character <- new [abc]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 3  # no match
+  ]
+]
+
+scenario string-find-next-invalid-index [
+  run [
+    1:address:array:character <- new [abc]
+    2:number <- find-next 1:address:array:character, 47/slash, 4/start-index
+  ]
+  memory-should-contain [
+    2 <- 4  # no change
+  ]
+]
+
+scenario string-find-next-first [
+  run [
+    1:address:array:character <- new [ab/c/]
+    2:number <- find-next 1:address:array:character, 47/slash, 0/start-index
+  ]
+  memory-should-contain [
+    2 <- 2  # first '/' of multiple
+  ]
+]
+
+scenario string-find-next-second [
+  run [
+    1:address:array:character <- new [ab/c/]
+    2:number <- find-next 1:address:array:character, 47/slash, 3/start-index
+  ]
+  memory-should-contain [
+    2 <- 4  # second '/' of multiple
+  ]
+]
+
+# next-index:number <- find-substring text:address:array:character, pattern:address:array:character, idx:number
+# like find-next, but searches for multiple characters
+# fairly dumb algorithm
+recipe find-substring [
+  local-scope
+  text:address:array:character <- next-ingredient
+  pattern:address:array:character <- next-ingredient
+  idx:number <- next-ingredient
+  first:character <- index *pattern, 0
+  # repeatedly check for match at current idx
+  len:number <- length *text
+  {
+    # does some unnecessary work checking for substrings even when there isn't enough of text left
+    done?:boolean <- greater-or-equal idx, len
+    break-if done?
+    found?:boolean <- match-at text, pattern, idx
+    break-if found?
+    idx <- add idx, 1
+    # optimization: skip past indices that definitely won't match
+    idx <- find-next text, first, idx
+    loop
+  }
+  reply idx
+]
+
+scenario find-substring-1 [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bc]
+    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1
+  ]
+]
+
+scenario find-substring-2 [
+  run [
+    1:address:array:character <- new [abcd]
+    2:address:array:character <- new [bc]
+    3:number <- find-substring 1:address:array:character, 2:address:array:character, 1
+  ]
+  memory-should-contain [
+    3 <- 1
+  ]
+]
+
+scenario find-substring-no-match [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bd]
+    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 3  # not found
+  ]
+]
+
+scenario find-substring-suffix-match [
+  run [
+    1:address:array:character <- new [abcd]
+    2:address:array:character <- new [cd]
+    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 2
+  ]
+]
+
+scenario find-substring-suffix-match-2 [
+  run [
+    1:address:array:character <- new [abcd]
+    2:address:array:character <- new [cde]
+    3:number <- find-substring 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 4  # not found
+  ]
+]
+
+# result:boolean <- match-at text:address:array:character, pattern:address:array:character, idx:number
+# checks if substring matches at index 'idx'
+recipe match-at [
+  local-scope
+  text:address:array:character <- next-ingredient
+  pattern:address:array:character <- next-ingredient
+  idx:number <- next-ingredient
+  pattern-len:number <- length *pattern
+  # check that there's space left for the pattern
+  {
+    x:number <- length *text
+    x <- subtract x, pattern-len
+    enough-room?:boolean <- lesser-or-equal idx, x
+    break-if enough-room?
+    reply 0/not-found
+  }
+  # check each character of pattern
+  pattern-idx:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal pattern-idx, pattern-len
+    break-if done?
+    c:character <- index *text, idx
+    exp:character <- index *pattern, pattern-idx
+    {
+      match?:boolean <- equal c, exp
+      break-if match?
+      reply 0/not-found
+    }
+    idx <- add idx, 1
+    pattern-idx <- add pattern-idx, 1
+    loop
+  }
+  reply 1/found
+]
+
+scenario match-at-checks-substring-at-index [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [ab]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # match found
+  ]
+]
+
+scenario match-at-reflexive [
+  run [
+    1:address:array:character <- new [abc]
+    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # match found
+  ]
+]
+
+scenario match-at-outside-bounds [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [a]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
+  ]
+  memory-should-contain [
+    3 <- 0  # never matches
+  ]
+]
+
+scenario match-at-empty-pattern [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new []
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # always matches empty pattern given a valid index
+  ]
+]
+
+scenario match-at-empty-pattern-outside-bound [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new []
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 4
+  ]
+  memory-should-contain [
+    3 <- 0  # no match
+  ]
+]
+
+scenario match-at-empty-text [
+  run [
+    1:address:array:character <- new []
+    2:address:array:character <- new [abc]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 0  # no match
+  ]
+]
+
+scenario match-at-empty-against-empty [
+  run [
+    1:address:array:character <- new []
+    3:boolean <- match-at 1:address:array:character, 1:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 1  # matches because pattern is also empty
+  ]
+]
+
+scenario match-at-inside-bounds [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bc]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 1
+  ]
+  memory-should-contain [
+    3 <- 1  # matches inner substring
+  ]
+]
+
+scenario match-at-inside-bounds-2 [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- new [bc]
+    3:boolean <- match-at 1:address:array:character, 2:address:array:character, 0
+  ]
+  memory-should-contain [
+    3 <- 0  # no match
+  ]
+]
+
+# result:address:array:address:array:character <- split s:address:array:character, delim:character
+recipe split [
+  local-scope
+  s:address:array:character <- next-ingredient
+  delim:character <- next-ingredient
+  # empty string? return empty array
+  len:number <- length *s
+  {
+    empty?:boolean <- equal len, 0
+    break-unless empty?
+    result:address:array:address:array:character <- new location:type, 0
+    reply result
+  }
+  # count #pieces we need room for
+  count:number <- copy 1  # n delimiters = n+1 pieces
+  idx:number <- copy 0
+  {
+    idx <- find-next s, delim, idx
+    done?:boolean <- greater-or-equal idx, len
+    break-if done?
+    idx <- add idx, 1
+    count <- add count, 1
+    loop
+  }
+  # allocate space
+  result:address:array:address:array:character <- new location:type, count
+  # repeatedly copy slices start..end until delimiter into result[curr-result]
+  curr-result:number <- copy 0
+  start:number <- copy 0
+  {
+    # while next delim exists
+    done?:boolean <- greater-or-equal start, len
+    break-if done?
+    end:number <- find-next s, delim, start
+    # copy start..end into result[curr-result]
+    dest:address:address:array:character <- index-address *result, curr-result
+    *dest <- string-copy s, start, end
+    # slide over to next slice
+    start <- add end, 1
+    curr-result <- add curr-result, 1
+    loop
+  }
+  reply result
+]
+
+scenario string-split-1 [
+  run [
+    1:address:array:character <- new [a/b]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    5:address:array:character <- index *2:address:array:address:array:character, 1
+    10:array:character <- copy *4:address:array:character
+    20:array:character <- copy *5:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 2  # length of result
+    10:string <- [a]
+    20:string <- [b]
+  ]
+]
+
+scenario string-split-2 [
+  run [
+    1:address:array:character <- new [a/b/c]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    5:address:array:character <- index *2:address:array:address:array:character, 1
+    6:address:array:character <- index *2:address:array:address:array:character, 2
+    10:array:character <- copy *4:address:array:character
+    20:array:character <- copy *5:address:array:character
+    30:array:character <- copy *6:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 3  # length of result
+    10:string <- [a]
+    20:string <- [b]
+    30:string <- [c]
+  ]
+]
+
+scenario string-split-missing [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    10:array:character <- copy *4:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 1  # length of result
+    10:string <- [abc]
+  ]
+]
+
+scenario string-split-empty [
+  run [
+    1:address:array:character <- new []
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 0  # empty result
+  ]
+]
+
+scenario string-split-empty-piece [
+  run [
+    1:address:array:character <- new [a/b//c]
+    2:address:array:address:array:character <- split 1:address:array:character, 47/slash
+    3:number <- length *2:address:array:address:array:character
+    4:address:array:character <- index *2:address:array:address:array:character, 0
+    5:address:array:character <- index *2:address:array:address:array:character, 1
+    6:address:array:character <- index *2:address:array:address:array:character, 2
+    7:address:array:character <- index *2:address:array:address:array:character, 3
+    10:array:character <- copy *4:address:array:character
+    20:array:character <- copy *5:address:array:character
+    30:array:character <- copy *6:address:array:character
+    40:array:character <- copy *7:address:array:character
+  ]
+  memory-should-contain [
+    3 <- 4  # length of result
+    10:string <- [a]
+    20:string <- [b]
+    30:string <- []
+    40:string <- [c]
+  ]
+]
+
+# x:address:array:character, y:address:array:character <- split-first text:address:array:character, delim:character
+recipe split-first [
+  local-scope
+  text:address:array:character <- next-ingredient
+  delim:character <- next-ingredient
+  # empty string? return empty strings
+  len:number <- length *text
+  {
+    empty?:boolean <- equal len, 0
+    break-unless empty?
+    x:address:array:character <- new []
+    y:address:array:character <- new []
+    reply x, y
+  }
+  idx:number <- find-next text, delim, 0
+  x:address:array:character <- string-copy text, 0, idx
+  idx <- add idx, 1
+  y:address:array:character <- string-copy text, idx, len
+  reply x, y
+]
+
+scenario string-split-first [
+  run [
+    1:address:array:character <- new [a/b]
+    2:address:array:character, 3:address:array:character <- split-first 1:address:array:character, 47/slash
+    10:array:character <- copy *2:address:array:character
+    20:array:character <- copy *3:address:array:character
+  ]
+  memory-should-contain [
+    10:string <- [a]
+    20:string <- [b]
+  ]
+]
+
+# result:address:array:character <- string-copy buf:address:array:character, start:number, end:number
+# todo: make this generic
+recipe string-copy [
+  local-scope
+  buf:address:array:character <- next-ingredient
+  start:number <- next-ingredient
+  end:number <- next-ingredient
+  # if end is out of bounds, trim it
+  len:number <- length *buf
+  end:number <- min len, end
+  # allocate space for result
+  len <- subtract end, start
+  result:address:array:character <- new character:type, len
+  # copy start..end into result[curr-result]
+  src-idx:number <- copy start
+  dest-idx:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal src-idx, end
+    break-if done?
+    src:character <- index *buf, src-idx
+    dest:address:character <- index-address *result, dest-idx
+    *dest <- copy src
+    src-idx <- add src-idx, 1
+    dest-idx <- add dest-idx, 1
+    loop
+  }
+  reply result
+]
+
+scenario string-copy-copies-substring [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- string-copy 1:address:array:character, 1, 3
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [bc]
+  ]
+]
+
+scenario string-copy-out-of-bounds [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- string-copy 1:address:array:character, 2, 4
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- [c]
+  ]
+]
+
+scenario string-copy-out-of-bounds-2 [
+  run [
+    1:address:array:character <- new [abc]
+    2:address:array:character <- string-copy 1:address:array:character, 3, 3
+    3:array:character <- copy *2:address:array:character
+  ]
+  memory-should-contain [
+    3:string <- []
+  ]
+]
+
+recipe min [
+  local-scope
+  x:number <- next-ingredient
+  y:number <- next-ingredient
+  {
+    return-x?:boolean <- lesser-than x, y
+    break-if return-x?
+    reply y
+  }
+  reply x
+]
+
+recipe max [
+  local-scope
+  x:number <- next-ingredient
+  y:number <- next-ingredient
+  {
+    return-x?:boolean <- greater-than x, y
+    break-if return-x?
+    reply y
+  }
+  reply x
+]
+
+ + + diff --git a/html/071channel.mu.html b/html/071channel.mu.html new file mode 100644 index 00000000..1bddb78c --- /dev/null +++ b/html/071channel.mu.html @@ -0,0 +1,412 @@ + + + + +Mu - 071channel.mu + + + + + + + + + + +
+# Mu synchronizes using channels rather than locks, like Erlang and Go.
+#
+# The two ends of a channel will usually belong to different routines, but
+# each end should only be used by a single one. Don't try to read from or
+# write to it from multiple routines at once.
+#
+# The key property of channels is that writing to a full channel or reading
+# from an empty one will put the current routine in 'waiting' state until the
+# operation can be completed.
+
+scenario channel [
+  run [
+    1:address:channel <- new-channel 3/capacity
+    1:address:channel <- write 1:address:channel, 34
+    2:character, 1:address:channel <- read 1:address:channel
+  ]
+  memory-should-contain [
+    2 <- 34
+  ]
+]
+
+container channel [
+  # To avoid locking, writer and reader will never write to the same location.
+  # So channels will include fields in pairs, one for the writer and one for the
+  # reader.
+  first-full:number  # for write
+  first-free:number  # for read
+  # A circular buffer contains values from index first-full up to (but not
+  # including) index first-empty. The reader always modifies it at first-full,
+  # while the writer always modifies it at first-empty.
+  data:address:array:character
+]
+
+# result:address:channel <- new-channel capacity:number
+recipe new-channel [
+  local-scope
+  # result = new channel
+  result:address:channel <- new channel:type
+  # result.first-full = 0
+  full:address:number <- get-address *result, first-full:offset
+  *full <- copy 0
+  # result.first-free = 0
+  free:address:number <- get-address *result, first-free:offset
+  *free <- copy 0
+  # result.data = new location[ingredient+1]
+  capacity:number <- next-ingredient
+  capacity <- add capacity, 1  # unused slot for 'full?' below
+  dest:address:address:array:character <- get-address *result, data:offset
+  *dest <- new character:type, capacity
+  reply result
+]
+
+# chan <- write chan:address:channel, val:character
+recipe write [
+  local-scope
+  chan:address:channel <- next-ingredient
+  val:character <- next-ingredient
+  {
+    # block if chan is full
+    full:boolean <- channel-full? chan
+    break-unless full
+    full-address:address:number <- get-address *chan, first-full:offset
+    wait-for-location *full-address
+  }
+  # store val
+  circular-buffer:address:array:character <- get *chan, data:offset
+  free:address:number <- get-address *chan, first-free:offset
+  dest:address:character <- index-address *circular-buffer, *free
+  *dest <- copy val
+  # mark its slot as filled
+  *free <- add *free, 1
+  {
+    # wrap free around to 0 if necessary
+    len:number <- length *circular-buffer
+    at-end?:boolean <- greater-or-equal *free, len
+    break-unless at-end?
+    *free <- copy 0
+  }
+  reply chan/same-as-ingredient:0
+]
+
+# result:character, chan <- read chan:address:channel
+recipe read [
+  local-scope
+  chan:address:channel <- next-ingredient
+  {
+    # block if chan is empty
+    empty?:boolean <- channel-empty? chan
+    break-unless empty?
+    free-address:address:number <- get-address *chan, first-free:offset
+    wait-for-location *free-address
+  }
+  # read result
+  full:address:number <- get-address *chan, first-full:offset
+  circular-buffer:address:array:character <- get *chan, data:offset
+  result:character <- index *circular-buffer, *full
+  # mark its slot as empty
+  *full <- add *full, 1
+  {
+    # wrap full around to 0 if necessary
+    len:number <- length *circular-buffer
+    at-end?:boolean <- greater-or-equal *full, len
+    break-unless at-end?
+    *full <- copy 0
+  }
+  reply result, chan/same-as-ingredient:0
+]
+
+recipe clear-channel [
+  local-scope
+  chan:address:channel <- next-ingredient
+  {
+    empty?:boolean <- channel-empty? chan
+    break-if empty?
+    _, chan <- read chan
+  }
+  reply chan/same-as-ingredient:0
+]
+
+scenario channel-initialization [
+  run [
+    1:address:channel <- new-channel 3/capacity
+    2:number <- get *1:address:channel, first-full:offset
+    3:number <- get *1:address:channel, first-free:offset
+  ]
+  memory-should-contain [
+    2 <- 0  # first-full
+    3 <- 0  # first-free
+  ]
+]
+
+scenario channel-write-increments-free [
+  run [
+    1:address:channel <- new-channel 3/capacity
+    1:address:channel <- write 1:address:channel, 34
+    2:number <- get *1:address:channel, first-full:offset
+    3:number <- get *1:address:channel, first-free:offset
+  ]
+  memory-should-contain [
+    2 <- 0  # first-full
+    3 <- 1  # first-free
+  ]
+]
+
+scenario channel-read-increments-full [
+  run [
+    1:address:channel <- new-channel 3/capacity
+    1:address:channel <- write 1:address:channel, 34
+    _, 1:address:channel <- read 1:address:channel
+    2:number <- get *1:address:channel, first-full:offset
+    3:number <- get *1:address:channel, first-free:offset
+  ]
+  memory-should-contain [
+    2 <- 1  # first-full
+    3 <- 1  # first-free
+  ]
+]
+
+scenario channel-wrap [
+  run [
+    # channel with just 1 slot
+    1:address:channel <- new-channel 1/capacity
+    # write and read a value
+    1:address:channel <- write 1:address:channel, 34
+    _, 1:address:channel <- read 1:address:channel
+    # first-free will now be 1
+    2:number <- get *1:address:channel, first-free:offset
+    3:number <- get *1:address:channel, first-free:offset
+    # write second value, verify that first-free wraps
+    1:address:channel <- write 1:address:channel, 34
+    4:number <- get *1:address:channel, first-free:offset
+    # read second value, verify that first-full wraps
+    _, 1:address:channel <- read 1:address:channel
+    5:number <- get *1:address:channel, first-full:offset
+  ]
+  memory-should-contain [
+    2 <- 1  # first-free after first write
+    3 <- 1  # first-full after first read
+    4 <- 0  # first-free after second write, wrapped
+    5 <- 0  # first-full after second read, wrapped
+  ]
+]
+
+## helpers
+
+# An empty channel has first-empty and first-full both at the same value.
+recipe channel-empty? [
+  local-scope
+  chan:address:channel <- next-ingredient
+  # return chan.first-full == chan.first-free
+  full:number <- get *chan, first-full:offset
+  free:number <- get *chan, first-free:offset
+  result:boolean <- equal full, free
+  reply result
+]
+
+# A full channel has first-empty just before first-full, wasting one slot.
+# (Other alternatives: https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction)
+recipe channel-full? [
+  local-scope
+  chan:address:channel <- next-ingredient
+  # tmp = chan.first-free + 1
+  tmp:number <- get *chan, first-free:offset
+  tmp <- add tmp, 1
+  {
+    # if tmp == chan.capacity, tmp = 0
+    len:number <- channel-capacity chan
+    at-end?:boolean <- greater-or-equal tmp, len
+    break-unless at-end?
+    tmp <- copy 0
+  }
+  # return chan.first-full == tmp
+  full:number <- get *chan, first-full:offset
+  result:boolean <- equal full, tmp
+  reply result
+]
+
+# result:number <- channel-capacity chan:address:channel
+recipe channel-capacity [
+  local-scope
+  chan:address:channel <- next-ingredient
+  q:address:array:character <- get *chan, data:offset
+  result:number <- length *q
+  reply result
+]
+
+scenario channel-new-empty-not-full [
+  run [
+    1:address:channel <- new-channel 3/capacity
+    2:boolean <- channel-empty? 1:address:channel
+    3:boolean <- channel-full? 1:address:channel
+  ]
+  memory-should-contain [
+    2 <- 1  # empty?
+    3 <- 0  # full?
+  ]
+]
+
+scenario channel-write-not-empty [
+  run [
+    1:address:channel <- new-channel 3/capacity
+    1:address:channel <- write 1:address:channel, 34
+    2:boolean <- channel-empty? 1:address:channel
+    3:boolean <- channel-full? 1:address:channel
+  ]
+  memory-should-contain [
+    2 <- 0  # empty?
+    3 <- 0  # full?
+  ]
+]
+
+scenario channel-write-full [
+  run [
+    1:address:channel <- new-channel 1/capacity
+    1:address:channel <- write 1:address:channel, 34
+    2:boolean <- channel-empty? 1:address:channel
+    3:boolean <- channel-full? 1:address:channel
+  ]
+  memory-should-contain [
+    2 <- 0  # empty?
+    3 <- 1  # full?
+  ]
+]
+
+scenario channel-read-not-full [
+  run [
+    1:address:channel <- new-channel 1/capacity
+    1:address:channel <- write 1:address:channel, 34
+    _, 1:address:channel <- read 1:address:channel
+    2:boolean <- channel-empty? 1:address:channel
+    3:boolean <- channel-full? 1:address:channel
+  ]
+  memory-should-contain [
+    2 <- 1  # empty?
+    3 <- 0  # full?
+  ]
+]
+
+# helper for channels of characters in particular
+# out <- buffer-lines in:address:channel, out:address:channel
+recipe buffer-lines [
+  local-scope
+  in:address:channel <- next-ingredient
+  out:address:channel <- next-ingredient
+  # repeat forever
+  {
+    line:address:buffer <- new-buffer, 30
+    # read characters from 'in' until newline, copy into line
+    {
+      +next-character
+      c:character, in <- read in
+      # drop a character on backspace
+      {
+        # special-case: if it's a backspace
+        backspace?:boolean <- equal c, 8
+        break-unless backspace?
+        # drop previous character
+        {
+          buffer-length:address:number <- get-address *line, length:offset
+          buffer-empty?:boolean <- equal *buffer-length, 0
+          break-if buffer-empty?
+          *buffer-length <- subtract *buffer-length, 1
+        }
+        # and don't append this one
+        loop +next-character:label
+      }
+      # append anything else
+      line <- buffer-append line, c
+      line-done?:boolean <- equal c, 10/newline
+      break-if line-done?
+      # stop buffering on eof (currently only generated by fake console)
+      eof?:boolean <- equal c, 0/eof
+      break-if eof?
+      loop
+    }
+    # copy line into 'out'
+    i:number <- copy 0
+    line-contents:address:array:character <- get *line, data:offset
+    max:number <- get *line, length:offset
+    {
+      done?:boolean <- greater-or-equal i, max
+      break-if done?
+      c:character <- index *line-contents, i
+      out <- write out, c
+      i <- add i, 1
+      loop
+    }
+    loop
+  }
+  reply out/same-as-ingredient:1
+]
+
+scenario buffer-lines-blocks-until-newline [
+  run [
+    1:address:channel/stdin <- new-channel 10/capacity
+    2:address:channel/buffered-stdin <- new-channel 10/capacity
+    3:boolean <- channel-empty? 2:address:channel/buffered-stdin
+    assert 3:boolean, [
+F buffer-lines-blocks-until-newline: channel should be empty after init]
+    # buffer stdin into buffered-stdin, try to read from buffered-stdin
+    4:number/buffer-routine <- start-running buffer-lines:recipe, 1:address:channel/stdin, 2:address:channel/buffered-stdin
+    wait-for-routine 4:number/buffer-routine
+    5:boolean <- channel-empty? 2:address:channel/buffered-stdin
+    assert 5:boolean, [
+F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up]
+    # write 'a'
+    1:address:channel <- write 1:address:channel, 97/a
+    restart 4:number/buffer-routine
+    wait-for-routine 4:number/buffer-routine
+    6:boolean <- channel-empty? 2:address:channel/buffered-stdin
+    assert 6:boolean, [
+F buffer-lines-blocks-until-newline: channel should be empty after writing 'a']
+    # write 'b'
+    1:address:channel <- write 1:address:channel, 98/b
+    restart 4:number/buffer-routine
+    wait-for-routine 4:number/buffer-routine
+    7:boolean <- channel-empty? 2:address:channel/buffered-stdin
+    assert 7:boolean, [
+F buffer-lines-blocks-until-newline: channel should be empty after writing 'b']
+    # write newline
+    1:address:channel <- write 1:address:channel, 10/newline
+    restart 4:number/buffer-routine
+    wait-for-routine 4:number/buffer-routine
+    8:boolean <- channel-empty? 2:address:channel/buffered-stdin
+    9:boolean/completed? <- not 8:boolean
+    assert 9:boolean/completed?, [
+F buffer-lines-blocks-until-newline: channel should contain data after writing newline]
+    trace 1, [test], [reached end]
+  ]
+  trace-should-contain [
+    test: reached end
+  ]
+]
+
+ + + diff --git a/html/071print.mu.html b/html/071print.mu.html deleted file mode 100644 index 444a8359..00000000 --- a/html/071print.mu.html +++ /dev/null @@ -1,722 +0,0 @@ - - - - -Mu - 071print.mu - - - - - - - - - - -
-# Wrappers around print primitives that take a 'screen' object and are thus
-# easier to test.
-
-container screen [
-  num-rows:number
-  num-columns:number
-  cursor-row:number
-  cursor-column:number
-  data:address:array:screen-cell
-]
-
-container screen-cell [
-  contents:character
-  color:number
-]
-
-recipe new-fake-screen [
-  local-scope
-  result:address:screen <- new screen:type
-  width:address:number <- get-address *result, num-columns:offset
-  *width <- next-ingredient
-  height:address:number <- get-address *result, num-rows:offset
-  *height <- next-ingredient
-  row:address:number <- get-address *result, cursor-row:offset
-  *row <- copy 0
-  column:address:number <- get-address *result, cursor-column:offset
-  *column <- copy 0
-  bufsize:number <- multiply *width, *height
-  buf:address:address:array:screen-cell <- get-address *result, data:offset
-  *buf <- new screen-cell:type, bufsize
-  clear-screen result
-  reply result
-]
-
-recipe clear-screen [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists
-  {
-    break-unless sc
-    # clear fake screen
-    buf:address:array:screen-cell <- get *sc, data:offset
-    max:number <- length *buf
-    i:number <- copy 0
-    {
-      done?:boolean <- greater-or-equal i, max
-      break-if done?
-      curr:address:screen-cell <- index-address *buf, i
-      curr-content:address:character <- get-address *curr, contents:offset
-      *curr-content <- copy 0/empty
-      curr-color:address:number <- get-address *curr, color:offset
-      *curr-color <- copy 7/white
-      i <- add i, 1
-      loop
-    }
-    # reset cursor
-    x:address:number <- get-address *sc, cursor-row:offset
-    *x <- copy 0
-    x <- get-address *sc, cursor-column:offset
-    *x <- copy 0
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  clear-display
-  reply sc/same-as-ingredient:0
-]
-
-recipe sync-screen [
-  local-scope
-  sc:address:screen <- next-ingredient
-  {
-    break-if sc
-    sync-display
-  }
-  # do nothing for fake screens
-]
-
-recipe fake-screen-is-empty? [
-  local-scope
-  sc:address:screen <- next-ingredient
-  reply-unless sc, 1/true
-  buf:address:array:screen-cell <- get *sc, data:offset
-  i:number <- copy 0
-  len:number <- length *buf
-  {
-    done?:boolean <- greater-or-equal i, len
-    break-if done?
-    curr:screen-cell <- index *buf, i
-    curr-contents:character <- get curr, contents:offset
-    i <- add i, 1
-    loop-unless curr-contents
-    # not 0
-    reply 0/false
-  }
-  reply 1/true
-]
-
-recipe print-character [
-  local-scope
-  sc:address:screen <- next-ingredient
-  c:character <- next-ingredient
-  color:number, color-found?:boolean <- next-ingredient
-  {
-    # default color to white
-    break-if color-found?
-    color <- copy 7/white
-  }
-  bg-color:number, bg-color-found?:boolean <- next-ingredient
-  {
-    # default bg-color to black
-    break-if bg-color-found?
-    bg-color <- copy 0/black
-  }
-  trace 90, [print-character], c
-  {
-    # if x exists
-    # (handle special cases exactly like in the real screen)
-    break-unless sc
-    width:number <- get *sc, num-columns:offset
-    height:number <- get *sc, num-rows:offset
-    # if cursor is out of bounds, silently exit
-    row:address:number <- get-address *sc, cursor-row:offset
-    legal?:boolean <- greater-or-equal *row, 0
-    reply-unless legal?, sc
-    legal? <- lesser-than *row, height
-    reply-unless legal?, sc
-    column:address:number <- get-address *sc, cursor-column:offset
-    legal? <- greater-or-equal *column, 0
-    reply-unless legal?, sc
-    legal? <- lesser-than *column, width
-    reply-unless legal?, sc
-    # special-case: newline
-    {
-      newline?:boolean <- equal c, 10/newline
-      break-unless newline?
-      {
-        # unless cursor is already at bottom
-        bottom:number <- subtract height, 1
-        at-bottom?:boolean <- greater-or-equal *row, bottom
-        break-if at-bottom?
-        # move it to the next row
-        *column <- copy 0
-        *row <- add *row, 1
-      }
-      reply sc/same-as-ingredient:0
-    }
-    # save character in fake screen
-    index:number <- multiply *row, width
-    index <- add index, *column
-    buf:address:array:screen-cell <- get *sc, data:offset
-    len:number <- length *buf
-    # special-case: backspace
-    {
-      backspace?:boolean <- equal c, 8
-      break-unless backspace?
-      {
-        # unless cursor is already at left margin
-        at-left?:boolean <- lesser-or-equal *column, 0
-        break-if at-left?
-        # clear previous location
-        *column <- subtract *column, 1
-        index <- subtract index, 1
-        cursor:address:screen-cell <- index-address *buf, index
-        cursor-contents:address:character <- get-address *cursor, contents:offset
-        *cursor-contents <- copy 32/space
-        cursor-color:address:number <- get-address *cursor, color:offset
-        *cursor-color <- copy 7/white
-      }
-      reply sc/same-as-ingredient:0
-    }
-    cursor:address:screen-cell <- index-address *buf, index
-    cursor-contents:address:character <- get-address *cursor, contents:offset
-    *cursor-contents <- copy c
-    cursor-color:address:number <- get-address *cursor, color:offset
-    *cursor-color <- copy color
-    # increment column unless it's already all the way to the right
-    {
-      right:number <- subtract width, 1
-      at-right?:boolean <- greater-or-equal *column, right
-      break-if at-right?
-      *column <- add *column, 1
-    }
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  print-character-to-display c, color, bg-color
-  reply sc/same-as-ingredient:0
-]
-
-scenario print-character-at-top-left [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    2:address:array:screen-cell <- get *1:address:screen, data:offset
-    3:array:screen-cell <- copy *2:address:array:screen-cell
-  ]
-  memory-should-contain [
-    3 <- 6  # width*height
-    4 <- 97  # 'a'
-    5 <- 7  # white
-    6 <- 0
-  ]
-]
-
-scenario print-character-color [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 97/a, 1/red
-    2:address:array:screen-cell <- get *1:address:screen, data:offset
-    3:array:screen-cell <- copy *2:address:array:screen-cell
-  ]
-  memory-should-contain [
-    3 <- 6  # width*height
-    4 <- 97  # 'a'
-    5 <- 1  # red
-    6 <- 0
-  ]
-]
-
-scenario print-backspace-character [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    1:address:screen <- print-character 1:address:screen, 8  # backspace
-    2:number <- get *1:address:screen, cursor-column:offset
-    3:address:array:screen-cell <- get *1:address:screen, data:offset
-    4:array:screen-cell <- copy *3:address:array:screen-cell
-  ]
-  memory-should-contain [
-    2 <- 0  # cursor column
-    4 <- 6  # width*height
-    5 <- 32  # space, not 'a'
-    6 <- 7  # white
-    7 <- 0
-  ]
-]
-
-scenario print-extra-backspace-character [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    1:address:screen <- print-character 1:address:screen, 8  # backspace
-    1:address:screen <- print-character 1:address:screen, 8  # backspace
-    2:number <- get *1:address:screen, cursor-column:offset
-    3:address:array:screen-cell <- get *1:address:screen, data:offset
-    4:array:screen-cell <- copy *3:address:array:screen-cell
-  ]
-  memory-should-contain [
-    2 <- 0  # cursor column
-    4 <- 6  # width*height
-    5 <- 32  # space, not 'a'
-    6 <- 7  # white
-    7 <- 0
-  ]
-]
-
-scenario print-at-right-margin [
-  run [
-    1:address:screen <- new-fake-screen 2/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    1:address:screen <- print-character 1:address:screen, 98  # 'b'
-    1:address:screen <- print-character 1:address:screen, 99  # 'c'
-    2:number <- get *1:address:screen, cursor-column:offset
-    3:address:array:screen-cell <- get *1:address:screen, data:offset
-    4:array:screen-cell <- copy *3:address:array:screen-cell
-  ]
-  memory-should-contain [
-    2 <- 1  # cursor column
-    4 <- 4  # width*height
-    5 <- 97  # 'a'
-    6 <- 7  # white
-    7 <- 99  # 'c' over 'b'
-    8 <- 7  # white
-    9 <- 0
-  ]
-]
-
-scenario print-newline-character [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    1:address:screen <- print-character 1:address:screen, 10/newline
-    2:number <- get *1:address:screen, cursor-row:offset
-    3:number <- get *1:address:screen, cursor-column:offset
-    4:address:array:screen-cell <- get *1:address:screen, data:offset
-    5:array:screen-cell <- copy *4:address:array:screen-cell
-  ]
-  memory-should-contain [
-    2 <- 1  # cursor row
-    3 <- 0  # cursor column
-    5 <- 6  # width*height
-    6 <- 97  # 'a'
-    7 <- 7  # white
-    8 <- 0
-  ]
-]
-
-scenario print-newline-at-bottom-line [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 10/newline
-    1:address:screen <- print-character 1:address:screen, 10/newline
-    1:address:screen <- print-character 1:address:screen, 10/newline
-    2:number <- get *1:address:screen, cursor-row:offset
-    3:number <- get *1:address:screen, cursor-column:offset
-  ]
-  memory-should-contain [
-    2 <- 1  # cursor row
-    3 <- 0  # cursor column
-  ]
-]
-
-scenario print-at-bottom-right [
-  run [
-    1:address:screen <- new-fake-screen 2/width, 2/height
-    1:address:screen <- print-character 1:address:screen, 10/newline
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    1:address:screen <- print-character 1:address:screen, 98  # 'b'
-    1:address:screen <- print-character 1:address:screen, 99  # 'c'
-    1:address:screen <- print-character 1:address:screen, 10/newline
-    1:address:screen <- print-character 1:address:screen, 100  # 'd'
-    2:number <- get *1:address:screen, cursor-row:offset
-    3:number <- get *1:address:screen, cursor-column:offset
-    4:address:array:screen-cell <- get *1:address:screen, data:offset
-    5:array:screen-cell <- copy *4:address:array:screen-cell
-  ]
-  memory-should-contain [
-    2 <- 1  # cursor row
-    3 <- 1  # cursor column
-    5 <- 4  # width*height
-    6 <- 0  # unused
-    7 <- 7  # white
-    8 <- 0  # unused
-    9 <- 7  # white
-    10 <- 97 # 'a'
-    11 <- 7  # white
-    12 <- 100  # 'd' over 'b' and 'c' and newline
-    13 <- 7  # white
-    14 <- 0
-  ]
-]
-
-recipe clear-line [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, clear line in fake screen
-  {
-    break-unless sc
-    width:number <- get *sc, num-columns:offset
-    column:address:number <- get-address *sc, cursor-column:offset
-    original-column:number <- copy *column
-    # space over the entire line
-    {
-      right:number <- subtract width, 1
-      done?:boolean <- greater-or-equal *column, right
-      break-if done?
-      print-character sc, [ ]  # implicitly updates 'column'
-      loop
-    }
-    # now back to where the cursor was
-    *column <- copy original-column
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  clear-line-on-display
-  reply sc/same-as-ingredient:0
-]
-
-recipe cursor-position [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, lookup cursor in fake screen
-  {
-    break-unless sc
-    row:number <- get *sc, cursor-row:offset
-    column:number <- get *sc, cursor-column:offset
-    reply row, column, sc/same-as-ingredient:0
-  }
-  row, column <- cursor-position-on-display
-  reply row, column, sc/same-as-ingredient:0
-]
-
-recipe move-cursor [
-  local-scope
-  sc:address:screen <- next-ingredient
-  new-row:number <- next-ingredient
-  new-column:number <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    row:address:number <- get-address *sc, cursor-row:offset
-    *row <- copy new-row
-    column:address:number <- get-address *sc, cursor-column:offset
-    *column <- copy new-column
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  move-cursor-on-display new-row, new-column
-  reply sc/same-as-ingredient:0
-]
-
-scenario clear-line-erases-printed-characters [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    # print a character
-    1:address:screen <- print-character 1:address:screen, 97  # 'a'
-    # move cursor to start of line
-    1:address:screen <- move-cursor 1:address:screen, 0/row, 0/column
-    # clear line
-    1:address:screen <- clear-line 1:address:screen
-    2:address:array:screen-cell <- get *1:address:screen, data:offset
-    3:array:screen-cell <- copy *2:address:array:screen-cell
-  ]
-  # screen should be blank
-  memory-should-contain [
-    3 <- 6  # width*height
-    4 <- 0
-    5 <- 7
-    6 <- 0
-    7 <- 7
-    8 <- 0
-    9 <- 7
-    10 <- 0
-    11 <- 7
-    12 <- 0
-    13 <- 7
-    14 <- 0
-    15 <- 7
-  ]
-]
-
-recipe cursor-down [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    {
-      # increment row unless it's already all the way down
-      height:number <- get *sc, num-rows:offset
-      row:address:number <- get-address *sc, cursor-row:offset
-      max:number <- subtract height, 1
-      at-bottom?:boolean <- greater-or-equal *row, max
-      break-if at-bottom?
-      *row <- add *row, 1
-    }
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  move-cursor-down-on-display
-  reply sc/same-as-ingredient:0
-]
-
-recipe cursor-up [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    {
-      # decrement row unless it's already all the way up
-      row:address:number <- get-address *sc, cursor-row:offset
-      at-top?:boolean <- lesser-or-equal *row, 0
-      break-if at-top?
-      *row <- subtract *row, 1
-    }
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  move-cursor-up-on-display
-  reply sc/same-as-ingredient:0
-]
-
-recipe cursor-right [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    {
-      # increment column unless it's already all the way to the right
-      width:number <- get *sc, num-columns:offset
-      column:address:number <- get-address *sc, cursor-column:offset
-      max:number <- subtract width, 1
-      at-bottom?:boolean <- greater-or-equal *column, max
-      break-if at-bottom?
-      *column <- add *column, 1
-    }
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  move-cursor-right-on-display
-  reply sc/same-as-ingredient:0
-]
-
-recipe cursor-left [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    {
-      # decrement column unless it's already all the way to the left
-      column:address:number <- get-address *sc, cursor-column:offset
-      at-top?:boolean <- lesser-or-equal *column, 0
-      break-if at-top?
-      *column <- subtract *column, 1
-    }
-    reply sc/same-as-ingredient:0
-  }
-  # otherwise, real screen
-  move-cursor-left-on-display
-  reply sc/same-as-ingredient:0
-]
-
-recipe cursor-to-start-of-line [
-  local-scope
-  sc:address:screen <- next-ingredient
-  row:number, _, sc <- cursor-position sc
-  column:number <- copy 0
-  sc <- move-cursor sc, row, column
-  reply sc/same-as-ingredient:0
-]
-
-recipe cursor-to-next-line [
-  local-scope
-  screen:address:screen <- next-ingredient
-  screen <- cursor-down screen
-  screen <- cursor-to-start-of-line screen
-  reply screen/same-as-ingredient:0
-]
-
-recipe screen-width [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    width:number <- get *sc, num-columns:offset
-    reply width
-  }
-  # otherwise, real screen
-  width:number <- display-width
-  reply width
-]
-
-recipe screen-height [
-  local-scope
-  sc:address:screen <- next-ingredient
-  # if x exists, move cursor in fake screen
-  {
-    break-unless sc
-    height:number <- get *sc, num-rows:offset
-    reply height
-  }
-  # otherwise, real screen
-  height:number <- display-height
-  reply height
-]
-
-recipe hide-cursor [
-  local-scope
-  screen:address:screen <- next-ingredient
-  # if x exists (not real display), do nothing
-  {
-    break-unless screen
-    reply screen
-  }
-  # otherwise, real screen
-  hide-cursor-on-display
-  reply screen
-]
-
-recipe show-cursor [
-  local-scope
-  screen:address:screen <- next-ingredient
-  # if x exists (not real display), do nothing
-  {
-    break-unless screen
-    reply screen
-  }
-  # otherwise, real screen
-  show-cursor-on-display
-  reply screen
-]
-
-recipe hide-screen [
-  local-scope
-  screen:address:screen <- next-ingredient
-  # if x exists (not real display), do nothing
-  # todo: help test this
-  {
-    break-unless screen
-    reply screen
-  }
-  # otherwise, real screen
-  hide-display
-  reply screen
-]
-
-recipe show-screen [
-  local-scope
-  screen:address:screen <- next-ingredient
-  # if x exists (not real display), do nothing
-  # todo: help test this
-  {
-    break-unless screen
-    reply screen
-  }
-  # otherwise, real screen
-  show-display
-  reply screen
-]
-
-recipe print-string [
-  local-scope
-  screen:address:screen <- next-ingredient
-  s:address:array:character <- next-ingredient
-  color:number, color-found?:boolean <- next-ingredient
-  {
-    # default color to white
-    break-if color-found?
-    color <- copy 7/white
-  }
-  bg-color:number, bg-color-found?:boolean <- next-ingredient
-  {
-    # default bg-color to black
-    break-if bg-color-found?
-    bg-color <- copy 0/black
-  }
-  len:number <- length *s
-  i:number <- copy 0
-  {
-    done?:boolean <- greater-or-equal i, len
-    break-if done?
-    c:character <- index *s, i
-    print-character screen, c, color, bg-color
-    i <- add i, 1
-    loop
-  }
-  reply screen/same-as-ingredient:0
-]
-
-scenario print-string-stops-at-right-margin [
-  run [
-    1:address:screen <- new-fake-screen 3/width, 2/height
-    2:address:array:character <- new [abcd]
-    1:address:screen <- print-string 1:address:screen, 2:address:array:character
-    3:address:array:screen-cell <- get *1:address:screen, data:offset
-    4:array:screen-cell <- copy *3:address:array:screen-cell
-  ]
-  memory-should-contain [
-    4 <- 6  # width*height
-    5 <- 97  # 'a'
-    6 <- 7  # white
-    7 <- 98  # 'b'
-    8 <- 7  # white
-    9 <- 100  # 'd' overwrites 'c'
-    10 <- 7  # white
-    11 <- 0  # unused
-  ]
-]
-
-recipe print-integer [
-  local-scope
-  screen:address:screen <- next-ingredient
-  n:number <- next-ingredient
-  color:number, color-found?:boolean <- next-ingredient
-  {
-    # default color to white
-    break-if color-found?
-    color <- copy 7/white
-  }
-  bg-color:number, bg-color-found?:boolean <- next-ingredient
-  {
-    # default bg-color to black
-    break-if bg-color-found?
-    bg-color <- copy 0/black
-  }
-  # todo: other bases besides decimal
-  s:address:array:character <- integer-to-decimal-string n
-  print-string screen, s, color, bg-color
-  reply screen/same-as-ingredient:0
-]
-
- - - diff --git a/html/072array.mu.html b/html/072array.mu.html new file mode 100644 index 00000000..e4ab32a8 --- /dev/null +++ b/html/072array.mu.html @@ -0,0 +1,77 @@ + + + + +Mu - 072array.mu + + + + + + + + + + +
+scenario array-from-args [
+  run [
+    1:address:array:character <- new-array 0, 1, 2
+    2:array:character <- copy *1:address:array:character
+  ]
+  memory-should-contain [
+    2 <- 3  # array length
+    3 <- 0
+    4 <- 1
+    5 <- 2
+  ]
+]
+
+# create an array out of a list of scalar args
+recipe new-array [
+  local-scope
+  capacity:number <- copy 0
+  {
+    # while read curr-value
+    curr-value:character, exists?:boolean <- next-ingredient
+    break-unless exists?
+    capacity <- add capacity, 1
+    loop
+  }
+  result:address:array:character <- new character:type, capacity
+  rewind-ingredients
+  i:number <- copy 0
+  {
+    # while read curr-value
+    done?:boolean <- greater-or-equal i, capacity
+    break-if done?
+    curr-value:character, exists?:boolean <- next-ingredient
+    assert exists?, [error in rewinding ingredients to new-array]
+    tmp:address:character <- index-address *result, i
+    *tmp <- copy curr-value
+    i <- add i, 1
+    loop
+  }
+  reply result
+]
+
+ + + diff --git a/html/072scenario_screen.cc.html b/html/072scenario_screen.cc.html deleted file mode 100644 index ade20fff..00000000 --- a/html/072scenario_screen.cc.html +++ /dev/null @@ -1,394 +0,0 @@ - - - - -Mu - 072scenario_screen.cc - - - - - - - - - - -
-//: Clean syntax to manipulate and check the screen in scenarios.
-//: Instructions 'assume-screen' and 'screen-should-contain' implicitly create
-//: a variable called 'screen' that is accessible inside other 'run'
-//: instructions in the scenario. 'screen-should-contain' can check unicode
-//: characters in the fake screen
-
-:(scenarios run_mu_scenario)
-:(scenario screen_in_scenario)
-scenario screen-in-scenario [
-  assume-screen 5/width, 3/height
-  run [
-    screen:address:screen <- print-character screen:address:screen, 97  # 'a'
-  ]
-  screen-should-contain [
-  #  01234
-    .a    .
-    .     .
-    .     .
-  ]
-]
-
-:(scenario screen_in_scenario_unicode)
-scenario screen-in-scenario-unicode-color [
-  assume-screen 5/width, 3/height
-  run [
-    screen:address:screen <- print-character screen:address:screen, 955/greek-small-lambda, 1/red
-    screen:address:screen <- print-character screen:address:screen, 97/a
-  ]
-  screen-should-contain [
-  #  01234
-    .λa   .
-    .     .
-    .     .
-  ]
-]
-
-:(scenario screen_in_scenario_color)
-# screen-should-contain can check unicode characters in the fake screen
-scenario screen-in-scenario-color [
-  assume-screen 5/width, 3/height
-  run [
-    screen:address:screen <- print-character screen:address:screen, 955/greek-small-lambda, 1/red
-    screen:address:screen <- print-character screen:address:screen, 97/a, 7/white
-  ]
-  # screen-should-contain shows everything
-  screen-should-contain [
-  #  01234
-    .λa   .
-    .     .
-    .     .
-  ]
-  # screen-should-contain-in-color filters out everything except the given
-  # color, all you see is the 'a' in white.
-  screen-should-contain-in-color 7/white, [
-  #  01234
-    . a   .
-    .     .
-    .     .
-  ]
-  # ..and the λ in red.
-  screen-should-contain-in-color 1/red, [
-  #  01234
-    .λ    .
-    .     .
-    .     .
-  ]
-]
-
-:(scenario screen_in_scenario_error)
-% Scenario_testing_scenario = true;
-% Hide_errors = true;
-scenario screen-in-scenario-error [
-  assume-screen 5/width, 3/height
-  run [
-    screen:address:screen <- print-character screen:address:screen, 97  # 'a'
-  ]
-  screen-should-contain [
-  #  01234
-    .b    .
-    .     .
-    .     .
-  ]
-]
-+error: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')
-
-:(scenario screen_in_scenario_color_error)
-% Scenario_testing_scenario = true;
-% Hide_errors = true;
-# screen-should-contain can check unicode characters in the fake screen
-scenario screen-in-scenario-color [
-  assume-screen 5/width, 3/height
-  run [
-    screen:address:screen <- print-character screen:address:screen, 97/a, 1/red
-  ]
-  screen-should-contain-in-color 2/green, [
-  #  01234
-    .a    .
-    .     .
-    .     .
-  ]
-]
-+error: expected screen location (0, 0) to be in color 2 instead of 1
-
-//: allow naming just for 'screen'
-:(before "End is_special_name Cases")
-if (s == "screen") return true;
-
-:(scenarios run)
-:(scenario convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations)
-% Scenario_testing_scenario = true;
-% Hide_errors = true;
-recipe main [
-  screen:number <- copy 1:number
-]
--error: mixing variable names and numeric addresses in main
-$error: 0
-:(scenarios run_mu_scenario)
-
-:(before "End Globals")
-// Scenarios may not define default-space, so they should fit within the
-// initial area of memory reserved for tests. We'll put the predefined
-// variables available to them at the end of that region.
-const long long int Max_variables_in_scenarios = Reserved_for_tests-100;
-long long int Next_predefined_global_for_scenarios = Max_variables_in_scenarios;
-:(before "End Setup")
-assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
-:(after "transform_all()" following "case RUN:")
-// There's a restriction on the number of variables 'run' can use, so that
-// it can avoid colliding with the dynamic allocator in case it doesn't
-// initialize a default-space.
-assert(Name[tmp_recipe.at(0)][""] < Max_variables_in_scenarios);
-
-:(before "End Globals")
-// Scenario Globals.
-const long long int SCREEN = Next_predefined_global_for_scenarios++;
-// End Scenario Globals.
-:(before "End Special Scenario Variable Names(r)")
-Name[r]["screen"] = SCREEN;
-
-:(before "End Rewrite Instruction(curr, recipe result)")
-// rewrite `assume-screen width, height` to
-// `screen:address:screen <- new-fake-screen width, height`
-if (curr.name == "assume-screen") {
-  curr.name = "new-fake-screen";
-  assert(curr.products.empty());
-  curr.products.push_back(reagent("screen:address:screen"));
-  curr.products.at(0).set_value(SCREEN);
-}
-
-//: screen-should-contain is a regular instruction
-:(before "End Primitive Recipe Declarations")
-SCREEN_SHOULD_CONTAIN,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN);
-:(before "End Primitive Recipe Checks")
-case SCREEN_SHOULD_CONTAIN: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SCREEN_SHOULD_CONTAIN: {
-  if (!Passed) break;
-  check_screen(current_instruction().ingredients.at(0).name, -1);
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SCREEN_SHOULD_CONTAIN_IN_COLOR,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR);
-:(before "End Primitive Recipe Checks")
-case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
-  if (!Passed) break;
-  assert(scalar(ingredients.at(0)));
-  check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0));
-  break;
-}
-
-:(before "End Types")
-// scan an array of characters in a unicode-aware, bounds-checked manner
-struct raw_string_stream {
-  long long int index;
-  const long long int max;
-  const char* buf;
-
-  raw_string_stream(const string&);
-  uint32_t get();  // unicode codepoint
-  uint32_t peek();  // unicode codepoint
-  bool at_end() const;
-  void skip_whitespace_and_comments();
-};
-
-:(code)
-void check_screen(const string& expected_contents, const int color) {
-  assert(!current_call().default_space);  // not supported
-  long long int screen_location = get_or_insert(Memory, SCREEN);
-  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
-  assert(data_offset >= 0);
-  long long int screen_data_location = screen_location+data_offset;  // type: address:array:character
-  long long int screen_data_start = get_or_insert(Memory, screen_data_location);  // type: array:character
-  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
-  long long int screen_width = get_or_insert(Memory, screen_location+width_offset);
-  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
-  long long int screen_height = get_or_insert(Memory, screen_location+height_offset);
-  raw_string_stream cursor(expected_contents);
-  // todo: too-long expected_contents should fail
-  long long int addr = screen_data_start+1;  // skip length
-  for (long long int row = 0; row < screen_height; ++row) {
-    cursor.skip_whitespace_and_comments();
-    if (cursor.at_end()) break;
-    assert(cursor.get() == '.');
-    for (long long int column = 0;  column < screen_width;  ++column, addr+= /*size of screen-cell*/2) {
-      const int cell_color_offset = 1;
-      uint32_t curr = cursor.get();
-      if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue;
-      if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) {
-        // filter out other colors
-        continue;
-      }
-      if (get_or_insert(Memory, addr) != 0 && Memory[addr] == curr) {
-        if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue;
-        // contents match but color is off
-        if (Current_scenario && !Scenario_testing_scenario) {
-          // genuine test in a mu file
-          raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(get_or_insert(Memory, addr)) << ") to be in color " << color << " instead of " << no_scientific(Memory[addr+cell_color_offset]) << "\n" << end();
-        }
-        else {
-          // just testing check_screen
-          raise_error << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << '\n' << end();
-        }
-        if (!Scenario_testing_scenario) {
-          Passed = false;
-          ++Num_failures;
-        }
-        return;
-      }
-
-      // really a mismatch
-      // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway.
-      char expected_pretty[10] = {0};
-      if (curr < 256 && !iscntrl(curr)) {
-        // " ('<curr>')"
-        expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0';
-      }
-      char actual_pretty[10] = {0};
-      if (get_or_insert(Memory, addr) < 256 && !iscntrl(Memory[addr])) {
-        // " ('<curr>')"
-        actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(get_or_insert(Memory, addr)), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0';
-      }
-
-      ostringstream color_phrase;
-      if (color != -1) color_phrase << " in color " << color;
-      if (Current_scenario && !Scenario_testing_scenario) {
-        // genuine test in a mu file
-        raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
-        dump_screen();
-      }
-      else {
-        // just testing check_screen
-        raise_error << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
-      }
-      if (!Scenario_testing_scenario) {
-        Passed = false;
-        ++Num_failures;
-      }
-      return;
-    }
-    assert(cursor.get() == '.');
-  }
-  cursor.skip_whitespace_and_comments();
-  assert(cursor.at_end());
-}
-
-raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {}
-
-bool raw_string_stream::at_end() const {
-  if (index >= max) return true;
-  if (tb_utf8_char_length(buf[index]) > max-index) {
-    raise_error << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
-    return true;
-  }
-  return false;
-}
-
-uint32_t raw_string_stream::get() {
-  assert(index < max);  // caller must check bounds before calling 'get'
-  uint32_t result = 0;
-  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
-  assert(length != TB_EOF);
-  index += length;
-  return result;
-}
-
-uint32_t raw_string_stream::peek() {
-  assert(index < max);  // caller must check bounds before calling 'get'
-  uint32_t result = 0;
-  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
-  assert(length != TB_EOF);
-  return result;
-}
-
-void raw_string_stream::skip_whitespace_and_comments() {
-  while (!at_end()) {
-    if (isspace(peek())) get();
-    else if (peek() == '#') {
-      // skip comment
-      get();
-      while (peek() != '\n') get();  // implicitly also handles CRLF
-    }
-    else break;
-  }
-}
-
-:(before "End Primitive Recipe Declarations")
-_DUMP_SCREEN,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN);
-:(before "End Primitive Recipe Checks")
-case _DUMP_SCREEN: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _DUMP_SCREEN: {
-  dump_screen();
-  break;
-}
-
-:(code)
-void dump_screen() {
-  assert(!current_call().default_space);  // not supported
-  long long int screen_location = get_or_insert(Memory, SCREEN);
-  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
-  long long int screen_width = get_or_insert(Memory, screen_location+width_offset);
-  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
-  long long int screen_height = get_or_insert(Memory, screen_location+height_offset);
-  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
-  assert(data_offset >= 0);
-  long long int screen_data_location = screen_location+data_offset;  // type: address:array:character
-  long long int screen_data_start = get_or_insert(Memory, screen_data_location);  // type: array:character
-  assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
-  long long int curr = screen_data_start+1;  // skip length
-  for (long long int row = 0; row < screen_height; ++row) {
-    cerr << '.';
-    for (long long int col = 0; col < screen_width; ++col) {
-      if (get_or_insert(Memory, curr))
-        cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr)));
-      else
-        cerr << ' ';
-      curr += /*size of screen-cell*/2;
-    }
-    cerr << ".\n";
-  }
-}
-
- - - diff --git a/html/073list.mu.html b/html/073list.mu.html new file mode 100644 index 00000000..5db8f8af --- /dev/null +++ b/html/073list.mu.html @@ -0,0 +1,100 @@ + + + + +Mu - 073list.mu + + + + + + + + + + +
+# A list links up multiple objects together to make them easier to manage.
+#
+# The objects must be of the same type. If you want to store multiple types in
+# a single list, use an exclusive-container.
+
+container list:_elem [
+  value:_elem
+  next:address:list:_elem
+]
+
+recipe push x:_elem, in:address:list:_elem -> result:address:list:_elem [
+  local-scope
+  load-ingredients
+  result <- new {(list _elem): type}
+  val:address:_elem <- get-address *result, value:offset
+  *val <- copy x
+  next:address:address:list:_elem <- get-address *result, next:offset
+  *next <- copy in
+  reply result
+]
+
+recipe first in:address:list:_elem -> result:_elem [
+  local-scope
+  load-ingredients
+  result <- get *in, value:offset
+]
+
+# result:address:list <- rest in:address:list
+recipe rest in:address:list:_elem -> result:address:list:_elem [
+  local-scope
+  load-ingredients
+  result <- get *in, next:offset
+]
+
+recipe force-specialization-list-number [
+  1:address:list:number <- push 2:number, 1:address:list:number
+  2:number <- first 1:address:list:number
+  1:address:list:number <- rest 1:address:list:number
+]
+
+# todo: automatically specialize code in scenarios
+scenario list-handling [
+  run [
+    1:address:list:number <- copy 0
+    2:number <- copy 3
+    1:address:list:number <- push 2:number, 1:address:list:number
+    1:address:list:number <- push 4, 1:address:list:number
+    1:address:list:number <- push 5, 1:address:list:number
+    2:number <- first 1:address:list:number
+    1:address:list:number <- rest 1:address:list:number
+    3:number <- first 1:address:list:number
+    1:address:list:number <- rest 1:address:list:number
+    4:number <- first 1:address:list:number
+    1:address:list:number <- rest 1:address:list:number
+  ]
+  memory-should-contain [
+    1 <- 0  # empty to empty, dust to dust..
+    2 <- 5
+    3 <- 4
+    4 <- 3
+  ]
+]
+
+ + + diff --git a/html/073scenario_screen_test.mu.html b/html/073scenario_screen_test.mu.html deleted file mode 100644 index ba50c80e..00000000 --- a/html/073scenario_screen_test.mu.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - -Mu - 073scenario_screen_test.mu - - - - - - - - - - -
-# To check our support for screens in scenarios, rewrite tests from print.mu
-
-scenario print-character-at-top-left-2 [
-  assume-screen 3/width, 2/height
-  run [
-    screen:address:screen <- print-character screen:address:screen, 97/a
-  ]
-  screen-should-contain [
-    .a  .
-    .   .
-  ]
-]
-
-scenario clear-line-erases-printed-characters-2 [
-  assume-screen 5/width, 3/height
-  run [
-    # print a character
-    screen:address:screen <- print-character screen:address:screen, 97/a
-    # move cursor to start of line
-    screen:address:screen <- move-cursor screen:address:screen, 0/row, 0/column
-    # clear line
-    screen:address:screen <- clear-line screen:address:screen
-  ]
-  screen-should-contain [
-    .     .
-    .     .
-    .     .
-  ]
-]
-
- - - diff --git a/html/074console.mu.html b/html/074console.mu.html deleted file mode 100644 index 62f88c3a..00000000 --- a/html/074console.mu.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - -Mu - 074console.mu - - - - - - - - - - -
-# Wrappers around interaction primitives that take a potentially fake object
-# and are thus easier to test.
-
-exclusive-container event [
-  text:character
-  keycode:number  # keys on keyboard without a unicode representation
-  touch:touch-event  # mouse, track ball, etc.
-  resize:resize-event
-  # update the assume-console handler if you add more variants
-]
-
-container touch-event [
-  type:number
-  row:number
-  column:number
-]
-
-container resize-event [
-  width:number
-  height:number
-]
-
-container console [
-  index:number
-  data:address:array:event
-]
-
-recipe new-fake-console [
-  local-scope
-  result:address:console <- new console:type
-  buf:address:address:array:event <- get-address *result, data:offset
-  *buf <- next-ingredient
-  idx:address:number <- get-address *result, index:offset
-  *idx <- copy 0
-  reply result
-]
-
-recipe read-event [
-  local-scope
-  x:address:console <- next-ingredient
-  {
-    break-unless x
-    idx:address:number <- get-address *x, index:offset
-    buf:address:array:event <- get *x, data:offset
-    {
-      max:number <- length *buf
-      done?:boolean <- greater-or-equal *idx, max
-      break-unless done?
-      dummy:address:event <- new event:type
-      reply *dummy, x/same-as-ingredient:0, 1/found, 1/quit
-    }
-    result:event <- index *buf, *idx
-    *idx <- add *idx, 1
-    reply result, x/same-as-ingredient:0, 1/found, 0/quit
-  }
-  switch  # real event source is infrequent; avoid polling it too much
-  result:event, found?:boolean <- check-for-interaction
-  reply result, x/same-as-ingredient:0, found?, 0/quit
-]
-
-# variant of read-event for just keyboard events. Discards everything that
-# isn't unicode, so no arrow keys, page-up/page-down, etc. But you still get
-# newlines, tabs, ctrl-d..
-recipe read-key [
-  local-scope
-  console:address:console <- next-ingredient
-  x:event, console, found?:boolean, quit?:boolean <- read-event console
-  reply-if quit?, 0, console/same-as-ingredient:0, found?, quit?
-  reply-unless found?, 0, console/same-as-ingredient:0, found?, quit?
-  c:address:character <- maybe-convert x, text:variant
-  reply-unless c, 0, console/same-as-ingredient:0, 0/found, 0/quit
-  reply *c, console/same-as-ingredient:0, 1/found, 0/quit
-]
-
-recipe send-keys-to-channel [
-  local-scope
-  console:address:console <- next-ingredient
-  chan:address:channel <- next-ingredient
-  screen:address:screen <- next-ingredient
-  {
-    c:character, console, found?:boolean, quit?:boolean <- read-key console
-    loop-unless found?
-    break-if quit?
-    assert c, [invalid event, expected text]
-    screen <- print-character screen, c
-    chan <- write chan, c
-    loop
-  }
-  reply console/same-as-ingredient:0, chan/same-as-ingredient:1, screen/same-as-ingredient:2
-]
-
-recipe wait-for-event [
-  local-scope
-  console:address:console <- next-ingredient
-  {
-    _, console, found?:boolean <- read-event console
-    loop-unless found?
-  }
-  reply console/same-as-ingredient:0
-]
-
-# use this helper to skip rendering if there's lots of other events queued up
-recipe has-more-events? [
-  local-scope
-  console:address:console <- next-ingredient
-  {
-    break-unless console
-    # fake consoles should be plenty fast; never skip
-    reply 0/false
-  }
-  result:boolean <- interactions-left?
-  reply result
-]
-
- - - diff --git a/html/074random.cc.html b/html/074random.cc.html new file mode 100644 index 00000000..86a40ca5 --- /dev/null +++ b/html/074random.cc.html @@ -0,0 +1,100 @@ + + + + +Mu - 074random.cc + + + + + + + + + + +
+:(before "End Primitive Recipe Declarations")
+RANDOM,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "random", RANDOM);
+:(before "End Primitive Recipe Checks")
+case RANDOM: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RANDOM: {
+  // todo: limited range of numbers, might be imperfectly random
+  // todo: thread state in extra ingredients and products
+  products.resize(1);
+  products.at(0).push_back(rand());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+MAKE_RANDOM_NONDETERMINISTIC,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "make-random-nondeterministic", MAKE_RANDOM_NONDETERMINISTIC);
+:(before "End Primitive Recipe Checks")
+case MAKE_RANDOM_NONDETERMINISTIC: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case MAKE_RANDOM_NONDETERMINISTIC: {
+  srand(time(NULL));
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+ROUND,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "round", ROUND);
+:(before "End Primitive Recipe Checks")
+case ROUND: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise_error << maybe(get(Recipe, r).name) << "'round' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'round' should be a number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case ROUND: {
+  products.resize(1);
+  products.at(0).push_back(rint(ingredients.at(0).at(0)));
+  break;
+}
+
+:(scenario round_to_nearest_integer)
+recipe main [
+  1:number <- round 12.2
+]
++mem: storing 12 in location 1
+
+:(before "End Includes")
+#include<math.h>
+
+ + + diff --git a/html/075duplex_list.mu.html b/html/075duplex_list.mu.html new file mode 100644 index 00000000..c7452600 --- /dev/null +++ b/html/075duplex_list.mu.html @@ -0,0 +1,593 @@ + + + + +Mu - 075duplex_list.mu + + + + + + + + + + +
+# A doubly linked list permits bidirectional traversal.
+
+container duplex-list:_elem [
+  value:_elem
+  next:address:duplex-list:_elem
+  prev:address:duplex-list:_elem
+]
+
+recipe push-duplex x:_elem, in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  result <- new {(duplex-list _elem): type}
+  val:address:_elem <- get-address *result, value:offset
+  *val <- copy x
+  next:address:address:duplex-list:_elem <- get-address *result, next:offset
+  *next <- copy in
+  reply-unless in
+  prev:address:address:duplex-list:_elem <- get-address *in, prev:offset
+  *prev <- copy result
+]
+
+recipe first-duplex in:address:duplex-list:_elem -> result:_elem [
+  local-scope
+  load-ingredients
+  reply-unless in, 0
+  result <- get *in, value:offset
+]
+
+recipe next-duplex in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  reply-unless in, 0
+  result <- get *in, next:offset
+]
+
+recipe prev-duplex in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  reply-unless in, 0
+  result <- get *in, prev:offset
+  reply result
+]
+
+scenario duplex-list-handling [
+  run [
+    # reserve locations 0, 1 and 2 to check for missing null check
+    1:number <- copy 34
+    2:number <- copy 35
+    3:address:duplex-list:character <- copy 0
+    3:address:duplex-list:character <- push-duplex 3, 3:address:duplex-list:character
+    3:address:duplex-list:character <- push-duplex 4, 3:address:duplex-list:character
+    3:address:duplex-list:character <- push-duplex 5, 3:address:duplex-list:character
+    4:address:duplex-list:character <- copy 3:address:duplex-list:character
+    5:character <- first-duplex 4:address:duplex-list:character
+    4:address:duplex-list:character <- next-duplex 4:address:duplex-list:character
+    6:character <- first-duplex 4:address:duplex-list:character
+    4:address:duplex-list:character <- next-duplex 4:address:duplex-list:character
+    7:character <- first-duplex 4:address:duplex-list:character
+    8:address:duplex-list:character <- next-duplex 4:address:duplex-list:character
+    9:character <- first-duplex 8:address:duplex-list:character
+    10:address:duplex-list:character <- next-duplex 8:address:duplex-list:character
+    11:address:duplex-list:character <- prev-duplex 8:address:duplex-list:character
+    4:address:duplex-list:character <- prev-duplex 4:address:duplex-list:character
+    12:character <- first-duplex 4:address:duplex-list:character
+    4:address:duplex-list:character <- prev-duplex 4:address:duplex-list:character
+    13:character <- first-duplex 4:address:duplex-list:character
+    14:boolean <- equal 3:address:duplex-list:character, 4:address:duplex-list:character
+  ]
+  memory-should-contain [
+    0 <- 0  # no modifications to null pointers
+    1 <- 34
+    2 <- 35
+    5 <- 5  # scanning next
+    6 <- 4
+    7 <- 3
+    8 <- 0  # null
+    9 <- 0  # first of null
+    10 <- 0  # next of null
+    11 <- 0  # prev of null
+    12 <- 4  # then start scanning prev
+    13 <- 5
+    14 <- 1  # list back at start
+  ]
+]
+
+# Inserts 'x' after 'in'. Returns some pointer into the list.
+recipe insert-duplex x:_elem, in:address:duplex-list:_elem -> new-node:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  new-node <- new {(duplex-list _elem): type}
+  val:address:_elem <- get-address *new-node, value:offset
+  *val <- copy x
+  next-node:address:duplex-list:_elem <- get *in, next:offset
+  # in.next = new-node
+  y:address:address:duplex-list:_elem <- get-address *in, next:offset
+  *y <- copy new-node
+  # new-node.prev = in
+  y <- get-address *new-node, prev:offset
+  *y <- copy in
+  # new-node.next = next-node
+  y <- get-address *new-node, next:offset
+  *y <- copy next-node
+  # if next-node is not null
+  reply-unless next-node, new-node
+  # next-node.prev = new-node
+  y <- get-address *next-node, prev:offset
+  *y <- copy new-node
+  reply new-node  # just signalling something changed; don't rely on the result
+]
+
+scenario inserting-into-duplex-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to head of list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character  # 2 points inside list
+    2:address:duplex-list:character <- insert-duplex 6, 2:address:duplex-list:character
+    # check structure like before
+    2:address:duplex-list:character <- copy 1:address:duplex-list:character
+    3:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    4:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    5:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    6:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    7:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    8:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    9:character <- first-duplex 2:address:duplex-list:character
+    10:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
+  ]
+  memory-should-contain [
+    3 <- 5  # scanning next
+    4 <- 4
+    5 <- 6  # inserted element
+    6 <- 3
+    7 <- 6  # then prev
+    8 <- 4
+    9 <- 5
+    10 <- 1  # list back at start
+  ]
+]
+
+scenario inserting-at-end-of-duplex-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to head of list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character  # 2 points inside list
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character  # now at end of list
+    2:address:duplex-list:character <- insert-duplex 6, 2:address:duplex-list:character
+    # check structure like before
+    2:address:duplex-list:character <- copy 1:address:duplex-list:character
+    3:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    4:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    5:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    6:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    7:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    8:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    9:character <- first-duplex 2:address:duplex-list:character
+    10:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
+  ]
+  memory-should-contain [
+    3 <- 5  # scanning next
+    4 <- 4
+    5 <- 3
+    6 <- 6  # inserted element
+    7 <- 3  # then prev
+    8 <- 4
+    9 <- 5
+    10 <- 1  # list back at start
+  ]
+]
+
+scenario inserting-after-start-of-duplex-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to head of list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
+    2:address:duplex-list:character <- insert-duplex 6, 1:address:duplex-list:character
+    # check structure like before
+    2:address:duplex-list:character <- copy 1:address:duplex-list:character
+    3:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    4:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    5:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    6:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    7:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    8:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    9:character <- first-duplex 2:address:duplex-list:character
+    10:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
+  ]
+  memory-should-contain [
+    3 <- 5  # scanning next
+    4 <- 6  # inserted element
+    5 <- 4
+    6 <- 3
+    7 <- 4  # then prev
+    8 <- 6
+    9 <- 5
+    10 <- 1  # list back at start
+  ]
+]
+
+# Removes 'in' from its surrounding list. Returns some valid pointer into the
+# rest of the list.
+#
+# Returns null if and only if list is empty. Beware: in that case any pointers
+# to the head are now invalid.
+recipe remove-duplex in:address:duplex-list:_elem -> next-node:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  # if 'in' is null, return
+  reply-unless in, in
+  next-node:address:duplex-list:_elem <- get *in, next:offset
+  prev-node:address:duplex-list:_elem <- get *in, prev:offset
+  # null in's pointers
+  x:address:address:duplex-list:_elem <- get-address *in, next:offset
+  *x <- copy 0
+  x <- get-address *in, prev:offset
+  *x <- copy 0
+  {
+    # if next-node is not null
+    break-unless next-node
+    # next-node.prev = prev-node
+    x <- get-address *next-node, prev:offset
+    *x <- copy prev-node
+  }
+  {
+    # if prev-node is not null
+    break-unless prev-node
+    # prev-node.next = next-node
+    x <- get-address *prev-node, next:offset
+    *x <- copy next-node
+    reply prev-node
+  }
+  reply next-node
+]
+
+scenario removing-from-duplex-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to head of list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character  # 2 points at second element
+    2:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character
+    3:boolean <- equal 2:address:duplex-list:character, 0
+    # check structure like before
+    2:address:duplex-list:character <- copy 1:address:duplex-list:character
+    4:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    5:character <- first-duplex 2:address:duplex-list:character
+    6:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    7:character <- first-duplex 2:address:duplex-list:character
+    8:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
+  ]
+  memory-should-contain [
+    3 <- 0  # remove returned non-null
+    4 <- 5  # scanning next, skipping deleted element
+    5 <- 3
+    6 <- 0  # no more elements
+    7 <- 5  # prev of final element
+    8 <- 1  # list back at start
+  ]
+]
+
+scenario removing-from-start-of-duplex-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to head of list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
+    # removing from head? return value matters.
+    1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character
+    # check structure like before
+    2:address:duplex-list:character <- copy 1:address:duplex-list:character
+    3:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    4:character <- first-duplex 2:address:duplex-list:character
+    5:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    6:character <- first-duplex 2:address:duplex-list:character
+    7:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
+  ]
+  memory-should-contain [
+    3 <- 4  # scanning next, skipping deleted element
+    4 <- 3
+    5 <- 0  # no more elements
+    6 <- 4  # prev of final element
+    7 <- 1  # list back at start
+  ]
+]
+
+scenario removing-from-end-of-duplex-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to head of list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character
+    1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character
+    # delete last element
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character
+    3:boolean <- equal 2:address:duplex-list:character, 0
+    # check structure like before
+    2:address:duplex-list:character <- copy 1:address:duplex-list:character
+    4:character <- first-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    5:character <- first-duplex 2:address:duplex-list:character
+    6:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- prev-duplex 2:address:duplex-list:character
+    7:character <- first-duplex 2:address:duplex-list:character
+    8:boolean <- equal 1:address:duplex-list:character, 2:address:duplex-list:character
+  ]
+  memory-should-contain [
+    3 <- 0  # remove returned non-null
+    4 <- 5  # scanning next, skipping deleted element
+    5 <- 4
+    6 <- 0  # no more elements
+    7 <- 5  # prev of final element
+    8 <- 1  # list back at start
+  ]
+]
+
+scenario removing-from-singleton-list [
+  run [
+    1:address:duplex-list:character <- copy 0  # 1 points to singleton list
+    1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character
+    2:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character
+    3:address:duplex-list:character <- get *1:address:duplex-list:character, next:offset
+    4:address:duplex-list:character <- get *1:address:duplex-list:character, prev:offset
+  ]
+  memory-should-contain [
+    2 <- 0  # remove returned null
+    3 <- 0  # removed node is also detached
+    4 <- 0
+  ]
+]
+
+# l:address:duplex-list <- remove-duplex-between start:address:duplex-list, end:address:duplex-list
+# Remove values between 'start' and 'end' (both exclusive). Returns some valid
+# pointer into the rest of the list.
+# Also clear pointers back out from start/end for hygiene.
+recipe remove-duplex-between start:address:duplex-list:_elem, end:address:duplex-list:_elem -> start:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  reply-unless start
+  # start->next->prev = 0
+  # start->next = end
+  next:address:address:duplex-list:_elem <- get-address *start, next:offset
+  nothing-to-delete?:boolean <- equal *next, end
+  reply-if nothing-to-delete?
+  prev:address:address:duplex-list:_elem <- get-address **next, prev:offset
+  *prev <- copy 0
+  *next <- copy end
+  reply-unless end
+  # end->prev->next = 0
+  # end->prev = start
+  prev <- get-address *end, prev:offset
+  next <- get-address **prev, next:offset
+  *next <- copy 0
+  *prev <- copy start
+]
+
+scenario remove-range [
+  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
+  1:address:duplex-list:character <- copy 0  # 1 points to singleton list
+  1:address:duplex-list:character <- push-duplex 18, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 17, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 16, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 15, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 14, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 13, 1:address:duplex-list:character
+  run [
+    # delete 16 onwards
+    # first pointer: to the third element
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    2:address:duplex-list:character <- remove-duplex-between 2:address:duplex-list:character, 0
+    # now check the list
+    4:character <- get *1:address:duplex-list:character, value:offset
+    5:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    6:character <- get *5:address:duplex-list:character, value:offset
+    7:address:duplex-list:character <- next-duplex 5:address:duplex-list:character
+    8:character <- get *7:address:duplex-list:character, value:offset
+    9:address:duplex-list:character <- next-duplex 7:address:duplex-list:character
+  ]
+  memory-should-contain [
+    4 <- 13
+    6 <- 14
+    8 <- 15
+    9 <- 0
+  ]
+]
+
+scenario remove-range-to-end [
+  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
+  1:address:duplex-list:character <- copy 0  # 1 points to singleton list
+  1:address:duplex-list:character <- push-duplex 18, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 17, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 16, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 15, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 14, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 13, 1:address:duplex-list:character
+  run [
+    # delete 15, 16 and 17
+    # first pointer: to the third element
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    # second pointer: to the fifth element
+    3:address:duplex-list:character <- next-duplex 2:address:duplex-list:character
+    3:address:duplex-list:character <- next-duplex 3:address:duplex-list:character
+    3:address:duplex-list:character <- next-duplex 3:address:duplex-list:character
+    3:address:duplex-list:character <- next-duplex 3:address:duplex-list:character
+    remove-duplex-between 2:address:duplex-list:character, 3:address:duplex-list:character
+    # now check the list
+    4:character <- get *1:address:duplex-list:character, value:offset
+    5:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    6:character <- get *5:address:duplex-list:character, value:offset
+    7:address:duplex-list:character <- next-duplex 5:address:duplex-list:character
+    8:character <- get *7:address:duplex-list:character, value:offset
+    9:address:duplex-list:character <- next-duplex 7:address:duplex-list:character
+  ]
+  memory-should-contain [
+    4 <- 13
+    6 <- 14
+    8 <- 18
+    9 <- 0
+  ]
+]
+
+scenario remove-range-empty [
+  # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
+  1:address:duplex-list:character <- copy 0  # 1 points to singleton list
+  1:address:duplex-list:character <- push-duplex 14, 1:address:duplex-list:character
+  1:address:duplex-list:character <- push-duplex 13, 1:address:duplex-list:character
+  run [
+    # delete 16 onwards
+    # first pointer: to the third element
+    2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    remove-duplex-between 1:address:duplex-list:character, 2:address:duplex-list:character
+    # now check the list
+    4:character <- get *1:address:duplex-list:character, value:offset
+    5:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+    6:character <- get *5:address:duplex-list:character, value:offset
+    7:address:duplex-list:character <- next-duplex 5:address:duplex-list:character
+  ]
+  memory-should-contain [
+    4 <- 13
+    6 <- 14
+    7 <- 0
+  ]
+]
+
+# Inserts list beginning at 'new' after 'in'. Returns some pointer into the list.
+recipe insert-duplex-range in:address:duplex-list:_elem, start:address:duplex-list:_elem -> in:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  reply-unless in
+  reply-unless start
+  end:address:duplex-list:_elem <- copy start
+  {
+    next:address:duplex-list:_elem <- next-duplex end/insert-range
+    break-unless next
+    end <- copy next
+    loop
+  }
+  next:address:duplex-list:_elem <- next-duplex in
+  dest:address:address:duplex-list:_elem <- get-address *end, next:offset
+  *dest <- copy next
+  {
+    break-unless next
+    dest <- get-address *next, prev:offset
+    *dest <- copy end
+  }
+  dest <- get-address *in, next:offset
+  *dest <- copy start
+  dest <- get-address *start, prev:offset
+  *dest <- copy in
+]
+
+recipe append-duplex in:address:duplex-list:_elem, new:address:duplex-list:_elem -> in:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  last:address:duplex-list:_elem <- last-duplex in
+  dest:address:address:duplex-list:_elem <- get-address *last, next:offset
+  *dest <- copy new
+  reply-unless new
+  dest <- get-address *new, prev:offset
+  *dest <- copy last
+]
+
+recipe last-duplex in:address:duplex-list:_elem -> result:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  result <- copy in
+  {
+    next:address:duplex-list:_elem <- next-duplex result
+    break-unless next
+    result <- copy next
+    loop
+  }
+]
+
+# helper for debugging
+recipe dump-duplex-from x:address:duplex-list:_elem [
+  local-scope
+  load-ingredients
+  $print x, [: ]
+  {
+    break-unless x
+    c:_elem <- get *x, value:offset
+    $print c, [ ]
+    x <- next-duplex x
+    {
+      is-newline?:boolean <- equal c, 10/newline
+      break-unless is-newline?
+      $print 10/newline
+      $print x, [: ]
+    }
+    loop
+  }
+  $print 10/newline, [---], 10/newline
+]
+
+recipe force-specialization-duplex-list-character [
+  1:address:duplex-list:character <- push-duplex 2:character, 1:address:duplex-list:character
+  2:character <- first-duplex 1:address:duplex-list:character
+  1:address:duplex-list:character <- next-duplex 1:address:duplex-list:character
+  1:address:duplex-list:character <- prev-duplex 1:address:duplex-list:character
+  1:address:duplex-list:character <- insert-duplex 2:character, 1:address:duplex-list:character
+  1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character
+  1:address:duplex-list:character <- remove-duplex-between 1:address:duplex-list:character, 1:address:duplex-list:character
+  1:address:duplex-list:character <- insert-duplex-range 1:address:duplex-list:character, 1:address:duplex-list:character
+  1:address:duplex-list:character <- append-duplex 1:address:duplex-list:character, 1:address:duplex-list:character
+  1:address:duplex-list:character <- last-duplex 1:address:duplex-list:character
+]
+
+ + + diff --git a/html/075scenario_console.cc.html b/html/075scenario_console.cc.html deleted file mode 100644 index 6ef48e09..00000000 --- a/html/075scenario_console.cc.html +++ /dev/null @@ -1,323 +0,0 @@ - - - - -Mu - 075scenario_console.cc - - - - - - - - - - -
-//: Clean syntax to manipulate and check the console in scenarios.
-//: Instruction 'assume-console' implicitly creates a variable called
-//: 'console' that is accessible inside other 'run' instructions in the
-//: scenario. Like with the fake screen, 'assume-console' transparently
-//: supports unicode.
-
-:(scenarios run_mu_scenario)
-:(scenario keyboard_in_scenario)
-scenario keyboard-in-scenario [
-  assume-console [
-    type [abc]
-  ]
-  run [
-    1:character, console:address:console, 2:boolean <- read-key console:address:console
-    3:character, console:address:console, 4:boolean <- read-key console:address:console
-    5:character, console:address:console, 6:boolean <- read-key console:address:console
-    7:character, console:address:console, 8:boolean, 9:boolean <- read-key console:address:console
-  ]
-  memory-should-contain [
-    1 <- 97  # 'a'
-    2 <- 1
-    3 <- 98  # 'b'
-    4 <- 1
-    5 <- 99  # 'c'
-    6 <- 1
-    7 <- 0  # unset
-    8 <- 1
-    9 <- 1  # end of test events
-  ]
-]
-
-:(before "End Scenario Globals")
-const long long int CONSOLE = Next_predefined_global_for_scenarios++;
-:(before "End Special Scenario Variable Names(r)")
-Name[r]["console"] = CONSOLE;
-
-//: allow naming just for 'console'
-:(before "End is_special_name Cases")
-if (s == "console") return true;
-
-:(before "End Primitive Recipe Declarations")
-ASSUME_CONSOLE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "assume-console", ASSUME_CONSOLE);
-:(before "End Primitive Recipe Checks")
-case ASSUME_CONSOLE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case ASSUME_CONSOLE: {
-  // create a temporary recipe just for parsing; it won't contain valid instructions
-  istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
-  recipe r;
-  slurp_body(in, r);
-  long long int num_events = count_events(r);
-  // initialize the events like in new-fake-console
-  long long int size = num_events*size_of_event() + /*space for length*/1;
-  ensure_space(size);
-  long long int event_data_address = Current_routine->alloc;
-  put(Memory, event_data_address, num_events);
-  ++Current_routine->alloc;
-  for (long long int i = 0; i < SIZE(r.steps); ++i) {
-    const instruction& curr = r.steps.at(i);
-    if (curr.name == "left-click") {
-      put(Memory, Current_routine->alloc, /*tag for 'touch-event' variant of 'event' exclusive-container*/2);
-      put(Memory, Current_routine->alloc+1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT);
-      put(Memory, Current_routine->alloc+1+/*offset of 'row' in 'mouse-event'*/1, to_integer(curr.ingredients.at(0).name));
-      put(Memory, Current_routine->alloc+1+/*offset of 'column' in 'mouse-event'*/2, to_integer(curr.ingredients.at(1).name));
-      Current_routine->alloc += size_of_event();
-    }
-    else if (curr.name == "press") {
-      string key = curr.ingredients.at(0).name;
-      if (is_integer(key))
-        put(Memory, Current_routine->alloc+1, to_integer(key));
-      else if (contains_key(Key, key))
-        put(Memory, Current_routine->alloc+1, Key[key]);
-      else
-        raise_error << "assume-console: can't press " << key << '\n' << end();
-      if (get_or_insert(Memory, Current_routine->alloc+1) < 256)
-        // these keys are in ascii
-        put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0);
-      else {
-        // distinguish from unicode
-        put(Memory, Current_routine->alloc, /*tag for 'keycode' variant of 'event' exclusive-container*/1);
-      }
-      Current_routine->alloc += size_of_event();
-    }
-    // End Event Handlers
-    else {
-      // keyboard input
-      assert(curr.name == "type");
-      const string& contents = curr.ingredients.at(0).name;
-      const char* raw_contents = contents.c_str();
-      long long int num_keyboard_events = unicode_length(contents);
-      long long int curr = 0;
-      for (long long int i = 0; i < num_keyboard_events; ++i) {
-        put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0);
-        uint32_t curr_character;
-        assert(curr < SIZE(contents));
-        tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
-        put(Memory, Current_routine->alloc+/*skip exclusive container tag*/1, curr_character);
-        curr += tb_utf8_char_length(raw_contents[curr]);
-        Current_routine->alloc += size_of_event();
-      }
-    }
-  }
-  assert(Current_routine->alloc == event_data_address+size);
-  // wrap the array of events in a console object
-  ensure_space(size_of_console());
-  put(Memory, CONSOLE, Current_routine->alloc);
-  Current_routine->alloc += size_of_console();
-  long long int console_address = get_or_insert(Memory, CONSOLE);
-  put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address);
-  break;
-}
-
-:(before "End Globals")
-map<string, long long int> Key;
-:(before "End One-time Setup")
-initialize_key_names();
-:(code)
-void initialize_key_names() {
-  Key["F1"] = TB_KEY_F1;
-  Key["F2"] = TB_KEY_F2;
-  Key["F3"] = TB_KEY_F3;
-  Key["F4"] = TB_KEY_F4;
-  Key["F5"] = TB_KEY_F5;
-  Key["F6"] = TB_KEY_F6;
-  Key["F7"] = TB_KEY_F7;
-  Key["F8"] = TB_KEY_F8;
-  Key["F9"] = TB_KEY_F9;
-  Key["F10"] = TB_KEY_F10;
-  Key["F11"] = TB_KEY_F11;
-  Key["F12"] = TB_KEY_F12;
-  Key["insert"] = TB_KEY_INSERT;
-  Key["delete"] = TB_KEY_DELETE;
-  Key["home"] = TB_KEY_HOME;
-  Key["end"] = TB_KEY_END;
-  Key["page-up"] = TB_KEY_PGUP;
-  Key["page-down"] = TB_KEY_PGDN;
-  Key["up-arrow"] = TB_KEY_ARROW_UP;
-  Key["down-arrow"] = TB_KEY_ARROW_DOWN;
-  Key["left-arrow"] = TB_KEY_ARROW_LEFT;
-  Key["right-arrow"] = TB_KEY_ARROW_RIGHT;
-  Key["ctrl-a"] = TB_KEY_CTRL_A;
-  Key["ctrl-b"] = TB_KEY_CTRL_B;
-  Key["ctrl-c"] = TB_KEY_CTRL_C;
-  Key["ctrl-d"] = TB_KEY_CTRL_D;
-  Key["ctrl-e"] = TB_KEY_CTRL_E;
-  Key["ctrl-f"] = TB_KEY_CTRL_F;
-  Key["ctrl-g"] = TB_KEY_CTRL_G;
-  Key["backspace"] = TB_KEY_BACKSPACE;
-  Key["ctrl-h"] = TB_KEY_CTRL_H;
-  Key["tab"] = TB_KEY_TAB;
-  Key["ctrl-i"] = TB_KEY_CTRL_I;
-  Key["ctrl-j"] = TB_KEY_CTRL_J;
-  Key["enter"] = TB_KEY_NEWLINE;  // ignore CR/LF distinction; there is only 'enter'
-  Key["ctrl-k"] = TB_KEY_CTRL_K;
-  Key["ctrl-l"] = TB_KEY_CTRL_L;
-  Key["ctrl-m"] = TB_KEY_CTRL_M;
-  Key["ctrl-n"] = TB_KEY_CTRL_N;
-  Key["ctrl-o"] = TB_KEY_CTRL_O;
-  Key["ctrl-p"] = TB_KEY_CTRL_P;
-  Key["ctrl-q"] = TB_KEY_CTRL_Q;
-  Key["ctrl-r"] = TB_KEY_CTRL_R;
-  Key["ctrl-s"] = TB_KEY_CTRL_S;
-  Key["ctrl-t"] = TB_KEY_CTRL_T;
-  Key["ctrl-u"] = TB_KEY_CTRL_U;
-  Key["ctrl-v"] = TB_KEY_CTRL_V;
-  Key["ctrl-w"] = TB_KEY_CTRL_W;
-  Key["ctrl-x"] = TB_KEY_CTRL_X;
-  Key["ctrl-y"] = TB_KEY_CTRL_Y;
-  Key["ctrl-z"] = TB_KEY_CTRL_Z;
-  Key["escape"] = TB_KEY_ESC;
-}
-
-:(scenario events_in_scenario)
-scenario events-in-scenario [
-  assume-console [
-    type [abc]
-    left-click 0, 1
-    press up-arrow
-    type [d]
-  ]
-  run [
-    # 3 keyboard events; each event occupies 4 locations
-    1:event <- read-event console:address:console
-    5:event <- read-event console:address:console
-    9:event <- read-event console:address:console
-    # mouse click
-    13:event <- read-event console:address:console
-    # non-character keycode
-    17:event <- read-event console:address:console
-    # final keyboard event
-    21:event <- read-event console:address:console
-  ]
-  memory-should-contain [
-    1 <- 0  # 'text'
-    2 <- 97  # 'a'
-    3 <- 0  # unused
-    4 <- 0  # unused
-    5 <- 0  # 'text'
-    6 <- 98  # 'b'
-    7 <- 0  # unused
-    8 <- 0  # unused
-    9 <- 0  # 'text'
-    10 <- 99  # 'c'
-    11 <- 0  # unused
-    12 <- 0  # unused
-    13 <- 2  # 'mouse'
-    14 <- 65513  # mouse click
-    15 <- 0  # row
-    16 <- 1  # column
-    17 <- 1  # 'keycode'
-    18 <- 65517  # up arrow
-    19 <- 0  # unused
-    20 <- 0  # unused
-    21 <- 0  # 'text'
-    22 <- 100  # 'd'
-    23 <- 0  # unused
-    24 <- 0  # unused
-    25 <- 0
-  ]
-]
-
-//: Deal with special keys and unmatched brackets by allowing each test to
-//: independently choose the unicode symbol to denote them.
-:(before "End Primitive Recipe Declarations")
-REPLACE_IN_CONSOLE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE);
-:(before "End Primitive Recipe Checks")
-case REPLACE_IN_CONSOLE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case REPLACE_IN_CONSOLE: {
-  assert(scalar(ingredients.at(0)));
-  if (!get_or_insert(Memory, CONSOLE)) {
-    raise_error << "console not initialized\n" << end();
-    break;
-  }
-  long long int console_address = get_or_insert(Memory, CONSOLE);
-  long long int console_data = get_or_insert(Memory, console_address+1);
-  long long int size = get_or_insert(Memory, console_data);  // array size
-  for (long long int i = 0, curr = console_data+1; i < size; ++i, curr+=size_of_event()) {
-    if (get_or_insert(Memory, curr) != /*text*/0) continue;
-    if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue;
-    for (long long int n = 0; n < size_of_event(); ++n)
-      put(Memory, curr+n, ingredients.at(1).at(n));
-  }
-  break;
-}
-
-:(code)
-long long int count_events(const recipe& r) {
-  long long int result = 0;
-  for (long long int i = 0; i < SIZE(r.steps); ++i) {
-    const instruction& curr = r.steps.at(i);
-    if (curr.name == "type")
-      result += unicode_length(curr.ingredients.at(0).name);
-    else
-      result++;
-  }
-  return result;
-}
-
-long long int size_of_event() {
-  // memoize result if already computed
-  static long long int result = 0;
-  if (result) return result;
-  type_tree* type = new type_tree(get(Type_ordinal, "event"));
-  result = size_of(type);
-  delete type;
-  return result;
-}
-
-long long int size_of_console() {
-  // memoize result if already computed
-  static long long int result = 0;
-  if (result) return result;
-  assert(get(Type_ordinal, "console"));
-  type_tree* type = new type_tree(get(Type_ordinal, "console"));
-  result = size_of(type);
-  delete type;
-  return result;
-}
-
- - - diff --git a/html/076scenario_console_test.mu.html b/html/076scenario_console_test.mu.html deleted file mode 100644 index bc0f6688..00000000 --- a/html/076scenario_console_test.mu.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - -Mu - 076scenario_console_test.mu - - - - - - - - - - -
-# To check our support for consoles in scenarios, rewrite tests from
-# scenario_console.mu
-# Tests for console interface.
-
-scenario read-key-in-mu [
-  assume-console [
-    type [abc]
-  ]
-  run [
-    1:character, console:address:console, 2:boolean <- read-key console:address:console
-    3:character, console:address:console, 4:boolean <- read-key console:address:console
-    5:character, console:address:console, 6:boolean <- read-key console:address:console
-    7:character, console:address:console, 8:boolean <- read-key console:address:console
-  ]
-  memory-should-contain [
-    1 <- 97  # 'a'
-    2 <- 1
-    3 <- 98  # 'b'
-    4 <- 1
-    5 <- 99  # 'c'
-    6 <- 1
-    7 <- 0  # eof
-    8 <- 1
-  ]
-]
-
- - - diff --git a/html/076stream.mu.html b/html/076stream.mu.html new file mode 100644 index 00000000..feaa978b --- /dev/null +++ b/html/076stream.mu.html @@ -0,0 +1,80 @@ + + + + +Mu - 076stream.mu + + + + + + + + + + +
+# new type to help incrementally read strings
+container stream [
+  index:number
+  data:address:array:character
+]
+
+recipe new-stream [
+  local-scope
+  result:address:stream <- new stream:type
+  i:address:number <- get-address *result, index:offset
+  *i <- copy 0
+  d:address:address:array:character <- get-address *result, data:offset
+  *d <- next-ingredient
+  reply result
+]
+
+recipe rewind-stream [
+  local-scope
+  in:address:stream <- next-ingredient
+  x:address:number <- get-address *in, index:offset
+  *x <- copy 0
+  reply in/same-as-arg:0
+]
+
+recipe read-line [
+  local-scope
+  in:address:stream <- next-ingredient
+  idx:address:number <- get-address *in, index:offset
+  s:address:array:character <- get *in, data:offset
+  next-idx:number <- find-next s, 10/newline, *idx
+  result:address:array:character <- string-copy s, *idx, next-idx
+  *idx <- add next-idx, 1  # skip newline
+  reply result
+]
+
+recipe end-of-stream? [
+  local-scope
+  in:address:stream <- next-ingredient
+  idx:number <- get *in, index:offset
+  s:address:array:character <- get *in, data:offset
+  len:number <- length *s
+  result:boolean <- greater-or-equal idx, len
+  reply result
+]
+
+ + + diff --git a/html/080display.cc.html b/html/080display.cc.html new file mode 100644 index 00000000..bf3ad0c8 --- /dev/null +++ b/html/080display.cc.html @@ -0,0 +1,534 @@ + + + + +Mu - 080display.cc + + + + + + + + + + +
+//: Take charge of the text-mode display and console.
+
+//:: Display management
+
+:(before "End Globals")
+long long int Display_row = 0, Display_column = 0;
+bool Autodisplay = true;
+
+:(before "End Primitive Recipe Declarations")
+OPEN_CONSOLE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "open-console", OPEN_CONSOLE);
+:(before "End Primitive Recipe Checks")
+case OPEN_CONSOLE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case OPEN_CONSOLE: {
+  tb_init();
+  Display_row = Display_column = 0;
+  long long int width = tb_width();
+  long long int height = tb_height();
+  if (width > 222 || height > 222) tb_shutdown();
+  if (width > 222)
+    raise_error << "sorry, mu doesn't support windows wider than 222 characters. Please resize your window.\n" << end();
+  if (height > 222)
+    raise_error << "sorry, mu doesn't support windows taller than 222 characters. Please resize your window.\n" << end();
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+CLOSE_CONSOLE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "close-console", CLOSE_CONSOLE);
+:(before "End Primitive Recipe Checks")
+case CLOSE_CONSOLE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case CLOSE_CONSOLE: {
+  tb_shutdown();
+  break;
+}
+
+:(before "End Teardown")
+tb_shutdown();
+
+:(before "End Primitive Recipe Declarations")
+CLEAR_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "clear-display", CLEAR_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case CLEAR_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case CLEAR_DISPLAY: {
+  tb_clear();
+  Display_row = Display_column = 0;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SYNC_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "sync-display", SYNC_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case SYNC_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SYNC_DISPLAY: {
+  tb_sync();
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+CLEAR_LINE_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "clear-line-on-display", CLEAR_LINE_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case CLEAR_LINE_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case CLEAR_LINE_ON_DISPLAY: {
+  long long int width = tb_width();
+  for (long long int x = Display_column; x < width; ++x) {
+    tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_BLACK);
+  }
+  tb_set_cursor(Display_column, Display_row);
+  if (Autodisplay) tb_present();
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+PRINT_CHARACTER_TO_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "print-character-to-display", PRINT_CHARACTER_TO_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case PRINT_CHARACTER_TO_DISPLAY: {
+  if (inst.ingredients.empty()) {
+    raise_error << maybe(get(Recipe, r).name) << "'print-character-to-display' requires at least one ingredient, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'print-character-to-display' should be a character, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (SIZE(inst.ingredients) > 1) {
+    if (!is_mu_number(inst.ingredients.at(1))) {
+      raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'print-character-to-display' should be a foreground color number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
+      break;
+    }
+  }
+  if (SIZE(inst.ingredients) > 2) {
+    if (!is_mu_number(inst.ingredients.at(2))) {
+      raise_error << maybe(get(Recipe, r).name) << "third ingredient of 'print-character-to-display' should be a background color number, but got " << inst.ingredients.at(2).original_string << '\n' << end();
+      break;
+    }
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case PRINT_CHARACTER_TO_DISPLAY: {
+  int h=tb_height(), w=tb_width();
+  long long int height = (h >= 0) ? h : 0;
+  long long int width = (w >= 0) ? w : 0;
+  long long int c = ingredients.at(0).at(0);
+  int color = TB_BLACK;
+  if (SIZE(ingredients) > 1) {
+    color = ingredients.at(1).at(0);
+  }
+  int bg_color = TB_BLACK;
+  if (SIZE(ingredients) > 2) {
+    bg_color = ingredients.at(2).at(0);
+    if (bg_color == 0) bg_color = TB_BLACK;
+  }
+  tb_change_cell(Display_column, Display_row, c, color, bg_color);
+  if (c == '\n' || c == '\r') {
+    if (Display_row < height-1) {
+      Display_column = 0;
+      ++Display_row;
+      tb_set_cursor(Display_column, Display_row);
+      if (Autodisplay) tb_present();
+    }
+    break;
+  }
+  if (c == '\b') {
+    if (Display_column > 0) {
+      tb_change_cell(Display_column-1, Display_row, ' ', color, bg_color);
+      --Display_column;
+      tb_set_cursor(Display_column, Display_row);
+      if (Autodisplay) tb_present();
+    }
+    break;
+  }
+  if (Display_column < width-1) {
+    ++Display_column;
+    tb_set_cursor(Display_column, Display_row);
+  }
+  if (Autodisplay) tb_present();
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+CURSOR_POSITION_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "cursor-position-on-display", CURSOR_POSITION_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case CURSOR_POSITION_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case CURSOR_POSITION_ON_DISPLAY: {
+  products.resize(2);
+  products.at(0).push_back(Display_row);
+  products.at(1).push_back(Display_column);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+MOVE_CURSOR_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "move-cursor-on-display", MOVE_CURSOR_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case MOVE_CURSOR_ON_DISPLAY: {
+  if (SIZE(inst.ingredients) != 2) {
+    raise_error << maybe(get(Recipe, r).name) << "'move-cursor-on-display' requires two ingredients, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(1))) {
+    raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case MOVE_CURSOR_ON_DISPLAY: {
+  Display_row = ingredients.at(0).at(0);
+  Display_column = ingredients.at(1).at(0);
+  tb_set_cursor(Display_column, Display_row);
+  if (Autodisplay) tb_present();
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+MOVE_CURSOR_DOWN_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "move-cursor-down-on-display", MOVE_CURSOR_DOWN_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case MOVE_CURSOR_DOWN_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case MOVE_CURSOR_DOWN_ON_DISPLAY: {
+  int h=tb_height();
+  long long int height = (h >= 0) ? h : 0;
+  if (Display_row < height-1) {
+    Display_row++;
+    tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
+  }
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+MOVE_CURSOR_UP_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "move-cursor-up-on-display", MOVE_CURSOR_UP_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case MOVE_CURSOR_UP_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case MOVE_CURSOR_UP_ON_DISPLAY: {
+  if (Display_row > 0) {
+    Display_row--;
+    tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
+  }
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+MOVE_CURSOR_RIGHT_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "move-cursor-right-on-display", MOVE_CURSOR_RIGHT_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
+  int w=tb_width();
+  long long int width = (w >= 0) ? w : 0;
+  if (Display_column < width-1) {
+    Display_column++;
+    tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
+  }
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+MOVE_CURSOR_LEFT_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "move-cursor-left-on-display", MOVE_CURSOR_LEFT_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case MOVE_CURSOR_LEFT_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case MOVE_CURSOR_LEFT_ON_DISPLAY: {
+  if (Display_column > 0) {
+    Display_column--;
+    tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
+  }
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+DISPLAY_WIDTH,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "display-width", DISPLAY_WIDTH);
+:(before "End Primitive Recipe Checks")
+case DISPLAY_WIDTH: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case DISPLAY_WIDTH: {
+  products.resize(1);
+  products.at(0).push_back(tb_width());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+DISPLAY_HEIGHT,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "display-height", DISPLAY_HEIGHT);
+:(before "End Primitive Recipe Checks")
+case DISPLAY_HEIGHT: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case DISPLAY_HEIGHT: {
+  products.resize(1);
+  products.at(0).push_back(tb_height());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+HIDE_CURSOR_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "hide-cursor-on-display", HIDE_CURSOR_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case HIDE_CURSOR_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case HIDE_CURSOR_ON_DISPLAY: {
+  tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SHOW_CURSOR_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "show-cursor-on-display", SHOW_CURSOR_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case SHOW_CURSOR_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SHOW_CURSOR_ON_DISPLAY: {
+  tb_set_cursor(Display_row, Display_column);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+HIDE_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "hide-display", HIDE_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case HIDE_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case HIDE_DISPLAY: {
+  Autodisplay = false;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SHOW_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "show-display", SHOW_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case SHOW_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SHOW_DISPLAY: {
+  Autodisplay = true;
+  tb_present();
+  break;
+}
+
+//:: Keyboard/mouse management
+
+:(before "End Primitive Recipe Declarations")
+WAIT_FOR_SOME_INTERACTION,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "wait-for-some-interaction", WAIT_FOR_SOME_INTERACTION);
+:(before "End Primitive Recipe Checks")
+case WAIT_FOR_SOME_INTERACTION: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case WAIT_FOR_SOME_INTERACTION: {
+  tb_event event;
+  tb_poll_event(&event);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+CHECK_FOR_INTERACTION,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "check-for-interaction", CHECK_FOR_INTERACTION);
+:(before "End Primitive Recipe Checks")
+case CHECK_FOR_INTERACTION: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case CHECK_FOR_INTERACTION: {
+  products.resize(2);  // result and status
+  tb_event event;
+  int event_type = tb_peek_event(&event, 5/*ms*/);
+  if (event_type == TB_EVENT_KEY && event.ch) {
+    products.at(0).push_back(/*text event*/0);
+    products.at(0).push_back(event.ch);
+    products.at(0).push_back(0);
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  // treat keys within ascii as unicode characters
+  if (event_type == TB_EVENT_KEY && event.key < 0xff) {
+    products.at(0).push_back(/*text event*/0);
+    if (event.key == TB_KEY_CTRL_C) tb_shutdown(), exit(1);
+    if (event.key == TB_KEY_BACKSPACE2) event.key = TB_KEY_BACKSPACE;
+    if (event.key == TB_KEY_CARRIAGE_RETURN) event.key = TB_KEY_NEWLINE;
+    products.at(0).push_back(event.key);
+    products.at(0).push_back(0);
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  // keys outside ascii aren't unicode characters but arbitrary termbox inventions
+  if (event_type == TB_EVENT_KEY) {
+    products.at(0).push_back(/*keycode event*/1);
+    products.at(0).push_back(event.key);
+    products.at(0).push_back(0);
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  if (event_type == TB_EVENT_MOUSE) {
+    products.at(0).push_back(/*touch event*/2);
+    products.at(0).push_back(event.key);  // which button, etc.
+    products.at(0).push_back(event.y);  // row
+    products.at(0).push_back(event.x);  // column
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  if (event_type == TB_EVENT_RESIZE) {
+    products.at(0).push_back(/*resize event*/3);
+    products.at(0).push_back(event.w);  // width
+    products.at(0).push_back(event.h);  // height
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  assert(event_type == 0);
+  products.at(0).push_back(0);
+  products.at(0).push_back(0);
+  products.at(0).push_back(0);
+  products.at(0).push_back(0);
+  products.at(1).push_back(/*found*/false);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+INTERACTIONS_LEFT,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "interactions-left?", INTERACTIONS_LEFT);
+:(before "End Primitive Recipe Checks")
+case INTERACTIONS_LEFT: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case INTERACTIONS_LEFT: {
+  products.resize(1);
+  products.at(0).push_back(tb_event_ready());
+  break;
+}
+
+//: a hack to make edit.mu more responsive
+
+:(before "End Primitive Recipe Declarations")
+CLEAR_DISPLAY_FROM,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "clear-display-from", CLEAR_DISPLAY_FROM);
+:(before "End Primitive Recipe Checks")
+case CLEAR_DISPLAY_FROM: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case CLEAR_DISPLAY_FROM: {
+  // todo: error checking
+  int row = ingredients.at(0).at(0);
+  int column = ingredients.at(1).at(0);
+  int left = ingredients.at(2).at(0);
+  int right = ingredients.at(3).at(0);
+  int height=tb_height();
+  for (; row < height; ++row, column=left) {  // start column from left in every inner loop except first
+    for (; column <= right; ++column) {
+      tb_change_cell(column, row, ' ', TB_WHITE, TB_BLACK);
+    }
+  }
+  if (Autodisplay) tb_present();
+  break;
+}
+
+ + + diff --git a/html/080trace_browser.cc.html b/html/080trace_browser.cc.html deleted file mode 100644 index d209225b..00000000 --- a/html/080trace_browser.cc.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - -Mu - 080trace_browser.cc - - - - - - - - - - -
-:(before "End Primitive Recipe Declarations")
-_BROWSE_TRACE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
-:(before "End Primitive Recipe Checks")
-case _BROWSE_TRACE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _BROWSE_TRACE: {
-  start_trace_browser();
-  break;
-}
-
-:(before "End Globals")
-set<long long int> Visible;
-long long int Top_of_screen = 0;
-long long int Last_printed_row = 0;
-map<int, long long int> Trace_index;  // screen row -> trace index
-
-:(code)
-void start_trace_browser() {
-  if (!Trace_stream) return;
-  cerr << "computing depth to display\n";
-  long long int min_depth = 9999;
-  for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
-    trace_line& curr_line = Trace_stream->past_lines.at(i);
-    if (curr_line.depth == 0) continue;
-    if (curr_line.depth < min_depth) min_depth = curr_line.depth;
-  }
-  cerr << "depth is " << min_depth << '\n';
-  cerr << "computing lines to display\n";
-  for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
-    if (Trace_stream->past_lines.at(i).depth == min_depth)
-      Visible.insert(i);
-  }
-  tb_init();
-  Display_row = Display_column = 0;
-  tb_event event;
-  Top_of_screen = 0;
-  refresh_screen_rows();
-  while (true) {
-    render();
-    do {
-      tb_poll_event(&event);
-    } while (event.type != TB_EVENT_KEY);
-    long long int key = event.key ? event.key : event.ch;
-    if (key == 'q' || key == 'Q') break;
-    if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
-      // move cursor one line down
-      if (Display_row < Last_printed_row) ++Display_row;
-    }
-    if (key == 'k' || key == TB_KEY_ARROW_UP) {
-      // move cursor one line up
-      if (Display_row > 0) --Display_row;
-    }
-    if (key == 'H') {
-      // move cursor to top of screen
-      Display_row = 0;
-    }
-    if (key == 'M') {
-      // move cursor to center of screen
-      Display_row = tb_height()/2;
-    }
-    if (key == 'L') {
-      // move cursor to bottom of screen
-      Display_row = tb_height()-1;
-    }
-    if (key == 'J' || key == TB_KEY_PGDN) {
-      // page-down
-      if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
-        Top_of_screen = Trace_index[tb_height()-1]+1;
-        refresh_screen_rows();
-      }
-    }
-    if (key == 'K' || key == TB_KEY_PGUP) {
-      // page-up is more convoluted
-      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
-        --Top_of_screen;
-        if (Top_of_screen <= 0) break;
-        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
-          --Top_of_screen;
-      }
-      if (Top_of_screen > 0)
-        refresh_screen_rows();
-    }
-    if (key == 'G') {
-      // go to bottom of screen; largely like page-up, interestingly
-      Top_of_screen = SIZE(Trace_stream->past_lines)-1;
-      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
-        --Top_of_screen;
-        if (Top_of_screen <= 0) break;
-        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
-          --Top_of_screen;
-      }
-      refresh_screen_rows();
-      // move cursor to bottom
-      Display_row = Last_printed_row;
-      refresh_screen_rows();
-    }
-    if (key == TB_KEY_CARRIAGE_RETURN) {
-      // expand lines under current by one level
-      assert(contains_key(Trace_index, Display_row));
-      long long int start_index = Trace_index[Display_row];
-      long long int index = 0;
-      // simultaneously compute end_index and min_depth
-      int min_depth = 9999;
-      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
-        if (contains_key(Visible, index)) break;
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth == 0) continue;
-        assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
-        if (curr_line.depth < min_depth) min_depth = curr_line.depth;
-      }
-      long long int end_index = index;
-      // mark as visible all intervening indices at min_depth
-      for (index = start_index; index < end_index; ++index) {
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth == min_depth) {
-          Visible.insert(index);
-        }
-      }
-      refresh_screen_rows();
-    }
-    if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
-      // collapse all lines under current
-      assert(contains_key(Trace_index, Display_row));
-      long long int start_index = Trace_index[Display_row];
-      long long int index = 0;
-      // end_index is the next line at a depth same as or lower than start_index
-      int initial_depth = Trace_stream->past_lines.at(start_index).depth;
-      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
-        if (!contains_key(Visible, index)) continue;
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth == 0) continue;
-        if (curr_line.depth <= initial_depth) break;
-      }
-      long long int end_index = index;
-      // mark as visible all intervening indices at min_depth
-      for (index = start_index+1; index < end_index; ++index) {
-        Visible.erase(index);
-      }
-      refresh_screen_rows();
-    }
-  }
-  tb_shutdown();
-}
-
-// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
-void refresh_screen_rows() {
-  long long int screen_row = 0, index = 0;
-  Trace_index.clear();
-  for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
-    // skip lines without depth for now
-    while (!contains_key(Visible, index)) {
-      ++index;
-      if (index >= SIZE(Trace_stream->past_lines)) goto done;
-    }
-    assert(index < SIZE(Trace_stream->past_lines));
-    Trace_index[screen_row] = index;
-  }
-done:;
-}
-
-void render() {
-  long long int screen_row = 0;
-  for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
-    if (!contains_key(Trace_index, screen_row)) break;
-    trace_line& curr_line = Trace_stream->past_lines.at(Trace_index[screen_row]);
-    ostringstream out;
-    out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
-    if (screen_row < tb_height()-1) {
-      long long int delta = lines_hidden(screen_row);
-      // home-brew escape sequence for red
-      if (delta > 999) out << "{";
-      out << " (" << lines_hidden(screen_row) << ")";
-      if (delta > 999) out << "}";
-    }
-    render_line(screen_row, out.str());
-  }
-  // clear rest of screen
-  Last_printed_row = screen_row-1;
-  for (; screen_row < tb_height(); ++screen_row) {
-    render_line(screen_row, "~");
-  }
-  // move cursor back to display row at the end
-  tb_set_cursor(0, Display_row);
-  tb_present();
-}
-
-long long int lines_hidden(long long int screen_row) {
-  assert(contains_key(Trace_index, screen_row));
-  if (!contains_key(Trace_index, screen_row+1))
-    return SIZE(Trace_stream->past_lines)-Trace_index[screen_row];
-  else
-    return Trace_index[screen_row+1] - Trace_index[screen_row];
-}
-
-void render_line(int screen_row, const string& s) {
-  long long int col = 0;
-  int color = TB_WHITE;
-  for (col = 0; col < tb_width() && col < SIZE(s); ++col) {
-    char c = s.at(col);  // todo: unicode
-    if (c == '\n') c = ';';  // replace newlines with semi-colons
-    // escapes. hack: can't start a line with them.
-    if (c == '{') { color = /*red*/1; c = ' '; }
-    if (c == '}') { color = TB_WHITE; c = ' '; }
-    tb_change_cell(col, screen_row, c, color, TB_BLACK);
-  }
-  for (; col < tb_width(); ++col) {
-    tb_change_cell(col, screen_row, ' ', TB_WHITE, TB_BLACK);
-  }
-}
-
- - - diff --git a/html/081print.mu.html b/html/081print.mu.html new file mode 100644 index 00000000..19efbb1c --- /dev/null +++ b/html/081print.mu.html @@ -0,0 +1,722 @@ + + + + +Mu - 081print.mu + + + + + + + + + + +
+# Wrappers around print primitives that take a 'screen' object and are thus
+# easier to test.
+
+container screen [
+  num-rows:number
+  num-columns:number
+  cursor-row:number
+  cursor-column:number
+  data:address:array:screen-cell
+]
+
+container screen-cell [
+  contents:character
+  color:number
+]
+
+recipe new-fake-screen [
+  local-scope
+  result:address:screen <- new screen:type
+  width:address:number <- get-address *result, num-columns:offset
+  *width <- next-ingredient
+  height:address:number <- get-address *result, num-rows:offset
+  *height <- next-ingredient
+  row:address:number <- get-address *result, cursor-row:offset
+  *row <- copy 0
+  column:address:number <- get-address *result, cursor-column:offset
+  *column <- copy 0
+  bufsize:number <- multiply *width, *height
+  buf:address:address:array:screen-cell <- get-address *result, data:offset
+  *buf <- new screen-cell:type, bufsize
+  clear-screen result
+  reply result
+]
+
+recipe clear-screen [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists
+  {
+    break-unless sc
+    # clear fake screen
+    buf:address:array:screen-cell <- get *sc, data:offset
+    max:number <- length *buf
+    i:number <- copy 0
+    {
+      done?:boolean <- greater-or-equal i, max
+      break-if done?
+      curr:address:screen-cell <- index-address *buf, i
+      curr-content:address:character <- get-address *curr, contents:offset
+      *curr-content <- copy 0/empty
+      curr-color:address:number <- get-address *curr, color:offset
+      *curr-color <- copy 7/white
+      i <- add i, 1
+      loop
+    }
+    # reset cursor
+    x:address:number <- get-address *sc, cursor-row:offset
+    *x <- copy 0
+    x <- get-address *sc, cursor-column:offset
+    *x <- copy 0
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  clear-display
+  reply sc/same-as-ingredient:0
+]
+
+recipe sync-screen [
+  local-scope
+  sc:address:screen <- next-ingredient
+  {
+    break-if sc
+    sync-display
+  }
+  # do nothing for fake screens
+]
+
+recipe fake-screen-is-empty? [
+  local-scope
+  sc:address:screen <- next-ingredient
+  reply-unless sc, 1/true
+  buf:address:array:screen-cell <- get *sc, data:offset
+  i:number <- copy 0
+  len:number <- length *buf
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    curr:screen-cell <- index *buf, i
+    curr-contents:character <- get curr, contents:offset
+    i <- add i, 1
+    loop-unless curr-contents
+    # not 0
+    reply 0/false
+  }
+  reply 1/true
+]
+
+recipe print-character [
+  local-scope
+  sc:address:screen <- next-ingredient
+  c:character <- next-ingredient
+  color:number, color-found?:boolean <- next-ingredient
+  {
+    # default color to white
+    break-if color-found?
+    color <- copy 7/white
+  }
+  bg-color:number, bg-color-found?:boolean <- next-ingredient
+  {
+    # default bg-color to black
+    break-if bg-color-found?
+    bg-color <- copy 0/black
+  }
+  trace 90, [print-character], c
+  {
+    # if x exists
+    # (handle special cases exactly like in the real screen)
+    break-unless sc
+    width:number <- get *sc, num-columns:offset
+    height:number <- get *sc, num-rows:offset
+    # if cursor is out of bounds, silently exit
+    row:address:number <- get-address *sc, cursor-row:offset
+    legal?:boolean <- greater-or-equal *row, 0
+    reply-unless legal?, sc
+    legal? <- lesser-than *row, height
+    reply-unless legal?, sc
+    column:address:number <- get-address *sc, cursor-column:offset
+    legal? <- greater-or-equal *column, 0
+    reply-unless legal?, sc
+    legal? <- lesser-than *column, width
+    reply-unless legal?, sc
+    # special-case: newline
+    {
+      newline?:boolean <- equal c, 10/newline
+      break-unless newline?
+      {
+        # unless cursor is already at bottom
+        bottom:number <- subtract height, 1
+        at-bottom?:boolean <- greater-or-equal *row, bottom
+        break-if at-bottom?
+        # move it to the next row
+        *column <- copy 0
+        *row <- add *row, 1
+      }
+      reply sc/same-as-ingredient:0
+    }
+    # save character in fake screen
+    index:number <- multiply *row, width
+    index <- add index, *column
+    buf:address:array:screen-cell <- get *sc, data:offset
+    len:number <- length *buf
+    # special-case: backspace
+    {
+      backspace?:boolean <- equal c, 8
+      break-unless backspace?
+      {
+        # unless cursor is already at left margin
+        at-left?:boolean <- lesser-or-equal *column, 0
+        break-if at-left?
+        # clear previous location
+        *column <- subtract *column, 1
+        index <- subtract index, 1
+        cursor:address:screen-cell <- index-address *buf, index
+        cursor-contents:address:character <- get-address *cursor, contents:offset
+        *cursor-contents <- copy 32/space
+        cursor-color:address:number <- get-address *cursor, color:offset
+        *cursor-color <- copy 7/white
+      }
+      reply sc/same-as-ingredient:0
+    }
+    cursor:address:screen-cell <- index-address *buf, index
+    cursor-contents:address:character <- get-address *cursor, contents:offset
+    *cursor-contents <- copy c
+    cursor-color:address:number <- get-address *cursor, color:offset
+    *cursor-color <- copy color
+    # increment column unless it's already all the way to the right
+    {
+      right:number <- subtract width, 1
+      at-right?:boolean <- greater-or-equal *column, right
+      break-if at-right?
+      *column <- add *column, 1
+    }
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  print-character-to-display c, color, bg-color
+  reply sc/same-as-ingredient:0
+]
+
+scenario print-character-at-top-left [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    2:address:array:screen-cell <- get *1:address:screen, data:offset
+    3:array:screen-cell <- copy *2:address:array:screen-cell
+  ]
+  memory-should-contain [
+    3 <- 6  # width*height
+    4 <- 97  # 'a'
+    5 <- 7  # white
+    6 <- 0
+  ]
+]
+
+scenario print-character-color [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 97/a, 1/red
+    2:address:array:screen-cell <- get *1:address:screen, data:offset
+    3:array:screen-cell <- copy *2:address:array:screen-cell
+  ]
+  memory-should-contain [
+    3 <- 6  # width*height
+    4 <- 97  # 'a'
+    5 <- 1  # red
+    6 <- 0
+  ]
+]
+
+scenario print-backspace-character [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    1:address:screen <- print-character 1:address:screen, 8  # backspace
+    2:number <- get *1:address:screen, cursor-column:offset
+    3:address:array:screen-cell <- get *1:address:screen, data:offset
+    4:array:screen-cell <- copy *3:address:array:screen-cell
+  ]
+  memory-should-contain [
+    2 <- 0  # cursor column
+    4 <- 6  # width*height
+    5 <- 32  # space, not 'a'
+    6 <- 7  # white
+    7 <- 0
+  ]
+]
+
+scenario print-extra-backspace-character [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    1:address:screen <- print-character 1:address:screen, 8  # backspace
+    1:address:screen <- print-character 1:address:screen, 8  # backspace
+    2:number <- get *1:address:screen, cursor-column:offset
+    3:address:array:screen-cell <- get *1:address:screen, data:offset
+    4:array:screen-cell <- copy *3:address:array:screen-cell
+  ]
+  memory-should-contain [
+    2 <- 0  # cursor column
+    4 <- 6  # width*height
+    5 <- 32  # space, not 'a'
+    6 <- 7  # white
+    7 <- 0
+  ]
+]
+
+scenario print-at-right-margin [
+  run [
+    1:address:screen <- new-fake-screen 2/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    1:address:screen <- print-character 1:address:screen, 98  # 'b'
+    1:address:screen <- print-character 1:address:screen, 99  # 'c'
+    2:number <- get *1:address:screen, cursor-column:offset
+    3:address:array:screen-cell <- get *1:address:screen, data:offset
+    4:array:screen-cell <- copy *3:address:array:screen-cell
+  ]
+  memory-should-contain [
+    2 <- 1  # cursor column
+    4 <- 4  # width*height
+    5 <- 97  # 'a'
+    6 <- 7  # white
+    7 <- 99  # 'c' over 'b'
+    8 <- 7  # white
+    9 <- 0
+  ]
+]
+
+scenario print-newline-character [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    1:address:screen <- print-character 1:address:screen, 10/newline
+    2:number <- get *1:address:screen, cursor-row:offset
+    3:number <- get *1:address:screen, cursor-column:offset
+    4:address:array:screen-cell <- get *1:address:screen, data:offset
+    5:array:screen-cell <- copy *4:address:array:screen-cell
+  ]
+  memory-should-contain [
+    2 <- 1  # cursor row
+    3 <- 0  # cursor column
+    5 <- 6  # width*height
+    6 <- 97  # 'a'
+    7 <- 7  # white
+    8 <- 0
+  ]
+]
+
+scenario print-newline-at-bottom-line [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 10/newline
+    1:address:screen <- print-character 1:address:screen, 10/newline
+    1:address:screen <- print-character 1:address:screen, 10/newline
+    2:number <- get *1:address:screen, cursor-row:offset
+    3:number <- get *1:address:screen, cursor-column:offset
+  ]
+  memory-should-contain [
+    2 <- 1  # cursor row
+    3 <- 0  # cursor column
+  ]
+]
+
+scenario print-at-bottom-right [
+  run [
+    1:address:screen <- new-fake-screen 2/width, 2/height
+    1:address:screen <- print-character 1:address:screen, 10/newline
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    1:address:screen <- print-character 1:address:screen, 98  # 'b'
+    1:address:screen <- print-character 1:address:screen, 99  # 'c'
+    1:address:screen <- print-character 1:address:screen, 10/newline
+    1:address:screen <- print-character 1:address:screen, 100  # 'd'
+    2:number <- get *1:address:screen, cursor-row:offset
+    3:number <- get *1:address:screen, cursor-column:offset
+    4:address:array:screen-cell <- get *1:address:screen, data:offset
+    5:array:screen-cell <- copy *4:address:array:screen-cell
+  ]
+  memory-should-contain [
+    2 <- 1  # cursor row
+    3 <- 1  # cursor column
+    5 <- 4  # width*height
+    6 <- 0  # unused
+    7 <- 7  # white
+    8 <- 0  # unused
+    9 <- 7  # white
+    10 <- 97 # 'a'
+    11 <- 7  # white
+    12 <- 100  # 'd' over 'b' and 'c' and newline
+    13 <- 7  # white
+    14 <- 0
+  ]
+]
+
+recipe clear-line [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, clear line in fake screen
+  {
+    break-unless sc
+    width:number <- get *sc, num-columns:offset
+    column:address:number <- get-address *sc, cursor-column:offset
+    original-column:number <- copy *column
+    # space over the entire line
+    {
+      right:number <- subtract width, 1
+      done?:boolean <- greater-or-equal *column, right
+      break-if done?
+      print-character sc, [ ]  # implicitly updates 'column'
+      loop
+    }
+    # now back to where the cursor was
+    *column <- copy original-column
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  clear-line-on-display
+  reply sc/same-as-ingredient:0
+]
+
+recipe cursor-position [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, lookup cursor in fake screen
+  {
+    break-unless sc
+    row:number <- get *sc, cursor-row:offset
+    column:number <- get *sc, cursor-column:offset
+    reply row, column, sc/same-as-ingredient:0
+  }
+  row, column <- cursor-position-on-display
+  reply row, column, sc/same-as-ingredient:0
+]
+
+recipe move-cursor [
+  local-scope
+  sc:address:screen <- next-ingredient
+  new-row:number <- next-ingredient
+  new-column:number <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    row:address:number <- get-address *sc, cursor-row:offset
+    *row <- copy new-row
+    column:address:number <- get-address *sc, cursor-column:offset
+    *column <- copy new-column
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  move-cursor-on-display new-row, new-column
+  reply sc/same-as-ingredient:0
+]
+
+scenario clear-line-erases-printed-characters [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    # print a character
+    1:address:screen <- print-character 1:address:screen, 97  # 'a'
+    # move cursor to start of line
+    1:address:screen <- move-cursor 1:address:screen, 0/row, 0/column
+    # clear line
+    1:address:screen <- clear-line 1:address:screen
+    2:address:array:screen-cell <- get *1:address:screen, data:offset
+    3:array:screen-cell <- copy *2:address:array:screen-cell
+  ]
+  # screen should be blank
+  memory-should-contain [
+    3 <- 6  # width*height
+    4 <- 0
+    5 <- 7
+    6 <- 0
+    7 <- 7
+    8 <- 0
+    9 <- 7
+    10 <- 0
+    11 <- 7
+    12 <- 0
+    13 <- 7
+    14 <- 0
+    15 <- 7
+  ]
+]
+
+recipe cursor-down [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    {
+      # increment row unless it's already all the way down
+      height:number <- get *sc, num-rows:offset
+      row:address:number <- get-address *sc, cursor-row:offset
+      max:number <- subtract height, 1
+      at-bottom?:boolean <- greater-or-equal *row, max
+      break-if at-bottom?
+      *row <- add *row, 1
+    }
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  move-cursor-down-on-display
+  reply sc/same-as-ingredient:0
+]
+
+recipe cursor-up [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    {
+      # decrement row unless it's already all the way up
+      row:address:number <- get-address *sc, cursor-row:offset
+      at-top?:boolean <- lesser-or-equal *row, 0
+      break-if at-top?
+      *row <- subtract *row, 1
+    }
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  move-cursor-up-on-display
+  reply sc/same-as-ingredient:0
+]
+
+recipe cursor-right [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    {
+      # increment column unless it's already all the way to the right
+      width:number <- get *sc, num-columns:offset
+      column:address:number <- get-address *sc, cursor-column:offset
+      max:number <- subtract width, 1
+      at-bottom?:boolean <- greater-or-equal *column, max
+      break-if at-bottom?
+      *column <- add *column, 1
+    }
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  move-cursor-right-on-display
+  reply sc/same-as-ingredient:0
+]
+
+recipe cursor-left [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    {
+      # decrement column unless it's already all the way to the left
+      column:address:number <- get-address *sc, cursor-column:offset
+      at-top?:boolean <- lesser-or-equal *column, 0
+      break-if at-top?
+      *column <- subtract *column, 1
+    }
+    reply sc/same-as-ingredient:0
+  }
+  # otherwise, real screen
+  move-cursor-left-on-display
+  reply sc/same-as-ingredient:0
+]
+
+recipe cursor-to-start-of-line [
+  local-scope
+  sc:address:screen <- next-ingredient
+  row:number, _, sc <- cursor-position sc
+  column:number <- copy 0
+  sc <- move-cursor sc, row, column
+  reply sc/same-as-ingredient:0
+]
+
+recipe cursor-to-next-line [
+  local-scope
+  screen:address:screen <- next-ingredient
+  screen <- cursor-down screen
+  screen <- cursor-to-start-of-line screen
+  reply screen/same-as-ingredient:0
+]
+
+recipe screen-width [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    width:number <- get *sc, num-columns:offset
+    reply width
+  }
+  # otherwise, real screen
+  width:number <- display-width
+  reply width
+]
+
+recipe screen-height [
+  local-scope
+  sc:address:screen <- next-ingredient
+  # if x exists, move cursor in fake screen
+  {
+    break-unless sc
+    height:number <- get *sc, num-rows:offset
+    reply height
+  }
+  # otherwise, real screen
+  height:number <- display-height
+  reply height
+]
+
+recipe hide-cursor [
+  local-scope
+  screen:address:screen <- next-ingredient
+  # if x exists (not real display), do nothing
+  {
+    break-unless screen
+    reply screen
+  }
+  # otherwise, real screen
+  hide-cursor-on-display
+  reply screen
+]
+
+recipe show-cursor [
+  local-scope
+  screen:address:screen <- next-ingredient
+  # if x exists (not real display), do nothing
+  {
+    break-unless screen
+    reply screen
+  }
+  # otherwise, real screen
+  show-cursor-on-display
+  reply screen
+]
+
+recipe hide-screen [
+  local-scope
+  screen:address:screen <- next-ingredient
+  # if x exists (not real display), do nothing
+  # todo: help test this
+  {
+    break-unless screen
+    reply screen
+  }
+  # otherwise, real screen
+  hide-display
+  reply screen
+]
+
+recipe show-screen [
+  local-scope
+  screen:address:screen <- next-ingredient
+  # if x exists (not real display), do nothing
+  # todo: help test this
+  {
+    break-unless screen
+    reply screen
+  }
+  # otherwise, real screen
+  show-display
+  reply screen
+]
+
+recipe print-string [
+  local-scope
+  screen:address:screen <- next-ingredient
+  s:address:array:character <- next-ingredient
+  color:number, color-found?:boolean <- next-ingredient
+  {
+    # default color to white
+    break-if color-found?
+    color <- copy 7/white
+  }
+  bg-color:number, bg-color-found?:boolean <- next-ingredient
+  {
+    # default bg-color to black
+    break-if bg-color-found?
+    bg-color <- copy 0/black
+  }
+  len:number <- length *s
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    c:character <- index *s, i
+    print-character screen, c, color, bg-color
+    i <- add i, 1
+    loop
+  }
+  reply screen/same-as-ingredient:0
+]
+
+scenario print-string-stops-at-right-margin [
+  run [
+    1:address:screen <- new-fake-screen 3/width, 2/height
+    2:address:array:character <- new [abcd]
+    1:address:screen <- print-string 1:address:screen, 2:address:array:character
+    3:address:array:screen-cell <- get *1:address:screen, data:offset
+    4:array:screen-cell <- copy *3:address:array:screen-cell
+  ]
+  memory-should-contain [
+    4 <- 6  # width*height
+    5 <- 97  # 'a'
+    6 <- 7  # white
+    7 <- 98  # 'b'
+    8 <- 7  # white
+    9 <- 100  # 'd' overwrites 'c'
+    10 <- 7  # white
+    11 <- 0  # unused
+  ]
+]
+
+recipe print-integer [
+  local-scope
+  screen:address:screen <- next-ingredient
+  n:number <- next-ingredient
+  color:number, color-found?:boolean <- next-ingredient
+  {
+    # default color to white
+    break-if color-found?
+    color <- copy 7/white
+  }
+  bg-color:number, bg-color-found?:boolean <- next-ingredient
+  {
+    # default bg-color to black
+    break-if bg-color-found?
+    bg-color <- copy 0/black
+  }
+  # todo: other bases besides decimal
+  s:address:array:character <- integer-to-decimal-string n
+  print-string screen, s, color, bg-color
+  reply screen/same-as-ingredient:0
+]
+
+ + + diff --git a/html/081run_interactive.cc.html b/html/081run_interactive.cc.html deleted file mode 100644 index 3802f61a..00000000 --- a/html/081run_interactive.cc.html +++ /dev/null @@ -1,489 +0,0 @@ - - - - -Mu - 081run_interactive.cc - - - - - - - - - - -
-//: Helper for various programming environments: run arbitrary mu code and
-//: return some result in string form.
-
-:(scenario run_interactive_code)
-recipe main [
-  1:number/raw <- copy 0
-  2:address:array:character <- new [1:number/raw <- copy 34]
-  run-interactive 2:address:array:character
-  3:number/raw <- copy 1:number/raw
-]
-+mem: storing 34 in location 3
-
-:(scenario run_interactive_empty)
-recipe main [
-  1:address:array:character <- copy 0/raw
-  2:address:array:character <- run-interactive 1:address:array:character
-]
-# result is null
-+mem: storing 0 in location 2
-
-//: run code in 'interactive mode', i.e. with errors+warnings off and return:
-//:   stringified output in case we want to print it to screen
-//:   any errors+warnings encountered
-//:   simulated screen any prints went to
-//:   any 'app' layer traces generated
-:(before "End Primitive Recipe Declarations")
-RUN_INTERACTIVE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "run-interactive", RUN_INTERACTIVE);
-:(before "End Primitive Recipe Checks")
-case RUN_INTERACTIVE: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise_error << maybe(get(Recipe, r).name) << "'run-interactive' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  if (!is_mu_string(inst.ingredients.at(0))) {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'run-interactive' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RUN_INTERACTIVE: {
-  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
-  if (!new_code_pushed_to_stack) {
-    products.resize(5);
-    products.at(0).push_back(0);
-    products.at(1).push_back(trace_error_warning_contents());
-    products.at(2).push_back(0);
-    products.at(3).push_back(trace_app_contents());
-    products.at(4).push_back(1);  // completed
-    run_code_end();
-    break;  // done with this instruction
-  }
-  else {
-    continue;  // not done with caller; don't increment current_step_index()
-  }
-}
-
-:(before "End Globals")
-bool Track_most_recent_products = false;
-:(before "End Tracing")
-trace_stream* Save_trace_stream = NULL;
-string Save_trace_file;
-:(before "End Setup")
-Track_most_recent_products = false;
-:(code)
-// reads a string, tries to call it as code (treating it as a test), saving
-// all warnings.
-// returns true if successfully called (no errors found during load and transform)
-bool run_interactive(long long int address) {
-  assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
-  // try to sandbox the run as best you can
-  // todo: test this
-  if (!Current_scenario) {
-    for (long long int i = 1; i < Reserved_for_tests; ++i)
-      Memory.erase(i);
-  }
-  string command = trim(strip_comments(read_mu_string(address)));
-  if (command.empty()) return false;
-  Recipe.erase(get(Recipe_ordinal, "interactive"));
-  Name[get(Recipe_ordinal, "interactive")].clear();
-  run_code_begin();
-  // don't kill the current routine on parse errors
-  routine* save_current_routine = Current_routine;
-  Current_routine = NULL;
-  // call run(string) but without the scheduling
-  load(string("recipe interactive [\n") +
-          "local-scope\n" +
-          "screen:address:screen <- next-ingredient\n" +
-          "$start-tracking-products\n" +
-          command + "\n" +
-          "$stop-tracking-products\n" +
-          "reply screen\n" +
-       "]\n");
-  transform_all();
-  Current_routine = save_current_routine;
-  if (trace_count("error") > 0) return false;
-  // now call 'sandbox' which will run 'interactive' in a separate routine,
-  // and wait for it
-  if (Save_trace_stream) {
-    ++Save_trace_stream->callstack_depth;
-    trace(9999, "trace") << "run-interactive: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
-    assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
-  Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
-  return true;
-}
-
-void run_code_begin() {
-  // stuff to undo later, in run_code_end()
-  Hide_warnings = true;
-  Hide_errors = true;
-  Disable_redefine_warnings = true;
-  Save_trace_stream = Trace_stream;
-  Save_trace_file = Trace_file;
-  Trace_file = "";
-  Trace_stream = new trace_stream;
-  Trace_stream->collect_depth = App_depth;
-}
-
-void run_code_end() {
-  Hide_warnings = false;
-  Hide_errors = false;
-  Disable_redefine_warnings = false;
-  delete Trace_stream;
-  Trace_stream = Save_trace_stream;
-  Save_trace_stream = NULL;
-  Trace_file = Save_trace_file;
-  Save_trace_file.clear();
-}
-
-:(before "End Load Recipes")
-load(string(
-"recipe interactive [\n") +  // just a dummy version to initialize the Recipe_ordinal and so on
-"]\n" +
-"recipe sandbox [\n" +
-  "local-scope\n" +
-  "screen:address:screen/shared <- new-fake-screen 30, 5\n" +
-  "r:number/routine_id <- start-running interactive:recipe, screen\n" +
-  "limit-time r, 100000/instructions\n" +
-  "wait-for-routine r\n" +
-  "sandbox-state:number <- routine-state r/routine_id\n" +
-  "completed?:boolean <- equal sandbox-state, 1/completed\n" +
-  "output:address:array:character <- $most-recent-products\n" +
-  "warnings:address:array:character <- save-errors-warnings\n" +
-  "stashes:address:array:character <- save-app-trace\n" +
-  "$cleanup-run-interactive\n" +
-  "reply output, warnings, screen, stashes, completed?\n" +
-"]\n");
-transform_all();
-recently_added_recipes.clear();
-
-//: adjust errors/warnings in the sandbox
-:(after "string maybe(string s)")
-  if (s == "interactive") return "";
-
-:(scenario run_interactive_comments)
-recipe main [
-  1:address:array:character <- new [# ab
-add 2, 2]
-  2:address:array:character <- run-interactive 1:address:array:character
-  3:array:character <- copy *2:address:array:character
-]
-+mem: storing 52 in location 4
-
-:(before "End Primitive Recipe Declarations")
-_START_TRACKING_PRODUCTS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS);
-:(before "End Primitive Recipe Checks")
-case _START_TRACKING_PRODUCTS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _START_TRACKING_PRODUCTS: {
-  Track_most_recent_products = true;
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_STOP_TRACKING_PRODUCTS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS);
-:(before "End Primitive Recipe Checks")
-case _STOP_TRACKING_PRODUCTS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _STOP_TRACKING_PRODUCTS: {
-  Track_most_recent_products = false;
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_MOST_RECENT_PRODUCTS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS);
-:(before "End Primitive Recipe Checks")
-case _MOST_RECENT_PRODUCTS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _MOST_RECENT_PRODUCTS: {
-  products.resize(1);
-  products.at(0).push_back(new_mu_string(Most_recent_products));
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SAVE_ERRORS_WARNINGS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "save-errors-warnings", SAVE_ERRORS_WARNINGS);
-:(before "End Primitive Recipe Checks")
-case SAVE_ERRORS_WARNINGS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SAVE_ERRORS_WARNINGS: {
-  products.resize(1);
-  products.at(0).push_back(trace_error_warning_contents());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SAVE_APP_TRACE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE);
-:(before "End Primitive Recipe Checks")
-case SAVE_APP_TRACE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SAVE_APP_TRACE: {
-  products.resize(1);
-  products.at(0).push_back(trace_app_contents());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_CLEANUP_RUN_INTERACTIVE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$cleanup-run-interactive", _CLEANUP_RUN_INTERACTIVE);
-:(before "End Primitive Recipe Checks")
-case _CLEANUP_RUN_INTERACTIVE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _CLEANUP_RUN_INTERACTIVE: {
-  run_code_end();
-  break;
-}
-
-:(scenario "run_interactive_returns_stringified_result")
-recipe main [
-  # try to interactively add 2 and 2
-  1:address:array:character <- new [add 2, 2]
-  2:address:array:character <- run-interactive 1:address:array:character
-  10:array:character <- copy 2:address:array:character/lookup
-]
-# first letter in the output should be '4' in unicode
-+mem: storing 52 in location 11
-
-:(scenario "run_interactive_returns_string")
-recipe main [
-  # try to interactively add 2 and 2
-  1:address:array:character <- new [
-    x:address:array:character <- new [a]
-    y:address:array:character <- new [b]
-    z:address:array:character <- string-append x:address:array:character, y:address:array:character
-  ]
-  2:address:array:character <- run-interactive 1:address:array:character
-  10:array:character <- copy 2:address:array:character/lookup
-]
-# output contains "ab"
-+mem: storing 97 in location 11
-+mem: storing 98 in location 12
-
-:(scenario "run_interactive_returns_errors")
-recipe main [
-  # run a command that generates an error
-  1:address:array:character <- new [x:number <- copy 34
-get x:number, foo:offset]
-  2:address:array:character, 3:address:array:character <- run-interactive 1:address:array:character
-  10:array:character <- copy 3:address:array:character/lookup
-]
-# error should be "unknown element foo in container number"
-+mem: storing 117 in location 11
-+mem: storing 110 in location 12
-+mem: storing 107 in location 13
-+mem: storing 110 in location 14
-# ...
-
-:(scenario run_interactive_with_comment)
-recipe main [
-  # 2 instructions, with a comment after the first
-  1:address:array:number <- new [a:number <- copy 0  # abc
-b:number <- copy 0
-]
-  2:address:array:character, 3:address:array:character <- run-interactive 1:address:array:character
-]
-# no errors
-+mem: storing 0 in location 3
-
-:(before "End Globals")
-string Most_recent_products;
-:(before "End Setup")
-Most_recent_products = "";
-:(before "End of Instruction")
-if (Track_most_recent_products) {
-  track_most_recent_products(current_instruction(), products);
-}
-:(code)
-void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
-  ostringstream out;
-  for (long long int i = 0; i < SIZE(products); ++i) {
-    // string
-    if (i < SIZE(instruction.products)) {
-      if (is_mu_string(instruction.products.at(i))) {
-        if (!scalar(products.at(i))) {
-          tb_shutdown();
-          cerr << read_mu_string(trace_error_warning_contents()) << '\n';
-          cerr << SIZE(products.at(i)) << ": ";
-          for (long long int j = 0; j < SIZE(products.at(i)); ++j)
-            cerr << no_scientific(products.at(i).at(j)) << ' ';
-          cerr << '\n';
-        }
-        assert(scalar(products.at(i)));
-        out << read_mu_string(products.at(i).at(0)) << '\n';
-        continue;
-      }
-      // End Record Product Special-cases
-    }
-    for (long long int j = 0; j < SIZE(products.at(i)); ++j)
-      out << no_scientific(products.at(i).at(j)) << ' ';
-    out << '\n';
-  }
-  Most_recent_products = out.str();
-}
-
-:(code)
-string strip_comments(string in) {
-  ostringstream result;
-  for (long long int i = 0; i < SIZE(in); ++i) {
-    if (in.at(i) != '#') {
-      result << in.at(i);
-    }
-    else {
-      while (i+1 < SIZE(in) && in.at(i+1) != '\n')
-        ++i;
-    }
-  }
-  return result.str();
-}
-
-long long int stringified_value_of_location(long long int address) {
-  // convert to string
-  ostringstream out;
-  out << no_scientific(get_or_insert(Memory, address));
-  return new_mu_string(out.str());
-}
-
-long long int trace_error_warning_contents() {
-  if (!Trace_stream) return 0;
-  ostringstream out;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (p->depth > Warning_depth) continue;
-    out << p->contents;
-    if (*--p->contents.end() != '\n') out << '\n';
-  }
-  string result = out.str();
-  if (result.empty()) return 0;
-  truncate(result);
-  return new_mu_string(result);
-}
-
-long long int trace_app_contents() {
-  if (!Trace_stream) return 0;
-  ostringstream out;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (p->depth != App_depth) continue;
-    out << p->contents;
-    if (*--p->contents.end() != '\n') out << '\n';
-  }
-  string result = out.str();
-  if (result.empty()) return 0;
-  truncate(result);
-  return new_mu_string(result);
-}
-
-void truncate(string& x) {
-  if (SIZE(x) > 512) {
-    x.erase(512);
-    *x.rbegin() = '\n';
-    *++x.rbegin() = '.';
-    *++++x.rbegin() = '.';
-  }
-}
-
-//: simpler version of run-interactive: doesn't do any running, just loads
-//: recipes and reports errors+warnings.
-
-:(before "End Primitive Recipe Declarations")
-RELOAD,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "reload", RELOAD);
-:(before "End Primitive Recipe Checks")
-case RELOAD: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise_error << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  if (!is_mu_string(inst.ingredients.at(0))) {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RELOAD: {
-  // clear any containers in advance
-  for (long long int i = 0; i < SIZE(recently_added_types); ++i) {
-    Type_ordinal.erase(get(Type, recently_added_types.at(i)).name);
-    Type.erase(recently_added_types.at(i));
-  }
-  string code = read_mu_string(ingredients.at(0).at(0));
-  run_code_begin();
-  routine* save_current_routine = Current_routine;
-  Current_routine = NULL;
-  vector<recipe_ordinal> recipes_reloaded = load(code);
-  for (long long int i = 0; i < SIZE(recipes_reloaded); ++i) {
-    Name.erase(recipes_reloaded.at(i));
-  }
-  transform_all();
-  Trace_stream->newline();  // flush trace
-  Current_routine = save_current_routine;
-  products.resize(1);
-  products.at(0).push_back(trace_error_warning_contents());
-  run_code_end();  // wait until we're done with the trace contents
-  break;
-}
-
-:(scenario reload_continues_past_error)
-recipe main [
-  local-scope
-  x:address:array:character <- new [recipe foo [
-  get 1234:number, foo:offset
-]]
-  reload x
-  1:number/raw <- copy 34
-]
-+mem: storing 34 in location 1
-
- - - diff --git a/html/082persist.cc.html b/html/082persist.cc.html deleted file mode 100644 index 1f6ed24f..00000000 --- a/html/082persist.cc.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - -Mu - 082persist.cc - - - - - - - - - - -
-//: Dead simple persistence.
-//:   'restore' - reads string from a file
-//:   'save' - writes string to a file
-
-:(before "End Primitive Recipe Declarations")
-RESTORE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "restore", RESTORE);
-:(before "End Primitive Recipe Checks")
-case RESTORE: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise_error << maybe(get(Recipe, r).name) << "'restore' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  string filename;
-  if (is_literal_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else if (is_mu_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restore' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RESTORE: {
-  string filename;
-  if (is_literal_string(current_instruction().ingredients.at(0))) {
-    filename = current_instruction().ingredients.at(0).name;
-  }
-  else if (is_mu_string(current_instruction().ingredients.at(0))) {
-    filename = read_mu_string(ingredients.at(0).at(0));
-  }
-  if (Current_scenario) {
-    // do nothing in tests
-    products.resize(1);
-    products.at(0).push_back(0);
-    break;
-  }
-  string contents = slurp("lesson/"+filename);
-  products.resize(1);
-  if (contents.empty())
-    products.at(0).push_back(0);
-  else
-    products.at(0).push_back(new_mu_string(contents));
-  break;
-}
-
-:(code)
-string slurp(const string& filename) {
-  ostringstream result;
-  ifstream fin(filename.c_str());
-  fin.peek();
-  if (!fin) return result.str();  // don't bother checking errno
-  const int N = 1024;
-  char buf[N];
-  while (!fin.eof()) {
-    bzero(buf, N);
-    fin.read(buf, N-1);  // leave at least one null
-    result << buf;
-  }
-  fin.close();
-  return result.str();
-}
-
-:(before "End Primitive Recipe Declarations")
-SAVE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "save", SAVE);
-:(before "End Primitive Recipe Checks")
-case SAVE: {
-  if (SIZE(inst.ingredients) != 2) {
-    raise_error << maybe(get(Recipe, r).name) << "'save' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
-    break;
-  }
-  if (is_literal_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else if (is_mu_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else {
-    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'save' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
-    break;
-  }
-  if (!is_mu_string(inst.ingredients.at(1))) {
-    raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'save' should be an address:array:character, but got " << inst.ingredients.at(1).to_string() << '\n' << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SAVE: {
-  if (Current_scenario) break;  // do nothing in tests
-  string filename;
-  if (is_literal_string(current_instruction().ingredients.at(0))) {
-    filename = current_instruction().ingredients.at(0).name;
-  }
-  else if (is_mu_string(current_instruction().ingredients.at(0))) {
-    filename = read_mu_string(ingredients.at(0).at(0));
-  }
-  ofstream fout(("lesson/"+filename).c_str());
-  string contents = read_mu_string(ingredients.at(1).at(0));
-  fout << contents;
-  fout.close();
-  if (!exists("lesson/.git")) break;
-  // bug in git: git diff -q messes up --exit-code
-  // explicitly say '--all' for git 1.9
-  int status = system("cd lesson; git add --all .; git diff HEAD --exit-code >/dev/null || git commit -a -m . >/dev/null");
-  if (status != 0)
-    raise_error << "error in commit: contents " << contents << '\n' << end();
-  break;
-}
-
-:(code)
-bool exists(const string& filename) {
-  struct stat dummy;
-  return 0 == stat(filename.c_str(), &dummy);
-}
-
- - - diff --git a/html/082scenario_screen.cc.html b/html/082scenario_screen.cc.html new file mode 100644 index 00000000..037fc9ff --- /dev/null +++ b/html/082scenario_screen.cc.html @@ -0,0 +1,394 @@ + + + + +Mu - 082scenario_screen.cc + + + + + + + + + + +
+//: Clean syntax to manipulate and check the screen in scenarios.
+//: Instructions 'assume-screen' and 'screen-should-contain' implicitly create
+//: a variable called 'screen' that is accessible inside other 'run'
+//: instructions in the scenario. 'screen-should-contain' can check unicode
+//: characters in the fake screen
+
+:(scenarios run_mu_scenario)
+:(scenario screen_in_scenario)
+scenario screen-in-scenario [
+  assume-screen 5/width, 3/height
+  run [
+    screen:address:screen <- print-character screen:address:screen, 97  # 'a'
+  ]
+  screen-should-contain [
+  #  01234
+    .a    .
+    .     .
+    .     .
+  ]
+]
+
+:(scenario screen_in_scenario_unicode)
+scenario screen-in-scenario-unicode-color [
+  assume-screen 5/width, 3/height
+  run [
+    screen:address:screen <- print-character screen:address:screen, 955/greek-small-lambda, 1/red
+    screen:address:screen <- print-character screen:address:screen, 97/a
+  ]
+  screen-should-contain [
+  #  01234
+    .λa   .
+    .     .
+    .     .
+  ]
+]
+
+:(scenario screen_in_scenario_color)
+# screen-should-contain can check unicode characters in the fake screen
+scenario screen-in-scenario-color [
+  assume-screen 5/width, 3/height
+  run [
+    screen:address:screen <- print-character screen:address:screen, 955/greek-small-lambda, 1/red
+    screen:address:screen <- print-character screen:address:screen, 97/a, 7/white
+  ]
+  # screen-should-contain shows everything
+  screen-should-contain [
+  #  01234
+    .λa   .
+    .     .
+    .     .
+  ]
+  # screen-should-contain-in-color filters out everything except the given
+  # color, all you see is the 'a' in white.
+  screen-should-contain-in-color 7/white, [
+  #  01234
+    . a   .
+    .     .
+    .     .
+  ]
+  # ..and the λ in red.
+  screen-should-contain-in-color 1/red, [
+  #  01234
+    .λ    .
+    .     .
+    .     .
+  ]
+]
+
+:(scenario screen_in_scenario_error)
+% Scenario_testing_scenario = true;
+% Hide_errors = true;
+scenario screen-in-scenario-error [
+  assume-screen 5/width, 3/height
+  run [
+    screen:address:screen <- print-character screen:address:screen, 97  # 'a'
+  ]
+  screen-should-contain [
+  #  01234
+    .b    .
+    .     .
+    .     .
+  ]
+]
++error: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')
+
+:(scenario screen_in_scenario_color_error)
+% Scenario_testing_scenario = true;
+% Hide_errors = true;
+# screen-should-contain can check unicode characters in the fake screen
+scenario screen-in-scenario-color [
+  assume-screen 5/width, 3/height
+  run [
+    screen:address:screen <- print-character screen:address:screen, 97/a, 1/red
+  ]
+  screen-should-contain-in-color 2/green, [
+  #  01234
+    .a    .
+    .     .
+    .     .
+  ]
+]
++error: expected screen location (0, 0) to be in color 2 instead of 1
+
+//: allow naming just for 'screen'
+:(before "End is_special_name Cases")
+if (s == "screen") return true;
+
+:(scenarios run)
+:(scenario convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations)
+% Scenario_testing_scenario = true;
+% Hide_errors = true;
+recipe main [
+  screen:number <- copy 1:number
+]
+-error: mixing variable names and numeric addresses in main
+$error: 0
+:(scenarios run_mu_scenario)
+
+:(before "End Globals")
+// Scenarios may not define default-space, so they should fit within the
+// initial area of memory reserved for tests. We'll put the predefined
+// variables available to them at the end of that region.
+const long long int Max_variables_in_scenarios = Reserved_for_tests-100;
+long long int Next_predefined_global_for_scenarios = Max_variables_in_scenarios;
+:(before "End Setup")
+assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
+:(after "transform_all()" following "case RUN:")
+// There's a restriction on the number of variables 'run' can use, so that
+// it can avoid colliding with the dynamic allocator in case it doesn't
+// initialize a default-space.
+assert(Name[tmp_recipe.at(0)][""] < Max_variables_in_scenarios);
+
+:(before "End Globals")
+// Scenario Globals.
+const long long int SCREEN = Next_predefined_global_for_scenarios++;
+// End Scenario Globals.
+:(before "End Special Scenario Variable Names(r)")
+Name[r]["screen"] = SCREEN;
+
+:(before "End Rewrite Instruction(curr, recipe result)")
+// rewrite `assume-screen width, height` to
+// `screen:address:screen <- new-fake-screen width, height`
+if (curr.name == "assume-screen") {
+  curr.name = "new-fake-screen";
+  assert(curr.products.empty());
+  curr.products.push_back(reagent("screen:address:screen"));
+  curr.products.at(0).set_value(SCREEN);
+}
+
+//: screen-should-contain is a regular instruction
+:(before "End Primitive Recipe Declarations")
+SCREEN_SHOULD_CONTAIN,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN);
+:(before "End Primitive Recipe Checks")
+case SCREEN_SHOULD_CONTAIN: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SCREEN_SHOULD_CONTAIN: {
+  if (!Passed) break;
+  check_screen(current_instruction().ingredients.at(0).name, -1);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SCREEN_SHOULD_CONTAIN_IN_COLOR,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR);
+:(before "End Primitive Recipe Checks")
+case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
+  if (!Passed) break;
+  assert(scalar(ingredients.at(0)));
+  check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0));
+  break;
+}
+
+:(before "End Types")
+// scan an array of characters in a unicode-aware, bounds-checked manner
+struct raw_string_stream {
+  long long int index;
+  const long long int max;
+  const char* buf;
+
+  raw_string_stream(const string&);
+  uint32_t get();  // unicode codepoint
+  uint32_t peek();  // unicode codepoint
+  bool at_end() const;
+  void skip_whitespace_and_comments();
+};
+
+:(code)
+void check_screen(const string& expected_contents, const int color) {
+  assert(!current_call().default_space);  // not supported
+  long long int screen_location = get_or_insert(Memory, SCREEN);
+  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
+  assert(data_offset >= 0);
+  long long int screen_data_location = screen_location+data_offset;  // type: address:array:character
+  long long int screen_data_start = get_or_insert(Memory, screen_data_location);  // type: array:character
+  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
+  long long int screen_width = get_or_insert(Memory, screen_location+width_offset);
+  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
+  long long int screen_height = get_or_insert(Memory, screen_location+height_offset);
+  raw_string_stream cursor(expected_contents);
+  // todo: too-long expected_contents should fail
+  long long int addr = screen_data_start+1;  // skip length
+  for (long long int row = 0; row < screen_height; ++row) {
+    cursor.skip_whitespace_and_comments();
+    if (cursor.at_end()) break;
+    assert(cursor.get() == '.');
+    for (long long int column = 0;  column < screen_width;  ++column, addr+= /*size of screen-cell*/2) {
+      const int cell_color_offset = 1;
+      uint32_t curr = cursor.get();
+      if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue;
+      if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) {
+        // filter out other colors
+        continue;
+      }
+      if (get_or_insert(Memory, addr) != 0 && Memory[addr] == curr) {
+        if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue;
+        // contents match but color is off
+        if (Current_scenario && !Scenario_testing_scenario) {
+          // genuine test in a mu file
+          raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(get_or_insert(Memory, addr)) << ") to be in color " << color << " instead of " << no_scientific(Memory[addr+cell_color_offset]) << "\n" << end();
+        }
+        else {
+          // just testing check_screen
+          raise_error << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << '\n' << end();
+        }
+        if (!Scenario_testing_scenario) {
+          Passed = false;
+          ++Num_failures;
+        }
+        return;
+      }
+
+      // really a mismatch
+      // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway.
+      char expected_pretty[10] = {0};
+      if (curr < 256 && !iscntrl(curr)) {
+        // " ('<curr>')"
+        expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0';
+      }
+      char actual_pretty[10] = {0};
+      if (get_or_insert(Memory, addr) < 256 && !iscntrl(Memory[addr])) {
+        // " ('<curr>')"
+        actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(get_or_insert(Memory, addr)), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0';
+      }
+
+      ostringstream color_phrase;
+      if (color != -1) color_phrase << " in color " << color;
+      if (Current_scenario && !Scenario_testing_scenario) {
+        // genuine test in a mu file
+        raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
+        dump_screen();
+      }
+      else {
+        // just testing check_screen
+        raise_error << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
+      }
+      if (!Scenario_testing_scenario) {
+        Passed = false;
+        ++Num_failures;
+      }
+      return;
+    }
+    assert(cursor.get() == '.');
+  }
+  cursor.skip_whitespace_and_comments();
+  assert(cursor.at_end());
+}
+
+raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {}
+
+bool raw_string_stream::at_end() const {
+  if (index >= max) return true;
+  if (tb_utf8_char_length(buf[index]) > max-index) {
+    raise_error << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
+    return true;
+  }
+  return false;
+}
+
+uint32_t raw_string_stream::get() {
+  assert(index < max);  // caller must check bounds before calling 'get'
+  uint32_t result = 0;
+  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
+  assert(length != TB_EOF);
+  index += length;
+  return result;
+}
+
+uint32_t raw_string_stream::peek() {
+  assert(index < max);  // caller must check bounds before calling 'get'
+  uint32_t result = 0;
+  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
+  assert(length != TB_EOF);
+  return result;
+}
+
+void raw_string_stream::skip_whitespace_and_comments() {
+  while (!at_end()) {
+    if (isspace(peek())) get();
+    else if (peek() == '#') {
+      // skip comment
+      get();
+      while (peek() != '\n') get();  // implicitly also handles CRLF
+    }
+    else break;
+  }
+}
+
+:(before "End Primitive Recipe Declarations")
+_DUMP_SCREEN,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN);
+:(before "End Primitive Recipe Checks")
+case _DUMP_SCREEN: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _DUMP_SCREEN: {
+  dump_screen();
+  break;
+}
+
+:(code)
+void dump_screen() {
+  assert(!current_call().default_space);  // not supported
+  long long int screen_location = get_or_insert(Memory, SCREEN);
+  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
+  long long int screen_width = get_or_insert(Memory, screen_location+width_offset);
+  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
+  long long int screen_height = get_or_insert(Memory, screen_location+height_offset);
+  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
+  assert(data_offset >= 0);
+  long long int screen_data_location = screen_location+data_offset;  // type: address:array:character
+  long long int screen_data_start = get_or_insert(Memory, screen_data_location);  // type: array:character
+  assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
+  long long int curr = screen_data_start+1;  // skip length
+  for (long long int row = 0; row < screen_height; ++row) {
+    cerr << '.';
+    for (long long int col = 0; col < screen_width; ++col) {
+      if (get_or_insert(Memory, curr))
+        cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr)));
+      else
+        cerr << ' ';
+      curr += /*size of screen-cell*/2;
+    }
+    cerr << ".\n";
+  }
+}
+
+ + + diff --git a/html/083scenario_screen_test.mu.html b/html/083scenario_screen_test.mu.html new file mode 100644 index 00000000..a93c7c20 --- /dev/null +++ b/html/083scenario_screen_test.mu.html @@ -0,0 +1,63 @@ + + + + +Mu - 083scenario_screen_test.mu + + + + + + + + + + +
+# To check our support for screens in scenarios, rewrite tests from print.mu
+
+scenario print-character-at-top-left-2 [
+  assume-screen 3/width, 2/height
+  run [
+    screen:address:screen <- print-character screen:address:screen, 97/a
+  ]
+  screen-should-contain [
+    .a  .
+    .   .
+  ]
+]
+
+scenario clear-line-erases-printed-characters-2 [
+  assume-screen 5/width, 3/height
+  run [
+    # print a character
+    screen:address:screen <- print-character screen:address:screen, 97/a
+    # move cursor to start of line
+    screen:address:screen <- move-cursor screen:address:screen, 0/row, 0/column
+    # clear line
+    screen:address:screen <- clear-line screen:address:screen
+  ]
+  screen-should-contain [
+    .     .
+    .     .
+    .     .
+  ]
+]
+
+ + + diff --git a/html/084console.mu.html b/html/084console.mu.html new file mode 100644 index 00000000..c91544f9 --- /dev/null +++ b/html/084console.mu.html @@ -0,0 +1,150 @@ + + + + +Mu - 084console.mu + + + + + + + + + + +
+# Wrappers around interaction primitives that take a potentially fake object
+# and are thus easier to test.
+
+exclusive-container event [
+  text:character
+  keycode:number  # keys on keyboard without a unicode representation
+  touch:touch-event  # mouse, track ball, etc.
+  resize:resize-event
+  # update the assume-console handler if you add more variants
+]
+
+container touch-event [
+  type:number
+  row:number
+  column:number
+]
+
+container resize-event [
+  width:number
+  height:number
+]
+
+container console [
+  index:number
+  data:address:array:event
+]
+
+recipe new-fake-console [
+  local-scope
+  result:address:console <- new console:type
+  buf:address:address:array:event <- get-address *result, data:offset
+  *buf <- next-ingredient
+  idx:address:number <- get-address *result, index:offset
+  *idx <- copy 0
+  reply result
+]
+
+recipe read-event [
+  local-scope
+  x:address:console <- next-ingredient
+  {
+    break-unless x
+    idx:address:number <- get-address *x, index:offset
+    buf:address:array:event <- get *x, data:offset
+    {
+      max:number <- length *buf
+      done?:boolean <- greater-or-equal *idx, max
+      break-unless done?
+      dummy:address:event <- new event:type
+      reply *dummy, x/same-as-ingredient:0, 1/found, 1/quit
+    }
+    result:event <- index *buf, *idx
+    *idx <- add *idx, 1
+    reply result, x/same-as-ingredient:0, 1/found, 0/quit
+  }
+  switch  # real event source is infrequent; avoid polling it too much
+  result:event, found?:boolean <- check-for-interaction
+  reply result, x/same-as-ingredient:0, found?, 0/quit
+]
+
+# variant of read-event for just keyboard events. Discards everything that
+# isn't unicode, so no arrow keys, page-up/page-down, etc. But you still get
+# newlines, tabs, ctrl-d..
+recipe read-key [
+  local-scope
+  console:address:console <- next-ingredient
+  x:event, console, found?:boolean, quit?:boolean <- read-event console
+  reply-if quit?, 0, console/same-as-ingredient:0, found?, quit?
+  reply-unless found?, 0, console/same-as-ingredient:0, found?, quit?
+  c:address:character <- maybe-convert x, text:variant
+  reply-unless c, 0, console/same-as-ingredient:0, 0/found, 0/quit
+  reply *c, console/same-as-ingredient:0, 1/found, 0/quit
+]
+
+recipe send-keys-to-channel [
+  local-scope
+  console:address:console <- next-ingredient
+  chan:address:channel <- next-ingredient
+  screen:address:screen <- next-ingredient
+  {
+    c:character, console, found?:boolean, quit?:boolean <- read-key console
+    loop-unless found?
+    break-if quit?
+    assert c, [invalid event, expected text]
+    screen <- print-character screen, c
+    chan <- write chan, c
+    loop
+  }
+  reply console/same-as-ingredient:0, chan/same-as-ingredient:1, screen/same-as-ingredient:2
+]
+
+recipe wait-for-event [
+  local-scope
+  console:address:console <- next-ingredient
+  {
+    _, console, found?:boolean <- read-event console
+    loop-unless found?
+  }
+  reply console/same-as-ingredient:0
+]
+
+# use this helper to skip rendering if there's lots of other events queued up
+recipe has-more-events? [
+  local-scope
+  console:address:console <- next-ingredient
+  {
+    break-unless console
+    # fake consoles should be plenty fast; never skip
+    reply 0/false
+  }
+  result:boolean <- interactions-left?
+  reply result
+]
+
+ + + diff --git a/html/085scenario_console.cc.html b/html/085scenario_console.cc.html new file mode 100644 index 00000000..061712fa --- /dev/null +++ b/html/085scenario_console.cc.html @@ -0,0 +1,323 @@ + + + + +Mu - 085scenario_console.cc + + + + + + + + + + +
+//: Clean syntax to manipulate and check the console in scenarios.
+//: Instruction 'assume-console' implicitly creates a variable called
+//: 'console' that is accessible inside other 'run' instructions in the
+//: scenario. Like with the fake screen, 'assume-console' transparently
+//: supports unicode.
+
+:(scenarios run_mu_scenario)
+:(scenario keyboard_in_scenario)
+scenario keyboard-in-scenario [
+  assume-console [
+    type [abc]
+  ]
+  run [
+    1:character, console:address:console, 2:boolean <- read-key console:address:console
+    3:character, console:address:console, 4:boolean <- read-key console:address:console
+    5:character, console:address:console, 6:boolean <- read-key console:address:console
+    7:character, console:address:console, 8:boolean, 9:boolean <- read-key console:address:console
+  ]
+  memory-should-contain [
+    1 <- 97  # 'a'
+    2 <- 1
+    3 <- 98  # 'b'
+    4 <- 1
+    5 <- 99  # 'c'
+    6 <- 1
+    7 <- 0  # unset
+    8 <- 1
+    9 <- 1  # end of test events
+  ]
+]
+
+:(before "End Scenario Globals")
+const long long int CONSOLE = Next_predefined_global_for_scenarios++;
+:(before "End Special Scenario Variable Names(r)")
+Name[r]["console"] = CONSOLE;
+
+//: allow naming just for 'console'
+:(before "End is_special_name Cases")
+if (s == "console") return true;
+
+:(before "End Primitive Recipe Declarations")
+ASSUME_CONSOLE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "assume-console", ASSUME_CONSOLE);
+:(before "End Primitive Recipe Checks")
+case ASSUME_CONSOLE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case ASSUME_CONSOLE: {
+  // create a temporary recipe just for parsing; it won't contain valid instructions
+  istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
+  recipe r;
+  slurp_body(in, r);
+  long long int num_events = count_events(r);
+  // initialize the events like in new-fake-console
+  long long int size = num_events*size_of_event() + /*space for length*/1;
+  ensure_space(size);
+  long long int event_data_address = Current_routine->alloc;
+  put(Memory, event_data_address, num_events);
+  ++Current_routine->alloc;
+  for (long long int i = 0; i < SIZE(r.steps); ++i) {
+    const instruction& curr = r.steps.at(i);
+    if (curr.name == "left-click") {
+      put(Memory, Current_routine->alloc, /*tag for 'touch-event' variant of 'event' exclusive-container*/2);
+      put(Memory, Current_routine->alloc+1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT);
+      put(Memory, Current_routine->alloc+1+/*offset of 'row' in 'mouse-event'*/1, to_integer(curr.ingredients.at(0).name));
+      put(Memory, Current_routine->alloc+1+/*offset of 'column' in 'mouse-event'*/2, to_integer(curr.ingredients.at(1).name));
+      Current_routine->alloc += size_of_event();
+    }
+    else if (curr.name == "press") {
+      string key = curr.ingredients.at(0).name;
+      if (is_integer(key))
+        put(Memory, Current_routine->alloc+1, to_integer(key));
+      else if (contains_key(Key, key))
+        put(Memory, Current_routine->alloc+1, Key[key]);
+      else
+        raise_error << "assume-console: can't press " << key << '\n' << end();
+      if (get_or_insert(Memory, Current_routine->alloc+1) < 256)
+        // these keys are in ascii
+        put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0);
+      else {
+        // distinguish from unicode
+        put(Memory, Current_routine->alloc, /*tag for 'keycode' variant of 'event' exclusive-container*/1);
+      }
+      Current_routine->alloc += size_of_event();
+    }
+    // End Event Handlers
+    else {
+      // keyboard input
+      assert(curr.name == "type");
+      const string& contents = curr.ingredients.at(0).name;
+      const char* raw_contents = contents.c_str();
+      long long int num_keyboard_events = unicode_length(contents);
+      long long int curr = 0;
+      for (long long int i = 0; i < num_keyboard_events; ++i) {
+        put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0);
+        uint32_t curr_character;
+        assert(curr < SIZE(contents));
+        tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
+        put(Memory, Current_routine->alloc+/*skip exclusive container tag*/1, curr_character);
+        curr += tb_utf8_char_length(raw_contents[curr]);
+        Current_routine->alloc += size_of_event();
+      }
+    }
+  }
+  assert(Current_routine->alloc == event_data_address+size);
+  // wrap the array of events in a console object
+  ensure_space(size_of_console());
+  put(Memory, CONSOLE, Current_routine->alloc);
+  Current_routine->alloc += size_of_console();
+  long long int console_address = get_or_insert(Memory, CONSOLE);
+  put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address);
+  break;
+}
+
+:(before "End Globals")
+map<string, long long int> Key;
+:(before "End One-time Setup")
+initialize_key_names();
+:(code)
+void initialize_key_names() {
+  Key["F1"] = TB_KEY_F1;
+  Key["F2"] = TB_KEY_F2;
+  Key["F3"] = TB_KEY_F3;
+  Key["F4"] = TB_KEY_F4;
+  Key["F5"] = TB_KEY_F5;
+  Key["F6"] = TB_KEY_F6;
+  Key["F7"] = TB_KEY_F7;
+  Key["F8"] = TB_KEY_F8;
+  Key["F9"] = TB_KEY_F9;
+  Key["F10"] = TB_KEY_F10;
+  Key["F11"] = TB_KEY_F11;
+  Key["F12"] = TB_KEY_F12;
+  Key["insert"] = TB_KEY_INSERT;
+  Key["delete"] = TB_KEY_DELETE;
+  Key["home"] = TB_KEY_HOME;
+  Key["end"] = TB_KEY_END;
+  Key["page-up"] = TB_KEY_PGUP;
+  Key["page-down"] = TB_KEY_PGDN;
+  Key["up-arrow"] = TB_KEY_ARROW_UP;
+  Key["down-arrow"] = TB_KEY_ARROW_DOWN;
+  Key["left-arrow"] = TB_KEY_ARROW_LEFT;
+  Key["right-arrow"] = TB_KEY_ARROW_RIGHT;
+  Key["ctrl-a"] = TB_KEY_CTRL_A;
+  Key["ctrl-b"] = TB_KEY_CTRL_B;
+  Key["ctrl-c"] = TB_KEY_CTRL_C;
+  Key["ctrl-d"] = TB_KEY_CTRL_D;
+  Key["ctrl-e"] = TB_KEY_CTRL_E;
+  Key["ctrl-f"] = TB_KEY_CTRL_F;
+  Key["ctrl-g"] = TB_KEY_CTRL_G;
+  Key["backspace"] = TB_KEY_BACKSPACE;
+  Key["ctrl-h"] = TB_KEY_CTRL_H;
+  Key["tab"] = TB_KEY_TAB;
+  Key["ctrl-i"] = TB_KEY_CTRL_I;
+  Key["ctrl-j"] = TB_KEY_CTRL_J;
+  Key["enter"] = TB_KEY_NEWLINE;  // ignore CR/LF distinction; there is only 'enter'
+  Key["ctrl-k"] = TB_KEY_CTRL_K;
+  Key["ctrl-l"] = TB_KEY_CTRL_L;
+  Key["ctrl-m"] = TB_KEY_CTRL_M;
+  Key["ctrl-n"] = TB_KEY_CTRL_N;
+  Key["ctrl-o"] = TB_KEY_CTRL_O;
+  Key["ctrl-p"] = TB_KEY_CTRL_P;
+  Key["ctrl-q"] = TB_KEY_CTRL_Q;
+  Key["ctrl-r"] = TB_KEY_CTRL_R;
+  Key["ctrl-s"] = TB_KEY_CTRL_S;
+  Key["ctrl-t"] = TB_KEY_CTRL_T;
+  Key["ctrl-u"] = TB_KEY_CTRL_U;
+  Key["ctrl-v"] = TB_KEY_CTRL_V;
+  Key["ctrl-w"] = TB_KEY_CTRL_W;
+  Key["ctrl-x"] = TB_KEY_CTRL_X;
+  Key["ctrl-y"] = TB_KEY_CTRL_Y;
+  Key["ctrl-z"] = TB_KEY_CTRL_Z;
+  Key["escape"] = TB_KEY_ESC;
+}
+
+:(scenario events_in_scenario)
+scenario events-in-scenario [
+  assume-console [
+    type [abc]
+    left-click 0, 1
+    press up-arrow
+    type [d]
+  ]
+  run [
+    # 3 keyboard events; each event occupies 4 locations
+    1:event <- read-event console:address:console
+    5:event <- read-event console:address:console
+    9:event <- read-event console:address:console
+    # mouse click
+    13:event <- read-event console:address:console
+    # non-character keycode
+    17:event <- read-event console:address:console
+    # final keyboard event
+    21:event <- read-event console:address:console
+  ]
+  memory-should-contain [
+    1 <- 0  # 'text'
+    2 <- 97  # 'a'
+    3 <- 0  # unused
+    4 <- 0  # unused
+    5 <- 0  # 'text'
+    6 <- 98  # 'b'
+    7 <- 0  # unused
+    8 <- 0  # unused
+    9 <- 0  # 'text'
+    10 <- 99  # 'c'
+    11 <- 0  # unused
+    12 <- 0  # unused
+    13 <- 2  # 'mouse'
+    14 <- 65513  # mouse click
+    15 <- 0  # row
+    16 <- 1  # column
+    17 <- 1  # 'keycode'
+    18 <- 65517  # up arrow
+    19 <- 0  # unused
+    20 <- 0  # unused
+    21 <- 0  # 'text'
+    22 <- 100  # 'd'
+    23 <- 0  # unused
+    24 <- 0  # unused
+    25 <- 0
+  ]
+]
+
+//: Deal with special keys and unmatched brackets by allowing each test to
+//: independently choose the unicode symbol to denote them.
+:(before "End Primitive Recipe Declarations")
+REPLACE_IN_CONSOLE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE);
+:(before "End Primitive Recipe Checks")
+case REPLACE_IN_CONSOLE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case REPLACE_IN_CONSOLE: {
+  assert(scalar(ingredients.at(0)));
+  if (!get_or_insert(Memory, CONSOLE)) {
+    raise_error << "console not initialized\n" << end();
+    break;
+  }
+  long long int console_address = get_or_insert(Memory, CONSOLE);
+  long long int console_data = get_or_insert(Memory, console_address+1);
+  long long int size = get_or_insert(Memory, console_data);  // array size
+  for (long long int i = 0, curr = console_data+1; i < size; ++i, curr+=size_of_event()) {
+    if (get_or_insert(Memory, curr) != /*text*/0) continue;
+    if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue;
+    for (long long int n = 0; n < size_of_event(); ++n)
+      put(Memory, curr+n, ingredients.at(1).at(n));
+  }
+  break;
+}
+
+:(code)
+long long int count_events(const recipe& r) {
+  long long int result = 0;
+  for (long long int i = 0; i < SIZE(r.steps); ++i) {
+    const instruction& curr = r.steps.at(i);
+    if (curr.name == "type")
+      result += unicode_length(curr.ingredients.at(0).name);
+    else
+      result++;
+  }
+  return result;
+}
+
+long long int size_of_event() {
+  // memoize result if already computed
+  static long long int result = 0;
+  if (result) return result;
+  type_tree* type = new type_tree(get(Type_ordinal, "event"));
+  result = size_of(type);
+  delete type;
+  return result;
+}
+
+long long int size_of_console() {
+  // memoize result if already computed
+  static long long int result = 0;
+  if (result) return result;
+  assert(get(Type_ordinal, "console"));
+  type_tree* type = new type_tree(get(Type_ordinal, "console"));
+  result = size_of(type);
+  delete type;
+  return result;
+}
+
+ + + diff --git a/html/086scenario_console_test.mu.html b/html/086scenario_console_test.mu.html new file mode 100644 index 00000000..8f0c8685 --- /dev/null +++ b/html/086scenario_console_test.mu.html @@ -0,0 +1,59 @@ + + + + +Mu - 086scenario_console_test.mu + + + + + + + + + + +
+# To check our support for consoles in scenarios, rewrite tests from
+# scenario_console.mu
+# Tests for console interface.
+
+scenario read-key-in-mu [
+  assume-console [
+    type [abc]
+  ]
+  run [
+    1:character, console:address:console, 2:boolean <- read-key console:address:console
+    3:character, console:address:console, 4:boolean <- read-key console:address:console
+    5:character, console:address:console, 6:boolean <- read-key console:address:console
+    7:character, console:address:console, 8:boolean <- read-key console:address:console
+  ]
+  memory-should-contain [
+    1 <- 97  # 'a'
+    2 <- 1
+    3 <- 98  # 'b'
+    4 <- 1
+    5 <- 99  # 'c'
+    6 <- 1
+    7 <- 0  # eof
+    8 <- 1
+  ]
+]
+
+ + + diff --git a/html/090trace_browser.cc.html b/html/090trace_browser.cc.html new file mode 100644 index 00000000..62aa2fbe --- /dev/null +++ b/html/090trace_browser.cc.html @@ -0,0 +1,248 @@ + + + + +Mu - 090trace_browser.cc + + + + + + + + + + +
+:(before "End Primitive Recipe Declarations")
+_BROWSE_TRACE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
+:(before "End Primitive Recipe Checks")
+case _BROWSE_TRACE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _BROWSE_TRACE: {
+  start_trace_browser();
+  break;
+}
+
+:(before "End Globals")
+set<long long int> Visible;
+long long int Top_of_screen = 0;
+long long int Last_printed_row = 0;
+map<int, long long int> Trace_index;  // screen row -> trace index
+
+:(code)
+void start_trace_browser() {
+  if (!Trace_stream) return;
+  cerr << "computing depth to display\n";
+  long long int min_depth = 9999;
+  for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
+    trace_line& curr_line = Trace_stream->past_lines.at(i);
+    if (curr_line.depth == 0) continue;
+    if (curr_line.depth < min_depth) min_depth = curr_line.depth;
+  }
+  cerr << "depth is " << min_depth << '\n';
+  cerr << "computing lines to display\n";
+  for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
+    if (Trace_stream->past_lines.at(i).depth == min_depth)
+      Visible.insert(i);
+  }
+  tb_init();
+  Display_row = Display_column = 0;
+  tb_event event;
+  Top_of_screen = 0;
+  refresh_screen_rows();
+  while (true) {
+    render();
+    do {
+      tb_poll_event(&event);
+    } while (event.type != TB_EVENT_KEY);
+    long long int key = event.key ? event.key : event.ch;
+    if (key == 'q' || key == 'Q') break;
+    if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
+      // move cursor one line down
+      if (Display_row < Last_printed_row) ++Display_row;
+    }
+    if (key == 'k' || key == TB_KEY_ARROW_UP) {
+      // move cursor one line up
+      if (Display_row > 0) --Display_row;
+    }
+    if (key == 'H') {
+      // move cursor to top of screen
+      Display_row = 0;
+    }
+    if (key == 'M') {
+      // move cursor to center of screen
+      Display_row = tb_height()/2;
+    }
+    if (key == 'L') {
+      // move cursor to bottom of screen
+      Display_row = tb_height()-1;
+    }
+    if (key == 'J' || key == TB_KEY_PGDN) {
+      // page-down
+      if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
+        Top_of_screen = Trace_index[tb_height()-1]+1;
+        refresh_screen_rows();
+      }
+    }
+    if (key == 'K' || key == TB_KEY_PGUP) {
+      // page-up is more convoluted
+      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
+        --Top_of_screen;
+        if (Top_of_screen <= 0) break;
+        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
+          --Top_of_screen;
+      }
+      if (Top_of_screen > 0)
+        refresh_screen_rows();
+    }
+    if (key == 'G') {
+      // go to bottom of screen; largely like page-up, interestingly
+      Top_of_screen = SIZE(Trace_stream->past_lines)-1;
+      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
+        --Top_of_screen;
+        if (Top_of_screen <= 0) break;
+        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
+          --Top_of_screen;
+      }
+      refresh_screen_rows();
+      // move cursor to bottom
+      Display_row = Last_printed_row;
+      refresh_screen_rows();
+    }
+    if (key == TB_KEY_CARRIAGE_RETURN) {
+      // expand lines under current by one level
+      assert(contains_key(Trace_index, Display_row));
+      long long int start_index = Trace_index[Display_row];
+      long long int index = 0;
+      // simultaneously compute end_index and min_depth
+      int min_depth = 9999;
+      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
+        if (contains_key(Visible, index)) break;
+        trace_line& curr_line = Trace_stream->past_lines.at(index);
+        if (curr_line.depth == 0) continue;
+        assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
+        if (curr_line.depth < min_depth) min_depth = curr_line.depth;
+      }
+      long long int end_index = index;
+      // mark as visible all intervening indices at min_depth
+      for (index = start_index; index < end_index; ++index) {
+        trace_line& curr_line = Trace_stream->past_lines.at(index);
+        if (curr_line.depth == min_depth) {
+          Visible.insert(index);
+        }
+      }
+      refresh_screen_rows();
+    }
+    if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
+      // collapse all lines under current
+      assert(contains_key(Trace_index, Display_row));
+      long long int start_index = Trace_index[Display_row];
+      long long int index = 0;
+      // end_index is the next line at a depth same as or lower than start_index
+      int initial_depth = Trace_stream->past_lines.at(start_index).depth;
+      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
+        if (!contains_key(Visible, index)) continue;
+        trace_line& curr_line = Trace_stream->past_lines.at(index);
+        if (curr_line.depth == 0) continue;
+        if (curr_line.depth <= initial_depth) break;
+      }
+      long long int end_index = index;
+      // mark as visible all intervening indices at min_depth
+      for (index = start_index+1; index < end_index; ++index) {
+        Visible.erase(index);
+      }
+      refresh_screen_rows();
+    }
+  }
+  tb_shutdown();
+}
+
+// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
+void refresh_screen_rows() {
+  long long int screen_row = 0, index = 0;
+  Trace_index.clear();
+  for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
+    // skip lines without depth for now
+    while (!contains_key(Visible, index)) {
+      ++index;
+      if (index >= SIZE(Trace_stream->past_lines)) goto done;
+    }
+    assert(index < SIZE(Trace_stream->past_lines));
+    Trace_index[screen_row] = index;
+  }
+done:;
+}
+
+void render() {
+  long long int screen_row = 0;
+  for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
+    if (!contains_key(Trace_index, screen_row)) break;
+    trace_line& curr_line = Trace_stream->past_lines.at(Trace_index[screen_row]);
+    ostringstream out;
+    out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
+    if (screen_row < tb_height()-1) {
+      long long int delta = lines_hidden(screen_row);
+      // home-brew escape sequence for red
+      if (delta > 999) out << "{";
+      out << " (" << lines_hidden(screen_row) << ")";
+      if (delta > 999) out << "}";
+    }
+    render_line(screen_row, out.str());
+  }
+  // clear rest of screen
+  Last_printed_row = screen_row-1;
+  for (; screen_row < tb_height(); ++screen_row) {
+    render_line(screen_row, "~");
+  }
+  // move cursor back to display row at the end
+  tb_set_cursor(0, Display_row);
+  tb_present();
+}
+
+long long int lines_hidden(long long int screen_row) {
+  assert(contains_key(Trace_index, screen_row));
+  if (!contains_key(Trace_index, screen_row+1))
+    return SIZE(Trace_stream->past_lines)-Trace_index[screen_row];
+  else
+    return Trace_index[screen_row+1] - Trace_index[screen_row];
+}
+
+void render_line(int screen_row, const string& s) {
+  long long int col = 0;
+  int color = TB_WHITE;
+  for (col = 0; col < tb_width() && col < SIZE(s); ++col) {
+    char c = s.at(col);  // todo: unicode
+    if (c == '\n') c = ';';  // replace newlines with semi-colons
+    // escapes. hack: can't start a line with them.
+    if (c == '{') { color = /*red*/1; c = ' '; }
+    if (c == '}') { color = TB_WHITE; c = ' '; }
+    tb_change_cell(col, screen_row, c, color, TB_BLACK);
+  }
+  for (; col < tb_width(); ++col) {
+    tb_change_cell(col, screen_row, ' ', TB_WHITE, TB_BLACK);
+  }
+}
+
+ + + diff --git a/html/091run_interactive.cc.html b/html/091run_interactive.cc.html new file mode 100644 index 00000000..30490309 --- /dev/null +++ b/html/091run_interactive.cc.html @@ -0,0 +1,489 @@ + + + + +Mu - 091run_interactive.cc + + + + + + + + + + +
+//: Helper for various programming environments: run arbitrary mu code and
+//: return some result in string form.
+
+:(scenario run_interactive_code)
+recipe main [
+  1:number/raw <- copy 0
+  2:address:array:character <- new [1:number/raw <- copy 34]
+  run-interactive 2:address:array:character
+  3:number/raw <- copy 1:number/raw
+]
++mem: storing 34 in location 3
+
+:(scenario run_interactive_empty)
+recipe main [
+  1:address:array:character <- copy 0/raw
+  2:address:array:character <- run-interactive 1:address:array:character
+]
+# result is null
++mem: storing 0 in location 2
+
+//: run code in 'interactive mode', i.e. with errors+warnings off and return:
+//:   stringified output in case we want to print it to screen
+//:   any errors+warnings encountered
+//:   simulated screen any prints went to
+//:   any 'app' layer traces generated
+:(before "End Primitive Recipe Declarations")
+RUN_INTERACTIVE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "run-interactive", RUN_INTERACTIVE);
+:(before "End Primitive Recipe Checks")
+case RUN_INTERACTIVE: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise_error << maybe(get(Recipe, r).name) << "'run-interactive' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_string(inst.ingredients.at(0))) {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'run-interactive' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RUN_INTERACTIVE: {
+  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
+  if (!new_code_pushed_to_stack) {
+    products.resize(5);
+    products.at(0).push_back(0);
+    products.at(1).push_back(trace_error_warning_contents());
+    products.at(2).push_back(0);
+    products.at(3).push_back(trace_app_contents());
+    products.at(4).push_back(1);  // completed
+    run_code_end();
+    break;  // done with this instruction
+  }
+  else {
+    continue;  // not done with caller; don't increment current_step_index()
+  }
+}
+
+:(before "End Globals")
+bool Track_most_recent_products = false;
+:(before "End Tracing")
+trace_stream* Save_trace_stream = NULL;
+string Save_trace_file;
+:(before "End Setup")
+Track_most_recent_products = false;
+:(code)
+// reads a string, tries to call it as code (treating it as a test), saving
+// all warnings.
+// returns true if successfully called (no errors found during load and transform)
+bool run_interactive(long long int address) {
+  assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
+  // try to sandbox the run as best you can
+  // todo: test this
+  if (!Current_scenario) {
+    for (long long int i = 1; i < Reserved_for_tests; ++i)
+      Memory.erase(i);
+  }
+  string command = trim(strip_comments(read_mu_string(address)));
+  if (command.empty()) return false;
+  Recipe.erase(get(Recipe_ordinal, "interactive"));
+  Name[get(Recipe_ordinal, "interactive")].clear();
+  run_code_begin();
+  // don't kill the current routine on parse errors
+  routine* save_current_routine = Current_routine;
+  Current_routine = NULL;
+  // call run(string) but without the scheduling
+  load(string("recipe interactive [\n") +
+          "local-scope\n" +
+          "screen:address:screen <- next-ingredient\n" +
+          "$start-tracking-products\n" +
+          command + "\n" +
+          "$stop-tracking-products\n" +
+          "reply screen\n" +
+       "]\n");
+  transform_all();
+  Current_routine = save_current_routine;
+  if (trace_count("error") > 0) return false;
+  // now call 'sandbox' which will run 'interactive' in a separate routine,
+  // and wait for it
+  if (Save_trace_stream) {
+    ++Save_trace_stream->callstack_depth;
+    trace(9999, "trace") << "run-interactive: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
+    assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
+  }
+  Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
+  return true;
+}
+
+void run_code_begin() {
+  // stuff to undo later, in run_code_end()
+  Hide_warnings = true;
+  Hide_errors = true;
+  Disable_redefine_warnings = true;
+  Save_trace_stream = Trace_stream;
+  Save_trace_file = Trace_file;
+  Trace_file = "";
+  Trace_stream = new trace_stream;
+  Trace_stream->collect_depth = App_depth;
+}
+
+void run_code_end() {
+  Hide_warnings = false;
+  Hide_errors = false;
+  Disable_redefine_warnings = false;
+  delete Trace_stream;
+  Trace_stream = Save_trace_stream;
+  Save_trace_stream = NULL;
+  Trace_file = Save_trace_file;
+  Save_trace_file.clear();
+}
+
+:(before "End Load Recipes")
+load(string(
+"recipe interactive [\n") +  // just a dummy version to initialize the Recipe_ordinal and so on
+"]\n" +
+"recipe sandbox [\n" +
+  "local-scope\n" +
+  "screen:address:screen/shared <- new-fake-screen 30, 5\n" +
+  "r:number/routine_id <- start-running interactive:recipe, screen\n" +
+  "limit-time r, 100000/instructions\n" +
+  "wait-for-routine r\n" +
+  "sandbox-state:number <- routine-state r/routine_id\n" +
+  "completed?:boolean <- equal sandbox-state, 1/completed\n" +
+  "output:address:array:character <- $most-recent-products\n" +
+  "warnings:address:array:character <- save-errors-warnings\n" +
+  "stashes:address:array:character <- save-app-trace\n" +
+  "$cleanup-run-interactive\n" +
+  "reply output, warnings, screen, stashes, completed?\n" +
+"]\n");
+transform_all();
+recently_added_recipes.clear();
+
+//: adjust errors/warnings in the sandbox
+:(after "string maybe(string s)")
+  if (s == "interactive") return "";
+
+:(scenario run_interactive_comments)
+recipe main [
+  1:address:array:character <- new [# ab
+add 2, 2]
+  2:address:array:character <- run-interactive 1:address:array:character
+  3:array:character <- copy *2:address:array:character
+]
++mem: storing 52 in location 4
+
+:(before "End Primitive Recipe Declarations")
+_START_TRACKING_PRODUCTS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS);
+:(before "End Primitive Recipe Checks")
+case _START_TRACKING_PRODUCTS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _START_TRACKING_PRODUCTS: {
+  Track_most_recent_products = true;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+_STOP_TRACKING_PRODUCTS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS);
+:(before "End Primitive Recipe Checks")
+case _STOP_TRACKING_PRODUCTS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _STOP_TRACKING_PRODUCTS: {
+  Track_most_recent_products = false;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+_MOST_RECENT_PRODUCTS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS);
+:(before "End Primitive Recipe Checks")
+case _MOST_RECENT_PRODUCTS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _MOST_RECENT_PRODUCTS: {
+  products.resize(1);
+  products.at(0).push_back(new_mu_string(Most_recent_products));
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SAVE_ERRORS_WARNINGS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "save-errors-warnings", SAVE_ERRORS_WARNINGS);
+:(before "End Primitive Recipe Checks")
+case SAVE_ERRORS_WARNINGS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE_ERRORS_WARNINGS: {
+  products.resize(1);
+  products.at(0).push_back(trace_error_warning_contents());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SAVE_APP_TRACE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE);
+:(before "End Primitive Recipe Checks")
+case SAVE_APP_TRACE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE_APP_TRACE: {
+  products.resize(1);
+  products.at(0).push_back(trace_app_contents());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+_CLEANUP_RUN_INTERACTIVE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$cleanup-run-interactive", _CLEANUP_RUN_INTERACTIVE);
+:(before "End Primitive Recipe Checks")
+case _CLEANUP_RUN_INTERACTIVE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _CLEANUP_RUN_INTERACTIVE: {
+  run_code_end();
+  break;
+}
+
+:(scenario "run_interactive_returns_stringified_result")
+recipe main [
+  # try to interactively add 2 and 2
+  1:address:array:character <- new [add 2, 2]
+  2:address:array:character <- run-interactive 1:address:array:character
+  10:array:character <- copy 2:address:array:character/lookup
+]
+# first letter in the output should be '4' in unicode
++mem: storing 52 in location 11
+
+:(scenario "run_interactive_returns_string")
+recipe main [
+  # try to interactively add 2 and 2
+  1:address:array:character <- new [
+    x:address:array:character <- new [a]
+    y:address:array:character <- new [b]
+    z:address:array:character <- string-append x:address:array:character, y:address:array:character
+  ]
+  2:address:array:character <- run-interactive 1:address:array:character
+  10:array:character <- copy 2:address:array:character/lookup
+]
+# output contains "ab"
++mem: storing 97 in location 11
++mem: storing 98 in location 12
+
+:(scenario "run_interactive_returns_errors")
+recipe main [
+  # run a command that generates an error
+  1:address:array:character <- new [x:number <- copy 34
+get x:number, foo:offset]
+  2:address:array:character, 3:address:array:character <- run-interactive 1:address:array:character
+  10:array:character <- copy 3:address:array:character/lookup
+]
+# error should be "unknown element foo in container number"
++mem: storing 117 in location 11
++mem: storing 110 in location 12
++mem: storing 107 in location 13
++mem: storing 110 in location 14
+# ...
+
+:(scenario run_interactive_with_comment)
+recipe main [
+  # 2 instructions, with a comment after the first
+  1:address:array:number <- new [a:number <- copy 0  # abc
+b:number <- copy 0
+]
+  2:address:array:character, 3:address:array:character <- run-interactive 1:address:array:character
+]
+# no errors
++mem: storing 0 in location 3
+
+:(before "End Globals")
+string Most_recent_products;
+:(before "End Setup")
+Most_recent_products = "";
+:(before "End of Instruction")
+if (Track_most_recent_products) {
+  track_most_recent_products(current_instruction(), products);
+}
+:(code)
+void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
+  ostringstream out;
+  for (long long int i = 0; i < SIZE(products); ++i) {
+    // string
+    if (i < SIZE(instruction.products)) {
+      if (is_mu_string(instruction.products.at(i))) {
+        if (!scalar(products.at(i))) {
+          tb_shutdown();
+          cerr << read_mu_string(trace_error_warning_contents()) << '\n';
+          cerr << SIZE(products.at(i)) << ": ";
+          for (long long int j = 0; j < SIZE(products.at(i)); ++j)
+            cerr << no_scientific(products.at(i).at(j)) << ' ';
+          cerr << '\n';
+        }
+        assert(scalar(products.at(i)));
+        out << read_mu_string(products.at(i).at(0)) << '\n';
+        continue;
+      }
+      // End Record Product Special-cases
+    }
+    for (long long int j = 0; j < SIZE(products.at(i)); ++j)
+      out << no_scientific(products.at(i).at(j)) << ' ';
+    out << '\n';
+  }
+  Most_recent_products = out.str();
+}
+
+:(code)
+string strip_comments(string in) {
+  ostringstream result;
+  for (long long int i = 0; i < SIZE(in); ++i) {
+    if (in.at(i) != '#') {
+      result << in.at(i);
+    }
+    else {
+      while (i+1 < SIZE(in) && in.at(i+1) != '\n')
+        ++i;
+    }
+  }
+  return result.str();
+}
+
+long long int stringified_value_of_location(long long int address) {
+  // convert to string
+  ostringstream out;
+  out << no_scientific(get_or_insert(Memory, address));
+  return new_mu_string(out.str());
+}
+
+long long int trace_error_warning_contents() {
+  if (!Trace_stream) return 0;
+  ostringstream out;
+  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
+    if (p->depth > Warning_depth) continue;
+    out << p->contents;
+    if (*--p->contents.end() != '\n') out << '\n';
+  }
+  string result = out.str();
+  if (result.empty()) return 0;
+  truncate(result);
+  return new_mu_string(result);
+}
+
+long long int trace_app_contents() {
+  if (!Trace_stream) return 0;
+  ostringstream out;
+  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
+    if (p->depth != App_depth) continue;
+    out << p->contents;
+    if (*--p->contents.end() != '\n') out << '\n';
+  }
+  string result = out.str();
+  if (result.empty()) return 0;
+  truncate(result);
+  return new_mu_string(result);
+}
+
+void truncate(string& x) {
+  if (SIZE(x) > 512) {
+    x.erase(512);
+    *x.rbegin() = '\n';
+    *++x.rbegin() = '.';
+    *++++x.rbegin() = '.';
+  }
+}
+
+//: simpler version of run-interactive: doesn't do any running, just loads
+//: recipes and reports errors+warnings.
+
+:(before "End Primitive Recipe Declarations")
+RELOAD,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "reload", RELOAD);
+:(before "End Primitive Recipe Checks")
+case RELOAD: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise_error << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_string(inst.ingredients.at(0))) {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RELOAD: {
+  // clear any containers in advance
+  for (long long int i = 0; i < SIZE(recently_added_types); ++i) {
+    Type_ordinal.erase(get(Type, recently_added_types.at(i)).name);
+    Type.erase(recently_added_types.at(i));
+  }
+  string code = read_mu_string(ingredients.at(0).at(0));
+  run_code_begin();
+  routine* save_current_routine = Current_routine;
+  Current_routine = NULL;
+  vector<recipe_ordinal> recipes_reloaded = load(code);
+  for (long long int i = 0; i < SIZE(recipes_reloaded); ++i) {
+    Name.erase(recipes_reloaded.at(i));
+  }
+  transform_all();
+  Trace_stream->newline();  // flush trace
+  Current_routine = save_current_routine;
+  products.resize(1);
+  products.at(0).push_back(trace_error_warning_contents());
+  run_code_end();  // wait until we're done with the trace contents
+  break;
+}
+
+:(scenario reload_continues_past_error)
+recipe main [
+  local-scope
+  x:address:array:character <- new [recipe foo [
+  get 1234:number, foo:offset
+]]
+  reload x
+  1:number/raw <- copy 34
+]
++mem: storing 34 in location 1
+
+ + + diff --git a/html/092persist.cc.html b/html/092persist.cc.html new file mode 100644 index 00000000..cce4913f --- /dev/null +++ b/html/092persist.cc.html @@ -0,0 +1,157 @@ + + + + +Mu - 092persist.cc + + + + + + + + + + +
+//: Dead simple persistence.
+//:   'restore' - reads string from a file
+//:   'save' - writes string to a file
+
+:(before "End Primitive Recipe Declarations")
+RESTORE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "restore", RESTORE);
+:(before "End Primitive Recipe Checks")
+case RESTORE: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise_error << maybe(get(Recipe, r).name) << "'restore' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  string filename;
+  if (is_literal_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else if (is_mu_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restore' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RESTORE: {
+  string filename;
+  if (is_literal_string(current_instruction().ingredients.at(0))) {
+    filename = current_instruction().ingredients.at(0).name;
+  }
+  else if (is_mu_string(current_instruction().ingredients.at(0))) {
+    filename = read_mu_string(ingredients.at(0).at(0));
+  }
+  if (Current_scenario) {
+    // do nothing in tests
+    products.resize(1);
+    products.at(0).push_back(0);
+    break;
+  }
+  string contents = slurp("lesson/"+filename);
+  products.resize(1);
+  if (contents.empty())
+    products.at(0).push_back(0);
+  else
+    products.at(0).push_back(new_mu_string(contents));
+  break;
+}
+
+:(code)
+string slurp(const string& filename) {
+  ostringstream result;
+  ifstream fin(filename.c_str());
+  fin.peek();
+  if (!fin) return result.str();  // don't bother checking errno
+  const int N = 1024;
+  char buf[N];
+  while (!fin.eof()) {
+    bzero(buf, N);
+    fin.read(buf, N-1);  // leave at least one null
+    result << buf;
+  }
+  fin.close();
+  return result.str();
+}
+
+:(before "End Primitive Recipe Declarations")
+SAVE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "save", SAVE);
+:(before "End Primitive Recipe Checks")
+case SAVE: {
+  if (SIZE(inst.ingredients) != 2) {
+    raise_error << maybe(get(Recipe, r).name) << "'save' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (is_literal_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else if (is_mu_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else {
+    raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'save' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_string(inst.ingredients.at(1))) {
+    raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'save' should be an address:array:character, but got " << inst.ingredients.at(1).to_string() << '\n' << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE: {
+  if (Current_scenario) break;  // do nothing in tests
+  string filename;
+  if (is_literal_string(current_instruction().ingredients.at(0))) {
+    filename = current_instruction().ingredients.at(0).name;
+  }
+  else if (is_mu_string(current_instruction().ingredients.at(0))) {
+    filename = read_mu_string(ingredients.at(0).at(0));
+  }
+  ofstream fout(("lesson/"+filename).c_str());
+  string contents = read_mu_string(ingredients.at(1).at(0));
+  fout << contents;
+  fout.close();
+  if (!exists("lesson/.git")) break;
+  // bug in git: git diff -q messes up --exit-code
+  // explicitly say '--all' for git 1.9
+  int status = system("cd lesson; git add --all .; git diff HEAD --exit-code >/dev/null || git commit -a -m . >/dev/null");
+  if (status != 0)
+    raise_error << "error in commit: contents " << contents << '\n' << end();
+  break;
+}
+
+:(code)
+bool exists(const string& filename) {
+  struct stat dummy;
+  return 0 == stat(filename.c_str(), &dummy);
+}
+
+ + + diff --git a/html/098check_type_pointers.cc.html b/html/098check_type_pointers.cc.html deleted file mode 100644 index 74de0da6..00000000 --- a/html/098check_type_pointers.cc.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - -Mu - 098check_type_pointers.cc - - - - - - - - - - -
-:(before "End Transform All")
-check_type_pointers();
-
-:(code)
-void check_type_pointers() {
-  for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-    if (any_type_ingredient_in_header(p->first)) continue;
-    const recipe& r = p->second;
-    for (long long int i = 0; i < SIZE(r.steps); ++i) {
-      const instruction& inst = r.steps.at(i);
-      for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
-        if (!inst.ingredients.at(j).type) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type\n" << end();
-          return;
-        }
-        if (!inst.ingredients.at(j).properties.at(0).second) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type name\n" << end();
-          return;
-        }
-      }
-      for (long long int j = 0; j < SIZE(inst.products); ++j) {
-        if (!inst.products.at(j).type) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type\n" << end();
-          return;
-        }
-        if (!inst.products.at(j).properties.at(0).second) {
-          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type name\n" << end();
-          return;
-        }
-      }
-    }
-  }
-}
-
- - - diff --git a/html/998check_type_pointers.cc.html b/html/998check_type_pointers.cc.html new file mode 100644 index 00000000..6532702e --- /dev/null +++ b/html/998check_type_pointers.cc.html @@ -0,0 +1,67 @@ + + + + +Mu - 998check_type_pointers.cc + + + + + + + + + + +
+:(before "End Transform All")
+check_type_pointers();
+
+:(code)
+void check_type_pointers() {
+  for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
+    if (any_type_ingredient_in_header(p->first)) continue;
+    const recipe& r = p->second;
+    for (long long int i = 0; i < SIZE(r.steps); ++i) {
+      const instruction& inst = r.steps.at(i);
+      for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
+        if (!inst.ingredients.at(j).type) {
+          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type\n" << end();
+          return;
+        }
+        if (!inst.ingredients.at(j).properties.at(0).second) {
+          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type name\n" << end();
+          return;
+        }
+      }
+      for (long long int j = 0; j < SIZE(inst.products); ++j) {
+        if (!inst.products.at(j).type) {
+          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type\n" << end();
+          return;
+        }
+        if (!inst.products.at(j).properties.at(0).second) {
+          raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type name\n" << end();
+          return;
+        }
+      }
+    }
+  }
+}
+
+ + + diff --git a/html/edit/006-sandbox-edit.mu.html b/html/edit/006-sandbox-edit.mu.html index a1e3bb2f..2381b4bd 100644 --- a/html/edit/006-sandbox-edit.mu.html +++ b/html/edit/006-sandbox-edit.mu.html @@ -155,7 +155,6 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } # position cursor in sandbox editor sandbox-in-focus?:address:boolean <- get-address *env, sandbox-in-focus?:offset *sandbox-in-focus? <- copy 1/true - reply result ] scenario sandbox-with-print-can-be-edited [ diff --git a/html/example1.mu.html b/html/example1.mu.html new file mode 100644 index 00000000..13333c91 --- /dev/null +++ b/html/example1.mu.html @@ -0,0 +1,37 @@ + + + + +Mu - example1.mu + + + + + + + + + + +
+recipe example1 [
+  a:number <- add 2, 2
+  a <- multiply a, 3
+]
+
+ + + diff --git a/html/factorial.mu.html b/html/factorial.mu.html index 31ac9c26..db6840b9 100644 --- a/html/factorial.mu.html +++ b/html/factorial.mu.html @@ -40,9 +40,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } ] ] -recipe factorial [ +recipe factorial n:number -> result:number [ local-scope - n:number <- next-ingredient + load-ingredients { # if n=0 return 1 zero?:boolean <- equal n, 0 @@ -52,8 +52,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } # return n * factorial(n-1) x:number <- subtract n, 1 subresult:number <- factorial x - result:number <- multiply subresult, n - reply result + result <- multiply subresult, n ] # unit test diff --git a/html/tangle.mu.html b/html/tangle.mu.html index 118b2386..8b0b7d13 100644 --- a/html/tangle.mu.html +++ b/html/tangle.mu.html @@ -38,9 +38,9 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } # This isn't a very tasteful example, just a simple demonstration of # possibilities. -recipe factorial [ +recipe factorial n:number -> result:number [ local-scope - n:number <- next-ingredient + load-ingredients { <base-case> } @@ -58,8 +58,7 @@ body { font-family: monospace; color: #eeeeee; background-color: #080808; } # return n * factorial(n - 1) x:number <- subtract n, 1 subresult:number <- factorial x - result:number <- multiply subresult, n - reply result + result <- multiply subresult, n ] recipe main [ -- cgit 1.4.1-2-gfad0