Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisveness committed Jan 2, 2021
0 parents commit e17451c
Show file tree
Hide file tree
Showing 7 changed files with 555 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: node_js

node_js:
- "node"
- "lts/*"

os:
- linux
- osx
- windows

after_success:
- c8 -r text-lcov npm test | coveralls
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## [1.0.0] - 2021-01-02

- Initial release
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright 2021 Chris Veness

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# UintNArray

[![Build Status](https://travis-ci.org/chrisveness/uintnarray.svg?branch=master)](https://travis-ci.org/chrisveness/uintnarray)
[![Coverage Status](https://coveralls.io/repos/github/chrisveness/uintnarray/badge.svg)](https://coveralls.io/github/chrisveness/uintnarray)

Arbitrary bit-width [typed array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) of unsigned integers.

Unsigned integer arrays of anything between 1 and 32 bit-width words, extending the standard JavaScript Uint8Array, Uint16Array, and Uint32Array.

Usage is equivalent to that of the standard [Uint8Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), [Uint16Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array), and [Uint32Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array), except that the constructor takes an initial argument specifying the bit-width of the words in the array.

## Usage

### In browser:

import UintNArray from 'https://cdn.jsdelivr.net/npm/uintnarray@1.0.0/uintnarray.js';

### In Node.js:

Install from [npm](https://www.npmjs.com/package/uintnarray) and then

import UintNArray from 'uintnarray';

### Example:

````javascript
const ui8 = new Uint8Array([1, 2, 3, 4, 255, 254, 253, 252]);
const uiN = new UintNArray(4, ui8.buffer);
console.log(uiN.toString()); // "0,1,0,2,0,3,0,4,15,15,15,14,15,13,15,12"
ui8[1] = 99;
console.log(uiN.toString()); // "0,1,6,3,0,3,0,4,15,15,15,14,15,13,15,12"
````

## Reference

Static properties, static methods, instance properties, and instance methods are as per the standard typed arrays [Uint8Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), [Uint16Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array), and [Uint32Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array), with the following exceptions:

### Constructor

`UintNArray(bitWidth)`\
`UintNArray(bitWidth, length)`\
`UintNArray(bitWidth, typedArray)`\
`UintNArray(bitWidth, object)`\
`UintNArray(bitWidth, buffer [, bitOffset [, length]])`

Equivalent to typed array constructors, except that the first argument is the bit width, which may be between 1 and 32.

When the second argument is a buffer, the third argument is a bit offset rather than a byte offset.

### Static properties

`UintNArray.BYTES_PER_ELEMENT` is not available as bit-width is specified on construction.

### Static methods

`UintNArray.from()` and `UintNArray.of()` are not available, as a `UintNArray` cannot be created without specifying bit-width.

### Instance properties

`Uint8Array.prototype.byteLength` may return fractional values.

`Uint8Array.prototype.byteOffset` may return fractional values.

## Endian-ness

Since it is working with arbitrary bit-widths, `UintNArray` is intrinsically big-endian.

Little-endian order is useful for flexibility in register storage: an (e.g.) `int8*` pointing to `00001001` will give the same value (9) as an `int16*` pointing to `00001001 00000000`. Most modern computer architectures (32-bit and increasingly 64-bit) are little-endian.

With arbitrary bit-width words, the opposite applies: the value ‘9’ should be the same in (e.g.) a 15-bit, 16-bit, 0r 17-bit word:

15-bit: `. .0000000 00001001`\
16-bit: `. 00000000 00001001`\
17-bit: `0 00000000 00001001`

Also, big-endian works as a byte stream: most network communications are big-endian – referred to as ‘network order’ (binary file formats such as PNG/GIF vary).

If required, little-endian ordering of units within the UintNArray can be obtained by using a [`DataView`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/DataView) on the `UintNArray.buffer`.
69 changes: 69 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "uintnarray",
"description": "Arbitrary bit-width typed array of unsigned integers",
"author": "Chris Veness",
"version": "1.0.0",
"license": "MIT",
"type": "module",
"main": "uintnarray.js",
"files": "uintnarray.js",
"scripts": {
"test": "mocha --exit test.js",
"lint": "eslint uintnarray.js test.js",
"cover": "c8 -r html npm test"
},
"devDependencies": {
"@babel/eslint-parser": "^7.0.0",
"@babel/plugin-syntax-top-level-await": "^7.0.0",
"c8": "^7.0.0",
"chai": "chaijs/chai",
"coveralls": "^3.0.0",
"eslint": "^7.0.0",
"mocha": "^8.0.0"
},
"eslintConfig": {
"env": {
"browser": true,
"es6": true,
"mocha": true,
"node": true
},
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2015,
"sourceType": "module"
},
"extends": "eslint:recommended",
"globals": {
"chai": true,
"should": true
},
"rules": {
"array-bracket-spacing": [ "error", "always" ],
"comma-dangle": [ "error", "always-multiline" ],
"comma-spacing": [ "error" ],
"curly": [ "error", "multi-line" ],
"indent": [ "error", 4, { "SwitchCase": 1 } ],
"key-spacing": [ "error", { "align": "value" } ],
"keyword-spacing": [ "error" ],
"no-case-declarations": "warn",
"no-console": [ "warn", { "allow": [ "error", "info", "debug" ] } ],
"no-irregular-whitespace": "warn",
"no-redeclare": "warn",
"no-shadow": "warn",
"no-unused-vars": "warn",
"no-var": "error",
"object-curly-spacing": [ "error", "always" ],
"prefer-const": "error",
"quotes": [ "error", "single", "avoid-escape" ],
"require-await": "error",
"semi": [ "error", "always" ],
"space-before-blocks": [ "error", "always" ],
"space-in-parens": [ "error" ],
"strict": [ "error", "global" ]
}
},
"babel": {
"plugins": ["@babel/plugin-syntax-top-level-await"]
}
}
141 changes: 141 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* UintNArray Test Harness © Chris Veness 2021 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

import UintNArray from './uintnarray.js';

if (typeof window == 'undefined') { // node ($ npm test)
const chai = await import('chai');
global.should = chai.should();
} else { // browser (movable-type.co.uk/dev/uintnarray-test.html)
window.should = chai.should();
}

const test = it; // just an alias

describe('construct from numeric (length)', function() {
test('numeric length', () => new UintNArray(4, 5).toString().should.equal('0,0,0,0,0'));
test('string length', () => new UintNArray(4, '5').toString().should.equal('0,0,0,0,0'));
test('resulting buffer', () => new UintNArray(4, 5).buffer.byteLength.should.equal(3));
});

describe('construct from iterable', function() {
test('toString', () => new UintNArray(4, [ 1, 2, 3, 4 ]).toString().should.equal('1,2,3,4'));
test('element overflow', () => new UintNArray(4, [ 15, 16, 17 ]).toString().should.equal('15,0,1'));
test('just below 2 bytes', () => new UintNArray(15, [ 10, 12 ]).toString().should.equal('10,12'));
test('just below 2 bytes buffer', () => new UintNArray(15, [ 10, 12 ]).buffer.byteLength.should.equal(4));
test('just above 2 bytes', () => new UintNArray(17, [ 10, 12 ]).toString().should.equal('10,12'));
test('just above 2 bytes buffer', () => new UintNArray(17, [ 10, 12 ]).buffer.byteLength.should.equal(5));
});

describe('construct from buffer', function() {
const ui8 = new Uint8Array([ 1, 2, 3, 4, 255, 254, 253, 252 ]);
test('default offset & length', () => new UintNArray(4, ui8.buffer).toString().should.equal('0,1,0,2,0,3,0,4,15,15,15,14,15,13,15,12'));
test('offset & default length', () => new UintNArray(4, ui8.buffer, 4).toString().should.equal('1,0,2,0,3,0,4,15,15,15,14,15,13,15,12'));
test('offset & length', () => new UintNArray(4, ui8.buffer, 4, 5).toString().should.equal('1,0,2,0,3'));
test('offset & zero length', () => new UintNArray(4, ui8.buffer, 0, 0).toString().should.equal(''));
test('non-numeric offset -> 0', () => new UintNArray(4, ui8.buffer, 'string').toString().should.equal('0,1,0,2,0,3,0,4,15,15,15,14,15,13,15,12'));
test('non-numeric length -> 0', () => new UintNArray(4, ui8.buffer, 0, 'string').toString().should.equal(''));
});

describe('construct from other', function() {
test('default arg2', () => new UintNArray(4).toString().should.equal(''));
test('non-numeric arg2', () => new UintNArray(4, 'string').toString().should.equal(''));
});

describe('typed array properties', function() {
const ui8 = new Uint8Array([ 1, 2, 3, 4, 255, 254, 253, 252 ]);
test('bytes per elmt', () => should.equal(UintNArray.BYTES_PER_ELEMENT, undefined));
test('byteLength', () => new UintNArray(3, [ 1, 2, 3, 4 ]).byteLength.should.equal(1.5));
test('buffer', () => new UintNArray(4, ui8.buffer).buffer.should.equal(ui8.buffer));
test('length', () => new UintNArray(4, ui8.buffer).length.should.equal(16)); // requires special handling
test('byteOffset', () => new UintNArray(4, ui8.buffer, 4).byteOffset.should.equal(0.5));
test('byteLength', () => new UintNArray(4, ui8.buffer, 4).byteLength.should.equal(7.5));
});

describe('typed array methods', function() {
const uiN1 = new UintNArray(4, 6);
uiN1.set([ 1, 2, 3 ], 2);
test('set() from array', () => uiN1.toString().should.equal('0,0,1,2,3,0'));
const uiN2 = new UintNArray(4, 6);
uiN2.set(new Uint8Array([ 4, 5, 6 ]), 2);
test('set() from typed array', () => uiN2.toString().should.equal('0,0,4,5,6,0'));
test('subarray() no args', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray().toString().should.equal('1,2,3,4'));
test('subarray() begin', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(2).toString().should.equal('3,4'));
test('subarray() begin+end', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(2, 3).toString().should.equal('3'));
test('subarray() begin+end 0', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(0, 0).toString().should.equal(''));
test('subarray() -ve begin', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(-2).toString().should.equal('3,4'));
test('subarray() -ve end', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(1, -1).toString().should.equal('2,3'));
test('subarray() outside range', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(-99, 99).toString().should.equal('1,2,3,4'));
test('subarray() non-numeric begin', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray('string').toString().should.equal('1,2,3,4'));
test('subarray() non-numeric end', () => new UintNArray(4, [ 1, 2, 3, 4 ]).subarray(0, 'string').toString().should.equal(''));
const uiN3 = new UintNArray(4, [ 1, 2, 3, 4 ]);
uiN3[-1] = 9;
uiN3[99] = 9;
test('ignore out-of-bounds set', () => uiN3.toString().should.equal('1,2,3,4'));
});

describe('inherited properties (a few)', function() {
test('name', () => UintNArray.name.should.equal('UintNArray'));
test('length', () => new UintNArray(4, 5).length.should.equal(5));
test('instanceof Array', () => (new UintNArray(1) instanceof Array).should.be.true);
test('instanceof UintNArray', () => (new UintNArray(1) instanceof UintNArray).should.be.true);
const uiN = new UintNArray(4);
uiN.myProperty = 99;
test('set arbitrary property', () => uiN.myProperty.should.equal(99));
});

describe('inherited methods (a few)', function() {
const ui8 = new Uint8Array([ 1, 2, 3, 4, 255, 254, 253, 252 ]);
test('toString', () => new UintNArray(4, ui8.buffer).toString().should.equal('0,1,0,2,0,3,0,4,15,15,15,14,15,13,15,12'));
test('includes ✓', () => new UintNArray(4, ui8.buffer).includes(4).should.equal(true));
test('includes ✗', () => new UintNArray(4, ui8.buffer).includes(5).should.equal(false));
test('fill', () => new UintNArray(4, 5).fill(12).toString().should.equal('12,12,12,12,12'));
});

describe('array bracket access', function() {
test('[0]', () => new UintNArray(4, [ 1, 2 ])[0].should.equal(1));
test('[1]', () => new UintNArray(4, [ 1, 2 ])[1].should.equal(2));
test('beyond', () => should.equal(new UintNArray(4, [ 1, 2 ])[2]), undefined);
test('before', () => should.equal(new UintNArray(4, [ 1, 2 ])[-1]), undefined);
});

describe('overridden properties', function() {
test('from', () => (typeof UintNArray.from([])).should.equal('undefined'));
test('of', () => (typeof UintNArray.of(1)).should.equal('undefined'));
});

describe('irregular word boundaries', function() {
test('3×3', () => new UintNArray(3, [ 1, 2, 3 ]).toString().should.equal('1,2,3'));
test('3×3 l', () => new UintNArray(3, [ 1, 2, 3 ]).buffer.byteLength.should.equal(2));
test('15×3', () => new UintNArray(15, [ 1, 2, 3 ]).toString().should.equal('1,2,3'));
test('15×3 l', () => new UintNArray(15, [ 1, 2, 3 ]).buffer.byteLength.should.equal(6));
test('17×3', () => new UintNArray(17, [ 1, 2, 3 ]).toString().should.equal('1,2,3'));
test('17×3 l', () => new UintNArray(17, [ 1, 2, 3 ]).buffer.byteLength.should.equal(7));
});

describe('truncate oversized iterable inputs', function() {
test('2-bit array', () => new UintNArray(2, [ 1, 2, 3, 4, 5, 6 ]).toString().should.equal('1,2,3,0,1,2'));
test('3-bit array', () => new UintNArray(3, [ 5, 6, 7, 8, 9, 10 ]).toString().should.equal('5,6,7,0,1,2'));
test('4-bit array', () => new UintNArray(4, [ 13, 14, 15, 16, 17, 18 ]).toString().should.equal('13,14,15,0,1,2'));
test('15-bit array', () => new UintNArray(15, [ 0x7ffd, 0x7ffe, 0x7fff, 0x8000, 0x8001, 0x8002 ]).toString().should.equal('32765,32766,32767,0,1,2'));
});

describe('constructor errors', function() {
test('0-width', () => should.Throw(function() { new UintNArray(0); }, RangeError));
test('+-width', () => should.Throw(function() { new UintNArray(33); }, RangeError));
test('no new', () => should.Throw(function() { UintNArray(4, 5); }, TypeError));
test('-ve length', () => should.Throw(function() { new UintNArray(1, -1); }, RangeError));
const ui8 = new Uint8Array([ 1, 2, 3, 4, 255, 254, 253, 252 ]);
test('-ve b-offset', () => should.Throw(function() { new UintNArray(4, ui8.buffer, -1); }, RangeError));
test('excess b-offset', () => should.Throw(function() { new UintNArray(4, ui8.buffer, 65); }, RangeError));
test('-ve b-length', () => should.Throw(function() { new UintNArray(4, ui8.buffer, 0, -1); }, RangeError));
test('excess b-length', () => should.Throw(function() { new UintNArray(4, ui8.buffer, 0, 17); }, RangeError));
});

describe('method errors', function() {
test('set() un-iterable', () => should.Throw(function() { new UintNArray(4).set(1, 1); }, TypeError));
test('set() non-numeric offset', () => should.Throw(function() { new UintNArray(4).set([ 1 ], 'string'); }, RangeError));
test('set() -ve offset', () => should.Throw(function() { new UintNArray(4).set([ 1, 2, 3 ], -1); }, RangeError));
test('set() excess offset', () => should.Throw(function() { new UintNArray(4).set([ 1, 2, 3 ], 2); }, RangeError));
});

0 comments on commit e17451c

Please sign in to comment.