diff --git a/examples/spec/base64.js b/examples/spec/base64.js index 0ab82c4..25036a1 100644 --- a/examples/spec/base64.js +++ b/examples/spec/base64.js @@ -26,4 +26,42 @@ test('roundtrip json', atob(btoa(jsonData)), jsonData); test('btoa binary', btoa('\x00\x01\x02'), 'AAEC'); test('atob binary', atob('AAEC'), '\x00\x01\x02'); +// Buffer base64 tests +console.log('\nBuffer Base64 Tests\n'); + +const buf1 = Buffer.from('Hello, World!'); +test('Buffer.toString base64', buf1.toString('base64'), 'SGVsbG8sIFdvcmxkIQ=='); + +const buf2 = Buffer.from('ABC'); +test('Buffer.toString base64 ABC', buf2.toString('base64'), 'QUJD'); + +const buf3 = Buffer.from(''); +test('Buffer.toString base64 empty', buf3.toString('base64'), ''); + +const buf4 = Buffer.from([0x00, 0x01, 0x02]); +test('Buffer.toString base64 binary', buf4.toString('base64'), 'AAEC'); + +const buf5 = Buffer.from('A'); +test('Buffer.toString base64 single', buf5.toString('base64'), 'QQ=='); + +const buf6 = Buffer.from('AB'); +test('Buffer.toString base64 two chars', buf6.toString('base64'), 'QUI='); + +// Buffer.toBase64() method +test('Buffer.toBase64', buf1.toBase64(), 'SGVsbG8sIFdvcmxkIQ=='); +test('Buffer.toBase64 ABC', buf2.toBase64(), 'QUJD'); +test('Buffer.toBase64 empty', buf3.toBase64(), ''); + +// Buffer hex encoding +console.log('\nBuffer Hex Tests\n'); + +const hexBuf1 = Buffer.from('Hello'); +test('Buffer.toString hex Hello', hexBuf1.toString('hex'), '48656c6c6f'); + +const hexBuf2 = Buffer.from([0xde, 0xad, 0xbe, 0xef]); +test('Buffer.toString hex deadbeef', hexBuf2.toString('hex'), 'deadbeef'); + +const hexBuf3 = Buffer.from([0x00, 0xff, 0x10]); +test('Buffer.toString hex padding', hexBuf3.toString('hex'), '00ff10'); + summary(); diff --git a/examples/spec/buffer.js b/examples/spec/buffer.js index 922aa78..bdfd461 100644 --- a/examples/spec/buffer.js +++ b/examples/spec/buffer.js @@ -120,4 +120,131 @@ const subarrayed = original.subarray(2, 8); test('TypedArray subarray length', subarrayed.length, 6); test('TypedArray subarray byteOffset', subarrayed.byteOffset, 2); +test('Buffer.isBuffer with Buffer', Buffer.isBuffer(Buffer.from('test')), true); +test('Buffer.isBuffer with string', Buffer.isBuffer('test'), false); +test('Buffer.isBuffer with number', Buffer.isBuffer(123), false); +test('Buffer.isBuffer with object', Buffer.isBuffer({}), false); +test('Buffer.isBuffer with Uint8Array', Buffer.isBuffer(new Uint8Array(4)), false); + +test('Buffer.isEncoding utf8', Buffer.isEncoding('utf8'), true); +test('Buffer.isEncoding utf-8', Buffer.isEncoding('utf-8'), true); +test('Buffer.isEncoding hex', Buffer.isEncoding('hex'), true); +test('Buffer.isEncoding base64', Buffer.isEncoding('base64'), true); +test('Buffer.isEncoding ascii', Buffer.isEncoding('ascii'), true); +test('Buffer.isEncoding latin1', Buffer.isEncoding('latin1'), true); +test('Buffer.isEncoding binary', Buffer.isEncoding('binary'), true); +test('Buffer.isEncoding invalid', Buffer.isEncoding('invalid'), false); +test('Buffer.isEncoding empty', Buffer.isEncoding(''), false); + +test('Buffer.byteLength string', Buffer.byteLength('Hello'), 5); +test('Buffer.byteLength buffer', Buffer.byteLength(Buffer.from('Hello')), 5); +test('Buffer.byteLength empty', Buffer.byteLength(''), 0); + +const concatBuf1 = Buffer.from('Hello'); +const concatBuf2 = Buffer.from(' '); +const concatBuf3 = Buffer.from('World'); +const concatenated = Buffer.concat([concatBuf1, concatBuf2, concatBuf3]); +test('Buffer.concat length', concatenated.length, 11); +test('Buffer.concat toString', concatenated.toString(), 'Hello World'); + +const emptyConcat = Buffer.concat([]); +test('Buffer.concat empty array', emptyConcat.length, 0); + +const singleConcat = Buffer.concat([Buffer.from('Only')]); +test('Buffer.concat single buffer', singleConcat.toString(), 'Only'); + +const limitedConcat = Buffer.concat([concatBuf1, concatBuf2, concatBuf3], 5); +test('Buffer.concat with totalLength', limitedConcat.length, 5); +test('Buffer.concat with totalLength content', limitedConcat.toString(), 'Hello'); + +const cmpA = Buffer.from('abc'); +const cmpB = Buffer.from('abc'); +const cmpC = Buffer.from('abd'); +const cmpD = Buffer.from('ab'); +test('Buffer.compare equal', Buffer.compare(cmpA, cmpB), 0); +test('Buffer.compare less', Buffer.compare(cmpA, cmpC), -1); +test('Buffer.compare greater', Buffer.compare(cmpC, cmpA), 1); +test('Buffer.compare shorter', Buffer.compare(cmpD, cmpA), -1); +test('Buffer.compare longer', Buffer.compare(cmpA, cmpD), 1); + +console.log('\nBuffer Encoding Tests\n'); + +const b64Buf = Buffer.from('Hello, World!'); +test('Buffer.toString base64', b64Buf.toString('base64'), 'SGVsbG8sIFdvcmxkIQ=='); +test('Buffer.toString BASE64 (case insensitive)', b64Buf.toString('BASE64'), 'SGVsbG8sIFdvcmxkIQ=='); + +const b64Decoded = Buffer.from('SGVsbG8sIFdvcmxkIQ==', 'base64'); +test('Buffer.from base64', b64Decoded.toString(), 'Hello, World!'); +test('Buffer.from BASE64 (case insensitive)', Buffer.from('SGVsbG8=', 'BASE64').toString(), 'Hello'); + +const b64Roundtrip = Buffer.from(Buffer.from('hello').toString('base64'), 'base64').toString(); +test('Buffer base64 roundtrip', b64Roundtrip, 'hello'); + +const hexBuf = Buffer.from('Hello'); +test('Buffer.toString hex', hexBuf.toString('hex'), '48656c6c6f'); +test('Buffer.toString HEX (case insensitive)', hexBuf.toString('HEX'), '48656c6c6f'); + +const hexDecoded = Buffer.from('48656c6c6f', 'hex'); +test('Buffer.from hex', hexDecoded.toString(), 'Hello'); +test('Buffer.from HEX (case insensitive)', Buffer.from('48656C6C6F', 'HEX').toString(), 'Hello'); + +const hexRoundtrip = Buffer.from(Buffer.from('world').toString('hex'), 'hex').toString(); +test('Buffer hex roundtrip', hexRoundtrip, 'world'); + +const utf8Buf = Buffer.from('Hello', 'utf8'); +test('Buffer.from utf8', utf8Buf.toString(), 'Hello'); +test('Buffer.from utf-8', Buffer.from('Hello', 'utf-8').toString(), 'Hello'); +test('Buffer.from UTF8 (case insensitive)', Buffer.from('Hello', 'UTF8').toString(), 'Hello'); +test('Buffer.from UTF-8 (case insensitive)', Buffer.from('Hello', 'UTF-8').toString(), 'Hello'); + +test('Buffer.toString utf8', utf8Buf.toString('utf8'), 'Hello'); +test('Buffer.toString utf-8', utf8Buf.toString('utf-8'), 'Hello'); + +const asciiBuf = Buffer.from('Hello', 'ascii'); +test('Buffer.from ascii', asciiBuf.toString(), 'Hello'); +test('Buffer.from ASCII (case insensitive)', Buffer.from('Hello', 'ASCII').toString(), 'Hello'); +test('Buffer.toString ascii', asciiBuf.toString('ascii'), 'Hello'); + +const latin1Buf = Buffer.from('Hello', 'latin1'); +test('Buffer.from latin1', latin1Buf.toString(), 'Hello'); +test('Buffer.from LATIN1 (case insensitive)', Buffer.from('Hello', 'LATIN1').toString(), 'Hello'); +test('Buffer.from binary', Buffer.from('Hello', 'binary').toString(), 'Hello'); +test('Buffer.from BINARY (case insensitive)', Buffer.from('Hello', 'BINARY').toString(), 'Hello'); + +test('Buffer.toString latin1', latin1Buf.toString('latin1'), 'Hello'); +test('Buffer.toString binary', latin1Buf.toString('binary'), 'Hello'); + +const ucs2Buf = Buffer.from('Hi', 'ucs2'); +test('Buffer.from ucs2 length', ucs2Buf.length, 4); // 2 chars * 2 bytes +test('Buffer.from ucs-2 length', Buffer.from('Hi', 'ucs-2').length, 4); +test('Buffer.from utf16le length', Buffer.from('Hi', 'utf16le').length, 4); +test('Buffer.from utf-16le length', Buffer.from('Hi', 'utf-16le').length, 4); +test('Buffer.from UCS2 (case insensitive)', Buffer.from('Hi', 'UCS2').length, 4); + +test('Buffer.from ucs2 byte 0', ucs2Buf[0], 0x48); +test('Buffer.from ucs2 byte 1', ucs2Buf[1], 0x00); +test('Buffer.from ucs2 byte 2', ucs2Buf[2], 0x69); +test('Buffer.from ucs2 byte 3', ucs2Buf[3], 0x00); + +test('Buffer.toString ucs2', ucs2Buf.toString('ucs2'), 'Hi'); +test('Buffer.toString ucs-2', ucs2Buf.toString('ucs-2'), 'Hi'); +test('Buffer.toString utf16le', ucs2Buf.toString('utf16le'), 'Hi'); +test('Buffer.toString utf-16le', ucs2Buf.toString('utf-16le'), 'Hi'); + +const binaryBuf = Buffer.from([0x00, 0x01, 0x02, 0xff]); +test('Buffer binary toString base64', binaryBuf.toString('base64'), 'AAEC/w=='); +test('Buffer binary toString hex', binaryBuf.toString('hex'), '000102ff'); + +const binaryFromB64 = Buffer.from('AAEC/w==', 'base64'); +test('Buffer binary from base64 byte 0', binaryFromB64[0], 0x00); +test('Buffer binary from base64 byte 1', binaryFromB64[1], 0x01); +test('Buffer binary from base64 byte 2', binaryFromB64[2], 0x02); +test('Buffer binary from base64 byte 3', binaryFromB64[3], 0xff); + +const binaryFromHex = Buffer.from('000102ff', 'hex'); +test('Buffer binary from hex byte 0', binaryFromHex[0], 0x00); +test('Buffer binary from hex byte 1', binaryFromHex[1], 0x01); +test('Buffer binary from hex byte 2', binaryFromHex[2], 0x02); +test('Buffer binary from hex byte 3', binaryFromHex[3], 0xff); + summary(); diff --git a/examples/spec/dataview.js b/examples/spec/dataview.js index 9ba4b51..a51dda0 100644 --- a/examples/spec/dataview.js +++ b/examples/spec/dataview.js @@ -2,46 +2,38 @@ import { test, summary } from './helpers.js'; console.log('DataView Tests\n'); -// Basic DataView creation const buffer = new ArrayBuffer(16); const dv = new DataView(buffer); test('DataView byteLength', dv.byteLength, 16); test('DataView byteOffset', dv.byteOffset, 0); -// DataView with offset const dv2 = new DataView(buffer, 4); test('DataView with offset byteLength', dv2.byteLength, 12); test('DataView with offset byteOffset', dv2.byteOffset, 4); -// DataView with offset and length const dv3 = new DataView(buffer, 4, 8); test('DataView with offset+length byteLength', dv3.byteLength, 8); test('DataView with offset+length byteOffset', dv3.byteOffset, 4); -// Uint8 get/set dv.setUint8(0, 255); test('setUint8/getUint8', dv.getUint8(0), 255); dv.setUint8(1, 0); test('setUint8/getUint8 zero', dv.getUint8(1), 0); -// Int16 get/set - Big Endian (default) dv.setInt16(0, 0x1234); test('setInt16 BE / getInt16 BE', dv.getInt16(0), 0x1234); test('setInt16 BE byte 0', dv.getUint8(0), 0x12); test('setInt16 BE byte 1', dv.getUint8(1), 0x34); -// Int16 get/set - Little Endian dv.setInt16(2, 0x5678, true); test('setInt16 LE / getInt16 LE', dv.getInt16(2, true), 0x5678); test('setInt16 LE byte 0', dv.getUint8(2), 0x78); test('setInt16 LE byte 1', dv.getUint8(3), 0x56); -// Read Int16 LE as BE and vice versa test('setInt16 LE / getInt16 BE', dv.getInt16(2, false), 0x7856); -// Int32 get/set - Big Endian (default) dv.setInt32(4, 0x12345678); test('setInt32 BE / getInt32 BE', dv.getInt32(4), 0x12345678); test('setInt32 BE byte 0', dv.getUint8(4), 0x12); @@ -49,15 +41,13 @@ test('setInt32 BE byte 1', dv.getUint8(5), 0x34); test('setInt32 BE byte 2', dv.getUint8(6), 0x56); test('setInt32 BE byte 3', dv.getUint8(7), 0x78); -// Int32 get/set - Little Endian -dv.setInt32(8, 0xAABBCCDD, true); -test('setInt32 LE / getInt32 LE', dv.getInt32(8, true), 0xAABBCCDD | 0); -test('setInt32 LE byte 0', dv.getUint8(8), 0xDD); -test('setInt32 LE byte 1', dv.getUint8(9), 0xCC); -test('setInt32 LE byte 2', dv.getUint8(10), 0xBB); -test('setInt32 LE byte 3', dv.getUint8(11), 0xAA); +dv.setInt32(8, 0xaabbccdd, true); +test('setInt32 LE / getInt32 LE', dv.getInt32(8, true), 0xaabbccdd | 0); +test('setInt32 LE byte 0', dv.getUint8(8), 0xdd); +test('setInt32 LE byte 1', dv.getUint8(9), 0xcc); +test('setInt32 LE byte 2', dv.getUint8(10), 0xbb); +test('setInt32 LE byte 3', dv.getUint8(11), 0xaa); -// Float32 get/set const testFloat = 3.14; dv.setFloat32(0, testFloat); const readFloat = dv.getFloat32(0); @@ -67,7 +57,6 @@ dv.setFloat32(4, testFloat, true); const readFloatLE = dv.getFloat32(4, true); test('setFloat32/getFloat32 LE approx', Math.abs(readFloatLE - testFloat) < 0.001, true); -// Float64 get/set const testDouble = 3.141592653589793; dv.setFloat64(0, testDouble); test('setFloat64/getFloat64 BE', dv.getFloat64(0), testDouble); @@ -75,7 +64,6 @@ test('setFloat64/getFloat64 BE', dv.getFloat64(0), testDouble); dv.setFloat64(8, testDouble, true); test('setFloat64/getFloat64 LE', dv.getFloat64(8, true), testDouble); -// Negative numbers dv.setInt16(0, -1234); test('setInt16/getInt16 negative BE', dv.getInt16(0), -1234); @@ -88,7 +76,6 @@ test('setInt32/getInt32 negative BE', dv.getInt32(4), -123456789); dv.setInt32(8, -123456789, true); test('setInt32/getInt32 negative LE', dv.getInt32(8, true), -123456789); -// Test that DataView shares buffer with TypedArrays const sharedBuffer = new ArrayBuffer(8); const sharedDV = new DataView(sharedBuffer); const sharedU8 = new Uint8Array(sharedBuffer); @@ -99,14 +86,12 @@ test('DataView/TypedArray share buffer byte 1', sharedU8[1], 0x02); test('DataView/TypedArray share buffer byte 2', sharedU8[2], 0x03); test('DataView/TypedArray share buffer byte 3', sharedU8[3], 0x04); -// Modify via TypedArray, read via DataView -sharedU8[4] = 0xAA; -sharedU8[5] = 0xBB; -sharedU8[6] = 0xCC; -sharedU8[7] = 0xDD; -test('TypedArray write / DataView read', sharedDV.getInt32(4), 0xAABBCCDD | 0); +sharedU8[4] = 0xaa; +sharedU8[5] = 0xbb; +sharedU8[6] = 0xcc; +sharedU8[7] = 0xdd; +test('TypedArray write / DataView read', sharedDV.getInt32(4), 0xaabbccdd | 0); -// Node.js style example from the prompt const exampleBuffer = new ArrayBuffer(16); const exampleView = new DataView(exampleBuffer); exampleView.setInt32(0, 123456); diff --git a/src/ant.c b/src/ant.c index d2d31f1..fbd533e 100644 --- a/src/ant.c +++ b/src/ant.c @@ -14647,6 +14647,7 @@ static jsval_t builtin_object_toString(struct js *js, jsval_t *args, int nargs) jsval_t check_obj = (t == T_FUNC) ? mkval(T_OBJ, vdata(obj)) : obj; const char *tostr_tag_key = get_toStringTag_sym_key(); jsoff_t tag_off = lkp(js, check_obj, tostr_tag_key, strlen(tostr_tag_key)); + if (tag_off == 0) tag_off = lkp_proto(js, check_obj, tostr_tag_key, strlen(tostr_tag_key)); if (tag_off != 0) { jsval_t tag_val = resolveprop(js, mkval(T_PROP, tag_off)); if (vtype(tag_val) == T_STR) { diff --git a/src/modules/buffer.c b/src/modules/buffer.c index 5ce6574..b5a8a13 100644 --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -328,9 +328,6 @@ static jsval_t create_typed_array(struct js *js, TypedArrayType type, ArrayBuffe js_set(js, obj, "byteLength", js_mknum((double)(length * element_size))); js_set(js, obj, "byteOffset", js_mknum((double)byte_offset)); js_set(js, obj, "BYTES_PER_ELEMENT", js_mknum((double)element_size)); - js_set(js, obj, "slice", js_mkfun(js_typedarray_slice)); - js_set(js, obj, "subarray", js_mkfun(js_typedarray_subarray)); - js_set(js, obj, "fill", js_mkfun(js_typedarray_fill)); js_set_getter(js, obj, typedarray_index_getter); js_set_setter(js, obj, typedarray_index_setter); @@ -929,25 +926,107 @@ static jsval_t js_dataview_setFloat64(struct js *js, jsval_t *args, int nargs) { return js_mkundef(); } -// Buffer.from(array/string/buffer) -static jsval_t js_buffer_from(struct js *js, jsval_t *args, int nargs) { - if (nargs < 1) { - return js_mkerr(js, "Buffer.from requires at least one argument"); +static uint8_t *hex_decode(const char *data, size_t len, size_t *out_len) { + if (len % 2 != 0) return NULL; + + size_t decoded_len = len / 2; + uint8_t *decoded = ANT_GC_MALLOC(decoded_len); + if (!decoded) return NULL; + + for (size_t i = 0; i < decoded_len; i++) { + unsigned int byte; + if (sscanf(data + i * 2, "%2x", &byte) != 1) { + ANT_GC_FREE(decoded); return NULL; + } + decoded[i] = (uint8_t)byte; } + *out_len = decoded_len; + return decoded; +} + +typedef enum { + ENC_UTF8, + ENC_HEX, + ENC_BASE64, + ENC_ASCII, + ENC_LATIN1, + ENC_UCS2, + ENC_UNKNOWN +} BufferEncoding; + +static BufferEncoding parse_encoding(const char *enc, size_t len) { + if (len == 3 && strncasecmp(enc, "hex", 3) == 0) return ENC_HEX; + if (len == 5 && strncasecmp(enc, "ascii", 5) == 0) return ENC_ASCII; + if (len == 6 && strncasecmp(enc, "base64", 6) == 0) return ENC_BASE64; + if ((len == 4 && strncasecmp(enc, "utf8", 4) == 0) || (len == 5 && strncasecmp(enc, "utf-8", 5) == 0)) return ENC_UTF8; + if ((len == 6 && strncasecmp(enc, "latin1", 6) == 0) || (len == 6 && strncasecmp(enc, "binary", 6) == 0)) return ENC_LATIN1; + + if ( + (len == 4 && strncasecmp(enc, "ucs2", 4) == 0) || + (len == 5 && strncasecmp(enc, "ucs-2", 5) == 0) || + (len == 7 && strncasecmp(enc, "utf16le", 7) == 0) || + (len == 8 && strncasecmp(enc, "utf-16le", 8) == 0) + ) return ENC_UCS2; + + return ENC_UNKNOWN; +} + +// Buffer.from(array/string/buffer, encoding) +static jsval_t js_buffer_from(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "Buffer.from requires at least one argument"); + if (js_type(args[0]) == JS_STR) { size_t len; char *str = js_getstr(js, args[0], &len); - ArrayBufferData *buffer = create_array_buffer_data(len); - if (!buffer) return js_mkerr(js, "Failed to allocate buffer"); + BufferEncoding encoding = ENC_UTF8; + if (nargs >= 2 && js_type(args[1]) == JS_STR) { + size_t enc_len; + char *enc_str = js_getstr(js, args[1], &enc_len); + encoding = parse_encoding(enc_str, enc_len); + if (encoding == ENC_UNKNOWN) encoding = ENC_UTF8; + } - memcpy(buffer->data, str, len); - jsval_t obj = create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, len, "Buffer"); - js_set(js, obj, "toString", js_mkfun(js_buffer_toString)); - js_set(js, obj, "toBase64", js_mkfun(js_buffer_toBase64)); - js_set(js, obj, "write", js_mkfun(js_buffer_write)); - return obj; + if (encoding == ENC_BASE64) { + size_t decoded_len; + uint8_t *decoded = base64_decode(str, len, &decoded_len); + if (!decoded) return js_mkerr(js, "Failed to decode base64"); + + ArrayBufferData *buffer = create_array_buffer_data(decoded_len); + if (!buffer) { free(decoded); return js_mkerr(js, "Failed to allocate buffer"); } + + memcpy(buffer->data, decoded, decoded_len); + free(decoded); + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, decoded_len, "Buffer"); + } else if (encoding == ENC_HEX) { + size_t decoded_len; + uint8_t *decoded = hex_decode(str, len, &decoded_len); + if (!decoded) return js_mkerr(js, "Failed to decode hex"); + + ArrayBufferData *buffer = create_array_buffer_data(decoded_len); + if (!buffer) { free(decoded); return js_mkerr(js, "Failed to allocate buffer"); } + + memcpy(buffer->data, decoded, decoded_len); + free(decoded); + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, decoded_len, "Buffer"); + } else if (encoding == ENC_UCS2) { + size_t decoded_len = len * 2; + ArrayBufferData *buffer = create_array_buffer_data(decoded_len); + if (!buffer) return js_mkerr(js, "Failed to allocate buffer"); + + for (size_t i = 0; i < len; i++) { + buffer->data[i * 2] = (uint8_t)str[i]; + buffer->data[i * 2 + 1] = 0; + } + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, decoded_len, "Buffer"); + } else { + ArrayBufferData *buffer = create_array_buffer_data(len); + if (!buffer) return js_mkerr(js, "Failed to allocate buffer"); + + memcpy(buffer->data, str, len); + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, len, "Buffer"); + } } jsval_t length_val = js_get(js, args[0], "length"); @@ -965,11 +1044,7 @@ static jsval_t js_buffer_from(struct js *js, jsval_t *args, int nargs) { } } - jsval_t obj = create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, len, "Buffer"); - js_set(js, obj, "toString", js_mkfun(js_buffer_toString)); - js_set(js, obj, "toBase64", js_mkfun(js_buffer_toBase64)); - js_set(js, obj, "write", js_mkfun(js_buffer_write)); - return obj; + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, len, "Buffer"); } return js_mkerr(js, "Invalid argument to Buffer.from"); @@ -986,13 +1061,7 @@ static jsval_t js_buffer_alloc(struct js *js, jsval_t *args, int nargs) { if (!buffer) return js_mkerr(js, "Failed to allocate buffer"); memset(buffer->data, 0, size); - - jsval_t obj = create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, size, "Buffer"); - js_set(js, obj, "toString", js_mkfun(js_buffer_toString)); - js_set(js, obj, "toBase64", js_mkfun(js_buffer_toBase64)); - js_set(js, obj, "write", js_mkfun(js_buffer_write)); - - return obj; + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, size, "Buffer"); } // Buffer.allocUnsafe(size) @@ -1004,13 +1073,7 @@ static jsval_t js_buffer_allocUnsafe(struct js *js, jsval_t *args, int nargs) { size_t size = (size_t)js_getnum(args[0]); ArrayBufferData *buffer = create_array_buffer_data(size); if (!buffer) return js_mkerr(js, "Failed to allocate buffer"); - - jsval_t obj = create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, size, "Buffer"); - js_set(js, obj, "toString", js_mkfun(js_buffer_toString)); - js_set(js, obj, "toBase64", js_mkfun(js_buffer_toBase64)); - js_set(js, obj, "write", js_mkfun(js_buffer_write)); - - return obj; + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, size, "Buffer"); } // Buffer.prototype.toString(encoding) @@ -1021,15 +1084,18 @@ static jsval_t js_buffer_toString(struct js *js, jsval_t *args, int nargs) { TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val); if (!ta_data) return js_mkerr(js, "Invalid Buffer"); - char *encoding = "utf8"; + BufferEncoding encoding = ENC_UTF8; if (nargs > 0 && js_type(args[0]) == JS_STR) { - encoding = js_getstr(js, args[0], NULL); + size_t enc_len; + char *enc_str = js_getstr(js, args[0], &enc_len); + encoding = parse_encoding(enc_str, enc_len); + if (encoding == ENC_UNKNOWN) encoding = ENC_UTF8; } uint8_t *data = ta_data->buffer->data + ta_data->byte_offset; size_t len = ta_data->byte_length; - if (strcmp(encoding, "base64") == 0) { + if (encoding == ENC_BASE64) { size_t out_len; char *encoded = base64_encode(data, len, &out_len); if (!encoded) return js_mkerr(js, "Failed to encode base64"); @@ -1037,7 +1103,7 @@ static jsval_t js_buffer_toString(struct js *js, jsval_t *args, int nargs) { jsval_t result = js_mkstr(js, encoded, out_len); free(encoded); return result; - } else if (strcmp(encoding, "hex") == 0) { + } else if (encoding == ENC_HEX) { char *hex = malloc(len * 2 + 1); if (!hex) return js_mkerr(js, "Failed to allocate hex string"); @@ -1048,7 +1114,18 @@ static jsval_t js_buffer_toString(struct js *js, jsval_t *args, int nargs) { jsval_t result = js_mkstr(js, hex, len * 2); free(hex); return result; - } else return js_mkstr(js, data, len); + } else if (encoding == ENC_UCS2) { + size_t char_count = len / 2; + char *str = malloc(char_count + 1); + if (!str) return js_mkerr(js, "Failed to allocate string"); + + for (size_t i = 0; i < char_count; i++) str[i] = (char)data[i * 2]; + str[char_count] = '\0'; + + jsval_t result = js_mkstr(js, str, char_count); + free(str); + return result; + } else return js_mkstr(js, (char *)data, len); } // Buffer.prototype.toBase64() @@ -1094,6 +1171,145 @@ static jsval_t js_buffer_write(struct js *js, jsval_t *args, int nargs) { return js_mknum((double)to_write); } +// Buffer.isBuffer(obj) +static jsval_t js_buffer_isBuffer(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkfalse(); + if (js_type(args[0]) != JS_OBJ) return js_mkfalse(); + + jsval_t proto = js_get_proto(js, args[0]); + jsval_t buffer_proto = js_get_ctor_proto(js, "Buffer", 6); + + return (proto == buffer_proto) ? js_mktrue() : js_mkfalse(); +} + +// Buffer.isEncoding(encoding) +static jsval_t js_buffer_isEncoding(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1 || js_type(args[0]) != JS_STR) return js_mkfalse(); + + size_t len; + char *enc = js_getstr(js, args[0], &len); + + if ((len == 4 && strncasecmp(enc, "utf8", 4) == 0) || + (len == 5 && strncasecmp(enc, "utf-8", 5) == 0) || + (len == 3 && strncasecmp(enc, "hex", 3) == 0) || + (len == 6 && strncasecmp(enc, "base64", 6) == 0) || + (len == 5 && strncasecmp(enc, "ascii", 5) == 0) || + (len == 6 && strncasecmp(enc, "latin1", 6) == 0) || + (len == 6 && strncasecmp(enc, "binary", 6) == 0) || + (len == 4 && strncasecmp(enc, "ucs2", 4) == 0) || + (len == 5 && strncasecmp(enc, "ucs-2", 5) == 0) || + (len == 7 && strncasecmp(enc, "utf16le", 7) == 0) || + (len == 8 && strncasecmp(enc, "utf-16le", 8) == 0)) { + return js_mktrue(); + } + + return js_mkfalse(); +} + +// Buffer.byteLength(string, encoding) +static jsval_t js_buffer_byteLength(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mknum(0); + + jsval_t arg = args[0]; + + if (js_type(arg) == JS_OBJ) { + jsval_t bytelen = js_get(js, arg, "byteLength"); + if (js_type(bytelen) == JS_NUM) return bytelen; + + jsval_t len = js_get(js, arg, "length"); + if (js_type(len) == JS_NUM) return len; + } + + if (js_type(arg) == JS_STR) { + size_t len; + js_getstr(js, arg, &len); + return js_mknum((double)len); + } + + return js_mknum(0); +} + +// Buffer.concat(list, totalLength) +static jsval_t js_buffer_concat(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1 || js_type(args[0]) != JS_OBJ) { + return js_mkerr(js, "First argument must be an array"); + } + + jsval_t list = args[0]; + jsval_t len_val = js_get(js, list, "length"); + if (js_type(len_val) != JS_NUM) { + return js_mkerr(js, "First argument must be an array"); + } + + size_t list_len = (size_t)js_getnum(len_val); + size_t total_length = 0; + + if (nargs > 1 && js_type(args[1]) == JS_NUM) { + total_length = (size_t)js_getnum(args[1]); + } else { + for (size_t i = 0; i < list_len; i++) { + char idx[16]; + snprintf(idx, sizeof(idx), "%zu", i); + jsval_t buf = js_get(js, list, idx); + jsval_t buf_len = js_get(js, buf, "length"); + if (js_type(buf_len) == JS_NUM) total_length += (size_t)js_getnum(buf_len); + } + } + + ArrayBufferData *buffer = create_array_buffer_data(total_length); + if (!buffer) return js_mkerr(js, "Failed to allocate buffer"); + + size_t offset = 0; + for (size_t i = 0; i < list_len && offset < total_length; i++) { + char idx[16]; + snprintf(idx, sizeof(idx), "%zu", i); + jsval_t buf = js_get(js, list, idx); + + jsval_t ta_data_val = js_get_slot(js, buf, SLOT_BUFFER); + TypedArrayData *ta = js_gettypedarray(ta_data_val); + if (!ta || !ta->buffer) continue; + + size_t copy_len = ta->byte_length; + if (offset + copy_len > total_length) { + copy_len = total_length - offset; + } + + memcpy(buffer->data + offset, ta->buffer->data + ta->byte_offset, copy_len); + offset += copy_len; + } + + return create_typed_array(js, TYPED_ARRAY_UINT8, buffer, 0, total_length, "Buffer"); +} + +// Buffer.compare(buf1, buf2) +static jsval_t js_buffer_compare(struct js *js, jsval_t *args, int nargs) { + if (nargs < 2) return js_mkerr(js, "Buffer.compare requires two arguments"); + + jsval_t ta1_val = js_get_slot(js, args[0], SLOT_BUFFER); + jsval_t ta2_val = js_get_slot(js, args[1], SLOT_BUFFER); + + TypedArrayData *ta1 = js_gettypedarray(ta1_val); + TypedArrayData *ta2 = js_gettypedarray(ta2_val); + + if (!ta1 || !ta2) { + return js_mkerr(js, "Arguments must be Buffers"); + } + + if (!ta1 || !ta1->buffer || !ta2 || !ta2->buffer) { + return js_mkerr(js, "Invalid buffer"); + } + + size_t len = ta1->byte_length < ta2->byte_length ? ta1->byte_length : ta2->byte_length; + int cmp = memcmp(ta1->buffer->data + ta1->byte_offset, ta2->buffer->data + ta2->byte_offset, len); + + if (cmp == 0) { + if (ta1->byte_length < ta2->byte_length) cmp = -1; + else if (ta1->byte_length > ta2->byte_length) cmp = 1; + } else cmp = cmp < 0 ? -1 : 1; + + return js_mknum((double)cmp); +} + static jsval_t js_sharedarraybuffer_constructor(struct js *js, jsval_t *args, int nargs) { size_t length = 0; if (nargs > 0 && js_type(args[0]) == JS_NUM) { @@ -1131,13 +1347,17 @@ void init_buffer_module() { js_set_descriptor(js, arraybuffer_ctor_obj, "name", 4, 0); js_set(js, glob, "ArrayBuffer", js_obj_to_func(arraybuffer_ctor_obj)); + jsval_t typedarray_proto = js_mkobj(js); + js_set(js, typedarray_proto, "slice", js_mkfun(js_typedarray_slice)); + js_set(js, typedarray_proto, "subarray", js_mkfun(js_typedarray_subarray)); + js_set(js, typedarray_proto, "fill", js_mkfun(js_typedarray_fill)); + js_set(js, typedarray_proto, get_toStringTag_sym_key(), js_mkstr(js, "TypedArray", 10)); + #define SETUP_TYPEDARRAY(name) \ do { \ jsval_t name##_ctor_obj = js_mkobj(js); \ jsval_t name##_proto = js_mkobj(js); \ - js_set(js, name##_proto, "slice", js_mkfun(js_typedarray_slice)); \ - js_set(js, name##_proto, "subarray", js_mkfun(js_typedarray_subarray)); \ - js_set(js, name##_proto, "fill", js_mkfun(js_typedarray_fill)); \ + js_set_proto(js, name##_proto, typedarray_proto); \ js_set_slot(js, name##_ctor_obj, SLOT_CFUNC, js_mkfun(js_##name##_constructor)); \ js_setprop(js, name##_ctor_obj, js_mkstr(js, "prototype", 9), name##_proto); \ js_set(js, glob, #name, js_obj_to_func(name##_ctor_obj)); \ @@ -1188,10 +1408,26 @@ void init_buffer_module() { js_set_descriptor(js, sharedarraybuffer_ctor_obj, "name", 4, 0); js_set(js, glob, "SharedArrayBuffer", js_obj_to_func(sharedarraybuffer_ctor_obj)); - jsval_t buffer_obj = js_mkobj(js); - js_set(js, buffer_obj, "from", js_mkfun(js_buffer_from)); - js_set(js, buffer_obj, "alloc", js_mkfun(js_buffer_alloc)); - js_set(js, buffer_obj, "allocUnsafe", js_mkfun(js_buffer_allocUnsafe)); - js_set(js, buffer_obj, get_toStringTag_sym_key(), js_mkstr(js, "Buffer", 6)); - js_set(js, glob, "Buffer", buffer_obj); + jsval_t buffer_ctor_obj = js_mkobj(js); + jsval_t buffer_proto = js_mkobj(js); + + js_set(js, buffer_proto, "toString", js_mkfun(js_buffer_toString)); + js_set(js, buffer_proto, "toBase64", js_mkfun(js_buffer_toBase64)); + js_set(js, buffer_proto, "write", js_mkfun(js_buffer_write)); + js_set(js, buffer_proto, get_toStringTag_sym_key(), js_mkstr(js, "Buffer", 6)); + + js_set(js, buffer_ctor_obj, "from", js_mkfun(js_buffer_from)); + js_set(js, buffer_ctor_obj, "alloc", js_mkfun(js_buffer_alloc)); + js_set(js, buffer_ctor_obj, "allocUnsafe", js_mkfun(js_buffer_allocUnsafe)); + js_set(js, buffer_ctor_obj, "isBuffer", js_mkfun(js_buffer_isBuffer)); + js_set(js, buffer_ctor_obj, "isEncoding", js_mkfun(js_buffer_isEncoding)); + js_set(js, buffer_ctor_obj, "byteLength", js_mkfun(js_buffer_byteLength)); + js_set(js, buffer_ctor_obj, "concat", js_mkfun(js_buffer_concat)); + js_set(js, buffer_ctor_obj, "compare", js_mkfun(js_buffer_compare)); + + js_set_slot(js, buffer_ctor_obj, SLOT_CFUNC, js_mkfun(js_buffer_from)); + js_mkprop_fast(js, buffer_ctor_obj, "prototype", 9, buffer_proto); + js_mkprop_fast(js, buffer_ctor_obj, "name", 4, ANT_STRING("Buffer")); + js_set_descriptor(js, buffer_ctor_obj, "name", 4, 0); + js_set(js, glob, "Buffer", js_obj_to_func(buffer_ctor_obj)); }