Custom-built bitwise calculator, new number types, and improved UI (#46)

This commit is contained in:
Borys Levytskyi
2023-05-10 13:59:20 +03:00
committed by GitHub
parent 38af3721fd
commit 23748831ba
38 changed files with 1099 additions and 582 deletions

34
src/core/Integer.test.ts Normal file
View 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
View 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;
}

View File

@@ -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)}`);
}

View File

@@ -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);
*/

View File

@@ -1,3 +0,0 @@
.sign-bit {
color:mediumseagreen !important;
}

View File

@@ -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.'
}

View File

@@ -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};

View File

@@ -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', () => {

View File

@@ -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');
},

View File

@@ -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;
};

View File

@@ -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};

View File

@@ -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');
});

View File

@@ -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");
}

View File

@@ -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);
});

View File

@@ -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");
})

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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
});
};

View File

@@ -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');
});
});

View File

@@ -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;

View File

@@ -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);
}
});

View File

@@ -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());

View File

@@ -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());
});
});

View File

@@ -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};

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -12,7 +12,7 @@
}
.vpc-view .host-part {
color: teal;
color: darkcyan;
}
.vpc-view .subnet-part {

View File

@@ -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:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback">&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</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:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback">&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</a>. Your feedback is greatly appreciated.</p>
<p><a href="https://github.com/BorisLevitskiy/BitwiseCmd">Project on <strong>GitHub</strong></a></p>
</div>;
};

View File

@@ -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>

View File

@@ -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;}

View File

@@ -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>&amp;</code> bitwise AND</li>
<li><code>|</code> bitwise inclusive OR</li>
@@ -61,10 +57,22 @@ function HelpResultView() {
<li><code>&gt;&gt;&gt;</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>

View File

@@ -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'>&gt;</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) {

View File

@@ -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:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback"><FontAwesomeIcon className="icon" icon={faEnvelope} size="lg" /><span className="link-text">idea or feedback</span></a>
</li>

View File

@@ -1,2 +1,3 @@
.changelog .item { margin-top: 2em; }
.changelog .item-new .date { font-weight: bold; text-decoration: underline;}
.changelog li { padding: 5px 0}

View File

@@ -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>;
}

View File

@@ -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';