char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f2e5e0e
main
1mod common; 2 3# [ test ] 4fn utf8_encoding_and_encode_into_respect_code_points_and_view_bounds () { 5let out = common:: run ( 6r#" 7const encoder=new TextEncoder();if(encoder.encoding!=='utf-8')throw Error('Encoding'); 8console.log([...encoder.encode('Aé😀\ud800')].join(',')); 9if(encoder.encode().length!==0||new TextDecoder().decode(encoder.encode(null))!=='null')throw Error('Defaults'); 10const source='Aé😀\ud800Z'; 11for(let capacity=0;capacity<=12;capacity++) { 12const storage=new Uint8Array(capacity+2).fill(99), dest=storage.subarray(1,capacity+1); 13const result=encoder.encodeInto(source,dest); 14if(storage[0]!==99||storage[capacity+1]!==99||result.written>capacity)throw Error('Bounds'); 15const expected=encoder.encode(source.slice(0,result.read)); 16if(expected.length!==result.written||expected.some((b,i)=>b!==dest[i]))throw Error('Counts'); 17if(result.read===3)throw Error('Split scalar'); 18} 19for (const source of ['\0', '\ud800', '\udc00', '\ud800\ud800', '\udc00\ud800', 'é\ud800😀\0z']) { 20for (let capacity = 0; capacity < 20; capacity++) { 21const expected = encoder.encode(source); 22const storage = new Uint8Array(capacity + 4).fill(99); 23const destination = storage.subarray(2, capacity + 2); 24const {read, written} = encoder.encodeInto(source, destination); 25if (written !== encoder.encode(source.slice(0, read)).length || 26expected.slice(0, written).some((byte, i) => byte !== destination[i]) || 27storage.slice(0, 2).some(byte => byte !== 99) || 28storage.slice(written + 2).some(byte => byte !== 99)) throw Error('UTF8 prefix bounds'); 29if (read < source.length && encoder.encode(String.fromCodePoint(source.codePointAt(read))).length <= capacity - written) { 30throw Error('Stopped before a complete scalar'); 31} 32} 33} 34const view=new Uint8Array(new SharedArrayBuffer(4));const result=encoder.encodeInto('😀x',view); 35if(result.read!==2||result.written!==4||[...view].join(',')!=='240,159,152,128')throw Error('Shared destination'); 36for(const bad of [new Uint16Array(4),new DataView(new ArrayBuffer(4)),[],null]) {try{encoder.encodeInto('x',bad);throw Error('Accepted destination');}catch(e){if(!(e instanceof TypeError))throw e;}} 37try{encoder.encode(Symbol());throw Error('Symbol source');}catch(e){if(!(e instanceof TypeError))throw e;} 38"# , 39& [], 40); 41assert_eq! ( out. stdout , b"65,195,169,240,159,152,128,239,191,189\n" ); 42} 43 44# [ test ] 45fn decoding_labels_legacy_encodings_and_fatal_errors () { 46let out = common:: run ( 47r#" 48const cases=[ 49[' UTF-8\t','utf-8',[195,169],'é'], 50['unicode-1-1-utf-8','utf-8',[240,159,152,128],'😀'], 51['ASCII','windows-1252',[128,233],'€é'], 52['latin1','windows-1252',[128],'€'], 53['shift_jis','shift_jis',[130,160],'あ'], 54['euc-jp','euc-jp',[164,162],'あ'], 55['gb18030','gb18030',[196,227],'你'], 56['big5','big5',[167,65],'你'], 57['koi8-r','koi8-r',[225],'А'], 58['utf-16','utf-16le',[65,0,61,216,0,222],'A😀'], 59['utf-16be','utf-16be',[0,65,216,61,222,0],'A😀'], 60['x-user-defined','x-user-defined',[128],'\uf780'], 61]; 62for(const [label,encoding,input,text]of cases) {const d=new TextDecoder(label);if(d.encoding!==encoding||d.decode(new Uint8Array(input))!==text)throw Error(label);} 63for(const label of ['bogus','replacement','iso-2022-cn','iso-2022-kr','utf-7','utf\u00a0-8']) {try{new TextDecoder(label);throw Error('Accepted label');}catch(e){if(!(e instanceof RangeError))throw e;}} 64const malformed=new Uint8Array([240,128,128,65,255]);if(new TextDecoder().decode(malformed)!=='���A�')throw Error('Replacement'); 65const fatal=new TextDecoder('utf-8',{fatal:true});try{fatal.decode(malformed);throw Error('Fatal accepted');}catch(e){if(!(e instanceof TypeError))throw e;} 66if(fatal.decode(new Uint8Array([65]))!=='A'||!fatal.fatal||fatal.ignoreBOM)throw Error('Reset'); 67console.log('labels'); 68"# , 69& [], 70); 71assert_eq! ( out. stdout , b"labels\n" ); 72} 73 74# [ test ] 75fn streaming_decode_preserves_partial_sequences_and_bom_state () { 76let out = common:: run ( 77r#" 78const d=new TextDecoder();const pieces=[[239],[187],[191,240],[159],[152,128,239],[187,191]]; 79let text='';for(const piece of pieces)text+=d.decode(new Uint8Array(piece),{stream:true});text+=d.decode(); 80if(text!=='😀\ufeff')throw Error('UTF8 streaming/BOM'); 81if(d.decode(new Uint8Array([239,187,191,65]))!=='A')throw Error('BOM reset'); 82if(new TextDecoder('utf-8',{ignoreBOM:true}).decode(new Uint8Array([239,187,191,65]))!=='\ufeffA')throw Error('ignoreBOM'); 83if(new TextDecoder('windows-1252').decode(new Uint8Array([239,187,191]))!=='')throw Error('Legacy BOM'); 84const utf16=new TextDecoder('utf-16be');let result='';for(const byte of [254,255,216,61,222,0])result+=utf16.decode(new Uint8Array([byte]),{stream:true});result+=utf16.decode();if(result!=='😀')throw Error('UTF16 streaming'); 85const incomplete=new TextDecoder();if(incomplete.decode(new Uint8Array([226]),{stream:true})!==''||incomplete.decode()!=='�'||incomplete.decode()!=='')throw Error('Flush/reset'); 86const fatal=new TextDecoder('utf-8',{fatal:true});fatal.decode(new Uint8Array([226]),{stream:true});try{fatal.decode();throw Error('Incomplete fatal');}catch(e){if(!(e instanceof TypeError))throw e;}if(fatal.decode(new Uint8Array([65]))!=='A')throw Error('Fatal reset'); 87const recovering=new TextDecoder('utf-8',{fatal:true});try{recovering.decode(new Uint8Array([255,65]),{stream:true});throw Error('Malformed stream');}catch(e){if(!(e instanceof TypeError))throw e;}if(recovering.decode()!=='')throw Error('Fatal chunk recovery'); 88const shift=new TextDecoder('iso-2022-jp');let shifted='';for(const byte of [27,36,66,36,34,27,40,66,65])shifted+=shift.decode(new Uint8Array([byte]),{stream:true});shifted+=shift.decode();if(shifted!=='あA')throw Error('Stateful encoding'); 89console.log('streaming'); 90"# , 91& [], 92); 93assert_eq! ( out. stdout , b"streaming\n" ); 94} 95 96# [ test ] 97fn buffer_sources_include_shared_resizable_detached_and_offset_views () { 98let out = common:: run ( 99r#" 100const d=new TextDecoder(), encoder=new TextEncoder(); 101const ab=new Uint8Array([0,65,66,0]).buffer;if(d.decode(new DataView(ab,1,2))!=='AB'||d.decode(new Uint8Array(ab,1,2))!=='AB')throw Error('View range'); 102const shared=new SharedArrayBuffer(2);new Uint8Array(shared).set([65,66]);if(d.decode(shared)!=='AB'||d.decode(new DataView(shared))!=='AB')throw Error('Shared'); 103const rab=new ArrayBuffer(4,{maxByteLength:8});new Uint8Array(rab).set([65,66,67,68]);const fixed=new Uint8Array(rab,0,4);rab.resize(2); 104for(const operation of [()=>d.decode(fixed),()=>encoder.encodeInto('x',fixed)]){try{operation();throw Error('Resizable view');}catch(e){if(!(e instanceof TypeError))throw e;}} 105for(const value of [rab,new DataView(rab),new Uint8Array(rab),new SharedArrayBuffer(2,{maxByteLength:8})]) {try{d.decode(value);throw Error('Resizable accepted');}catch(e){if(!(e instanceof TypeError))throw e;}} 106const detached=new ArrayBuffer(4), typed=new Uint8Array(detached), dataView=new DataView(detached);detached.transfer(); 107for(const value of [detached,typed,dataView])if(d.decode(value)!=='')throw Error('Detached source'); 108const zero=encoder.encodeInto('x',typed);if(zero.read!==0||zero.written!==0)throw Error('Detached destination'); 109const transferred=new Uint8Array([65]);if(d.decode(transferred,{get stream(){transferred.buffer.transfer();return false;}})!=='')throw Error('Detached in conversion'); 110for(const bad of [null,{},[65],'A',1]) {try{d.decode(bad);throw Error('Invalid BufferSource');}catch(e){if(!(e instanceof TypeError))throw e;}} 111console.log('buffers'); 112"# , 113& [], 114); 115assert_eq! ( out. stdout , b"buffers\n" ); 116} 117 118# [ test ] 119fn conversions_branding_and_captured_methods_follow_web_interfaces () { 120let out = common:: run ( 121r#" 122const decoder=new TextDecoder(), bytes=new Uint8Array([65]); 123if(decoder.decode(bytes,{get stream(){bytes[0]=66;return false;}})!=='B')throw Error('Option conversion/copy order'); 124const log=[];new TextDecoder({toString(){log.push('label');return 'utf-8';}},{get fatal(){log.push('fatal');return false;},get ignoreBOM(){log.push('bom');return false;}});if(log.join(',')!=='label,fatal,bom')throw Error('Conversion order'); 125for(const call of [()=>TextEncoder(),()=>TextDecoder(),()=>TextEncoder.prototype.encode.call({}),()=>TextDecoder.prototype.decode.call({}),()=>new TextDecoder(undefined,1),()=>decoder.decode(undefined,true)]) {try{call();throw Error('Invalid invocation');}catch(e){if(!(e instanceof TypeError))throw e;}} 126if(Object.prototype.toString.call(decoder)!=='[object TextDecoder]'||!Object.getOwnPropertyDescriptor(TextDecoder.prototype,'decode').enumerable)throw Error('Interface'); 127const encode=new TextEncoder(), decode=new TextDecoder();const poison=()=>{throw Error('Poisoned');}; 128Uint8Array.prototype.set=poison;ArrayBuffer.isView=poison;String.prototype.toWellFormed=poison;Object.getOwnPropertyDescriptor=poison; 129if(decode.decode(encode.encode('hello'))!=='hello')throw Error('Captured functions');const dest=new Uint8Array(3);if(encode.encodeInto('éx',dest).written!==3)throw Error('Captured set'); 130console.log('interfaces'); 131"# , 132& [], 133); 134assert_eq! ( out. stdout , b"interfaces\n" ); 135} 136 137# [ test ] 138fn encoding_transform_streams_handle_split_scalars_and_backpressure () { 139let out = common:: run ( 140r#" 141const encoder=new TextEncoderStream(), decoder=new TextDecoderStream(); 142if(encoder.encoding!=='utf-8'||decoder.encoding!=='utf-8'||decoder.fatal||decoder.ignoreBOM)throw Error('Stream properties'); 143const received=[];const consumer=(async()=>{for await(const text of encoder.readable.pipeThrough(decoder))received.push(text);})(); 144const writer=encoder.writable.getWriter();await writer.write('A\ud83d');await writer.write('');await writer.write('\ude00B\ud800');await writer.close();await consumer; 145if(received.join('')!=='A😀B�')throw Error('Surrogate carry'); 146const legacy=new TextDecoderStream('shift_jis');let text='';const collecting=(async()=>{for await(const chunk of legacy.readable)text+=chunk;})();const output=legacy.writable.getWriter();await output.write(new Uint8Array([130]));await output.write(new Uint8Array([160]));await output.close();await collecting;if(text!=='あ')throw Error('Legacy transform'); 147console.log('transforms'); 148"# , 149& [], 150); 151assert_eq! ( out. stdout , b"transforms\n" ); 152} 153 154# [ test ] 155fn stream_wrappers_keep_captured_accessors_and_controller_methods () { 156let out = common:: run ( 157r#" 158const prototype = TransformStream.prototype; 159const readable = Object.getOwnPropertyDescriptor(prototype, 'readable'); 160const writable = Object.getOwnPropertyDescriptor(prototype, 'writable'); 161const controller = TransformStreamDefaultController.prototype; 162const enqueue = Object.getOwnPropertyDescriptor(controller, 'enqueue'); 163const poison = () => { throw Error('Poisoned stream hook'); }; 164try { 165Object.defineProperty(prototype, 'readable', {get: poison, configurable: true}); 166Object.defineProperty(prototype, 'writable', {get: poison, configurable: true}); 167Object.defineProperty(controller, 'enqueue', {value: poison, configurable: true}); 168const encoder = new TextEncoderStream(), decoder = new TextDecoderStream(); 169const chunks = []; 170const consumed = (async () => { 171for await (const chunk of encoder.readable.pipeThrough(decoder)) chunks.push(chunk); 172})(); 173const writer = encoder.writable.getWriter(); 174await writer.write('A\ud83d'); 175await writer.write('\ude00B'); 176await writer.close(); 177await consumed; 178if (chunks.join('') !== 'A😀B') throw Error('Captured stream pipeline'); 179if (TextEncoder.length !== 0 || TextDecoder.length !== 0 || 180TextEncoderStream.length !== 0 || TextDecoderStream.length !== 0 || 181TextEncoder.prototype.encode.length !== 0 || 182TextEncoder.prototype.encodeInto.length !== 2 || 183TextDecoder.prototype.decode.length !== 0) throw Error('Callable arity'); 184} finally { 185Object.defineProperty(prototype, 'readable', readable); 186Object.defineProperty(prototype, 'writable', writable); 187Object.defineProperty(controller, 'enqueue', enqueue); 188} 189console.log('captured streams'); 190"# , 191& [], 192); 193assert_eq! ( out. stdout , b"captured streams\n" ); 194} 195 196# [ test ] 197fn fatal_decoder_stream_errors_both_sides_without_unhandled_rejections () { 198let out = common:: run ( 199r#" 200const stream=new TextDecoderStream('utf-8',{fatal:true});const reader=stream.readable.getReader(),writer=stream.writable.getWriter(); 201const reading=reader.read().then(()=>{throw Error('Expected read error');},e=>{if(!(e instanceof TypeError))throw e;}); 202try{await writer.write(new Uint8Array([255]));throw Error('Expected write error');}catch(e){if(!(e instanceof TypeError))throw e;} 203await reading; 204const bad=new TextDecoderStream(),r=bad.readable.getReader(),w=bad.writable.getWriter();const rejected=r.read().catch(e=>{if(!(e instanceof TypeError))throw e;});try{await w.write(undefined);throw Error('Invalid stream chunk');}catch(e){if(!(e instanceof TypeError))throw e;}await rejected; 205console.log('errors'); 206"# , 207& [], 208); 209assert_eq! ( out. stdout , b"errors\n" ); 210} 211 212# [ test ] 213fn default_options_and_internal_transformers_ignore_prototype_pollution () { 214let out = common:: run ( 215r#" 216Object.prototype.fatal=true;Object.prototype.ignoreBOM=true;Object.prototype.stream=true; 217Object.prototype.start=c=>c.enqueue('injected');Object.prototype.readableType='bytes';Object.prototype.highWaterMark=-1; 218const decoder=new TextDecoder();if(decoder.fatal||decoder.ignoreBOM||decoder.decode(new Uint8Array([226,130]))!=='�')throw Error('Default dictionary'); 219const pipeline=new TextDecoderStream(),encoder=new TextEncoderStream(); 220if(pipeline.fatal||pipeline.ignoreBOM)throw Error('Stream defaults'); 221delete Object.prototype.highWaterMark; 222const result=[];const reading=(async()=>{for await(const chunk of pipeline.readable)result.push(chunk);})(); 223const writer=pipeline.writable.getWriter();await writer.write(new Uint8Array([65]));await writer.close();await reading;if(result.join('')!=='A')throw Error('Inherited transformer hook'); 224console.log('defaults'); 225"# , 226& [], 227); 228assert_eq! ( out. stdout , b"defaults\n" ); 229}