mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2025-12-12 16:02:08 +01:00
Custom-built bitwise calculator, new number types, and improved UI (#46)
This commit is contained in:
34
src/core/Integer.test.ts
Normal file
34
src/core/Integer.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Integer } from "./Integer"
|
||||
import { UINT32_MAX_VALUE } from "./const";
|
||||
|
||||
it('converts signed to unsigned and vice versa', () => {
|
||||
const n1 = new Integer(-1, 8);
|
||||
const n2 = n1.toUnsigned();
|
||||
const n3 = n2.toSigned();
|
||||
|
||||
expect(n2.signed).toBe(false);
|
||||
expect(n2.num()).toBe(255);
|
||||
|
||||
expect(n3.signed).toBe(true);
|
||||
expect(n3.num()).toBe(-1);
|
||||
|
||||
expect(new Integer(1, 32, false).resize(64).toSigned().maxBitSize).toBe(64);
|
||||
});
|
||||
|
||||
it('convers to different type', () => {
|
||||
const src = new Integer(-1);
|
||||
const dest = new Integer(1, 64, false);
|
||||
|
||||
const n = src.convertTo(dest);
|
||||
expect(n.num()).toBe(UINT32_MAX_VALUE);
|
||||
})
|
||||
|
||||
it('converts to largest size', () => {
|
||||
const n8 = new Integer(-1, 8);
|
||||
const n16 = n8.resize(16);
|
||||
const n32 = n16.resize(32);
|
||||
|
||||
expect(n8.num()).toBe(-1);
|
||||
expect(n16.num()).toBe(-1);
|
||||
expect(n32.num()).toBe(-1);
|
||||
});
|
||||
118
src/core/Integer.ts
Normal file
118
src/core/Integer.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { type } from "os";
|
||||
import { INT32_MAX_VALUE, INT32_MIN_VALUE } from "./const";
|
||||
import { asIntN } from "./utils";
|
||||
import formatter from "./formatter";
|
||||
import calc from "./calc";
|
||||
|
||||
export type JsNumber = number | bigint;
|
||||
|
||||
|
||||
export class Integer {
|
||||
|
||||
readonly value: bigint;
|
||||
readonly maxBitSize: number;
|
||||
readonly signed: boolean;
|
||||
|
||||
constructor(value: JsNumber, maxBitSize?: number, signed? : boolean) {
|
||||
this.value = typeof value == "bigint" ? value : BigInt(value);
|
||||
this.maxBitSize = maxBitSize != null ? maxBitSize : (value >= INT32_MIN_VALUE && value <= INT32_MAX_VALUE) ? 32 : 64;
|
||||
this.signed = signed == null ? true : signed == true;
|
||||
}
|
||||
|
||||
static unsigned(value : JsNumber, maxBitSize?: number) {
|
||||
return new Integer(value, maxBitSize, false);
|
||||
}
|
||||
|
||||
static long(value: JsNumber) : Integer {
|
||||
return new Integer(value, 64);
|
||||
}
|
||||
|
||||
static int(value: JsNumber) : Integer {
|
||||
return new Integer(value, 32);
|
||||
}
|
||||
|
||||
static short(value: JsNumber) : Integer {
|
||||
return new Integer(value, 16);
|
||||
}
|
||||
|
||||
static byte(value: JsNumber) : Integer {
|
||||
return new Integer(value, 8);
|
||||
}
|
||||
|
||||
toUnsigned() {
|
||||
return this.signed
|
||||
? new Integer(BigInt("0b" + this.toString(2)), this.maxBitSize, false)
|
||||
: new Integer(this.value, this.maxBitSize, this.signed);
|
||||
|
||||
}
|
||||
|
||||
toSigned() {
|
||||
|
||||
if(this.signed)
|
||||
return new Integer(this.value, this.maxBitSize, this.signed);
|
||||
|
||||
const bin = calc.engine.applyTwosComplement(this.toString(2));
|
||||
const n = BigInt("0b"+bin);
|
||||
|
||||
return new Integer(bin[0] == '1' ? n : -n, this.maxBitSize, true)
|
||||
}
|
||||
|
||||
resize(newSize: number) {
|
||||
|
||||
if(newSize < this.maxBitSize)
|
||||
throw new Error("Size cannot be reduced");
|
||||
|
||||
if(newSize > 64)
|
||||
throw new Error(`Bit size of ${newSize} is not supported`)
|
||||
|
||||
return new Integer(this.value, newSize, this.signed);
|
||||
}
|
||||
|
||||
convertTo(other: Integer) {
|
||||
|
||||
let newValue = this.value;
|
||||
|
||||
if(this.signed && !other.signed)
|
||||
newValue = this.toUnsigned().value;
|
||||
|
||||
return new Integer(newValue, other.maxBitSize, other.signed);
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.value.toString();
|
||||
}
|
||||
|
||||
toString(base?:number) {
|
||||
return formatter.numberToString(this, base || 10);
|
||||
}
|
||||
|
||||
num() {
|
||||
return asIntN(this.value);
|
||||
}
|
||||
|
||||
bigint() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
export function asInteger(num: JsNumber | Integer | string): Integer {
|
||||
|
||||
if(typeof num == "string")
|
||||
return asInteger(BigInt(num));
|
||||
|
||||
if(isInteger(num))
|
||||
return num;
|
||||
|
||||
if(typeof num == "number" && isNaN(num)) {
|
||||
throw new Error("Cannot create BoundedNumber from NaN");
|
||||
}
|
||||
|
||||
const size = num > INT32_MAX_VALUE || num < INT32_MIN_VALUE ? 64 : 32;
|
||||
|
||||
const n = typeof num == "bigint" ? num : BigInt(num);
|
||||
return new Integer(n, size);
|
||||
}
|
||||
|
||||
export function isInteger(num: JsNumber | Integer): num is Integer {
|
||||
return (<Integer>num).maxBitSize !== undefined;
|
||||
}
|
||||
@@ -1,126 +1,251 @@
|
||||
import calc from './calc';
|
||||
import { ScalarValue } from '../expression/expression';
|
||||
import { INT32_MAX_VALUE } from './const';
|
||||
import { asBoundedNumber } from './types';
|
||||
import { Integer, asInteger } from './Integer';
|
||||
import { INT32_MIN_VALUE, INT64_MAX_VALUE, UINT64_MAX_VALUE } from './const';
|
||||
|
||||
describe('calc.flipBit', () => {
|
||||
it('calculates flipped bit 32-bit number', () => {
|
||||
expect(calc.flipBit(0, 31).value).toBe(1);
|
||||
expect(calc.flipBit(1, 31).value).toBe(0);
|
||||
expect(calc.flipBit(-1, 31).value).toBe(-2);
|
||||
expect(calc.flipBit(2147483647, 0).value).toBe(-1);
|
||||
expect(calc.flipBit(-1, 0).value).toBe(2147483647);
|
||||
expect(calc.flipBit(2147483647, 30).value).toBe(2147483645);
|
||||
expect(calc.flipBit(0, 31).num()).toBe(1);
|
||||
expect(calc.flipBit(1, 31).num()).toBe(0);
|
||||
expect(calc.flipBit(-1, 31).num()).toBe(-2);
|
||||
expect(calc.flipBit(2147483647, 0).num()).toBe(-1);
|
||||
expect(calc.flipBit(-1, 0).num()).toBe(2147483647);
|
||||
expect(calc.flipBit(2147483647, 30).num()).toBe(2147483645);
|
||||
});
|
||||
|
||||
it('sing-bit in 8-bit number', () => {
|
||||
const result = calc.flipBit(new Integer(-1, 8), 0);
|
||||
expect(result.maxBitSize).toBe(8);
|
||||
});
|
||||
|
||||
it('caulate flipped bit 64-bit nubmer', () => {
|
||||
const int64max = BigInt("9223372036854775807");
|
||||
expect(calc.flipBit(BigInt(int64max), 0).value.toString()).toBe("-1");
|
||||
const int64max = asInteger("9223372036854775807");
|
||||
expect(calc.flipBit(int64max, 0).num()).toBe(-1);
|
||||
});
|
||||
|
||||
it('treats usingned type differently', () => {
|
||||
|
||||
const v = BigInt("0b01111111");
|
||||
const r = calc.flipBit(new Integer(v, 8), 0);
|
||||
|
||||
expect(r.num()).toBe(-1);
|
||||
expect(r.signed).toBe(true);
|
||||
|
||||
const ur = calc.flipBit(Integer.unsigned(v, 8), 0)
|
||||
expect(ur.num()).toBe(255);
|
||||
expect(ur.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('calculates flipped bit', () => {
|
||||
expect(calc.flipBit(0, 31).value).toBe(1);
|
||||
expect(calc.flipBit(1, 31).value).toBe(0);
|
||||
expect(calc.flipBit(-1, 31).value).toBe(-2);
|
||||
expect(calc.flipBit(2147483647, 0).value).toBe(-1);
|
||||
expect(calc.flipBit(-1, 0).value).toBe(2147483647);
|
||||
expect(calc.flipBit(2147483647, 30).value).toBe(2147483645);
|
||||
expect(calc.flipBit(0, 31).num()).toBe(1);
|
||||
expect(calc.flipBit(1, 31).num()).toBe(0);
|
||||
expect(calc.flipBit(-1, 31).num()).toBe(-2);
|
||||
expect(calc.flipBit(2147483647, 0).num()).toBe(-1);
|
||||
expect(calc.flipBit(-1, 0).num()).toBe(2147483647);
|
||||
expect(calc.flipBit(2147483647, 30).num()).toBe(2147483645);
|
||||
});
|
||||
|
||||
it('supports ulong', () => {
|
||||
const ulong = calc.flipBit(new Integer(INT64_MAX_VALUE, 64, false), 0);
|
||||
expect(ulong.toString()).toBe(UINT64_MAX_VALUE.toString());
|
||||
})
|
||||
|
||||
it('calcualte 31th bit in 64-bit int', () => {
|
||||
expect(calc.flipBit(calc.promoteTo64Bit(-1), 31).value.toString()).toBe("8589934591");
|
||||
});
|
||||
|
||||
describe('calc.addSpace', () => {
|
||||
it('resizes number based on the space required', () => {
|
||||
const n8 = new Integer(1, 8);
|
||||
const n16 = new Integer(1, 16);
|
||||
|
||||
expect(calc.addSpace(n8, 0).maxBitSize).toBe(8);
|
||||
expect(calc.addSpace(n8, 1).maxBitSize).toBe(16);
|
||||
expect(calc.addSpace(n8, 9).maxBitSize).toBe(32);
|
||||
expect(calc.addSpace(n16, 1).maxBitSize).toBe(32);
|
||||
expect(calc.addSpace(n16, 32).maxBitSize).toBe(64);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('calc.numberOfBitsDisplayed', () => {
|
||||
it('calculates number of bits', () => {
|
||||
expect(calc.numberOfBitsDisplayed(1)).toBe(1);
|
||||
expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(64);
|
||||
expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(32);
|
||||
expect(calc.numberOfBitsDisplayed(2)).toBe(2);
|
||||
expect(calc.numberOfBitsDisplayed(3)).toBe(2);
|
||||
expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36);
|
||||
expect(calc.numberOfBitsDisplayed(-INT32_MAX_VALUE)).toBe(32);
|
||||
expect(calc.numberOfBitsDisplayed(-(BigInt(INT32_MAX_VALUE+1)))).toBe(64);
|
||||
expect(calc.numberOfBitsDisplayed(INT32_MIN_VALUE-1)).toBe(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calc.applyTwosComplement', () => {
|
||||
it('applies twos complement', () => {
|
||||
expect(calc.applyTwosComplement("010")).toBe("110");
|
||||
expect(calc.applyTwosComplement("110")).toBe("010"); // reverse
|
||||
expect(calc.applyTwosComplement("110")).toBe("010");
|
||||
expect(calc.applyTwosComplement("0")).toBe("10");
|
||||
expect(calc.applyTwosComplement("10101100")).toBe("01010100");
|
||||
expect(calc.applyTwosComplement("01010100")).toBe("10101100"); // reverse
|
||||
});
|
||||
});
|
||||
|
||||
describe('calc.rshift', () => {
|
||||
|
||||
it('produces number when given number and vice vers', () => {
|
||||
const number = calc.rshift({value:1, maxBitSize:32}, 1).value;
|
||||
const bigInt = calc.rshift({value:BigInt(1), maxBitSize:32}, 1).value;
|
||||
|
||||
expect(typeof number).toBe('number');
|
||||
expect(number).toBe(2);
|
||||
|
||||
expect(typeof bigInt).toBe('bigint');
|
||||
expect(bigInt.toString()).toBe('2');
|
||||
});
|
||||
|
||||
it('supports scalar values', () => {
|
||||
const operand = new ScalarValue(1);
|
||||
expect(calc.rshift(operand, 1).value).toBe(2);
|
||||
});
|
||||
describe('calc.lshift', () => {
|
||||
|
||||
it("respects bit size", () => {
|
||||
expect(calc.rshift({value: BigInt("0b0100"), maxBitSize: 4}, 2).value.toString()).toBe("0");
|
||||
expect(calc.lshift(new Integer(BigInt("0b0100"), 4), 2).num()).toBe(0);
|
||||
});
|
||||
|
||||
it('transitions number to negative', ()=> {
|
||||
// 4-bit space
|
||||
expect(calc.rshift({value: BigInt("0b0100"), maxBitSize: 4}, 1).value.toString()).toBe("-8");
|
||||
expect(calc.lshift(new Integer(BigInt("0b0100"), 4), 1).num()).toBe(-8);
|
||||
|
||||
// 5-bit space
|
||||
expect(calc.rshift({value: BigInt("0b00100"), maxBitSize: 5}, 1).value.toString()).toBe("8");
|
||||
expect(calc.rshift({value: BigInt("0b01000"), maxBitSize: 5}, 1).value.toString()).toBe("-16");
|
||||
expect(calc.lshift(new Integer(BigInt("0b00100"), 5), 1).num()).toBe(8);
|
||||
expect(calc.lshift(new Integer(BigInt("0b01000"), 5), 1).num()).toBe(-16);
|
||||
|
||||
// 32-bit
|
||||
expect(calc.rshift({value: BigInt("2147483647"), maxBitSize: 32}, 1).value.toString()).toBe("-2");
|
||||
expect(calc.rshift({value: BigInt("2147483647"), maxBitSize: 32}, 2).value.toString()).toBe("-4");
|
||||
expect(calc.lshift(new Integer(BigInt("2147483647"), 32), 1).num()).toBe(-2);
|
||||
expect(calc.lshift(new Integer(BigInt("2147483647"), 32), 2).num()).toBe(-4);
|
||||
|
||||
// 64-bit
|
||||
expect(calc.rshift({value: BigInt("9223372036854775807"), maxBitSize: 64}, 1).value.toString()).toBe("-2");
|
||||
expect(calc.rshift({value: BigInt("9223372036854775807"), maxBitSize: 64}, 2).value.toString()).toBe("-4");
|
||||
expect(calc.rshift({value: BigInt("2147483647"), maxBitSize: 64}, 1).value.toString()).toBe("4294967294");
|
||||
expect(calc.rshift({value: BigInt("2147483647"), maxBitSize: 64}, 2).value.toString()).toBe("8589934588");
|
||||
expect(calc.lshift(new Integer(BigInt("9223372036854775807"), 64), 1).num()).toBe(-2);
|
||||
expect(calc.lshift(new Integer(BigInt("9223372036854775807"), 64), 2).num()).toBe(-4);
|
||||
expect(calc.lshift(new Integer(BigInt("2147483647"), 64), 1).value.toString()).toBe("4294967294");
|
||||
expect(calc.lshift(new Integer(BigInt("2147483647"), 64), 2).value.toString()).toBe("8589934588");
|
||||
});
|
||||
|
||||
it('test', () => {
|
||||
const actual = calc.lshift(asInteger(100081515), 31).num();
|
||||
expect(actual).toBe(-2147483648);
|
||||
});
|
||||
|
||||
it('1 to sign bit', () => {
|
||||
const actual = calc.lshift(asInteger(1), 31).num();
|
||||
expect(actual).toBe(-2147483648);
|
||||
});
|
||||
|
||||
it('resizes items if operands have different sizes', () => {
|
||||
// -1L|-1 - 64 bit
|
||||
// 1123123u|-1 - 64 bit
|
||||
const r = calc.or(new Integer(-1, 64), new Integer(-1));
|
||||
expect(r.maxBitSize).toBe(64);
|
||||
expect(r.num()).toBe(-1);
|
||||
|
||||
const r2 = calc.or(Integer.unsigned(1123123, 32), new Integer(-1));
|
||||
expect(r2.maxBitSize).toBe(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('preserves C compiler behvaior', () => {
|
||||
it('lshift same size bytes', () => {
|
||||
const int = Integer.int(-1);
|
||||
const long = Integer.long(-1);
|
||||
|
||||
expect(calc.lshift(int, 32).num()).toBe(int.num());
|
||||
expect(calc.lshift(long, 32).num()).toBe(-4294967296);
|
||||
expect(calc.lshift(long, 64).num()).toBe(long.num());
|
||||
|
||||
expect(calc.rshift(int, 32).num()).toBe(int.num());
|
||||
expect(calc.rshift(long, 32).num()).toBe(-1);
|
||||
expect(calc.rshift(long, 64).num()).toBe(long.num());
|
||||
|
||||
expect(calc.urshift(int, 32).num()).toBe(int.num());
|
||||
expect(calc.urshift(long, 32).num()).toBe(4294967295);
|
||||
expect(calc.urshift(long, 64).num()).toBe(long.num());
|
||||
});
|
||||
|
||||
it('shift by bigger numbers of bytes', () => {
|
||||
const byte = Integer.byte(-1);
|
||||
expect(calc.urshift(byte, 9).num()).toBe(calc.urshift(byte, 1).num());
|
||||
expect(calc.urshift(byte, 17).num()).toBe(calc.urshift(byte, 1).num());
|
||||
expect(calc.urshift(byte, 18).num()).toBe(calc.urshift(byte, 2).num());
|
||||
|
||||
const int = Integer.int(-1);
|
||||
expect(calc.lshift(int, 33).num()).toBe(-2);
|
||||
expect(calc.rshift(int, 33).num()).toBe(-1);
|
||||
|
||||
expect(calc.rshift(Integer.int(1), 33).num()).toBe(0);
|
||||
expect(calc.rshift(Integer.int(1), 32).num()).toBe(1);
|
||||
|
||||
expect(calc.lshift(Integer.byte(1), 20).num()).toBe(16);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calc misc", () => {
|
||||
|
||||
|
||||
it('promoteTo64Bit', () => {
|
||||
expect(calc.promoteTo64Bit(-1).value.toString(2)).toBe("11111111111111111111111111111111");
|
||||
const n = asInteger(-1);
|
||||
expect(calc.toBinaryString(calc.promoteTo64Bit(n))).toBe("11111111111111111111111111111111");
|
||||
});
|
||||
|
||||
it('binaryRepresentation', () => {
|
||||
expect(calc.toBinaryString(asBoundedNumber(2147483647))).toBe("1111111111111111111111111111111");
|
||||
expect(calc.toBinaryString(new ScalarValue(2147483647))).toBe("1111111111111111111111111111111");
|
||||
})
|
||||
|
||||
expect(calc.toBinaryString(asInteger(2147483647))).toBe("1111111111111111111111111111111");
|
||||
});
|
||||
|
||||
it('not 64bit', () => {
|
||||
const actual = calc.not(asInteger("8920390230576132")).toString();
|
||||
expect(actual).toBe("-8920390230576133");
|
||||
});
|
||||
});
|
||||
|
||||
describe("bitwise ", () => {
|
||||
describe("calc.engine.", () => {
|
||||
it("not", () => {
|
||||
expect(calc.engine.not("0101")).toBe("1010");
|
||||
expect(calc.engine.not("11111")).toBe("00000")
|
||||
});
|
||||
|
||||
it("or", () => {
|
||||
expect(calc.engine.or("1", "1")).toBe("1");
|
||||
expect(calc.engine.or("1", "0")).toBe("1");
|
||||
expect(calc.engine.or("0", "0")).toBe("0");
|
||||
expect(calc.engine.or("10101", "01111")).toBe("11111");
|
||||
});
|
||||
|
||||
it("and", () => {
|
||||
expect(calc.engine.and("1", "1")).toBe("1");
|
||||
expect(calc.engine.and("1", "0")).toBe("0");
|
||||
expect(calc.engine.and("0", "0")).toBe("0");
|
||||
expect(calc.engine.and("10101", "11011")).toBe("10001");
|
||||
});
|
||||
|
||||
it("xor", () => {
|
||||
expect(calc.engine.xor("1", "1")).toBe("0");
|
||||
expect(calc.engine.xor("1", "0")).toBe("1");
|
||||
expect(calc.engine.xor("0", "0")).toBe("0");
|
||||
expect(calc.engine.xor("10101", "11011")).toBe("01110");
|
||||
});
|
||||
|
||||
it("lshift", () => {
|
||||
expect(calc.engine.lshift("1", 1)).toBe("0");
|
||||
expect(calc.engine.lshift("01", 1)).toBe("10");
|
||||
expect(calc.engine.lshift("01101", 4)).toBe("10000");
|
||||
expect(calc.engine.lshift("000001", 4)).toBe("010000");
|
||||
});
|
||||
|
||||
it("rshift", () => {
|
||||
expect(calc.engine.rshift("1", 1)).toBe("1");
|
||||
expect(calc.engine.rshift("01", 1)).toBe("00");
|
||||
expect(calc.engine.rshift("0101", 2)).toBe("0001");
|
||||
expect(calc.engine.rshift("1000", 3)).toBe("1111");
|
||||
expect(calc.engine.rshift("1101", 1)).toBe("1110");
|
||||
});
|
||||
|
||||
it("urshift", () => {
|
||||
expect(calc.engine.urshift("1", 1)).toBe("0");
|
||||
expect(calc.engine.urshift("01", 1)).toBe("00");
|
||||
expect(calc.engine.urshift("0101", 2)).toBe("0001");
|
||||
expect(calc.engine.urshift("1000", 3)).toBe("0001");
|
||||
expect(calc.engine.urshift("1101", 1)).toBe("0110");
|
||||
});
|
||||
|
||||
it('flipbit', () => {
|
||||
expect(calc.engine.flipBit("1", 0)).toBe("0");
|
||||
expect(calc.engine.flipBit("101", 1)).toBe("111");
|
||||
});
|
||||
|
||||
it('applyTwosComplement', () => {
|
||||
expect(calc.engine.applyTwosComplement("010")).toBe("110");
|
||||
expect(calc.engine.applyTwosComplement("110")).toBe("010"); // reverse
|
||||
expect(calc.engine.applyTwosComplement("110")).toBe("010");
|
||||
expect(calc.engine.applyTwosComplement("0")).toBe("10");
|
||||
expect(calc.engine.applyTwosComplement("10101100")).toBe("01010100");
|
||||
expect(calc.engine.applyTwosComplement("01010100")).toBe("10101100"); // reverse
|
||||
});
|
||||
})
|
||||
|
||||
describe("engine comparison", () => {
|
||||
|
||||
it("NOT same as in node", () => {
|
||||
|
||||
for(var i = -100; i<100;i++) {
|
||||
const expected = bin(~i);
|
||||
const actual = calc.bitwise.not(bin(i));
|
||||
const actual = calc.engine.not(bin(i));
|
||||
expect(`${i} is ${actual}`).toBe(`${i} is ${(expected)}`);
|
||||
}
|
||||
});
|
||||
@@ -129,7 +254,7 @@ describe("bitwise ", () => {
|
||||
for(var x = -100; x<100;x++) {
|
||||
const y = 5+3%x+x%6*(-x);
|
||||
const expected = bin(x | y);
|
||||
const actual = calc.bitwise.or(bin(x), bin(y));
|
||||
const actual = calc.engine.or(bin(x), bin(y));
|
||||
|
||||
expect(`${x} is ${actual}`).toBe(`${x} is ${(expected)}`);
|
||||
}
|
||||
@@ -139,7 +264,7 @@ describe("bitwise ", () => {
|
||||
for(var x = -100; x<100;x++) {
|
||||
const y = 5+3%x+x%6*(-x);
|
||||
const expected = bin(x & y);
|
||||
const actual = calc.bitwise.and(bin(x), bin(y));
|
||||
const actual = calc.engine.and(bin(x), bin(y));
|
||||
|
||||
expect(`${x} is ${actual}`).toBe(`${x} is ${(expected)}`);
|
||||
}
|
||||
|
||||
329
src/core/calc.ts
329
src/core/calc.ts
@@ -1,51 +1,214 @@
|
||||
import formatter from "./formatter";
|
||||
import { BoundedNumber, JsNumber, maxBitSize, asBoundedNumber } from "./types";
|
||||
import { Integer, JsNumber, asInteger } from "./Integer";
|
||||
import { asIntN } from "./utils";
|
||||
|
||||
export default {
|
||||
abs (num : BoundedNumber) : BoundedNumber {
|
||||
return asBoundedNumber(num.value >= 0 ? num.value : -num.value);
|
||||
abs (num : Integer) : Integer {
|
||||
return asInteger(num.value >= 0 ? num.value : -num.value);
|
||||
},
|
||||
|
||||
maxBitSize(num : JsNumber) : number {
|
||||
return maxBitSize(num);
|
||||
numberOfBitsDisplayed: function (num: Integer | JsNumber) : number {
|
||||
return this.toBinaryString(asInteger(num)).length;
|
||||
},
|
||||
|
||||
numberOfBitsDisplayed: function (num: JsNumber) : number {
|
||||
|
||||
if(num < 0) {
|
||||
return typeof num == 'bigint' ? 64 : 32
|
||||
};
|
||||
|
||||
return num.toString(2).length;
|
||||
flipBit: function(num: Integer | JsNumber, bitIndex: number): Integer {
|
||||
return this._applySingle(asInteger(num), (bin) => this.engine.flipBit(bin, bitIndex));
|
||||
},
|
||||
|
||||
flipBit: function(num: BoundedNumber | JsNumber, index: number): BoundedNumber {
|
||||
promoteTo64Bit(number: Integer) : Integer {
|
||||
const bin = this.toBinaryString(number);
|
||||
return new Integer(BigInt("0b" + bin), 64);
|
||||
},
|
||||
|
||||
num = asBoundedNumber(num);
|
||||
const is64bit = num.maxBitSize == 64;
|
||||
const size = num.maxBitSize;
|
||||
const bin = formatter.bin(num.value).padStart(size, '0');
|
||||
const staysNegative = (bin[0] == "1" && index > 0);
|
||||
const becomesNegative = (bin[0] == "0" && index == 0);
|
||||
addSpace(number: Integer, requiredSpace: number) : Integer {
|
||||
const bin = this.toBinaryString(number);
|
||||
const totalSpaceRequired = number.maxBitSize + requiredSpace;
|
||||
return new Integer(BigInt("0b" + bin), nextPowOfTwo(totalSpaceRequired));
|
||||
},
|
||||
|
||||
let m = 1;
|
||||
let flipped = bin.substring(0, index) + flip(bin[index]) + bin.substring(index+1);
|
||||
operation (op1: Integer, operator: string, op2 : Integer) : Integer {
|
||||
switch(operator) {
|
||||
case ">>": return this.rshift(op1, op2.value);
|
||||
case ">>>": return this.urshift(op1, op2.value);
|
||||
case "<<": return this.lshift(op1, op2.value);
|
||||
case "&": return this.and(op1,op2);
|
||||
case "|": return this.or(op1,op2);
|
||||
case "^": return this.xor(op1,op2);
|
||||
default: throw new Error(operator + " operator is not supported");
|
||||
}
|
||||
},
|
||||
|
||||
if(staysNegative || becomesNegative) {
|
||||
flipped = this.applyTwosComplement(flipped);
|
||||
m=-1;
|
||||
toBinaryString(num: Integer) : string {
|
||||
const bitSize = num.maxBitSize;
|
||||
const bin = this.abs(num).value.toString(2);
|
||||
|
||||
if(bin.length > bitSize!)
|
||||
throw new Error(`Binary represenation '${bin}' is bigger than the given bit size ${bitSize}`)
|
||||
|
||||
const r = num.value < 0
|
||||
? this.engine.applyTwosComplement(bin.padStart(bitSize, '0'))
|
||||
: bin;
|
||||
|
||||
return r;
|
||||
},
|
||||
|
||||
lshift (num: Integer, numBytes : JsNumber) : Integer {
|
||||
|
||||
let bytes = asIntN(numBytes);
|
||||
|
||||
if(num.maxBitSize == numBytes)
|
||||
return num; // Preserve C undefined behavior
|
||||
|
||||
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
||||
|
||||
return this._applySingle(num, bin => this.engine.lshift(bin, bytes));
|
||||
},
|
||||
|
||||
rshift (num : Integer, numBytes : JsNumber) : Integer {
|
||||
|
||||
let bytes = asIntN(numBytes);
|
||||
|
||||
if(num.maxBitSize == numBytes)
|
||||
return num; // Preserve C undefined behavior
|
||||
|
||||
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
||||
|
||||
return this._applySingle(num, bin => this.engine.rshift(bin, bytes));
|
||||
},
|
||||
|
||||
urshift (num : Integer, numBytes : JsNumber) : Integer {
|
||||
|
||||
let bytes = asIntN(numBytes);
|
||||
|
||||
if(num.maxBitSize == numBytes)
|
||||
return num; // Preserve C undefined behavior
|
||||
|
||||
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
||||
|
||||
return this._applySingle(num, bin => this.engine.urshift(bin, bytes));
|
||||
},
|
||||
|
||||
not(num:Integer) : Integer {
|
||||
return this._applySingle(num, this.engine.not);
|
||||
},
|
||||
|
||||
and (num1 : Integer, num2 : Integer) : Integer {
|
||||
return this._applyTwo(num1, num2, this.engine.and);
|
||||
},
|
||||
|
||||
or (num1 : Integer, num2 : Integer) : Integer {
|
||||
return this._applyTwo(num1, num2, this.engine.or);
|
||||
},
|
||||
|
||||
xor (num1 : Integer, num2 : Integer) : Integer {
|
||||
return this._applyTwo(num1, num2, this.engine.xor);
|
||||
},
|
||||
|
||||
_applySingle(num: Integer, operation: (bin:string) => string) : Integer {
|
||||
|
||||
let bin = this.toBinaryString(num).padStart(num.maxBitSize, '0');
|
||||
|
||||
bin = operation(bin);
|
||||
|
||||
let negative = false;
|
||||
|
||||
if(num.signed && bin['0'] == '1') {
|
||||
bin = this.engine.applyTwosComplement(bin);
|
||||
negative = true;
|
||||
}
|
||||
|
||||
const n : JsNumber = is64bit ? BigInt("0b"+ flipped)*BigInt(m) : parseInt(flipped, 2)*m;
|
||||
return asBoundedNumber(n);
|
||||
const result = BigInt("0b" + bin) * BigInt(negative ? -1 : 1);
|
||||
return new Integer(typeof num.value == "bigint" ? result : asIntN(result), num.maxBitSize, num.signed);
|
||||
},
|
||||
|
||||
promoteTo64Bit(number: number) : BoundedNumber {
|
||||
const bin = this.toBinaryString(asBoundedNumber(number));
|
||||
return asBoundedNumber(BigInt("0b" + bin));
|
||||
_applyTwo(op1: Integer, op2: Integer, operation: (bin1:string, bin2:string) => string) : Integer {
|
||||
|
||||
if(op1.maxBitSize == op2.maxBitSize && op1.signed != op2.signed)
|
||||
throw new Error("Operator `" + operation + "` cannot be applied to signed and unsigned operands of the same size");
|
||||
|
||||
const [num1, num2] = equalizeSize(op1, op2);
|
||||
|
||||
let bin1 = this.toBinaryString(num1).padStart(num1.maxBitSize, '0');
|
||||
let bin2 = this.toBinaryString(num2).padStart(num2.maxBitSize, '0');
|
||||
|
||||
let resultBin = operation(bin1, bin2);
|
||||
|
||||
let m = BigInt(1);
|
||||
|
||||
if(resultBin['0'] == '1') {
|
||||
resultBin = this.engine.applyTwosComplement(resultBin);
|
||||
m = BigInt(-1);
|
||||
}
|
||||
|
||||
const result = BigInt("0b" + resultBin) * m;
|
||||
return new Integer(result, num1.maxBitSize);
|
||||
},
|
||||
|
||||
engine: {
|
||||
lshift (bin: string, bytes: number):string {
|
||||
return bin.substring(bytes) + "0".repeat(bytes);
|
||||
},
|
||||
rshift (bin: string, bytes: number):string {
|
||||
const pad = bin[0].repeat(bytes);
|
||||
return pad + bin.substring(0, bin.length - bytes);
|
||||
},
|
||||
urshift (bin: string, bytes: number):string {
|
||||
const pad = '0'.repeat(bytes);
|
||||
return pad + bin.substring(0, bin.length - bytes);
|
||||
},
|
||||
not (bin: string) : string {
|
||||
|
||||
return bin
|
||||
.split('').map(c => flip(c))
|
||||
.join("");
|
||||
},
|
||||
or (bin1: string, bin2 : string) : string {
|
||||
|
||||
checkSameLength(bin1, bin2);
|
||||
|
||||
const result = [];
|
||||
for(var i=0; i<bin1.length; i++) {
|
||||
|
||||
const b1 = bin1[i] === "1";
|
||||
const b2 = bin2[i] === "1";
|
||||
|
||||
result.push(b1 || b2 ? "1" : "0");
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
and (bin1: string, bin2 : string) : string {
|
||||
|
||||
checkSameLength(bin1, bin2);
|
||||
|
||||
const result = [];
|
||||
for(var i=0; i<bin1.length; i++) {
|
||||
|
||||
const b1 = bin1[i] === "1";
|
||||
const b2 = bin2[i] === "1";
|
||||
|
||||
result.push(b1 && b2 ? "1" : "0");
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
xor (bin1: string, bin2:string) : string {
|
||||
|
||||
checkSameLength(bin1, bin2);
|
||||
|
||||
const result = [];
|
||||
for(var i=0; i<bin1.length; i++) {
|
||||
|
||||
const b1 = bin1[i] === "1";
|
||||
const b2 = bin2[i] === "1";
|
||||
|
||||
result.push(b1 != b2 ? "1" : "0");
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
flipBit(bin: string, bitIndex : number) : string {
|
||||
return bin.substring(0, bitIndex) + flip(bin[bitIndex]) + bin.substring(bitIndex+1)
|
||||
},
|
||||
applyTwosComplement: (bin:string):string => {
|
||||
var lastIndex = bin.lastIndexOf('1');
|
||||
|
||||
@@ -64,79 +227,45 @@ export default {
|
||||
|
||||
return flipped.join('') + bin.substring(lastIndex) ;
|
||||
},
|
||||
|
||||
toBinaryString(num: BoundedNumber) : string {
|
||||
|
||||
const bitSize = num.maxBitSize;
|
||||
const bin = this.abs(num).value.toString(2);
|
||||
|
||||
if(bin.length > bitSize!)
|
||||
throw new Error(`Binary represenation '${bin}' is bigger than the given bit size ${bitSize}`)
|
||||
|
||||
const r = num.value < 0
|
||||
? this.applyTwosComplement(bin.padStart(bitSize, '0'))
|
||||
: bin;
|
||||
|
||||
return r;
|
||||
},
|
||||
|
||||
rshift (num: BoundedNumber, numBytes : JsNumber) : BoundedNumber {
|
||||
|
||||
const bytes = asIntN(numBytes);
|
||||
|
||||
let bin = this.toBinaryString(num).padStart(num.maxBitSize, '0');
|
||||
|
||||
bin = bin.substring(bytes) + "0".repeat(bytes);
|
||||
|
||||
let m = BigInt(1);
|
||||
|
||||
if(bin['0'] == '1') {
|
||||
bin = this.applyTwosComplement(bin);
|
||||
m = BigInt(-1);
|
||||
}
|
||||
|
||||
const result = BigInt("0b" + bin) * m;
|
||||
return asBoundedNumber(typeof num.value == "bigint" ? result : asIntN(result));
|
||||
},
|
||||
|
||||
bitwise: {
|
||||
not: (bin: string) : string => {
|
||||
|
||||
var padded = bin
|
||||
.split('').map(c => flip(c))
|
||||
.join("");
|
||||
|
||||
return padded;
|
||||
},
|
||||
or: (bin1: string, bin2 : string) : string => {
|
||||
|
||||
const result = [];
|
||||
for(var i=0; i<bin1.length; i++) {
|
||||
|
||||
const b1 = bin1[i] === "1";
|
||||
const b2 = bin2[i] === "1";
|
||||
|
||||
result.push(b1 || b2 ? "1" : "0");
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
and: (bin1: string, bin2 : string) : string => {
|
||||
|
||||
const result = [];
|
||||
for(var i=0; i<bin1.length; i++) {
|
||||
|
||||
const b1 = bin1[i] === "1";
|
||||
const b2 = bin2[i] === "1";
|
||||
|
||||
result.push(b1 && b2 ? "1" : "0");
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function checkSameLength(bin1: string, bin2: string) {
|
||||
if (bin1.length != bin2.length)
|
||||
throw new Error("Binary strings must have the same length");
|
||||
}
|
||||
|
||||
function flip(bit:string):string {
|
||||
return bit === "0" ? "1" : "0";
|
||||
}
|
||||
|
||||
function nextPowOfTwo(num: number) : number {
|
||||
let p = 2;
|
||||
while(p < num) p = p*2;
|
||||
return p;
|
||||
}
|
||||
|
||||
function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] {
|
||||
|
||||
if(n1.maxBitSize == n2.maxBitSize)
|
||||
{
|
||||
if(n1.signed === n2.signed) return [n1,n2];
|
||||
|
||||
// Example int and usinged int. Poromoted both yo 64 bit
|
||||
return [n1.resize(n1.maxBitSize*2).toSigned(), n2.resize(n2.maxBitSize*2).toSigned()];
|
||||
}
|
||||
|
||||
return n1.maxBitSize > n2.maxBitSize
|
||||
? [n1, n2.convertTo(n1)]
|
||||
: [n1.convertTo(n2), n2];
|
||||
}
|
||||
|
||||
/*
|
||||
// c#
|
||||
var op = -1;
|
||||
var r = op>>>33;
|
||||
Console.WriteLine(Convert.ToString(op, 2).PadLeft(32, '0'));
|
||||
Console.WriteLine(Convert.ToString(r,2).PadLeft(32, '0'));
|
||||
Console.WriteLine(Convert.ToString(r));
|
||||
Console.WriteLine(r.GetType().Name);
|
||||
*/
|
||||
@@ -1,3 +0,0 @@
|
||||
.sign-bit {
|
||||
color:mediumseagreen !important;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export type BinaryStringViewProps = {
|
||||
emphasizeBytes?: boolean;
|
||||
className?:string;
|
||||
disableHighlight?:boolean,
|
||||
bitSize?: number
|
||||
signBitIndex?: number,
|
||||
};
|
||||
|
||||
export type FlipBitEventArg = {
|
||||
@@ -36,7 +36,7 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
||||
}
|
||||
|
||||
getChildren() {
|
||||
var bits = this.createBits(this.props.binaryString.split(''), this.props.bitSize);
|
||||
var bits = this.createBits(this.props.binaryString.split(''));
|
||||
|
||||
if(this.props.emphasizeBytes) {
|
||||
return this.splitIntoBytes(bits);
|
||||
@@ -53,19 +53,13 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
||||
|
||||
let signBitIndex = -1;
|
||||
|
||||
if(bitChars.length === bitSize)
|
||||
signBitIndex = 0;
|
||||
|
||||
if(bitSize != null && bitChars.length > bitSize)
|
||||
signBitIndex = bitChars.length - bitSize!;
|
||||
|
||||
return bitChars.map((c, i) => {
|
||||
|
||||
var className = c == '1' ? `one${css}` : `zero${css}`;
|
||||
var tooltip = '';
|
||||
|
||||
if(i === signBitIndex) {
|
||||
className += ' sign-bit';
|
||||
if(i === this.props.signBitIndex) {
|
||||
className += ' accent1';
|
||||
tooltip = 'Signature bit. 0 means a positive number and 1 means a negative.'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
const INT32_MAX_VALUE = 2147483647;
|
||||
const INT32_MIN_VALUE = -INT32_MAX_VALUE;
|
||||
const INT32_MIN_VALUE = -2147483648;
|
||||
const UINT32_MAX_VALUE = 4294967295;
|
||||
const INT64_MAX_VALUE = BigInt("9223372036854775807");
|
||||
const INT64_MIN_VALUE = BigInt("-9223372036854775807");
|
||||
export {INT32_MAX_VALUE, INT32_MIN_VALUE, INT64_MAX_VALUE, INT64_MIN_VALUE};
|
||||
const INT64_MIN_VALUE = BigInt("-9223372036854775808");
|
||||
const UINT64_MAX_VALUE = BigInt("18446744073709551615");
|
||||
export {INT32_MAX_VALUE, INT32_MIN_VALUE, INT64_MAX_VALUE, INT64_MIN_VALUE, UINT32_MAX_VALUE, UINT64_MAX_VALUE};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import formatter from './formatter';
|
||||
import { Integer } from './Integer';
|
||||
|
||||
describe("formatter", () => {
|
||||
it('formats string', () => {
|
||||
@@ -8,8 +9,11 @@ describe("formatter", () => {
|
||||
});
|
||||
|
||||
it('respects size when formatting negative number', () => {
|
||||
expect(formatter.bin(-1)).toBe("11111111111111111111111111111111");
|
||||
expect(formatter.bin(BigInt(-1))).toBe("1111111111111111111111111111111111111111111111111111111111111111");
|
||||
const minusOne = BigInt(-1);
|
||||
const n32 = new Integer(minusOne, 32);
|
||||
const n64 = new Integer(minusOne, 64);
|
||||
expect(formatter.bin(n32)).toBe("11111111111111111111111111111111");
|
||||
expect(formatter.bin(n64)).toBe("1111111111111111111111111111111111111111111111111111111111111111");
|
||||
});
|
||||
|
||||
it('formats large binary number correctly', () => {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { BoundFunction } from "@testing-library/react";
|
||||
import calc from "./calc";
|
||||
import { BoundedNumber, JsNumber, isBoundedNumber, asBoundedNumber } from "./types";
|
||||
import { Integer, JsNumber, asInteger } from "./Integer";
|
||||
export type NumberBase = 'dec' | 'hex' | 'bin';
|
||||
|
||||
const formatter = {
|
||||
numberToString: function(num: BoundedNumber | JsNumber, base: NumberBase) : string {
|
||||
numberToString: function(num: Integer | JsNumber, base: NumberBase | number) : string {
|
||||
|
||||
num = asBoundedNumber(num);
|
||||
num = asInteger(num);
|
||||
base = typeof base == "string" ? getBase(base) : base;
|
||||
|
||||
switch(base) {
|
||||
case 'hex':
|
||||
case 16:
|
||||
var hexVal = calc.abs(num).value.toString(16);
|
||||
return num.value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
|
||||
case 'bin':
|
||||
case 2:
|
||||
return calc.toBinaryString(num);
|
||||
case 'dec':
|
||||
case 10:
|
||||
return num.value.toString(10);
|
||||
default:
|
||||
throw new Error("Unexpected kind: " + base)
|
||||
@@ -33,10 +33,10 @@ const formatter = {
|
||||
|
||||
return sb.join('');
|
||||
},
|
||||
bin(number: JsNumber) {
|
||||
bin(number: Integer | JsNumber) {
|
||||
return this.numberToString(number, 'bin');
|
||||
},
|
||||
emBin(number: JsNumber) {
|
||||
emBin(number: Integer | JsNumber) {
|
||||
return this.padLeft(this.bin(number), 8, '0');
|
||||
},
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { type } from "os";
|
||||
|
||||
export type JsNumber = number | bigint;
|
||||
export type BoundedNumber = {
|
||||
value: JsNumber,
|
||||
maxBitSize: number
|
||||
}
|
||||
|
||||
export function asBoundedNumber(num: JsNumber | BoundedNumber): BoundedNumber {
|
||||
|
||||
if(isBoundedNumber(num))
|
||||
return num;
|
||||
|
||||
if(typeof num == "number" && isNaN(num)) {
|
||||
throw new Error("Cannot create BoundedNumber from NaN");
|
||||
}
|
||||
|
||||
return {value:num, maxBitSize: maxBitSize(num)};
|
||||
}
|
||||
|
||||
export function isBoundedNumber(num: JsNumber | BoundedNumber): num is BoundedNumber {
|
||||
return (<BoundedNumber>num).maxBitSize !== undefined;
|
||||
}
|
||||
|
||||
export function maxBitSize(num : JsNumber) : number {
|
||||
return typeof num == "bigint" ? 64 : 32;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JsNumber } from "./types";
|
||||
import { Integer, JsNumber, isInteger } from "./Integer";
|
||||
|
||||
function chunkifyString(input: string, chunkSize: number) : string[] {
|
||||
|
||||
@@ -11,8 +11,19 @@ function chunkifyString(input: string, chunkSize: number) : string[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
function asIntN(num: JsNumber) : number {
|
||||
function asIntN(num: JsNumber | Integer) : number {
|
||||
if(isInteger(num))
|
||||
return asIntN(num.value);
|
||||
|
||||
return typeof num == "bigint" ? parseInt(num.toString()): num as number;
|
||||
}
|
||||
|
||||
export {chunkifyString, asIntN};
|
||||
function random(from: number, to: number) {
|
||||
return Math.floor(Math.random() * (to+1));
|
||||
}
|
||||
|
||||
function randomBool() {
|
||||
return random(1, 10000) % 2 == 0;
|
||||
}
|
||||
|
||||
export {chunkifyString, asIntN, random, randomBool};
|
||||
@@ -1,13 +1,12 @@
|
||||
import ScalarValue from "./ScalarValue";
|
||||
import BitwiseOperator from './BitwiseOperator';
|
||||
import { INT32_MAX_VALUE } from "../core/const";
|
||||
|
||||
it('can apply ~ operand', () => {
|
||||
var op = new ScalarValue(10, 'dec');
|
||||
var expr = new BitwiseOperator(op, "~");
|
||||
|
||||
var result = expr.evaluate();
|
||||
expect(result.value).toBe(-11);
|
||||
expect(result.value.num()).toBe(-11);
|
||||
expect(result.base).toBe('dec');
|
||||
});
|
||||
|
||||
@@ -16,6 +15,6 @@ it('can apply & operand', () => {
|
||||
var op2 = new ScalarValue(4, 'dec');
|
||||
var expr = new BitwiseOperator(op1, "|");
|
||||
var result = expr.evaluate(op2);
|
||||
expect(result.value).toBe(7);
|
||||
expect(result.value.num()).toBe(7);
|
||||
expect(result.base).toBe('dec');
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import calc from '../core/calc';
|
||||
import ScalarValue from './ScalarValue';
|
||||
import engine from './engine';
|
||||
import { ExpressionElement } from './expression-interfaces';
|
||||
|
||||
export default class BitwiseOperator implements ExpressionElement {
|
||||
@@ -29,8 +29,8 @@ export default class BitwiseOperator implements ExpressionElement {
|
||||
var evaluatedOperand = this.operand.evaluate();
|
||||
|
||||
return this.operator == "~"
|
||||
? engine.applyNotOperator(this.operand.getUnderlyingScalarOperand())
|
||||
: engine.applyOperator(operand!, this.operator, evaluatedOperand);
|
||||
? applyNotOperator(this.operand.getUnderlyingScalarOperand())
|
||||
: applyOperator(operand!, this.operator, evaluatedOperand);
|
||||
}
|
||||
|
||||
getUnderlyingScalarOperand() : ScalarValue {
|
||||
@@ -41,3 +41,42 @@ export default class BitwiseOperator implements ExpressionElement {
|
||||
return this.operator + this.operand.toString();
|
||||
}
|
||||
}
|
||||
|
||||
function applyNotOperator(operand: ScalarValue) : ScalarValue {
|
||||
return new ScalarValue(calc.not(operand.value), operand.base);
|
||||
}
|
||||
|
||||
function applyOperator(op1 : ScalarValue, operator: string, op2 : ScalarValue) : ScalarValue {
|
||||
|
||||
const isShift = /<|>/.test(operator);
|
||||
if(!isShift)
|
||||
{
|
||||
if(op1.value.maxBitSize == op2.value.maxBitSize && op1.value.signed != op2.value.signed)
|
||||
throw new Error("Operator `" + operator + "` cannot be applied to signed and unsigned operands of the same " + op2.value.maxBitSize + " -bit size");
|
||||
|
||||
equalizeSize(op1, op2);
|
||||
}
|
||||
|
||||
const result = calc.operation(op1.value, operator, op2.value);
|
||||
return new ScalarValue(result, op2.base);
|
||||
}
|
||||
|
||||
function equalizeSize(op1: ScalarValue, op2: ScalarValue) {
|
||||
|
||||
const n1 = op1.value;
|
||||
const n2 = op2.value;
|
||||
|
||||
if(n1.maxBitSize === n2.maxBitSize)
|
||||
{
|
||||
if(n1.signed === n2.signed) return;
|
||||
|
||||
// Example int and usinged int. Poromoted both to 64 bit
|
||||
op1.setValue(n1.resize(n1.maxBitSize*2).toSigned()).setLabel("converted");
|
||||
op2.setValue(n2.resize(n2.maxBitSize*2).toSigned()).setLabel("converted");
|
||||
}
|
||||
|
||||
if(n1.maxBitSize > n2.maxBitSize)
|
||||
op2.setValue(n2.convertTo(n1)).setLabel("converted");
|
||||
else
|
||||
op1.setValue(n1.convertTo(n2)).setLabel("converted");
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
import ScalarValue from "./ScalarValue";
|
||||
import ListOfNumbersExpression from "./ListOfNumbersExpression";
|
||||
import { numberParser } from "./numberParser";
|
||||
|
||||
it('calculates max bits length', () => {
|
||||
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [ScalarValue.parse("10"), ScalarValue.parse("0xabef"), ScalarValue.parse("0b01010")])
|
||||
const v1 = new ScalarValue(numberParser.parse("10").value);
|
||||
const v2 = new ScalarValue(numberParser.parse("0xabef").value);
|
||||
const v3 = new ScalarValue(numberParser.parse("0b01010").value);
|
||||
|
||||
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [v1, v2, v3])
|
||||
expect(expr.maxBitsLength).toBe(16);
|
||||
});
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import exp from "constants";
|
||||
import BitwiseOperator from "./BitwiseOperator";
|
||||
import ScalarValue from "./ScalarValue"
|
||||
|
||||
it('supports evaluation of big int', () => {
|
||||
const v = new ScalarValue(BigInt(1));
|
||||
const op = new BitwiseOperator(new ScalarValue(1), "<<");
|
||||
|
||||
//const r = op.evaluate(v);
|
||||
//expect(r.isBigInt()).toBe(true);
|
||||
//expect(r.value.toString()).toBe("2");
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
import ScalarValue from './ScalarValue';
|
||||
|
||||
it('parsed from dec string', () => {
|
||||
var op = ScalarValue.parse('123');
|
||||
expect(op.base).toBe('dec');
|
||||
expect(op.value).toBe(123);
|
||||
});
|
||||
|
||||
it('parsed from bin string', () => {
|
||||
var op = ScalarValue.parse('0b10');
|
||||
expect(op.base).toBe('bin');
|
||||
expect(op.value).toBe(2);
|
||||
});
|
||||
|
||||
it('parsed from hex string', () => {
|
||||
var op = ScalarValue.parse('0x10');
|
||||
expect(op.base).toBe('hex');
|
||||
expect(op.value).toBe(16);
|
||||
});
|
||||
@@ -1,11 +1,11 @@
|
||||
import { INT32_MAX_VALUE } from "../core/const";
|
||||
import ScalarValue from "./ScalarValue";
|
||||
|
||||
|
||||
it('supports bigint', () => {
|
||||
const int = new ScalarValue(1);
|
||||
const bigint = new ScalarValue(BigInt(1));
|
||||
expect(int.isBigInt()).toBe(false);
|
||||
expect(bigint.isBigInt()).toBe(true);
|
||||
expect(int.bitSize()).toBe(32);
|
||||
expect(bigint.bitSize()).toBe(64);
|
||||
it('converts numbers to bigint', () => {
|
||||
const int32 = new ScalarValue(INT32_MAX_VALUE);
|
||||
const int64 = new ScalarValue(BigInt(INT32_MAX_VALUE+1));
|
||||
|
||||
expect(int32.value.maxBitSize).toBe(32);
|
||||
expect(int64.value.maxBitSize).toBe(64);
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
import {numberParser} from './numberParser';
|
||||
import { ExpressionElement as ExpressionElement } from './expression-interfaces';
|
||||
import { NumberBase } from '../core/formatter';
|
||||
import { INT32_MAX_VALUE, INT32_MIN_VALUE, INT64_MAX_VALUE, INT64_MIN_VALUE } from '../core/const';
|
||||
import { BoundedNumber, JsNumber, isBoundedNumber, asBoundedNumber } from '../core/types';
|
||||
import calc from '../core/calc';
|
||||
import { INT32_MAX_VALUE, INT32_MIN_VALUE, INT64_MAX_VALUE, INT64_MIN_VALUE, UINT64_MAX_VALUE } from '../core/const';
|
||||
import { Integer, JsNumber, isInteger, asInteger } from '../core/Integer';
|
||||
|
||||
var globalId : number = 1;
|
||||
|
||||
@@ -11,38 +10,33 @@ var globalId : number = 1;
|
||||
// Represents scalar numeric value
|
||||
export default class ScalarValue implements ExpressionElement {
|
||||
id: number;
|
||||
value: JsNumber;
|
||||
value: Integer;
|
||||
base: NumberBase;
|
||||
isOperator: boolean;
|
||||
maxBitSize: number;
|
||||
label: string;
|
||||
|
||||
constructor(value : BoundedNumber | JsNumber, base?: NumberBase) {
|
||||
constructor(value : Integer | JsNumber, base?: NumberBase) {
|
||||
|
||||
if(!isBoundedNumber(value))
|
||||
value = asBoundedNumber(value);
|
||||
if(!isInteger(value))
|
||||
value = asInteger(value);
|
||||
|
||||
ScalarValue.validateSupported(value);
|
||||
|
||||
this.id = globalId++;
|
||||
this.value = 0;
|
||||
this.maxBitSize = 0;
|
||||
this.value = value as Integer;
|
||||
this.base = base || "dec";
|
||||
this.isOperator = false;
|
||||
|
||||
this.setValue(value);
|
||||
this.label = '';
|
||||
}
|
||||
|
||||
bitSize() : number {
|
||||
return this.isBigInt() ? 64 : 32;
|
||||
setValue(value : Integer) : ScalarValue {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
isBigInt() : boolean {
|
||||
return typeof this.value === 'bigint';
|
||||
}
|
||||
|
||||
setValue(value : BoundedNumber) {
|
||||
this.value = value.value;
|
||||
this.maxBitSize = value.maxBitSize;
|
||||
setLabel(label : string) : ScalarValue {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
evaluate() : ScalarValue {
|
||||
@@ -50,39 +44,17 @@ export default class ScalarValue implements ExpressionElement {
|
||||
}
|
||||
|
||||
getUnderlyingScalarOperand() : ScalarValue {
|
||||
return this
|
||||
return this;
|
||||
}
|
||||
|
||||
static validateSupported(num : BoundedNumber) {
|
||||
static validateSupported(num : Integer) {
|
||||
|
||||
if(typeof num.value == "bigint" && (num.value < INT64_MIN_VALUE || num.value > INT64_MAX_VALUE)) {
|
||||
throw new Error(`64-bit numbers are supported in range from ${INT64_MIN_VALUE} to ${INT64_MAX_VALUE}`);
|
||||
if(num.signed && (num.value < INT64_MIN_VALUE || num.value > INT64_MAX_VALUE)) {
|
||||
throw new Error(`Signed 64-bit numbers are supported in range from ${INT64_MIN_VALUE} to ${INT64_MAX_VALUE}. Given number was ${num}`);
|
||||
}
|
||||
|
||||
if(typeof num.value == "number" && (num.value < INT32_MIN_VALUE || num.value > INT32_MAX_VALUE)) {
|
||||
throw new Error(`Numer JavaScript type can only by used for numbers in range from ${INT32_MIN_VALUE} to ${INT32_MAX_VALUE}`)
|
||||
if(!num.signed && (num.value > UINT64_MAX_VALUE)) {
|
||||
throw new Error(`Unisgned 64-bit numbers larger than ${UINT64_MAX_VALUE} are not supported. Given number was ${num}`);
|
||||
}
|
||||
}
|
||||
|
||||
static parse(input: string) : ScalarValue {
|
||||
|
||||
var parsed = ScalarValue.tryParse(input);
|
||||
|
||||
if(parsed == null) {
|
||||
throw new Error(input + " is not a valid number");
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static tryParse(input: string) : ScalarValue | null {
|
||||
|
||||
var parsed = numberParser.parse(input);
|
||||
|
||||
if(!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ScalarValue(parsed.value, parsed.base);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
key={i}
|
||||
sign={itm.sign}
|
||||
css={itm.css}
|
||||
bitSize={itm.bitSize}
|
||||
bitSize={itm.maxBitSize}
|
||||
allowFlipBits={itm.allowFlipBits}
|
||||
expressionItem={itm.expression}
|
||||
emphasizeBytes={this.props.emphasizeBytes}
|
||||
@@ -88,7 +88,9 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
}
|
||||
render() {
|
||||
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
|
||||
const maxBits = Math.max()
|
||||
const scalar = this.props.expressionItem.evaluate();
|
||||
const bin = formatter.numberToString(scalar.value, 'bin').padStart(maxNumberOfBits, '0');
|
||||
const signBitIndex = scalar.value.signed && bin.length >= scalar.value.maxBitSize ? bin.length - scalar.value.maxBitSize : -1;
|
||||
|
||||
return <tr className={"row-with-bits " + css}>
|
||||
<td className="sign">{sign}</td>
|
||||
@@ -96,21 +98,16 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
<td className="bin">
|
||||
<BinaryStringView
|
||||
emphasizeBytes={emphasizeBytes}
|
||||
binaryString={formatter.padLeft(this.getBinaryString(), maxNumberOfBits, '0')}
|
||||
binaryString={bin}
|
||||
allowFlipBits={allowFlipBits}
|
||||
bitSize={this.props.bitSize}
|
||||
signBitIndex={signBitIndex}
|
||||
onFlipBit={args => this.flipBit(args)} />
|
||||
</td>
|
||||
<td className="other">{this.getAlternative()}</td>
|
||||
<td className="info" data-test-name='ignore'>{this.getInfo(maxNumberOfBits)}</td>
|
||||
<td className="info accent1" data-test-name='ignore'>{this.getInfo(maxNumberOfBits)}</td>
|
||||
</tr>;;
|
||||
}
|
||||
|
||||
getBinaryString(): string {
|
||||
var v = this.props.expressionItem.evaluate();
|
||||
return formatter.numberToString(v.value, 'bin');
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
|
||||
// For expressions like |~2
|
||||
@@ -146,12 +143,14 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
const op = this.props.expressionItem.getUnderlyingScalarOperand();
|
||||
const { bitIndex: index, binaryStringLength: totalLength } = args;
|
||||
|
||||
if(totalLength > op.bitSize() && (totalLength - index) > op.bitSize()) {
|
||||
op.setValue(calc.promoteTo64Bit(op.value as number));
|
||||
const maxBitSize = op.value.maxBitSize;
|
||||
const space = (totalLength - index - maxBitSize);
|
||||
if(totalLength > op.value.maxBitSize && space > 0) {
|
||||
op.setValue(calc.addSpace(op.value, space));
|
||||
}
|
||||
|
||||
const pad = op.bitSize() - totalLength;
|
||||
const newValue = calc.flipBit(op, pad + index);
|
||||
const pad = op.value.maxBitSize - totalLength;
|
||||
const newValue = calc.flipBit(op.value, pad + index);
|
||||
op.setValue(newValue);
|
||||
this.props.onBitFlipped();
|
||||
}
|
||||
@@ -159,18 +158,21 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
getInfo(maxNumberOfBits:number) {
|
||||
var op = this.props.expressionItem.getUnderlyingScalarOperand();
|
||||
|
||||
if (op.isBigInt())
|
||||
if((op.value.maxBitSize != 32 || op.value.maxBitSize <= maxNumberOfBits) || op.label.length > 0)
|
||||
{
|
||||
const title = `BigInt JavaScript type is used to reprsent this number. All bitwise operations that involve this number have their operands converted to BigInt. BitwiseCmd treats this number as 64-bit number.`;
|
||||
let title = `BitwiseCmd treats this number as ${op.value.maxBitSize}-bit integer`;
|
||||
let text = `${op.value.maxBitSize}-bit `;
|
||||
|
||||
return <span title={title} style={{cursor:"help"}}>(64-bit BigInt)</span>;
|
||||
if(!op.value.signed)
|
||||
text += " unsigned ";
|
||||
|
||||
if(op.label.length > 0)
|
||||
{
|
||||
text += " (converted)";
|
||||
title += ". This number was converted to facilitate bitwise operation with an operand of a different type";
|
||||
}
|
||||
|
||||
if(op.bitSize() == 32 && maxNumberOfBits >= 32)
|
||||
{
|
||||
const title = "BitwiseCmd treats this number as 32-bit integer. First bit is a sign bit. Try clicking on the first bit and see what will happen.";
|
||||
|
||||
return <span title={title} style={{cursor:"help"}}>(32-bit Number)</span>;
|
||||
return <span title={title} style={{cursor:"help"}}>{text}</span>;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ScalarValue, ListOfNumbersExpression, BitwiseOperationExpression, Bitwi
|
||||
import { ExpressionElement, Expression } from '../expression-interfaces';
|
||||
import calc from '../../core/calc';
|
||||
import formatter from '../../core/formatter';
|
||||
import exp from 'constants';
|
||||
|
||||
type Config = {
|
||||
emphasizeBytes: boolean;
|
||||
@@ -15,7 +14,7 @@ type ExpressionRowModel = {
|
||||
expression: ExpressionElement;
|
||||
allowFlipBits: boolean;
|
||||
label: string;
|
||||
bitSize: number;
|
||||
maxBitSize: number;
|
||||
}
|
||||
|
||||
export default class BitwiseResultViewModel {
|
||||
@@ -88,7 +87,7 @@ export default class BitwiseResultViewModel {
|
||||
expression: expr,
|
||||
allowFlipBits: this.allowFlipBits,
|
||||
label: '',
|
||||
bitSize: expr.bitSize(),
|
||||
maxBitSize: expr.value.maxBitSize,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -105,7 +104,7 @@ export default class BitwiseResultViewModel {
|
||||
label: this.getLabel(resultNumber),
|
||||
expression: expr.operand,
|
||||
allowFlipBits: this.allowFlipBits,
|
||||
bitSize: resultNumber.bitSize()
|
||||
maxBitSize: resultNumber.value.maxBitSize
|
||||
});
|
||||
};
|
||||
|
||||
@@ -119,7 +118,7 @@ export default class BitwiseResultViewModel {
|
||||
expression: resultExpr,
|
||||
allowFlipBits: false,
|
||||
label: '',
|
||||
bitSize: resultExpr.bitSize()
|
||||
maxBitSize: resultExpr.value.maxBitSize
|
||||
});
|
||||
};
|
||||
|
||||
@@ -132,7 +131,7 @@ export default class BitwiseResultViewModel {
|
||||
expression: expr,
|
||||
allowFlipBits: false,
|
||||
label: '',
|
||||
bitSize: expr.bitSize()
|
||||
maxBitSize: expr.value.maxBitSize
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import ScalarValue from "./ScalarValue";
|
||||
import engine from "./engine";
|
||||
|
||||
describe('evaluate', () => {
|
||||
|
||||
it('treats differently big ints and regular numbers', () => {
|
||||
const bigResult = engine.applyOperator(new ScalarValue(BigInt(2147483647)), "<<", new ScalarValue(BigInt(1)));
|
||||
const result = engine.applyOperator(new ScalarValue(2147483647), "<<", new ScalarValue(1));
|
||||
|
||||
expect(bigResult.value.toString()).toBe("4294967294");
|
||||
expect(result.value.toString()).toBe("-2");
|
||||
});
|
||||
|
||||
it("can execute all operators without errors", () => {
|
||||
|
||||
const operators = [">>", "<<", "|", "&", "^"];
|
||||
|
||||
// >>> not supported by BigInt
|
||||
expect(() => engine.applyOperator(new ScalarValue(1), ">>>", new ScalarValue(2))).not.toThrow();
|
||||
|
||||
operators.forEach(o => {
|
||||
expect(() => engine.applyOperator(new ScalarValue(BigInt(1)), o, new ScalarValue(BigInt(2)))).not.toThrow();
|
||||
expect(() => engine.applyOperator(new ScalarValue(1), o, new ScalarValue(2))).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('promotes either of operands to BigInt if the other one is', () => {
|
||||
const bint = new ScalarValue(BigInt(1));
|
||||
const int = new ScalarValue(1);
|
||||
|
||||
const rshift = engine.applyOperator(bint, ">>", int);
|
||||
expect(rshift.isBigInt()).toBe(true);
|
||||
expect(rshift.value.toString()).toBe('0');
|
||||
|
||||
const lshift = engine.applyOperator(int, "<<", bint);
|
||||
expect(lshift.isBigInt()).toBe(true);
|
||||
expect(lshift.value.toString()).toBe('2');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import calc from "../core/calc";
|
||||
import { JsNumber, asBoundedNumber } from "../core/types";
|
||||
import ScalarValue from "./ScalarValue";
|
||||
|
||||
const engine = {
|
||||
applyNotOperator(operand: ScalarValue) : ScalarValue {
|
||||
return new ScalarValue(~operand.value, operand.base);
|
||||
},
|
||||
applyOperator(op1 : ScalarValue, operator: string, op2 : ScalarValue) : ScalarValue {
|
||||
|
||||
const result = evalute(op1.value, operator, op2.value);
|
||||
|
||||
return new ScalarValue(result, op2.base);
|
||||
}
|
||||
};
|
||||
|
||||
function evalute(op1 : JsNumber, operator: string, op2 : JsNumber) : JsNumber{
|
||||
const o1 = equalizeType(op2, op1);
|
||||
const o2 = equalizeType(op1, op2);
|
||||
|
||||
const a = o1 as any;
|
||||
const b = o2 as any;
|
||||
|
||||
switch(operator) {
|
||||
case ">>": return (a >> b) as (JsNumber);
|
||||
case ">>>": return (a >>> b) as (JsNumber);
|
||||
case "<<": return calc.rshift(asBoundedNumber(o1), o2).value;
|
||||
case "&": return (b & a) as (JsNumber);
|
||||
case "|": return (b | a) as (JsNumber);
|
||||
case "^": return (b ^ a) as (JsNumber);
|
||||
default: throw new Error(operator + " operator is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
function equalizeType(source : JsNumber, dest : JsNumber) : JsNumber {
|
||||
|
||||
return typeof source == 'bigint' && typeof dest != 'bigint'
|
||||
? BigInt(dest)
|
||||
: dest;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default engine;
|
||||
@@ -1,15 +1,16 @@
|
||||
import { parser, ListOfNumbersExpression, BitwiseOperationExpression, ScalarValue, BitwiseOperator } from "./expression";
|
||||
import { random } from "../core/utils";
|
||||
import { INT32_MAX_VALUE } from "../core/const";
|
||||
|
||||
describe("expression parser", () => {
|
||||
|
||||
it("pares list of number expression", () => {
|
||||
it("parses list of number expression", () => {
|
||||
var result = parser.parse("1 2 3");
|
||||
expect(result).toBeInstanceOf(ListOfNumbersExpression);
|
||||
});
|
||||
|
||||
it("doesn't list of numbers in case of bad numbers", () => {
|
||||
expect(parser.parse("1 2 z")).toBeNull();
|
||||
//expect(parser.parse("-")).toBeNull();
|
||||
expect(parser.parse("")).toBeNull();
|
||||
});
|
||||
|
||||
@@ -38,7 +39,7 @@ describe("expression parser", () => {
|
||||
|
||||
expect(first).toBeInstanceOf(ScalarValue);
|
||||
|
||||
expect((first as ScalarValue).value).toBe(1);
|
||||
expect((first as ScalarValue).value.toString()).toBe("1");
|
||||
|
||||
expect(second).toBeInstanceOf(BitwiseOperator);
|
||||
var secondOp = second as BitwiseOperator;
|
||||
@@ -53,4 +54,122 @@ describe("expression parser", () => {
|
||||
var result = parser.parse("1|~2") as BitwiseOperationExpression;
|
||||
expect(result.children.length).toBe(2);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe("comparison with nodejs engine", () => {
|
||||
|
||||
it('set 32-bit', () => {
|
||||
|
||||
const inputs = [
|
||||
"1485578196>>14",
|
||||
"921979543<<31",
|
||||
"1123|324",
|
||||
"213&9531",
|
||||
"120^442161",
|
||||
"1<<7",
|
||||
"2>>>8",
|
||||
"2<<7"
|
||||
];
|
||||
|
||||
inputs.forEach(i => testBinary(i, i));
|
||||
});
|
||||
|
||||
it('random: two inbary strings 64-bit', () => {
|
||||
|
||||
const signs = ["|", "&", "^", "<<", ">>", ">>>"]
|
||||
|
||||
for(var i =0; i<1000; i++){
|
||||
|
||||
const sign = signs[random(0, signs.length-1)];
|
||||
const isShift = sign.length > 1;
|
||||
const op1 = random(-INT32_MAX_VALUE, INT32_MAX_VALUE);
|
||||
const op2 = isShift ? random(0, 31) : random(-INT32_MAX_VALUE, INT32_MAX_VALUE);
|
||||
|
||||
const input = op1.toString() + sign + op2.toString();
|
||||
|
||||
testBinary(input, input);
|
||||
}
|
||||
});
|
||||
|
||||
it('random: 64 and 32-bit', () => {
|
||||
|
||||
for(var i =0; i<1000; i++){
|
||||
|
||||
const num = random(-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
||||
const actualInput = "~" + num.toString();
|
||||
const expectedInput = num > INT32_MAX_VALUE ? `~BigInt("${num}")` : actualInput;
|
||||
const expected = eval(expectedInput).toString();
|
||||
|
||||
let actual = "";
|
||||
|
||||
try
|
||||
{
|
||||
const expr = parser.parse(actualInput) as BitwiseOperationExpression;
|
||||
const bo = (expr.children[0] as BitwiseOperator);
|
||||
const res = bo.evaluate();
|
||||
actual = res.value.toString();
|
||||
|
||||
if(actual != expected) {
|
||||
const uop = bo.getUnderlyingScalarOperand();
|
||||
console.log(`Expected:${expectedInput}\nActual:${actualInput}\n${uop.value} ${uop.value.maxBitSize}\n${res.value} ${typeof res.value} ${res.value.maxBitSize}`)
|
||||
}
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
|
||||
console.log(`Error:\nExpected:${expectedInput}\nActual:${actualInput}\n${typeof actualInput}`);
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('random: two inbary strings 64-bit', () => {
|
||||
|
||||
const signs = ["|", "&", "^"]
|
||||
|
||||
for(var i =0; i<1000; i++){
|
||||
const sign = signs[random(0, signs.length-1)];
|
||||
const isShift = sign.length > 1;
|
||||
const op1 = random(-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
||||
const op2 = isShift ? random(0, 63) : Number.MAX_SAFE_INTEGER;
|
||||
|
||||
const actualInput = `${op1}l${sign}${op2}l`;
|
||||
const expectedInput = `BigInt("${op1}")${sign}BigInt("${op2}")`;
|
||||
|
||||
testBinary(expectedInput, actualInput);
|
||||
}
|
||||
});
|
||||
|
||||
function testBinary(expectedInput:string, actualInput: string) {
|
||||
|
||||
const expected = eval(expectedInput).toString();
|
||||
|
||||
let actual = "";
|
||||
|
||||
try
|
||||
{
|
||||
var expr = parser.parse(actualInput) as BitwiseOperationExpression;
|
||||
|
||||
var op1 = expr.children[0] as ScalarValue;
|
||||
var op2 = expr.children[1] as BitwiseOperator;
|
||||
|
||||
actual = op2.evaluate(op1).value.toString();
|
||||
const equals = actual === expected;
|
||||
|
||||
if(!equals)
|
||||
{
|
||||
console.log(`Expected:${expectedInput}\n$Actual:${actualInput}\nop1:${typeof op1.value}\nop2:${typeof op2.getUnderlyingScalarOperand().value}`);
|
||||
}
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
console.log(`Error:\nExpected:${expectedInput}\nActual:${actualInput}\n${typeof actualInput}`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
}
|
||||
});
|
||||
@@ -3,6 +3,8 @@ import BitwiseOperator from './BitwiseOperator'
|
||||
import ListOfNumbersExpression from './ListOfNumbersExpression';
|
||||
import BitwiseOperationExpression from './BitwiseOperationExpression';
|
||||
import { Expression, ExpressionElement } from './expression-interfaces';
|
||||
import { numberParser, numberRegexString } from './numberParser';
|
||||
import { parse } from 'path';
|
||||
|
||||
export { default as ScalarValue } from './ScalarValue';
|
||||
export { default as BitwiseOperator } from './BitwiseOperator';
|
||||
@@ -16,6 +18,7 @@ interface IExpressionParserFactory {
|
||||
|
||||
class ExpressionParser {
|
||||
factories: IExpressionParserFactory[];
|
||||
|
||||
constructor() {
|
||||
this.factories = [];
|
||||
};
|
||||
@@ -36,6 +39,7 @@ class ExpressionParser {
|
||||
var i = 0, l = this.factories.length, factory;
|
||||
|
||||
for(;i<l;i++) {
|
||||
|
||||
factory = this.factories[i];
|
||||
|
||||
if(factory.canCreate(trimmed) == true){
|
||||
@@ -61,8 +65,8 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
||||
|
||||
return input.split(' ')
|
||||
.filter(p => p.length > 0)
|
||||
.map(p => ScalarValue.tryParse(p))
|
||||
.filter(n => n == null)
|
||||
.map(p => numberParser.caseParse(p))
|
||||
.filter(n => n == false)
|
||||
.length == 0;
|
||||
};
|
||||
|
||||
@@ -70,7 +74,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
||||
|
||||
const numbers = input.split(' ')
|
||||
.filter(p => p.length > 0)
|
||||
.map(m => ScalarValue.parse(m));
|
||||
.map(m => parseScalarValue(m));
|
||||
|
||||
return new ListOfNumbersExpression(input, numbers);
|
||||
}
|
||||
@@ -81,8 +85,8 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
regex: RegExp;
|
||||
|
||||
constructor() {
|
||||
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,l,L,a-f,0-9]+)))+$/;
|
||||
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,l,L,a-f,0-9]+))/g;
|
||||
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,l,s,u,a-f,0-9]+)))+$/i;
|
||||
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,l,s,u,,a-f,0-9]+))/gi;
|
||||
}
|
||||
|
||||
canCreate (input: string) : boolean {
|
||||
@@ -104,17 +108,19 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
return new BitwiseOperationExpression(normalizedString, operands)
|
||||
};
|
||||
|
||||
parseMatch (m:any): ExpressionElement {
|
||||
parseMatch (m:RegExpExecArray): ExpressionElement {
|
||||
|
||||
var input = m[0],
|
||||
operator = m[1],
|
||||
num = m[2];
|
||||
|
||||
var parsed = null;
|
||||
|
||||
if(num.indexOf('~') == 0) {
|
||||
parsed = new BitwiseOperator(ScalarValue.parse(num.substring(1)), '~');
|
||||
parsed = new BitwiseOperator(parseScalarValue(num.substring(1)), '~');
|
||||
}
|
||||
else {
|
||||
parsed = ScalarValue.parse(num);
|
||||
parsed = parseScalarValue(num);
|
||||
}
|
||||
|
||||
if(operator == null) {
|
||||
@@ -129,6 +135,13 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
};
|
||||
}
|
||||
|
||||
function parseScalarValue(input : string) : ScalarValue {
|
||||
const n = numberParser.parse(input);
|
||||
var sv = new ScalarValue(n.value, n.base);
|
||||
if(sv.value.maxBitSize != n.value.maxBitSize) throw new Error("Gotcha!");
|
||||
return sv;
|
||||
}
|
||||
|
||||
var parser = new ExpressionParser();
|
||||
parser.addFactory(new ListOfNumbersExpressionFactory());
|
||||
parser.addFactory(new BitwiseOperationExpressionFactory());
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import exp from 'constants';
|
||||
import { asIntN } from '../core/utils';
|
||||
import {numberParser, ParsedNumber} from './numberParser';
|
||||
|
||||
describe("parser", () => {
|
||||
@@ -7,12 +9,25 @@ describe("parser", () => {
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
var number = result as ParsedNumber;
|
||||
expect(number.value.value).toBe(10);
|
||||
expect(number.value.maxBitSize).toBe(32);
|
||||
expect(asIntN(number.value.num())).toBe(10);
|
||||
expect(number.base).toBe('dec');
|
||||
expect(number.input).toBe('10');
|
||||
});
|
||||
|
||||
it('parses bigint numbers', () => {
|
||||
it('parses negative numbers', () => {
|
||||
expect(numberParser.parse('-1')?.value.num()).toBe(-1);
|
||||
expect(numberParser.parse('-0b10')?.value.num()).toBe(-2);
|
||||
expect(numberParser.parse('-0x10')?.value.num()).toBe(-16);
|
||||
});
|
||||
|
||||
it('parses 64-bit numbers by size', () => {
|
||||
const dec = numberParser.parse('3433374389036042');
|
||||
expect(dec?.value.toString()).toBe('3433374389036042');
|
||||
expect(dec?.value.maxBitSize).toBe(64);
|
||||
});
|
||||
|
||||
it('parses 64-bit numbers with L notation', () => {
|
||||
const dec = numberParser.parse('10L');
|
||||
expect(dec).not.toBeNull();
|
||||
|
||||
@@ -20,25 +35,9 @@ describe("parser", () => {
|
||||
expect(typeof dec?.value.value).toBe("bigint");
|
||||
expect(dec?.base).toBe('dec');
|
||||
expect(dec?.input).toBe('10L');
|
||||
|
||||
const bin = numberParser.parse('0b10l');
|
||||
expect(bin).not.toBeNull();
|
||||
|
||||
expect(bin?.value.value).toBe(BigInt(2));
|
||||
expect(typeof bin?.value.value).toBe("bigint");
|
||||
expect(bin?.base).toBe('bin');
|
||||
expect(bin?.input).toBe('0b10l');
|
||||
|
||||
const hex = numberParser.parse('0xfL');
|
||||
expect(hex).not.toBeNull();
|
||||
|
||||
expect(hex?.value.value.toString()).toBe(BigInt(15).toString());
|
||||
expect(typeof hex?.value.value).toBe("bigint");
|
||||
expect(hex?.base).toBe('hex');
|
||||
expect(hex?.input).toBe('0xfL');
|
||||
expect(dec?.value.maxBitSize).toBe(64);
|
||||
});
|
||||
|
||||
|
||||
it('switches to bigint if value exceeds max safe int', () => {
|
||||
const unsafeInt = BigInt(Number.MAX_SAFE_INTEGER)+BigInt(1);
|
||||
|
||||
@@ -76,7 +75,8 @@ describe("parser", () => {
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
var number = result as ParsedNumber;
|
||||
expect(number.value.value).toBe(171);
|
||||
expect(number.value.maxBitSize).toBe(32);
|
||||
expect(number.value.num()).toBe(171);
|
||||
expect(number.base).toBe('hex');
|
||||
expect(number.input).toBe('0xab');
|
||||
});
|
||||
@@ -86,39 +86,82 @@ describe("parser", () => {
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
var number = result as ParsedNumber;
|
||||
expect(number.value.value).toBe(6);
|
||||
expect(number.value.num()).toBe(6);
|
||||
expect(number.base).toBe('bin');
|
||||
expect(number.input).toBe('0b0110');
|
||||
});
|
||||
|
||||
it('returns null on bad inputs', () => {
|
||||
expect(numberParser.parse('abc')).toBeNull();
|
||||
expect(numberParser.parse('')).toBeNull();
|
||||
expect(numberParser.caseParse('abc')).toBe(false);
|
||||
expect(numberParser.caseParse('')).toBe(false);
|
||||
expect(numberParser.caseParse('-1u')).toBe(true);
|
||||
expect(() => numberParser.parse('abc')).toThrowError("abc is not a number");
|
||||
expect(() => (numberParser.parse(''))).toThrowError('input is null or empty');
|
||||
});
|
||||
|
||||
it('parses big int', () => {
|
||||
var v = numberParser.parse('1l')?.value
|
||||
expect(typeof v?.value).toBe("bigint");
|
||||
expect(v?.value.toString()).toBe("1");
|
||||
expect(v?.num()).toBe(1);
|
||||
});
|
||||
|
||||
xit('parses single', () => {
|
||||
it('fits usigned int32 max value into 32-bit data type', () => {
|
||||
const n1 = numberParser.parse("4294967295u");
|
||||
const n2 = numberParser.parse("4294967296u");
|
||||
|
||||
expect(n1?.value.maxBitSize).toBe(32);
|
||||
expect(n2?.value.maxBitSize).toBe(64);
|
||||
expect(n1?.value.signed).toBe(false);
|
||||
expect(n2?.value.signed).toBe(false);
|
||||
})
|
||||
|
||||
it('parses single', () => {
|
||||
var v = numberParser.parse('1s')?.value
|
||||
expect(typeof v?.value).toBe("number");
|
||||
expect(v?.maxBitSize).toBe(16);
|
||||
expect(v?.value.toString()).toBe("1");
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(true);
|
||||
|
||||
var v2 = numberParser.parse('1i8')?.value
|
||||
expect(v2).toEqual(v);
|
||||
//var v2 = numberParser.parse('1i8')?.value
|
||||
//expect(v2).toEqual(v);
|
||||
});
|
||||
|
||||
xit('parses byte', () => {
|
||||
var v = numberParser.parse('1b')?.value
|
||||
expect(typeof v?.value).toBe("number");
|
||||
expect(v?.maxBitSize).toBe(16);
|
||||
expect(v?.value.toString()).toBe("1");
|
||||
it('cannot parse negative usigned', () => {
|
||||
expect(() => numberParser.parse('-1u')).toThrowError("-1u unsigned integer cannot be negative");
|
||||
});
|
||||
|
||||
var v2 = numberParser.parse('1i16')?.value
|
||||
expect(v2).toEqual(v);
|
||||
it('parses usigned single', () => {
|
||||
var v = numberParser.parse('1us')?.value
|
||||
expect(v?.maxBitSize).toBe(16);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses usigned int32', () => {
|
||||
var v = numberParser.parse('1u')?.value
|
||||
expect(v?.maxBitSize).toBe(32);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses usigned byte', () => {
|
||||
var v = numberParser.parse('1ub')?.value
|
||||
expect(v?.maxBitSize).toBe(8);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses usigned long', () => {
|
||||
var v = numberParser.parse('1ul')?.value
|
||||
expect(v?.maxBitSize).toBe(64);
|
||||
expect(v?.num()).toBe(1);
|
||||
expect(v?.signed).toBe(false);
|
||||
});
|
||||
|
||||
it('parses byte', () => {
|
||||
var v = numberParser.parse('1b')?.value
|
||||
expect(v?.maxBitSize).toBe(8);
|
||||
expect(v?.num()).toBe(1);
|
||||
|
||||
//var v2 = numberParser.parse('1i16')?.value
|
||||
//expect(v2?.num()).toEqual(v?.num());
|
||||
});
|
||||
});
|
||||
@@ -1,94 +1,83 @@
|
||||
import { INT32_MAX_VALUE, INT32_MIN_VALUE } from "../core/const";
|
||||
import { INT32_MAX_VALUE, UINT32_MAX_VALUE } from "../core/const";
|
||||
import { NumberBase } from "../core/formatter";
|
||||
import { BoundedNumber, asBoundedNumber } from "../core/types";
|
||||
import { Integer } from "../core/Integer";
|
||||
|
||||
// byte -i8 or b
|
||||
// single - i16 or s
|
||||
|
||||
const decimalRegex = /^-?\d+[l,L]?$/;
|
||||
const hexRegex = /^-?0x[0-9,a-f]+[l,L]?$/i;
|
||||
const binRegex = /^-?0b[0-1]+[l,L]?$/i;
|
||||
|
||||
interface ParserConfig {
|
||||
regex: RegExp,
|
||||
base: NumberBase,
|
||||
parse: (input: string) => BoundedNumber
|
||||
}
|
||||
const numberRegexString = "-?([0-9]+|0b[0-1]+|0x[0-9,a-f]+)(l|s|b|ul|us|ub|u)?";
|
||||
const numberRegexFullString = "^"+numberRegexString+"$"
|
||||
|
||||
export interface ParsedNumber {
|
||||
value: BoundedNumber;
|
||||
value: Integer;
|
||||
base: NumberBase;
|
||||
input: string;
|
||||
}
|
||||
|
||||
var knownParsers : ParserConfig[] = [
|
||||
{ regex: decimalRegex, base: 'dec', parse:(s) => parseIntSafe(s, 10) },
|
||||
{ regex: hexRegex, base: 'hex', parse:(s) => parseIntSafe(s, 16)},
|
||||
{ regex: binRegex, base: 'bin', parse:(s) => parseIntSafe(s, 2) }];
|
||||
|
||||
|
||||
class NumberParser {
|
||||
|
||||
parsers: ParserConfig[];
|
||||
numberRegexString: string;
|
||||
|
||||
constructor(parsers: ParserConfig[])
|
||||
constructor()
|
||||
{
|
||||
this.parsers = parsers;
|
||||
this.numberRegexString = numberRegexString;
|
||||
}
|
||||
|
||||
parse (input : string) : ParsedNumber | null {
|
||||
return this.parsers.map(p => this.applyParser(p, input)).reduce((c, n) => c || n);
|
||||
};
|
||||
|
||||
parseOperator (input: string) : string | null {
|
||||
var m = input.match(input);
|
||||
|
||||
if(m == null || m.length == 0) {
|
||||
return null;
|
||||
caseParse(input : string) {
|
||||
const regex = new RegExp(numberRegexFullString);
|
||||
return regex.test(input);
|
||||
}
|
||||
|
||||
return m[0];
|
||||
};
|
||||
parse (input : string) : ParsedNumber {
|
||||
|
||||
applyParser(parser : ParserConfig, rawInput: string) : ParsedNumber | null {
|
||||
if(input.length == 0) throw new Error("input is null or empty");
|
||||
|
||||
if(!parser.regex.test(rawInput)) {
|
||||
return null;
|
||||
}
|
||||
const regex = new RegExp(numberRegexFullString, "i");
|
||||
|
||||
var value = parser.parse(rawInput);
|
||||
const m = regex.exec(input);
|
||||
|
||||
if(m == null || m.length == 0)
|
||||
throw new Error(input + " is not a number");
|
||||
|
||||
const value = parseInteger(m[0], m[1], m[2] || '');
|
||||
|
||||
return {
|
||||
value: value,
|
||||
base: parser.base,
|
||||
input: rawInput
|
||||
}
|
||||
base: getBase(input),
|
||||
input: input
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const MAX_SAFE_INTn = BigInt(INT32_MAX_VALUE);
|
||||
const MIN_SAFE_INTn = BigInt(INT32_MIN_VALUE);
|
||||
function parseInteger(input : string, numberPart: string, suffix: string) : Integer {
|
||||
|
||||
function parseIntSafe(input : string, radix: number) : BoundedNumber {
|
||||
|
||||
const bigIntStr = input.replace('-', '').replace('l', '').replace('L', '');
|
||||
let bigInt = BigInt(bigIntStr);
|
||||
const isNegative = input.startsWith('-');
|
||||
const isBigInt = input.toLowerCase().endsWith('l');
|
||||
let num = BigInt(numberPart);
|
||||
const signed = !suffix.startsWith('u');
|
||||
|
||||
if(isNegative) bigInt *= BigInt(-1);
|
||||
if(!signed && isNegative)
|
||||
throw new Error(input + " unsigned integer cannot be negative");
|
||||
|
||||
if(isBigInt) return asBoundedNumber(bigInt);
|
||||
|
||||
if(bigInt > MAX_SAFE_INTn)
|
||||
return asBoundedNumber(bigInt);
|
||||
|
||||
if(bigInt < MIN_SAFE_INTn)
|
||||
return asBoundedNumber(bigInt);
|
||||
|
||||
return asBoundedNumber(parseInt(input.replace(/0(x|b)/, ''), radix));
|
||||
const size = getSizeBySuffix(suffix, num, signed);
|
||||
return new Integer(isNegative ? -num : num, size, signed);
|
||||
}
|
||||
|
||||
const numberParser = new NumberParser(knownParsers);
|
||||
function getSizeBySuffix(suffix: string, value : bigint, signed: boolean) {
|
||||
|
||||
export {numberParser};
|
||||
const max32 = signed ? INT32_MAX_VALUE : UINT32_MAX_VALUE;
|
||||
|
||||
switch(suffix.replace('u', '').toLowerCase()) {
|
||||
case 'l': return 64;
|
||||
case 's': return 16;
|
||||
case 'b': return 8;
|
||||
default: return value > max32 ? 64 : 32;
|
||||
}
|
||||
}
|
||||
|
||||
function getBase(input: string): NumberBase {
|
||||
|
||||
if(input.indexOf('0b') > -1) return 'bin';
|
||||
if(input.indexOf('0x') > -1) return 'hex';
|
||||
return 'dec';
|
||||
}
|
||||
|
||||
const numberParser = new NumberParser();
|
||||
|
||||
export {numberParser, numberRegexString};
|
||||
|
||||
@@ -5,7 +5,7 @@ html { height: 100% }
|
||||
font-family: Verdana;
|
||||
font-size: 0.8em;
|
||||
margin: 0;
|
||||
padding: 20px 100px 0 100px;
|
||||
padding: 0px 30px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
@@ -17,7 +17,17 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.header-cmd { color: #c5c5c5 }
|
||||
|
||||
.mono { font-family: monospace; font-size: 1.3em }
|
||||
.expressionInput { width: 500px; padding: 3px; border: none; outline: none; }
|
||||
.expressionInput {
|
||||
padding: 3px;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: solid 1px rgba(255, 255, 255, 0.5);
|
||||
z-index: 100;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 5px;
|
||||
width: 600px;
|
||||
background: ragba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.hidden { display: none;}
|
||||
|
||||
@@ -46,14 +56,11 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
|
||||
.hex .prefix { display: inline; }
|
||||
|
||||
.help { padding: 10px; }
|
||||
.help ul { list-style-type: none; margin: 0; padding: 0; }
|
||||
.help p { margin-top: 0.5em }
|
||||
|
||||
.indicator { padding: 0px 5px; background: transparent; border: none; cursor: pointer; vertical-align: middle; }
|
||||
.error { color: maroon; }
|
||||
|
||||
.soft { opacity: 0.7 }
|
||||
|
||||
.small-text { font-size: 0.8em;}
|
||||
|
||||
#view { padding: 10px}
|
||||
@@ -70,13 +77,15 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.light .on { color: #121212; }
|
||||
.light .prefix { color: #888}
|
||||
.light .other { color: #bbb }
|
||||
.light .hashLink, .light .hashLink:visited { color: #ddd; }
|
||||
.light .hashLink, .light .hashLink:visited { color: #aaa; }
|
||||
.light .hashLink:hover { color: #888 }
|
||||
.light ul.top-links li:hover { background: #ddd }
|
||||
.light .error { color: #da586d }
|
||||
.light button.btn { color: black}
|
||||
.light button.btn:hover { background: #ddd}
|
||||
.light button.btn:disabled { color: #888; background-color: inherit; }
|
||||
.light .accent1 { color:rgb(56, 161, 103)}
|
||||
.light .expressionInput { border-bottom: solid 1px rgba(0, 0, 0, 0.5);}
|
||||
|
||||
/* Dark */
|
||||
.dark { background: #121212; color: white;}
|
||||
@@ -87,7 +96,7 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.dark .on { color: white; }
|
||||
.dark .zero { color: #999;}
|
||||
.dark .prefix { color: #999}
|
||||
.dark .other { color: #444;}
|
||||
.dark .other { color: #777;}
|
||||
.dark .hashLink, .dark .hashLink:visited { color: #333 }
|
||||
.dark .hashLink:hover { color: #999 }
|
||||
.dark ul.top-links li:hover { background: #333 }
|
||||
@@ -95,6 +104,7 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.dark button.btn { color: white}
|
||||
.dark button.btn:hover { background: #333}
|
||||
.dark button.btn:disabled { color: #999; background-color: inherit; }
|
||||
.dark .accent1 { color:mediumseagreen}ч
|
||||
|
||||
/*
|
||||
Midnight Theme
|
||||
@@ -118,6 +128,7 @@ code { font-size: 1.2em; font-weight: bold; }
|
||||
.midnight button.btn { color: white}
|
||||
.midnight button.btn:hover { background: #132537}
|
||||
.midnight button.btn:disabled { color: #85a0ad; background-color: inherit; }
|
||||
.midnight .accent1 { color:mediumseagreen}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
@@ -151,3 +162,22 @@ button:focus {outline:0;}
|
||||
|
||||
.expressionInput { width: 350px; }
|
||||
}
|
||||
|
||||
|
||||
#output {
|
||||
padding: 30px 30px;
|
||||
}
|
||||
|
||||
.input-p {
|
||||
display:inline;
|
||||
z-index: 101;
|
||||
position: absolute;
|
||||
margin-right: -10px;
|
||||
margin-top: 3px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.debug-indicators {
|
||||
left: 1px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import cmd, { CommandInput } from './shell/cmd';
|
||||
import AppRoot from './shell/components/AppRoot';
|
||||
import log from 'loglevel';
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
.vpc-view .host-part {
|
||||
color: teal;
|
||||
color: darkcyan;
|
||||
}
|
||||
|
||||
.vpc-view .subnet-part {
|
||||
|
||||
@@ -4,7 +4,7 @@ function AboutResultView() {
|
||||
|
||||
return <div className="aboutTpl" data-result-type="help">
|
||||
<p> Created by <a href="http://boryslevytskyi.github.io/">Borys Levytskyi</a>. Please give it a like if BitwiseCmd has helped you in your work.</p>
|
||||
<p>If you have an idea, suggestion or you've spotted a bug here, please send it to <a href="mailto:bitwisecmd@gmail.com?subject=Feedback">bitwisecmd@gmail.com</a> or tweet on <a href="http://twitter.com/BitwiseCmd">@BitwiseCmd</a>. Your feedback is greatly appreciated.</p>
|
||||
<p>If you have an idea or, suggestion or you've spotted a bug here, please send it to <a href="mailto:bitwisecmd@gmail.com?subject=Feedback">bitwisecmd@gmail.com</a>. Your feedback is greatly appreciated.</p>
|
||||
<p><a href="https://github.com/BorisLevitskiy/BitwiseCmd">Project on <strong>GitHub</strong></a></p>
|
||||
</div>;
|
||||
};
|
||||
|
||||
@@ -49,7 +49,11 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className={`app-root ${this.state.uiTheme}`}>
|
||||
|
||||
const enableNewUi = this.props.appState.env != 'prod' || true;
|
||||
const newUi = enableNewUi ? 'new-ui' : '';
|
||||
|
||||
return <div className={`app-root ${this.state.uiTheme} ${newUi}`}>
|
||||
<DebugIndicators appState={this.props.appState} />
|
||||
<div className="header">
|
||||
<h1>Bitwise<span className="header-cmd">Cmd</span>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
.help .section {margin-bottom:10px;}
|
||||
.help ul { list-style-type: none; margin: 0; margin-top: 0.2em; padding: 0; }
|
||||
.help p { margin-top: 0.5em }
|
||||
.help .section {padding: 1em;}
|
||||
.help .panel-container {overflow: hidden;}
|
||||
.help .left-panel {float:left; margin-right: 20px;}
|
||||
.help .right-panel {float:left;}
|
||||
.help .left-panel {float:left; margin-right: 1em;}
|
||||
.help .right-panel {float:left; }
|
||||
.help .section-title {font-weight: bold;}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import CommandLink from '../../core/components/CommandLink';
|
||||
import './HelpResultView.css';
|
||||
import { INT32_MAX_VALUE, INT32_MIN_VALUE } from '../../core/const';
|
||||
|
||||
function HelpResultView() {
|
||||
|
||||
@@ -8,23 +9,23 @@ function HelpResultView() {
|
||||
<div className="panel-container">
|
||||
<div className="left-panel">
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Bitwise Calculation Commands</strong>
|
||||
<div className="section-title soft">Bitwise Calculation Commands</div>
|
||||
<ul>
|
||||
<li><code><CommandLink text="23 | 34" /></code> — type bitwise expression to see the result in binary</li>
|
||||
<li><code><CommandLink text="23 34" /></code> — type one or more numbers to see their binary representations</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">IP Address & Networking Commands</strong>
|
||||
<div className="section-title soft">IP Address & Networking Commands</div>
|
||||
<ul>
|
||||
<li><code><CommandLink text="127.0.0.1" /></code> — enter a single or multiple IP addresses (separated by space) to see their binary representation</li>
|
||||
<li><code><CommandLink text="127.0.0.1" /></code> — enter single or multiple IP addresses (separated by space) to see their binary representation</li>
|
||||
<li><code><CommandLink text="192.168.0.1/8" /></code> — subnet mask notations are supported as well</li>
|
||||
<li><code><CommandLink text="subnet 192.168.24.1/14" /></code> — display information about a subnet (network address, broadcast address, etc.)</li>
|
||||
<li><code><CommandLink text="vpc 192.168.24.1/24" /></code> — see how VPC network address bits are divided between VPC address, Subnets, and Hosts</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Color Theme Commands</strong>
|
||||
<div className="section-title soft">Color Theme Commands</div>
|
||||
<ul>
|
||||
<li><code><CommandLink text="light" /></code> — set the Light color theme</li>
|
||||
<li><code><CommandLink text="dark" /></code> — set the Dark color theme</li>
|
||||
@@ -32,7 +33,7 @@ function HelpResultView() {
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Other Commands</strong>
|
||||
<div className="section-title soft">Other Commands</div>
|
||||
<ul>
|
||||
<li><code><CommandLink text="clear" /></code> — clear output pane</li>
|
||||
<li><code><CommandLink text="help" /></code> — display this help</li>
|
||||
@@ -45,12 +46,7 @@ function HelpResultView() {
|
||||
</div>
|
||||
<div className="right-panel">
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Supported Bitwise Operations</strong><br />
|
||||
<small>
|
||||
<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators">
|
||||
as implemented in the JavaScript engine of your browser. If it cannot do it, BitwiseCmd cannot do it.
|
||||
</a>
|
||||
</small>
|
||||
<div className="section-title soft">Supported Bitwise Operations</div>
|
||||
<ul>
|
||||
<li><code>&</code> — bitwise AND</li>
|
||||
<li><code>|</code> — bitwise inclusive OR</li>
|
||||
@@ -61,10 +57,22 @@ function HelpResultView() {
|
||||
<li><code>>>></code> — zero-fill right shift</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section soft-border">
|
||||
<div className="section-title soft">Supported Number Types <sup className='accent1'>NEW</sup></div>
|
||||
<p>
|
||||
BitiwseCmd no longer uses the browser's JavaScript engine for the execution of bitwise operations. It has its own calculator implementation which brings supports bitwise operations on the following <i>signed</i> and <i>unsigned</i> data types:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>8-bit integer</code> - numbers entered with <code>b</code> or <code>ub</code> suffixes for signed and unsigned versions respectively (e.g. <CommandLink text='10b 10ub' />).</li>
|
||||
<li><code>64-bit integer</code> - numbers entered with <code>s</code> or <code>us</code> suffixes for signed and unsigned versions respectively (e.g. <CommandLink text='10s 10us' />).</li>
|
||||
<li><code>32-bit integer</code> - numbers entered without suffixes that fall in range of {INT32_MIN_VALUE} and {INT32_MAX_VALUE}. Use <code>u</code> suffix to denote an unsigned version of 32-bit integer. This is a default number type.</li>
|
||||
<li><code>64-bit integer</code> - numbers entered without suffixes and exceed the 32-bit range or entered with and and <code>ul</code> suffixes for signed and unsigned versions respectively (e.g. <CommandLink text='10l 10ul' />).</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="section">
|
||||
<strong className="section-title soft">Tip</strong>
|
||||
<p>
|
||||
You can click on bits to flip them in number inputs (e.g. <CommandLink text="2 4" />) or IP addresses (e.g. <CommandLink text="192.168.0.0/8" />)
|
||||
You can click on bits to flip them in number inputs (e.g. <CommandLink text="2 4" />) or IP addresses (e.g. <CommandLink text="192.168.0.0/8" />).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,13 +24,16 @@ export default class InputBox extends React.Component<IInputBoxProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input id="in" type="text"
|
||||
return <React.Fragment>
|
||||
<span className='input-p'>></span>
|
||||
<input id="in" type="text"
|
||||
ref={(input) => { this.nameInput = input; }}
|
||||
onKeyUp={e => this.onKeyUp(e)}
|
||||
onKeyDown={e => this.onKeyDown(e)}
|
||||
className="expressionInput mono"
|
||||
placeholder="type an expression like '1>>2' or 'help' "
|
||||
autoComplete="off"/>;
|
||||
autoComplete="off"/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
onKeyUp(e: any) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import { faEnvelope, faDonate } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faTwitter, faGithub} from "@fortawesome/free-brands-svg-icons";
|
||||
import { faGithub} from "@fortawesome/free-brands-svg-icons";
|
||||
import './TopLinks.css';
|
||||
import cmd from '../cmd';
|
||||
|
||||
@@ -16,9 +16,6 @@ function TopLinks() {
|
||||
<li>
|
||||
<a href="https://github.com/BorisLevitskiy/BitwiseCmd"><FontAwesomeIcon className="icon" icon={faGithub} size="lg" /><span className="link-text">github</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/BitwiseCmd"><FontAwesomeIcon className="icon" icon={faTwitter} size="lg" /><span className="link-text">twitter</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:bitwisecmd@gmail.com?subject=Feedback"><FontAwesomeIcon className="icon" icon={faEnvelope} size="lg" /><span className="link-text">idea or feedback</span></a>
|
||||
</li>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.changelog .item { margin-top: 2em; }
|
||||
.changelog .item-new .date { font-weight: bold; text-decoration: underline;}
|
||||
.changelog li { padding: 5px 0}
|
||||
@@ -8,48 +8,71 @@ function WhatsnewResultView() {
|
||||
<h3>Changelog</h3>
|
||||
<div className='item item-new'>
|
||||
<p>
|
||||
<span className="soft date">May 10th, 2023</span> <br/>
|
||||
<p>
|
||||
Behold! After a long time of inactivity, BitwiseCmd is getting an update. Here is what changed:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Browser's JavaScript engine is no longer used for the execution of bitwise operations.
|
||||
BitwiseCmd has its own shiny custom-built bitwise calculator that supports operations integer of different sizes (8,16,32, and 64 bits) as well as their signed and unsigned versions. <CommandLink text='Check it out!' command='-1b 255ub -1 4294967295u -1l 18446744073709551615u' />.
|
||||
This calculator tries to follow the same behavior of bitwise operations as implemented in C.
|
||||
This includes shifting an integer by the number of bytes equal to its size (spoiler: you get the same number, this is undefined behavior in C. Don't believe me? Check this <a href="https://codeyarns.com/tech/2004-12-20-c-shift-operator-mayhem.html#gsc.tab=0">link</a>).</li>
|
||||
<li>A slightly improved UI</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<div className='item'>
|
||||
<span className="soft date">May 5th, 2023</span> <br/>
|
||||
Fixed <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues/13">bug</a> with incorrect binary representation of 64 bit numbers.
|
||||
<p>
|
||||
Fixed <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues/13">bug</a> with incorrect binary representation of 64-bit numbers.
|
||||
</p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">Jul 24th, 2021</span> <br/>
|
||||
<ul>
|
||||
<li>Added support of <code>vpc</code> command to see hpw VPC network address is divided bettwen VPC, Subnets and Hosts. Try it out: <CommandLink text="vpc 192.168.24.1/24" /></li>
|
||||
<li>Added support of <code>vpc</code> command to see how VPC network address is divided between VPC, Subnets, and Hosts. Try it out: <CommandLink text="vpc 192.168.24.1/24" /></li>
|
||||
<li>Added ability to remove individual results</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">Jun 16th, 2021</span> <br/>
|
||||
Added support of <code>subnet</code> command to display information about subnet ip adress such. Try it out: <CommandLink text="subnet 192.168.24.1/14" />
|
||||
<span className="soft date">Jun 16th, 2021</span>
|
||||
<p>
|
||||
Added support of <code>subnet</code> command to display information about subnet IP address such. Try it out: <CommandLink text="subnet 192.168.24.1/14" />
|
||||
</p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">Jun 14th, 2021</span> <br/>
|
||||
Added support of ip addresses and subnet masks notatioans. Try them out:
|
||||
<span className="soft date">Jun 14th, 2021</span>
|
||||
<p>
|
||||
Added support of IP addresses and subnet mask notations. Try them out:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Single IP address <CommandLink text="127.0.0.1" /></li>
|
||||
<li>A single IP address <CommandLink text="127.0.0.1" /></li>
|
||||
<li>Multiple IP addresses and subnet mask notations <CommandLink text="127.0.0.1 192.168.0.0/24" /></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">Jun 6th, 2017</span> <br/>
|
||||
<span className="soft date">Jun 6th, 2017</span>
|
||||
<p>
|
||||
Added <code><CommandLink text="guid" /></code> command. Use it for generating v4 GUIDs </p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft date">May 27th, 2017</span> <br/>
|
||||
<span className="soft date">May 27th, 2017</span>
|
||||
<p>
|
||||
Added support of binary number notation (e.g. <code><CommandLink text="0b10101" /></code>). </p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft">May 20th, 2017</span> <br/>
|
||||
New <CommandLink text="Midnight" /> theme added. </p>
|
||||
<span className="soft">May 20th, 2017</span>
|
||||
<p>
|
||||
A new <CommandLink text="Midnight" /> theme was added.
|
||||
</p>
|
||||
</div>
|
||||
<div className="item">
|
||||
<p><span className="soft">May 16th, 2017</span> <br/>
|
||||
Complete rewrite using React. Old implementation is available at <a href="http://bitwisecmd.com/old">http://bitwisecmd.com/old</a>. Please let me know if you have problems with this release by <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues">creating issue</a> in Github Repo.</p>
|
||||
<span className="soft">May 16th, 2017</span>
|
||||
<p>
|
||||
Complete rewrite using React. Please let me know if you have problems with this release by <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues">creating an issue</a> in Github Repo.
|
||||
</p>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import hash from '../core/hash';
|
||||
import AppState from './AppState';
|
||||
import { Env } from './interfaces';
|
||||
import appStateStore from './appStateStore';
|
||||
import CommandLink from '../core/components/CommandLink';
|
||||
|
||||
export type StartupAppData = {
|
||||
appState: AppState,
|
||||
@@ -11,7 +10,7 @@ export type StartupAppData = {
|
||||
}
|
||||
|
||||
const STARTUP_COMMAND_KEY = 'StartupCommand';
|
||||
const DEFAULT_COMMANDS = ['help', '127.0.0.1 192.168.0.0/8', '-1 -1L', '1|2&6', '4 0b1000000 0x80'];
|
||||
const DEFAULT_COMMANDS = ['help', '127.0.0.1 192.168.0.1/8', '-1b -1s -1 -1l', '1|2&6', '4 0b1000000 0x80'];
|
||||
|
||||
function bootstrapAppData() : StartupAppData {
|
||||
const env = window.location.host === "bitwisecmd.com" ? 'prod' : 'stage';
|
||||
|
||||
Reference in New Issue
Block a user