char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f2e5e0e
main
1import { registerUncloneable } from "ext:web/serialization.js" ; 2import { 3byteLength as sharedLength , 4growable , 5} from "ext:core/shared_array_buffer" ; 6import { 7ArrayBuffer , 8getter , 9MathMin , 10RangeError , 11String , 12StringPrototypeSlice as slice , 13StringPrototypeToWellFormed as wellFormed , 14TypedArrayPrototypeGetBuffer as buffer , 15TypedArrayPrototypeGetByteLength as byteLength , 16TypedArrayPrototypeGetByteOffset as byteOffset , 17TypedArrayPrototypeGetTag as tag , 18TypeError , 19Uint8Array , 20Uint8ArrayPrototypeSet as setBytes , 21uncurryThis , 22WeakMap , 23WeakMapPrototypeSet as set , 24} from "ext:core/primordials" ; 25import { dictionary , state , string } from "ext:web/idl.js" ; 26import { installInterface } from "ext:web/interface.js" ; 27import { 28TransformStream , 29TransformStreamDefaultController , 30} from "ext:webstreams/streams.js" ; 31import { 32op_encoding_close as dispose , 33op_encoding_decode as decodeBytes , 34op_encoding_encode as encodeBytes , 35op_encoding_encode_into as encodeInto , 36op_encoding_label as resolve , 37} from "ext:core/ops" ; 38import type { EncodeCounts } from "ext:core/ops" ; 39import type { ReadableStream , WritableStream } from "ext:webstreams/streams.js" ; 40 41interface DecoderState { 42id :number | null ; 43encoding :string ; 44fatal :boolean ; 45ignoreBOM :boolean ; 46} 47 48const isView = ArrayBuffer . isView ; 49const abLength = getter ( ArrayBuffer . prototype , "byteLength" ); 50const dvBuffer = getter ( DataView . prototype , "buffer" ), 51dvOffset = getter ( DataView . prototype , "byteOffset" ), 52dvLength = getter ( DataView . prototype , "byteLength" ); 53const resizable = getter ( ArrayBuffer . prototype , "resizable" ); 54const charCode = uncurryThis ( String . prototype . charCodeAt ); 55const register :( registry :FinalizationRegistry < DecoderState >, target :WeakKey , held :DecoderState ) => void = 56uncurryThis ( FinalizationRegistry . prototype . register ); 57const registry = new FinalizationRegistry < DecoderState >(( s ) => { 58if ( s . id !== null ) dispose ( s . id ); 59}); 60const encoders = new WeakMap < object , object >(), 61decoders = new WeakMap < object , DecoderState >(), 62encoderStreams = new WeakMap < object , { transform :TransformStream < unknown , Uint8Array < ArrayBuffer >> }>(), 63decoderStreams = new WeakMap < object , { transform :TransformStream < unknown , string >; decoder :TextDecoder }>(); 64registerUncloneable ( encoders ); 65registerUncloneable ( decoders ); 66registerUncloneable ( encoderStreams ); 67registerUncloneable ( decoderStreams ); 68const empty = new Uint8Array ( 0 ); 69// Capturing a generic prototype erases its chunk parameters; retain them at these bindings. 70const readable = getter ( TransformStream . prototype , "readable" ) as 71< I , O > ( stream :TransformStream < I , O >) => ReadableStream < O >; 72const writable = getter ( TransformStream . prototype , "writable" ) as 73< I , O > ( stream :TransformStream < I , O >) => WritableStream < I >; 74const enqueue = uncurryThis ( 75TransformStreamDefaultController . prototype . enqueue , 76) as < O > ( controller :TransformStreamDefaultController < O >, chunk :NoInfer < O >) => void ; 77 78function bufferLength ( b :unknown ) :number { 79let length , shared = false ; 80try { 81length = abLength ( b ); 82} catch { 83try { 84length = sharedLength ( b ); 85shared = true ; 86} catch { 87throw new TypeError ( "Expected an ArrayBuffer or an ArrayBuffer view" ); 88} 89} 90if ( shared ?growable ( b ) :resizable ( b )) { 91throw new TypeError ( 92"Resizable buffers are not allowed by the Encoding API" , 93); 94} 95return length ; 96} 97function view ( value :unknown ) :Uint8Array < ArrayBufferLike > { 98if ( ! isView ( value )) { 99const length = bufferLength ( value ); 100if ( length === 0 ) return empty ; 101return new Uint8Array ( value as ArrayBufferLike , 0 , length ); 102} 103const dataView = tag ( value ) === undefined , 104b = dataView ?dvBuffer ( value ) :buffer ( value ); 105bufferLength ( b ); 106let offset , length ; 107if ( dataView ) { 108try { 109offset = dvOffset ( value ); 110length = dvLength ( value ); 111} catch { 112return empty ; 113} 114} else { 115offset = byteOffset ( value ); 116length = byteLength ( value ); 117} 118if ( length === 0 ) return empty ; 119return new Uint8Array ( b , offset , length ); 120} 121function decodeInput ( value :unknown ) :Uint8Array < ArrayBuffer > { 122// Rust never borrows shared or resizable V8 backing storage. 123const bytes = view ( value ); 124try { 125abLength ( buffer ( bytes )); 126return bytes as Uint8Array < ArrayBuffer >; 127} catch { 128return new Uint8Array ( bytes ); 129} 130} 131function decode ( s :DecoderState , bytes :Uint8Array < ArrayBuffer >, stream :boolean ) :string { 132let result ; 133try { 134result = decodeBytes ([ 135s . id , 136s . encoding , 137s . fatal , 138s . ignoreBOM , 139bytes , 140stream , 141]); 142} catch ( error ) { 143if ( s . id !== null ) dispose ( s . id ); 144s . id = null ; 145throw error ; 146} 147s . id = result [ 2 ]; 148if ( result [ 1 ]) { 149throw new TypeError ( "The encoded data is not valid for this encoding" ); 150} 151return result [ 0 ]; 152} 153 154class TextEncoder { 155constructor () { 156set ( encoders , this , {}); 157} 158get encoding () :"utf-8" { 159state ( encoders , this ); 160return "utf-8" ; 161} 162encode ( input :unknown = "" ) :Uint8Array < ArrayBuffer > { 163state ( encoders , this ); 164return encodeBytes ( wellFormed ( string ( input ))); 165} 166encodeInto ( rawSource :unknown , destination :unknown ) :EncodeCounts { 167state ( encoders , this ); 168if ( arguments . length < 2 ) { 169throw new TypeError ( "TextEncoder.encodeInto requires two arguments" ); 170} 171const source = string ( rawSource ); 172if ( tag ( destination ) !== "Uint8Array" ) { 173throw new TypeError ( "Destination must be a Uint8Array" ); 174} 175const storage = buffer ( destination ); 176bufferLength ( storage ); 177let shared = false ; 178try { 179abLength ( storage ); 180} catch { 181shared = true ; 182} 183if ( ! shared ) return encodeInto ( source , destination as Uint8Array < ArrayBuffer >); 184const capacity = byteLength ( destination ); 185// One extra UTF-16 unit keeps a pair intact at the bounded source slice. 186const prefix = slice ( source , 0 , capacity + 1 ); 187const bytes = new Uint8Array ( MathMin ( capacity , prefix . length * 3 )); 188const result = encodeInto ( prefix , bytes ); 189if ( result . written ) { 190setBytes ( destination as Uint8Array < ArrayBufferLike >, new Uint8Array ( buffer ( bytes ), 0 , result . written )); 191} 192return result ; 193} 194} 195class TextDecoder { 196constructor ( rawLabel :unknown = "utf-8" , rawOptions :unknown = undefined ) { 197const label = string ( rawLabel ); 198const options = dictionary ( rawOptions ); 199const fatal = !! options . fatal , ignoreBOM = !! options . ignoreBOM ; 200const encoding = resolve ( label ); 201if ( encoding === null ) { 202throw new RangeError ( `Unsupported encoding: ${ label } ` ); 203} 204const s :DecoderState = { id :null , encoding, fatal, ignoreBOM}; 205set ( decoders , this , s ); 206register ( registry , this , s ); 207} 208get encoding () :string { 209return state ( decoders , this ). encoding ; 210} 211get fatal () :boolean { 212return state ( decoders , this ). fatal ; 213} 214get ignoreBOM () :boolean { 215return state ( decoders , this ). ignoreBOM ; 216} 217decode ( input :unknown = undefined , rawOptions :unknown = undefined ) :string { 218const s = state ( decoders , this ); 219if ( input !== undefined ) view ( input ); 220const options = dictionary ( rawOptions ); 221const stream = !! options . stream ; 222return decode ( s , input === undefined ?empty :decodeInput ( input ), stream ); 223} 224} 225class TextEncoderStream { 226constructor () { 227let pending = "" ; 228const transform = new TransformStream < unknown , Uint8Array < ArrayBuffer >>( 229{ 230__proto__ :null , 231transform ( rawChunk , controller ) { 232let chunk = string ( rawChunk ); 233if ( chunk . length === 0 ) return ; 234chunk = pending + chunk ; 235pending = "" ; 236const last = charCode ( chunk , chunk . length - 1 ); 237if ( last >= 0xd800 && last <= 0xdbff ) { 238pending = slice ( chunk , - 1 ); 239chunk = slice ( chunk , 0 , - 1 ); 240} 241if ( chunk . length ) { 242enqueue ( controller , encodeBytes ( wellFormed ( chunk ))); 243} 244}, 245flush ( controller ) { 246if ( pending ) enqueue ( controller , encodeBytes ( "\ufffd" )); 247}, 248}, 249{ __proto__ :null }, 250{ __proto__ :null }, 251); 252set ( encoderStreams , this , { transform}); 253} 254get encoding () :"utf-8" { 255state ( encoderStreams , this ); 256return "utf-8" ; 257} 258get readable () :ReadableStream < Uint8Array < ArrayBuffer >> { 259return readable ( state ( encoderStreams , this ). transform ); 260} 261get writable () :WritableStream < unknown > { 262return writable ( state ( encoderStreams , this ). transform ); 263} 264} 265class TextDecoderStream { 266constructor ( label :unknown = "utf-8" , options :unknown = undefined ) { 267const decoder = new TextDecoder ( label , options ); 268const transform = new TransformStream < unknown , string >( 269{ 270__proto__ :null , 271transform ( chunk , controller ) { 272const text = decode ( 273state ( decoders , decoder ), 274decodeInput ( chunk ), 275true , 276); 277if ( text . length ) enqueue ( controller , text ); 278}, 279flush ( controller ) { 280const text = decode ( state ( decoders , decoder ), empty , false ); 281if ( text . length ) enqueue ( controller , text ); 282}, 283}, 284{ __proto__ :null }, 285{ __proto__ :null }, 286); 287set ( decoderStreams , this , { transform, decoder}); 288} 289get encoding () :string { 290return state ( decoders , state ( decoderStreams , this ). decoder ). encoding ; 291} 292get fatal () :boolean { 293return state ( decoders , state ( decoderStreams , this ). decoder ). fatal ; 294} 295get ignoreBOM () :boolean { 296return state ( decoders , state ( decoderStreams , this ). decoder ). ignoreBOM ; 297} 298get readable () :ReadableStream < string > { 299return readable ( state ( decoderStreams , this ). transform ); 300} 301get writable () :WritableStream < unknown > { 302return writable ( state ( decoderStreams , this ). transform ); 303} 304} 305for ( 306const ctor of [ TextEncoder , TextDecoder , TextEncoderStream , TextDecoderStream ] 307) installInterface ( ctor . name , ctor ); 308 309export { TextDecoder , TextDecoderStream , TextEncoder , TextEncoderStream };