Skip to content

Commit

Permalink
Add getAll/setAll
Browse files Browse the repository at this point in the history
  • Loading branch information
oleggromov committed Nov 10, 2021
1 parent 976719f commit 8cca24d
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 21 deletions.
55 changes: 54 additions & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Magipack from './index';

const ERROR_REGEX = /^BitwiseOption/;
const ERROR_REGEX = /^Magipack/;

describe('Magipack', () => {
describe('single bit options', () => {
Expand Down Expand Up @@ -213,6 +213,59 @@ describe('Magipack', () => {
});
});

describe('getAll/setAll', () => {
let magipack: Magipack;

beforeEach(() => {
magipack = new Magipack([
{name: 'first', size: 4, type: 'uint'},
{name: 'second', size: 4, type: 'uint'},
])
});

it('sets all values at once', () => {
magipack.setAll({
first: BigInt(3),
second: BigInt(2),
});
expect(magipack.get('first')).toBe(BigInt(3));
expect(magipack.get('second')).toBe(BigInt(2));
});

it('throws on incomplete/extra input for setAll', () => {
expect(() => {
magipack.setAll({
first: BigInt(3),
});
}).toThrowError(ERROR_REGEX);
expect(() => {
magipack.setAll({
first: BigInt(3),
second: BigInt(4),
third: BigInt(5),
});
}).toThrowError(ERROR_REGEX);
});

it('returns all values at once', () => {
magipack.setAll({
first: BigInt(8),
second: BigInt(12),
});
expect(magipack.getAll()).toEqual({
first: BigInt(8),
second: BigInt(12),
});
});

it('throws when undefined options are found for getAll', () => {
magipack.set('first', BigInt(3));
expect(() => {
magipack.getAll();
}).toThrowError(ERROR_REGEX);
});
});

describe('API typing and exceptions', () => {
it('requires all option parameters', () => {
expect(() => {
Expand Down
62 changes: 42 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export interface MagipackOption {

export type MagipackOptionType = 'bool' | 'uint' | 'sint';

export type MagipackOptionMap = Record<string, MagipackInternalValue>;

// ToDo:
// - stronger typing
// - support ASCII

type MagipackInternalValue = boolean | bigint;
Expand All @@ -36,7 +39,7 @@ export default class Magipack {
const result: MagipackOption = {
name: inputOpt.name,
size: inputOpt.size,
type: this._getOptionType(inputOpt),
type: this.getOptionType(inputOpt),
};

this.options[inputOpt.name] = {
Expand All @@ -55,7 +58,7 @@ export default class Magipack {
const value = (input & mask) >> bit;

this.options[option.name] = {
value: this._readOptionValue(option, value),
value: this.readOptionValue(option, value),
options: option,
};

Expand All @@ -68,7 +71,7 @@ export default class Magipack {
let bit = BigInt(0);

for (const option of this.supported) {
result |= this._writeOptionValue(this.options[option.name]) << bit;
result |= this.writeOptionValue(this.options[option.name]) << bit;
bit += BigInt(option.size);
}

Expand All @@ -80,31 +83,50 @@ export default class Magipack {
}

get(name: string): MagipackInternalValue {
this._throwOnUnsupportedOption(name);
this._throwOnNoValue(name);
this.throwOnUnsupportedOption(name);
this.throwOnNoValue(name);

return this.options[name].value as MagipackInternalValue;
}

getAll(): MagipackOptionMap {
const result: MagipackOptionMap = {}
Object.keys(this.options).forEach((key) => {
result[key] = this.get(key);
});
return result;
}

set(name: string, value: MagipackInternalValue): void {
this._throwOnUnsupportedOption(name);
this._throwOnTypeMismatch(name, value);
this.throwOnUnsupportedOption(name);
this.throwOnTypeMismatch(name, value);

this.options[name].value = value;
}

_readOptionValue(option: MagipackOption, value: bigint): MagipackInternalValue {
setAll(values: MagipackOptionMap): void {
const remaining = new Set(Object.keys(this.options));
Object.entries(values).forEach(([key, value]) => {
this.set(key, value);
remaining.delete(key);
});
if (remaining.size) {
throw error(`setAll missing keys - ${[...remaining.keys()].toString()}`);
}
}

private readOptionValue(option: MagipackOption, value: bigint): MagipackInternalValue {
if (option.type === 'bool') {
return Boolean(value);
}
if (option.type === 'uint') {
return value;
}

return this._readSignedValue(option, value);
return this.readSignedValue(option, value);
}

_readSignedValue(option: MagipackOption, value: bigint): bigint {
private readSignedValue(option: MagipackOption, value: bigint): bigint {
const significantBits = BigInt(option.size - 1);

const signMask = BigInt(1) << (significantBits);
Expand All @@ -116,18 +138,18 @@ export default class Magipack {
return isNegative ? -maskedValue : maskedValue;
}

_writeOptionValue(option: InternalOption): bigint {
private writeOptionValue(option: InternalOption): bigint {
const {type, size} = option.options;
const value = BigInt(option.value ?? 0);

if (type === 'bool' || type === 'uint') {
return BigInt(value);
}

return this._writeSignedValue(value, size);
return this.writeSignedValue(value, size);
}

_writeSignedValue(value: bigint, size: number): bigint {
private writeSignedValue(value: bigint, size: number): bigint {
const significantBits = BigInt(size - 1);

const signBit = value < 0 ? BigInt(1) : BigInt(0);
Expand All @@ -139,13 +161,13 @@ export default class Magipack {
return signMask | (absoluteValue & valueMask);
}

_throwOnUnsupportedOption(name: string) {
if (!this._isOptionSupported(name)) {
private throwOnUnsupportedOption(name: string) {
if (!this.isOptionSupported(name)) {
throw error(`unsupported option "${name}"`);
}
}

_throwOnTypeMismatch(name: string, value: MagipackInternalValue) {
private throwOnTypeMismatch(name: string, value: MagipackInternalValue) {
const {type, size} = this.options[name].options;

if (typeof value === 'bigint') {
Expand Down Expand Up @@ -178,17 +200,17 @@ export default class Magipack {
}
}

_throwOnNoValue(name: string) {
private throwOnNoValue(name: string) {
if (typeof this.options[name].value === 'undefined') {
throw error(`option "${name}" value is unset`);
}
}

_isOptionSupported(name: string) {
private isOptionSupported(name: string) {
return this.supported.findIndex(opt => name === opt.name) !== -1;
}

_getOptionType(option: MagipackOption): MagipackOptionType {
private getOptionType(option: MagipackOption): MagipackOptionType {
const {name, size, type} = option;

if (!name || !size || !type) {
Expand All @@ -211,7 +233,7 @@ export default class Magipack {
}

function error(msg: string) {
return new Error(`BitwiseOptions: ${msg}`);
return new Error(`Magipack: ${msg}`);
}

function powPositive(base: bigint, power: bigint) {
Expand Down

0 comments on commit 8cca24d

Please sign in to comment.