about summary refs log tree commit diff stats
path: root/src/js/encoding.nim
blob: 31f6f5750405af06f1acbf3a8fcff5d2b68eef7a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import std/streams

import js/arraybuffer
import js/dict
import js/error
import js/javascript

import chakasu/charset
import chakasu/decoderstream
import chakasu/encoderstream

type
  TextEncoder = ref object

  TextDecoder = ref object
    encoding: Charset
    errorMode: DecoderErrorMode
    ignoreBOM {.jsget.}: bool
    doNotFlush: bool
    bomSeen: bool
    decoder: DecoderStream
    encoder: EncoderStream # to return the string to JS
    istream: StringStream

jsDestructor(TextDecoder)
jsDestructor(TextEncoder)

type TextDecoderOptions = object of JSDict
  fatal: bool
  ignoreBOM: bool

func newTextDecoder(label = "utf-8", options = TextDecoderOptions()):
    JSResult[TextDecoder] {.jsctor.} =
  let errorMode = if options.fatal:
    DECODER_ERROR_MODE_FATAL
  else:
    DECODER_ERROR_MODE_REPLACEMENT
  let encoding = getCharset(label)
  if encoding in {CHARSET_UNKNOWN, CHARSET_REPLACEMENT}:
    return err(newRangeError("Invalid encoding label"))
  return ok(TextDecoder(
    errorMode: errorMode,
    ignoreBOM: options.ignoreBOM,
    encoding: encoding
  ))

type TextDecodeOptions = object of JSDict
  stream: bool

#TODO AllowSharedBufferSource
proc decode(this: TextDecoder, input = none(JSArrayBufferView),
    options = TextDecodeOptions()): string {.jsfunc.} =
  if not this.doNotFlush:
    if this.istream != nil:
      this.istream.close()
    if this.decoder != nil:
      this.decoder.close()
    if this.encoder != nil:
      this.encoder.close()
    this.istream = newStringStream()
    this.decoder = newDecoderStream(this.istream, cs = this.encoding,
      errormode = this.errorMode)
    this.encoder = newEncoderStream(this.decoder, cs = CHARSET_UTF_8)
    this.bomSeen = false
  if this.doNotFlush != options.stream:
    this.doNotFlush = options.stream
    this.decoder.setInhibitCheckEnd(options.stream)
  if input.isSome:
    let input = input.get
    let pos = this.istream.getPosition()
    #TODO input offset?
    this.istream.writeData(input.abuf.p, int(input.abuf.len))
    this.istream.setPosition(pos)
  #TODO this should return a JSString, so we do not needlessly re-encode
  # the output. (Right now we do, implicitly through toJS.)
  return this.encoder.readAll()

func jencoding(this: TextDecoder): string {.jsfget: "encoding".} =
  return $this.encoding

func fatal(this: TextDecoder): bool {.jsfget.} =
  return this.errorMode == DECODER_ERROR_MODE_FATAL

func newTextEncoder(): TextEncoder {.jsctor.} =
  return TextEncoder()

func jencoding(this: TextEncoder): string {.jsfget: "encoding".} =
  return "utf-8"

proc dealloc_wrap(rt: JSRuntime, opaque, p: pointer) {.cdecl.} =
  dealloc(p)

proc encode(this: TextEncoder, input = ""): JSUint8Array {.jsfunc.} =
  # input is already UTF-8 here :P
  let buf = cast[ptr UncheckedArray[uint8]](alloc(input.len))
  copyMem(buf, unsafeAddr input[0], input.len)
  let abuf = JSArrayBuffer(
    p: buf,
    len: csize_t(input.len),
    dealloc: dealloc_wrap
  )
  return JSUint8Array(
    abuf: abuf,
    offset: 0,
    nmemb: csize_t(input.len)
  )

#TODO encodeInto

proc addEncodingModule*(ctx: JSContext) =
  ctx.registerType(TextDecoder)
  ctx.registerType(TextEncoder)