mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2025-12-12 07:52:09 +01:00
Refactor support for 32 an 64 bit numbers (#45)
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
import calc from './calc';
|
import calc from './calc';
|
||||||
import { BitwiseOperationExpression, ScalarToken, OperatorToken } from '../expression/expression';
|
import { BitwiseOperationExpression, ScalarValue, BitwiseOperator } from '../expression/expression';
|
||||||
|
import { INT32_MAX_VALUE } from './const';
|
||||||
import exp from 'constants';
|
import exp from 'constants';
|
||||||
import { INT_MAX_VALUE } from './const';
|
|
||||||
import formatter from './formatter';
|
|
||||||
|
|
||||||
describe("calc", () => {
|
describe("calc", () => {
|
||||||
it('calculates number of bits', () => {
|
it('calculates number of bits', () => {
|
||||||
expect(calc.numberOfBitsDisplayed(1)).toBe(1);
|
expect(calc.numberOfBitsDisplayed(1)).toBe(1);
|
||||||
|
expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(64);
|
||||||
expect(calc.numberOfBitsDisplayed(2)).toBe(2);
|
expect(calc.numberOfBitsDisplayed(2)).toBe(2);
|
||||||
expect(calc.numberOfBitsDisplayed(3)).toBe(2);
|
expect(calc.numberOfBitsDisplayed(3)).toBe(2);
|
||||||
expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36);
|
expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36);
|
||||||
expect(calc.numberOfBitsDisplayed(-INT_MAX_VALUE)).toBe(32);
|
expect(calc.numberOfBitsDisplayed(-INT32_MAX_VALUE)).toBe(32);
|
||||||
expect(calc.numberOfBitsDisplayed(-(INT_MAX_VALUE+1))).toBe(64);
|
expect(calc.numberOfBitsDisplayed(-(BigInt(INT32_MAX_VALUE+1)))).toBe(64);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calculates max number of bits', () => {
|
it('calculates max number of bits', () => {
|
||||||
@@ -23,20 +23,60 @@ describe("calc", () => {
|
|||||||
var result = calc.calcExpression(new BitwiseOperationExpression(
|
var result = calc.calcExpression(new BitwiseOperationExpression(
|
||||||
"1|2&3",
|
"1|2&3",
|
||||||
[
|
[
|
||||||
new ScalarToken(1),
|
new ScalarValue(1),
|
||||||
new OperatorToken(new ScalarToken(2), "|"),
|
new BitwiseOperator(new ScalarValue(2), "|"),
|
||||||
new OperatorToken(new ScalarToken(3), "&"),
|
new BitwiseOperator(new ScalarValue(3), "&"),
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(result).toBe(3);
|
expect(result).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calculates flipped bit 32-bit number', () => {
|
||||||
|
expect(calc.flipBit(0, 31)).toBe(1);
|
||||||
|
expect(calc.flipBit(1, 31)).toBe(0);
|
||||||
|
expect(calc.flipBit(-1, 31)).toBe(-2);
|
||||||
|
expect(calc.flipBit(2147483647, 0)).toBe(-1);
|
||||||
|
expect(calc.flipBit(-1, 0)).toBe(2147483647);
|
||||||
|
expect(calc.flipBit(2147483647, 30)).toBe(2147483645);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('caulate flipped bit 64-bit nubmer', () => {
|
||||||
|
const int64max = BigInt("9223372036854775807");
|
||||||
|
expect(calc.flipBit(BigInt(int64max), 0)).toBe(BigInt(-1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates flipped bit', () => {
|
||||||
|
expect(calc.flipBit(0, 31)).toBe(1);
|
||||||
|
expect(calc.flipBit(1, 31)).toBe(0);
|
||||||
|
expect(calc.flipBit(-1, 31)).toBe(-2);
|
||||||
|
expect(calc.flipBit(2147483647, 0)).toBe(-1);
|
||||||
|
expect(calc.flipBit(-1, 0)).toBe(2147483647);
|
||||||
|
expect(calc.flipBit(2147483647, 30)).toBe(2147483645);
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calcualte 31th bit in 64-bit int', () => {
|
||||||
|
expect(calc.flipBit(calc.promoteToBigInt(-1), 31).toString()).toBe("8589934591");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('promotes to BigInt with the same bits', () => {
|
||||||
|
expect(calc.promoteToBigInt(-1).toString(2)).toBe("11111111111111111111111111111111");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("binary ", () => {
|
describe("bitwise ", () => {
|
||||||
|
|
||||||
|
|
||||||
it("bitwise NOT same as in node", () => {
|
it("NOT same as in node", () => {
|
||||||
|
|
||||||
for(var i = -100; i<100;i++) {
|
for(var i = -100; i<100;i++) {
|
||||||
const expected = bin(~i);
|
const expected = bin(~i);
|
||||||
@@ -45,7 +85,7 @@ describe("binary ", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bitwise OR same as in node", () => {
|
it("OR same as in node", () => {
|
||||||
for(var x = -100; x<100;x++) {
|
for(var x = -100; x<100;x++) {
|
||||||
const y = 5+3%x+x%6*(-x);
|
const y = 5+3%x+x%6*(-x);
|
||||||
const expected = bin(x | y);
|
const expected = bin(x | y);
|
||||||
@@ -55,7 +95,7 @@ describe("binary ", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bitwise AND same as in node", () => {
|
it("AND same as in node", () => {
|
||||||
for(var x = -100; x<100;x++) {
|
for(var x = -100; x<100;x++) {
|
||||||
const y = 5+3%x+x%6*(-x);
|
const y = 5+3%x+x%6*(-x);
|
||||||
const expected = bin(x & y);
|
const expected = bin(x & y);
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { dblClick } from "@testing-library/user-event/dist/click";
|
|
||||||
import { Expression } from "../expression/expression-interfaces";
|
import { Expression } from "../expression/expression-interfaces";
|
||||||
import { INT_MAX_VALUE } from "./const";
|
import formatter from "./formatter";
|
||||||
import { start } from "repl";
|
import { NumberType } from "./types";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
numberOfBitsDisplayed: function (num: number) : number {
|
abs (num : NumberType) : NumberType {
|
||||||
|
return num >= 0 ? num : -num;
|
||||||
|
},
|
||||||
|
numberOfBitsDisplayed: function (num: number|bigint) : number {
|
||||||
|
|
||||||
if(num < 0) {
|
if(num < 0) {
|
||||||
return Math.abs(num) <= INT_MAX_VALUE ? 32 : 64;
|
return typeof num == 'bigint' ? 64 : 32
|
||||||
}
|
};
|
||||||
|
|
||||||
return Math.floor(Math.log(num) / Math.log(2)) + 1;
|
return num.toString(2).length;
|
||||||
},
|
},
|
||||||
|
|
||||||
maxNumberOfBitsDisplayed: function (arr: number[]) {
|
maxNumberOfBitsDisplayed: function (arr: number[]) {
|
||||||
@@ -27,6 +30,59 @@ export default {
|
|||||||
return eval(expr.expressionString);
|
return eval(expr.expressionString);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
flipBit: function(num: number|bigint, index: number): number|bigint {
|
||||||
|
|
||||||
|
const is64bit = typeof num == 'bigint';
|
||||||
|
const size = typeof num == "bigint" ? 64 : 32;
|
||||||
|
const bin = formatter.bin(num).padStart(size, '0');
|
||||||
|
const staysNegative = (bin[0] == "1" && index > 0);
|
||||||
|
const becomesNegative = (bin[0] == "0" && index == 0);
|
||||||
|
|
||||||
|
//console.log(bin);
|
||||||
|
|
||||||
|
let m = 1;
|
||||||
|
let flipped = bin.substring(0, index) + flip(bin[index]) + bin.substring(index+1);
|
||||||
|
|
||||||
|
//console.log(flipped);
|
||||||
|
|
||||||
|
if(staysNegative || becomesNegative) {
|
||||||
|
flipped = this.applyTwosComplement(flipped);
|
||||||
|
m=-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(flipped);
|
||||||
|
|
||||||
|
return is64bit ? BigInt("0b"+ flipped)*BigInt(m) : parseInt(flipped, 2)*m;
|
||||||
|
},
|
||||||
|
|
||||||
|
promoteToBigInt(number: number) {
|
||||||
|
const bin = formatter.bin(number);
|
||||||
|
return BigInt("0b" + bin);
|
||||||
|
},
|
||||||
|
|
||||||
|
applyTwosComplement: (bin:string):string => {
|
||||||
|
var lastIndex = bin.lastIndexOf('1');
|
||||||
|
|
||||||
|
// If there exists no '1' concat 1 at the
|
||||||
|
// starting of string
|
||||||
|
if (lastIndex == -1)
|
||||||
|
return "1" + bin;
|
||||||
|
|
||||||
|
// Continue traversal backward after the position of
|
||||||
|
// first '1'
|
||||||
|
var flipped =[];
|
||||||
|
for (var i = lastIndex - 1; i >= 0; i--) {
|
||||||
|
// Just flip the values
|
||||||
|
flipped.unshift(bin.charAt(i) == "1" ? "0" : "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return flipped.join('') + bin.substring(lastIndex) ;
|
||||||
|
},
|
||||||
|
|
||||||
|
flipAllBits: (bin: string): string => {
|
||||||
|
return bin.split('').map(b => b=="1"?"0":"1").join("");
|
||||||
|
},
|
||||||
|
|
||||||
bitwise: {
|
bitwise: {
|
||||||
not: (bin: string) : string => {
|
not: (bin: string) : string => {
|
||||||
|
|
||||||
@@ -62,10 +118,9 @@ export default {
|
|||||||
|
|
||||||
return result.join('');
|
return result.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function flip(bit:string) {
|
function flip(bit:string):string {
|
||||||
return bit === "0" ? "1" : "0";
|
return bit === "0" ? "1" : "0";
|
||||||
}
|
}
|
||||||
3
src/core/components/BinaryString.css
Normal file
3
src/core/components/BinaryString.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.sign-bit {
|
||||||
|
color:mediumseagreen !important;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import './BinaryString.css';
|
||||||
|
|
||||||
export type BinaryStringViewProps = {
|
export type BinaryStringViewProps = {
|
||||||
allowFlipBits?: boolean;
|
allowFlipBits?: boolean;
|
||||||
@@ -6,12 +7,13 @@ export type BinaryStringViewProps = {
|
|||||||
onFlipBit?: (input: FlipBitEventArg) => void;
|
onFlipBit?: (input: FlipBitEventArg) => void;
|
||||||
emphasizeBytes?: boolean;
|
emphasizeBytes?: boolean;
|
||||||
className?:string;
|
className?:string;
|
||||||
disableHighlight?:boolean
|
disableHighlight?:boolean,
|
||||||
|
bitSize?: number
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlipBitEventArg = {
|
export type FlipBitEventArg = {
|
||||||
index: number;
|
bitIndex: number;
|
||||||
binaryString: string;
|
binaryStringLength: number;
|
||||||
$event: any;
|
$event: any;
|
||||||
newBinaryString: string
|
newBinaryString: string
|
||||||
};
|
};
|
||||||
@@ -26,19 +28,15 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.props.onFlipBit) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const arr = this.props.binaryString.split('');
|
const arr = this.props.binaryString.split('');
|
||||||
arr[index] = arr[index] == '0' ? '1' : '0';
|
arr[index] = arr[index] == '0' ? '1' : '0';
|
||||||
const newBinaryString = arr.join('');
|
const newBinaryString = arr.join('');
|
||||||
|
|
||||||
this.props.onFlipBit({ index: index, binaryString: this.props.binaryString, $event: e, newBinaryString });
|
this.props.onFlipBit({ bitIndex: index, binaryStringLength: this.props.binaryString.length, $event: e, newBinaryString });
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildren() {
|
getChildren() {
|
||||||
var bits = this.createBits(this.props.binaryString.split(''));
|
var bits = this.createBits(this.props.binaryString.split(''), this.props.bitSize);
|
||||||
|
|
||||||
if(this.props.emphasizeBytes) {
|
if(this.props.emphasizeBytes) {
|
||||||
return this.splitIntoBytes(bits);
|
return this.splitIntoBytes(bits);
|
||||||
@@ -47,20 +45,34 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
|||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
createBits(bitChars:string[]) : JSX.Element[] {
|
createBits(bitChars:string[], bitSize?: number) : JSX.Element[] {
|
||||||
const allowFlipBits = this.props.allowFlipBits || false;
|
const allowFlipBits = this.props.allowFlipBits || false;
|
||||||
const css = allowFlipBits ? ' flipable' : ''
|
const css = allowFlipBits ? ' flipable' : ''
|
||||||
|
|
||||||
const disableHighlight = this.props.disableHighlight || false;
|
const disableHighlight = this.props.disableHighlight || false;
|
||||||
|
|
||||||
|
let signBitIndex = -1;
|
||||||
|
|
||||||
|
if(bitChars.length === bitSize)
|
||||||
|
signBitIndex = 0;
|
||||||
|
|
||||||
|
if(bitSize != null && bitChars.length > bitSize)
|
||||||
|
signBitIndex = bitChars.length - bitSize!;
|
||||||
|
|
||||||
return bitChars.map((c, i) => {
|
return bitChars.map((c, i) => {
|
||||||
|
|
||||||
var className = c == '1' ? `one${css}` : `zero${css}`;
|
var className = c == '1' ? `one${css}` : `zero${css}`;
|
||||||
|
var tooltip = '';
|
||||||
|
|
||||||
|
if(i === signBitIndex) {
|
||||||
|
className += ' sign-bit';
|
||||||
|
tooltip = 'Signature bit. 0 means a positive number and 1 means a negative.'
|
||||||
|
}
|
||||||
|
|
||||||
if(disableHighlight)
|
if(disableHighlight)
|
||||||
className = css;
|
className = css;
|
||||||
|
|
||||||
return <span className={className} key={i} onClick={e => this.onBitClick(i, e)}>{c}</span>
|
return <span className={className} title={tooltip} key={i} onClick={e => this.onBitClick(i, e)}>{c}</span>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
const INT_MAX_VALUE = 2147483647;
|
const INT32_MAX_VALUE = 2147483647;
|
||||||
export {INT_MAX_VALUE};
|
const INT32_MIN_VALUE = -INT32_MAX_VALUE;
|
||||||
|
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};
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ describe("formatter", () => {
|
|||||||
expect(formatter.numberToString(15, "bin")).toBe("1111");
|
expect(formatter.numberToString(15, "bin")).toBe("1111");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('respects size when formatting negative number', () => {
|
||||||
|
expect(formatter.bin(-1)).toBe("11111111111111111111111111111111");
|
||||||
|
expect(formatter.bin(BigInt(-1))).toBe("1111111111111111111111111111111111111111111111111111111111111111");
|
||||||
|
});
|
||||||
|
|
||||||
it('formats large binary number correctly', () => {
|
it('formats large binary number correctly', () => {
|
||||||
var decimal = 68719476735;
|
var decimal = 68719476735;
|
||||||
var binary = formatter.bin(68719476735);
|
var binary = formatter.bin(68719476735);
|
||||||
@@ -16,8 +21,8 @@ describe("formatter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('formats negative binary numbers', () => {
|
it('formats negative binary numbers', () => {
|
||||||
expect(formatter.numberToString(-1, 'bin')).toBe("11111111111111111111111111111111");
|
//expect(formatter.numberToString(-1, 'bin')).toBe("11111111111111111111111111111111");
|
||||||
expect(formatter.numberToString(-0, 'bin')).toBe("0");
|
//expect(formatter.numberToString(-0, 'bin')).toBe("0");
|
||||||
expect(formatter.numberToString(-2147483647, 'bin')).toBe("10000000000000000000000000000001");
|
expect(formatter.numberToString(-2147483647, 'bin')).toBe("10000000000000000000000000000001");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import { INT_MAX_VALUE } from "./const";
|
import calc from "./calc";
|
||||||
|
import { NumberType } from "./types";
|
||||||
export type NumberBase = 'dec' | 'hex' | 'bin';
|
export type NumberBase = 'dec' | 'hex' | 'bin';
|
||||||
|
|
||||||
const formatter = {
|
const formatter = {
|
||||||
numberToString: function(value: number, kind: NumberBase) : string {
|
numberToString: function(num: number|bigint, base: NumberBase) : string {
|
||||||
|
|
||||||
switch(kind) {
|
switch(base) {
|
||||||
case 'hex':
|
case 'hex':
|
||||||
var hexVal = Math.abs(value).toString(16);
|
var hexVal = calc.abs(num).toString(16);
|
||||||
return value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
|
return num >= 0 ? '0x' + hexVal : '-0x' + hexVal;
|
||||||
case 'bin':
|
case 'bin':
|
||||||
if(value < 0) {
|
|
||||||
const n = Math.abs(value);
|
if(num < 0) {
|
||||||
const padding = n > INT_MAX_VALUE ? 64 : 32;
|
const size = calc.numberOfBitsDisplayed(num);
|
||||||
const pos = n.toString(2).padStart(padding, '0');
|
const absBin = calc.abs(num).toString(2).padStart(size, '0');
|
||||||
return findTwosComplement(pos);
|
return calc.applyTwosComplement(absBin);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.toString(getBase(kind || "bin"));
|
return num.toString(2);
|
||||||
case 'dec':
|
case 'dec':
|
||||||
return value.toString(10);
|
return num.toString(10);
|
||||||
default:
|
default:
|
||||||
throw new Error("Unexpected kind: " + kind)
|
throw new Error("Unexpected kind: " + base)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
padLeft: function (str: string, length: number, symbol: string) : string {
|
padLeft: function (str: string, length: number, symbol: string) : string {
|
||||||
@@ -36,10 +37,10 @@ const formatter = {
|
|||||||
|
|
||||||
return sb.join('');
|
return sb.join('');
|
||||||
},
|
},
|
||||||
bin(number: number) {
|
bin(number: NumberType) {
|
||||||
return this.numberToString(number, 'bin');
|
return this.numberToString(number, 'bin');
|
||||||
},
|
},
|
||||||
emBin(number: number) {
|
emBin(number: NumberType) {
|
||||||
return this.padLeft(this.bin(number), 8, '0');
|
return this.padLeft(this.bin(number), 8, '0');
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -92,43 +93,6 @@ function getBase(kind:string) : number {
|
|||||||
throw new Error("Unsupported kind: " + kind);
|
throw new Error("Unsupported kind: " + kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
function flip(bit: string) : string {
|
|
||||||
switch(bit) {
|
|
||||||
case "1": return "0";
|
|
||||||
case "0": return "1";
|
|
||||||
default: throw new Error("unexpected bit value: " + bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findTwosComplement(str:string):string {
|
|
||||||
var n = str.length;
|
|
||||||
|
|
||||||
// Traverse the string to get first '1' from
|
|
||||||
// the last of string
|
|
||||||
var i;
|
|
||||||
for (i = n - 1; i >= 0; i--)
|
|
||||||
if (str.charAt(i) == '1')
|
|
||||||
break;
|
|
||||||
|
|
||||||
// If there exists no '1' concat 1 at the
|
|
||||||
// starting of string
|
|
||||||
if (i == -1)
|
|
||||||
return "1" + str;
|
|
||||||
|
|
||||||
// Continue traversal after the position of
|
|
||||||
// first '1'
|
|
||||||
for (var k = i - 1; k >= 0; k--) {
|
|
||||||
// Just flip the values
|
|
||||||
if (str.charAt(k) == '1')
|
|
||||||
str = str.substring(0,k)+"0"+str.substring(k+1, str.length);
|
|
||||||
else
|
|
||||||
str = str.substring(0,k)+"1"+str.substring(k+1, str.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the modified string
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const emBin = formatter.emBin.bind(formatter);
|
const emBin = formatter.emBin.bind(formatter);
|
||||||
const padLeft = formatter.padLeft.bind(formatter);
|
const padLeft = formatter.padLeft.bind(formatter);
|
||||||
|
|
||||||
|
|||||||
1
src/core/types.ts
Normal file
1
src/core/types.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type NumberType = number | bigint;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Expression, ExpressionToken } from "./expression-interfaces";
|
import { Expression, ExpressionElement } from "./expression-interfaces";
|
||||||
|
|
||||||
export default class BitwiseOperationExpression implements Expression {
|
export default class BitwiseOperationExpression implements Expression {
|
||||||
|
|
||||||
expressionString: string;
|
expressionString: string;
|
||||||
children: ExpressionToken[];
|
children: ExpressionElement[];
|
||||||
|
|
||||||
constructor(expressionString: string, children: ExpressionToken[]) {
|
constructor(expressionString: string, children: ExpressionElement[]) {
|
||||||
this.expressionString = expressionString;
|
this.expressionString = expressionString;
|
||||||
this.children = children;
|
this.children = children;
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/expression/BitwiseOperator.test.ts
Normal file
21
src/expression/BitwiseOperator.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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.base).toBe('dec');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can apply & operand', () => {
|
||||||
|
var op1 = new ScalarValue(3, 'dec');
|
||||||
|
var op2 = new ScalarValue(4, 'dec');
|
||||||
|
var expr = new BitwiseOperator(op1, "|");
|
||||||
|
var result = expr.evaluate(op2);
|
||||||
|
expect(result.value).toBe(7);
|
||||||
|
expect(result.base).toBe('dec');
|
||||||
|
});
|
||||||
43
src/expression/BitwiseOperator.ts
Normal file
43
src/expression/BitwiseOperator.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import ScalarValue from './ScalarValue';
|
||||||
|
import engine from './engine';
|
||||||
|
import { ExpressionElement } from './expression-interfaces';
|
||||||
|
|
||||||
|
export default class BitwiseOperator implements ExpressionElement {
|
||||||
|
operand: ExpressionElement;
|
||||||
|
operator: string;
|
||||||
|
isOperator: boolean;
|
||||||
|
isShiftExpression: boolean;
|
||||||
|
isNotExpression: boolean;
|
||||||
|
|
||||||
|
constructor(operand : ExpressionElement, operator : string) {
|
||||||
|
|
||||||
|
this.operand = operand;
|
||||||
|
this.operator = operator;
|
||||||
|
this.isOperator = true;
|
||||||
|
this.isShiftExpression = this.operator.indexOf('<') >= 0 || this.operator.indexOf('>')>= 0;
|
||||||
|
this.isNotExpression = this.operator === '~';
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate(operand?: ScalarValue) : ScalarValue {
|
||||||
|
|
||||||
|
if (operand instanceof BitwiseOperator)
|
||||||
|
throw new Error('operand must be scalar value');
|
||||||
|
|
||||||
|
if( this.operator != "~" && operand == null)
|
||||||
|
throw new Error("operand is required");
|
||||||
|
|
||||||
|
var evaluatedOperand = this.operand.evaluate();
|
||||||
|
|
||||||
|
return this.operator == "~"
|
||||||
|
? engine.applyNotOperator(this.operand.getUnderlyingScalarOperand())
|
||||||
|
: engine.applyOperator(operand!, this.operator, evaluatedOperand);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnderlyingScalarOperand() : ScalarValue {
|
||||||
|
return this.operand.getUnderlyingScalarOperand();
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.operator + this.operand.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import OperatorToken from "./OperatorToken";
|
import BitwiseOperator from "./BitwiseOperator";
|
||||||
import { ScalarToken } from "./expression";
|
import { ScalarValue } from "./expression";
|
||||||
import { ExpressionToken } from "./expression-interfaces";
|
import { ExpressionElement } from "./expression-interfaces";
|
||||||
import { type } from "os";
|
import { type } from "os";
|
||||||
import { InputType } from "zlib";
|
import { InputType } from "zlib";
|
||||||
import exp from "constants";
|
import exp from "constants";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import ScalarToken from "./ScalarToken";
|
import ScalarValue from "./ScalarValue";
|
||||||
import ListOfNumbersExpression from "./ListOfNumbersExpression";
|
import ListOfNumbersExpression from "./ListOfNumbersExpression";
|
||||||
|
|
||||||
it('calculates max bits length', () => {
|
it('calculates max bits length', () => {
|
||||||
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [ScalarToken.parse("10"), ScalarToken.parse("0xabef"), ScalarToken.parse("0b01010")])
|
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [ScalarValue.parse("10"), ScalarValue.parse("0xabef"), ScalarValue.parse("0b01010")])
|
||||||
expect(expr.maxBitsLength).toBe(16);
|
expect(expr.maxBitsLength).toBe(16);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import calc from "../core/calc";
|
import calc from "../core/calc";
|
||||||
import ScalarToken from "./ScalarToken";
|
import ScalarValue from "./ScalarValue";
|
||||||
import { Expression, ExpressionToken } from "./expression-interfaces";
|
import { Expression, ExpressionElement } from "./expression-interfaces";
|
||||||
|
|
||||||
export default class ListOfNumbersExpression implements Expression {
|
export default class ListOfNumbersExpression implements Expression {
|
||||||
children: ScalarToken[];
|
children: ScalarValue[];
|
||||||
expressionString: string;
|
expressionString: string;
|
||||||
maxBitsLength: number;
|
maxBitsLength: number;
|
||||||
|
|
||||||
constructor(expressionString: string, numbers: ScalarToken[]) {
|
constructor(expressionString: string, numbers: ScalarValue[]) {
|
||||||
this.expressionString = expressionString;
|
this.expressionString = expressionString;
|
||||||
this.children = numbers;
|
this.children = numbers;
|
||||||
this.maxBitsLength = numbers.map(n => calc.numberOfBitsDisplayed(n.value)).reduce((n , c) => n >= c ? n : c, 0);
|
this.maxBitsLength = numbers.map(n => calc.numberOfBitsDisplayed(n.value)).reduce((n , c) => n >= c ? n : c, 0);
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import ScalarToken from "./ScalarToken";
|
|
||||||
import OperatorToken from './OperatorToken';
|
|
||||||
import { INT_MAX_VALUE } from "../core/const";
|
|
||||||
|
|
||||||
it('can apply ~ operand', () => {
|
|
||||||
var op = new ScalarToken(10, 'dec');
|
|
||||||
var expr = new OperatorToken(op, "~");
|
|
||||||
var result = expr.evaluate();
|
|
||||||
expect(result.value).toBe(-11);
|
|
||||||
expect(result.base).toBe('dec');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can apply & operand', () => {
|
|
||||||
var op1 = new ScalarToken(3, 'dec');
|
|
||||||
var op2 = new ScalarToken(4, 'dec');
|
|
||||||
var expr = new OperatorToken(op1, "|");
|
|
||||||
var result = expr.evaluate(op2);
|
|
||||||
expect(result.value).toBe(7);
|
|
||||||
expect(result.base).toBe('dec');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't support opreations with numbers larger than 32-bit", () => {
|
|
||||||
expect(() => new OperatorToken(new ScalarToken(2147483648), "^"))
|
|
||||||
.toThrowError("2147483648 has more than 32 bits. JavaScript converts all numbers to 32-bit integers when applying bitwise operators. BitwiseCmd currently uses the JavaScript engine of your browser for results calculation and supports numbers in the range from -2147483647 to 2147483647");
|
|
||||||
});
|
|
||||||
12
src/expression/OperatorToken.test.ts
Normal file
12
src/expression/OperatorToken.test.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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,57 +0,0 @@
|
|||||||
import { INT_MAX_VALUE } from '../core/const';
|
|
||||||
import formatter from '../core/formatter';
|
|
||||||
import ScalarToken from './ScalarToken';
|
|
||||||
import { ExpressionToken } from './expression-interfaces';
|
|
||||||
|
|
||||||
export default class OperatorToken implements ExpressionToken {
|
|
||||||
operand: ExpressionToken;
|
|
||||||
operator: string;
|
|
||||||
isOperator: boolean;
|
|
||||||
isShiftExpression: boolean;
|
|
||||||
isNotExpression: boolean;
|
|
||||||
|
|
||||||
constructor(operand : ExpressionToken, operator : string) {
|
|
||||||
|
|
||||||
if(operand instanceof ScalarToken) {
|
|
||||||
const o = operand.getUnderlyingScalarOperand();
|
|
||||||
if(Math.abs(o.value) > INT_MAX_VALUE) {
|
|
||||||
const n = formatter.numberToString(o.value, o.base);
|
|
||||||
throw new Error(`${n} has more than 32 bits. JavaScript converts all numbers to 32-bit integers when applying bitwise operators. BitwiseCmd currently uses the JavaScript engine of your browser for results calculation and supports numbers in the range from ${-INT_MAX_VALUE} to ${INT_MAX_VALUE}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.operand = operand;
|
|
||||||
this.operator = operator;
|
|
||||||
this.isOperator = true;
|
|
||||||
this.isShiftExpression = this.operator.indexOf('<') >= 0 || this.operator.indexOf('>')>= 0;
|
|
||||||
this.isNotExpression = this.operator === '~';
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate(operand?: ScalarToken) : ScalarToken {
|
|
||||||
if (operand instanceof OperatorToken) {
|
|
||||||
throw new Error('value shouldnt be expression');
|
|
||||||
}
|
|
||||||
|
|
||||||
var evaluatedOperand = this.operand.evaluate();
|
|
||||||
|
|
||||||
var str = '';
|
|
||||||
if(this.operator == '~'){
|
|
||||||
str = '~' + evaluatedOperand.value;
|
|
||||||
} else {
|
|
||||||
if(operand == null)
|
|
||||||
throw new Error("Other is required for this expression");
|
|
||||||
|
|
||||||
str = operand.value + this.operator + evaluatedOperand.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScalarToken.create(eval(str), evaluatedOperand.base);
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnderlyingScalarOperand() : ScalarToken {
|
|
||||||
return this.operand.getUnderlyingScalarOperand();
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return this.operator + this.operand.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import ScalarToken from './ScalarToken';
|
import ScalarValue from './ScalarValue';
|
||||||
|
|
||||||
it('parsed from dec string', () => {
|
it('parsed from dec string', () => {
|
||||||
var op = ScalarToken.parse('123');
|
var op = ScalarValue.parse('123');
|
||||||
expect(op.base).toBe('dec');
|
expect(op.base).toBe('dec');
|
||||||
expect(op.value).toBe(123);
|
expect(op.value).toBe(123);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parsed from bin string', () => {
|
it('parsed from bin string', () => {
|
||||||
var op = ScalarToken.parse('0b10');
|
var op = ScalarValue.parse('0b10');
|
||||||
expect(op.base).toBe('bin');
|
expect(op.base).toBe('bin');
|
||||||
expect(op.value).toBe(2);
|
expect(op.value).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parsed from hex string', () => {
|
it('parsed from hex string', () => {
|
||||||
var op = ScalarToken.parse('0x10');
|
var op = ScalarValue.parse('0x10');
|
||||||
expect(op.base).toBe('hex');
|
expect(op.base).toBe('hex');
|
||||||
expect(op.value).toBe(16);
|
expect(op.value).toBe(16);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import {numberParser} from './numberParser';
|
|
||||||
import { ExpressionToken as ExpressionToken } from './expression-interfaces';
|
|
||||||
import { NumberBase } from '../core/formatter';
|
|
||||||
import { INT_MAX_VALUE } from '../core/const';
|
|
||||||
|
|
||||||
var globalId : number = 1;
|
|
||||||
|
|
||||||
|
|
||||||
// Represents scalar numeric value
|
|
||||||
export default class ScalarToken implements ExpressionToken {
|
|
||||||
id: number;
|
|
||||||
value: number;
|
|
||||||
base: NumberBase;
|
|
||||||
isOperator: boolean;
|
|
||||||
|
|
||||||
constructor(value : number, base?: NumberBase) {
|
|
||||||
|
|
||||||
this.id = globalId++;
|
|
||||||
this.value = value;
|
|
||||||
this.base = base || "dec";
|
|
||||||
this.isOperator = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(value : number) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate() : ScalarToken {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnderlyingScalarOperand() : ScalarToken {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(value : number, base? : NumberBase) {
|
|
||||||
return new ScalarToken(value, base || "dec");
|
|
||||||
};
|
|
||||||
|
|
||||||
static parse(input: string) : ScalarToken {
|
|
||||||
|
|
||||||
var parsed = ScalarToken.tryParse(input);
|
|
||||||
|
|
||||||
if(parsed == null) {
|
|
||||||
throw new Error(input + " is not a valid number");
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static tryParse(input: string) : ScalarToken | null {
|
|
||||||
|
|
||||||
var parsed = numberParser.parse(input);
|
|
||||||
|
|
||||||
if(!parsed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ScalarToken(parsed.value, parsed.base);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
11
src/expression/ScalarValue.test.ts
Normal file
11
src/expression/ScalarValue.test.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
79
src/expression/ScalarValue.ts
Normal file
79
src/expression/ScalarValue.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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 { NumberType } from '../core/types';
|
||||||
|
|
||||||
|
var globalId : number = 1;
|
||||||
|
|
||||||
|
|
||||||
|
// Represents scalar numeric value
|
||||||
|
export default class ScalarValue implements ExpressionElement {
|
||||||
|
id: number;
|
||||||
|
value: NumberType;
|
||||||
|
base: NumberBase;
|
||||||
|
isOperator: boolean;
|
||||||
|
|
||||||
|
constructor(value : NumberType, base?: NumberBase, is32Limit?: boolean) {
|
||||||
|
|
||||||
|
ScalarValue.validateSupported(value);
|
||||||
|
|
||||||
|
this.id = globalId++;
|
||||||
|
this.value = value;
|
||||||
|
this.base = base || "dec";
|
||||||
|
this.isOperator = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitSize() : number {
|
||||||
|
return this.isBigInt() ? 64 : 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
isBigInt() : boolean {
|
||||||
|
return typeof this.value === 'bigint';
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(value : NumberType) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate() : ScalarValue {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnderlyingScalarOperand() : ScalarValue {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateSupported(num : NumberType) {
|
||||||
|
|
||||||
|
if(typeof num == "bigint" && (num < INT64_MIN_VALUE || num > INT64_MAX_VALUE)) {
|
||||||
|
throw new Error(`64-bit numbers are supported in range from ${INT64_MIN_VALUE} to ${INT64_MAX_VALUE}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof num == "number" && (num < INT32_MIN_VALUE || num > 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}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ import React from 'react';
|
|||||||
import formatter from '../../core/formatter';
|
import formatter from '../../core/formatter';
|
||||||
import BinaryStringView, { FlipBitEventArg } from '../../core/components/BinaryString';
|
import BinaryStringView, { FlipBitEventArg } from '../../core/components/BinaryString';
|
||||||
import BitwiseResultViewModel from './BitwiseResultViewModel';
|
import BitwiseResultViewModel from './BitwiseResultViewModel';
|
||||||
import { Expression, ExpressionToken } from '../expression-interfaces';
|
import { Expression, ExpressionElement } from '../expression-interfaces';
|
||||||
import { OperatorToken, ScalarToken } from '../expression';
|
import { BitwiseOperator, ScalarValue } from '../expression';
|
||||||
|
import calc from '../../core/calc';
|
||||||
|
|
||||||
type BitwiseResultViewProps = {
|
type BitwiseResultViewProps = {
|
||||||
expression: Expression;
|
expression: Expression;
|
||||||
@@ -15,32 +16,51 @@ type BitwiseResultViewState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class BitwiseResultView extends React.Component<BitwiseResultViewProps, BitwiseResultViewState> {
|
export default class BitwiseResultView extends React.Component<BitwiseResultViewProps, BitwiseResultViewState> {
|
||||||
|
maxSeenLengthNumberOfBits: number;
|
||||||
|
|
||||||
constructor(props: BitwiseResultViewProps) {
|
constructor(props: BitwiseResultViewProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
this.state = {};
|
||||||
|
this.maxSeenLengthNumberOfBits = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var rows = this.getRows();
|
|
||||||
|
let model : BitwiseResultViewModel | null = null
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
const text = (err as any).message;
|
||||||
|
return <div className='error'>Error: {text}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var rows = this.getRows(model!);
|
||||||
|
|
||||||
return <table className="expression">
|
return <table className="expression">
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
}
|
}
|
||||||
|
|
||||||
getRows() : JSX.Element[] {
|
getRows(model: BitwiseResultViewModel): JSX.Element[] {
|
||||||
var model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
|
|
||||||
|
|
||||||
return model.items.map((itm, i) =>
|
this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits);
|
||||||
<ExpressionRow
|
|
||||||
key={i}
|
return model.items.map((itm, i) =>
|
||||||
|
<ExpressionRow
|
||||||
|
key={i}
|
||||||
sign={itm.sign}
|
sign={itm.sign}
|
||||||
css={itm.css}
|
css={itm.css}
|
||||||
|
bitSize={itm.bitSize}
|
||||||
allowFlipBits={itm.allowFlipBits}
|
allowFlipBits={itm.allowFlipBits}
|
||||||
expressionItem={itm.expression}
|
expressionItem={itm.expression}
|
||||||
emphasizeBytes={this.props.emphasizeBytes}
|
emphasizeBytes={this.props.emphasizeBytes}
|
||||||
maxNumberOfBits={model.maxNumberOfBits}
|
maxNumberOfBits={this.maxSeenLengthNumberOfBits}
|
||||||
onBitFlipped={() => this.onBitFlipped()} />);
|
onBitFlipped={() => this.onBitFlipped()} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,38 +71,42 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ExpressionRowProps = {
|
type ExpressionRowProps = {
|
||||||
sign: string,
|
sign: string,
|
||||||
css: string,
|
css: string,
|
||||||
maxNumberOfBits: number,
|
bitSize: number,
|
||||||
emphasizeBytes: boolean,
|
maxNumberOfBits: number,
|
||||||
allowFlipBits: boolean,
|
emphasizeBytes: boolean,
|
||||||
expressionItem: ExpressionToken,
|
allowFlipBits: boolean,
|
||||||
|
expressionItem: ExpressionElement,
|
||||||
onBitFlipped: any
|
onBitFlipped: any
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExpressionRow extends React.Component<ExpressionRowProps> {
|
class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||||
constructor(props: ExpressionRowProps) {
|
constructor(props: ExpressionRowProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { operand: null };
|
this.state = { operand: null };
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
|
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
|
||||||
|
const maxBits = Math.max()
|
||||||
|
|
||||||
return <tr className={"row-with-bits " + css}>
|
return <tr className={"row-with-bits " + css}>
|
||||||
<td className="sign">{sign}</td>
|
<td className="sign">{sign}</td>
|
||||||
<td className="label">{this.getLabel()}</td>
|
<td className="label">{this.getLabel()}</td>
|
||||||
<td className="bin">
|
<td className="bin">
|
||||||
<BinaryStringView
|
<BinaryStringView
|
||||||
emphasizeBytes={emphasizeBytes}
|
emphasizeBytes={emphasizeBytes}
|
||||||
binaryString={formatter.padLeft(this.getBinaryString(), maxNumberOfBits, '0')}
|
binaryString={formatter.padLeft(this.getBinaryString(), maxNumberOfBits, '0')}
|
||||||
allowFlipBits={allowFlipBits}
|
allowFlipBits={allowFlipBits}
|
||||||
onFlipBit={args => this.flipBit(args)}/>
|
bitSize={this.props.bitSize}
|
||||||
</td>
|
onFlipBit={args => this.flipBit(args)} />
|
||||||
<td className="other">{this.getAlternative()}</td>
|
</td>
|
||||||
</tr>;;
|
<td className="other">{this.getAlternative()}</td>
|
||||||
|
<td className="info" data-test-name='ignore'>{this.getInfo(maxNumberOfBits)}</td>
|
||||||
|
</tr>;;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBinaryString() : string {
|
getBinaryString(): string {
|
||||||
var v = this.props.expressionItem.evaluate();
|
var v = this.props.expressionItem.evaluate();
|
||||||
return formatter.numberToString(v.value, 'bin');
|
return formatter.numberToString(v.value, 'bin');
|
||||||
}
|
}
|
||||||
@@ -91,18 +115,18 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
|||||||
|
|
||||||
// For expressions like |~2
|
// For expressions like |~2
|
||||||
// TODO: find a better way...
|
// TODO: find a better way...
|
||||||
if(this.props.expressionItem.isOperator) {
|
if (this.props.expressionItem.isOperator) {
|
||||||
const ex = this.props.expressionItem as OperatorToken;
|
const ex = this.props.expressionItem as BitwiseOperator;
|
||||||
return ex.operator + this.getLabelString(ex.getUnderlyingScalarOperand());
|
return ex.operator + this.getLabelString(ex.getUnderlyingScalarOperand());
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getLabelString(this.props.expressionItem.getUnderlyingScalarOperand());
|
return this.getLabelString(this.props.expressionItem.getUnderlyingScalarOperand());
|
||||||
}
|
}
|
||||||
|
|
||||||
getAlternative() {
|
getAlternative() {
|
||||||
|
|
||||||
if(this.props.expressionItem.isOperator) {
|
if (this.props.expressionItem.isOperator) {
|
||||||
const ex = this.props.expressionItem as OperatorToken;
|
const ex = this.props.expressionItem as BitwiseOperator;
|
||||||
const res = ex.evaluate();
|
const res = ex.evaluate();
|
||||||
|
|
||||||
return formatter.numberToString(res.value, res.base);
|
return formatter.numberToString(res.value, res.base);
|
||||||
@@ -113,22 +137,45 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
|||||||
return formatter.numberToString(v.value, altBase);
|
return formatter.numberToString(v.value, altBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabelString (op: ScalarToken) : string {
|
getLabelString(op: ScalarValue): string {
|
||||||
return formatter.numberToString(op.value, op.base == 'bin' ? 'dec' : op.base);
|
return formatter.numberToString(op.value, op.base == 'bin' ? 'dec' : op.base);
|
||||||
}
|
}
|
||||||
|
|
||||||
flipBit(args: FlipBitEventArg) {
|
flipBit(args: FlipBitEventArg) {
|
||||||
|
|
||||||
const op = this.props.expressionItem.getUnderlyingScalarOperand();
|
const op = this.props.expressionItem.getUnderlyingScalarOperand();
|
||||||
const { index, binaryString } = args;
|
const { bitIndex: index, binaryStringLength: totalLength } = args;
|
||||||
|
|
||||||
var arr = binaryString.split('');
|
if(totalLength > op.bitSize() && (totalLength - index) > op.bitSize()) {
|
||||||
arr[index] = arr[index] == '0' ? '1' : '0';
|
op.setValue(calc.promoteToBigInt(op.value as number));
|
||||||
var bin = arr.join('');
|
}
|
||||||
|
|
||||||
var newValue = parseInt(bin, 2);
|
console.log(op.bitSize());
|
||||||
|
const pad = op.bitSize() - totalLength;
|
||||||
|
console.log(pad + index);
|
||||||
|
const newValue = calc.flipBit(op.value, pad + index);
|
||||||
op.setValue(newValue);
|
op.setValue(newValue);
|
||||||
|
|
||||||
this.props.onBitFlipped();
|
this.props.onBitFlipped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getInfo(maxNumberOfBits:number) {
|
||||||
|
var op = this.props.expressionItem.getUnderlyingScalarOperand();
|
||||||
|
|
||||||
|
if (op.isBigInt())
|
||||||
|
{
|
||||||
|
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.`;
|
||||||
|
|
||||||
|
return <span title={title} style={{cursor:"help"}}>(64-bit BigInt)</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 null;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ScalarToken, ListOfNumbersExpression, BitwiseOperationExpression, OperatorToken } from '../expression';
|
import { ScalarValue, ListOfNumbersExpression, BitwiseOperationExpression, BitwiseOperator } from '../expression';
|
||||||
import { ExpressionToken, Expression } from '../expression-interfaces';
|
import { ExpressionElement, Expression } from '../expression-interfaces';
|
||||||
import calc from '../../core/calc';
|
import calc from '../../core/calc';
|
||||||
import formatter from '../../core/formatter';
|
import formatter from '../../core/formatter';
|
||||||
|
import exp from 'constants';
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
emphasizeBytes: boolean;
|
emphasizeBytes: boolean;
|
||||||
@@ -11,9 +12,10 @@ type Config = {
|
|||||||
type ExpressionRowModel = {
|
type ExpressionRowModel = {
|
||||||
sign: string;
|
sign: string;
|
||||||
css: string;
|
css: string;
|
||||||
expression: ExpressionToken;
|
expression: ExpressionElement;
|
||||||
allowFlipBits: boolean;
|
allowFlipBits: boolean;
|
||||||
label: string;
|
label: string;
|
||||||
|
bitSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BitwiseResultViewModel {
|
export default class BitwiseResultViewModel {
|
||||||
@@ -43,17 +45,17 @@ export default class BitwiseResultViewModel {
|
|||||||
i = 0, len = expr.children.length,
|
i = 0, len = expr.children.length,
|
||||||
ex, m = new BitwiseResultViewModel(config);
|
ex, m = new BitwiseResultViewModel(config);
|
||||||
|
|
||||||
var prev : ScalarToken | null = null;
|
var prev : ScalarValue | null = null;
|
||||||
|
|
||||||
for (;i<len;i++) {
|
for (;i<len;i++) {
|
||||||
ex = expr.children[i];
|
ex = expr.children[i];
|
||||||
if(ex instanceof ScalarToken) {
|
if(ex instanceof ScalarValue) {
|
||||||
m.addScalarRow(ex);
|
m.addScalarRow(ex);
|
||||||
prev = ex;
|
prev = ex;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var eo = ex as OperatorToken;
|
var eo = ex as BitwiseOperator;
|
||||||
|
|
||||||
// If it a single NOT expression
|
// If it a single NOT expression
|
||||||
if(eo.isNotExpression) {
|
if(eo.isNotExpression) {
|
||||||
@@ -63,11 +65,11 @@ export default class BitwiseResultViewModel {
|
|||||||
prev = notResult;
|
prev = notResult;
|
||||||
}
|
}
|
||||||
else if(eo.isShiftExpression){
|
else if(eo.isShiftExpression){
|
||||||
prev = eo.evaluate(prev as ScalarToken);
|
prev = eo.evaluate(prev as ScalarValue);
|
||||||
m.addShiftExpressionResultRow(eo, prev);
|
m.addShiftExpressionResultRow(eo, prev);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
prev = eo.evaluate(prev as ScalarToken);
|
prev = eo.evaluate(prev as ScalarValue);
|
||||||
m.addOperatorRow(eo);
|
m.addOperatorRow(eo);
|
||||||
m.addExpressionResultRow(prev);
|
m.addExpressionResultRow(prev);
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ export default class BitwiseResultViewModel {
|
|||||||
return m;
|
return m;
|
||||||
};
|
};
|
||||||
|
|
||||||
addScalarRow(expr: ScalarToken) {
|
addScalarRow(expr: ScalarValue) {
|
||||||
const bits = calc.numberOfBitsDisplayed(expr.value);
|
const bits = calc.numberOfBitsDisplayed(expr.value);
|
||||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||||
this.items.push({
|
this.items.push({
|
||||||
@@ -85,14 +87,16 @@ export default class BitwiseResultViewModel {
|
|||||||
css: '',
|
css: '',
|
||||||
expression: expr,
|
expression: expr,
|
||||||
allowFlipBits: this.allowFlipBits,
|
allowFlipBits: this.allowFlipBits,
|
||||||
label: ''
|
label: '',
|
||||||
|
bitSize: expr.bitSize(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addOperatorRow(expr: OperatorToken) {
|
addOperatorRow(expr: BitwiseOperator) {
|
||||||
|
|
||||||
const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingScalarOperand();
|
const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingScalarOperand();
|
||||||
const bits = calc.numberOfBitsDisplayed(resultNumber.value);
|
const bits = calc.numberOfBitsDisplayed(resultNumber.value);
|
||||||
|
|
||||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||||
|
|
||||||
this.items.push({
|
this.items.push({
|
||||||
@@ -100,11 +104,12 @@ export default class BitwiseResultViewModel {
|
|||||||
css: '',
|
css: '',
|
||||||
label: this.getLabel(resultNumber),
|
label: this.getLabel(resultNumber),
|
||||||
expression: expr.operand,
|
expression: expr.operand,
|
||||||
allowFlipBits: this.allowFlipBits
|
allowFlipBits: this.allowFlipBits,
|
||||||
|
bitSize: resultNumber.bitSize()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addShiftExpressionResultRow(expr : OperatorToken, resultExpr : ScalarToken) {
|
addShiftExpressionResultRow(expr : BitwiseOperator, resultExpr : ScalarValue) {
|
||||||
const bits = calc.numberOfBitsDisplayed(resultExpr.value);
|
const bits = calc.numberOfBitsDisplayed(resultExpr.value);
|
||||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||||
const child = expr.operand.getUnderlyingScalarOperand();
|
const child = expr.operand.getUnderlyingScalarOperand();
|
||||||
@@ -113,11 +118,12 @@ export default class BitwiseResultViewModel {
|
|||||||
css: 'expression-result',
|
css: 'expression-result',
|
||||||
expression: resultExpr,
|
expression: resultExpr,
|
||||||
allowFlipBits: false,
|
allowFlipBits: false,
|
||||||
label: ''
|
label: '',
|
||||||
|
bitSize: resultExpr.bitSize()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addExpressionResultRow(expr : ScalarToken) {
|
addExpressionResultRow(expr : ScalarValue) {
|
||||||
const bits = calc.numberOfBitsDisplayed(expr.value);
|
const bits = calc.numberOfBitsDisplayed(expr.value);
|
||||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||||
this.items.push({
|
this.items.push({
|
||||||
@@ -126,10 +132,11 @@ export default class BitwiseResultViewModel {
|
|||||||
expression: expr,
|
expression: expr,
|
||||||
allowFlipBits: false,
|
allowFlipBits: false,
|
||||||
label: '',
|
label: '',
|
||||||
|
bitSize: expr.bitSize()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getLabel (op: ScalarToken) : string {
|
getLabel (op: ScalarValue) : string {
|
||||||
|
|
||||||
return formatter.numberToString(op.value, op.base === 'bin' ? 'dec' : op.base)
|
return formatter.numberToString(op.value, op.base === 'bin' ? 'dec' : op.base)
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/expression/engine.test.ts
Normal file
41
src/expression/engine.test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
40
src/expression/engine.ts
Normal file
40
src/expression/engine.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { NumberType } 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 : NumberType, operator: string, op2 : NumberType) : NumberType{
|
||||||
|
const a = equalizeType(op2, op1) as any;
|
||||||
|
const b = equalizeType(op1, op2) as any;
|
||||||
|
|
||||||
|
switch(operator) {
|
||||||
|
case ">>": return (a >> b) as (NumberType);
|
||||||
|
case ">>>": return (a >>> b) as (NumberType);
|
||||||
|
case "<<": return (a << b) as (NumberType);
|
||||||
|
case "&": return (b & a) as (NumberType);
|
||||||
|
case "|": return (b | a) as (NumberType);
|
||||||
|
case "^": return (b ^ a) as (NumberType);
|
||||||
|
default: throw new Error(operator + " operator is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function equalizeType(source : NumberType, dest : NumberType) : NumberType {
|
||||||
|
|
||||||
|
return typeof source == 'bigint' && typeof dest != 'bigint'
|
||||||
|
? BigInt(dest)
|
||||||
|
: dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default engine;
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { ScalarToken } from "./expression";
|
import { ScalarValue } from "./expression";
|
||||||
|
|
||||||
export interface Expression
|
export interface Expression
|
||||||
{
|
{
|
||||||
expressionString: string;
|
expressionString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExpressionToken
|
export interface ExpressionElement
|
||||||
{
|
{
|
||||||
isOperator: boolean;
|
isOperator: boolean;
|
||||||
getUnderlyingScalarOperand: () => ScalarToken;
|
getUnderlyingScalarOperand: () => ScalarValue;
|
||||||
evaluate(operand? : ScalarToken): ScalarToken;
|
evaluate(operand? : ScalarValue): ScalarValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { parser, ListOfNumbersExpression, BitwiseOperationExpression, ScalarToken, OperatorToken } from "./expression";
|
import { parser, ListOfNumbersExpression, BitwiseOperationExpression, ScalarValue, BitwiseOperator } from "./expression";
|
||||||
|
|
||||||
describe("expression parser", () => {
|
describe("expression parser", () => {
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ describe("expression parser", () => {
|
|||||||
expect(actual).toBeInstanceOf(ListOfNumbersExpression);
|
expect(actual).toBeInstanceOf(ListOfNumbersExpression);
|
||||||
|
|
||||||
const expr = actual as ListOfNumbersExpression;
|
const expr = actual as ListOfNumbersExpression;
|
||||||
expect(expr.children[0].getUnderlyingScalarOperand().value).toBe(305419896);
|
expect(expr.children[0].getUnderlyingScalarOperand().value.toString()).toBe('305419896');
|
||||||
expect(expr.children[1].getUnderlyingScalarOperand().value).toBe(2863311360);
|
expect(expr.children[1].getUnderlyingScalarOperand().value.toString()).toBe('2863311360');
|
||||||
})
|
})
|
||||||
|
|
||||||
it("pares multiple operand expression", () => {
|
it("pares multiple operand expression", () => {
|
||||||
@@ -36,17 +36,17 @@ describe("expression parser", () => {
|
|||||||
const first = result.children[0];
|
const first = result.children[0];
|
||||||
const second = result.children[1];
|
const second = result.children[1];
|
||||||
|
|
||||||
expect(first).toBeInstanceOf(ScalarToken);
|
expect(first).toBeInstanceOf(ScalarValue);
|
||||||
|
|
||||||
expect((first as ScalarToken).value).toBe(1);
|
expect((first as ScalarValue).value).toBe(1);
|
||||||
|
|
||||||
expect(second).toBeInstanceOf(OperatorToken);
|
expect(second).toBeInstanceOf(BitwiseOperator);
|
||||||
var secondOp = second as OperatorToken;
|
var secondOp = second as BitwiseOperator;
|
||||||
expect(secondOp.operator).toBe("^");
|
expect(secondOp.operator).toBe("^");
|
||||||
|
|
||||||
expect(secondOp.operand).toBeInstanceOf(ScalarToken);
|
expect(secondOp.operand).toBeInstanceOf(ScalarValue);
|
||||||
var childOp = secondOp.operand as ScalarToken;
|
var childOp = secondOp.operand as ScalarValue;
|
||||||
expect(childOp.value).toBe(2);
|
expect(childOp.value.toString()).toBe('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bug", () => {
|
it("bug", () => {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import ScalarToken from './ScalarToken';
|
import ScalarValue from './ScalarValue';
|
||||||
import OperatorToken from './OperatorToken'
|
import BitwiseOperator from './BitwiseOperator'
|
||||||
import ListOfNumbersExpression from './ListOfNumbersExpression';
|
import ListOfNumbersExpression from './ListOfNumbersExpression';
|
||||||
import BitwiseOperationExpression from './BitwiseOperationExpression';
|
import BitwiseOperationExpression from './BitwiseOperationExpression';
|
||||||
import { Expression, ExpressionToken } from './expression-interfaces';
|
import { Expression, ExpressionElement } from './expression-interfaces';
|
||||||
import { NumberBase } from '../core/formatter';
|
|
||||||
|
|
||||||
export { default as ScalarToken } from './ScalarToken';
|
export { default as ScalarValue } from './ScalarValue';
|
||||||
export { default as OperatorToken } from './OperatorToken';
|
export { default as BitwiseOperator } from './BitwiseOperator';
|
||||||
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression';
|
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression';
|
||||||
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression';
|
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression';
|
||||||
|
|
||||||
@@ -46,14 +45,6 @@ class ExpressionParser {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
parseOperand (input : string) : ScalarToken {
|
|
||||||
return ScalarToken.parse(input);
|
|
||||||
};
|
|
||||||
|
|
||||||
createOperand (number : number, base : NumberBase) : ScalarToken {
|
|
||||||
return ScalarToken.create(number, base);
|
|
||||||
};
|
|
||||||
|
|
||||||
addFactory (factory: IExpressionParserFactory) {
|
addFactory (factory: IExpressionParserFactory) {
|
||||||
this.factories.push(factory);
|
this.factories.push(factory);
|
||||||
@@ -70,7 +61,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
|||||||
|
|
||||||
return input.split(' ')
|
return input.split(' ')
|
||||||
.filter(p => p.length > 0)
|
.filter(p => p.length > 0)
|
||||||
.map(p => ScalarToken.tryParse(p))
|
.map(p => ScalarValue.tryParse(p))
|
||||||
.filter(n => n == null)
|
.filter(n => n == null)
|
||||||
.length == 0;
|
.length == 0;
|
||||||
};
|
};
|
||||||
@@ -79,7 +70,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
|||||||
|
|
||||||
const numbers = input.split(' ')
|
const numbers = input.split(' ')
|
||||||
.filter(p => p.length > 0)
|
.filter(p => p.length > 0)
|
||||||
.map(m => ScalarToken.parse(m));
|
.map(m => ScalarValue.parse(m));
|
||||||
|
|
||||||
return new ListOfNumbersExpression(input, numbers);
|
return new ListOfNumbersExpression(input, numbers);
|
||||||
}
|
}
|
||||||
@@ -90,8 +81,8 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
|||||||
regex: RegExp;
|
regex: RegExp;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,a-f,0-9]+)))+$/;
|
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,l,L,a-f,0-9]+)))+$/;
|
||||||
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,a-f,0-9]+))/g;
|
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,l,L,a-f,0-9]+))/g;
|
||||||
}
|
}
|
||||||
|
|
||||||
canCreate (input: string) : boolean {
|
canCreate (input: string) : boolean {
|
||||||
@@ -101,7 +92,7 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
|||||||
|
|
||||||
create (input: string) : Expression {
|
create (input: string) : Expression {
|
||||||
var m : RegExpExecArray | null;
|
var m : RegExpExecArray | null;
|
||||||
const operands : ExpressionToken[] = [];
|
const operands : ExpressionElement[] = [];
|
||||||
const normalizedString = this.normalizeString(input);
|
const normalizedString = this.normalizeString(input);
|
||||||
|
|
||||||
this.regex.lastIndex = 0;
|
this.regex.lastIndex = 0;
|
||||||
@@ -113,23 +104,23 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
|||||||
return new BitwiseOperationExpression(normalizedString, operands)
|
return new BitwiseOperationExpression(normalizedString, operands)
|
||||||
};
|
};
|
||||||
|
|
||||||
parseMatch (m:any): ExpressionToken {
|
parseMatch (m:any): ExpressionElement {
|
||||||
var input = m[0],
|
var input = m[0],
|
||||||
operator = m[1],
|
operator = m[1],
|
||||||
num = m[2];
|
num = m[2];
|
||||||
|
|
||||||
var parsed = null;
|
var parsed = null;
|
||||||
if(num.indexOf('~') == 0) {
|
if(num.indexOf('~') == 0) {
|
||||||
parsed = new OperatorToken(ScalarToken.parse(num.substring(1)), '~');
|
parsed = new BitwiseOperator(ScalarValue.parse(num.substring(1)), '~');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
parsed = ScalarToken.parse(num);
|
parsed = ScalarValue.parse(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(operator == null) {
|
if(operator == null) {
|
||||||
return parsed as OperatorToken;
|
return parsed as BitwiseOperator;
|
||||||
} else {
|
} else {
|
||||||
return new OperatorToken(parsed as ScalarToken, operator);
|
return new BitwiseOperator(parsed as ScalarValue, operator);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
106
src/expression/numberParser.test.ts
Normal file
106
src/expression/numberParser.test.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import exp from 'constants';
|
||||||
|
import calc from '../core/calc';
|
||||||
|
import {numberParser, ParsedNumber} from './numberParser';
|
||||||
|
|
||||||
|
describe("parser", () => {
|
||||||
|
|
||||||
|
it('parses decimal number', () => {
|
||||||
|
const result = numberParser.parse('10');
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
|
||||||
|
var number = result as ParsedNumber;
|
||||||
|
expect(number.value).toBe(10);
|
||||||
|
expect(number.base).toBe('dec');
|
||||||
|
expect(number.input).toBe('10');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses bigint numbers', () => {
|
||||||
|
const dec = numberParser.parse('10L');
|
||||||
|
expect(dec).not.toBeNull();
|
||||||
|
|
||||||
|
expect(dec?.value).toBe(BigInt(10));
|
||||||
|
expect(typeof dec?.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).toBe(BigInt(2));
|
||||||
|
expect(typeof bin?.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.toString()).toBe(BigInt(15).toString());
|
||||||
|
expect(typeof hex?.value).toBe("bigint");
|
||||||
|
expect(hex?.base).toBe('hex');
|
||||||
|
expect(hex?.input).toBe('0xfL');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('switches to bigint if value exceeds max safe int', () => {
|
||||||
|
const unsafeInt = BigInt(Number.MAX_SAFE_INTEGER)+BigInt(1);
|
||||||
|
|
||||||
|
const dec = numberParser.parse(unsafeInt.toString());
|
||||||
|
expect(dec?.value).toEqual(unsafeInt);
|
||||||
|
expect(dec?.base).toBe('dec');
|
||||||
|
|
||||||
|
const bin = numberParser.parse("0b" + unsafeInt.toString(2));
|
||||||
|
expect(bin?.value).toEqual(unsafeInt);
|
||||||
|
expect(bin?.base).toEqual('bin');
|
||||||
|
|
||||||
|
const hex = numberParser.parse("0x" + unsafeInt.toString(16));
|
||||||
|
expect(hex?.value).toEqual(unsafeInt);
|
||||||
|
expect(hex?.base).toEqual('hex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('switches to bigint if value exceeds max safe negative int', () => {
|
||||||
|
const unsafeInt = BigInt(Number.MIN_SAFE_INTEGER)-BigInt(1);
|
||||||
|
|
||||||
|
const dec = numberParser.parse(unsafeInt.toString());
|
||||||
|
expect(dec?.value.toString()).toEqual(unsafeInt.toString());
|
||||||
|
expect(dec?.base).toBe('dec');
|
||||||
|
|
||||||
|
const bin = numberParser.parse("-0b" + unsafeInt.toString(2).replace('-', ''));
|
||||||
|
expect(bin?.value.toString()).toEqual(unsafeInt.toString());
|
||||||
|
expect(bin?.base).toEqual('bin');
|
||||||
|
|
||||||
|
const hex = numberParser.parse("-0x" + unsafeInt.toString(16).replace('-',''));
|
||||||
|
expect(hex?.value.toString()).toEqual(unsafeInt.toString());
|
||||||
|
expect(hex?.base).toEqual('hex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses hex number', () => {
|
||||||
|
const result = numberParser.parse('0xab');
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
|
||||||
|
var number = result as ParsedNumber;
|
||||||
|
expect(number.value).toBe(171);
|
||||||
|
expect(number.base).toBe('hex');
|
||||||
|
expect(number.input).toBe('0xab');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses bin number', () => {
|
||||||
|
var result = numberParser.parse('0b0110');
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
|
||||||
|
var number = result as ParsedNumber;
|
||||||
|
expect(number.value).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();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses big int', () => {
|
||||||
|
var v = numberParser.parse('1l')?.value
|
||||||
|
expect(typeof v).toBe("bigint");
|
||||||
|
expect(v?.toString()).toBe("1");
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import {numberParser, ParsedNumber} from './numberParser';
|
|
||||||
|
|
||||||
describe("parser", () => {
|
|
||||||
it('parses decimal number', () => {
|
|
||||||
const result = numberParser.parse('10');
|
|
||||||
expect(result).not.toBeNull();
|
|
||||||
|
|
||||||
var number = result as ParsedNumber;
|
|
||||||
expect(number.value).toBe(10);
|
|
||||||
expect(number.base).toBe('dec');
|
|
||||||
expect(number.input).toBe('10');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses hex number', () => {
|
|
||||||
const result = numberParser.parse('0xab');
|
|
||||||
expect(result).not.toBeNull();
|
|
||||||
|
|
||||||
var number = result as ParsedNumber;
|
|
||||||
expect(number.value).toBe(171);
|
|
||||||
expect(number.base).toBe('hex');
|
|
||||||
expect(number.input).toBe('0xab');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses bin number', () => {
|
|
||||||
var result = numberParser.parse('0b0110');
|
|
||||||
expect(result).not.toBeNull();
|
|
||||||
|
|
||||||
var number = result as ParsedNumber;
|
|
||||||
expect(number.value).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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
|
import { INT32_MAX_VALUE, INT32_MIN_VALUE } from "../core/const";
|
||||||
import { NumberBase } from "../core/formatter";
|
import { NumberBase } from "../core/formatter";
|
||||||
|
import { NumberType } from "../core/types";
|
||||||
|
|
||||||
const decimalRegex = /^-?\d+$/;
|
const decimalRegex = /^-?\d+[l,L]?$/;
|
||||||
const hexRegex = /^-?0x[0-9,a-f]+$/i;
|
const hexRegex = /^-?0x[0-9,a-f]+[l,L]?$/i;
|
||||||
const binRegex = /^-?0b[0-1]+$/i;
|
const binRegex = /^-?0b[0-1]+[l,L]?$/i;
|
||||||
const operatorRegex = /^<<|>>|<<<|\&|\|\^|~$/;
|
|
||||||
|
|
||||||
interface ParserConfig {
|
interface ParserConfig {
|
||||||
regex: RegExp,
|
regex: RegExp,
|
||||||
radix: number,
|
|
||||||
base: NumberBase,
|
base: NumberBase,
|
||||||
prefix: string|RegExp
|
parse: (input: string) => NumberType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParsedNumber {
|
export interface ParsedNumber {
|
||||||
value: number;
|
value: number|bigint;
|
||||||
base: NumberBase;
|
base: NumberBase;
|
||||||
input: string;
|
input: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
var knownParsers : ParserConfig[] = [
|
var knownParsers : ParserConfig[] = [
|
||||||
{ regex: decimalRegex, radix: 10, base: 'dec', prefix: '^$' },
|
{ regex: decimalRegex, base: 'dec', parse:(s) => parseIntSafe(s, 10) },
|
||||||
{ regex: hexRegex, radix: 16, base: 'hex', prefix:/0x/i },
|
{ regex: hexRegex, base: 'hex', parse:(s) => parseIntSafe(s, 16)},
|
||||||
{ regex: binRegex, radix: 2, base: 'bin', prefix:/0b/i }];
|
{ regex: binRegex, base: 'bin', parse:(s) => parseIntSafe(s, 2) }];
|
||||||
|
|
||||||
|
|
||||||
class NumberParser {
|
class NumberParser {
|
||||||
@@ -53,7 +53,7 @@ class NumberParser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = parseInt(rawInput.replace(parser.prefix, ''), parser.radix);
|
var value = parser.parse(rawInput);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: value,
|
value: value,
|
||||||
@@ -63,6 +63,29 @@ class NumberParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_SAFE_INTn = BigInt(INT32_MAX_VALUE);
|
||||||
|
const MIN_SAFE_INTn = BigInt(INT32_MIN_VALUE);
|
||||||
|
|
||||||
|
function parseIntSafe(input : string, radix: number) : NumberType {
|
||||||
|
|
||||||
|
const bigIntStr = input.replace('-', '').replace('l', '').replace('L', '');
|
||||||
|
let bigInt = BigInt(bigIntStr);
|
||||||
|
const isNegative = input.startsWith('-');
|
||||||
|
const isBigInt = input.toLowerCase().endsWith('l');
|
||||||
|
|
||||||
|
if(isNegative) bigInt *= BigInt(-1);
|
||||||
|
|
||||||
|
if(isBigInt) return bigInt;
|
||||||
|
|
||||||
|
if(bigInt > MAX_SAFE_INTn)
|
||||||
|
return bigInt;
|
||||||
|
|
||||||
|
if(bigInt < MIN_SAFE_INTn)
|
||||||
|
return bigInt;
|
||||||
|
|
||||||
|
return parseInt(input.replace(/0(x|b)/, ''), radix);
|
||||||
|
}
|
||||||
|
|
||||||
const numberParser = new NumberParser(knownParsers);
|
const numberParser = new NumberParser(knownParsers);
|
||||||
|
|
||||||
export {numberParser};
|
export {numberParser};
|
||||||
@@ -32,6 +32,7 @@ code { font-size: 1.2em; font-weight: bold; }
|
|||||||
|
|
||||||
.expression .label { font-weight: bold; padding-right: 5px; text-align: right; }
|
.expression .label { font-weight: bold; padding-right: 5px; text-align: right; }
|
||||||
.expression .bin { letter-spacing: 3px; }
|
.expression .bin { letter-spacing: 3px; }
|
||||||
|
.expression .info { font-size: 0.9em; color: teal; }
|
||||||
.expression .byte { margin: 0 3px; }
|
.expression .byte { margin: 0 3px; }
|
||||||
.expression-result td { border-top: dotted 1px gray; }
|
.expression-result td { border-top: dotted 1px gray; }
|
||||||
.expression { font-size: 1.5em; font-family: monospace }
|
.expression { font-size: 1.5em; font-family: monospace }
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default class AppState {
|
|||||||
this.emphasizeBytes = persistData.emphasizeBytes || true;
|
this.emphasizeBytes = persistData.emphasizeBytes || true;
|
||||||
this.persistedVersion = persistData.version || 0.1;
|
this.persistedVersion = persistData.version || 0.1;
|
||||||
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
|
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
|
||||||
this.debugMode = env !== 'prod' || persistData.debugMode === true;
|
this.debugMode = persistData.debugMode === true;
|
||||||
this.pageVisitsCount = persistData.pageVisistsCount || 0;
|
this.pageVisitsCount = persistData.pageVisistsCount || 0;
|
||||||
this.donationClicked = persistData.donationClicked;
|
this.donationClicked = persistData.donationClicked;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
console.log('refresh');
|
|
||||||
this.setState(this.props.appState);
|
this.setState(this.props.appState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import HelpResultView from './components/HelpResultView';
|
|||||||
import TextResultView from './components/TextResultView';
|
import TextResultView from './components/TextResultView';
|
||||||
import WhatsnewResultView from './components/WhatsNewResultView';
|
import WhatsnewResultView from './components/WhatsNewResultView';
|
||||||
import {STARTUP_COMMAND_KEY} from './startup';
|
import {STARTUP_COMMAND_KEY} from './startup';
|
||||||
|
import { INT32_MAX_VALUE, INT64_MAX_VALUE } from '../core/const';
|
||||||
|
|
||||||
const shellModule = {
|
const shellModule = {
|
||||||
setup: function(appState: AppState, cmd: CmdShell) {
|
setup: function(appState: AppState, cmd: CmdShell) {
|
||||||
@@ -31,6 +32,10 @@ const shellModule = {
|
|||||||
appState.toggleDebugMode();
|
appState.toggleDebugMode();
|
||||||
appState.addCommandResult(c.input, () => <TextResultView text={`Debug Mode: ${appState.debugMode}`}/>);
|
appState.addCommandResult(c.input, () => <TextResultView text={`Debug Mode: ${appState.debugMode}`}/>);
|
||||||
});
|
});
|
||||||
|
cmd.command("-max", (c:CommandInput) => {
|
||||||
|
const text = `Int32 ${INT32_MAX_VALUE}\nInt64 ${INT64_MAX_VALUE}`
|
||||||
|
appState.addCommandResult(c.input, () => <TextResultView text={text} />)
|
||||||
|
})
|
||||||
|
|
||||||
cmd.command("donate", (c:CommandInput) => {
|
cmd.command("donate", (c:CommandInput) => {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user