summary refs log tree commit diff stats
path: root/compiler/llstream.nim
blob: 0a1e09fc86d8caff525898a553f315b933800558 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#
#
#           The Nim Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Low-level streams for high performance.

import
  strutils

# support '-d:useGnuReadline' for backwards compatibility:
when not defined(windows) and (defined(useGnuReadline) or defined(useLinenoise)):
  import rdstdin

type
  TLLStreamKind* = enum       # enum of different stream implementations
    llsNone,                  # null stream: reading and writing has no effect
    llsString,                # stream encapsulates a string
    llsFile,                  # stream encapsulates a file
    llsStdIn                  # stream encapsulates stdin
  TLLStream* = object of RootObj
    kind*: TLLStreamKind # accessible for low-level access (lexbase uses this)
    f*: File
    s*: string
    rd*, wr*: int             # for string streams
    lineOffset*: int          # for fake stdin line numbers

  PLLStream* = ref TLLStream

proc llStreamOpen*(data: string): PLLStream =
  new(result)
  result.s = data
  result.kind = llsString

proc llStreamOpen*(f: File): PLLStream =
  new(result)
  result.f = f
  result.kind = llsFile

proc llStreamOpen*(filename: string, mode: FileMode): PLLStream =
  new(result)
  result.kind = llsFile
  if not open(result.f, filename, mode): result = nil

proc llStreamOpen*(): PLLStream =
  new(result)
  result.kind = llsNone

proc llStreamOpenStdIn*(): PLLStream =
  new(result)
  result.kind = llsStdIn
  result.s = ""
  result.lineOffset = -1

proc llStreamClose*(s: PLLStream) =
  case s.kind
  of llsNone, llsString, llsStdIn:
    discard
  of llsFile:
    close(s.f)

when not declared(readLineFromStdin):
  # fallback implementation:
  proc readLineFromStdin(prompt: string, line: var string): bool =
    stdout.write(prompt)
    result = readLine(stdin, line)
    if not result:
      stdout.write("\n")
      quit(0)

proc endsWith*(x: string, s: set[char]): bool =
  var i = x.len-1
  while i >= 0 and x[i] == ' ': dec(i)
  if i >= 0 and x[i] in s:
    result = true

const
  LineContinuationOprs = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^',
                          '|', '%', '&', '$', '@', '~', ','}
  AdditionalLineContinuationOprs = {'#', ':', '='}

proc endsWithOpr*(x: string): bool =
  # also used by the standard template filter:
  result = x.endsWith(LineContinuationOprs)

proc continueLine(line: string, inTripleString: bool): bool {.inline.} =
  result = inTripleString or
      line[0] == ' ' or
      line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)

proc countTriples(s: string): int =
  var i = 0
  while i < s.len:
    if s[i] == '"' and s[i+1] == '"' and s[i+2] == '"':
      inc result
      inc i, 2
    inc i

proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int =
  s.s = ""
  s.rd = 0
  var line = newStringOfCap(120)
  var triples = 0
  while readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line):
    add(s.s, line)
    add(s.s, "\n")
    inc triples, countTriples(line)
    if not continueLine(line, (triples and 1) == 1): break
  inc(s.lineOffset)
  result = min(bufLen, len(s.s) - s.rd)
  if result > 0:
    copyMem(buf, addr(s.s[s.rd]), result)
    inc(s.rd, result)

proc llStreamRead*(s: PLLStream, buf: pointer, bufLen: int): int =
  case s.kind
  of llsNone:
    result = 0
  of llsString:
    result = min(bufLen, len(s.s) - s.rd)
    if result > 0:
      copyMem(buf, addr(s.s[0 + s.rd]), result)
      inc(s.rd, result)
  of llsFile:
    result = readBuffer(s.f, buf, bufLen)
  of llsStdIn:
    result = llReadFromStdin(s, buf, bufLen)

proc llStreamReadLine*(s: PLLStream, line: var string): bool =
  setLen(line, 0)
  case s.kind
  of llsNone:
    result = true
  of llsString:
    while s.rd < len(s.s):
      case s.s[s.rd]
      of '\x0D':
        inc(s.rd)
        if s.s[s.rd] == '\x0A': inc(s.rd)
        break
      of '\x0A':
        inc(s.rd)
        break
      else:
        add(line, s.s[s.rd])
        inc(s.rd)
    result = line.len > 0 or s.rd < len(s.s)
  of llsFile:
    result = readLine(s.f, line)
  of llsStdIn:
    result = readLine(stdin, line)

proc llStreamWrite*(s: PLLStream, data: string) =
  case s.kind
  of llsNone, llsStdIn:
    discard
  of llsString:
    add(s.s, data)
    inc(s.wr, len(data))
  of llsFile:
    write(s.f, data)

proc llStreamWriteln*(s: PLLStream, data: string) =
  llStreamWrite(s, data)
  llStreamWrite(s, "\n")

proc llStreamWrite*(s: PLLStream, data: char) =
  var c: char
  case s.kind
  of llsNone, llsStdIn:
    discard
  of llsString:
    add(s.s, data)
    inc(s.wr)
  of llsFile:
    c = data
    discard writeBuffer(s.f, addr(c), sizeof(c))

proc llStreamWrite*(s: PLLStream, buf: pointer, buflen: int) =
  case s.kind
  of llsNone, llsStdIn:
    discard
  of llsString:
    if buflen > 0:
      setLen(s.s, len(s.s) + buflen)
      copyMem(addr(s.s[0 + s.wr]), buf, buflen)
      inc(s.wr, buflen)
  of llsFile:
    discard writeBuffer(s.f, buf, buflen)

proc llStreamReadAll*(s: PLLStream): string =
  const
    bufSize = 2048
  case s.kind
  of llsNone, llsStdIn:
    result = ""
  of llsString:
    if s.rd == 0: result = s.s
    else: result = substr(s.s, s.rd)
    s.rd = len(s.s)
  of llsFile:
    result = newString(bufSize)
    var bytes = readBuffer(s.f, addr(result[0]), bufSize)
    var i = bytes
    while bytes == bufSize:
      setLen(result, i + bufSize)
      bytes = readBuffer(s.f, addr(result[i + 0]), bufSize)
      inc(i, bytes)
    setLen(result, i)
an class="nv">address:array:character <- new [abc] 10:boolean/raw <- equal x, x ] memory-should-contain [ 10 <- 1 # x == x for all x ] ] scenario text-equal-identical [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [abc] 10:boolean/raw <- equal x, y ] memory-should-contain [ 10 <- 1 # abc == abc ] ] scenario text-equal-distinct-lengths [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [abcd] 10:boolean/raw <- equal x, y ] memory-should-contain [ 10 <- 0 # abc != abcd ] trace-should-contain [ text-equal: comparing lengths ] trace-should-not-contain [ text-equal: comparing characters ] ] scenario text-equal-with-empty [ run [ local-scope x:address:array:character <- new [] y:address:array:character <- new [abcd] 10:boolean/raw <- equal x, y ] memory-should-contain [ 10 <- 0 # "" != abcd ] ] scenario text-equal-common-lengths-but-distinct [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [abd] 10:boolean/raw <- equal x, y ] memory-should-contain [ 10 <- 0 # abc != abd ] ] # A new type to help incrementally construct texts. container buffer [ length:number data:address:array:character ] def new-buffer capacity:number -> result:address:buffer [ local-scope load-ingredients result <- new buffer:type *result <- put *result, length:offset, 0 { break-if capacity # capacity not provided capacity <- copy 10 } data:address:array:character <- new character:type, capacity *result <- put *result, data:offset, data return result ] def grow-buffer in:address:buffer -> in:address:buffer [ local-scope load-ingredients # double buffer size olddata:address:array:character <- get *in, data:offset oldlen:number <- length *olddata newlen:number <- multiply oldlen, 2 newdata:address:array:character <- new character:type, newlen *in <- put *in, data:offset, newdata # copy old contents i:number <- copy 0 { done?:boolean <- greater-or-equal i, oldlen break-if done? src:character <- index *olddata, i *newdata <- put-index *newdata, i, src i <- add i, 1 loop } ] def buffer-full? in:address:buffer -> result:boolean [ local-scope load-ingredients len:number <- get *in, length:offset s:address:array:character <- get *in, data:offset capacity:number <- length *s result <- greater-or-equal len, capacity ] # most broadly applicable definition of append to a buffer: just call to-text def append buf:address:buffer, x:_elem -> buf:address:buffer [ local-scope load-ingredients text:address:array:character <- to-text x len:number <- length *text i:number <- copy 0 { done?:boolean <- greater-or-equal i, len break-if done? c:character <- index *text, i buf <- append buf, c i <- add i, 1 loop } ] def append in:address:buffer, c:character -> in:address:buffer [ local-scope load-ingredients len:number <- get *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 return-if empty? len <- subtract len, 1 *in <- put *in, length:offset, len return } { # grow buffer if necessary full?:boolean <- buffer-full? in break-unless full? in <- grow-buffer in } s:address:array:character <- get *in, data:offset *s <- put-index *s, len, c len <- add len, 1 *in <- put *in, length:offset, len ] scenario buffer-append-works [ run [ local-scope x:address:buffer <- new-buffer 3 s1:address:array:character <- get *x, data:offset c:character <- copy 97/a x <- append x, c c:character <- copy 98/b x <- append x, c c:character <- copy 99/c x <- append x, c s2:address:array:character <- get *x, data:offset 10:boolean/raw <- equal s1, s2 11:array:character/raw <- copy *s2 +buffer-filled c:character <- copy 100/d x <- append x, c s3:address:array:character <- get *x, data:offset 20:boolean/raw <- equal s1, s3 21:number/raw <- get *x, length:offset 30:array:character/raw <- copy *s3 ] memory-should-contain [ # before +buffer-filled 10 <- 1 # no change in data pointer 11 <- 3 # size of data 12 <- 97 # data 13 <- 98 14 <- 99 # in the end 20 <- 0 # data pointer has grown 21 <- 4 # final length 30 <- 6 # but data's capacity has doubled 31 <- 97 # data 32 <- 98 33 <- 99 34 <- 100 35 <- 0 36 <- 0 ] ] scenario buffer-append-to-empty [ run [ local-scope x:address:buffer <- new-buffer c:character <- copy 97/a x <- append x, c ] ] scenario buffer-append-handles-backspace [ run [ local-scope x:address:buffer <- new-buffer 3 c:character <- copy 97/a x <- append x, c c:character <- copy 98/b x <- append x, c c:character <- copy 8/backspace x <- append x, c s:address:array:character <- buffer-to-array x 10:array:character/raw <- copy *s ] memory-should-contain [ 10 <- 1 # length 11 <- 97 # contents 12 <- 0 ] ] def buffer-to-array in:address:buffer -> result:address:array:character [ local-scope load-ingredients { # propagate null buffer break-if in return 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 <- new character:type, len i:number <- copy 0 { done?:boolean <- greater-or-equal i, len break-if done? src:character <- index *s, i *result <- put-index *result, i, src i <- add i, 1 loop } ] def append a:address:array:character, b:address:array:character -> result:address:array:character [ local-scope load-ingredients # handle null addresses return-unless a, b return-unless b, a # result = new character[a.length + b.length] a-len:number <- length *a b-len:number <- length *b result-len:number <- add a-len, b-len result <- 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] in:character <- index *a, i *result <- put-index *result, result-idx, 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] in:character <- index *b, i *result <- put-index *result, result-idx, in i <- add i, 1 result-idx <- add result-idx, 1 loop } ] scenario text-append-1 [ run [ local-scope x:address:array:character <- new [hello,] y:address:array:character <- new [ world!] z:address:array:character <- append x, y 10:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [hello, world!] ] ] scenario text-append-null [ run [ local-scope x:address:array:character <- copy 0 y:address:array:character <- new [ world!] z:address:array:character <- append x, y 10:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [ world!] ] ] scenario text-append-null-2 [ run [ local-scope x:address:array:character <- new [hello,] y:address:array:character <- copy 0 z:address:array:character <- append x, y 10:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [hello,] ] ] scenario replace-character-in-text [ run [ local-scope x:address:array:character <- new [abc] x <- replace x, 98/b, 122/z 10:array:character/raw <- copy *x ] memory-should-contain [ 10:array:character <- [azc] ] ] def replace s:address:array:character, oldc:character, newc:character, from:number/optional -> s:address:array:character [ local-scope load-ingredients len:number <- length *s i:number <- find-next s, oldc, from done?:boolean <- greater-or-equal i, len return-if done?, s/same-as-ingredient:0 *s <- put-index *s, i, newc i <- add i, 1 s <- replace s, oldc, newc, i ] scenario replace-character-at-start [ run [ local-scope x:address:array:character <- new [abc] x <- replace x, 97/a, 122/z 10:array:character/raw <- copy *x ] memory-should-contain [ 10:array:character <- [zbc] ] ] scenario replace-character-at-end [ run [ local-scope x:address:array:character <- new [abc] x <- replace x, 99/c, 122/z 10:array:character/raw <- copy *x ] memory-should-contain [ 10:array:character <- [abz] ] ] scenario replace-character-missing [ run [ local-scope x:address:array:character <- new [abc] x <- replace x, 100/d, 122/z 10:array:character/raw <- copy *x ] memory-should-contain [ 10:array:character <- [abc] ] ] scenario replace-all-characters [ run [ local-scope x:address:array:character <- new [banana] x <- replace x, 97/a, 122/z 10:array:character/raw <- copy *x ] memory-should-contain [ 10:array:character <- [bznznz] ] ] # replace underscores in first with remaining args def interpolate template:address:array:character -> result:address:array:character [ local-scope load-ingredients # consume just the template # compute result-len, space to allocate for result tem-len:number <- length *template result-len:number <- copy tem-len { # while ingredients remain 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 <- 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] *result <- put-index *result, result-idx, 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 *result <- put-index *result, result-idx, 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 *result <- put-index *result, result-idx, in i <- add i, 1 result-idx <- add result-idx, 1 loop } ] scenario interpolate-works [ run [ local-scope x:address:array:character <- new [abc_ghi] y:address:array:character <- new [def] z:address:array:character <- interpolate x, y 10:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [abcdefghi] ] ] scenario interpolate-at-start [ run [ local-scope x:address:array:character <- new [_, hello!] y:address:array:character <- new [abc] z:address:array:character <- interpolate x, y 10:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [abc, hello!] 22 <- 0 # out of bounds ] ] scenario interpolate-at-end [ run [ x:address:array:character <- new [hello, _] y:address:array:character <- new [abc] z:address:array:character <- interpolate x, y 10:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [hello, abc] ] ] # result:boolean <- space? c:character def space? c:character -> result:boolean [ local-scope load-ingredients # most common case first result <- equal c, 32/space return-if result result <- equal c, 10/newline return-if result result <- equal c, 9/tab return-if result result <- equal c, 13/carriage-return return-if 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 return-if result result <- equal c, 12/ctrl-l return-if result result <- equal c, 133/ctrl-0085 return-if result result <- equal c, 160/no-break-space return-if result result <- equal c, 5760/ogham-space-mark return-if result result <- equal c, 8192/en-quad return-if result result <- equal c, 8193/em-quad return-if result result <- equal c, 8194/en-space return-if result result <- equal c, 8195/em-space return-if result result <- equal c, 8196/three-per-em-space return-if result result <- equal c, 8197/four-per-em-space return-if result result <- equal c, 8198/six-per-em-space return-if result result <- equal c, 8199/figure-space return-if result result <- equal c, 8200/punctuation-space return-if result result <- equal c, 8201/thin-space return-if result result <- equal c, 8202/hair-space return-if result result <- equal c, 8206/left-to-right return-if result result <- equal c, 8207/right-to-left return-if result result <- equal c, 8232/line-separator return-if result result <- equal c, 8233/paragraph-separator return-if result result <- equal c, 8239/narrow-no-break-space return-if result result <- equal c, 8287/medium-mathematical-space return-if result result <- equal c, 12288/ideographic-space ] def trim s:address:array:character -> result:address:array:character [ local-scope load-ingredients 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 <- new character:type, 0 return } 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 *result <- put-index *result, j, src i <- add i, 1 j <- add j, 1 loop } ] scenario trim-unmodified [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- trim x 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-left [ run [ local-scope x:address:array:character <- new [ abc] y:address:array:character <- trim x 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-right [ run [ local-scope x:address:array:character <- new [abc ] y:address:array:character <- trim x 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-left-right [ run [ local-scope x:address:array:character <- new [ abc ] y:address:array:character <- trim x 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-newline-tab [ run [ local-scope x:address:array:character <- new [ abc ] y:address:array:character <- trim x 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] def find-next text:address:array:character, pattern:character, idx:number -> next-index:number [ local-scope load-ingredients 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 } return idx ] scenario text-find-next [ run [ local-scope x:address:array:character <- new [a/b] 10:number/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 1 ] ] scenario text-find-next-empty [ run [ local-scope x:address:array:character <- new [] 10:number/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 0 ] ] scenario text-find-next-initial [ run [ local-scope x:address:array:character <- new [/abc] 10:number/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 0 # prefix match ] ] scenario text-find-next-final [ run [ local-scope x:address:array:character <- new [abc/] 10:number/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 3 # suffix match ] ] scenario text-find-next-missing [ run [ local-scope x:address:array:character <- new [abcd] 10:number/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 4 # no match ] ] scenario text-find-next-invalid-index [ run [ local-scope x:address:array:character <- new [abc] 10:number/raw <- find-next x, 47/slash, 4/start-index ] memory-should-contain [ 10 <- 4 # no change ] ] scenario text-find-next-first [ run [ local-scope x:address:array:character <- new [ab/c/] 10:number/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 2 # first '/' of multiple ] ] scenario text-find-next-second [ run [ local-scope x:address:array:character <- new [ab/c/] 10:number/raw <- find-next x, 47/slash, 3/start-index ] memory-should-contain [ 10 <- 4 # second '/' of multiple ] ] # search for a pattern of multiple characters # fairly dumb algorithm def find-next text:address:array:character, pattern:address:array:character, idx:number -> next-index:number [ local-scope load-ingredients first:character <- index *pattern, 0 # repeatedly check for match at current idx len:number <- length *text { # does some unnecessary work checking 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 } return idx ] scenario find-next-text-1 [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [bc] 10:number/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 1 ] ] scenario find-next-text-2 [ run [ local-scope x:address:array:character <- new [abcd] y:address:array:character <- new [bc] 10:number/raw <- find-next x, y, 1 ] memory-should-contain [ 10 <- 1 ] ] scenario find-next-no-match [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [bd] 10:number/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 3 # not found ] ] scenario find-next-suffix-match [ run [ local-scope x:address:array:character <- new [abcd] y:address:array:character <- new [cd] 10:number/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 2 ] ] scenario find-next-suffix-match-2 [ run [ local-scope x:address:array:character <- new [abcd] y:address:array:character <- new [cde] 10:number/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 4 # not found ] ] # checks if pattern matches at index 'idx' def match-at text:address:array:character, pattern:address:array:character, idx:number -> result:boolean [ local-scope load-ingredients 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? return 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? return 0/not-found } idx <- add idx, 1 pattern-idx <- add pattern-idx, 1 loop } return 1/found ] scenario match-at-checks-pattern-at-index [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [ab] 10:boolean/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 1 # match found ] ] scenario match-at-reflexive [ run [ local-scope x:address:array:character <- new [abc] 10:boolean/raw <- match-at x, x, 0 ] memory-should-contain [ 10 <- 1 # match found ] ] scenario match-at-outside-bounds [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [a] 10:boolean/raw <- match-at x, y, 4 ] memory-should-contain [ 10 <- 0 # never matches ] ] scenario match-at-empty-pattern [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [] 10:boolean/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 1 # always matches empty pattern given a valid index ] ] scenario match-at-empty-pattern-outside-bound [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [] 10:boolean/raw <- match-at x, y, 4 ] memory-should-contain [ 10 <- 0 # no match ] ] scenario match-at-empty-text [ run [ local-scope x:address:array:character <- new [] y:address:array:character <- new [abc] 10:boolean/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 0 # no match ] ] scenario match-at-empty-against-empty [ run [ local-scope x:address:array:character <- new [] 10:boolean/raw <- match-at x, x, 0 ] memory-should-contain [ 10 <- 1 # matches because pattern is also empty ] ] scenario match-at-inside-bounds [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [bc] 10:boolean/raw <- match-at x, y, 1 ] memory-should-contain [ 10 <- 1 # match ] ] scenario match-at-inside-bounds-2 [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- new [bc] 10:boolean/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 0 # no match ] ] def split s:address:array:character, delim:character -> result:address:array:address:array:character [ local-scope load-ingredients # empty text? return empty array len:number <- length *s { empty?:boolean <- equal len, 0 break-unless empty? result <- new {(address array character): type}, 0 return } # 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 <- new {(address array character): 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:array:character <- copy-range s, start, end *result <- put-index *result, curr-result, dest # slide over to next slice start <- add end, 1 curr-result <- add curr-result, 1 loop } ] scenario text-split-1 [ run [ local-scope x:address:array:character <- new [a/b] y:address:array:address:array:character <- split x, 47/slash 10:number/raw <- length *y a:address:array:character <- index *y, 0 b:address:array:character <- index *y, 1 20:array:character/raw <- copy *a 30:array:character/raw <- copy *b ] memory-should-contain [ 10 <- 2 # length of result 20:array:character <- [a] 30:array:character <- [b] ] ] scenario text-split-2 [ run [ local-scope x:address:array:character <- new [a/b/c] y:address:array:address:array:character <- split x, 47/slash 10:number/raw <- length *y a:address:array:character <- index *y, 0 b:address:array:character <- index *y, 1 c:address:array:character <- index *y, 2 20:array:character/raw <- copy *a 30:array:character/raw <- copy *b 40:array:character/raw <- copy *c ] memory-should-contain [ 10 <- 3 # length of result 20:array:character <- [a] 30:array:character <- [b] 40:array:character <- [c] ] ] scenario text-split-missing [ run [ local-scope x:address:array:character <- new [abc] y:address:array:address:array:character <- split x, 47/slash 10:number/raw <- length *y a:address:array:character <- index *y, 0 20:array:character/raw <- copy *a ] memory-should-contain [ 10 <- 1 # length of result 20:array:character <- [abc] ] ] scenario text-split-empty [ run [ local-scope x:address:array:character <- new [] y:address:array:address:array:character <- split x, 47/slash 10:number/raw <- length *y ] memory-should-contain [ 10 <- 0 # empty result ] ] scenario text-split-empty-piece [ run [ local-scope x:address:array:character <- new [a/b//c] y:address:array:address:array:character <- split x:address:array:character, 47/slash 10:number/raw <- length *y a:address:array:character <- index *y, 0 b:address:array:character <- index *y, 1 c:address:array:character <- index *y, 2 d:address:array:character <- index *y, 3 20:array:character/raw <- copy *a 30:array:character/raw <- copy *b 40:array:character/raw <- copy *c 50:array:character/raw <- copy *d ] memory-should-contain [ 10 <- 4 # length of result 20:array:character <- [a] 30:array:character <- [b] 40:array:character <- [] 50:array:character <- [c] ] ] def split-first text:address:array:character, delim:character -> x:address:array:character, y:address:array:character [ local-scope load-ingredients # empty text? return empty texts len:number <- length *text { empty?:boolean <- equal len, 0 break-unless empty? x:address:array:character <- new [] y:address:array:character <- new [] return } idx:number <- find-next text, delim, 0 x:address:array:character <- copy-range text, 0, idx idx <- add idx, 1 y:address:array:character <- copy-range text, idx, len ] scenario text-split-first [ run [ local-scope x:address:array:character <- new [a/b] y:address:array:character, z:address:array:character <- split-first x, 47/slash 10:array:character/raw <- copy *y 20:array:character/raw <- copy *z ] memory-should-contain [ 10:array:character <- [a] 20:array:character <- [b] ] ] def copy-range buf:address:array:character, start:number, end:number -> result:address:array:character [ local-scope load-ingredients # 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 *result <- put-index *result, dest-idx, src src-idx <- add src-idx, 1 dest-idx <- add dest-idx, 1 loop } ] scenario text-copy-copies-partial-text [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- copy-range x, 1, 3 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [bc] ] ] scenario text-copy-out-of-bounds [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- copy-range x, 2, 4 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [c] ] ] scenario text-copy-out-of-bounds-2 [ run [ local-scope x:address:array:character <- new [abc] y:address:array:character <- copy-range x, 3, 3 1:array:character/raw <- copy *y ] memory-should-contain [ 1:array:character <- [] ] ]