import std/streams
import js/error
import js/javascript
import js/jstypes
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)