-----------------------------------------------------------------------------
-- HTTP/1.1 client support for the Lua language.
-- LuaSocket toolkit.
-- Author: Diego Nehab
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-------------------------------------------------------------------------------
socket.http = {}
local _M = socket.http
-----------------------------------------------------------------------------
-- Program constants
-----------------------------------------------------------------------------
-- connection timeout in seconds
_M.TIMEOUT = 60
-- user agent field sent in request
_M.USERAGENT = socket._VERSION
-- supported schemes and their particulars
local SCHEMES = {
http = {
port = 80
, create = function(t)
return socket.tcp end }
, https = {
port = 443
, create = function(t)
return https.tcp(t) end }}
-- default scheme and port for document retrieval
local SCHEME = 'http'
local PORT = SCHEMES[SCHEME].port
-----------------------------------------------------------------------------
-- Reads MIME headers from a connection, unfolding where needed
-----------------------------------------------------------------------------
local function receiveheaders(sock, headers)
local line, name, value, err
headers = headers or {}
-- get first line
line, err = sock:receive()
if err then return nil, err end
-- headers go until a blank line is found
while line ~= "" do
-- get field-name and value
name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
if not (name and value) then return nil, "malformed reponse headers" end
name = string.lower(name)
-- get next line (value might be folded)
line, err = sock:receive()
if err then return nil, err end
-- unfold any folded values
while string.find(line, "^%s") do
value = value .. line
line = sock:receive()
if err then return nil, err end
end
-- save pair in table
if headers[name] then headers[name] = headers[name] .. ", " .. value
else headers[name] = value end
end
return headers
end
-----------------------------------------------------------------------------
-- Extra sources and sinks
-----------------------------------------------------------------------------
socket.sourcet["http-chunked"] = function(sock, headers)
return setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
-- get chunk size, skip extention
local line, err = sock:receive()
if err then return nil, err end
local size = tonumber(string.gsub(line, ";.*", ""), 16)
if not size then return nil, "invalid chunk size" end
-- was it the last chunk?
if size > 0 then
-- if not, get chunk and skip terminating CRLF
local chunk, err, part = sock:receive(size)
if chunk then sock:receive() end
return chunk, err
else
-- if it was, read trailers into headers table
headers, err = receiveheaders(sock, headers)
if not headers then return nil, err end
end
end
})
end
socket.sinkt["http-chunked"] = function(sock)
return setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then return sock:send("0\r\n\r\n") end
local size = string.format("%X\r\n", string.len(chunk))
return sock:send(size .. chunk .. "\r\n")
end
})
end
-----------------------------------------------------------------------------
-- Low level HTTP API
-----------------------------------------------------------------------------
local metat = { __index = {} }
function _M.open(host, port, create)
-- create socket with user connect function, or with default
local c = socket.try(create())
local h = setmetatable({ c = c }, metat)
-- create finalized try
h.try = socket.newtry(function() h:close() end)
-- set timeout before connecting
h.try(c:settimeout(_M.TIMEOUT))
h.try(c:connect(host, port))
-- here everything worked
return h
end
function metat.__index:sendrequestline(method, uri)
local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
return self.try(self.c:send(reqline))
end
function metat.__index:sendheaders(tosend)
local canonic = headers.canonic
local h = "\r\n"
for f, v in pairs(tosend) do
h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h
end
self.try(self.c:send(h))
return 1
end
function metat.__index:sendbody(headers, source, step)
source = source or ltn12.source.empty()
step = step or ltn12.pump.step
-- if we don't know the size in advance, send chunked and hope for the best
local mode = "http-chunked"
if headers["content-length"] then mode = "keep-open" end
return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step))
end
function metat.__index:receivestatusline()
local status,ec = self.try(self.c:receive(5))
-- identify HTTP/0.9 responses, which do not contain a status line
-- this is just a heuristic, but is what the RFC recommends
if status ~= "HTTP/" then
if ec == "timeout" then
return 408
end
return nil, status
end
-- otherwise proceed reading a status line
status = self.try(self.c:receive("*l", status))
local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
return self.try(tonumber(code), status)
end
function metat.__index:receiveheaders()
return self.try(receiveheaders(self.c))
end
function metat.__index:receivebody(headers, sink, step)
sink = sink or ltn12.sink.null()
step = step or ltn12.pump.step
local length = tonumber(headers["content-length"])
local t = headers["transfer-encoding"] -- shortcut
local<<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mu - 060string.mu</title>
<meta name="Generator" content="Vim/7.4">
<meta name="plugin-version" content="vim7.4_v1">
<meta name="syntax" content="none">
<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
<meta name="colorscheme" content="minimal">
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
body { font-family: monospace; color: #eeeeee; background-color: #080808; }
* { font-size: 1.05em; }
.Special { color: #ff6060; }
.Comment { color: #9090ff; }
.Underlined { color: #c000c0; text-decoration: underline; }
.Identifier { color: #804000; }
-->
</style>
<script type='text/javascript'>
<!--
-->
</script>
</head>
<body>
<pre id='vimCodeElement'>
<span class="Comment"># Some useful helpers for dealing with strings.</span>
recipe string-equal [
<span class="Underlined">local</span>-scope
a:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
a-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *a
b:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
b-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *b
<span class="Comment"># compare lengths</span>
{
trace 99, [string-equal], [comparing lengths]
<span class="Identifier">length</span>-equal?:boolean<span class="Special"> <- </span>equal a-len, b-len
break-if <span class="Identifier">length</span>-equal?
reply 0
}
<span class="Comment"># compare each corresponding character</span>
trace 99, [string-equal], [comparing characters]
i:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
done?:boolean<span class="Special"> <- </span>greater-or-equal i, a-len
break-if done?
a2:character<span class="Special"> <- </span>index *a, i
b2:character<span class="Special"> <- </span>index *b, i
{
chars-<span class="Identifier">match</span>?:boolean<span class="Special"> <- </span>equal a2, b2
break-if chars-<span class="Identifier">match</span>?
reply 0
}
i<span class="Special"> <- </span>add i, 1
loop
}
reply 1
]
scenario string-equal-reflexive [
run [
default-space:address:<span class="Identifier">array</span>:location<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, 30
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
3:boolean/<span class="Special">raw <- </span>string-equal x, x
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># x == x for all x</span>
]
]
scenario string-equal-identical [
run [
default-space:address:<span class="Identifier">array</span>:location<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, 30
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
y:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
3:boolean/<span class="Special">raw <- </span>string-equal x, y
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># abc == abc</span>
]
]
scenario string-equal-distinct-lengths [
run [
default-space:address:<span class="Identifier">array</span>:location<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, 30
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
y:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abcd]
3:boolean/<span class="Special">raw <- </span>string-equal x, y
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># abc != abcd</span>
]
trace-should-contain [
string-equal: comparing lengths
]
trace-should-not-contain [
string-equal: comparing characters
]
]
scenario string-equal-with-empty [
run [
default-space:address:<span class="Identifier">array</span>:location<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, 30
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
y:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abcd]
3:boolean/<span class="Special">raw <- </span>string-equal x, y
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># "" != abcd</span>
]
]
scenario string-equal-common-lengths-but-distinct [
run [
default-space:address:<span class="Identifier">array</span>:location<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, 30
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
y:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abd]
3:boolean/<span class="Special">raw <- </span>string-equal x, y
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># abc != abd</span>
]
]
<span class="Comment"># A new type to help incrementally construct strings.</span>
container buffer [
<span class="Identifier">length</span>:number
data:address:<span class="Identifier">array</span>:character
]
recipe <span class="Identifier">new</span>-buffer [
<span class="Underlined">local</span>-scope
result:address:buffer<span class="Special"> <- </span><span class="Identifier">new</span> buffer:<span class="Identifier">type</span>
len:address:number<span class="Special"> <- </span>get-address *result, <span class="Identifier">length</span>:offset
*len:address:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
s:address:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get-address *result, data:offset
capacity:number, found?:boolean<span class="Special"> <- </span>next-ingredient
<span class="Identifier">assert</span> found?, [<span class="Identifier">new</span>-buffer must get a capacity argument]
*s<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, capacity
reply result
]
recipe grow-buffer [
<span class="Underlined">local</span>-scope
<span class="Identifier">in</span>:address:buffer<span class="Special"> <- </span>next-ingredient
<span class="Comment"># double buffer size</span>
x:address:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get-address *<span class="Identifier">in</span>, data:offset
oldlen:number<span class="Special"> <- </span><span class="Identifier">length</span> **x
newlen:number<span class="Special"> <- </span>multiply oldlen, 2
olddata:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *x
*x<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, newlen
<span class="Comment"># copy old contents</span>
i:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
done?:boolean<span class="Special"> <- </span>greater-or-equal i, oldlen
break-if done?
src:character<span class="Special"> <- </span>index *olddata, i
dest:address:character<span class="Special"> <- </span>index-address **x, i
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> src
i<span class="Special"> <- </span>add i, 1
loop
}
reply <span class="Identifier">in</span>
]
recipe buffer-full? [
<span class="Underlined">local</span>-scope
<span class="Identifier">in</span>:address:buffer<span class="Special"> <- </span>next-ingredient
len:number<span class="Special"> <- </span>get *<span class="Identifier">in</span>, <span class="Identifier">length</span>:offset
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *<span class="Identifier">in</span>, data:offset
capacity:number<span class="Special"> <- </span><span class="Identifier">length</span> *s
result:boolean<span class="Special"> <- </span>greater-or-equal len, capacity
reply result
]
<span class="Comment"># in <- buffer-append in:address:buffer, c:character</span>
recipe buffer-<span class="Identifier">append</span> [
<span class="Underlined">local</span>-scope
<span class="Identifier">in</span>:address:buffer<span class="Special"> <- </span>next-ingredient
c:character<span class="Special"> <- </span>next-ingredient
len:address:number<span class="Special"> <- </span>get-address *<span class="Identifier">in</span>, <span class="Identifier">length</span>:offset
{
<span class="Comment"># backspace? just drop last character if it exists and return</span>
backspace?:boolean<span class="Special"> <- </span>equal c, 8/backspace
break-unless backspace?
empty?:boolean<span class="Special"> <- </span>lesser-or-equal *len, 0
reply-if empty?, <span class="Identifier">in</span>/same-as-ingredient:0
*len<span class="Special"> <- </span>subtract *len, 1
reply <span class="Identifier">in</span>/same-as-ingredient:0
}
{
<span class="Comment"># grow buffer if necessary</span>
full?:boolean<span class="Special"> <- </span>buffer-full? <span class="Identifier">in</span>
break-unless full?
<span class="Identifier">in</span><span class="Special"> <- </span>grow-buffer <span class="Identifier">in</span>
}
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *<span class="Identifier">in</span>, data:offset
dest:address:character<span class="Special"> <- </span>index-address *s, *len
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> c
*len<span class="Special"> <- </span>add *len, 1
reply <span class="Identifier">in</span>/same-as-ingredient:0
]
scenario buffer-<span class="Identifier">append</span>-works [
run [
<span class="Underlined">local</span>-scope
x:address:buffer<span class="Special"> <- </span><span class="Identifier">new</span>-buffer 3
s1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *x:address:buffer, data:offset
x:address:buffer<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x:address:buffer, 97 <span class="Comment"># 'a'</span>
x:address:buffer<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x:address:buffer, 98 <span class="Comment"># 'b'</span>
x:address:buffer<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x:address:buffer, 99 <span class="Comment"># 'c'</span>
s2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *x:address:buffer, data:offset
1:boolean/<span class="Special">raw <- </span>equal s1:address:<span class="Identifier">array</span>:character, s2:address:<span class="Identifier">array</span>:character
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *s2:address:<span class="Identifier">array</span>:character
+buffer-filled
x:address:buffer<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x:address:buffer, 100 <span class="Comment"># 'd'</span>
s3:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *x:address:buffer, data:offset
10:boolean/<span class="Special">raw <- </span>equal s1:address:<span class="Identifier">array</span>:character, s3:address:<span class="Identifier">array</span>:character
11:number/<span class="Special">raw <- </span>get *x:address:buffer, <span class="Identifier">length</span>:offset
12:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *s3:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
<span class="Comment"># before +buffer-filled</span>
1<span class="Special"> <- </span>1 <span class="Comment"># no change in data pointer</span>
2<span class="Special"> <- </span>3 <span class="Comment"># size of data</span>
3<span class="Special"> <- </span>97 <span class="Comment"># data</span>
4<span class="Special"> <- </span>98
5<span class="Special"> <- </span>99
<span class="Comment"># in the end</span>
10<span class="Special"> <- </span>0 <span class="Comment"># data pointer has grown</span>
11<span class="Special"> <- </span>4 <span class="Comment"># final length</span>
12<span class="Special"> <- </span>6 <span class="Comment"># but data's capacity has doubled</span>
13<span class="Special"> <- </span>97 <span class="Comment"># data</span>
14<span class="Special"> <- </span>98
15<span class="Special"> <- </span>99
16<span class="Special"> <- </span>100
17<span class="Special"> <- </span>0
18<span class="Special"> <- </span>0
]
]
scenario buffer-<span class="Identifier">append</span>-handles-backspace [
run [
<span class="Underlined">local</span>-scope
x:address:buffer<span class="Special"> <- </span><span class="Identifier">new</span>-buffer 3
x<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x, 97 <span class="Comment"># 'a'</span>
x<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x, 98 <span class="Comment"># 'b'</span>
x<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> x, 8/backspace
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>buffer-to-<span class="Identifier">array</span> x
1:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *s
]
memory-should-contain [
1<span class="Special"> <- </span>1 <span class="Comment"># length</span>
2<span class="Special"> <- </span>97 <span class="Comment"># contents</span>
3<span class="Special"> <- </span>0
]
]
<span class="Comment"># result:address:array:character <- integer-to-decimal-string n:number</span>
recipe integer-to-decimal-string [
<span class="Underlined">local</span>-scope
n:number<span class="Special"> <- </span>next-ingredient
<span class="Comment"># is it zero?</span>
{
break-if n
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [0]
reply result
}
<span class="Comment"># save sign</span>
negate-result:boolean<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
negative?:boolean<span class="Special"> <- </span>lesser-than n, 0
break-unless negative?
negate-result<span class="Special"> <- </span><span class="Identifier">copy</span> 1
n<span class="Special"> <- </span>multiply n, -1
}
<span class="Comment"># add digits from right to left into intermediate buffer</span>
tmp:address:buffer<span class="Special"> <- </span><span class="Identifier">new</span>-buffer 30
digit-base:number<span class="Special"> <- </span><span class="Identifier">copy</span> 48 <span class="Comment"># '0'</span>
{
done?:boolean<span class="Special"> <- </span>equal n, 0
break-if done?
n, digit:number<span class="Special"> <- </span><span class="Identifier">divide</span>-with-remainder n, 10
c:character<span class="Special"> <- </span>add digit-base, digit
tmp:address:buffer<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> tmp, c
loop
}
<span class="Comment"># add sign</span>
{
break-unless negate-result:boolean
tmp<span class="Special"> <- </span>buffer-<span class="Identifier">append</span> tmp, 45 <span class="Comment"># '-'</span>
}
<span class="Comment"># reverse buffer into string result</span>
len:number<span class="Special"> <- </span>get *tmp, <span class="Identifier">length</span>:offset
buf:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *tmp, data:offset
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, len
i:number<span class="Special"> <- </span>subtract len, 1 <span class="Comment"># source index, decreasing</span>
j:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0 <span class="Comment"># destination index, increasing</span>
{
<span class="Comment"># while i >= 0</span>
done?:boolean<span class="Special"> <- </span>lesser-than i, 0
break-if done?
<span class="Comment"># result[j] = tmp[i]</span>
src:character<span class="Special"> <- </span>index *buf, i
dest:address:character<span class="Special"> <- </span>index-address *result, j
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> src
i<span class="Special"> <- </span>subtract i, 1
j<span class="Special"> <- </span>add j, 1
loop
}
reply result
]
recipe buffer-to-<span class="Identifier">array</span> [
<span class="Underlined">local</span>-scope
<span class="Identifier">in</span>:address:buffer<span class="Special"> <- </span>next-ingredient
{
<span class="Comment"># propagate null buffer</span>
break-if <span class="Identifier">in</span>
reply 0
}
len:number<span class="Special"> <- </span>get *<span class="Identifier">in</span>, <span class="Identifier">length</span>:offset
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>get *<span class="Identifier">in</span>, data:offset
<span class="Comment"># we can't just return s because it is usually the wrong length</span>
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, len
i:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
done?:boolean<span class="Special"> <- </span>greater-or-equal i, len
break-if done?
src:character<span class="Special"> <- </span>index *s, i
dest:address:character<span class="Special"> <- </span>index-address *result, i
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> src
i<span class="Special"> <- </span>add i, 1
loop
}
reply result
]
scenario integer-to-decimal-digit-zero [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>integer-to-decimal-string 0
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[0]
]
]
scenario integer-to-decimal-digit-positive [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>integer-to-decimal-string 234
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[234]
]
]
scenario integer-to-decimal-digit-negative [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>integer-to-decimal-string -1
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2<span class="Special"> <- </span>2
3<span class="Special"> <- </span>45 <span class="Comment"># '-'</span>
4<span class="Special"> <- </span>49 <span class="Comment"># '1'</span>
]
]
<span class="Comment"># result:address:array:character <- string-append a:address:array:character, b:address:array:character</span>
recipe string-<span class="Identifier">append</span> [
<span class="Underlined">local</span>-scope
<span class="Comment"># result = new character[a.length + b.length]</span>
a:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
a-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *a
b:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
b-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *b
result-len:number<span class="Special"> <- </span>add a-len, b-len
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, result-len
<span class="Comment"># copy a into result</span>
result-idx:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
i:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
<span class="Comment"># while i < a.length</span>
a-done?:boolean<span class="Special"> <- </span>greater-or-equal i, a-len
break-if a-done?
<span class="Comment"># result[result-idx] = a[i]</span>
out:address:character<span class="Special"> <- </span>index-address *result, result-idx
<span class="Identifier">in</span>:character<span class="Special"> <- </span>index *a, i
*out<span class="Special"> <- </span><span class="Identifier">copy</span> <span class="Identifier">in</span>
i<span class="Special"> <- </span>add i, 1
result-idx<span class="Special"> <- </span>add result-idx, 1
loop
}
<span class="Comment"># copy b into result</span>
i<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
<span class="Comment"># while i < b.length</span>
b-done?:boolean<span class="Special"> <- </span>greater-or-equal i, b-len
break-if b-done?
<span class="Comment"># result[result-idx] = a[i]</span>
out:address:character<span class="Special"> <- </span>index-address *result, result-idx
<span class="Identifier">in</span>:character<span class="Special"> <- </span>index *b, i
*out<span class="Special"> <- </span><span class="Identifier">copy</span> <span class="Identifier">in</span>
i<span class="Special"> <- </span>add i, 1
result-idx<span class="Special"> <- </span>add result-idx, 1
loop
}
reply result
]
scenario string-<span class="Identifier">append</span>-1 [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [hello,]
2:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [ world!]
3:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>string-<span class="Identifier">append</span> 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 2:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
4:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *3:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
4:string<span class="Special"> <- </span>[hello, world!]
]
]
scenario replace-character-<span class="Identifier">in</span>-string [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc]
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>string-replace 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 98/b, 122/z
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[azc]
]
]
recipe string-replace [
<span class="Underlined">local</span>-scope
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
oldc:character<span class="Special"> <- </span>next-ingredient
newc:character<span class="Special"> <- </span>next-ingredient
from:number<span class="Special"> <- </span>next-ingredient
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *s
i:number<span class="Special"> <- </span>find-next s, oldc, from
done?:boolean<span class="Special"> <- </span>greater-or-equal i, len
reply-if done?, s/same-as-ingredient:0
dest:address:character<span class="Special"> <- </span>index-address *s, i
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> newc
i<span class="Special"> <- </span>add i, 1
s<span class="Special"> <- </span>string-replace s, oldc, newc, i
reply s/same-as-ingredient:0
]
scenario replace-character-at-start [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc]
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>string-replace 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 97/a, 122/z
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[zbc]
]
]
scenario replace-character-at-end [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc]
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>string-replace 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 99/c, 122/z
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[abz]
]
]
scenario replace-character-missing [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc]
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>string-replace 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 100/d, 122/z
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[abc]
]
]
scenario replace-all-characters [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [banana]
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span>string-replace 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 97/a, 122/z
2:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
2:string<span class="Special"> <- </span>[bznznz]
]
]
<span class="Comment"># replace underscores in first with remaining args</span>
<span class="Comment"># result:address:array:character <- interpolate template:address:array:character, ...</span>
recipe <span class="Identifier">interpolate</span> [
<span class="Underlined">local</span>-scope
template:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
<span class="Comment"># compute result-len, space to allocate for result</span>
tem-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *template
result-len:number<span class="Special"> <- </span><span class="Identifier">copy</span> tem-len
{
<span class="Comment"># while arg received</span>
a:address:<span class="Identifier">array</span>:character, <span class="Identifier">arg</span>-received?:boolean<span class="Special"> <- </span>next-ingredient
break-unless <span class="Identifier">arg</span>-received?
<span class="Comment"># result-len = result-len + arg.length - 1 (for the 'underscore' being replaced)</span>
a-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *a
result-len<span class="Special"> <- </span>add result-len, a-len
result-len<span class="Special"> <- </span>subtract result-len, 1
loop
}
rewind-ingredients
_<span class="Special"> <- </span>next-ingredient <span class="Comment"># skip template</span>
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, result-len
<span class="Comment"># repeatedly copy sections of template and 'holes' into result</span>
result-idx:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
i:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
<span class="Comment"># while arg received</span>
a:address:<span class="Identifier">array</span>:character, <span class="Identifier">arg</span>-received?:boolean<span class="Special"> <- </span>next-ingredient
break-unless <span class="Identifier">arg</span>-received?
<span class="Comment"># copy template into result until '_'</span>
{
<span class="Comment"># while i < template.length</span>
tem-done?:boolean<span class="Special"> <- </span>greater-or-equal i, tem-len
break-if tem-done?, +done:label
<span class="Comment"># while template[i] != '_'</span>
<span class="Identifier">in</span>:character<span class="Special"> <- </span>index *template, i
underscore?:boolean<span class="Special"> <- </span>equal <span class="Identifier">in</span>, 95/_
break-if underscore?
<span class="Comment"># result[result-idx] = template[i]</span>
out:address:character<span class="Special"> <- </span>index-address *result, result-idx
*out<span class="Special"> <- </span><span class="Identifier">copy</span> <span class="Identifier">in</span>
i<span class="Special"> <- </span>add i, 1
result-idx<span class="Special"> <- </span>add result-idx, 1
loop
}
<span class="Comment"># copy 'a' into result</span>
j:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
<span class="Comment"># while j < a.length</span>
<span class="Identifier">arg</span>-done?:boolean<span class="Special"> <- </span>greater-or-equal j, a-len
break-if <span class="Identifier">arg</span>-done?
<span class="Comment"># result[result-idx] = a[j]</span>
<span class="Identifier">in</span>:character<span class="Special"> <- </span>index *a, j
out:address:character<span class="Special"> <- </span>index-address *result, result-idx
*out<span class="Special"> <- </span><span class="Identifier">copy</span> <span class="Identifier">in</span>
j<span class="Special"> <- </span>add j, 1
result-idx<span class="Special"> <- </span>add result-idx, 1
loop
}
<span class="Comment"># skip '_' in template</span>
i<span class="Special"> <- </span>add i, 1
loop <span class="Comment"># interpolate next arg</span>
}
+done
<span class="Comment"># done with holes; copy rest of template directly into result</span>
{
<span class="Comment"># while i < template.length</span>
tem-done?:boolean<span class="Special"> <- </span>greater-or-equal i, tem-len
break-if tem-done?
<span class="Comment"># result[result-idx] = template[i]</span>
<span class="Identifier">in</span>:character<span class="Special"> <- </span>index *template, i
out:address:character<span class="Special"> <- </span>index-address *result, result-idx:number
*out<span class="Special"> <- </span><span class="Identifier">copy</span> <span class="Identifier">in</span>
i<span class="Special"> <- </span>add i, 1
result-idx<span class="Special"> <- </span>add result-idx, 1
loop
}
reply result
]
scenario <span class="Identifier">interpolate</span>-works [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc _]
2:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [def]
3:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">interpolate</span> 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 2:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
4:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *3:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
4:string<span class="Special"> <- </span>[abc def]
]
]
scenario <span class="Identifier">interpolate</span>-at-start [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [_, hello!]
2:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc]
3:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">interpolate</span> 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 2:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
4:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *3:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
4:string<span class="Special"> <- </span>[abc, hello!]
16<span class="Special"> <- </span>0 <span class="Comment"># out of bounds</span>
]
]
scenario <span class="Identifier">interpolate</span>-at-end [
run [
1:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [hello, _]
2:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">new</span> [abc]
3:address:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">interpolate</span> 1:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>, 2:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
4:<span class="Identifier">array</span>:character/<span class="Special">raw <- </span><span class="Identifier">copy</span> *3:address:<span class="Identifier">array</span>:character/<span class="Special">raw</span>
]
memory-should-contain [
4:string<span class="Special"> <- </span>[hello, abc]
]
]
<span class="Comment"># result:boolean <- space? c:character</span>
recipe space? [
<span class="Underlined">local</span>-scope
c:character<span class="Special"> <- </span>next-ingredient
<span class="Comment"># most common case first</span>
result:boolean<span class="Special"> <- </span>equal c, 32/space
jump-if result +reply:label
result<span class="Special"> <- </span>equal c, 10/newline
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 9/tab
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 13/carriage-<span class="Identifier">return</span>
jump-if result, +reply:label
<span class="Comment"># remaining uncommon cases in sorted order</span>
<span class="Comment"># <a href="http://unicode.org">http://unicode.org</a> code-points in unicode-set Z and Pattern_White_Space</span>
result<span class="Special"> <- </span>equal c, 11/ctrl-k
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 12/ctrl-l
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 133/ctrl-0085
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 160/no-break-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 5760/ogham-space-mark
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8192/en-quad
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8193/em-quad
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8194/en-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8195/em-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8196/three-per-em-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8197/four-per-em-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8198/six-per-em-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8199/figure-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8200/punctuation-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8201/thin-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8202/hair-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8206/left-to-right
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8207/right-to-left
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8232/line-separator
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8233/paragraph-separator
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8239/narrow-no-break-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 8287/medium-mathematical-space
jump-if result, +reply:label
result<span class="Special"> <- </span>equal c, 12288/ideographic-space
jump-if result, +reply:label
+reply
reply result
]
<span class="Comment"># result:address:array:character <- trim s:address:array:character</span>
recipe trim [
<span class="Underlined">local</span>-scope
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *s
<span class="Comment"># left trim: compute start</span>
start:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
{
at-end?:boolean<span class="Special"> <- </span>greater-or-equal start, len
break-unless at-end?
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, 0
reply result
}
curr:character<span class="Special"> <- </span>index *s, start
whitespace?:boolean<span class="Special"> <- </span>space? curr
break-unless whitespace?
start<span class="Special"> <- </span>add start, 1
loop
}
<span class="Comment"># right trim: compute end</span>
end:number<span class="Special"> <- </span>subtract len, 1
{
not-at-start?:boolean<span class="Special"> <- </span>greater-than end, start
<span class="Identifier">assert</span> not-at-start?, [end ran up against start]
curr:character<span class="Special"> <- </span>index *s, end
whitespace?:boolean<span class="Special"> <- </span>space? curr
break-unless whitespace?
end<span class="Special"> <- </span>subtract end, 1
loop
}
<span class="Comment"># result = new character[end+1 - start]</span>
<span class="Identifier">new</span>-len:number<span class="Special"> <- </span>subtract end, start, -1
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, <span class="Identifier">new</span>-len
<span class="Comment"># copy the untrimmed parts between start and end</span>
i:number<span class="Special"> <- </span><span class="Identifier">copy</span> start
j:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
<span class="Comment"># while i <= end</span>
done?:boolean<span class="Special"> <- </span>greater-than i, end
break-if done?
<span class="Comment"># result[j] = s[i]</span>
src:character<span class="Special"> <- </span>index *s, i
dest:address:character<span class="Special"> <- </span>index-address *result, j
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> src
i<span class="Special"> <- </span>add i, 1
j<span class="Special"> <- </span>add j, 1
loop
}
reply result
]
scenario trim-unmodified [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>trim 1:address:<span class="Identifier">array</span>:character
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[abc]
]
]
scenario trim-left [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [ abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>trim 1:address:<span class="Identifier">array</span>:character
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[abc]
]
]
scenario trim-right [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc ]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>trim 1:address:<span class="Identifier">array</span>:character
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[abc]
]
]
scenario trim-left-right [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [ abc ]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>trim 1:address:<span class="Identifier">array</span>:character
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[abc]
]
]
scenario trim-newline-tab [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [ abc
]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>trim 1:address:<span class="Identifier">array</span>:character
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[abc]
]
]
<span class="Comment"># next-index:number <- find-next text:address:array:character, pattern:character, idx:number</span>
recipe find-next [
<span class="Underlined">local</span>-scope
text:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
pattern:character<span class="Special"> <- </span>next-ingredient
idx:number<span class="Special"> <- </span>next-ingredient
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *text
{
eof?:boolean<span class="Special"> <- </span>greater-or-equal idx, len
break-if eof?
curr:character<span class="Special"> <- </span>index *text, idx
found?:boolean<span class="Special"> <- </span>equal curr, pattern
break-if found?
idx<span class="Special"> <- </span>add idx, 1
loop
}
reply idx
]
scenario string-find-next [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [a/b]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 0/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>1
]
]
scenario string-find-next-empty [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 0/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>0
]
]
scenario string-find-next-initial [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [/abc]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 0/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>0 <span class="Comment"># prefix match</span>
]
]
scenario string-find-next-final [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc/]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 0/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>3 <span class="Comment"># suffix match</span>
]
]
scenario string-find-next-missing [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 0/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>3 <span class="Comment"># no match</span>
]
]
scenario string-find-next-invalid-index [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 4/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>4 <span class="Comment"># no change</span>
]
]
scenario string-find-next-first [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [ab/c/]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 0/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>2 <span class="Comment"># first '/' of multiple</span>
]
]
scenario string-find-next-second [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [ab/c/]
2:number<span class="Special"> <- </span>find-next 1:address:<span class="Identifier">array</span>:character, 47/slash, 3/start-index
]
memory-should-contain [
2<span class="Special"> <- </span>4 <span class="Comment"># second '/' of multiple</span>
]
]
<span class="Comment"># next-index:number <- find-substring text:address:array:character, pattern:address:array:character, idx:number</span>
<span class="Comment"># like find-next, but searches for multiple characters</span>
<span class="Comment"># fairly dumb algorithm</span>
recipe find-<span class="Identifier">substring</span> [
<span class="Underlined">local</span>-scope
text:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
pattern:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
idx:number<span class="Special"> <- </span>next-ingredient
first:character<span class="Special"> <- </span>index *pattern, 0
<span class="Comment"># repeatedly check for match at current idx</span>
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *text
{
<span class="Comment"># does some unnecessary work checking for substrings even when there isn't enough of text left</span>
done?:boolean<span class="Special"> <- </span>greater-or-equal idx, len
break-if done?
found?:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at text, pattern, idx
break-if found?
idx<span class="Special"> <- </span>add idx, 1
<span class="Comment"># optimization: skip past indices that definitely won't match</span>
idx<span class="Special"> <- </span>find-next text, first, idx
loop
}
reply idx
]
scenario find-<span class="Identifier">substring</span>-1 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [bc]
3:number<span class="Special"> <- </span>find-<span class="Identifier">substring</span> 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>1
]
]
scenario find-<span class="Identifier">substring</span>-2 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abcd]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [bc]
3:number<span class="Special"> <- </span>find-<span class="Identifier">substring</span> 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 1
]
memory-should-contain [
3<span class="Special"> <- </span>1
]
]
scenario find-<span class="Identifier">substring</span>-no-<span class="Identifier">match</span> [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [bd]
3:number<span class="Special"> <- </span>find-<span class="Identifier">substring</span> 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>3 <span class="Comment"># not found</span>
]
]
scenario find-<span class="Identifier">substring</span>-suffix-<span class="Identifier">match</span> [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abcd]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [cd]
3:number<span class="Special"> <- </span>find-<span class="Identifier">substring</span> 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>2
]
]
scenario find-<span class="Identifier">substring</span>-suffix-<span class="Identifier">match</span>-2 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abcd]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [cde]
3:number<span class="Special"> <- </span>find-<span class="Identifier">substring</span> 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>4 <span class="Comment"># not found</span>
]
]
<span class="Comment"># result:boolean <- match-at text:address:array:character, pattern:address:array:character, idx:number</span>
<span class="Comment"># checks if substring matches at index 'idx'</span>
recipe <span class="Identifier">match</span>-at [
<span class="Underlined">local</span>-scope
text:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
pattern:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
idx:number<span class="Special"> <- </span>next-ingredient
pattern-len:number<span class="Special"> <- </span><span class="Identifier">length</span> *pattern
<span class="Comment"># check that there's space left for the pattern</span>
{
x:number<span class="Special"> <- </span><span class="Identifier">length</span> *text
x<span class="Special"> <- </span>subtract x, pattern-len
enough-room?:boolean<span class="Special"> <- </span>lesser-or-equal idx, x
break-if enough-room?
reply 0/not-found
}
<span class="Comment"># check each character of pattern</span>
pattern-idx:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
done?:boolean<span class="Special"> <- </span>greater-or-equal pattern-idx, pattern-len
break-if done?
c:character<span class="Special"> <- </span>index *text, idx
<span class="Identifier">exp</span>:character<span class="Special"> <- </span>index *pattern, pattern-idx
{
<span class="Identifier">match</span>?:boolean<span class="Special"> <- </span>equal c, <span class="Identifier">exp</span>
break-if <span class="Identifier">match</span>?
reply 0/not-found
}
idx<span class="Special"> <- </span>add idx, 1
pattern-idx<span class="Special"> <- </span>add pattern-idx, 1
loop
}
reply 1/found
]
scenario <span class="Identifier">match</span>-at-checks-<span class="Identifier">substring</span>-at-index [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [ab]
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># match found</span>
]
]
scenario <span class="Identifier">match</span>-at-reflexive [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 1:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># match found</span>
]
]
scenario <span class="Identifier">match</span>-at-outside-bounds [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [a]
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 4
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># never matches</span>
]
]
scenario <span class="Identifier">match</span>-at-empty-pattern [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># always matches empty pattern given a valid index</span>
]
]
scenario <span class="Identifier">match</span>-at-empty-pattern-outside-bound [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 4
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># no match</span>
]
]
scenario <span class="Identifier">match</span>-at-empty-text [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># no match</span>
]
]
scenario <span class="Identifier">match</span>-at-empty-against-empty [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 1:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># matches because pattern is also empty</span>
]
]
scenario <span class="Identifier">match</span>-at-inside-bounds [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [bc]
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 1
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># matches inner substring</span>
]
]
scenario <span class="Identifier">match</span>-at-inside-bounds-2 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [bc]
3:boolean<span class="Special"> <- </span><span class="Identifier">match</span>-at 1:address:<span class="Identifier">array</span>:character, 2:address:<span class="Identifier">array</span>:character, 0
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># no match</span>
]
]
<span class="Comment"># result:address:array:address:array:character <- split s:address:array:character, delim:character</span>
recipe <span class="Identifier">split</span> [
<span class="Underlined">local</span>-scope
s:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
delim:character<span class="Special"> <- </span>next-ingredient
<span class="Comment"># empty string? return empty array</span>
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *s
{
empty?:boolean<span class="Special"> <- </span>equal len, 0
break-unless empty?
result:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, 0
reply result
}
<span class="Comment"># count #pieces we need room for</span>
count:number<span class="Special"> <- </span><span class="Identifier">copy</span> 1 <span class="Comment"># n delimiters = n+1 pieces</span>
idx:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
idx<span class="Special"> <- </span>find-next s, delim, idx
done?:boolean<span class="Special"> <- </span>greater-or-equal idx, len
break-if done?
idx<span class="Special"> <- </span>add idx, 1
count<span class="Special"> <- </span>add count, 1
loop
}
<span class="Comment"># allocate space</span>
result:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> location:<span class="Identifier">type</span>, count
<span class="Comment"># repeatedly copy slices start..end until delimiter into result[curr-result]</span>
curr-result:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
start:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
<span class="Comment"># while next delim exists</span>
done?:boolean<span class="Special"> <- </span>greater-or-equal start, len
break-if done?
end:number<span class="Special"> <- </span>find-next s, delim, start
<span class="Comment"># copy start..end into result[curr-result]</span>
dest:address:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index-address *result, curr-result
*dest<span class="Special"> <- </span>string-<span class="Identifier">copy</span> s, start, end
<span class="Comment"># slide over to next slice</span>
start<span class="Special"> <- </span>add end, 1
curr-result<span class="Special"> <- </span>add curr-result, 1
loop
}
reply result
]
scenario string-<span class="Identifier">split</span>-1 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [a/b]
2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">split</span> 1:address:<span class="Identifier">array</span>:character, 47/slash
3:number<span class="Special"> <- </span><span class="Identifier">length</span> *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character
4:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 0
5:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 1
10:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *4:address:<span class="Identifier">array</span>:character
20:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *5:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3<span class="Special"> <- </span>2 <span class="Comment"># length of result</span>
10:string<span class="Special"> <- </span>[a]
20:string<span class="Special"> <- </span>[b]
]
]
scenario string-<span class="Identifier">split</span>-2 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [a/b/c]
2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">split</span> 1:address:<span class="Identifier">array</span>:character, 47/slash
3:number<span class="Special"> <- </span><span class="Identifier">length</span> *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character
4:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 0
5:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 1
6:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 2
10:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *4:address:<span class="Identifier">array</span>:character
20:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *5:address:<span class="Identifier">array</span>:character
30:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *6:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3<span class="Special"> <- </span>3 <span class="Comment"># length of result</span>
10:string<span class="Special"> <- </span>[a]
20:string<span class="Special"> <- </span>[b]
30:string<span class="Special"> <- </span>[c]
]
]
scenario string-<span class="Identifier">split</span>-missing [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">split</span> 1:address:<span class="Identifier">array</span>:character, 47/slash
3:number<span class="Special"> <- </span><span class="Identifier">length</span> *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character
4:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 0
10:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *4:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3<span class="Special"> <- </span>1 <span class="Comment"># length of result</span>
10:string<span class="Special"> <- </span>[abc]
]
]
scenario string-<span class="Identifier">split</span>-empty [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">split</span> 1:address:<span class="Identifier">array</span>:character, 47/slash
3:number<span class="Special"> <- </span><span class="Identifier">length</span> *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3<span class="Special"> <- </span>0 <span class="Comment"># empty result</span>
]
]
scenario string-<span class="Identifier">split</span>-empty-piece [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [a/b<span class="Comment">//c]</span>
2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">split</span> 1:address:<span class="Identifier">array</span>:character, 47/slash
3:number<span class="Special"> <- </span><span class="Identifier">length</span> *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character
4:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 0
5:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 1
6:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 2
7:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>index *2:address:<span class="Identifier">array</span>:address:<span class="Identifier">array</span>:character, 3
10:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *4:address:<span class="Identifier">array</span>:character
20:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *5:address:<span class="Identifier">array</span>:character
30:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *6:address:<span class="Identifier">array</span>:character
40:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *7:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3<span class="Special"> <- </span>4 <span class="Comment"># length of result</span>
10:string<span class="Special"> <- </span>[a]
20:string<span class="Special"> <- </span>[b]
30:string<span class="Special"> <- </span>[]
40:string<span class="Special"> <- </span>[c]
]
]
<span class="Comment"># x:address:array:character, y:address:array:character <- split-first text:address:array:character, delim:character</span>
recipe <span class="Identifier">split</span>-first [
<span class="Underlined">local</span>-scope
text:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
delim:character<span class="Special"> <- </span>next-ingredient
<span class="Comment"># empty string? return empty strings</span>
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *text
{
empty?:boolean<span class="Special"> <- </span>equal len, 0
break-unless empty?
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
y:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> []
reply x, y
}
idx:number<span class="Special"> <- </span>find-next text, delim, 0
x:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>string-<span class="Identifier">copy</span> text, 0, idx
idx<span class="Special"> <- </span>add idx, 1
y:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>string-<span class="Identifier">copy</span> text, idx, len
reply x, y
]
scenario string-<span class="Identifier">split</span>-first [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [a/b]
2:address:<span class="Identifier">array</span>:character, 3:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">split</span>-first 1:address:<span class="Identifier">array</span>:character, 47/slash
10:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
20:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *3:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
10:string<span class="Special"> <- </span>[a]
20:string<span class="Special"> <- </span>[b]
]
]
<span class="Comment"># result:address:array:character <- string-copy buf:address:array:character, start:number, end:number</span>
<span class="Comment"># todo: make this generic</span>
recipe string-<span class="Identifier">copy</span> [
<span class="Underlined">local</span>-scope
buf:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>next-ingredient
start:number<span class="Special"> <- </span>next-ingredient
end:number<span class="Special"> <- </span>next-ingredient
<span class="Comment"># if end is out of bounds, trim it</span>
len:number<span class="Special"> <- </span><span class="Identifier">length</span> *buf
end:number<span class="Special"> <- </span><span class="Identifier">min</span> len, end
<span class="Comment"># allocate space for result</span>
len<span class="Special"> <- </span>subtract end, start
result:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> character:<span class="Identifier">type</span>, len
<span class="Comment"># copy start..end into result[curr-result]</span>
src-idx:number<span class="Special"> <- </span><span class="Identifier">copy</span> start
dest-idx:number<span class="Special"> <- </span><span class="Identifier">copy</span> 0
{
done?:boolean<span class="Special"> <- </span>greater-or-equal src-idx, end
break-if done?
src:character<span class="Special"> <- </span>index *buf, src-idx
dest:address:character<span class="Special"> <- </span>index-address *result, dest-idx
*dest<span class="Special"> <- </span><span class="Identifier">copy</span> src
src-idx<span class="Special"> <- </span>add src-idx, 1
dest-idx<span class="Special"> <- </span>add dest-idx, 1
loop
}
reply result
]
scenario string-<span class="Identifier">copy</span>-copies-<span class="Identifier">substring</span> [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>string-<span class="Identifier">copy</span> 1:address:<span class="Identifier">array</span>:character, 1, 3
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[bc]
]
]
scenario string-<span class="Identifier">copy</span>-out-of-bounds [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>string-<span class="Identifier">copy</span> 1:address:<span class="Identifier">array</span>:character, 2, 4
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[c]
]
]
scenario string-<span class="Identifier">copy</span>-out-of-bounds-2 [
run [
1:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">new</span> [abc]
2:address:<span class="Identifier">array</span>:character<span class="Special"> <- </span>string-<span class="Identifier">copy</span> 1:address:<span class="Identifier">array</span>:character, 3, 3
3:<span class="Identifier">array</span>:character<span class="Special"> <- </span><span class="Identifier">copy</span> *2:address:<span class="Identifier">array</span>:character
]
memory-should-contain [
3:string<span class="Special"> <- </span>[]
]
]
recipe <span class="Identifier">min</span> [
<span class="Underlined">local</span>-scope
x:number<span class="Special"> <- </span>next-ingredient
y:number<span class="Special"> <- </span>next-ingredient
{
<span class="Identifier">return</span>-x?:boolean<span class="Special"> <- </span>lesser-than x, y
break-if <span class="Identifier">return</span>-x?
reply y
}
reply x
]
recipe <span class="Identifier">max</span> [
<span class="Underlined">local</span>-scope
x:number<span class="Special"> <- </span>next-ingredient
y:number<span class="Special"> <- </span>next-ingredient
{
<span class="Identifier">return</span>-x?:boolean<span class="Special"> <- </span>greater-than x, y
break-if <span class="Identifier">return</span>-x?
reply y
}
reply x
]
</pre>
</body>
</html>
<!-- vim: set foldmethod=manual : -->