mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2025-12-10 06:52:05 +01:00
Other ops (#68)
This commit is contained in:
30
package-lock.json
generated
30
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
@@ -2442,6 +2443,18 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/free-regular-svg-icons": {
|
||||||
|
"version": "6.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz",
|
||||||
|
"integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==",
|
||||||
|
"license": "(CC-BY-4.0 AND MIT)",
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||||
"version": "6.7.2",
|
"version": "6.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
|
||||||
@@ -4440,7 +4453,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
|
||||||
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
|
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.4.0",
|
"@eslint-community/regexpp": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "5.62.0",
|
"@typescript-eslint/scope-manager": "5.62.0",
|
||||||
@@ -17968,20 +17980,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss/node_modules/yaml": {
|
|
||||||
"version": "2.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
|
||||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||||
@@ -18345,6 +18343,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||||
"license": "(MIT OR CC0-1.0)",
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
@@ -18869,6 +18868,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz",
|
||||||
"integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==",
|
"integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/bonjour": "^3.5.9",
|
"@types/bonjour": "^3.5.9",
|
||||||
"@types/connect-history-api-fallback": "^1.3.5",
|
"@types/connect-history-api-fallback": "^1.3.5",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import calc from './calc';
|
import calc from './calc';
|
||||||
import { Integer, asInteger } from './Integer';
|
import { Integer, asInteger } from './Integer';
|
||||||
import { INT32_MIN_VALUE, INT64_MAX_VALUE, UINT64_MAX_VALUE } from './const';
|
import { INT32_MIN_VALUE, INT32_MAX_VALUE, INT64_MAX_VALUE, INT64_MIN_VALUE, UINT64_MAX_VALUE } from './const';
|
||||||
|
|
||||||
describe('calc.flipBit', () => {
|
describe('calc.flipBit', () => {
|
||||||
it('calculates flipped bit 32-bit number', () => {
|
it('calculates flipped bit 32-bit number', () => {
|
||||||
@@ -86,7 +86,98 @@ describe('calc.xor', () => {
|
|||||||
it('positive and negative nubmer', () => {
|
it('positive and negative nubmer', () => {
|
||||||
expect(calc.xor(Integer.int(-1), Integer.int(10)).num()).toBe(-11);
|
expect(calc.xor(Integer.int(-1), Integer.int(10)).num()).toBe(-11);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
describe('calc.add', () => {
|
||||||
|
it('adds positives', () => {
|
||||||
|
expect(calc.add(Integer.int(2), Integer.int(3)).num()).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds negative and positive', () => {
|
||||||
|
expect(calc.add(Integer.int(-2), Integer.int(3)).num()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds negatives', () => {
|
||||||
|
expect(calc.add(Integer.int(-2), Integer.int(-3)).num()).toBe(-5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps on 32-bit overflow', () => {
|
||||||
|
expect(calc.add(Integer.int(INT32_MAX_VALUE), Integer.int(1)).num()).toBe(INT32_MIN_VALUE);
|
||||||
|
expect(calc.add(Integer.int(INT32_MAX_VALUE), Integer.int(2)).num()).toBe(-2147483647);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps on 64-bit overflow', () => {
|
||||||
|
const r = calc.add(new Integer(INT64_MAX_VALUE, 64), Integer.long(1));
|
||||||
|
expect(r.value.toString()).toBe(INT64_MIN_VALUE.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('promotes to larger operand size', () => {
|
||||||
|
const r = calc.add(Integer.int(-1), Integer.long(2));
|
||||||
|
expect(r.maxBitSize).toBe(64);
|
||||||
|
expect(r.num()).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calc.sub', () => {
|
||||||
|
it('subtracts positives', () => {
|
||||||
|
expect(calc.sub(Integer.int(7), Integer.int(2)).num()).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subtracts negative and positive', () => {
|
||||||
|
expect(calc.sub(Integer.int(-2), Integer.int(3)).num()).toBe(-5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subtracts negatives', () => {
|
||||||
|
expect(calc.sub(Integer.int(-2), Integer.int(-3)).num()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps on 32-bit overflow', () => {
|
||||||
|
// 2147483647 - (-1) = 2147483648 -> -2147483648
|
||||||
|
expect(calc.sub(Integer.int(2147483647), Integer.int(-1)).num()).toBe(-2147483648);
|
||||||
|
// -2147483648 - 1 = -2147483649 -> 2147483647
|
||||||
|
expect(calc.sub(Integer.int(-2147483648), Integer.int(1)).num()).toBe(2147483647);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('promotes to larger operand size', () => {
|
||||||
|
const r = calc.sub(Integer.int(-1), Integer.long(2));
|
||||||
|
expect(r.maxBitSize).toBe(64);
|
||||||
|
expect(r.num()).toBe(-3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calc.div', () => {
|
||||||
|
it('divides positives with truncation', () => {
|
||||||
|
expect(calc.div(Integer.int(7), Integer.int(2)).num()).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('divides negative and positive with truncation', () => {
|
||||||
|
expect(calc.div(Integer.int(-7), Integer.int(2)).num()).toBe(-3);
|
||||||
|
expect(calc.div(Integer.int(7), Integer.int(-2)).num()).toBe(-3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('divides negatives', () => {
|
||||||
|
expect(calc.div(Integer.int(-8), Integer.int(-2)).num()).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps INT_MIN / -1 to INT_MIN for 32-bit', () => {
|
||||||
|
expect(calc.div(Integer.int(-2147483648), Integer.int(-1)).num()).toBe(-2147483648);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('64-bit division', () => {
|
||||||
|
const r = calc.div(new Integer(INT64_MAX_VALUE, 64), Integer.long(2));
|
||||||
|
expect(r.value.toString()).toBe('4611686018427387903');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('promotes to larger operand size', () => {
|
||||||
|
const r = calc.div(Integer.int(-8), Integer.long(2));
|
||||||
|
expect(r.maxBitSize).toBe(64);
|
||||||
|
expect(r.num()).toBe(-4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('division by zero throws', () => {
|
||||||
|
expect(() => calc.div(Integer.int(1), Integer.int(0))).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('calc.lshift', () => {
|
describe('calc.lshift', () => {
|
||||||
|
|
||||||
@@ -200,6 +291,7 @@ describe("calc misc", () => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("calc.engine.", () => {
|
describe("calc.engine.", () => {
|
||||||
it("not", () => {
|
it("not", () => {
|
||||||
expect(calc.engine.not("0101")).toBe("1010");
|
expect(calc.engine.not("0101")).toBe("1010");
|
||||||
@@ -227,6 +319,82 @@ describe("calc.engine.", () => {
|
|||||||
expect(calc.engine.xor("10101", "11011")).toBe("01110");
|
expect(calc.engine.xor("10101", "11011")).toBe("01110");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("add", () => {
|
||||||
|
// 1-bit
|
||||||
|
expect(calc.engine.add("1", "1")).toBe("0"); // 1 + 1 = 2 -> 0 (wrap)
|
||||||
|
expect(calc.engine.add("1", "0")).toBe("1");
|
||||||
|
expect(calc.engine.add("0", "0")).toBe("0");
|
||||||
|
|
||||||
|
// 4-bit
|
||||||
|
expect(calc.engine.add("0001", "0001")).toBe("0010"); // 1+1=2
|
||||||
|
expect(calc.engine.add("0111", "0001")).toBe("1000"); // 7+1=8
|
||||||
|
expect(calc.engine.add("1111", "0001")).toBe("0000"); // 15+1=16 -> 0 (wrap)
|
||||||
|
expect(calc.engine.add("1111", "1111")).toBe("1110"); // 15+15=30 -> 14
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mul", () => {
|
||||||
|
// 4-bit unsigned-style values
|
||||||
|
expect(calc.engine.mul("0001", "0011")).toBe("0011"); // 1*3=3
|
||||||
|
expect(calc.engine.mul("0010", "0011")).toBe("0110"); // 2*3=6
|
||||||
|
expect(calc.engine.mul("1111", "0010")).toBe("1110"); // 15*2=30 -> 14
|
||||||
|
|
||||||
|
// two's complement semantics for negatives
|
||||||
|
expect(calc.engine.mul("1111", "0011")).toBe("1101"); // (-1)*3 = -3
|
||||||
|
expect(calc.engine.mul("1111", "1111")).toBe("0001"); // (-1)*(-1) = 1
|
||||||
|
expect(calc.engine.mul("1000", "0010")).toBe("0000"); // (-8)*2 = -16 -> 0
|
||||||
|
|
||||||
|
// 8-bit example
|
||||||
|
expect(calc.engine.mul("11111111", "00000010")).toBe("11111110"); // (-1)*2 = -2
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sub", () => {
|
||||||
|
// 4-bit examples
|
||||||
|
expect(calc.engine.sub("0011", "0001")).toBe("0010"); // 3-1=2
|
||||||
|
expect(calc.engine.sub("0000", "0001")).toBe("1111"); // 0-1 -> -1
|
||||||
|
expect(calc.engine.sub("1000", "0001")).toBe("0111"); // -8-1 -> 7 (wrap)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("div", () => {
|
||||||
|
// 4-bit unsigned-style values
|
||||||
|
expect(calc.engine.div("0110", "0011")).toBe("0010"); // 6/3=2
|
||||||
|
expect(calc.engine.div("0111", "0010")).toBe("0011"); // 7/2=3
|
||||||
|
|
||||||
|
// two's complement negatives
|
||||||
|
expect(calc.engine.div("1111", "0010")).toBe("0000"); // (-1)/2 = 0 (trunc toward zero)
|
||||||
|
expect(calc.engine.div("1000", "0010")).toBe("1100"); // (-8)/2 = -4 -> 1100
|
||||||
|
|
||||||
|
// division by zero
|
||||||
|
expect(() => calc.engine.div("0001", "0000")).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("add", () => {
|
||||||
|
// 1-bit
|
||||||
|
expect(calc.engine.add("1", "1")).toBe("0"); // 1 + 1 = 2 -> 0 (wrap)
|
||||||
|
expect(calc.engine.add("1", "0")).toBe("1");
|
||||||
|
expect(calc.engine.add("0", "0")).toBe("0");
|
||||||
|
|
||||||
|
// 4-bit
|
||||||
|
expect(calc.engine.add("0001", "0001")).toBe("0010"); // 1+1=2
|
||||||
|
expect(calc.engine.add("0111", "0001")).toBe("1000"); // 7+1=8
|
||||||
|
expect(calc.engine.add("1111", "0001")).toBe("0000"); // 15+1=16 -> 0 (wrap)
|
||||||
|
expect(calc.engine.add("1111", "1111")).toBe("1110"); // 15+15=30 -> 14
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mul", () => {
|
||||||
|
// 4-bit unsigned-style values
|
||||||
|
expect(calc.engine.mul("0001", "0011")).toBe("0011"); // 1*3=3
|
||||||
|
expect(calc.engine.mul("0010", "0011")).toBe("0110"); // 2*3=6
|
||||||
|
expect(calc.engine.mul("1111", "0010")).toBe("1110"); // 15*2=30 -> 14
|
||||||
|
|
||||||
|
// two's complement semantics for negatives
|
||||||
|
expect(calc.engine.mul("1111", "0011")).toBe("1101"); // (-1)*3 = -3
|
||||||
|
expect(calc.engine.mul("1111", "1111")).toBe("0001"); // (-1)*(-1) = 1
|
||||||
|
expect(calc.engine.mul("1000", "0010")).toBe("0000"); // (-8)*2 = -16 -> 0
|
||||||
|
|
||||||
|
// 8-bit example
|
||||||
|
expect(calc.engine.mul("11111111", "00000010")).toBe("11111110"); // (-1)*2 = -2
|
||||||
|
});
|
||||||
|
|
||||||
it("lshift", () => {
|
it("lshift", () => {
|
||||||
expect(calc.engine.lshift("1", 1)).toBe("0");
|
expect(calc.engine.lshift("1", 1)).toBe("0");
|
||||||
expect(calc.engine.lshift("01", 1)).toBe("10");
|
expect(calc.engine.lshift("01", 1)).toBe("10");
|
||||||
@@ -263,4 +431,4 @@ describe("calc.engine.", () => {
|
|||||||
expect(calc.engine.applyTwosComplement("10101100")).toBe("01010100");
|
expect(calc.engine.applyTwosComplement("10101100")).toBe("01010100");
|
||||||
expect(calc.engine.applyTwosComplement("01010100")).toBe("10101100"); // reverse
|
expect(calc.engine.applyTwosComplement("01010100")).toBe("10101100"); // reverse
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
117
src/core/calc.ts
117
src/core/calc.ts
@@ -10,7 +10,7 @@ const calc = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
flipBit: function(num: Integer | JsNumber, bitIndex: number): Integer {
|
flipBit: function(num: Integer | JsNumber, bitIndex: number): Integer {
|
||||||
return this._applySingle(asInteger(num), (bin) => this.engine.flipBit(bin, bitIndex));
|
return this._executeForSingleOperand(asInteger(num), (bin) => this.engine.flipBit(bin, bitIndex));
|
||||||
},
|
},
|
||||||
|
|
||||||
promoteTo64Bit(number: Integer) : Integer {
|
promoteTo64Bit(number: Integer) : Integer {
|
||||||
@@ -33,9 +33,13 @@ const calc = {
|
|||||||
case ">>": return this.rshift(op1, op2.value);
|
case ">>": return this.rshift(op1, op2.value);
|
||||||
case ">>>": return this.urshift(op1, op2.value);
|
case ">>>": return this.urshift(op1, op2.value);
|
||||||
case "<<": return this.lshift(op1, op2.value);
|
case "<<": return this.lshift(op1, op2.value);
|
||||||
case "&": return this.and(op1,op2);
|
case "&": return this.and(op1, op2);
|
||||||
case "|": return this.or(op1,op2);
|
case "|": return this.or(op1, op2);
|
||||||
case "^": return this.xor(op1,op2);
|
case "^": return this.xor(op1, op2);
|
||||||
|
case "+": return this.add(op1, op2);
|
||||||
|
case "-": return this.sub(op1, op2);
|
||||||
|
case "*": return this.mul(op1, op2);
|
||||||
|
case "/": return this.div(op1, op2);
|
||||||
default: throw new Error(operator + " operator is not supported");
|
default: throw new Error(operator + " operator is not supported");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -63,7 +67,7 @@ const calc = {
|
|||||||
|
|
||||||
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
||||||
|
|
||||||
return this._applySingle(num, bin => this.engine.lshift(bin, bytes));
|
return this._executeForSingleOperand(num, bin => this.engine.lshift(bin, bytes));
|
||||||
},
|
},
|
||||||
|
|
||||||
rshift (num : Integer, numBytes : JsNumber) : Integer {
|
rshift (num : Integer, numBytes : JsNumber) : Integer {
|
||||||
@@ -75,7 +79,7 @@ const calc = {
|
|||||||
|
|
||||||
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
||||||
|
|
||||||
return this._applySingle(num, bin => this.engine.rshift(bin, bytes));
|
return this._executeForSingleOperand(num, bin => this.engine.rshift(bin, bytes));
|
||||||
},
|
},
|
||||||
|
|
||||||
urshift (num : Integer, numBytes : JsNumber) : Integer {
|
urshift (num : Integer, numBytes : JsNumber) : Integer {
|
||||||
@@ -87,26 +91,42 @@ const calc = {
|
|||||||
|
|
||||||
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
while(bytes > num.maxBitSize) bytes -= num.maxBitSize;
|
||||||
|
|
||||||
return this._applySingle(num, bin => this.engine.urshift(bin, bytes));
|
return this._executeForSingleOperand(num, bin => this.engine.urshift(bin, bytes));
|
||||||
},
|
},
|
||||||
|
|
||||||
not(num:Integer) : Integer {
|
not(num:Integer) : Integer {
|
||||||
return this._applySingle(num, this.engine.not);
|
return this._executeForSingleOperand(num, this.engine.not);
|
||||||
},
|
},
|
||||||
|
|
||||||
and (num1 : Integer, num2 : Integer) : Integer {
|
and (num1 : Integer, num2 : Integer) : Integer {
|
||||||
return this._applyTwo(num1, num2, this.engine.and);
|
return this._executeForTwoOperands(num1, num2, this.engine.and);
|
||||||
},
|
},
|
||||||
|
|
||||||
or (num1 : Integer, num2 : Integer) : Integer {
|
or (num1 : Integer, num2 : Integer) : Integer {
|
||||||
return this._applyTwo(num1, num2, this.engine.or);
|
return this._executeForTwoOperands(num1, num2, this.engine.or);
|
||||||
},
|
},
|
||||||
|
|
||||||
xor (num1 : Integer, num2 : Integer) : Integer {
|
xor (num1 : Integer, num2 : Integer) : Integer {
|
||||||
return this._applyTwo(num1, num2, this.engine.xor);
|
return this._executeForTwoOperands(num1, num2, this.engine.xor);
|
||||||
|
},
|
||||||
|
|
||||||
|
mul (num1: Integer, num2: Integer) : Integer {
|
||||||
|
return this._executeForTwoOperands(num1, num2, this.engine.mul);
|
||||||
|
},
|
||||||
|
|
||||||
|
sub (num1: Integer, num2: Integer) : Integer {
|
||||||
|
return this._executeForTwoOperands(num1, num2, this.engine.sub);
|
||||||
|
},
|
||||||
|
|
||||||
|
div (num1: Integer, num2: Integer) : Integer {
|
||||||
|
return this._executeForTwoOperands(num1, num2, this.engine.div);
|
||||||
},
|
},
|
||||||
|
|
||||||
_applySingle(num: Integer, operation: (bin:string) => string) : Integer {
|
add(num1: Integer, num2: Integer) : Integer {
|
||||||
|
return this._executeForTwoOperands(num1, num2, this.engine.add);
|
||||||
|
},
|
||||||
|
|
||||||
|
_executeForSingleOperand(num: Integer, operation: (bin:string) => string) : Integer {
|
||||||
|
|
||||||
let bin = this.toBinaryString(num).padStart(num.maxBitSize, num.value < 0 ? '1' : '0');
|
let bin = this.toBinaryString(num).padStart(num.maxBitSize, num.value < 0 ? '1' : '0');
|
||||||
|
|
||||||
@@ -123,7 +143,7 @@ const calc = {
|
|||||||
return new Integer(typeof num.value === "bigint" ? result : asIntN(result), num.maxBitSize, num.signed);
|
return new Integer(typeof num.value === "bigint" ? result : asIntN(result), num.maxBitSize, num.signed);
|
||||||
},
|
},
|
||||||
|
|
||||||
_applyTwo(op1: Integer, op2: Integer, operation: (bin1:string, bin2:string) => string) : Integer {
|
_executeForTwoOperands(op1: Integer, op2: Integer, operation: (bin1:string, bin2:string) => string) : Integer {
|
||||||
|
|
||||||
if(op1.maxBitSize === op2.maxBitSize && op1.signed !== op2.signed)
|
if(op1.maxBitSize === op2.maxBitSize && op1.signed !== op2.signed)
|
||||||
throw new Error("This operation cannot be applied to signed and unsigned operands of the same size");
|
throw new Error("This operation cannot be applied to signed and unsigned operands of the same size");
|
||||||
@@ -209,6 +229,72 @@ const calc = {
|
|||||||
|
|
||||||
return result.join('');
|
return result.join('');
|
||||||
},
|
},
|
||||||
|
add (bin1: string, bin2:string) : string {
|
||||||
|
checkSameLength(bin1, bin2);
|
||||||
|
const len = bin1.length;
|
||||||
|
let carry = 0;
|
||||||
|
const out: string[] = new Array(len);
|
||||||
|
|
||||||
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
|
const b1 = bin1[i] === '1' ? 1 : 0;
|
||||||
|
const b2 = bin2[i] === '1' ? 1 : 0;
|
||||||
|
const sum = b1 + b2 + carry;
|
||||||
|
out[i] = (sum % 2) === 1 ? '1' : '0';
|
||||||
|
carry = sum >= 2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overflow carry is discarded to keep fixed width
|
||||||
|
return out.join('');
|
||||||
|
},
|
||||||
|
mul (bin1: string, bin2: string) : string {
|
||||||
|
checkSameLength(bin1, bin2);
|
||||||
|
const len = bin1.length;
|
||||||
|
|
||||||
|
const toSignedBigInt = (bin: string) => {
|
||||||
|
if (bin[0] === '1') {
|
||||||
|
const mag = BigInt('0b' + calc.engine.applyTwosComplement(bin));
|
||||||
|
return -mag;
|
||||||
|
}
|
||||||
|
return BigInt('0b' + bin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const a = toSignedBigInt(bin1);
|
||||||
|
const b = toSignedBigInt(bin2);
|
||||||
|
const product = a * b;
|
||||||
|
|
||||||
|
const modulo = (BigInt(1) << BigInt(len));
|
||||||
|
const wrapped = ((product % modulo) + modulo) % modulo;
|
||||||
|
return wrapped.toString(2).padStart(len, '0');
|
||||||
|
},
|
||||||
|
sub (bin1: string, bin2: string) : string {
|
||||||
|
checkSameLength(bin1, bin2);
|
||||||
|
// Two's complement subtraction: a - b == a + (-b)
|
||||||
|
const negB = calc.engine.applyTwosComplement(bin2);
|
||||||
|
return calc.engine.add(bin1, negB);
|
||||||
|
},
|
||||||
|
div (bin1: string, bin2: string) : string {
|
||||||
|
checkSameLength(bin1, bin2);
|
||||||
|
const len = bin1.length;
|
||||||
|
|
||||||
|
const toSignedBigInt = (bin: string) => {
|
||||||
|
if (bin[0] === '1') {
|
||||||
|
const mag = BigInt('0b' + calc.engine.applyTwosComplement(bin));
|
||||||
|
return -mag;
|
||||||
|
}
|
||||||
|
return BigInt('0b' + bin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const a = toSignedBigInt(bin1);
|
||||||
|
const b = toSignedBigInt(bin2);
|
||||||
|
if (b === BigInt(0))
|
||||||
|
throw new Error('Division by zero');
|
||||||
|
|
||||||
|
const quotient = a / b; // BigInt division truncates toward zero
|
||||||
|
|
||||||
|
const modulo = (BigInt(1) << BigInt(len));
|
||||||
|
const wrapped = (((quotient % modulo) + modulo) % modulo);
|
||||||
|
return wrapped.toString(2).padStart(len, '0');
|
||||||
|
},
|
||||||
flipBit(bin: string, bitIndex : number) : string {
|
flipBit(bin: string, bitIndex : number) : string {
|
||||||
return bin.substring(0, bitIndex) + flip(bin[bitIndex]) + bin.substring(bitIndex+1)
|
return bin.substring(0, bitIndex) + flip(bin[bitIndex]) + bin.substring(bitIndex+1)
|
||||||
},
|
},
|
||||||
@@ -252,12 +338,13 @@ function nextPowOfTwo(num: number) : number {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Promotes both numbers to the same size using the size of the bigger one
|
||||||
function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] {
|
function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] {
|
||||||
if(n1.maxBitSize === n2.maxBitSize)
|
if(n1.maxBitSize === n2.maxBitSize)
|
||||||
{
|
{
|
||||||
if(n1.signed === n2.signed) return [n1,n2];
|
if(n1.signed === n2.signed) return [n1,n2];
|
||||||
|
|
||||||
// Example int and usinged int. Poromoted both yo 64 bit
|
// Example int and usinged int. Poromoted both to 64 bit
|
||||||
return [n1.resize(n1.maxBitSize*2).toSigned(), n2.resize(n2.maxBitSize*2).toSigned()];
|
return [n1.resize(n1.maxBitSize*2).toSigned(), n2.resize(n2.maxBitSize*2).toSigned()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,4 +361,4 @@ function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] {
|
|||||||
Console.WriteLine(Convert.ToString(r,2).PadLeft(32, '0'));
|
Console.WriteLine(Convert.ToString(r,2).PadLeft(32, '0'));
|
||||||
Console.WriteLine(Convert.ToString(r));
|
Console.WriteLine(Convert.ToString(r));
|
||||||
Console.WriteLine(r.GetType().Name);
|
Console.WriteLine(r.GetType().Name);
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { Operator, Operand, ListOfNumbers } from '../expression';
|
|||||||
import calc from '../../core/calc';
|
import calc from '../../core/calc';
|
||||||
import { Integer } from '../../core/Integer';
|
import { Integer } from '../../core/Integer';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faInfoCircle, faUndo } from '@fortawesome/free-solid-svg-icons';
|
import { faArrowRotateLeft as iconUndo } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { faCircleQuestion as iconTip } from '@fortawesome/free-regular-svg-icons';
|
||||||
import loglevel from 'loglevel';
|
import loglevel from 'loglevel';
|
||||||
import IconWithToolTip from '../../shell/components/IconWithTooltip';
|
import IconWithToolTip from '../../shell/components/IconWithTooltip';
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
|
|||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (this.scalar.value.value < 0)
|
if (this.scalar.value.value < 0)
|
||||||
buttons.push(<IconWithToolTip icon={faInfoCircle}>
|
buttons.push(<IconWithToolTip icon={iconTip}>
|
||||||
<div className='accent1 tooltip-header'>Two's Complement</div>
|
<div className='accent1 tooltip-header'>Two's Complement</div>
|
||||||
<p>
|
<p>
|
||||||
This is a negative number. It's binary representation is <u>inverted</u> using <strong>Two's Complement</strong> operation.
|
This is a negative number. It's binary representation is <u>inverted</u> using <strong>Two's Complement</strong> operation.
|
||||||
@@ -159,7 +160,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
|
|||||||
</IconWithToolTip>)
|
</IconWithToolTip>)
|
||||||
|
|
||||||
if (!this.originalValue.isTheSame(this.scalar.value))
|
if (!this.originalValue.isTheSame(this.scalar.value))
|
||||||
buttons.push(<button title='Undo all changes' className='undo' data-control="undo" onClick={() => this.undo()}><FontAwesomeIcon icon={faUndo} /></button>);
|
buttons.push(<button title='Undo all changes' className='undo' data-control="undo" onClick={() => this.undo()}><FontAwesomeIcon icon={iconUndo} /></button>);
|
||||||
|
|
||||||
return <React.Fragment>{buttons}</React.Fragment>
|
return <React.Fragment>{buttons}</React.Fragment>
|
||||||
}
|
}
|
||||||
@@ -263,4 +264,4 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
|
|||||||
|
|
||||||
return <React.Fragment>{children}</React.Fragment>
|
return <React.Fragment>{children}</React.Fragment>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ describe("expression parser", () => {
|
|||||||
expect(parser.parse("~1")).toBeInstanceOf(BitwiseOperation);
|
expect(parser.parse("~1")).toBeInstanceOf(BitwiseOperation);
|
||||||
expect(parser.parse("1^2")).toBeInstanceOf(BitwiseOperation);
|
expect(parser.parse("1^2")).toBeInstanceOf(BitwiseOperation);
|
||||||
expect(parser.parse("1|2")).toBeInstanceOf(BitwiseOperation);
|
expect(parser.parse("1|2")).toBeInstanceOf(BitwiseOperation);
|
||||||
|
expect(parser.parse("1*2")).toBeInstanceOf(BitwiseOperation);
|
||||||
|
expect(parser.parse("1-2")).toBeInstanceOf(BitwiseOperation);
|
||||||
|
expect(parser.parse("1/2")).toBeInstanceOf(BitwiseOperation);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("parses big binary bitwise expression", () => {
|
it("parses big binary bitwise expression", () => {
|
||||||
@@ -30,6 +33,46 @@ describe("expression parser", () => {
|
|||||||
expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('2863311360');
|
expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('2863311360');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("parses addition operation", () => {
|
||||||
|
const expr = parser.parse("23 + 34") as BitwiseOperation;
|
||||||
|
expect(expr.children.length).toBe(2);
|
||||||
|
|
||||||
|
expect(expr.children[1]).toBeInstanceOf(Operator);
|
||||||
|
expect((expr.children[1] as Operator).operator).toBe("+");
|
||||||
|
expect(expr.children[0].getUnderlyingOperand().value.toString()).toBe('23');
|
||||||
|
expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('34')
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses multiplication operation", () => {
|
||||||
|
const expr = parser.parse("23 * 34") as BitwiseOperation;
|
||||||
|
expect(expr.children.length).toBe(2);
|
||||||
|
|
||||||
|
expect(expr.children[1]).toBeInstanceOf(Operator);
|
||||||
|
expect((expr.children[1] as Operator).operator).toBe("*");
|
||||||
|
expect(expr.children[0].getUnderlyingOperand().value.toString()).toBe('23');
|
||||||
|
expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('34')
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses division operation", () => {
|
||||||
|
const expr = parser.parse("23 / 34") as BitwiseOperation;
|
||||||
|
expect(expr.children.length).toBe(2);
|
||||||
|
|
||||||
|
expect(expr.children[1]).toBeInstanceOf(Operator);
|
||||||
|
expect((expr.children[1] as Operator).operator).toBe("/");
|
||||||
|
expect(expr.children[0].getUnderlyingOperand().value.toString()).toBe('23');
|
||||||
|
expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('34')
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses subtraction operation", () => {
|
||||||
|
const expr = parser.parse("23 - 34") as BitwiseOperation;
|
||||||
|
expect(expr.children.length).toBe(2);
|
||||||
|
|
||||||
|
expect(expr.children[1]).toBeInstanceOf(Operator);
|
||||||
|
expect((expr.children[1] as Operator).operator).toBe("-");
|
||||||
|
expect(expr.children[0].getUnderlyingOperand().value.toString()).toBe('23');
|
||||||
|
expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('34')
|
||||||
|
});
|
||||||
|
|
||||||
it("pares multiple operand expression", () => {
|
it("pares multiple operand expression", () => {
|
||||||
const result = parser.parse("1^2") as BitwiseOperation;
|
const result = parser.parse("1^2") as BitwiseOperation;
|
||||||
expect(result.children.length).toBe(2);
|
expect(result.children.length).toBe(2);
|
||||||
@@ -61,6 +104,60 @@ describe("expression parser", () => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("multiplication", () => {
|
||||||
|
it("evaluates simple products", () => {
|
||||||
|
const expr = parser.parse("2*3") as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand);
|
||||||
|
expect(res.value.toString()).toBe('6');
|
||||||
|
|
||||||
|
const expr2 = parser.parse("-2*3") as BitwiseOperation;
|
||||||
|
const res2 = (expr2.children[1] as Operator).evaluate(expr2.children[0] as Operand);
|
||||||
|
expect(res2.value.toString()).toBe('-6');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("wraps on 32-bit overflow", () => {
|
||||||
|
const expr = parser.parse("2147483647*2") as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand);
|
||||||
|
expect(res.value.toString()).toBe('-2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("wraps on 64-bit overflow", () => {
|
||||||
|
const expr = parser.parse("9223372036854775807l*2l") as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand);
|
||||||
|
expect(res.value.toString()).toBe('-2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("division", () => {
|
||||||
|
it("evaluates simple quotients with truncation", () => {
|
||||||
|
const expr1 = parser.parse("7/2") as BitwiseOperation; // -> 3
|
||||||
|
const res1 = (expr1.children[1] as Operator).evaluate(expr1.children[0] as Operand);
|
||||||
|
expect(res1.value.toString()).toBe('3');
|
||||||
|
|
||||||
|
const expr2 = parser.parse("-7/2") as BitwiseOperation; // -> -3
|
||||||
|
const res2 = (expr2.children[1] as Operator).evaluate(expr2.children[0] as Operand);
|
||||||
|
expect(res2.value.toString()).toBe('-3');
|
||||||
|
|
||||||
|
const expr3 = parser.parse("1/2") as BitwiseOperation; // -> 0
|
||||||
|
const res3 = (expr3.children[1] as Operator).evaluate(expr3.children[0] as Operand);
|
||||||
|
expect(res3.value.toString()).toBe('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles INT_MIN/-1 based on parsed width", () => {
|
||||||
|
// Parser promotes -2147483648 to 64-bit (abs value exceeds INT32_MAX),
|
||||||
|
// so the result is a positive 64-bit 2147483648.
|
||||||
|
const expr = parser.parse("-2147483648/-1") as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand);
|
||||||
|
expect(res.value.toString()).toBe('2147483648');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("64-bit division", () => {
|
||||||
|
const expr = parser.parse("9223372036854775807l/2l") as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand);
|
||||||
|
expect(res.value.toString()).toBe('4611686018427387903');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("comparison with nodejs engine", () => {
|
describe("comparison with nodejs engine", () => {
|
||||||
|
|
||||||
it('set 32-bit', () => {
|
it('set 32-bit', () => {
|
||||||
@@ -81,7 +178,7 @@ describe("comparison with nodejs engine", () => {
|
|||||||
|
|
||||||
it('random: two inbary strings 64-bit', () => {
|
it('random: two inbary strings 64-bit', () => {
|
||||||
|
|
||||||
const signs = ["|", "&", "^", "<<", ">>", ">>>"]
|
const signs = ["|", "&", "^", "<<", ">>", ">>>", "*", "/", "-"]
|
||||||
|
|
||||||
for(var i =0; i<1000; i++){
|
for(var i =0; i<1000; i++){
|
||||||
|
|
||||||
@@ -92,7 +189,34 @@ describe("comparison with nodejs engine", () => {
|
|||||||
|
|
||||||
const input = op1.toString() + sign + op2.toString();
|
const input = op1.toString() + sign + op2.toString();
|
||||||
|
|
||||||
testBinary(input, input);
|
if (sign === "*") {
|
||||||
|
// Use BigInt to avoid precision issues and wrap to 32-bit two's complement
|
||||||
|
const modulo = (BigInt(1) << BigInt(32));
|
||||||
|
const expectedNum = ((BigInt(op1) * BigInt(op2)) % modulo + modulo) % modulo;
|
||||||
|
const expectedSigned = expectedNum >= (BigInt(1) << BigInt(31)) ? expectedNum - modulo : expectedNum;
|
||||||
|
const expr = parser.parse(input) as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand).value.toString();
|
||||||
|
expect(res).toBe(expectedSigned.toString());
|
||||||
|
} else if (sign === "/") {
|
||||||
|
const denom = op2 === 0 ? 1 : op2;
|
||||||
|
const q = BigInt(op1) / BigInt(denom); // trunc toward zero
|
||||||
|
const modulo = (BigInt(1) << BigInt(32));
|
||||||
|
const expectedNum = ((q % modulo) + modulo) % modulo;
|
||||||
|
const expectedSigned = expectedNum >= (BigInt(1) << BigInt(31)) ? expectedNum - modulo : expectedNum;
|
||||||
|
const expr = parser.parse(op1.toString() + '/' + denom.toString()) as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand).value.toString();
|
||||||
|
expect(res).toBe(expectedSigned.toString());
|
||||||
|
} else if (sign === "-") {
|
||||||
|
const diff = BigInt(op1) - BigInt(op2);
|
||||||
|
const modulo = (BigInt(1) << BigInt(32));
|
||||||
|
const expectedNum = ((diff % modulo) + modulo) % modulo;
|
||||||
|
const expectedSigned = expectedNum >= (BigInt(1) << BigInt(31)) ? expectedNum - modulo : expectedNum;
|
||||||
|
const expr = parser.parse(input) as BitwiseOperation;
|
||||||
|
const res = (expr.children[1] as Operator).evaluate(expr.children[0] as Operand).value.toString();
|
||||||
|
expect(res).toBe(expectedSigned.toString());
|
||||||
|
} else {
|
||||||
|
testBinary(input, input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,4 +301,4 @@ describe("comparison with nodejs engine", () => {
|
|||||||
|
|
||||||
expect(actual).toBe(expected);
|
expect(actual).toBe(expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
|||||||
regex: RegExp;
|
regex: RegExp;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.fullRegex = /^[-,~,<,>,&,^|,b,x,l,s,u,a-f,0-9,\s]+$/i;
|
this.fullRegex = /^[-,~,<,>,&,^|,*,/,-,b,x,l,s,u,a-f,0-9,+\s]+$/i;
|
||||||
this.regex = /(<<|>>|>>>|\||&|\^)?(~?-?(?:[b,x,l,s,u,,a-f,0-9]+))/gi;
|
this.regex = /(<<|>>|>>>|\||&|\^|\+|\*|\/|(?<!^)-)?(~?-?(?:[b,x,l,s,u,,a-f,0-9]+))/gi;
|
||||||
}
|
}
|
||||||
|
|
||||||
canCreate (input: string) : boolean {
|
canCreate (input: string) : boolean {
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ html { height: 100% }
|
|||||||
/* Theme-scoped code styling to avoid cross-theme overrides */
|
/* Theme-scoped code styling to avoid cross-theme overrides */
|
||||||
.light code, .dark code, .midnight code, .bladerunner code { font-size: 1.2em; font-weight: bold; }
|
.light code, .dark code, .midnight code, .bladerunner code { font-size: 1.2em; font-weight: bold; }
|
||||||
|
|
||||||
|
h1 { font-family: Impact, Verdana;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
.icon { margin-right: 5px; vertical-align: middle; }
|
.icon { margin-right: 5px; vertical-align: middle; }
|
||||||
.header-cmd { color: #c5c5c5 }
|
.header-cmd { color: #c5c5c5 }
|
||||||
|
|
||||||
@@ -217,13 +221,14 @@ button.link-button {text-decoration: underline;}
|
|||||||
.bladerunner .solid-background { background: #0b0f14; }
|
.bladerunner .solid-background { background: #0b0f14; }
|
||||||
.bladerunner .header-cmd { color: #ff7fb0a8 !important; }
|
.bladerunner .header-cmd { color: #ff7fb0a8 !important; }
|
||||||
.bladerunner .lights-on .header-cmd { color: #ff7fb0 !important; }
|
.bladerunner .lights-on .header-cmd { color: #ff7fb0 !important; }
|
||||||
/* Rule for element when it has both .header and .on */
|
|
||||||
.bladerunner .header.lights-on { text-shadow: 0 0 4px rgba(255, 127, 176, 0.35), 0 0 9px rgba(255, 127, 176, 0.22); }
|
.bladerunner .header.lights-on { text-shadow: 0 0 4px rgba(255, 127, 176, 0.35), 0 0 9px rgba(255, 127, 176, 0.22); }
|
||||||
.bladerunner .header h1 { text-transform: uppercase; color: #66d9e8a8; font-family: Impact, Verdana, sans-serif; cursor: pointer; position: relative; z-index: 1; letter-spacing: 0.02em; }
|
.bladerunner .header h1 { text-transform: uppercase; color: #66d9e8a8; cursor: pointer; position: relative; z-index: 1; font-weight: bold; user-select: none;}
|
||||||
.bladerunner .header h1.lights-on { color: #66d9e8; text-shadow: 0 0 4px rgba(102, 217, 232, 0.35), 0 0 9px rgba(102, 217, 232, 0.22); }
|
|
||||||
|
.bladerunner .header h1.lights-on { color: #66d9e8; text-shadow: 0 0 4px rgba(102, 217, 232, 0.35), 0 0 9px rgba(102, 217, 232, 0.22); }
|
||||||
.bladerunner .header h1.lights-on::after {
|
.bladerunner .header h1.lights-on::after {
|
||||||
/* Neon-style halo: layered cyan + magenta radial glows */
|
/* Neon-style halo: layered cyan + magenta radial glows */
|
||||||
content: "";
|
content: "";
|
||||||
|
animation: flicker 1s forwards;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -243,6 +248,21 @@ button.link-button {text-decoration: underline;}
|
|||||||
rgba(0, 0, 0, 0) 72%);
|
rgba(0, 0, 0, 0) 72%);
|
||||||
filter: blur(35px);
|
filter: blur(35px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes flicker {
|
||||||
|
20%, 24%, 55% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
.bladerunner {color: #e6f0ff;}
|
.bladerunner {color: #e6f0ff;}
|
||||||
.bladerunner .expression { color: white; }
|
.bladerunner .expression { color: white; }
|
||||||
.bladerunner .expressionInput { color: white; }
|
.bladerunner .expressionInput { color: white; }
|
||||||
@@ -260,17 +280,16 @@ button.link-button {text-decoration: underline;}
|
|||||||
.bladerunner button.btn { color: #00eaff }
|
.bladerunner button.btn { color: #00eaff }
|
||||||
.bladerunner button.btn:hover { background: #11222c }
|
.bladerunner button.btn:hover { background: #11222c }
|
||||||
.bladerunner button.btn:disabled { color: #0e6e7e; background-color: inherit; }
|
.bladerunner button.btn:disabled { color: #0e6e7e; background-color: inherit; }
|
||||||
.bladerunner .accent1 { color: mediumseagreen }
|
.bladerunner .accent1 { color: #e3a600 }
|
||||||
.bladerunner .accent1-border { border-color: mediumseagreen }
|
.bladerunner .accent1 button:hover { text-shadow: 0 0 2px #e3a600 }
|
||||||
|
.bladerunner .accent1-border { border-color: #e3a600 }
|
||||||
.bladerunner .button { border-color: #00eaff }
|
.bladerunner .button { border-color: #00eaff }
|
||||||
.bladerunner code { color: #e3a600 }
|
.bladerunner code { color: #e3a600 }
|
||||||
.bladerunner code a, .bladerunner code a:visited { color: #e3a600 }
|
.bladerunner code a, .bladerunner code a:visited { color: #e3a600 }
|
||||||
.bladerunner .command-link { color: #5fc2e9 }
|
.bladerunner .command-link { color: #5fc2e9 }
|
||||||
.bladerunner .command-link:hover,
|
.bladerunner .command-link:hover,
|
||||||
.bladerunner .command-link:focus {
|
.bladerunner .command-link:focus { text-shadow: 0 0 2px rgba(69, 243, 255, 0.949); }
|
||||||
/* very dim magenta glow */
|
|
||||||
text-shadow: 0 0 2px rgba(255, 127, 176, 0.5);
|
|
||||||
}
|
|
||||||
.bladerunner .soft { color: #aebed0 }
|
.bladerunner .soft { color: #aebed0 }
|
||||||
.bladerunner .solid-border { background-color: #0e161d; border-color: #1f3b4a;}
|
.bladerunner .solid-border { background-color: #0e161d; border-color: #1f3b4a;}
|
||||||
.bladerunner .expressionInput {border-color: #1f3b4a;}
|
.bladerunner .expressionInput {border-color: #1f3b4a;}
|
||||||
@@ -279,18 +298,20 @@ button.link-button {text-decoration: underline;}
|
|||||||
.bladerunner .expression {color: #e6f0ff;}
|
.bladerunner .expression {color: #e6f0ff;}
|
||||||
.bladerunner .settings-button button {color: #b9cfe2;}
|
.bladerunner .settings-button button {color: #b9cfe2;}
|
||||||
.bladerunner button.hashLink, .bladerunner a.hashLink, .bladerunner a.hashLink:visited { color: #52687b; }
|
.bladerunner button.hashLink, .bladerunner a.hashLink, .bladerunner a.hashLink:visited { color: #52687b; }
|
||||||
.bladerunner button.hashLink:hover, .bladerunner a.hashLink:hover {
|
.bladerunner button.hashLink:hover, .bladerunner .undo button:hover, .bladerunner a.hashLink:hover {
|
||||||
/* subtle glow derived from current hover color */
|
/* subtle glow derived from current hover color */
|
||||||
--text-shadow: 0 0 2px currentColor;
|
--text-shadow: 0 0 2px currentColor;
|
||||||
text-shadow:0 0 2px rgb(229, 33, 0.5);
|
text-shadow:0 0 2px rgb(229, 33, 0.5);
|
||||||
color: #5fc2e9;
|
color: #5fc2e9;
|
||||||
}
|
}
|
||||||
|
.bladerunner .undo button:hover { opacity: 1; }
|
||||||
.bladerunner .cur { color: #6c8497; }
|
.bladerunner .cur { color: #6c8497; }
|
||||||
|
|
||||||
/* Blade Runner: subtle glow for SVG icons inside hash links on hover/focus */
|
/* Blade Runner: subtle glow for SVG icons inside hash links on hover/focus */
|
||||||
.bladerunner .hashLink:hover .icon,
|
.bladerunner .hashLink:hover .icon,
|
||||||
.bladerunner .hashLink:focus .icon,
|
.bladerunner .hashLink:focus .icon,
|
||||||
.bladerunner .hashLink:hover svg,
|
.bladerunner .hashLink:hover svg,
|
||||||
|
.bladerunner .undo:hover svg,
|
||||||
.bladerunner .hashLink:focus svg {
|
.bladerunner .hashLink:focus svg {
|
||||||
filter: drop-shadow(0 0 2px currentColor);
|
filter: drop-shadow(0 0 2px currentColor);
|
||||||
}
|
}
|
||||||
@@ -304,9 +325,31 @@ button.link-button {text-decoration: underline;}
|
|||||||
.bladerunner .top-links button:focus {
|
.bladerunner .top-links button:focus {
|
||||||
/* very dim magenta glow */
|
/* very dim magenta glow */
|
||||||
text-shadow:0 0 2px rgb(229, 33, 0.5);
|
text-shadow:0 0 2px rgb(229, 33, 0.5);
|
||||||
color: #FFBF00;
|
color: hsl(45, 100%, 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
.bladerunner .top-links li {position: relative;}
|
||||||
|
.bladerunner .top-links button:hover::after, .bladerunner .top-links a:hover::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
right: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(50%, -50%);
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 200%;
|
||||||
|
height: 120%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at center,
|
||||||
|
rgba(227, 166, 0, 0.85) 0%,
|
||||||
|
rgba(227, 166, 0, 0.55) 22%,
|
||||||
|
rgba(0, 0, 0, 0) 60%);
|
||||||
|
filter: blur(20px);
|
||||||
|
}*/
|
||||||
|
|
||||||
/* Blade Runner: add matching glow to SVG icons inside top-links on hover/focus */
|
/* Blade Runner: add matching glow to SVG icons inside top-links on hover/focus */
|
||||||
.bladerunner .top-links a:hover .icon,
|
.bladerunner .top-links a:hover .icon,
|
||||||
.bladerunner .top-links a:focus .icon,
|
.bladerunner .top-links a:focus .icon,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
|
||||||
export const APP_VERSION = 9;
|
export const APP_VERSION = 10;
|
||||||
|
|
||||||
export type PersistedAppData = {
|
export type PersistedAppData = {
|
||||||
emphasizeBytes: boolean;
|
emphasizeBytes: boolean;
|
||||||
|
|||||||
@@ -19,29 +19,18 @@ const toggleLights = (): void => {
|
|||||||
if (lightIsOn)
|
if (lightIsOn)
|
||||||
turnLightsOff();
|
turnLightsOff();
|
||||||
else
|
else
|
||||||
turnLightsOnWithFlickering();
|
turnLightsOn();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const turnLightsOnWithFlickering = (): void => {
|
const turnLightsOn = (): void => {
|
||||||
const header = getHeader();
|
const header = getHeader();
|
||||||
const flickers = [true, false, true, false, true, false, true];
|
header.classList.add(LIGHTS_ON_CLASS);
|
||||||
|
}
|
||||||
log.info("Turning lights on with flickering");
|
|
||||||
|
|
||||||
flickers.forEach((flicker, index) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (flicker)
|
|
||||||
header.classList.add(LIGHTS_ON_CLASS);
|
|
||||||
else
|
|
||||||
header.classList.remove(LIGHTS_ON_CLASS);
|
|
||||||
}, index * 100);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const startLightsIfWereOnAfterDelay = (): void => {
|
const startLightsIfWereOnAfterDelay = (): void => {
|
||||||
if (getLightIsOn())
|
if (getLightIsOn())
|
||||||
setTimeout(turnLightsOnWithFlickering, 500);
|
setTimeout(turnLightsOn, 500);
|
||||||
else
|
else
|
||||||
log.info("Lights are off by user preference");
|
log.info("Lights are off by user preference");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function HelpResultView() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="right-panel">
|
<div className="right-panel">
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div className="section-title soft">Supported Bitwise Operations</div>
|
<div className="section-title soft">Supported Operations</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>&</code> — bitwise AND</li>
|
<li><code>&</code> — bitwise AND</li>
|
||||||
<li><code>|</code> — bitwise inclusive OR</li>
|
<li><code>|</code> — bitwise inclusive OR</li>
|
||||||
@@ -57,10 +57,23 @@ function HelpResultView() {
|
|||||||
<li><code><<</code> — left shift</li>
|
<li><code><<</code> — left shift</li>
|
||||||
<li><code>>></code> — sign propagating right shift</li>
|
<li><code>>></code> — sign propagating right shift</li>
|
||||||
<li><code>>>></code> — zero-fill right shift</li>
|
<li><code>>>></code> — zero-fill right shift</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><code>+</code> — addition</li>
|
||||||
|
<li><code>-</code> — subtraction</li>
|
||||||
|
<li><code>*</code> — multiplication</li>
|
||||||
|
<li><code>/</code> — division (truncates toward zero*)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='important-note'>
|
<div className='important-note'>
|
||||||
<FontAwesomeIcon icon={faCircleExclamation} size='lg'/> <a target='_blank' href='https://en.cppreference.com/w/c/language/operator_precedence' rel="noreferrer">Operator precedence</a> is IGNORED. Operators are executed <strong>left-to-right</strong>.
|
<FontAwesomeIcon icon={faCircleExclamation} size='lg'/> <a target='_blank' href='https://en.cppreference.com/w/c/language/operator_precedence' rel="noreferrer">Operator precedence</a> is IGNORED. Operators are executed <strong>left-to-right</strong>.
|
||||||
</div>
|
</div>
|
||||||
|
<p className='description'>
|
||||||
|
* “Truncates toward zero” means the fractional part is discarded and the quotient is rounded toward 0.
|
||||||
|
For example: <code>7/2 = 3</code>, <code>-7/2 = -3</code>.
|
||||||
|
Learn more:
|
||||||
|
<a target='_blank' rel='noreferrer' href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Division#bigint_division'> JS BigInt division</a>,
|
||||||
|
<a target='_blank' rel='noreferrer' href='https://en.cppreference.com/w/c/language/operator_arithmetic#Integer_arithmetic'> C integer division</a>.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="section soft-border">
|
<div className="section soft-border">
|
||||||
<div className="section-title soft">Supported Number Types</div>
|
<div className="section-title soft">Supported Number Types</div>
|
||||||
@@ -86,3 +99,4 @@ function HelpResultView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default HelpResultView;
|
export default HelpResultView;
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ function SettingsPane(props : SettingsPaneProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className='setting'>
|
<div className='setting'>
|
||||||
<button type="button" onClick={() => appState.toggleDimExtrBits()}>
|
<button type="button" onClick={() => appState.toggleDimExtrBits()}>
|
||||||
<FontAwesomeIcon size='xl' icon={appState.dimExtraBits ? faToggleOn : faToggleOff} /> Dim Extra Bits
|
<FontAwesomeIcon size='xl' icon={appState.dimExtraBits ? faToggleOn : faToggleOff} /> Dim Padding Bits
|
||||||
</button>
|
</button>
|
||||||
<p className='description'>
|
<p className='description'>
|
||||||
{appState.dimExtraBits
|
{appState.dimExtraBits
|
||||||
? "Extra bits used for padding are now dimmed."
|
? "Extra bits, used for padding to align numbers of different sizes, are dimmed."
|
||||||
: "No bits are dimmed."}
|
: "Extra bits used for padding are not dimmed."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='setting'>
|
<div className='setting'>
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ function WhatsNewResultView() {
|
|||||||
return <div className="changelog">
|
return <div className="changelog">
|
||||||
<h3>Changelog</h3>
|
<h3>Changelog</h3>
|
||||||
<div className='item item-new'>
|
<div className='item item-new'>
|
||||||
|
<p>
|
||||||
|
<span className="soft date">Nov 8th, 2025</span> <br />
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Expressions now support arithmetic operators alongside bitwise ones: <code>+</code>, <code>-</code>, <code>*</code>, and <code>/</code>.</li>
|
||||||
|
<li>Try examples: <code><CommandLink text="15 + 3" /></code>, <code><CommandLink text="10 - 4" /></code>, <code><CommandLink text="3 * -5" /></code>, <code><CommandLink text="7 / 2 + 3" /></code>.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className='item'>
|
||||||
<p>
|
<p>
|
||||||
<span className="soft date">Nov 6th, 2025</span> <br />
|
<span className="soft date">Nov 6th, 2025</span> <br />
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
1
src/types/fontawesome-regular.d.ts
vendored
Normal file
1
src/types/fontawesome-regular.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module '@fortawesome/free-regular-svg-icons';
|
||||||
Reference in New Issue
Block a user