Allow to switch a number sign (#47)

This commit is contained in:
Borys Levytskyi
2023-05-10 18:40:59 +03:00
committed by GitHub
parent 8371e3c086
commit 214f85c52d
26 changed files with 248 additions and 202 deletions

View File

@@ -0,0 +1,37 @@
import calc from "./calc";
describe("engine comparison", () => {
it("NOT same as in node", () => {
for(var i = -100; i<100;i++) {
const expected = bin(~i);
const actual = calc.engine.not(bin(i));
expect(`${i} is ${actual}`).toBe(`${i} is ${(expected)}`);
}
});
it("OR same as in node", () => {
for(var x = -100; x<100;x++) {
const y = 5+3%x+x%6*(-x);
const expected = bin(x | y);
const actual = calc.engine.or(bin(x), bin(y));
expect(`${x} is ${actual}`).toBe(`${x} is ${(expected)}`);
}
});
it("AND same as in node", () => {
for(var x = -100; x<100;x++) {
const y = 5+3%x+x%6*(-x);
const expected = bin(x & y);
const actual = calc.engine.and(bin(x), bin(y));
expect(`${x} is ${actual}`).toBe(`${x} is ${(expected)}`);
}
});
function bin(i: number) {
return (i >= 0 ? i : i >>> 0).toString(2).padStart(32, i<0 ? '1' : '0');
}
});

View File

@@ -236,41 +236,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
}); });
})
describe("engine comparison", () => {
it("NOT same as in node", () => {
for(var i = -100; i<100;i++) {
const expected = bin(~i);
const actual = calc.engine.not(bin(i));
expect(`${i} is ${actual}`).toBe(`${i} is ${(expected)}`);
}
});
it("OR same as in node", () => {
for(var x = -100; x<100;x++) {
const y = 5+3%x+x%6*(-x);
const expected = bin(x | y);
const actual = calc.engine.or(bin(x), bin(y));
expect(`${x} is ${actual}`).toBe(`${x} is ${(expected)}`);
}
});
it("AND same as in node", () => {
for(var x = -100; x<100;x++) {
const y = 5+3%x+x%6*(-x);
const expected = bin(x & y);
const actual = calc.engine.and(bin(x), bin(y));
expect(`${x} is ${actual}`).toBe(`${x} is ${(expected)}`);
}
});
function bin(i: number) {
return (i >= 0 ? i : i >>> 0).toString(2).padStart(32, i<0 ? '1' : '0');
}
}); });

View File

@@ -26,6 +26,7 @@ export default {
}, },
operation (op1: Integer, operator: string, op2 : Integer) : Integer { operation (op1: Integer, operator: string, op2 : Integer) : Integer {
switch(operator) { switch(operator) {
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);
@@ -246,7 +247,7 @@ function nextPowOfTwo(num: number) : number {
} }
function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] { function equalizeSize(n1: Integer, n2: Integer) : [Integer, Integer] {
console.log('equalizeSize()', new Error().stack);
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];

View File

@@ -51,8 +51,6 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
const disableHighlight = this.props.disableHighlight || false; const disableHighlight = this.props.disableHighlight || false;
let signBitIndex = -1;
return bitChars.map((c, i) => { return bitChars.map((c, i) => {
var className = c == '1' ? `one${css}` : `zero${css}`; var className = c == '1' ? `one${css}` : `zero${css}`;

View File

@@ -1,6 +1,6 @@
import { Expression, ExpressionElement } from "./expression-interfaces"; import { Expression, ExpressionElement } from "./expression-interfaces";
export default class BitwiseOperationExpression implements Expression { export default class BitwiseOperation implements Expression {
expressionString: string; expressionString: string;
children: ExpressionElement[]; children: ExpressionElement[];

View File

@@ -1,5 +1,5 @@
import BitwiseOperator from "./BitwiseOperator"; import Operator from "./Operator";
import { ScalarValue } from "./expression"; import { Operand } from "./expression";
import { ExpressionElement } from "./expression-interfaces"; import { ExpressionElement } from "./expression-interfaces";
import { type } from "os"; import { type } from "os";
import { InputType } from "zlib"; import { InputType } from "zlib";

View File

@@ -0,0 +1,12 @@
import Operand from "./Operand";
import ListOfNumbers from "./ListOfNumbers";
import { numberParser } from "./numberParser";
it('calculates max bits length', () => {
const v1 = new Operand(numberParser.parse("10").value);
const v2 = new Operand(numberParser.parse("0xabef").value);
const v3 = new Operand(numberParser.parse("0b01010").value);
var expr = new ListOfNumbers("10 0xabef 0b01010", [v1, v2, v3])
expect(expr.maxBitsLength).toBe(16);
});

View File

@@ -1,13 +1,13 @@
import calc from "../core/calc"; import calc from "../core/calc";
import ScalarValue from "./ScalarValue"; import Operand from "./Operand";
import { Expression, ExpressionElement } from "./expression-interfaces"; import { Expression, ExpressionElement } from "./expression-interfaces";
export default class ListOfNumbersExpression implements Expression { export default class ListOfNumbers implements Expression {
children: ScalarValue[]; children: Operand[];
expressionString: string; expressionString: string;
maxBitsLength: number; maxBitsLength: number;
constructor(expressionString: string, numbers: ScalarValue[]) { constructor(expressionString: string, numbers: Operand[]) {
this.expressionString = expressionString; this.expressionString = expressionString;
this.children = numbers; this.children = numbers;
this.maxBitsLength = numbers.map(n => calc.numberOfBitsDisplayed(n.value)).reduce((n , c) => n >= c ? n : c, 0); this.maxBitsLength = numbers.map(n => calc.numberOfBitsDisplayed(n.value)).reduce((n , c) => n >= c ? n : c, 0);

View File

@@ -1,12 +0,0 @@
import ScalarValue from "./ScalarValue";
import ListOfNumbersExpression from "./ListOfNumbersExpression";
import { numberParser } from "./numberParser";
it('calculates max bits length', () => {
const v1 = new ScalarValue(numberParser.parse("10").value);
const v2 = new ScalarValue(numberParser.parse("0xabef").value);
const v3 = new ScalarValue(numberParser.parse("0b01010").value);
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [v1, v2, v3])
expect(expr.maxBitsLength).toBe(16);
});

View File

@@ -1,10 +1,10 @@
import { INT32_MAX_VALUE } from "../core/const"; import { INT32_MAX_VALUE } from "../core/const";
import ScalarValue from "./ScalarValue"; import Operand from "./Operand";
it('converts numbers to bigint', () => { it('converts numbers to bigint', () => {
const int32 = new ScalarValue(INT32_MAX_VALUE); const int32 = new Operand(INT32_MAX_VALUE);
const int64 = new ScalarValue(BigInt(INT32_MAX_VALUE+1)); const int64 = new Operand(BigInt(INT32_MAX_VALUE+1));
expect(int32.value.maxBitSize).toBe(32); expect(int32.value.maxBitSize).toBe(32);
expect(int64.value.maxBitSize).toBe(64); expect(int64.value.maxBitSize).toBe(64);

View File

@@ -8,7 +8,7 @@ var globalId : number = 1;
// Represents scalar numeric value // Represents scalar numeric value
export default class ScalarValue implements ExpressionElement { export default class Operand implements ExpressionElement {
id: number; id: number;
value: Integer; value: Integer;
base: NumberBase; base: NumberBase;
@@ -20,7 +20,7 @@ export default class ScalarValue implements ExpressionElement {
if(!isInteger(value)) if(!isInteger(value))
value = asInteger(value); value = asInteger(value);
ScalarValue.validateSupported(value); Operand.validateSupported(value);
this.id = globalId++; this.id = globalId++;
this.value = value as Integer; this.value = value as Integer;
@@ -29,21 +29,21 @@ export default class ScalarValue implements ExpressionElement {
this.label = ''; this.label = '';
} }
setValue(value : Integer) : ScalarValue { setValue(value : Integer) : Operand {
this.value = value; this.value = value;
return this; return this;
} }
setLabel(label : string) : ScalarValue { setLabel(label : string) : Operand {
this.label = label; this.label = label;
return this; return this;
} }
evaluate() : ScalarValue { evaluate() : Operand {
return this; return this;
} }
getUnderlyingScalarOperand() : ScalarValue { getUnderlyingOperand() : Operand {
return this; return this;
} }

View File

@@ -1,9 +1,9 @@
import ScalarValue from "./ScalarValue"; import Operand from "./Operand";
import BitwiseOperator from './BitwiseOperator'; import Operator from './Operator';
it('can apply ~ operand', () => { it('can apply ~ operand', () => {
var op = new ScalarValue(10, 'dec'); var op = new Operand(10, 'dec');
var expr = new BitwiseOperator(op, "~"); var expr = new Operator(op, "~");
var result = expr.evaluate(); var result = expr.evaluate();
expect(result.value.num()).toBe(-11); expect(result.value.num()).toBe(-11);
@@ -11,9 +11,9 @@ it('can apply ~ operand', () => {
}); });
it('can apply & operand', () => { it('can apply & operand', () => {
var op1 = new ScalarValue(3, 'dec'); var op1 = new Operand(3, 'dec');
var op2 = new ScalarValue(4, 'dec'); var op2 = new Operand(4, 'dec');
var expr = new BitwiseOperator(op1, "|"); var expr = new Operator(op1, "|");
var result = expr.evaluate(op2); var result = expr.evaluate(op2);
expect(result.value.num()).toBe(7); expect(result.value.num()).toBe(7);
expect(result.base).toBe('dec'); expect(result.base).toBe('dec');

View File

@@ -1,8 +1,8 @@
import calc from '../core/calc'; import calc from '../core/calc';
import ScalarValue from './ScalarValue'; import Operand from './Operand';
import { ExpressionElement } from './expression-interfaces'; import { ExpressionElement } from './expression-interfaces';
export default class BitwiseOperator implements ExpressionElement { export default class Operator implements ExpressionElement {
operand: ExpressionElement; operand: ExpressionElement;
operator: string; operator: string;
isOperator: boolean; isOperator: boolean;
@@ -18,9 +18,9 @@ export default class BitwiseOperator implements ExpressionElement {
this.isNotExpression = this.operator === '~'; this.isNotExpression = this.operator === '~';
} }
evaluate(operand?: ScalarValue) : ScalarValue { evaluate(operand?: Operand) : Operand {
if (operand instanceof BitwiseOperator) if (operand instanceof Operator)
throw new Error('operand must be scalar value'); throw new Error('operand must be scalar value');
if( this.operator != "~" && operand == null) if( this.operator != "~" && operand == null)
@@ -29,12 +29,12 @@ export default class BitwiseOperator implements ExpressionElement {
var evaluatedOperand = this.operand.evaluate(); var evaluatedOperand = this.operand.evaluate();
return this.operator == "~" return this.operator == "~"
? applyNotOperator(this.operand.getUnderlyingScalarOperand()) ? applyNotOperator(this.operand.getUnderlyingOperand())
: applyOperator(operand!, this.operator, evaluatedOperand); : applyOperator(operand!, this.operator, evaluatedOperand);
} }
getUnderlyingScalarOperand() : ScalarValue { getUnderlyingOperand() : Operand {
return this.operand.getUnderlyingScalarOperand(); return this.operand.getUnderlyingOperand();
} }
toString(): string { toString(): string {
@@ -42,13 +42,14 @@ export default class BitwiseOperator implements ExpressionElement {
} }
} }
function applyNotOperator(operand: ScalarValue) : ScalarValue { function applyNotOperator(operand: Operand) : Operand {
return new ScalarValue(calc.not(operand.value), operand.base); return new Operand(calc.not(operand.value), operand.base);
} }
function applyOperator(op1 : ScalarValue, operator: string, op2 : ScalarValue) : ScalarValue { function applyOperator(op1 : Operand, operator: string, op2 : Operand) : Operand {
const isShift = /<|>/.test(operator); const isShift = /<|>/.test(operator);
if(!isShift) if(!isShift)
{ {
if(op1.value.maxBitSize == op2.value.maxBitSize && op1.value.signed != op2.value.signed) if(op1.value.maxBitSize == op2.value.maxBitSize && op1.value.signed != op2.value.signed)
@@ -57,11 +58,13 @@ function applyOperator(op1 : ScalarValue, operator: string, op2 : ScalarValue) :
equalizeSize(op1, op2); equalizeSize(op1, op2);
} }
console.log(op1.value, operator, op2.value);
const result = calc.operation(op1.value, operator, op2.value); const result = calc.operation(op1.value, operator, op2.value);
return new ScalarValue(result, op2.base); console.log('=', result);
return new Operand(result, op2.base);
} }
function equalizeSize(op1: ScalarValue, op2: ScalarValue) { function equalizeSize(op1: Operand, op2: Operand) {
const n1 = op1.value; const n1 = op1.value;
const n2 = op2.value; const n2 = op2.value;

View File

@@ -3,7 +3,7 @@ import formatter from '../../core/formatter';
import BinaryStringView, { FlipBitEventArg } from '../../core/components/BinaryString'; import BinaryStringView, { FlipBitEventArg } from '../../core/components/BinaryString';
import BitwiseResultViewModel from './BitwiseResultViewModel'; import BitwiseResultViewModel from './BitwiseResultViewModel';
import { Expression, ExpressionElement } from '../expression-interfaces'; import { Expression, ExpressionElement } from '../expression-interfaces';
import { BitwiseOperator, ScalarValue } from '../expression'; import { Operator, Operand, ListOfNumbers } from '../expression';
import calc from '../../core/calc'; import calc from '../../core/calc';
type BitwiseResultViewProps = { type BitwiseResultViewProps = {
@@ -27,18 +27,18 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
render() { render() {
let model : BitwiseResultViewModel | null = null let model : BitwiseResultViewModel | null = null
const isListOfNumbers = this.props.expression instanceof ListOfNumbers;
try try
{ {
model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes); model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
} }
catch(err) { catch(err) {
const text = (err as any).message; const text = (err as any).message;
return <div className='error'>Error: {text}</div> return <div className='error'>Error: {text}</div>
} }
var rows = this.getRows(model!, isListOfNumbers);
var rows = this.getRows(model!);
return <table className="expression"> return <table className="expression">
<tbody> <tbody>
@@ -47,7 +47,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
</table> </table>
} }
getRows(model: BitwiseResultViewModel): JSX.Element[] { getRows(model: BitwiseResultViewModel, allowSignChange : boolean): JSX.Element[] {
this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits); this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits);
@@ -58,6 +58,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
css={itm.css} css={itm.css}
bitSize={itm.maxBitSize} bitSize={itm.maxBitSize}
allowFlipBits={itm.allowFlipBits} allowFlipBits={itm.allowFlipBits}
allowSignChange={allowSignChange}
expressionItem={itm.expression} expressionItem={itm.expression}
emphasizeBytes={this.props.emphasizeBytes} emphasizeBytes={this.props.emphasizeBytes}
maxNumberOfBits={this.maxSeenLengthNumberOfBits} maxNumberOfBits={this.maxSeenLengthNumberOfBits}
@@ -77,15 +78,20 @@ type ExpressionRowProps = {
maxNumberOfBits: number, maxNumberOfBits: number,
emphasizeBytes: boolean, emphasizeBytes: boolean,
allowFlipBits: boolean, allowFlipBits: boolean,
allowSignChange: boolean,
expressionItem: ExpressionElement, expressionItem: ExpressionElement,
onBitFlipped: any onBitFlipped: any
} }
class ExpressionRow extends React.Component<ExpressionRowProps> { class ExpressionRow extends React.Component<ExpressionRowProps> {
infoWasShown: boolean = false;
constructor(props: ExpressionRowProps) { constructor(props: ExpressionRowProps) {
super(props); super(props);
this.state = { operand: null }; this.state = { operand: null };
} }
render() { render() {
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props; const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
const scalar = this.props.expressionItem.evaluate(); const scalar = this.props.expressionItem.evaluate();
@@ -113,17 +119,17 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
// For expressions like |~2 // For expressions like |~2
// TODO: find a better way... // TODO: find a better way...
if (this.props.expressionItem.isOperator) { if (this.props.expressionItem.isOperator) {
const ex = this.props.expressionItem as BitwiseOperator; const ex = this.props.expressionItem as Operator;
return ex.operator + this.getLabelString(ex.getUnderlyingScalarOperand()); return ex.operator + this.getLabelString(ex.getUnderlyingOperand());
} }
return this.getLabelString(this.props.expressionItem.getUnderlyingScalarOperand()); return this.getLabelString(this.props.expressionItem.getUnderlyingOperand());
} }
getAlternative() { getAlternative() {
if (this.props.expressionItem.isOperator) { if (this.props.expressionItem.isOperator) {
const ex = this.props.expressionItem as BitwiseOperator; const ex = this.props.expressionItem as Operator;
const res = ex.evaluate(); const res = ex.evaluate();
return formatter.numberToString(res.value, res.base); return formatter.numberToString(res.value, res.base);
@@ -134,13 +140,13 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
return formatter.numberToString(v.value, altBase); return formatter.numberToString(v.value, altBase);
} }
getLabelString(op: ScalarValue): string { getLabelString(op: Operand): string {
return formatter.numberToString(op.value, op.base == 'bin' ? 'dec' : op.base); return formatter.numberToString(op.value, op.base == 'bin' ? 'dec' : op.base);
} }
flipBit(args: FlipBitEventArg) { flipBit(args: FlipBitEventArg) {
const op = this.props.expressionItem.getUnderlyingScalarOperand(); const op = this.props.expressionItem.getUnderlyingOperand();
const { bitIndex: index, binaryStringLength: totalLength } = args; const { bitIndex: index, binaryStringLength: totalLength } = args;
const maxBitSize = op.value.maxBitSize; const maxBitSize = op.value.maxBitSize;
@@ -155,27 +161,47 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
this.props.onBitFlipped(); this.props.onBitFlipped();
} }
onChangeSign () {
var op = this.props.expressionItem.getUnderlyingOperand();
op.setValue(op.value.signed ? op.value.toUnsigned() : op.value.toSigned());
this.forceUpdate();
}
getInfo(maxNumberOfBits:number) { getInfo(maxNumberOfBits:number) {
var op = this.props.expressionItem.getUnderlyingScalarOperand();
const op = this.props.expressionItem.getUnderlyingOperand();
const allBitsDisplayed = op.value.maxBitSize != 32 || op.value.maxBitSize <= maxNumberOfBits;
const { allowSignChange } = this.props;
const hasLabel = op.label.length > 0;
if((op.value.maxBitSize != 32 || op.value.maxBitSize <= maxNumberOfBits) || op.label.length > 0) if(!allBitsDisplayed && !hasLabel && !this.infoWasShown)
return null;
this.infoWasShown = true;
const children = [];
let title = `BitwiseCmd treats this number as ${op.value.maxBitSize}-bit integer`;
let text = `${op.value.maxBitSize}-bit `;
const signedStr = op.value.signed ? 'signed' : 'unsigned';
const signedOther = op.value.signed ? 'usigned' : 'signed';
const signedTitle = `Click to change to ${signedOther} preserving the same bits`;
if(op.label.length > 0)
{ {
let title = `BitwiseCmd treats this number as ${op.value.maxBitSize}-bit integer`; text += " (converted)";
let text = `${op.value.maxBitSize}-bit `; title += ". This number was converted to facilitate bitwise operation with an operand of a different type";
if(!op.value.signed)
text += " unsigned ";
if(op.label.length > 0)
{
text += " (converted)";
title += ". This number was converted to facilitate bitwise operation with an operand of a different type";
}
return <span title={title} style={{cursor:"help"}}>{text}</span>;
} }
return null; children.push(<span title={title} style={{cursor:"help"}}>{text.trim()}</span>);
if(allBitsDisplayed)
{
if(allowSignChange)
children.push(<button className='accent1' title={signedTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>);
else if(!op.value.signed)
children.push(<span className='accent1'> {signedStr}</span>)
}
return <React.Fragment>{children}</React.Fragment>
} }
} }

View File

@@ -1,4 +1,4 @@
import { ScalarValue, ListOfNumbersExpression, BitwiseOperationExpression, BitwiseOperator } from '../expression'; import { Operand, ListOfNumbers, BitwiseOperation, Operator } from '../expression';
import { ExpressionElement, Expression } from '../expression-interfaces'; import { ExpressionElement, Expression } from '../expression-interfaces';
import calc from '../../core/calc'; import calc from '../../core/calc';
import formatter from '../../core/formatter'; import formatter from '../../core/formatter';
@@ -31,30 +31,30 @@ export default class BitwiseResultViewModel {
this.allowFlipBits = allowFlipBits === true; this.allowFlipBits = allowFlipBits === true;
} }
static buildListOfNumbers(expr : ListOfNumbersExpression, config : Config) { static buildListOfNumbers(expr : ListOfNumbers, config : Config) {
var model = new BitwiseResultViewModel(config); var model = new BitwiseResultViewModel(config);
expr.children.forEach(op => model.addScalarRow(op)); expr.children.forEach(op => model.addScalarRow(op));
model.maxNumberOfBits = BitwiseResultViewModel.applyEmphasizeBytes(model.maxNumberOfBits, model.emphasizeBytes); model.maxNumberOfBits = BitwiseResultViewModel.applyEmphasizeBytes(model.maxNumberOfBits, model.emphasizeBytes);
return model; return model;
} }
static buildBitwiseOperation (expr : BitwiseOperationExpression, config : Config) { static buildBitwiseOperation (expr : BitwiseOperation, config : Config) {
var op = expr.children[0], var op = expr.children[0],
i = 0, len = expr.children.length, i = 0, len = expr.children.length,
ex, m = new BitwiseResultViewModel(config); ex, m = new BitwiseResultViewModel(config);
var prev : ScalarValue | null = null; var prev : Operand | null = null;
for (;i<len;i++) { for (;i<len;i++) {
ex = expr.children[i]; ex = expr.children[i];
if(ex instanceof ScalarValue) { if(ex instanceof Operand) {
m.addScalarRow(ex); m.addScalarRow(ex);
prev = ex; prev = ex;
continue; continue;
} }
var eo = ex as BitwiseOperator; var eo = ex as Operator;
// If it a single NOT expression // If it a single NOT expression
if(eo.isNotExpression) { if(eo.isNotExpression) {
@@ -64,11 +64,11 @@ export default class BitwiseResultViewModel {
prev = notResult; prev = notResult;
} }
else if(eo.isShiftExpression){ else if(eo.isShiftExpression){
prev = eo.evaluate(prev as ScalarValue); prev = eo.evaluate(prev as Operand);
m.addShiftExpressionResultRow(eo, prev); m.addShiftExpressionResultRow(eo, prev);
} else { } else {
prev = eo.evaluate(prev as ScalarValue); prev = eo.evaluate(prev as Operand);
m.addOperatorRow(eo); m.addOperatorRow(eo);
m.addExpressionResultRow(prev); m.addExpressionResultRow(prev);
} }
@@ -78,7 +78,7 @@ export default class BitwiseResultViewModel {
return m; return m;
}; };
addScalarRow(expr: ScalarValue) { addScalarRow(expr: Operand) {
const bits = calc.numberOfBitsDisplayed(expr.value); const bits = calc.numberOfBitsDisplayed(expr.value);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
this.items.push({ this.items.push({
@@ -91,9 +91,9 @@ export default class BitwiseResultViewModel {
}); });
}; };
addOperatorRow(expr: BitwiseOperator) { addOperatorRow(expr: Operator) {
const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingScalarOperand(); const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingOperand();
const bits = calc.numberOfBitsDisplayed(resultNumber.value); const bits = calc.numberOfBitsDisplayed(resultNumber.value);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
@@ -108,10 +108,10 @@ export default class BitwiseResultViewModel {
}); });
}; };
addShiftExpressionResultRow(expr : BitwiseOperator, resultExpr : ScalarValue) { addShiftExpressionResultRow(expr : Operator, resultExpr : Operand) {
const bits = calc.numberOfBitsDisplayed(resultExpr.value); const bits = calc.numberOfBitsDisplayed(resultExpr.value);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
const child = expr.operand.getUnderlyingScalarOperand(); const child = expr.operand.getUnderlyingOperand();
this.items.push({ this.items.push({
sign: expr.operator + formatter.numberToString(child.value, child.base), sign: expr.operator + formatter.numberToString(child.value, child.base),
css: 'expression-result', css: 'expression-result',
@@ -122,7 +122,7 @@ export default class BitwiseResultViewModel {
}); });
}; };
addExpressionResultRow(expr : ScalarValue) { addExpressionResultRow(expr : Operand) {
const bits = calc.numberOfBitsDisplayed(expr.value); const bits = calc.numberOfBitsDisplayed(expr.value);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
this.items.push({ this.items.push({
@@ -135,7 +135,7 @@ export default class BitwiseResultViewModel {
}); });
}; };
getLabel (op: ScalarValue) : string { getLabel (op: Operand) : string {
return formatter.numberToString(op.value, op.base === 'bin' ? 'dec' : op.base) return formatter.numberToString(op.value, op.base === 'bin' ? 'dec' : op.base)
} }
@@ -155,14 +155,15 @@ export default class BitwiseResultViewModel {
}; };
static createModel(expr : Expression, emphasizeBytes: boolean) : BitwiseResultViewModel { static createModel(expr : Expression, emphasizeBytes: boolean) : BitwiseResultViewModel {
if(expr instanceof ListOfNumbersExpression) {
if(expr instanceof ListOfNumbers) {
return BitwiseResultViewModel.buildListOfNumbers(expr, { return BitwiseResultViewModel.buildListOfNumbers(expr, {
emphasizeBytes: emphasizeBytes, emphasizeBytes: emphasizeBytes,
allowFlipBits: true allowFlipBits: true
}); });
} }
if(expr instanceof BitwiseOperationExpression) { if(expr instanceof BitwiseOperation) {
return BitwiseResultViewModel.buildBitwiseOperation(expr, { return BitwiseResultViewModel.buildBitwiseOperation(expr, {
emphasizeBytes: emphasizeBytes, emphasizeBytes: emphasizeBytes,
allowFlipBits: true allowFlipBits: true

View File

@@ -1,4 +1,4 @@
import { ScalarValue } from "./expression"; import { Operand } from "./expression";
export interface Expression export interface Expression
{ {
@@ -8,7 +8,7 @@ export interface Expression
export interface ExpressionElement export interface ExpressionElement
{ {
isOperator: boolean; isOperator: boolean;
getUnderlyingScalarOperand: () => ScalarValue; getUnderlyingOperand: () => Operand;
evaluate(operand? : ScalarValue): ScalarValue; evaluate(operand? : Operand): Operand;
} }

View File

@@ -1,4 +1,4 @@
import { parser, ListOfNumbersExpression, BitwiseOperationExpression, ScalarValue, BitwiseOperator } from "./expression"; import { parser, ListOfNumbers, BitwiseOperation, Operand, Operator } from "./expression";
import { random } from "../core/utils"; import { random } from "../core/utils";
import { INT32_MAX_VALUE } from "../core/const"; import { INT32_MAX_VALUE } from "../core/const";
@@ -6,7 +6,7 @@ describe("expression parser", () => {
it("parses list of number expression", () => { it("parses list of number expression", () => {
var result = parser.parse("1 2 3"); var result = parser.parse("1 2 3");
expect(result).toBeInstanceOf(ListOfNumbersExpression); expect(result).toBeInstanceOf(ListOfNumbers);
}); });
it("doesn't list of numbers in case of bad numbers", () => { it("doesn't list of numbers in case of bad numbers", () => {
@@ -15,43 +15,43 @@ describe("expression parser", () => {
}); });
it("pares different operations expressions", () => { it("pares different operations expressions", () => {
expect(parser.parse("~1")).toBeInstanceOf(BitwiseOperationExpression); expect(parser.parse("~1")).toBeInstanceOf(BitwiseOperation);
expect(parser.parse("1^2")).toBeInstanceOf(BitwiseOperationExpression); expect(parser.parse("1^2")).toBeInstanceOf(BitwiseOperation);
expect(parser.parse("1|2")).toBeInstanceOf(BitwiseOperationExpression); expect(parser.parse("1|2")).toBeInstanceOf(BitwiseOperation);
}); });
it("parses big binary bitwise expression", () => { it("parses big binary bitwise expression", () => {
const input = "0b00010010001101000101011001111000 0b10101010101010101010101000000000"; const input = "0b00010010001101000101011001111000 0b10101010101010101010101000000000";
const actual = parser.parse(input); const actual = parser.parse(input);
expect(actual).toBeInstanceOf(ListOfNumbersExpression); expect(actual).toBeInstanceOf(ListOfNumbers);
const expr = actual as ListOfNumbersExpression; const expr = actual as ListOfNumbers;
expect(expr.children[0].getUnderlyingScalarOperand().value.toString()).toBe('305419896'); expect(expr.children[0].getUnderlyingOperand().value.toString()).toBe('305419896');
expect(expr.children[1].getUnderlyingScalarOperand().value.toString()).toBe('2863311360'); expect(expr.children[1].getUnderlyingOperand().value.toString()).toBe('2863311360');
}) })
it("pares multiple operand expression", () => { it("pares multiple operand expression", () => {
const result = parser.parse("1^2") as BitwiseOperationExpression; const result = parser.parse("1^2") as BitwiseOperation;
expect(result.children.length).toBe(2); expect(result.children.length).toBe(2);
const first = result.children[0]; const first = result.children[0];
const second = result.children[1]; const second = result.children[1];
expect(first).toBeInstanceOf(ScalarValue); expect(first).toBeInstanceOf(Operand);
expect((first as ScalarValue).value.toString()).toBe("1"); expect((first as Operand).value.toString()).toBe("1");
expect(second).toBeInstanceOf(BitwiseOperator); expect(second).toBeInstanceOf(Operator);
var secondOp = second as BitwiseOperator; var secondOp = second as Operator;
expect(secondOp.operator).toBe("^"); expect(secondOp.operator).toBe("^");
expect(secondOp.operand).toBeInstanceOf(ScalarValue); expect(secondOp.operand).toBeInstanceOf(Operand);
var childOp = secondOp.operand as ScalarValue; var childOp = secondOp.operand as Operand;
expect(childOp.value.toString()).toBe('2'); expect(childOp.value.toString()).toBe('2');
}); });
it("bug", () => { it("bug", () => {
var result = parser.parse("1|~2") as BitwiseOperationExpression; var result = parser.parse("1|~2") as BitwiseOperation;
expect(result.children.length).toBe(2); expect(result.children.length).toBe(2);
}); });
}); });
@@ -104,13 +104,13 @@ describe("comparison with nodejs engine", () => {
try try
{ {
const expr = parser.parse(actualInput) as BitwiseOperationExpression; const expr = parser.parse(actualInput) as BitwiseOperation;
const bo = (expr.children[0] as BitwiseOperator); const bo = (expr.children[0] as Operator);
const res = bo.evaluate(); const res = bo.evaluate();
actual = res.value.toString(); actual = res.value.toString();
if(actual != expected) { if(actual != expected) {
const uop = bo.getUnderlyingScalarOperand(); const uop = bo.getUnderlyingOperand();
console.log(`Expected:${expectedInput}\nActual:${actualInput}\n${uop.value} ${uop.value.maxBitSize}\n${res.value} ${typeof res.value} ${res.value.maxBitSize}`) console.log(`Expected:${expectedInput}\nActual:${actualInput}\n${uop.value} ${uop.value.maxBitSize}\n${res.value} ${typeof res.value} ${res.value.maxBitSize}`)
} }
} }
@@ -151,17 +151,17 @@ describe("comparison with nodejs engine", () => {
try try
{ {
var expr = parser.parse(actualInput) as BitwiseOperationExpression; var expr = parser.parse(actualInput) as BitwiseOperation;
var op1 = expr.children[0] as ScalarValue; var op1 = expr.children[0] as Operand;
var op2 = expr.children[1] as BitwiseOperator; var op2 = expr.children[1] as Operator;
actual = op2.evaluate(op1).value.toString(); actual = op2.evaluate(op1).value.toString();
const equals = actual === expected; const equals = actual === expected;
if(!equals) if(!equals)
{ {
console.log(`Expected:${expectedInput}\n$Actual:${actualInput}\nop1:${typeof op1.value}\nop2:${typeof op2.getUnderlyingScalarOperand().value}`); console.log(`Expected:${expectedInput}\n$Actual:${actualInput}\nop1:${typeof op1.value}\nop2:${typeof op2.getUnderlyingOperand().value}`);
} }
} }
catch(err) catch(err)

View File

@@ -1,15 +1,14 @@
import ScalarValue from './ScalarValue'; import Operand from './Operand';
import BitwiseOperator from './BitwiseOperator' import Operator from './Operator'
import ListOfNumbersExpression from './ListOfNumbersExpression'; import ListOfNumbers from './ListOfNumbers';
import BitwiseOperationExpression from './BitwiseOperationExpression'; import BitwiseOperation from './BitwiseOperation';
import { Expression, ExpressionElement } from './expression-interfaces'; import { Expression, ExpressionElement } from './expression-interfaces';
import { numberParser, numberRegexString } from './numberParser'; import { numberParser, numberRegexString } from './numberParser';
import { parse } from 'path';
export { default as ScalarValue } from './ScalarValue'; export { default as Operand } from './Operand';
export { default as BitwiseOperator } from './BitwiseOperator'; export { default as Operator } from './Operator';
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression'; export { default as ListOfNumbers } from './ListOfNumbers';
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression'; export { default as BitwiseOperation } from './BitwiseOperation';
interface IExpressionParserFactory { interface IExpressionParserFactory {
canCreate: (input: string) => boolean; canCreate: (input: string) => boolean;
@@ -76,7 +75,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
.filter(p => p.length > 0) .filter(p => p.length > 0)
.map(m => parseScalarValue(m)); .map(m => parseScalarValue(m));
return new ListOfNumbersExpression(input, numbers); return new ListOfNumbers(input, numbers);
} }
} }
@@ -105,7 +104,7 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
operands.push(this.parseMatch(m)); operands.push(this.parseMatch(m));
} }
return new BitwiseOperationExpression(normalizedString, operands) return new BitwiseOperation(normalizedString, operands)
}; };
parseMatch (m:RegExpExecArray): ExpressionElement { parseMatch (m:RegExpExecArray): ExpressionElement {
@@ -117,16 +116,16 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
var parsed = null; var parsed = null;
if(num.indexOf('~') == 0) { if(num.indexOf('~') == 0) {
parsed = new BitwiseOperator(parseScalarValue(num.substring(1)), '~'); parsed = new Operator(parseScalarValue(num.substring(1)), '~');
} }
else { else {
parsed = parseScalarValue(num); parsed = parseScalarValue(num);
} }
if(operator == null) { if(operator == null) {
return parsed as BitwiseOperator; return parsed as Operator;
} else { } else {
return new BitwiseOperator(parsed as ScalarValue, operator); return new Operator(parsed as Operand, operator);
} }
}; };
@@ -135,9 +134,9 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
}; };
} }
function parseScalarValue(input : string) : ScalarValue { function parseScalarValue(input : string) : Operand {
const n = numberParser.parse(input); const n = numberParser.parse(input);
var sv = new ScalarValue(n.value, n.base); var sv = new Operand(n.value, n.base);
if(sv.value.maxBitSize != n.value.maxBitSize) throw new Error("Gotcha!"); if(sv.value.maxBitSize != n.value.maxBitSize) throw new Error("Gotcha!");
return sv; return sv;
} }

View File

@@ -37,7 +37,9 @@ code { font-size: 1.2em; font-weight: bold; }
.result .content { padding-left: 10px} .result .content { padding-left: 10px}
.result .cur { margin-right: 5px; } .result .cur { margin-right: 5px; }
.input-string { margin-right: 5px;}
.hashLink { text-decoration: none; margin-left: 5px; visibility: hidden; margin-right: 0; padding: 0; text-decoration: none; } .hashLink { text-decoration: none; margin-left: 5px; visibility: hidden; margin-right: 0; padding: 0; text-decoration: none; }
a.hashLink { font-size: 1.1em;}
.hashLink:hover { text-decoration: underline; margin-left: 5px; background: none; } .hashLink:hover { text-decoration: underline; margin-left: 5px; background: none; }
.result:hover .hashLink { visibility: visible } .result:hover .hashLink { visibility: visible }
@@ -57,7 +59,8 @@ code { font-size: 1.2em; font-weight: bold; }
.hex .prefix { display: inline; } .hex .prefix { display: inline; }
.indicator { padding: 0px 5px; background: transparent; border: none; cursor: pointer; vertical-align: middle; } .indicator { padding: 0px 5px; background: transparent; border: none; cursor: pointer; vertical-align: middle; margin-left: -3em; color:rgba(0, 0, 0, 0.25) }
.error { color: maroon; } .error { color: maroon; }
.soft { opacity: 0.7 } .soft { opacity: 0.7 }
@@ -68,9 +71,12 @@ code { font-size: 1.2em; font-weight: bold; }
.cur { color: lightgray; } .cur { color: lightgray; }
button { border: none; text-decoration: underline;}
/* Light */ /* Light */
.light { background: #fafafa; } .light { background: #fafafa; }
.light a, .light a:visited { color: #222; } .light .header-cmd { color: #919191 }
.light a, .light a:visited, .light button { color: #222; }
.light .one { color: black; } .light .one { color: black; }
.light .zero { color: #888; } .light .zero { color: #888; }
.light .indicator { color: #ddd; } .light .indicator { color: #ddd; }
@@ -91,29 +97,29 @@ code { font-size: 1.2em; font-weight: bold; }
.dark { background: #121212; color: white;} .dark { background: #121212; color: white;}
.dark .expression { color: white;} .dark .expression { color: white;}
.dark .expressionInput { color: white; } .dark .expressionInput { color: white; }
.dark a, .dark a:visited { color: white; } .dark a, .dark a:visited, .dark button { color: white; }
.dark .indicator { color: #555; } .dark .indicator { color: #555; }
.dark .on { color: white; } .dark .on { color: white; }
.dark .zero { color: #999;} .dark .zero { color: #999;}
.dark .prefix { color: #999} .dark .prefix { color: #999}
.dark .other { color: #777;} .dark .other { color: #777;}
.dark .hashLink, .dark .hashLink:visited { color: #333 } .dark .hashLink, .dark .hashLink:visited { color: #555 }
.dark .hashLink:hover { color: #999 } .dark .hashLink:hover { color: #999 }
.dark ul.top-links li:hover { background: #333 } .dark ul.top-links li:hover { background: #333 }
.dark .error { color: #da586d} .dark .error { color: #da586d}
.dark button.btn { color: white} .dark button.btn { color: white}
.dark button.btn:hover { background: #333} .dark button.btn:hover { background: #333}
.dark button.btn:disabled { color: #999; background-color: inherit; } .dark button.btn:disabled { color: #999; background-color: inherit; }
.dark .accent1 { color:mediumseagreen}ч .dark .accent1 { color:mediumseagreen}
/* /*
Midnight Theme Midnight Theme
*/ */
.midnight .header-cmd { color: #85a0ad }
.midnight { background: #2c3e50; color: white } .midnight { background: #2c3e50; color: white }
.midnight .header-cmd { color: #7ea3b5 !important }
.midnight .expression { color: white;} .midnight .expression { color: white;}
.midnight .expressionInput { color: white;} .midnight .expressionInput { color: white;}
.midnight a, .dark a:visited { color: white; } .midnight a, .dark a:visited, .midnight button { color: white; }
.midnight .indicator { color: #85a0ad; } .midnight .indicator { color: #85a0ad; }
.midnight .on { color: white; } .midnight .on { color: white; }
.midnight .zero { color: #85a0ad;} .midnight .zero { color: #85a0ad;}
@@ -177,7 +183,3 @@ button:focus {outline:0;}
opacity: 0.5; opacity: 0.5;
} }
.debug-indicators {
left: 1px;
opacity: 0.5;
}

View File

@@ -1,11 +1,11 @@
import log from 'loglevel'; import log from 'loglevel';
const APP_VERSION = 9; export const APP_VERSION = 9;
export type PersistedAppData = { export type PersistedAppData = {
emphasizeBytes: boolean; emphasizeBytes: boolean;
uiTheme: string; uiTheme: string;
version: number; version: number | null;
debugMode: boolean | null; debugMode: boolean | null;
pageVisistsCount: number; pageVisistsCount: number;
donationClicked: boolean donationClicked: boolean
@@ -41,7 +41,7 @@ export default class AppState {
this.uiTheme = persistData.uiTheme || 'midnight'; this.uiTheme = persistData.uiTheme || 'midnight';
this.env = env; this.env = env;
this.emphasizeBytes = persistData.emphasizeBytes || true; this.emphasizeBytes = !!persistData.emphasizeBytes;
this.persistedVersion = persistData.version || 0.1; this.persistedVersion = persistData.version || 0.1;
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion; this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
this.debugMode = persistData.debugMode === true; this.debugMode = persistData.debugMode === true;

View File

@@ -1,12 +1,21 @@
import AppState, { PersistedAppData } from "./AppState"; import AppState, { APP_VERSION, PersistedAppData } from "./AppState";
const storeKey = 'AppState'; const storeKey = 'AppState';
const DEFAULT_DATA : PersistedAppData = {
uiTheme: 'midnight',
emphasizeBytes: true,
version: APP_VERSION,
debugMode: false,
pageVisistsCount: 0,
donationClicked: false
}
export default { export default {
getPersistedData() : PersistedAppData { getPersistedData() : PersistedAppData {
var json = window.localStorage.getItem(storeKey); var json = window.localStorage.getItem(storeKey);
if(!json) { if(!json) {
return {} as PersistedAppData; return DEFAULT_DATA;
} }
try { try {
@@ -14,7 +23,7 @@ export default {
} }
catch(ex) { catch(ex) {
console.error('Failed to parse AppState json. Json Value: \n' + json, ex); console.error('Failed to parse AppState json. Json Value: \n' + json, ex);
return {} as PersistedAppData;; return DEFAULT_DATA;;
} }
}, },

View File

@@ -1,2 +1,2 @@
.debug-indicators { position: absolute; top: 1em; left: 1em} .debug-indicators { position: absolute; top: 3.5em; right: 0.1em}
.debug-indicators span {display: block;} .debug-indicators span {display: block;}

View File

@@ -21,8 +21,8 @@ const DisplayResultView: React.FunctionComponent<DisplayResultProps> = (props) =
return <div className="result"> return <div className="result">
<div className="input mono"> <div className="input mono">
<span className="cur"> <span className="cur">&gt;</span>
&gt;</span>{props.input} <span className="input-string">{props.input}</span>
<a className="hashLink" title="Link for this expression" href={window.location.pathname + '#' + props.inputHash}> <a className="hashLink" title="Link for this expression" href={window.location.pathname + '#' + props.inputHash}>
<FontAwesomeIcon className="icon" icon={faHashtag} size="xs" /> <FontAwesomeIcon className="icon" icon={faHashtag} size="xs" />
</a> </a>

View File

@@ -1,7 +1,14 @@
.help ul { list-style-type: none; margin: 0; margin-top: 0.2em; padding: 0; } .help ul { list-style-type: none; margin: 0; margin-left: 10px; margin-top: 0.2em; padding: 0; }
.help li { padding: 1px;}
.help p { margin-top: 0.5em } .help p { margin-top: 0.5em }
.help .section {padding: 1em;} .help .section {padding: 1em;}
.help .panel-container {overflow: hidden;} .help .panel-container {overflow: hidden;}
.help .left-panel {float:left; margin-right: 1em;} .help .left-panel {float:left; margin-right: 1em;}
.help .right-panel {float:left; } .help .right-panel {float:left; }
.help .section-title {font-weight: bold;} .help .section-title {font-weight: bold;}
@media (min-width: 1024px) {
.left-panel, .right-panel {
width: 45%;
}
}

View File

@@ -10,7 +10,7 @@ function TopLinks() {
return <ul className="top-links"> return <ul className="top-links">
<li> <li>
<a href='https://www.paypal.com/donate/?hosted_button_id=3GREJYC4T5AJ8' onClick={onDonate} target="_blank"> <a href='https://www.paypal.com/donate/?hosted_button_id=3GREJYC4T5AJ8' onClick={onDonate} target="_blank">
<FontAwesomeIcon className='icon' icon={faDonate} size="lg" />donate <FontAwesomeIcon className='icon' icon={faDonate} size="lg" /><span className='link-text'>donate</span>
</a> </a>
</li> </li>
<li> <li>

View File

@@ -53,7 +53,7 @@ function getStartupCommands(appState : AppState) : string[] {
startupCommands = hashArgs; startupCommands = hashArgs;
} }
log.debug('Executing startup commands', startupCommands); log.debug('Startup commands loaded', startupCommands);
return startupCommands; return startupCommands;
} }