mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2026-01-26 05:34:13 +01:00
Refactor support for 32 an 64 bit numbers (#45)
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { Expression, ExpressionToken } from "./expression-interfaces";
|
||||
import { Expression, ExpressionElement } from "./expression-interfaces";
|
||||
|
||||
export default class BitwiseOperationExpression implements Expression {
|
||||
|
||||
expressionString: string;
|
||||
children: ExpressionToken[];
|
||||
children: ExpressionElement[];
|
||||
|
||||
constructor(expressionString: string, children: ExpressionToken[]) {
|
||||
constructor(expressionString: string, children: ExpressionElement[]) {
|
||||
this.expressionString = expressionString;
|
||||
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 { ScalarToken } from "./expression";
|
||||
import { ExpressionToken } from "./expression-interfaces";
|
||||
import BitwiseOperator from "./BitwiseOperator";
|
||||
import { ScalarValue } from "./expression";
|
||||
import { ExpressionElement } from "./expression-interfaces";
|
||||
import { type } from "os";
|
||||
import { InputType } from "zlib";
|
||||
import exp from "constants";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ScalarToken from "./ScalarToken";
|
||||
import ScalarValue from "./ScalarValue";
|
||||
import ListOfNumbersExpression from "./ListOfNumbersExpression";
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import calc from "../core/calc";
|
||||
import ScalarToken from "./ScalarToken";
|
||||
import { Expression, ExpressionToken } from "./expression-interfaces";
|
||||
import ScalarValue from "./ScalarValue";
|
||||
import { Expression, ExpressionElement } from "./expression-interfaces";
|
||||
|
||||
export default class ListOfNumbersExpression implements Expression {
|
||||
children: ScalarToken[];
|
||||
children: ScalarValue[];
|
||||
expressionString: string;
|
||||
maxBitsLength: number;
|
||||
|
||||
constructor(expressionString: string, numbers: ScalarToken[]) {
|
||||
constructor(expressionString: string, numbers: ScalarValue[]) {
|
||||
this.expressionString = expressionString;
|
||||
this.children = numbers;
|
||||
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', () => {
|
||||
var op = ScalarToken.parse('123');
|
||||
var op = ScalarValue.parse('123');
|
||||
expect(op.base).toBe('dec');
|
||||
expect(op.value).toBe(123);
|
||||
});
|
||||
|
||||
it('parsed from bin string', () => {
|
||||
var op = ScalarToken.parse('0b10');
|
||||
var op = ScalarValue.parse('0b10');
|
||||
expect(op.base).toBe('bin');
|
||||
expect(op.value).toBe(2);
|
||||
});
|
||||
|
||||
it('parsed from hex string', () => {
|
||||
var op = ScalarToken.parse('0x10');
|
||||
var op = ScalarValue.parse('0x10');
|
||||
expect(op.base).toBe('hex');
|
||||
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 BinaryStringView, { FlipBitEventArg } from '../../core/components/BinaryString';
|
||||
import BitwiseResultViewModel from './BitwiseResultViewModel';
|
||||
import { Expression, ExpressionToken } from '../expression-interfaces';
|
||||
import { OperatorToken, ScalarToken } from '../expression';
|
||||
import { Expression, ExpressionElement } from '../expression-interfaces';
|
||||
import { BitwiseOperator, ScalarValue } from '../expression';
|
||||
import calc from '../../core/calc';
|
||||
|
||||
type BitwiseResultViewProps = {
|
||||
expression: Expression;
|
||||
@@ -15,32 +16,51 @@ type BitwiseResultViewState = {
|
||||
}
|
||||
|
||||
export default class BitwiseResultView extends React.Component<BitwiseResultViewProps, BitwiseResultViewState> {
|
||||
maxSeenLengthNumberOfBits: number;
|
||||
|
||||
constructor(props: BitwiseResultViewProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.maxSeenLengthNumberOfBits = 0;
|
||||
}
|
||||
|
||||
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">
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
getRows() : JSX.Element[] {
|
||||
var model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
|
||||
getRows(model: BitwiseResultViewModel): JSX.Element[] {
|
||||
|
||||
return model.items.map((itm, i) =>
|
||||
<ExpressionRow
|
||||
key={i}
|
||||
this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits);
|
||||
|
||||
return model.items.map((itm, i) =>
|
||||
<ExpressionRow
|
||||
key={i}
|
||||
sign={itm.sign}
|
||||
css={itm.css}
|
||||
bitSize={itm.bitSize}
|
||||
allowFlipBits={itm.allowFlipBits}
|
||||
expressionItem={itm.expression}
|
||||
emphasizeBytes={this.props.emphasizeBytes}
|
||||
maxNumberOfBits={model.maxNumberOfBits}
|
||||
emphasizeBytes={this.props.emphasizeBytes}
|
||||
maxNumberOfBits={this.maxSeenLengthNumberOfBits}
|
||||
onBitFlipped={() => this.onBitFlipped()} />);
|
||||
}
|
||||
|
||||
@@ -51,38 +71,42 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
}
|
||||
|
||||
type ExpressionRowProps = {
|
||||
sign: string,
|
||||
css: string,
|
||||
maxNumberOfBits: number,
|
||||
emphasizeBytes: boolean,
|
||||
allowFlipBits: boolean,
|
||||
expressionItem: ExpressionToken,
|
||||
sign: string,
|
||||
css: string,
|
||||
bitSize: number,
|
||||
maxNumberOfBits: number,
|
||||
emphasizeBytes: boolean,
|
||||
allowFlipBits: boolean,
|
||||
expressionItem: ExpressionElement,
|
||||
onBitFlipped: any
|
||||
}
|
||||
|
||||
class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
constructor(props: ExpressionRowProps) {
|
||||
super(props);
|
||||
this.state = { operand: null };
|
||||
}
|
||||
super(props);
|
||||
this.state = { operand: null };
|
||||
}
|
||||
render() {
|
||||
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
|
||||
|
||||
const maxBits = Math.max()
|
||||
|
||||
return <tr className={"row-with-bits " + css}>
|
||||
<td className="sign">{sign}</td>
|
||||
<td className="label">{this.getLabel()}</td>
|
||||
<td className="bin">
|
||||
<BinaryStringView
|
||||
emphasizeBytes={emphasizeBytes}
|
||||
binaryString={formatter.padLeft(this.getBinaryString(), maxNumberOfBits, '0')}
|
||||
allowFlipBits={allowFlipBits}
|
||||
onFlipBit={args => this.flipBit(args)}/>
|
||||
</td>
|
||||
<td className="other">{this.getAlternative()}</td>
|
||||
</tr>;;
|
||||
<td className="sign">{sign}</td>
|
||||
<td className="label">{this.getLabel()}</td>
|
||||
<td className="bin">
|
||||
<BinaryStringView
|
||||
emphasizeBytes={emphasizeBytes}
|
||||
binaryString={formatter.padLeft(this.getBinaryString(), maxNumberOfBits, '0')}
|
||||
allowFlipBits={allowFlipBits}
|
||||
bitSize={this.props.bitSize}
|
||||
onFlipBit={args => this.flipBit(args)} />
|
||||
</td>
|
||||
<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();
|
||||
return formatter.numberToString(v.value, 'bin');
|
||||
}
|
||||
@@ -91,18 +115,18 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
|
||||
// For expressions like |~2
|
||||
// TODO: find a better way...
|
||||
if(this.props.expressionItem.isOperator) {
|
||||
const ex = this.props.expressionItem as OperatorToken;
|
||||
if (this.props.expressionItem.isOperator) {
|
||||
const ex = this.props.expressionItem as BitwiseOperator;
|
||||
return ex.operator + this.getLabelString(ex.getUnderlyingScalarOperand());
|
||||
}
|
||||
|
||||
return this.getLabelString(this.props.expressionItem.getUnderlyingScalarOperand());
|
||||
return this.getLabelString(this.props.expressionItem.getUnderlyingScalarOperand());
|
||||
}
|
||||
|
||||
getAlternative() {
|
||||
|
||||
if(this.props.expressionItem.isOperator) {
|
||||
const ex = this.props.expressionItem as OperatorToken;
|
||||
if (this.props.expressionItem.isOperator) {
|
||||
const ex = this.props.expressionItem as BitwiseOperator;
|
||||
const res = ex.evaluate();
|
||||
|
||||
return formatter.numberToString(res.value, res.base);
|
||||
@@ -113,22 +137,45 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
|
||||
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);
|
||||
}
|
||||
|
||||
flipBit(args: FlipBitEventArg) {
|
||||
flipBit(args: FlipBitEventArg) {
|
||||
|
||||
const op = this.props.expressionItem.getUnderlyingScalarOperand();
|
||||
const { index, binaryString } = args;
|
||||
const op = this.props.expressionItem.getUnderlyingScalarOperand();
|
||||
const { bitIndex: index, binaryStringLength: totalLength } = args;
|
||||
|
||||
var arr = binaryString.split('');
|
||||
arr[index] = arr[index] == '0' ? '1' : '0';
|
||||
var bin = arr.join('');
|
||||
if(totalLength > op.bitSize() && (totalLength - index) > op.bitSize()) {
|
||||
op.setValue(calc.promoteToBigInt(op.value as number));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 { ExpressionToken, Expression } from '../expression-interfaces';
|
||||
import { ScalarValue, ListOfNumbersExpression, BitwiseOperationExpression, BitwiseOperator } from '../expression';
|
||||
import { ExpressionElement, Expression } from '../expression-interfaces';
|
||||
import calc from '../../core/calc';
|
||||
import formatter from '../../core/formatter';
|
||||
import exp from 'constants';
|
||||
|
||||
type Config = {
|
||||
emphasizeBytes: boolean;
|
||||
@@ -11,9 +12,10 @@ type Config = {
|
||||
type ExpressionRowModel = {
|
||||
sign: string;
|
||||
css: string;
|
||||
expression: ExpressionToken;
|
||||
expression: ExpressionElement;
|
||||
allowFlipBits: boolean;
|
||||
label: string;
|
||||
bitSize: number;
|
||||
}
|
||||
|
||||
export default class BitwiseResultViewModel {
|
||||
@@ -43,17 +45,17 @@ export default class BitwiseResultViewModel {
|
||||
i = 0, len = expr.children.length,
|
||||
ex, m = new BitwiseResultViewModel(config);
|
||||
|
||||
var prev : ScalarToken | null = null;
|
||||
var prev : ScalarValue | null = null;
|
||||
|
||||
for (;i<len;i++) {
|
||||
ex = expr.children[i];
|
||||
if(ex instanceof ScalarToken) {
|
||||
if(ex instanceof ScalarValue) {
|
||||
m.addScalarRow(ex);
|
||||
prev = ex;
|
||||
continue;
|
||||
}
|
||||
|
||||
var eo = ex as OperatorToken;
|
||||
var eo = ex as BitwiseOperator;
|
||||
|
||||
// If it a single NOT expression
|
||||
if(eo.isNotExpression) {
|
||||
@@ -63,11 +65,11 @@ export default class BitwiseResultViewModel {
|
||||
prev = notResult;
|
||||
}
|
||||
else if(eo.isShiftExpression){
|
||||
prev = eo.evaluate(prev as ScalarToken);
|
||||
prev = eo.evaluate(prev as ScalarValue);
|
||||
m.addShiftExpressionResultRow(eo, prev);
|
||||
} else {
|
||||
|
||||
prev = eo.evaluate(prev as ScalarToken);
|
||||
prev = eo.evaluate(prev as ScalarValue);
|
||||
m.addOperatorRow(eo);
|
||||
m.addExpressionResultRow(prev);
|
||||
}
|
||||
@@ -77,7 +79,7 @@ export default class BitwiseResultViewModel {
|
||||
return m;
|
||||
};
|
||||
|
||||
addScalarRow(expr: ScalarToken) {
|
||||
addScalarRow(expr: ScalarValue) {
|
||||
const bits = calc.numberOfBitsDisplayed(expr.value);
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
this.items.push({
|
||||
@@ -85,14 +87,16 @@ export default class BitwiseResultViewModel {
|
||||
css: '',
|
||||
expression: expr,
|
||||
allowFlipBits: this.allowFlipBits,
|
||||
label: ''
|
||||
label: '',
|
||||
bitSize: expr.bitSize(),
|
||||
});
|
||||
};
|
||||
|
||||
addOperatorRow(expr: OperatorToken) {
|
||||
addOperatorRow(expr: BitwiseOperator) {
|
||||
|
||||
const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingScalarOperand();
|
||||
const bits = calc.numberOfBitsDisplayed(resultNumber.value);
|
||||
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
|
||||
this.items.push({
|
||||
@@ -100,11 +104,12 @@ export default class BitwiseResultViewModel {
|
||||
css: '',
|
||||
label: this.getLabel(resultNumber),
|
||||
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);
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
const child = expr.operand.getUnderlyingScalarOperand();
|
||||
@@ -113,11 +118,12 @@ export default class BitwiseResultViewModel {
|
||||
css: 'expression-result',
|
||||
expression: resultExpr,
|
||||
allowFlipBits: false,
|
||||
label: ''
|
||||
label: '',
|
||||
bitSize: resultExpr.bitSize()
|
||||
});
|
||||
};
|
||||
|
||||
addExpressionResultRow(expr : ScalarToken) {
|
||||
addExpressionResultRow(expr : ScalarValue) {
|
||||
const bits = calc.numberOfBitsDisplayed(expr.value);
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
this.items.push({
|
||||
@@ -126,10 +132,11 @@ export default class BitwiseResultViewModel {
|
||||
expression: expr,
|
||||
allowFlipBits: false,
|
||||
label: '',
|
||||
bitSize: expr.bitSize()
|
||||
});
|
||||
};
|
||||
|
||||
getLabel (op: ScalarToken) : string {
|
||||
getLabel (op: ScalarValue) : string {
|
||||
|
||||
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
|
||||
{
|
||||
expressionString: string;
|
||||
}
|
||||
|
||||
export interface ExpressionToken
|
||||
export interface ExpressionElement
|
||||
{
|
||||
isOperator: boolean;
|
||||
getUnderlyingScalarOperand: () => ScalarToken;
|
||||
evaluate(operand? : ScalarToken): ScalarToken;
|
||||
getUnderlyingScalarOperand: () => ScalarValue;
|
||||
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", () => {
|
||||
|
||||
@@ -25,8 +25,8 @@ describe("expression parser", () => {
|
||||
expect(actual).toBeInstanceOf(ListOfNumbersExpression);
|
||||
|
||||
const expr = actual as ListOfNumbersExpression;
|
||||
expect(expr.children[0].getUnderlyingScalarOperand().value).toBe(305419896);
|
||||
expect(expr.children[1].getUnderlyingScalarOperand().value).toBe(2863311360);
|
||||
expect(expr.children[0].getUnderlyingScalarOperand().value.toString()).toBe('305419896');
|
||||
expect(expr.children[1].getUnderlyingScalarOperand().value.toString()).toBe('2863311360');
|
||||
})
|
||||
|
||||
it("pares multiple operand expression", () => {
|
||||
@@ -36,17 +36,17 @@ describe("expression parser", () => {
|
||||
const first = result.children[0];
|
||||
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);
|
||||
var secondOp = second as OperatorToken;
|
||||
expect(second).toBeInstanceOf(BitwiseOperator);
|
||||
var secondOp = second as BitwiseOperator;
|
||||
expect(secondOp.operator).toBe("^");
|
||||
|
||||
expect(secondOp.operand).toBeInstanceOf(ScalarToken);
|
||||
var childOp = secondOp.operand as ScalarToken;
|
||||
expect(childOp.value).toBe(2);
|
||||
expect(secondOp.operand).toBeInstanceOf(ScalarValue);
|
||||
var childOp = secondOp.operand as ScalarValue;
|
||||
expect(childOp.value.toString()).toBe('2');
|
||||
});
|
||||
|
||||
it("bug", () => {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import ScalarToken from './ScalarToken';
|
||||
import OperatorToken from './OperatorToken'
|
||||
import ScalarValue from './ScalarValue';
|
||||
import BitwiseOperator from './BitwiseOperator'
|
||||
import ListOfNumbersExpression from './ListOfNumbersExpression';
|
||||
import BitwiseOperationExpression from './BitwiseOperationExpression';
|
||||
import { Expression, ExpressionToken } from './expression-interfaces';
|
||||
import { NumberBase } from '../core/formatter';
|
||||
import { Expression, ExpressionElement } from './expression-interfaces';
|
||||
|
||||
export { default as ScalarToken } from './ScalarToken';
|
||||
export { default as OperatorToken } from './OperatorToken';
|
||||
export { default as ScalarValue } from './ScalarValue';
|
||||
export { default as BitwiseOperator } from './BitwiseOperator';
|
||||
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression';
|
||||
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression';
|
||||
|
||||
@@ -46,14 +45,6 @@ class ExpressionParser {
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
parseOperand (input : string) : ScalarToken {
|
||||
return ScalarToken.parse(input);
|
||||
};
|
||||
|
||||
createOperand (number : number, base : NumberBase) : ScalarToken {
|
||||
return ScalarToken.create(number, base);
|
||||
};
|
||||
|
||||
addFactory (factory: IExpressionParserFactory) {
|
||||
this.factories.push(factory);
|
||||
@@ -70,7 +61,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
||||
|
||||
return input.split(' ')
|
||||
.filter(p => p.length > 0)
|
||||
.map(p => ScalarToken.tryParse(p))
|
||||
.map(p => ScalarValue.tryParse(p))
|
||||
.filter(n => n == null)
|
||||
.length == 0;
|
||||
};
|
||||
@@ -79,7 +70,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
||||
|
||||
const numbers = input.split(' ')
|
||||
.filter(p => p.length > 0)
|
||||
.map(m => ScalarToken.parse(m));
|
||||
.map(m => ScalarValue.parse(m));
|
||||
|
||||
return new ListOfNumbersExpression(input, numbers);
|
||||
}
|
||||
@@ -90,8 +81,8 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
regex: RegExp;
|
||||
|
||||
constructor() {
|
||||
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,a-f,0-9]+)))+$/;
|
||||
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,a-f,0-9]+))/g;
|
||||
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,l,L,a-f,0-9]+)))+$/;
|
||||
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,l,L,a-f,0-9]+))/g;
|
||||
}
|
||||
|
||||
canCreate (input: string) : boolean {
|
||||
@@ -101,7 +92,7 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
|
||||
create (input: string) : Expression {
|
||||
var m : RegExpExecArray | null;
|
||||
const operands : ExpressionToken[] = [];
|
||||
const operands : ExpressionElement[] = [];
|
||||
const normalizedString = this.normalizeString(input);
|
||||
|
||||
this.regex.lastIndex = 0;
|
||||
@@ -113,23 +104,23 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
return new BitwiseOperationExpression(normalizedString, operands)
|
||||
};
|
||||
|
||||
parseMatch (m:any): ExpressionToken {
|
||||
parseMatch (m:any): ExpressionElement {
|
||||
var input = m[0],
|
||||
operator = m[1],
|
||||
num = m[2];
|
||||
|
||||
var parsed = null;
|
||||
if(num.indexOf('~') == 0) {
|
||||
parsed = new OperatorToken(ScalarToken.parse(num.substring(1)), '~');
|
||||
parsed = new BitwiseOperator(ScalarValue.parse(num.substring(1)), '~');
|
||||
}
|
||||
else {
|
||||
parsed = ScalarToken.parse(num);
|
||||
parsed = ScalarValue.parse(num);
|
||||
}
|
||||
|
||||
if(operator == null) {
|
||||
return parsed as OperatorToken;
|
||||
return parsed as BitwiseOperator;
|
||||
} 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 { NumberType } from "../core/types";
|
||||
|
||||
const decimalRegex = /^-?\d+$/;
|
||||
const hexRegex = /^-?0x[0-9,a-f]+$/i;
|
||||
const binRegex = /^-?0b[0-1]+$/i;
|
||||
const operatorRegex = /^<<|>>|<<<|\&|\|\^|~$/;
|
||||
const decimalRegex = /^-?\d+[l,L]?$/;
|
||||
const hexRegex = /^-?0x[0-9,a-f]+[l,L]?$/i;
|
||||
const binRegex = /^-?0b[0-1]+[l,L]?$/i;
|
||||
|
||||
interface ParserConfig {
|
||||
regex: RegExp,
|
||||
radix: number,
|
||||
base: NumberBase,
|
||||
prefix: string|RegExp
|
||||
parse: (input: string) => NumberType
|
||||
}
|
||||
|
||||
export interface ParsedNumber {
|
||||
value: number;
|
||||
value: number|bigint;
|
||||
base: NumberBase;
|
||||
input: string;
|
||||
}
|
||||
|
||||
var knownParsers : ParserConfig[] = [
|
||||
{ regex: decimalRegex, radix: 10, base: 'dec', prefix: '^$' },
|
||||
{ regex: hexRegex, radix: 16, base: 'hex', prefix:/0x/i },
|
||||
{ regex: binRegex, radix: 2, base: 'bin', prefix:/0b/i }];
|
||||
{ regex: decimalRegex, base: 'dec', parse:(s) => parseIntSafe(s, 10) },
|
||||
{ regex: hexRegex, base: 'hex', parse:(s) => parseIntSafe(s, 16)},
|
||||
{ regex: binRegex, base: 'bin', parse:(s) => parseIntSafe(s, 2) }];
|
||||
|
||||
|
||||
class NumberParser {
|
||||
@@ -53,7 +53,7 @@ class NumberParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = parseInt(rawInput.replace(parser.prefix, ''), parser.radix);
|
||||
var value = parser.parse(rawInput);
|
||||
|
||||
return {
|
||||
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);
|
||||
|
||||
export {numberParser};
|
||||
Reference in New Issue
Block a user