about summary refs log tree commit diff stats
path: root/src/io
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-09-14 01:41:47 +0200
committerbptato <nincsnevem662@gmail.com>2023-09-14 02:01:21 +0200
commitc1b8338045716b25d664c0b8dd91eac0cb76480e (patch)
treea9c0a6763f180c2b6dd380aa880253ffc7685d85 /src/io
parentdb0798acccbedcef4b16737f6be0cf7388cc0528 (diff)
downloadchawan-c1b8338045716b25d664c0b8dd91eac0cb76480e.tar.gz
move around more modules
* ips -> io/
* loader related stuff -> loader/
* tempfile -> extern/
* buffer, forkserver -> server/
* lineedit, window -> display/
* cell -> types/
* opt -> types/
Diffstat (limited to 'src/io')
-rw-r--r--src/io/about.nim28
-rw-r--r--src/io/connecterror.nim18
-rw-r--r--src/io/data.nim43
-rw-r--r--src/io/file.nim107
-rw-r--r--src/io/headers.nim100
-rw-r--r--src/io/http.nim142
-rw-r--r--src/io/lineedit.nim369
-rw-r--r--src/io/loader.nim394
-rw-r--r--src/io/loaderhandle.nim73
-rw-r--r--src/io/promise.nim2
-rw-r--r--src/io/request.nim332
-rw-r--r--src/io/response.nim74
-rw-r--r--src/io/serialize.nim444
-rw-r--r--src/io/serversocket.nim28
-rw-r--r--src/io/socketstream.nim147
-rw-r--r--src/io/tempfile.nim18
-rw-r--r--src/io/window.nim54
17 files changed, 620 insertions, 1753 deletions
diff --git a/src/io/about.nim b/src/io/about.nim
deleted file mode 100644
index 737a291b..00000000
--- a/src/io/about.nim
+++ /dev/null
@@ -1,28 +0,0 @@
-import tables
-
-import io/connecterror
-import io/headers
-import io/loaderhandle
-import io/request
-import types/url
-
-const chawan = staticRead"res/chawan.html"
-const HeaderTable = {
-  "Content-Type": "text/html"
-}.toTable()
-
-proc loadAbout*(handle: LoaderHandle, request: Request) =
-  template t(body: untyped) =
-    if not body:
-      return
-  if request.url.pathname == "blank":
-    t handle.sendResult(0)
-    t handle.sendStatus(200) # ok
-    t handle.sendHeaders(newHeaders(HeaderTable))
-  elif request.url.pathname == "chawan":
-    t handle.sendResult(0)
-    t handle.sendStatus(200) # ok
-    t handle.sendHeaders(newHeaders(HeaderTable))
-    t handle.sendData(chawan)
-  else:
-    t handle.sendResult(ERROR_ABOUT_PAGE_NOT_FOUND)
diff --git a/src/io/connecterror.nim b/src/io/connecterror.nim
deleted file mode 100644
index d2af5762..00000000
--- a/src/io/connecterror.nim
+++ /dev/null
@@ -1,18 +0,0 @@
-import bindings/curl
-
-type ConnectErrorCode* = enum
-  ERROR_INVALID_DATA_URL = (-7, "invalid data URL")
-  ERROR_ABOUT_PAGE_NOT_FOUND = (-6, "about page not found")
-  ERROR_FILE_NOT_FOUND = (-5, "file not found")
-  ERROR_SOURCE_NOT_FOUND = (-4, "clone source could not be found"),
-  ERROR_LOADER_KILLED = (-3, "loader killed during transfer"),
-  ERROR_DISALLOWED_URL = (-2, "url not allowed by filter"),
-  ERROR_UNKNOWN_SCHEME = (-1, "unknown scheme")
-
-converter toInt*(code: ConnectErrorCode): int =
-  return int(code)
-
-func getLoaderErrorMessage*(code: int): string =
-  if code < 0:
-    return $ConnectErrorCode(code)
-  return $curl_easy_strerror(CURLcode(cint(code)))
diff --git a/src/io/data.nim b/src/io/data.nim
deleted file mode 100644
index 3afe58f0..00000000
--- a/src/io/data.nim
+++ /dev/null
@@ -1,43 +0,0 @@
-import base64
-import strutils
-import tables
-
-import io/connecterror
-import io/headers
-import io/loaderhandle
-import io/request
-import types/url
-
-proc loadData*(handle: LoaderHandle, request: Request) =
-  template t(body: untyped) =
-    if not body:
-      return
-  var str = $request.url
-  let si = "data:".len # start index
-  var ct = ""
-  for i in si ..< str.len:
-    if str[i] == ',':
-      break
-    ct &= str[i]
-  let sd = si + ct.len + 1 # data start
-  if ct.endsWith(";base64"):
-    try:
-      let d = base64.decode(str[sd .. ^1]) # decode from ct end + 1
-      t handle.sendResult(0)
-      t handle.sendStatus(200)
-      ct.setLen(ct.len - ";base64".len) # remove base64 indicator
-      t handle.sendHeaders(newHeaders({
-        "Content-Type": ct
-      }.toTable()))
-      if d.len > 0:
-        t handle.sendData(d)
-    except ValueError:
-      discard handle.sendResult(ERROR_INVALID_DATA_URL)
-  else:
-    t handle.sendResult(0)
-    t handle.sendStatus(200)
-    t handle.sendHeaders(newHeaders({
-      "Content-Type": ct
-    }.toTable()))
-    if ct.len + 1 < str.len:
-      t handle.sendData(addr str[sd], str.len - sd)
diff --git a/src/io/file.nim b/src/io/file.nim
deleted file mode 100644
index fe732d6c..00000000
--- a/src/io/file.nim
+++ /dev/null
@@ -1,107 +0,0 @@
-import algorithm
-import os
-import streams
-import tables
-
-import io/connecterror
-import io/headers
-import io/loaderhandle
-import types/url
-
-proc loadDir(handle: LoaderHandle, url: URL, path: string) =
-  template t(body: untyped) =
-    if not body:
-      return
-  var path = path
-  if path[^1] != '/': #TODO dos/windows
-    path &= '/'
-  var base = $url
-  if base[^1] != '/': #TODO dos/windows
-    base &= '/'
-  t handle.sendResult(0)
-  t handle.sendStatus(200) # ok
-  t handle.sendHeaders(newHeaders({"Content-Type": "text/html"}.toTable()))
-  t handle.sendData("""
-<HTML>
-<HEAD>
-<BASE HREF="""" & base & """">
-<TITLE>Directory list of """ & path & """</TITLE>
-</HEAD>
-<BODY>
-<H1>Directory list of """ & path & """</H1>
-[DIR]&nbsp; <A HREF="../">../</A></br>
-""")
-  var fs: seq[(PathComponent, string)]
-  for pc, file in walkDir(path, relative = true):
-    fs.add((pc, file))
-  fs.sort(cmp = proc(a, b: (PathComponent, string)): int = cmp(a[1], b[1]))
-  for (pc, file) in fs:
-    case pc
-    of pcDir:
-      t handle.sendData("[DIR]&nbsp; ")
-    of pcFile:
-      t handle.sendData("[FILE] ")
-    of pcLinkToDir, pcLinkToFile:
-      t handle.sendData("[LINK] ")
-    var fn = file
-    if pc == pcDir:
-      fn &= '/'
-    t handle.sendData("<A HREF=\"" & fn & "\">" & fn & "</A>")
-    if pc in {pcLinkToDir, pcLinkToFile}:
-      discard handle.sendData(" -> " & expandSymlink(path / file))
-    t handle.sendData("<br>")
-  t handle.sendData("""
-</BODY>
-</HTML>""")
-
-proc loadSymlink(handle: LoaderHandle, path: string) =
-  template t(body: untyped) =
-    if not body:
-      return
-  t handle.sendResult(0)
-  t handle.sendStatus(200) # ok
-  t handle.sendHeaders(newHeaders({"Content-Type": "text/html"}.toTable()))
-  let sl = expandSymlink(path)
-  t handle.sendData("""
-<HTML>
-<HEAD>
-<TITLE>Symlink view<TITLE>
-</HEAD>
-<BODY>
-Symbolic link to <A HREF="""" & sl & """">""" & sl & """</A></br>
-</BODY>
-</HTML>""")
-
-proc loadFile(handle: LoaderHandle, istream: Stream) =
-  template t(body: untyped) =
-    if not body:
-      return
-  t handle.sendResult(0)
-  t handle.sendStatus(200) # ok
-  t handle.sendHeaders(newHeaders())
-  while not istream.atEnd:
-    const bufferSize = 4096
-    var buffer {.noinit.}: array[bufferSize, char]
-    while true:
-      let n = readData(istream, addr buffer[0], bufferSize)
-      if n == 0:
-        break
-      t handle.sendData(addr buffer[0], n)
-      if n < bufferSize:
-        break
-
-proc loadFilePath*(handle: LoaderHandle, url: URL) =
-  when defined(windows) or defined(OS2) or defined(DOS):
-    let path = url.path.serialize_unicode_dos()
-  else:
-    let path = url.path.serialize_unicode()
-  let istream = newFileStream(path, fmRead)
-  if istream == nil:
-    if dirExists(path):
-      handle.loadDir(url, path)
-    elif symlinkExists(path):
-      handle.loadSymlink(path)
-    else:
-      discard handle.sendResult(ERROR_FILE_NOT_FOUND)
-  else:
-    handle.loadFile(istream)
diff --git a/src/io/headers.nim b/src/io/headers.nim
deleted file mode 100644
index b02f30df..00000000
--- a/src/io/headers.nim
+++ /dev/null
@@ -1,100 +0,0 @@
-import tables
-
-import bindings/quickjs
-import js/error
-import js/fromjs
-import js/javascript
-import utils/twtstr
-
-type
-  Headers* = ref object
-    table* {.jsget.}: Table[string, seq[string]]
-
-  HeadersInitType = enum
-    HEADERS_INIT_SEQUENCE, HEADERS_INIT_TABLE
-
-  HeadersInit* = object
-    case t: HeadersInitType
-    of HEADERS_INIT_SEQUENCE:
-      s: seq[(string, string)]
-    of HEADERS_INIT_TABLE:
-      tab: Table[string, string]
-
-jsDestructor(Headers)
-
-proc fromJS2*(ctx: JSContext, val: JSValue, res: var JSResult[HeadersInit]) =
-  if JS_IsUndefined(val) or JS_IsNull(val):
-    res.err(nil)
-    return
-  if isSequence(ctx, val):
-    let x = fromJS[seq[(string, string)]](ctx, val)
-    if x.isSome:
-      res.ok(HeadersInit(t: HEADERS_INIT_SEQUENCE, s: x.get))
-  else:
-    let x = fromJS[Table[string, string]](ctx, val)
-    if x.isSome:
-      res.ok(HeadersInit(t: HEADERS_INIT_TABLE, tab: x.get))
-
-proc fill*(headers: Headers, s: seq[(string, string)]) =
-  for (k, v) in s:
-    if k in headers.table:
-      headers.table[k].add(v)
-    else:
-      headers.table[k] = @[v]
-
-proc fill*(headers: Headers, tab: Table[string, string]) =
-  for k, v in tab:
-    if k in headers.table:
-      headers.table[k].add(v)
-    else:
-      headers.table[k] = @[v]
-
-proc fill*(headers: Headers, init: HeadersInit) =
-  if init.t == HEADERS_INIT_SEQUENCE:
-    headers.fill(init.s)
-  else: # table
-    headers.fill(init.tab)
-
-func newHeaders*(): Headers =
-  return Headers()
-
-func newHeaders(obj = none(HeadersInit)): Headers {.jsctor.} =
-  let headers = Headers()
-  if obj.isSome:
-    headers.fill(obj.get)
-  return headers
-
-func newHeaders*(table: Table[string, string]): Headers =
-  let headers = Headers()
-  for k, v in table:
-    let k = k.toHeaderCase()
-    if k in headers.table:
-      headers.table[k].add(v)
-    else:
-      headers.table[k] = @[v]
-  return headers
-
-func clone*(headers: Headers): Headers =
-  return Headers(
-    table: headers.table
-  )
-
-proc add*(headers: var Headers, k, v: string) =
-  let k = k.toHeaderCase()
-  if k notin headers.table:
-    headers.table[k] = @[v]
-  else:
-    headers.table[k].add(v)
-
-proc `[]=`*(headers: var Headers, k, v: string) =
-  headers.table[k.toHeaderCase()] = @[v]
-
-func getOrDefault*(headers: Headers, k: string, default = ""): string =
-  let k = k.toHeaderCase()
-  if k in headers.table:
-    headers.table[k][0]
-  else:
-    default
-
-proc addHeadersModule*(ctx: JSContext) =
-  ctx.registerType(Headers)
diff --git a/src/io/http.nim b/src/io/http.nim
deleted file mode 100644
index 0a5a6d79..00000000
--- a/src/io/http.nim
+++ /dev/null
@@ -1,142 +0,0 @@
-import options
-import strutils
-
-import bindings/curl
-import io/headers
-import io/loaderhandle
-import io/request
-import types/blob
-import types/formdata
-import types/url
-import utils/opt
-import utils/twtstr
-
-type
-  CurlHandle* = ref CurlHandleObj
-  CurlHandleObj = object
-    curl*: CURL
-    statusline: bool
-    headers: Headers
-    request: Request
-    handle*: LoaderHandle
-    mime: curl_mime
-    slist: curl_slist
-
-func newCurlHandle(curl: CURL, request: Request, handle: LoaderHandle):
-    CurlHandle =
-  return CurlHandle(
-    headers: newHeaders(),
-    curl: curl,
-    handle: handle,
-    request: request
-  )
-
-proc cleanup*(handleData: CurlHandle) =
-  handleData.handle.close()
-  if handleData.mime != nil:
-    curl_mime_free(handleData.mime)
-  if handleData.slist != nil:
-    curl_slist_free_all(handleData.slist)
-  curl_easy_cleanup(handleData.curl)
-
-template setopt(curl: CURL, opt: CURLoption, arg: typed) =
-  discard curl_easy_setopt(curl, opt, arg)
-
-template setopt(curl: CURL, opt: CURLoption, arg: string) =
-  discard curl_easy_setopt(curl, opt, cstring(arg))
-
-template getinfo(curl: CURL, info: CURLINFO, arg: typed) =
-  discard curl_easy_getinfo(curl, info, arg)
-
-proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t,
-    userdata: pointer): csize_t {.cdecl.} =
-  var line = newString(nitems)
-  for i in 0..<nitems:
-    line[i] = p[i]
-
-  let op = cast[CurlHandle](userdata)
-  if not op.statusline:
-    op.statusline = true
-    if not op.handle.sendResult(int(CURLE_OK)):
-      return 0
-    var status: clong
-    op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
-    if not op.handle.sendStatus(cast[int](status)):
-      return 0
-    return nitems
-
-  let k = line.until(':')
-
-  if k.len == line.len:
-    # empty line (last, before body) or invalid (=> error)
-    if not op.handle.sendHeaders(op.headers):
-      return 0
-    return nitems
-
-  let v = line.substr(k.len + 1).strip()
-  op.headers.add(k, v)
-  return nitems
-
-# From the documentation: size is always 1.
-proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t,
-    userdata: pointer): csize_t {.cdecl.} =
-  let handleData = cast[CurlHandle](userdata)
-  if nmemb > 0:
-    if not handleData.handle.sendData(p, int(nmemb)):
-      return 0
-  return nmemb
-
-proc applyPostBody(curl: CURL, request: Request, handleData: CurlHandle) =
-  if request.multipart.isOk:
-    handleData.mime = curl_mime_init(curl)
-    doAssert handleData.mime != nil
-    for entry in request.multipart.get:
-      let part = curl_mime_addpart(handleData.mime)
-      doAssert part != nil
-      curl_mime_name(part, cstring(entry.name))
-      if entry.isstr:
-        curl_mime_data(part, cstring(entry.svalue), csize_t(entry.svalue.len))
-      else:
-        let blob = entry.value
-        if blob.isfile: #TODO ?
-          curl_mime_filedata(part, cstring(WebFile(blob).path))
-        else:
-          curl_mime_data(part, blob.buffer, csize_t(blob.size))
-        # may be overridden by curl_mime_filedata, so set it here
-        curl_mime_filename(part, cstring(entry.filename))
-    curl.setopt(CURLOPT_MIMEPOST, handleData.mime)
-  elif request.body.issome:
-    curl.setopt(CURLOPT_POSTFIELDS, cstring(request.body.get))
-    curl.setopt(CURLOPT_POSTFIELDSIZE, request.body.get.len)
-
-proc loadHttp*(handle: LoaderHandle, curlm: CURLM,
-    request: Request): CurlHandle =
-  let curl = curl_easy_init()
-  doAssert curl != nil
-  let surl = request.url.serialize()
-  curl.setopt(CURLOPT_URL, surl)
-  let handleData = curl.newCurlHandle(request, handle)
-  curl.setopt(CURLOPT_WRITEDATA, handleData)
-  curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)
-  curl.setopt(CURLOPT_HEADERDATA, handleData)
-  curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)
-  if request.proxy != nil:
-    let purl = request.proxy.serialize()
-    curl.setopt(CURLOPT_PROXY, purl)
-  case request.httpmethod
-  of HTTP_GET:
-    curl.setopt(CURLOPT_HTTPGET, 1)
-  of HTTP_POST:
-    curl.setopt(CURLOPT_POST, 1)
-    curl.applyPostBody(request, handleData)
-  else: discard #TODO
-  for k, v in request.headers:
-    let header = k & ": " & v
-    handleData.slist = curl_slist_append(handleData.slist, cstring(header))
-  if handleData.slist != nil:
-    curl.setopt(CURLOPT_HTTPHEADER, handleData.slist)
-  let res = curl_multi_add_handle(curlm, curl)
-  if res != CURLM_OK:
-    discard handle.sendResult(int(res))
-    return nil
-  return handleData
diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim
deleted file mode 100644
index 1c1d273c..00000000
--- a/src/io/lineedit.nim
+++ /dev/null
@@ -1,369 +0,0 @@
-import sequtils
-import streams
-import strutils
-import unicode
-
-import bindings/quickjs
-import buffer/cell
-import display/term
-import js/javascript
-import types/color
-import utils/opt
-import utils/twtstr
-
-import chakasu/charset
-import chakasu/decoderstream
-import chakasu/encoderstream
-
-type
-  LineEditState* = enum
-    EDIT, FINISH, CANCEL
-
-  LineHistory* = ref object
-    lines: seq[string]
-
-  LineEdit* = ref object
-    isnew*: bool #TODO hack
-    news*: seq[Rune]
-    prompt*: string
-    promptw: int
-    current: string
-    state*: LineEditState
-    escNext*: bool
-    cursor: int
-    shift: int
-    minlen: int
-    maxwidth: int
-    displen: int
-    disallowed: set[char]
-    hide: bool
-    term: Terminal
-    hist: LineHistory
-    histindex: int
-    histtmp: string
-
-jsDestructor(LineEdit)
-
-func newLineHistory*(): LineHistory =
-  return LineHistory()
-
-proc printesc(edit: LineEdit, rs: seq[Rune]) =
-  var dummy = 0
-  edit.term.write(edit.term.processOutputString0(rs.items, true, dummy))
-
-proc print(edit: LineEdit, s: string) =
-  var dummy = 0
-  edit.term.write(edit.term.processOutputString(s, dummy))
-
-template kill0(edit: LineEdit, i: int) =
-  edit.space(i)
-  edit.backward0(i)
-
-template kill0(edit: LineEdit) =
-  let w = min(edit.news.width(edit.cursor), edit.displen)
-  edit.kill0(w)
-
-proc backward0(state: LineEdit, i: int) =
-  state.term.cursorBackward(i)
-
-proc forward0(state: LineEdit, i: int) =
-  state.term.cursorForward(i)
-
-proc begin0(edit: LineEdit) =
-  edit.term.cursorBegin()
-  edit.forward0(edit.minlen)
-
-proc space(edit: LineEdit, i: int) =
-  edit.term.write(' '.repeat(i))
-
-#TODO this is broken (e.g. it doesn't account for shift, but for other
-# reasons too)
-proc generateOutput*(edit: LineEdit): FixedGrid =
-  result = newFixedGrid(edit.promptw + edit.maxwidth)
-  var x = 0
-  for r in edit.prompt.runes():
-    result[x].str &= $r
-    x += r.width()
-  if edit.hide:
-    for r in edit.news:
-      let w = r.width()
-      result[x].str = '*'.repeat(w)
-      x += w
-      if x >= result.width: break
-  else:
-    for r in edit.news:
-      result[x].str &= $r
-      x += r.width()
-      if x >= result.width: break
-  var s = ""
-  for c in result:
-    s &= c.str
-
-proc getCursorX*(edit: LineEdit): int =
-  return edit.promptw + edit.news.width(edit.shift, edit.cursor)
-
-proc redraw(state: LineEdit) =
-  if state.shift + state.displen > state.news.len:
-    state.displen = state.news.len - state.shift
-  var dispw = state.news.width(state.shift, state.shift + state.displen)
-  while dispw > state.maxwidth - 1:
-    dispw -= state.news[state.shift + state.displen - 1].width()
-    dec state.displen
-  state.begin0()
-  let os = state.news.substr(state.shift, state.shift + state.displen)
-  if state.hide:
-    state.print('*'.repeat(os.width()))
-  else:
-    state.printesc(os)
-  state.space(max(state.maxwidth - state.minlen - os.width(), 0))
-  state.begin0()
-  state.forward0(state.news.width(state.shift, state.cursor))
-
-proc zeroShiftRedraw(state: LineEdit) =
-  state.shift = 0
-  state.displen = state.news.len
-  state.redraw()
-
-proc fullRedraw*(state: LineEdit) =
-  state.displen = state.news.len
-  if state.cursor > state.shift:
-    var shiftw = state.news.width(state.shift, state.cursor)
-    while shiftw > state.maxwidth - 1:
-      inc state.shift
-      shiftw -= state.news[state.shift].width()
-  else:
-    state.shift = max(state.cursor - 1, 0)
-  state.redraw()
-
-proc drawPrompt*(edit: LineEdit) =
-  edit.term.write(edit.prompt)
-
-proc insertCharseq(edit: LineEdit, cs: var seq[Rune]) =
-  let escNext = edit.escNext
-  var i = 0
-  for j in 0 ..< cs.len:
-    if cs[i].isAscii():
-      let c = cast[char](cs[i])
-      if not escNext and c in Controls or c in edit.disallowed:
-        continue
-    if i != j:
-      cs[i] = cs[j]
-    inc i
-
-  edit.escNext = false
-  if cs.len == 0:
-    return
-
-  if edit.cursor >= edit.news.len and edit.news.width(edit.shift, edit.cursor) + cs.width() < edit.maxwidth:
-    edit.news &= cs
-    edit.cursor += cs.len
-    if edit.hide:
-      edit.print('*'.repeat(cs.width()))
-    else:
-      edit.printesc(cs)
-  else:
-    edit.news.insert(cs, edit.cursor)
-    edit.cursor += cs.len
-    edit.fullRedraw()
-
-proc cancel(edit: LineEdit) {.jsfunc.} =
-  edit.state = CANCEL
-
-proc submit(edit: LineEdit) {.jsfunc.} =
-  let s = $edit.news
-  if edit.hist.lines.len == 0 or s != edit.hist.lines[^1]:
-    edit.hist.lines.add(s)
-  edit.state = FINISH
-
-proc backspace(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor > 0:
-    let w = edit.news[edit.cursor - 1].width()
-    edit.news.delete(edit.cursor - 1..edit.cursor - 1)
-    dec edit.cursor
-    if edit.cursor == edit.news.len and edit.shift == 0:
-      edit.backward0(w)
-      edit.kill0(w)
-    else:
-      edit.fullRedraw()
-
-const buflen = 128
-var buf {.threadVar.}: array[buflen, uint32]
-proc write*(edit: LineEdit, s: string, cs: Charset): bool =
-  let ss = newStringStream(s)
-  let ds = newDecoderStream(ss, cs = cs, buflen = buflen,
-    errormode = DECODER_ERROR_MODE_FATAL)
-  var cseq: seq[Rune]
-  while not ds.atEnd:
-    let n = ds.readData(buf)
-    for i in 0 ..< n div 4:
-      let r = cast[Rune](buf[i])
-      cseq.add(r)
-  if ds.failed:
-    return false
-  edit.insertCharseq(cseq)
-  return true
-
-proc write(edit: LineEdit, s: string): bool {.jsfunc.} =
-  edit.write(s, CHARSET_UTF_8)
-
-proc delete(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor >= 0 and edit.cursor < edit.news.len:
-    let w = edit.news[edit.cursor].width()
-    edit.news.delete(edit.cursor..edit.cursor)
-    if edit.cursor == edit.news.len and edit.shift == 0:
-      edit.kill0(w)
-    else:
-      edit.fullRedraw()
-
-proc escape(edit: LineEdit) {.jsfunc.} =
-  edit.escNext = true
-
-proc clear(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor > 0:
-    edit.news.delete(0..edit.cursor - 1)
-    edit.cursor = 0
-    edit.zeroShiftRedraw()
-
-proc kill(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor < edit.news.len:
-    edit.kill0()
-    edit.news.setLen(edit.cursor)
-
-proc backward(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor > 0:
-    dec edit.cursor
-    if edit.cursor > edit.shift or edit.shift == 0:
-      edit.backward0(edit.news[edit.cursor].width())
-    else:
-      edit.fullRedraw()
-
-proc forward(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor < edit.news.len:
-    inc edit.cursor
-    if edit.news.width(edit.shift, edit.cursor) < edit.maxwidth:
-      var n = 1
-      if edit.news.len > edit.cursor:
-        n = edit.news[edit.cursor].width()
-      edit.forward0(n)
-    else:
-      edit.fullRedraw()
-
-proc prevWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
-  let oc = edit.cursor
-  while edit.cursor > 0:
-    dec edit.cursor
-    if edit.news[edit.cursor].breaksWord(check):
-      break
-  if edit.cursor != oc:
-    if edit.cursor > edit.shift or edit.shift == 0:
-      edit.backward0(edit.news.width(edit.cursor, oc))
-    else:
-      edit.fullRedraw()
-
-proc nextWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
-  let oc = edit.cursor
-  let ow = edit.news.width(edit.shift, edit.cursor)
-  while edit.cursor < edit.news.len:
-    inc edit.cursor
-    if edit.cursor < edit.news.len:
-      if edit.news[edit.cursor].breaksWord(check):
-        break
-  if edit.cursor != oc:
-    let dw = edit.news.width(oc, edit.cursor)
-    if ow + dw < edit.maxwidth:
-      edit.forward0(dw)
-    else:
-      edit.fullRedraw()
-
-proc clearWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
-  var i = edit.cursor
-  if i > 0:
-    # point to the previous character
-    dec i
-  while i > 0:
-    dec i
-    if edit.news[i].breaksWord(check):
-      inc i
-      break
-  if i != edit.cursor:
-    edit.news.delete(i..<edit.cursor)
-    edit.cursor = i
-    edit.fullRedraw()
-
-proc killWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} =
-  var i = edit.cursor
-  if i < edit.news.len and edit.news[i].breaksWord(check):
-    inc i
-  while i < edit.news.len:
-    if edit.news[i].breaksWord(check):
-      break
-    inc i
-  if i != edit.cursor:
-    edit.news.delete(edit.cursor..<i)
-    edit.fullRedraw()
-
-proc begin(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor > 0:
-    if edit.shift == 0:
-      edit.backward0(edit.news.width(0, edit.cursor))
-      edit.cursor = 0
-    else:
-      edit.cursor = 0
-      edit.fullRedraw()
-
-proc `end`(edit: LineEdit) {.jsfunc.} =
-  if edit.cursor < edit.news.len:
-    if edit.news.width(edit.shift, edit.news.len) < edit.maxwidth:
-      edit.forward0(edit.news.width(edit.cursor, edit.news.len))
-      edit.cursor = edit.news.len
-    else:
-      edit.cursor = edit.news.len
-      edit.fullRedraw()
-
-proc prevHist(edit: LineEdit) {.jsfunc.} =
-  if edit.histindex > 0:
-    if edit.news.len > 0:
-      edit.histtmp = $edit.news
-    dec edit.histindex
-    edit.news = edit.hist.lines[edit.histindex].toRunes()
-    edit.begin()
-    edit.end()
-    edit.fullRedraw()
-
-proc nextHist(edit: LineEdit) {.jsfunc.} =
-  if edit.histindex + 1 < edit.hist.lines.len:
-    inc edit.histindex
-    edit.news = edit.hist.lines[edit.histindex].toRunes()
-    edit.begin()
-    edit.end()
-    edit.fullRedraw()
-  elif edit.histindex < edit.hist.lines.len:
-    inc edit.histindex
-    edit.news = edit.histtmp.toRunes()
-    edit.begin()
-    edit.end()
-    edit.fullRedraw()
-    edit.histtmp = ""
-
-proc readLine*(prompt: string, termwidth: int, current = "",
-               disallowed: set[char] = {}, hide = false,
-               term: Terminal, hist: LineHistory): LineEdit =
-  result = LineEdit(
-    prompt: prompt,
-    promptw: prompt.width(),
-    current: current,
-    news: current.toRunes(),
-    minlen: prompt.width(),
-    disallowed: disallowed,
-    hide: hide,
-    term: term,
-    isnew: true
-  )
-  result.cursor = result.news.width()
-  result.maxwidth = termwidth - result.promptw
-  result.displen = result.cursor
-  result.hist = hist
-  result.histindex = result.hist.lines.len
-
-proc addLineEditModule*(ctx: JSContext) =
-  ctx.registerType(LineEdit)
diff --git a/src/io/loader.nim b/src/io/loader.nim
deleted file mode 100644
index a6be3f6d..00000000
--- a/src/io/loader.nim
+++ /dev/null
@@ -1,394 +0,0 @@
-# A file loader server (?)
-# The idea here is that we receive requests with a socket, then respond to each
-# with a response (ideally a document.)
-# For now, the protocol looks like:
-# C: Request
-# S: res (0 => success, _ => error)
-# if success:
-#  S: status code
-#  S: headers
-#  S: response body
-#
-# The body is passed to the stream as-is, so effectively nothing can follow it.
-
-import nativesockets
-import net
-import options
-import posix
-import streams
-import strutils
-import tables
-
-import bindings/curl
-import io/about
-import io/connecterror
-import io/data
-import io/file
-import io/headers
-import io/http
-import io/loaderhandle
-import io/posixstream
-import io/promise
-import io/request
-import io/response
-import io/urlfilter
-import ips/serialize
-import ips/serversocket
-import ips/socketstream
-import js/error
-import js/javascript
-import types/cookie
-import types/referer
-import types/url
-import utils/mimeguess
-import utils/twtstr
-
-import chakasu/charset
-
-export request
-export response
-
-type
-  FileLoader* = ref object
-    process*: Pid
-    connecting*: Table[int, ConnectData]
-    ongoing*: Table[int, OngoingData]
-    unregistered*: seq[int]
-    registerFun*: proc(fd: int)
-    unregisterFun*: proc(fd: int)
-
-  ConnectData = object
-    promise: Promise[JSResult[Response]]
-    stream: Stream
-    request: Request
-
-  OngoingData = object
-    buf: string
-    readbufsize: int
-    response: Response
-    bodyRead: Promise[string]
-
-  LoaderCommand = enum
-    LOAD
-    QUIT
-
-  LoaderContext = ref object
-    ssock: ServerSocket
-    alive: bool
-    curlm: CURLM
-    config: LoaderConfig
-    extra_fds: seq[curl_waitfd]
-    handleList: seq[CurlHandle]
-
-  LoaderConfig* = object
-    defaultheaders*: Headers
-    filter*: URLFilter
-    cookiejar*: CookieJar
-    referrerpolicy*: ReferrerPolicy
-    proxy*: URL
-    # When set to false, requests with a proxy URL are overridden by the
-    # loader proxy.
-    acceptProxy*: bool
-
-  FetchPromise* = Promise[JSResult[Response]]
-
-proc addFd(ctx: LoaderContext, fd: int, flags: int) =
-  ctx.extra_fds.add(curl_waitfd(
-    fd: cast[cint](fd),
-    events: cast[cshort](flags)
-  ))
-
-proc loadResource(ctx: LoaderContext, request: Request, handle: LoaderHandle) =
-  case request.url.scheme
-  of "file":
-    handle.loadFilePath(request.url)
-    handle.close()
-  of "http", "https":
-    let handleData = handle.loadHttp(ctx.curlm, request)
-    if handleData != nil:
-      ctx.handleList.add(handleData)
-  of "about":
-    handle.loadAbout(request)
-    handle.close()
-  of "data":
-    handle.loadData(request)
-    handle.close()
-  else:
-    discard handle.sendResult(ERROR_UNKNOWN_SCHEME)
-    handle.close()
-
-proc onLoad(ctx: LoaderContext, stream: Stream) =
-  var request: Request
-  stream.sread(request)
-  if not ctx.config.filter.match(request.url):
-    stream.swrite(ERROR_DISALLOWED_URL)
-    stream.close()
-  else:
-    let handle = newLoaderHandle(stream, request.canredir)
-    for k, v in ctx.config.defaultHeaders.table:
-      if k notin request.headers.table:
-        request.headers.table[k] = v
-    if ctx.config.cookiejar != nil and ctx.config.cookiejar.cookies.len > 0:
-      if "Cookie" notin request.headers.table:
-        let cookie = ctx.config.cookiejar.serialize(request.url)
-        if cookie != "":
-          request.headers["Cookie"] = cookie
-    if request.referer != nil and "Referer" notin request.headers.table:
-      let r = getReferer(request.referer, request.url, ctx.config.referrerpolicy)
-      if r != "":
-        request.headers["Referer"] = r
-    if request.proxy == nil or not ctx.config.acceptProxy:
-      request.proxy = ctx.config.proxy
-    ctx.loadResource(request, handle)
-
-proc acceptConnection(ctx: LoaderContext) =
-  #TODO TODO TODO acceptSocketStream should be non-blocking here,
-  # otherwise the client disconnecting between poll and accept could
-  # block this indefinitely.
-  let stream = ctx.ssock.acceptSocketStream()
-  try:
-    var cmd: LoaderCommand
-    stream.sread(cmd)
-    case cmd
-    of LOAD:
-      ctx.onLoad(stream)
-    of QUIT:
-      ctx.alive = false
-      stream.close()
-  except IOError:
-    # End-of-file, broken pipe, or something else. For now we just
-    # ignore it and pray nothing breaks.
-    # (TODO: this is probably not a very good idea.)
-    stream.close()
-
-proc finishCurlTransfer(ctx: LoaderContext, handleData: CurlHandle, res: int) =
-  if res != int(CURLE_OK):
-    discard handleData.handle.sendResult(int(res))
-  discard curl_multi_remove_handle(ctx.curlm, handleData.curl)
-  handleData.cleanup()
-
-proc exitLoader(ctx: LoaderContext) =
-  for handleData in ctx.handleList:
-    ctx.finishCurlTransfer(handleData, ERROR_LOADER_KILLED)
-  discard curl_multi_cleanup(ctx.curlm)
-  curl_global_cleanup()
-  ctx.ssock.close()
-  quit(0)
-
-var gctx: LoaderContext
-proc initLoaderContext(fd: cint, config: LoaderConfig): LoaderContext =
-  if curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK:
-    raise newException(Defect, "Failed to initialize libcurl.")
-  let curlm = curl_multi_init()
-  if curlm == nil:
-    raise newException(Defect, "Failed to initialize multi handle.")
-  var ctx = LoaderContext(
-    alive: true,
-    curlm: curlm,
-    config: config
-  )
-  gctx = ctx
-  #TODO ideally, buffered would be true. Unfortunately this conflicts with
-  # sendFileHandle/recvFileHandle.
-  ctx.ssock = initServerSocket(buffered = false)
-  # The server has been initialized, so the main process can resume execution.
-  var writef: File
-  if not open(writef, FileHandle(fd), fmWrite):
-    raise newException(Defect, "Failed to open input handle.")
-  writef.write(char(0u8))
-  writef.flushFile()
-  close(writef)
-  discard close(fd)
-  onSignal SIGTERM, SIGINT:
-    discard sig
-    gctx.exitLoader()
-  ctx.addFd(int(ctx.ssock.sock.getFd()), CURL_WAIT_POLLIN)
-  return ctx
-
-proc runFileLoader*(fd: cint, config: LoaderConfig) =
-  var ctx = initLoaderContext(fd, config)
-  while ctx.alive:
-    var numfds: cint = 0
-    #TODO do not discard
-    discard curl_multi_poll(ctx.curlm, addr ctx.extra_fds[0],
-      cuint(ctx.extra_fds.len), 30_000, addr numfds)
-    discard curl_multi_perform(ctx.curlm, addr numfds)
-    for extra_fd in ctx.extra_fds.mitems:
-      # For now, this is always ssock.sock.getFd().
-      if extra_fd.events == extra_fd.revents:
-        ctx.acceptConnection()
-        extra_fd.revents = 0
-    var msgs_left: cint = 1
-    while msgs_left > 0:
-      let msg = curl_multi_info_read(ctx.curlm, addr msgs_left)
-      if msg == nil:
-        break
-      if msg.msg == CURLMSG_DONE: # the only possible value atm
-        var idx = -1
-        for i in 0 ..< ctx.handleList.len:
-          if ctx.handleList[i].curl == msg.easy_handle:
-            idx = i
-            break
-        assert idx != -1
-        ctx.finishCurlTransfer(ctx.handleList[idx], int(msg.data.result))
-        ctx.handleList.del(idx)
-  ctx.exitLoader()
-
-proc getAttribute(contentType, attrname: string): string =
-  let kvs = contentType.after(';')
-  var i = kvs.find(attrname)
-  var s = ""
-  if i != -1 and kvs.len > i + attrname.len and
-      kvs[i + attrname.len] == '=':
-    i += attrname.len + 1
-    while i < kvs.len and kvs[i] in AsciiWhitespace:
-      inc i
-    var q = false
-    for j in i ..< kvs.len:
-      if q:
-        s &= kvs[j]
-      else:
-        if kvs[j] == '\\':
-          q = true
-        elif kvs[j] == ';' or kvs[j] in AsciiWhitespace:
-          break
-        else:
-          s &= kvs[j]
-  return s
-
-proc applyHeaders(loader: FileLoader, request: Request, response: Response) =
-  if "Content-Type" in response.headers.table:
-    #TODO this is inefficient and broken on several levels. (In particular,
-    # it breaks mailcap named attributes other than charset.)
-    # Ideally, contentType would be a separate object type.
-    let header = response.headers.table["Content-Type"][0].toLowerAscii()
-    response.contenttype = header.until(';').strip().toLowerAscii()
-    response.charset = getCharset(header.getAttribute("charset"))
-  else:
-    response.contenttype = guessContentType($response.url.path,
-      "application/octet-stream", DefaultGuess)
-  if "Location" in response.headers.table:
-    if response.status in 301u16..303u16 or response.status in 307u16..308u16:
-      let location = response.headers.table["Location"][0]
-      let url = parseUrl(location, option(request.url))
-      if url.isSome:
-        if (response.status == 303 and
-            request.httpmethod notin {HTTP_GET, HTTP_HEAD}) or
-            (response.status == 301 or response.status == 302 and
-            request.httpmethod == HTTP_POST):
-          response.redirect = newRequest(url.get, HTTP_GET,
-            mode = request.mode, credentialsMode = request.credentialsMode,
-            destination = request.destination)
-        else:
-          response.redirect = newRequest(url.get, request.httpmethod,
-            body = request.body, multipart = request.multipart,
-            mode = request.mode, credentialsMode = request.credentialsMode,
-            destination = request.destination)
-
-#TODO: add init
-proc fetch*(loader: FileLoader, input: Request): FetchPromise =
-  let stream = connectSocketStream(loader.process, false, blocking = true)
-  stream.swrite(LOAD)
-  stream.swrite(input)
-  stream.flush()
-  let fd = int(stream.source.getFd())
-  loader.registerFun(fd)
-  let promise = FetchPromise()
-  loader.connecting[fd] = ConnectData(
-    promise: promise,
-    request: input,
-    stream: stream
-  )
-  return promise
-
-const BufferSize = 4096
-
-proc handleHeaders(loader: FileLoader, request: Request, response: Response,
-    stream: Stream): bool =
-  var status: int
-  stream.sread(status)
-  response.status = cast[uint16](status)
-  response.headers = newHeaders()
-  stream.sread(response.headers)
-  loader.applyHeaders(request, response)
-  # Only a stream of the response body may arrive after this point.
-  response.body = stream
-  return true # success
-
-proc onConnected*(loader: FileLoader, fd: int) =
-  let connectData = loader.connecting[fd]
-  let stream = connectData.stream
-  let promise = connectData.promise
-  let request = connectData.request
-  var res: int
-  stream.sread(res)
-  let response = newResponse(res, request, fd, stream)
-  if res == 0 and loader.handleHeaders(request, response, stream):
-    assert loader.unregisterFun != nil
-    let realCloseImpl = stream.closeImpl
-    stream.closeImpl = nil
-    response.unregisterFun = proc() =
-      loader.ongoing.del(fd)
-      loader.unregistered.add(fd)
-      loader.unregisterFun(fd)
-      realCloseImpl(stream)
-    loader.ongoing[fd] = OngoingData(
-      response: response,
-      readbufsize: BufferSize,
-      bodyRead: response.bodyRead
-    )
-    SocketStream(stream).source.getFd().setBlocking(false)
-    promise.resolve(JSResult[Response].ok(response))
-  else:
-    loader.unregisterFun(fd)
-    loader.unregistered.add(fd)
-    let err = newTypeError("NetworkError when attempting to fetch resource")
-    promise.resolve(JSResult[Response].err(err))
-  loader.connecting.del(fd)
-
-proc onRead*(loader: FileLoader, fd: int) =
-  loader.ongoing.withValue(fd, buffer):
-    let response = buffer[].response
-    while true:
-      let olen = buffer[].buf.len
-      buffer[].buf.setLen(olen + buffer.readbufsize)
-      try:
-        let n = response.body.readData(addr buffer[].buf[olen],
-          buffer.readbufsize)
-        if n != 0:
-          if buffer[].readbufsize < BufferSize:
-            buffer[].readbufsize = min(BufferSize, buffer[].readbufsize * 2)
-        buffer[].buf.setLen(olen + n)
-        if response.body.atEnd():
-          buffer[].bodyRead.resolve(buffer[].buf)
-          buffer[].bodyRead = nil
-          buffer[].buf = ""
-          response.unregisterFun()
-        break
-      except ErrorAgain, ErrorWouldBlock:
-        assert buffer.readbufsize > 1
-        buffer.readbufsize = buffer.readbufsize div 2
-
-proc onError*(loader: FileLoader, fd: int) =
-  loader.onRead(fd)
-
-proc doRequest*(loader: FileLoader, request: Request, blocking = true,
-    canredir = false): Response =
-  let response = Response(url: request.url)
-  let stream = connectSocketStream(loader.process, false, blocking = true)
-  if canredir:
-    request.canredir = true #TODO set this somewhere else?
-  stream.swrite(LOAD)
-  stream.swrite(request)
-  stream.flush()
-  stream.sread(response.res)
-  if response.res == 0:
-    if loader.handleHeaders(request, response, stream):
-      if not blocking:
-        stream.source.getFd().setBlocking(blocking)
-  return response
-
-proc quit*(loader: FileLoader) =
-  let stream = connectSocketStream(loader.process)
-  if stream != nil:
-    stream.swrite(QUIT)
diff --git a/src/io/loaderhandle.nim b/src/io/loaderhandle.nim
deleted file mode 100644
index 077b1a2a..00000000
--- a/src/io/loaderhandle.nim
+++ /dev/null
@@ -1,73 +0,0 @@
-import net
-import streams
-
-import io/posixstream
-import io/headers
-import ips/serialize
-import ips/socketstream
-
-type LoaderHandle* = ref object
-  ostream: Stream
-  # Only the first handle can be redirected, because a) mailcap can only
-  # redirect the first handle and b) async redirects would result in race
-  # conditions that would be difficult to untangle.
-  canredir: bool
-  sostream: Stream # saved ostream when redirected
-
-# Create a new loader handle, with the output stream ostream.
-proc newLoaderHandle*(ostream: Stream, canredir: bool): LoaderHandle =
-  return LoaderHandle(ostream: ostream, canredir: canredir)
-
-proc getFd*(handle: LoaderHandle): int =
-  return int(SocketStream(handle.ostream).source.getFd())
-
-proc sendResult*(handle: LoaderHandle, res: int): bool =
-  try:
-    handle.ostream.swrite(res)
-    return true
-  except IOError: # broken pipe
-    return false
-
-proc sendStatus*(handle: LoaderHandle, status: int): bool =
-  try:
-    handle.ostream.swrite(status)
-    return true
-  except IOError: # broken pipe
-    return false
-
-proc sendHeaders*(handle: LoaderHandle, headers: Headers): bool =
-  try:
-    handle.ostream.swrite(headers)
-    if handle.canredir:
-      var redir: bool
-      handle.ostream.sread(redir)
-      if redir:
-        let fd = SocketStream(handle.ostream).recvFileHandle()
-        handle.sostream = handle.ostream
-        let stream = newPosixStream(fd)
-        handle.ostream = stream
-    return true
-  except IOError: # broken pipe
-    return false
-
-proc sendData*(handle: LoaderHandle, p: pointer, nmemb: int): bool =
-  try:
-    handle.ostream.writeData(p, nmemb)
-    return true
-  except IOError: # broken pipe
-    return false
-
-proc sendData*(handle: LoaderHandle, s: string): bool =
-  if s.len > 0:
-    return handle.sendData(unsafeAddr s[0], s.len)
-  return true
-
-proc close*(handle: LoaderHandle) =
-  if handle.sostream != nil:
-    try:
-      handle.sostream.swrite(true)
-    except IOError:
-      # ignore error, that just means the buffer has already closed the stream
-      discard
-    handle.sostream.close()
-  handle.ostream.close()
diff --git a/src/io/promise.nim b/src/io/promise.nim
index ee4f0654..549b878e 100644
--- a/src/io/promise.nim
+++ b/src/io/promise.nim
@@ -1,6 +1,6 @@
 import tables
 
-import utils/opt
+import types/opt
 
 type
   PromiseState* = enum
diff --git a/src/io/request.nim b/src/io/request.nim
deleted file mode 100644
index 675ea048..00000000
--- a/src/io/request.nim
+++ /dev/null
@@ -1,332 +0,0 @@
-import options
-import streams
-import strutils
-import tables
-
-import bindings/quickjs
-import io/headers
-import js/dict
-import js/error
-import js/fromjs
-import js/javascript
-import types/blob
-import types/formdata
-import types/referer
-import types/url
-
-type
-  HttpMethod* = enum
-    HTTP_GET = "GET"
-    HTTP_CONNECT = "CONNECT"
-    HTTP_DELETE = "DELETE"
-    HTTP_HEAD = "HEAD"
-    HTTP_OPTIONS = "OPTIONS"
-    HTTP_PATCH = "PATCH"
-    HTTP_POST = "POST"
-    HTTP_PUT = "PUT"
-    HTTP_TRACE = "TRACE"
-
-  RequestMode* = enum
-    NO_CORS = "no-cors"
-    SAME_ORIGIN = "same-origin"
-    CORS = "cors"
-    NAVIGATE = "navigate"
-    WEBSOCKET = "websocket"
-
-  RequestDestination* = enum
-    NO_DESTINATION = ""
-    AUDIO = "audio"
-    AUDIOWORKLET = "audioworklet"
-    DOCUMENT = "document"
-    EMBED = "embed"
-    FONT = "font"
-    FRAME = "frame"
-    IFRAME = "iframe"
-    IMAGE = "image"
-    MANIFEST = "manifest"
-    OBJECT = "object"
-    PAINTWORKLET = "paintworklet"
-    REPORT = "report"
-    SCRIPT = "script"
-    SERVICEWORKER = "serviceworker"
-    SHAREDWORKER = "sharedworker"
-    STYLE = "style"
-    TRACK = "track"
-    WORKER = "worker"
-    XSLT = "xslt"
-
-  CredentialsMode* = enum
-    SAME_ORIGIN = "same-origin"
-    OMIT = "omit"
-    INCLUDE = "include"
-
-  CORSAttribute* = enum
-    NO_CORS = "no-cors"
-    ANONYMOUS = "anonymous"
-    USE_CREDENTIALS = "use-credentials"
-
-type
-  Request* = ref RequestObj
-  RequestObj* = object
-    httpmethod*: HttpMethod
-    url*: Url
-    headers* {.jsget.}: Headers
-    body*: Opt[string]
-    multipart*: Opt[FormData]
-    referer*: URL
-    mode* {.jsget.}: RequestMode
-    destination* {.jsget.}: RequestDestination
-    credentialsMode* {.jsget.}: CredentialsMode
-    proxy*: URL #TODO do something with this
-    canredir*: bool
- 
-  ReadableStream* = ref object of Stream
-    isource*: Stream
-    buf: string
-    isend: bool
-
-jsDestructor(Request)
-
-proc js_url(this: Request): string {.jsfget: "url".} =
-  return $this.url
-
-#TODO pretty sure this is incorrect
-proc js_referrer(this: Request): string {.jsfget: "referrer".} =
-  if this.referer != nil:
-    return $this.referer
-  return ""
-
-iterator pairs*(headers: Headers): (string, string) =
-  for k, vs in headers.table:
-    for v in vs:
-      yield (k, v)
-
-proc rsReadData(s: Stream, buffer: pointer, bufLen: int): int =
-  var s = ReadableStream(s)
-  if s.atEnd:
-    return 0
-  while s.buf.len < bufLen:
-    var len: int
-    s.isource.read(len)
-    if len == 0:
-      result = s.buf.len
-      copyMem(buffer, addr(s.buf[0]), result)
-      s.buf = s.buf.substr(result)
-      s.isend = true
-      return
-    var nbuf: string
-    s.isource.readStr(len, nbuf)
-    s.buf &= nbuf
-  assert s.buf.len >= bufLen
-  result = bufLen
-  copyMem(buffer, addr(s.buf[0]), result)
-  s.buf = s.buf.substr(result)
-  if s.buf.len == 0:
-    var len: int
-    s.isource.read(len)
-    if len == 0:
-      s.isend = true
-    else:
-      s.isource.readStr(len, s.buf)
-
-proc rsAtEnd(s: Stream): bool =
-  ReadableStream(s).isend
-
-proc rsClose(s: Stream) = {.cast(tags: [WriteIOEffect]).}: #TODO TODO TODO ew.
-  var s = ReadableStream(s)
-  if s.isend: return
-  s.buf = ""
-  while true:
-    var len: int
-    s.isource.read(len)
-    if len == 0:
-      s.isend = true
-      break
-    s.isource.setPosition(s.isource.getPosition() + len)
-
-proc newReadableStream*(isource: Stream): ReadableStream =
-  new(result)
-  result.isource = isource
-  result.readDataImpl = rsReadData
-  result.atEndImpl = rsAtEnd
-  result.closeImpl = rsClose
-  var len: int
-  result.isource.read(len)
-  if len == 0:
-    result.isend = true
-  else:
-    result.isource.readStr(len, result.buf)
-
-func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(),
-    body = opt(string), multipart = opt(FormData), mode = RequestMode.NO_CORS,
-    credentialsMode = CredentialsMode.SAME_ORIGIN,
-    destination = RequestDestination.NO_DESTINATION, proxy: URL = nil,
-    canredir = false): Request =
-  return Request(
-    url: url,
-    httpmethod: httpmethod,
-    headers: headers,
-    body: body,
-    multipart: multipart,
-    mode: mode,
-    credentialsMode: credentialsMode,
-    destination: destination,
-    proxy: proxy
-  )
-
-func newRequest*(url: URL, httpmethod = HTTP_GET,
-    headers: seq[(string, string)] = @[], body = opt(string),
-    multipart = opt(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil,
-    canredir = false):
-    Request =
-  let hl = newHeaders()
-  for pair in headers:
-    let (k, v) = pair
-    hl.table[k] = @[v]
-  return newRequest(url, httpmethod, hl, body, multipart, mode, proxy = proxy)
-
-func createPotentialCORSRequest*(url: URL, destination: RequestDestination, cors: CORSAttribute, fallbackFlag = false): Request =
-  var mode = if cors == NO_CORS:
-    RequestMode.NO_CORS
-  else:
-    RequestMode.CORS
-  if fallbackFlag and mode == NO_CORS:
-    mode = SAME_ORIGIN
-  let credentialsMode = if cors == ANONYMOUS:
-    CredentialsMode.SAME_ORIGIN
-  else: CredentialsMode.INCLUDE
-  return newRequest(url, destination = destination, mode = mode, credentialsMode = credentialsMode)
-
-type
-  BodyInitType = enum
-    BODY_INIT_BLOB, BODY_INIT_FORM_DATA, BODY_INIT_URL_SEARCH_PARAMS,
-    BODY_INIT_STRING
-
-  BodyInit = object
-    #TODO ReadableStream, BufferSource
-    case t: BodyInitType
-    of BODY_INIT_BLOB:
-      blob: Blob
-    of BODY_INIT_FORM_DATA:
-      formData: FormData
-    of BODY_INIT_URL_SEARCH_PARAMS:
-      searchParams: URLSearchParams
-    of BODY_INIT_STRING:
-      str: string
-
-  RequestInit* = object of JSDict
-    #TODO aliasing in dicts
-    `method`: HttpMethod # default: GET
-    headers: Opt[HeadersInit]
-    body: Opt[BodyInit]
-    referrer: Opt[string]
-    referrerPolicy: Opt[ReferrerPolicy]
-    credentials: Opt[CredentialsMode]
-    proxyUrl: URL
-    mode: Opt[RequestMode]
-
-proc fromJS2*(ctx: JSContext, val: JSValue, res: var JSResult[BodyInit]) =
-  if JS_IsUndefined(val) or JS_IsNull(val):
-    res.err(nil)
-    return
-  if not JS_IsObject(val):
-    res.err(newTypeError("Not an object"))
-    return
-  block formData:
-    let x = fromJS[FormData](ctx, val)
-    if x.isSome:
-      res.ok(BodyInit(t: BODY_INIT_FORM_DATA, formData: x.get))
-      return
-  block blob:
-    let x = fromJS[Blob](ctx, val)
-    if x.isSome:
-      res.ok(BodyInit(t: BODY_INIT_BLOB, blob: x.get))
-      return
-  block searchParams:
-    let x = fromJS[URLSearchParams](ctx, val)
-    if x.isSome:
-      res.ok(BodyInit(t: BODY_INIT_URL_SEARCH_PARAMS, searchParams: x.get))
-      return
-  block str:
-    let x = fromJS[string](ctx, val)
-    if x.isSome:
-      res.ok(BodyInit(t: BODY_INIT_STRING, str: x.get))
-      return
-  res.err(newTypeError("Invalid body init type"))
-
-func newRequest*[T: string|Request](ctx: JSContext, resource: T,
-    init = none(RequestInit)): JSResult[Request] {.jsctor.} =
-  when T is string:
-    let url = ?newURL(resource)
-    if url.username != "" or url.password != "":
-      return err(newTypeError("Input URL contains a username or password"))
-    var httpMethod = HTTP_GET
-    var headers = newHeaders()
-    let referer: URL = nil
-    var credentials = CredentialsMode.SAME_ORIGIN
-    var body: Opt[string]
-    var multipart: Opt[FormData]
-    var proxyUrl: URL #TODO?
-    let fallbackMode = opt(RequestMode.CORS)
-  else:
-    let url = resource.url
-    var httpMethod = resource.httpMethod
-    var headers = resource.headers.clone()
-    let referer = resource.referer
-    var credentials = resource.credentialsMode
-    var body = resource.body
-    var multipart = resource.multipart
-    var proxyUrl = resource.proxy #TODO?
-    let fallbackMode = opt(RequestMode)
-    #TODO window
-  var mode = fallbackMode.get(RequestMode.NO_CORS)
-  let destination = NO_DESTINATION
-  #TODO origin, window
-  if init.isSome:
-    if mode == RequestMode.NAVIGATE:
-      mode = RequestMode.SAME_ORIGIN
-    #TODO flags?
-    #TODO referrer
-    let init = init.get
-    httpMethod = init.`method`
-    if init.body.isSome:
-      let ibody = init.body.get
-      case ibody.t
-      of BODY_INIT_FORM_DATA:
-        multipart = opt(ibody.formData)
-      of BODY_INIT_STRING:
-        body = opt(ibody.str)
-      else:
-        discard #TODO
-      if httpMethod in {HTTP_GET, HTTP_HEAD}:
-        return err(newTypeError("HEAD or GET Request cannot have a body."))
-    if init.headers.isSome:
-      headers.fill(init.headers.get)
-    if init.credentials.isSome:
-      credentials = init.credentials.get
-    if init.mode.isSome:
-      mode = init.mode.get
-    #TODO find a standard compatible way to implement this
-    proxyUrl = init.proxyUrl
-  return ok(Request(
-    url: url,
-    httpmethod: httpmethod,
-    headers: headers,
-    body: body,
-    multipart: multipart,
-    mode: mode,
-    credentialsMode: credentials,
-    destination: destination,
-    proxy: proxyUrl,
-    referer: referer
-  ))
-
-func credentialsMode*(attribute: CORSAttribute): CredentialsMode =
-  case attribute
-  of NO_CORS, ANONYMOUS:
-    return SAME_ORIGIN
-  of USE_CREDENTIALS:
-    return INCLUDE
-
-proc addRequestModule*(ctx: JSContext) =
-  ctx.registerType(Request)
diff --git a/src/io/response.nim b/src/io/response.nim
deleted file mode 100644
index 6d4db42f..00000000
--- a/src/io/response.nim
+++ /dev/null
@@ -1,74 +0,0 @@
-import streams
-
-import bindings/quickjs
-import io/headers
-import io/promise
-import io/request
-import js/error
-import js/javascript
-import types/url
-
-import chakasu/charset
-
-type
-  Response* = ref object
-    res*: int
-    fd*: int
-    body*: Stream
-    bodyUsed* {.jsget.}: bool
-    contenttype* {.jsget.}: string
-    status* {.jsget.}: uint16
-    headers* {.jsget.}: Headers
-    redirect*: Request
-    url*: URL #TODO should be urllist?
-    unregisterFun*: proc()
-    bodyRead*: Promise[string]
-    charset*: Charset
-
-jsDestructor(Response)
-
-proc newResponse*(res: int, request: Request, fd = -1, stream: Stream = nil):
-    Response =
-  return Response(
-    res: res,
-    url: request.url,
-    body: stream,
-    bodyRead: Promise[string](),
-    fd: fd
-  )
-
-func sok(response: Response): bool {.jsfget: "ok".} =
-  return response.status in 200u16 .. 299u16
-
-func surl(response: Response): string {.jsfget: "url".} =
-  return $response.url
-
-#TODO: this should be a property of body
-proc close*(response: Response) {.jsfunc.} =
-  response.bodyUsed = true
-  if response.unregisterFun != nil:
-    response.unregisterFun()
-  if response.body != nil:
-    response.body.close()
-
-proc text*(response: Response): Promise[JSResult[string]] {.jsfunc.} =
-  if response.bodyRead == nil:
-    let p = newPromise[JSResult[string]]()
-    let err = JSResult[string]
-      .err(newTypeError("Body has already been consumed"))
-    p.resolve(err)
-    return p
-  let bodyRead = response.bodyRead
-  response.bodyRead = nil
-  return bodyRead.then(proc(s: string): JSResult[string] =
-    ok(s))
-
-proc json(ctx: JSContext, this: Response): Promise[JSResult[JSValue]]
-    {.jsfunc.} =
-  return this.text().then(proc(s: JSResult[string]): JSResult[JSValue] =
-    let s = ?s
-    return ok(JS_ParseJSON(ctx, cstring(s), cast[csize_t](s.len),
-      cstring"<input>")))
-
-proc addResponseModule*(ctx: JSContext) =
-  ctx.registerType(Response)
diff --git a/src/io/serialize.nim b/src/io/serialize.nim
new file mode 100644
index 00000000..fd558ba9
--- /dev/null
+++ b/src/io/serialize.nim
@@ -0,0 +1,444 @@
+# Write data to streams.
+
+import options
+import sets
+import streams
+import tables
+
+import js/regex
+import loader/request
+import types/blob
+import types/buffersource
+import types/formdata
+import types/url
+import types/opt
+
+proc swrite*(stream: Stream, n: SomeNumber)
+proc sread*(stream: Stream, n: var SomeNumber)
+func slen*(n: SomeNumber): int
+
+proc swrite*[T](stream: Stream, s: set[T])
+proc sread*[T](stream: Stream, s: var set[T])
+func slen*[T](s: set[T]): int
+
+proc swrite*[T: enum](stream: Stream, x: T)
+proc sread*[T: enum](stream: Stream, x: var T)
+func slen*[T: enum](x: T): int
+
+proc swrite*(stream: Stream, s: string)
+proc sread*(stream: Stream, s: var string)
+func slen*(s: string): int
+
+proc swrite*(stream: Stream, b: bool)
+proc sread*(stream: Stream, b: var bool)
+func slen*(b: bool): int
+
+proc swrite*(stream: Stream, url: Url)
+proc sread*(stream: Stream, url: var Url)
+func slen*(url: Url): int
+
+proc swrite*(stream: Stream, tup: tuple)
+proc sread*(stream: Stream, tup: var tuple)
+func slen*(tup: tuple): int
+
+proc swrite*[T](stream: Stream, s: seq[T])
+proc sread*[T](stream: Stream, s: var seq[T])
+func slen*(s: seq): int
+
+proc swrite*[U, V](stream: Stream, t: Table[U, V])
+proc sread*[U, V](stream: Stream, t: var Table[U, V])
+func slen*[U, V](t: Table[U, V]): int
+
+proc swrite*(stream: Stream, obj: object)
+proc sread*(stream: Stream, obj: var object)
+func slen*(obj: object): int
+
+proc swrite*(stream: Stream, obj: ref object)
+proc sread*(stream: Stream, obj: var ref object)
+func slen*(obj: ref object): int
+
+proc swrite*(stream: Stream, part: FormDataEntry)
+proc sread*(stream: Stream, part: var FormDataEntry)
+func slen*(part: FormDataEntry): int
+
+proc swrite*(stream: Stream, blob: Blob)
+proc sread*(stream: Stream, blob: var Blob)
+func slen*(blob: Blob): int
+
+proc swrite*[T](stream: Stream, o: Option[T])
+proc sread*[T](stream: Stream, o: var Option[T])
+func slen*[T](o: Option[T]): int
+
+proc swrite*[T, E](stream: Stream, o: Result[T, E])
+proc sread*[T, E](stream: Stream, o: var Result[T, E])
+func slen*[T, E](o: Result[T, E]): int
+
+proc swrite*(stream: Stream, regex: Regex)
+proc sread*(stream: Stream, regex: var Regex)
+func slen*(regex: Regex): int
+
+proc swrite*(stream: Stream, source: BufferSource)
+proc sread*(stream: Stream, source: var BufferSource)
+func slen*(source: BufferSource): int
+
+proc swrite*(stream: Stream, n: SomeNumber) =
+  stream.write(n)
+
+proc sread*(stream: Stream, n: var SomeNumber) =
+  if stream.readData(addr n, sizeof(n)) < sizeof(n):
+    raise newException(EOFError, "eof")
+
+func slen*(n: SomeNumber): int =
+  return sizeof(n)
+
+proc swrite*[T: enum](stream: Stream, x: T) =
+  static:
+    doAssert sizeof(int) >= sizeof(T)
+  stream.swrite(int(x))
+
+proc sread*[T: enum](stream: Stream, x: var T) =
+  var i: int
+  stream.sread(i)
+  x = cast[T](i)
+
+func slen*[T: enum](x: T): int =
+  return sizeof(int)
+
+proc swrite*[T](stream: Stream, s: set[T]) =
+  stream.swrite(s.card)
+  for e in s:
+    stream.swrite(e)
+
+proc sread*[T](stream: Stream, s: var set[T]) =
+  var len: int
+  stream.sread(len)
+  for i in 0 ..< len:
+    var x: T
+    stream.sread(x)
+    s.incl(x)
+
+func slen*[T](s: set[T]): int =
+  result = slen(s.card)
+  for x in s:
+    result += slen(x)
+
+proc swrite*(stream: Stream, s: string) =
+  stream.swrite(s.len)
+  stream.write(s)
+
+proc sread*(stream: Stream, s: var string) =
+  var len: int
+  stream.sread(len)
+  if len > 0:
+    s = newString(len)
+    prepareMutation(s)
+    if stream.readData(addr s[0], len) < len:
+      raise newException(EOFError, "eof")
+  else:
+    s = ""
+
+func slen*(s: string): int =
+  slen(s.len) + s.len
+
+proc swrite*(stream: Stream, b: bool) =
+  if b:
+    stream.swrite(1u8)
+  else:
+    stream.swrite(0u8)
+
+proc sread*(stream: Stream, b: var bool) =
+  var n: uint8
+  stream.sread(n)
+  if n == 1u8:
+    b = true
+  else:
+    assert n == 0u8
+    b = false
+
+func slen*(b: bool): int =
+  return sizeof(uint8)
+
+proc swrite*(stream: Stream, url: URL) =
+  if url != nil:
+    stream.swrite(url.serialize())
+  else:
+    stream.swrite("")
+
+proc sread*(stream: Stream, url: var URL) =
+  var s: string
+  stream.sread(s)
+  if s == "":
+    url = nil
+  else:
+    let x = newURL(s)
+    if x.isSome:
+      url = x.get
+    else:
+      url = nil
+
+func slen*(url: URL): int =
+  if url == nil:
+    return slen("")
+  return slen(url.serialize())
+
+proc swrite*(stream: Stream, tup: tuple) =
+  for f in tup.fields:
+    stream.swrite(f)
+
+proc sread*(stream: Stream, tup: var tuple) =
+  for f in tup.fields:
+    stream.sread(f)
+
+func slen*(tup: tuple): int =
+  for f in tup.fields:
+    result += slen(f)
+
+proc swrite*[T](stream: Stream, s: seq[T]) =
+  stream.swrite(s.len)
+  var i = 0
+  for m in s:
+    stream.swrite(m)
+    inc i
+
+proc sread*[T](stream: Stream, s: var seq[T]) =
+  var len: int
+  stream.sread(len)
+  s.setLen(len)
+  for i in 0..<len:
+    stream.sread(s[i])
+
+func slen*(s: seq): int =
+  result = slen(s.len)
+  for x in s:
+    result += slen(x)
+
+proc swrite*[U, V](stream: Stream, t: Table[U, V]) =
+  stream.swrite(t.len)
+  for k, v in t:
+    stream.swrite(k)
+    stream.swrite(v)
+
+proc sread*[U, V](stream: Stream, t: var Table[U, V]) =
+  var len: int
+  stream.sread(len)
+  for i in 0..<len:
+    var k: U
+    stream.sread(k)
+    var v: V
+    stream.sread(v)
+    t[k] = v
+
+func slen*[U, V](t: Table[U, V]): int =
+  result = slen(t.len)
+  for k, v in t:
+    result += slen(k)
+    result += slen(v)
+
+proc swrite*(stream: Stream, obj: object) =
+  for f in obj.fields:
+    stream.swrite(f)
+
+proc sread*(stream: Stream, obj: var object) =
+  for f in obj.fields:
+    stream.sread(f)
+
+func slen*(obj: object): int =
+  for f in obj.fields:
+    result += slen(f)
+
+proc swrite*(stream: Stream, obj: ref object) =
+  stream.swrite(obj != nil)
+  if obj != nil:
+    stream.swrite(obj[])
+
+proc sread*(stream: Stream, obj: var ref object) =
+  var n: bool
+  stream.sread(n)
+  if n:
+    new(obj)
+    stream.sread(obj[])
+
+func slen*(obj: ref object): int =
+  result = slen(obj != nil)
+  if obj != nil:
+    result += slen(obj[])
+
+proc swrite*(stream: Stream, part: FormDataEntry) =
+  stream.swrite(part.isstr)
+  stream.swrite(part.name)
+  stream.swrite(part.filename)
+  if part.isstr:
+    stream.swrite(part.svalue)
+  else:
+    stream.swrite(part.value)
+
+proc sread*(stream: Stream, part: var FormDataEntry) =
+  var isstr: bool
+  stream.sread(isstr)
+  if isstr:
+    part = FormDataEntry(isstr: true)
+  else:
+    part = FormDataEntry(isstr: false)
+  stream.sread(part.name)
+  stream.sread(part.filename)
+  if part.isstr:
+    stream.sread(part.svalue)
+  else:
+    stream.sread(part.value)
+
+func slen*(part: FormDataEntry): int =
+  result += slen(part.isstr)
+  result += slen(part.name)
+  result += slen(part.filename)
+  if part.isstr:
+    result += slen(part.svalue)
+  else:
+    result += slen(part.value)
+
+#TODO clean up this mess
+proc swrite*(stream: Stream, blob: Blob) =
+  stream.swrite(blob.isfile)
+  if blob.isfile:
+    stream.swrite(WebFile(blob).path)
+  else:
+    stream.swrite(blob.ctype)
+    stream.swrite(blob.size)
+    stream.writeData(blob.buffer, int(blob.size))
+
+proc sread*(stream: Stream, blob: var Blob) =
+  var isfile: bool
+  stream.sread(isfile)
+  if isfile:
+    var file = new WebFile
+    file.isfile = true
+    stream.sread(file.path)
+    blob = file
+  else:
+    new(blob)
+    stream.sread(blob.ctype)
+    stream.sread(blob.size)
+    blob.buffer = alloc(blob.size)
+    blob.deallocFun = dealloc
+    if blob.size > 0:
+      assert stream.readData(blob.buffer, int(blob.size)) == int(blob.size)
+
+func slen*(blob: Blob): int =
+  result += slen(blob.isfile)
+  if blob.isfile:
+    result = slen(WebFile(blob).path)
+  else:
+    result += slen(blob.ctype)
+    result += slen(blob.size)
+    result += int(blob.size) #TODO ??
+
+proc swrite*[T](stream: Stream, o: Option[T]) =
+  stream.swrite(o.issome)
+  if o.isSome:
+    stream.swrite(o.get)
+
+proc sread*[T](stream: Stream, o: var Option[T]) =
+  var x: bool
+  stream.sread(x)
+  if x:
+    var m: T
+    stream.sread(m)
+    o = some(m)
+  else:
+    o = none(T)
+
+func slen*[T](o: Option[T]): int =
+  result = slen(o.isSome)
+  if o.isSome:
+    result += slen(o.get)
+
+proc swrite*[T, E](stream: Stream, o: Result[T, E]) =
+  stream.swrite(o.isOk)
+  if o.isOk:
+    when not (T is void):
+      stream.swrite(o.get)
+  else:
+    when not (E is void):
+      stream.swrite(o.error)
+
+proc sread*[T, E](stream: Stream, o: var Result[T, E]) =
+  var x: bool
+  stream.sread(x)
+  if x:
+    when not (T is void):
+      var m: T
+      stream.sread(m)
+      o.ok(m)
+    else:
+      o.ok()
+  else:
+    when not (E is void):
+      var e: E
+      stream.sread(e)
+      o.err(e)
+    else:
+      o.err()
+
+func slen*[T, E](o: Result[T, E]): int =
+  result = slen(o.isSome)
+  if o.isSome:
+    when not (T is void):
+      result += slen(o.get)
+  else:
+    when not (E is void):
+      result += slen(o.error)
+
+proc swrite*(stream: Stream, regex: Regex) =
+  stream.swrite(regex.plen)
+  stream.writeData(regex.bytecode, regex.plen)
+  stream.swrite(regex.buf)
+
+proc sread*(stream: Stream, regex: var Regex) =
+  assert regex.bytecode == nil
+  stream.sread(regex.plen)
+  regex.bytecode = cast[ptr uint8](alloc(regex.plen))
+  regex.clone = true
+  let l = stream.readData(regex.bytecode, regex.plen)
+  stream.sread(regex.buf)
+  if l != regex.plen:
+    `=destroy`(regex)
+
+func slen*(regex: Regex): int =
+  result += slen(regex.plen)
+  result += regex.plen
+  result += slen(regex.buf)
+
+proc swrite*(stream: Stream, source: BufferSource) =
+  stream.swrite(source.t)
+  case source.t
+  of CLONE: stream.swrite(source.clonepid)
+  of LOAD_REQUEST: stream.swrite(source.request)
+  of LOAD_PIPE: stream.swrite(source.fd)
+  stream.swrite(source.location)
+  stream.swrite(source.contenttype)
+  stream.swrite(source.charset)
+
+proc sread*(stream: Stream, source: var BufferSource) =
+  var t: BufferSourceType
+  stream.sread(t)
+  case t
+  of CLONE:
+    source = BufferSource(t: CLONE)
+    stream.sread(source.clonepid)
+  of LOAD_REQUEST:
+    source = BufferSource(t: LOAD_REQUEST)
+    stream.sread(source.request)
+  of LOAD_PIPE:
+    source = BufferSource(t: LOAD_PIPE)
+    stream.sread(source.fd)
+  stream.sread(source.location)
+  stream.sread(source.contenttype)
+  stream.sread(source.charset)
+
+func slen*(source: BufferSource): int =
+  result += slen(source.t)
+  case source.t
+  of CLONE: result += slen(source.clonepid)
+  of LOAD_REQUEST: result += slen(source.request)
+  of LOAD_PIPE: result += slen(source.fd)
+  result += slen(source.location)
+  result += slen(source.contenttype)
diff --git a/src/io/serversocket.nim b/src/io/serversocket.nim
new file mode 100644
index 00000000..61b633a9
--- /dev/null
+++ b/src/io/serversocket.nim
@@ -0,0 +1,28 @@
+import nativesockets
+import net
+import os
+when defined(posix):
+  import posix
+
+type ServerSocket* = object
+  sock*: Socket
+  path*: string
+
+var SocketDirectory* = "/tmp/cha"
+const SocketPathPrefix = "cha_sock_"
+proc getSocketPath*(pid: Pid): string =
+  SocketDirectory / SocketPathPrefix & $pid
+
+proc initServerSocket*(buffered = true, blocking = true): ServerSocket =
+  createDir(SocketDirectory)
+  result.sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP, buffered)
+  if not blocking:
+    result.sock.getFd().setBlocking(false)
+  result.path = getSocketPath(getpid())
+  discard unlink(cstring(result.path))
+  bindUnix(result.sock, result.path)
+  listen(result.sock)
+
+proc close*(ssock: ServerSocket) =
+  close(ssock.sock)
+  discard unlink(cstring(ssock.path))
diff --git a/src/io/socketstream.nim b/src/io/socketstream.nim
new file mode 100644
index 00000000..6b648003
--- /dev/null
+++ b/src/io/socketstream.nim
@@ -0,0 +1,147 @@
+import nativesockets
+import net
+import os
+import streams
+
+when defined(posix):
+  import posix
+
+import io/posixstream
+import io/serversocket
+
+type SocketStream* = ref object of Stream
+  source*: Socket
+  blk*: bool
+  isend: bool
+
+proc sockReadData(s: Stream, buffer: pointer, len: int): int =
+  assert len != 0
+  let s = SocketStream(s)
+  let wasend = s.isend
+  if s.blk:
+    while result < len:
+      let n = s.source.recv(cast[pointer](cast[int](buffer) + result), len - result)
+      if n < 0:
+        if result == 0:
+          result = n
+        break
+      elif n == 0:
+        s.isend = true
+        break
+      result += n
+  else:
+    result = s.source.recv(buffer, len)
+  if result == 0:
+    if wasend:
+      raise newException(EOFError, "eof")
+    s.isend = true
+  if result < 0:
+    raisePosixIOError()
+  elif result == 0:
+    s.isend = true
+
+proc sockWriteData(s: Stream, buffer: pointer, len: int) =
+  var i = 0
+  while i < len:
+    let n = SocketStream(s).source.send(cast[pointer](cast[int](buffer) + i), len - i)
+    if n < 0:
+      raise newException(IOError, $strerror(errno))
+    i += n
+
+proc sockAtEnd(s: Stream): bool =
+  SocketStream(s).isend
+
+proc sockClose(s: Stream) = {.cast(tags: []).}: #...sigh
+  let s = SocketStream(s)
+  s.source.close()
+
+# See https://stackoverflow.com/a/4491203
+proc sendFileHandle*(s: SocketStream, fd: FileHandle) =
+  assert not s.source.hasDataBuffered
+  var hdr: Tmsghdr
+  var iov: IOVec
+  var space: csize_t
+  {.emit: [
+  space, """ = CMSG_SPACE(sizeof(int));""",
+  ].}
+  var cmsgbuf = alloc(cast[int](space))
+  var buf = char(0)
+  iov.iov_base = addr buf
+  iov.iov_len = csize_t(1)
+  zeroMem(addr hdr, sizeof(hdr))
+  hdr.msg_iov = addr iov
+  hdr.msg_iovlen = 1
+  hdr.msg_control = cmsgbuf
+  # ...sigh
+  {.emit: [
+  hdr.msg_controllen, """ = CMSG_LEN(sizeof(int));""",
+  ].}
+  let cmsg = CMSG_FIRSTHDR(addr hdr)
+  # FileHandle is cint, so sizeof(FileHandle) in c is sizeof(int).
+  when sizeof(FileHandle) != sizeof(cint):
+    error("Or not...")
+  {.emit: [
+  cmsg.cmsg_len, """ = CMSG_LEN(sizeof(int));"""
+  ].}
+  cmsg.cmsg_level = SOL_SOCKET
+  cmsg.cmsg_type = SCM_RIGHTS
+  cast[ptr FileHandle](CMSG_DATA(cmsg))[] = fd
+  let n = sendmsg(s.source.getFd(), addr hdr, 0)
+  dealloc(cmsgbuf)
+  assert n == int(iov.iov_len) #TODO remove this
+
+proc recvFileHandle*(s: SocketStream): FileHandle =
+  assert not s.source.hasDataBuffered
+  var iov: IOVec
+  var hdr: Tmsghdr
+  var buf: char
+  var cmsgbuf = alloc(CMSG_SPACE(csize_t(sizeof(FileHandle))))
+  iov.iov_base = addr buf
+  iov.iov_len = 1
+  zeroMem(addr hdr, sizeof(hdr))
+  hdr.msg_iov = addr iov
+  hdr.msg_iovlen = 1
+  hdr.msg_control = cmsgbuf
+  {.emit: [
+  hdr.msg_controllen, """ = CMSG_SPACE(sizeof(int));"""
+  ].}
+  let n = recvmsg(s.source.getFd(), addr hdr, 0)
+  assert n != 0, "Unexpected EOF" #TODO remove this
+  assert n > 0, "Failed to receive message " & $osLastError() #TODO remove this
+  var cmsg = CMSG_FIRSTHDR(addr hdr)
+  result = cast[ptr FileHandle](CMSG_DATA(cmsg))[]
+  dealloc(cmsgbuf)
+
+func newSocketStream*(): SocketStream =
+  new(result)
+  result.readDataImpl = cast[proc (s: Stream, buffer: pointer, bufLen: int): int
+      {.nimcall, raises: [Defect, IOError, OSError], tags: [ReadIOEffect], gcsafe.}
+  ](sockReadData) # ... ???
+  result.writeDataImpl = sockWriteData
+  result.atEndImpl = sockAtEnd
+  result.closeImpl = sockClose
+
+proc setBlocking*(ss: SocketStream, blocking: bool) =
+  ss.source.getFd().setBlocking(blocking)
+
+proc connectSocketStream*(path: string, buffered = true, blocking = true): SocketStream =
+  result = newSocketStream()
+  result.blk = blocking
+  let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP, buffered)
+  if not blocking:
+    sock.getFd().setBlocking(false)
+  connectUnix(sock, path)
+  result.source = sock
+
+proc connectSocketStream*(pid: Pid, buffered = true, blocking = true): SocketStream =
+  try:
+    connectSocketStream(getSocketPath(pid), buffered, blocking)
+  except OSError:
+    return nil
+
+proc acceptSocketStream*(ssock: ServerSocket, blocking = true): SocketStream =
+  result = newSocketStream()
+  result.blk = blocking
+  var sock: Socket
+  ssock.sock.accept(sock, inheritable = true)
+  result.source = sock
diff --git a/src/io/tempfile.nim b/src/io/tempfile.nim
deleted file mode 100644
index d99ea4dc..00000000
--- a/src/io/tempfile.nim
+++ /dev/null
@@ -1,18 +0,0 @@
-import os
-
-var tmpf_seq: int
-proc getTempFile*(tmpdir: string, ext = ""): string =
-  if not dirExists(tmpdir):
-    createDir(tmpdir)
-  var tmpf = tmpdir / "chatmp" & $tmpf_seq
-  if ext != "":
-    tmpf &= "."
-    tmpf &= ext
-  while fileExists(tmpf):
-    inc tmpf_seq
-    tmpf = tmpdir / "chatmp" & $tmpf_seq
-    if ext != "":
-      tmpf &= "."
-      tmpf &= ext
-  inc tmpf_seq
-  return tmpf
diff --git a/src/io/window.nim b/src/io/window.nim
deleted file mode 100644
index 278fc3fb..00000000
--- a/src/io/window.nim
+++ /dev/null
@@ -1,54 +0,0 @@
-import terminal
-
-when defined(posix):
-  import termios
-
-
-type
-  WindowAttributes* = object
-    width*: int
-    height*: int
-    ppc*: int # cell width
-    ppl*: int # cell height
-    cell_ratio*: float64 # ppl / ppc
-    width_px*: int
-    height_px*: int
-
-proc getWindowAttributes*(tty: File): WindowAttributes =
-  when defined(posix):
-    if tty.isatty():
-      var win: IOctl_WinSize
-      if ioctl(cint(getOsFileHandle(tty)), TIOCGWINSZ, addr win) != -1:
-        var cols = win.ws_col
-        var rows = win.ws_row
-        if cols == 0:
-          cols = 80
-        if rows == 0:
-          rows = 24
-        # Filling the last row without raw mode breaks things. However,
-        # not supporting Windows means we can always have raw mode, so we can
-        # use all available columns.
-        result.width = int(cols)
-        result.height = int(rows)
-        result.ppc = int(win.ws_xpixel) div result.width
-        result.ppl = int(win.ws_ypixel) div result.height
-        # some terminal emulators (aka vte) don't set ws_xpixel or ws_ypixel.
-        # solution: use xterm.
-        if result.ppc == 0:
-          result.ppc = 9
-        if result.ppl == 0:
-          result.ppl = 18
-        result.width_px = result.width * result.ppc
-        result.height_px = result.height * result.ppl
-        result.cell_ratio = result.ppl / result.ppc
-        return
-  # for Windows. unused.
-  result.width = terminalWidth() - 1
-  result.height = terminalHeight()
-  if result.height == 0:
-    result.height = 24
-  result.ppc = 9
-  result.ppl = 18
-  result.cell_ratio = result.ppl / result.ppc
-  result.width_px = result.ppc * result.width
-  result.height_px = result.ppl * result.height
lass="p">>,</span> p<span class="Delimiter">-&gt;</span>first<span class="Delimiter">))</span> <span id="L112" class="LineNr">112 </span> <span class="Normal">delete</span> p<span class="Delimiter">-&gt;</span>second<span class="Delimiter">;</span> <span id="L113" class="LineNr">113 </span> <span class="Delimiter">}</span> <span id="L114" class="LineNr">114 </span> <span class="Special">Type_abbreviations</span><span class="Delimiter">.</span><a href='050scenario.cc.html#L60'>clear</a><span class="Delimiter">();</span> <span id="L115" class="LineNr">115 </span> <span class="Special">Type_abbreviations</span> = <span class="Special">Type_abbreviations_snapshot</span><span class="Delimiter">;</span> <span id="L116" class="LineNr">116 </span><span class="Delimiter">}</span> <span id="L117" class="LineNr">117 </span><span class="Normal">void</span> <a href='019type_abbreviations.cc.html#L117'>clear_type_abbreviations</a><span class="Delimiter">()</span> <span class="Delimiter">{</span> <span id="L118" class="LineNr">118 </span> <span class="Normal">for</span> <span class="Delimiter">(</span>map&lt;string<span class="Delimiter">,</span> type_tree*&gt;::iterator p = <span class="Special">Type_abbreviations</span><span class="Delimiter">.</span>begin<span class="Delimiter">();</span> p != <span class="Special">Type_abbreviations</span><span class="Delimiter">.</span><a href='003trace.cc.html#L225'>end</a><span class="Delimiter">();</span> ++p<span class="Delimiter">)</span> <span id="L119" class="LineNr">119 </span> <span class="Normal">delete</span> p<span class="Delimiter">-&gt;</span>second<span class="Delimiter">;</span> <span id="L120" class="LineNr">120 </span> <span class="Special">Type_abbreviations</span><span class="Delimiter">.</span><a href='050scenario.cc.html#L60'>clear</a><span class="Delimiter">();</span> <span id="L121" class="LineNr">121 </span><span class="Delimiter">}</span> <span id="L122" class="LineNr">122 </span> <span id="L123" class="LineNr">123 </span><span class="SalientComment">//:: A few default abbreviations.</span> <span id="L124" class="LineNr">124 </span> <span id="L125" class="LineNr">125 </span><span class="Delimiter">:(before &quot;End Mu Types Initialization&quot;)</span> <span id="L126" class="LineNr">126 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> <span class="Constant">&quot;&amp;&quot;</span><span class="Delimiter">,</span> <a href='019type_abbreviations.cc.html#L58'>new_type_tree</a><span class="Delimiter">(</span><span class="Constant">&quot;address&quot;</span><span class="Delimiter">));</span> <span id="L127" class="LineNr">127 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> <span class="Constant">&quot;@&quot;</span><span class="Delimiter">,</span> <a href='019type_abbreviations.cc.html#L58'>new_type_tree</a><span class="Delimiter">(</span><span class="Constant">&quot;array&quot;</span><span class="Delimiter">));</span> <span id="L128" class="LineNr">128 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> <span class="Constant">&quot;num&quot;</span><span class="Delimiter">,</span> <a href='019type_abbreviations.cc.html#L58'>new_type_tree</a><span class="Delimiter">(</span><span class="Constant">&quot;number&quot;</span><span class="Delimiter">));</span> <span id="L129" class="LineNr">129 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> <span class="Constant">&quot;bool&quot;</span><span class="Delimiter">,</span> <a href='019type_abbreviations.cc.html#L58'>new_type_tree</a><span class="Delimiter">(</span><span class="Constant">&quot;boolean&quot;</span><span class="Delimiter">));</span> <span id="L130" class="LineNr">130 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> <span class="Constant">&quot;char&quot;</span><span class="Delimiter">,</span> <a href='019type_abbreviations.cc.html#L58'>new_type_tree</a><span class="Delimiter">(</span><span class="Constant">&quot;character&quot;</span><span class="Delimiter">));</span> <span id="L131" class="LineNr">131 </span> <span id="L132" class="LineNr">132 </span><span class="Delimiter">:(scenario use_type_abbreviations_when_declaring_type_abbreviations)</span> <span id="L133" class="LineNr">133 </span><span class="muData">type</span> foo = &amp;:num <span id="L134" class="LineNr">134 </span><span class="muRecipe">def</span> <a href='000organization.cc.html#L113'>main</a> [ <span id="L135" class="LineNr">135 </span> <span class="Constant">1</span>:foo<span class="Special"> &lt;- </span>copy<span class="Constant"> null</span> <span id="L136" class="LineNr">136 </span>] <span id="L137" class="LineNr">137 </span><span class="traceContains">+transform: product type after expanding abbreviations: (&quot;address&quot; &quot;number&quot;)</span> <span id="L138" class="LineNr">138 </span> <span id="L139" class="LineNr">139 </span><span class="SalientComment">//:: Expand type aliases before running.</span> <span id="L140" class="LineNr">140 </span><span class="Comment">//: We'll do this in a transform so that we don't need to define abbreviations</span> <span id="L141" class="LineNr">141 </span><span class="Comment">//: before we use them.</span> <span id="L142" class="LineNr">142 </span> <span id="L143" class="LineNr">143 </span><span class="Delimiter">:(scenario abbreviations_for_address_and_array)</span> <span id="L144" class="LineNr">144 </span><span class="muRecipe">def</span> <a href='000organization.cc.html#L113'>main</a> [ <span id="L145" class="LineNr">145 </span> f <span class="Constant">1</span>:&amp;:num <span class="Comment"># abbreviation for 'address:number'</span> <span id="L146" class="LineNr">146 </span> f <span class="Constant">2</span>:@:num <span class="Comment"># abbreviation for 'array:number'</span> <span id="L147" class="LineNr">147 </span> f <span class="Constant">3</span>:&amp;:@:num <span class="Comment"># combining '&amp;' and '@'</span> <span id="L148" class="LineNr">148 </span> f <span class="Constant">4</span>:&amp;:&amp;:@:&amp;:@:num <span class="Comment"># ..any number of times</span> <span id="L149" class="LineNr">149 </span> f <span class="Delimiter">{</span><span class="Constant">5</span>: <span class="Delimiter">(</span>array <span class="Delimiter">(</span>&amp; num<span class="Delimiter">)</span> <span class="Constant">3</span><span class="Delimiter">)}</span> <span class="Comment"># support for dilated reagents and more complex parse trees</span> <span id="L150" class="LineNr">150 </span>] <span id="L151" class="LineNr">151 </span><span class="muRecipe">def</span> f [ <span id="L152" class="LineNr">152 </span>] <span id="L153" class="LineNr">153 </span><span class="traceContains">+transform: --- expand type abbreviations in <a href='010vm.cc.html#L19'>recipe</a> 'main'</span> <span id="L154" class="LineNr">154 </span><span class="traceContains">+transform: ingredient type after expanding abbreviations: (&quot;address&quot; &quot;number&quot;)</span> <span id="L155" class="LineNr">155 </span><span class="traceContains">+transform: ingredient type after expanding abbreviations: (&quot;array&quot; &quot;number&quot;)</span> <span id="L156" class="LineNr">156 </span><span class="traceContains">+transform: ingredient type after expanding abbreviations: (&quot;address&quot; &quot;array&quot; &quot;number&quot;)</span> <span id="L157" class="LineNr">157 </span><span class="traceContains">+transform: ingredient type after expanding abbreviations: (&quot;address&quot; &quot;address&quot; &quot;array&quot; &quot;address&quot; &quot;array&quot; &quot;number&quot;)</span> <span id="L158" class="LineNr">158 </span><span class="traceContains">+transform: ingredient type after expanding abbreviations: (&quot;array&quot; (&quot;address&quot; &quot;number&quot;) &quot;3&quot;)</span> <span id="L159" class="LineNr">159 </span> <span id="L160" class="LineNr">160 </span><span class="Delimiter">:(before &quot;Transform.push_back(update_instruction_operations)&quot;)</span> <span id="L161" class="LineNr">161 </span><span class="Special">Transform</span><span class="Delimiter">.</span>push_back<span class="Delimiter">(</span>expand_type_abbreviations<span class="Delimiter">);</span> <span class="Comment">// idempotent</span> <span id="L162" class="LineNr">162 </span><span class="Comment">// Begin Type Modifying Transforms</span> <span id="L163" class="LineNr">163 </span><span class="Comment">// End Type Modifying Transforms</span> <span id="L164" class="LineNr">164 </span> <span id="L165" class="LineNr">165 </span><span class="Delimiter">:(code)</span> <span id="L166" class="LineNr">166 </span><span class="Normal">void</span> expand_type_abbreviations<span class="Delimiter">(</span><span class="Normal">const</span> <a href='010vm.cc.html#L14'>recipe_ordinal</a> r<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L167" class="LineNr">167 </span> expand_type_abbreviations<span class="Delimiter">(</span>get<span class="Delimiter">(</span><span class="Special">Recipe</span><span class="Delimiter">,</span> r<span class="Delimiter">));</span> <span id="L168" class="LineNr">168 </span><span class="Delimiter">}</span> <span id="L169" class="LineNr">169 </span> <span id="L170" class="LineNr">170 </span><span class="Normal">void</span> expand_type_abbreviations<span class="Delimiter">(</span><span class="Normal">const</span> recipe&amp; caller<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L171" class="LineNr">171 </span> <a href='003trace.cc.html#L189'>trace</a><span class="Delimiter">(</span><span class="Constant">9991</span><span class="Delimiter">,</span> <span class="Constant">&quot;transform&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;--- expand type abbreviations in <a href='010vm.cc.html#L19'>recipe</a> '&quot;</span> &lt;&lt; caller<span class="Delimiter">.</span>name &lt;&lt; <span class="Constant">&quot;'&quot;</span> &lt;&lt; <a href='003trace.cc.html#L225'>end</a><span class="Delimiter">();</span> <span id="L172" class="LineNr">172 </span> <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span> i &lt; <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>caller<span class="Delimiter">.</span>steps<span class="Delimiter">);</span> ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L173" class="LineNr">173 </span> <span class="Normal">const</span> instruction&amp; inst = caller<span class="Delimiter">.</span>steps<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">);</span> <span id="L174" class="LineNr">174 </span> <a href='003trace.cc.html#L189'>trace</a><span class="Delimiter">(</span><span class="Constant">9991</span><span class="Delimiter">,</span> <span class="Constant">&quot;transform&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;instruction '&quot;</span> &lt;&lt; to_original_string<span class="Delimiter">(</span>inst<span class="Delimiter">)</span> &lt;&lt; end<span class="Delimiter">();</span> <span id="L175" class="LineNr">175 </span> <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">long</span> <span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span> i &lt; <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">);</span> ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L176" class="LineNr">176 </span> expand_type_abbreviations<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>type<span class="Delimiter">);</span> <span id="L177" class="LineNr">177 </span> <a href='003trace.cc.html#L189'>trace</a><span class="Delimiter">(</span><span class="Constant">9992</span><span class="Delimiter">,</span> <span class="Constant">&quot;transform&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;ingredient type after expanding abbreviations: &quot;</span> &lt;&lt; names_to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>type<span class="Delimiter">)</span> &lt;&lt; <a href='003trace.cc.html#L225'>end</a><span class="Delimiter">();</span> <span id="L178" class="LineNr">178 </span> <span class="Delimiter">}</span> <span id="L179" class="LineNr">179 </span> <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">long</span> <span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span> i &lt; <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">);</span> ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L180" class="LineNr">180 </span> expand_type_abbreviations<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>type<span class="Delimiter">);</span> <span id="L181" class="LineNr">181 </span> <a href='003trace.cc.html#L189'>trace</a><span class="Delimiter">(</span><span class="Constant">9992</span><span class="Delimiter">,</span> <span class="Constant">&quot;transform&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;product type after expanding abbreviations: &quot;</span> &lt;&lt; names_to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>type<span class="Delimiter">)</span> &lt;&lt; <a href='003trace.cc.html#L225'>end</a><span class="Delimiter">();</span> <span id="L182" class="LineNr">182 </span> <span class="Delimiter">}</span> <span id="L183" class="LineNr">183 </span> <span class="Delimiter">}</span> <span id="L184" class="LineNr">184 </span> <span class="Comment">// End Expand Type Abbreviations(caller)</span> <span id="L185" class="LineNr">185 </span><span class="Delimiter">}</span> <span id="L186" class="LineNr">186 </span> <span id="L187" class="LineNr">187 </span><span class="Normal">void</span> expand_type_abbreviations<span class="Delimiter">(</span>type_tree* type<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L188" class="LineNr">188 </span> <span class="Normal">if</span> <span class="Delimiter">(</span>!type<span class="Delimiter">)</span> <span class="Identifier">return</span><span class="Delimiter">;</span> <span id="L189" class="LineNr">189 </span> <span class="Normal">if</span> <span class="Delimiter">(</span>!type<span class="Delimiter">-&gt;</span>atom<span class="Delimiter">)</span> <span class="Delimiter">{</span> <span id="L190" class="LineNr">190 </span> expand_type_abbreviations<span class="Delimiter">(</span>type<span class="Delimiter">-&gt;</span>left<span class="Delimiter">);</span> <span id="L191" class="LineNr">191 </span> expand_type_abbreviations<span class="Delimiter">(</span>type<span class="Delimiter">-&gt;</span>right<span class="Delimiter">);</span> <span id="L192" class="LineNr">192 </span> <span class="Identifier">return</span><span class="Delimiter">;</span> <span id="L193" class="LineNr">193 </span> <span class="Delimiter">}</span> <span id="L194" class="LineNr">194 </span> <span class="Normal">if</span> <span class="Delimiter">(</span><a href='001help.cc.html#L226'>contains_key</a><span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> type<span class="Delimiter">-&gt;</span>name<span class="Delimiter">))</span> <span id="L195" class="LineNr">195 </span> *type = type_tree<span class="Delimiter">(</span>*get<span class="Delimiter">(</span><span class="Special">Type_abbreviations</span><span class="Delimiter">,</span> type<span class="Delimiter">-&gt;</span>name<span class="Delimiter">));</span> <span id="L196" class="LineNr">196 </span><span class="Delimiter">}</span> </pre> </body> </html> <!-- vim: set foldmethod=manual : -->