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("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 {
switch(operator) {
case ">>": return this.rshift(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] {
console.log('equalizeSize()', new Error().stack);
if(n1.maxBitSize == n2.maxBitSize)
{
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;
let signBitIndex = -1;
return bitChars.map((c, i) => {
var className = c == '1' ? `one${css}` : `zero${css}`;

View File

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

View File

@@ -1,5 +1,5 @@
import BitwiseOperator from "./BitwiseOperator";
import { ScalarValue } from "./expression";
import Operator from "./Operator";
import { Operand } from "./expression";
import { ExpressionElement } from "./expression-interfaces";
import { type } from "os";
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 ScalarValue from "./ScalarValue";
import Operand from "./Operand";
import { Expression, ExpressionElement } from "./expression-interfaces";
export default class ListOfNumbersExpression implements Expression {
children: ScalarValue[];
export default class ListOfNumbers implements Expression {
children: Operand[];
expressionString: string;
maxBitsLength: number;
constructor(expressionString: string, numbers: ScalarValue[]) {
constructor(expressionString: string, numbers: Operand[]) {
this.expressionString = expressionString;
this.children = numbers;
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 ScalarValue from "./ScalarValue";
import Operand from "./Operand";
it('converts numbers to bigint', () => {
const int32 = new ScalarValue(INT32_MAX_VALUE);
const int64 = new ScalarValue(BigInt(INT32_MAX_VALUE+1));
const int32 = new Operand(INT32_MAX_VALUE);
const int64 = new Operand(BigInt(INT32_MAX_VALUE+1));
expect(int32.value.maxBitSize).toBe(32);
expect(int64.value.maxBitSize).toBe(64);

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import calc from '../core/calc';
import ScalarValue from './ScalarValue';
import Operand from './Operand';
import { ExpressionElement } from './expression-interfaces';
export default class BitwiseOperator implements ExpressionElement {
export default class Operator implements ExpressionElement {
operand: ExpressionElement;
operator: string;
isOperator: boolean;
@@ -18,9 +18,9 @@ export default class BitwiseOperator implements ExpressionElement {
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');
if( this.operator != "~" && operand == null)
@@ -29,12 +29,12 @@ export default class BitwiseOperator implements ExpressionElement {
var evaluatedOperand = this.operand.evaluate();
return this.operator == "~"
? applyNotOperator(this.operand.getUnderlyingScalarOperand())
? applyNotOperator(this.operand.getUnderlyingOperand())
: applyOperator(operand!, this.operator, evaluatedOperand);
}
getUnderlyingScalarOperand() : ScalarValue {
return this.operand.getUnderlyingScalarOperand();
getUnderlyingOperand() : Operand {
return this.operand.getUnderlyingOperand();
}
toString(): string {
@@ -42,13 +42,14 @@ export default class BitwiseOperator implements ExpressionElement {
}
}
function applyNotOperator(operand: ScalarValue) : ScalarValue {
return new ScalarValue(calc.not(operand.value), operand.base);
function applyNotOperator(operand: Operand) : Operand {
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);
if(!isShift)
{
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);
}
console.log(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 n2 = op2.value;

View File

@@ -3,7 +3,7 @@ import formatter from '../../core/formatter';
import BinaryStringView, { FlipBitEventArg } from '../../core/components/BinaryString';
import BitwiseResultViewModel from './BitwiseResultViewModel';
import { Expression, ExpressionElement } from '../expression-interfaces';
import { BitwiseOperator, ScalarValue } from '../expression';
import { Operator, Operand, ListOfNumbers } from '../expression';
import calc from '../../core/calc';
type BitwiseResultViewProps = {
@@ -27,18 +27,18 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
render() {
let model : BitwiseResultViewModel | null = null
const isListOfNumbers = this.props.expression instanceof ListOfNumbers;
try
{
{
model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
}
catch(err) {
}
catch(err) {
const text = (err as any).message;
return <div className='error'>Error: {text}</div>
}
}
var rows = this.getRows(model!);
var rows = this.getRows(model!, isListOfNumbers);
return <table className="expression">
<tbody>
@@ -47,7 +47,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
</table>
}
getRows(model: BitwiseResultViewModel): JSX.Element[] {
getRows(model: BitwiseResultViewModel, allowSignChange : boolean): JSX.Element[] {
this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits);
@@ -58,6 +58,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
css={itm.css}
bitSize={itm.maxBitSize}
allowFlipBits={itm.allowFlipBits}
allowSignChange={allowSignChange}
expressionItem={itm.expression}
emphasizeBytes={this.props.emphasizeBytes}
maxNumberOfBits={this.maxSeenLengthNumberOfBits}
@@ -77,15 +78,20 @@ type ExpressionRowProps = {
maxNumberOfBits: number,
emphasizeBytes: boolean,
allowFlipBits: boolean,
allowSignChange: boolean,
expressionItem: ExpressionElement,
onBitFlipped: any
}
class ExpressionRow extends React.Component<ExpressionRowProps> {
infoWasShown: boolean = false;
constructor(props: ExpressionRowProps) {
super(props);
this.state = { operand: null };
}
render() {
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
const scalar = this.props.expressionItem.evaluate();
@@ -113,17 +119,17 @@ 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 BitwiseOperator;
return ex.operator + this.getLabelString(ex.getUnderlyingScalarOperand());
const ex = this.props.expressionItem as Operator;
return ex.operator + this.getLabelString(ex.getUnderlyingOperand());
}
return this.getLabelString(this.props.expressionItem.getUnderlyingScalarOperand());
return this.getLabelString(this.props.expressionItem.getUnderlyingOperand());
}
getAlternative() {
if (this.props.expressionItem.isOperator) {
const ex = this.props.expressionItem as BitwiseOperator;
const ex = this.props.expressionItem as Operator;
const res = ex.evaluate();
return formatter.numberToString(res.value, res.base);
@@ -134,13 +140,13 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
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);
}
flipBit(args: FlipBitEventArg) {
const op = this.props.expressionItem.getUnderlyingScalarOperand();
const op = this.props.expressionItem.getUnderlyingOperand();
const { bitIndex: index, binaryStringLength: totalLength } = args;
const maxBitSize = op.value.maxBitSize;
@@ -155,27 +161,47 @@ class ExpressionRow extends React.Component<ExpressionRowProps> {
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) {
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`;
let text = `${op.value.maxBitSize}-bit `;
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>;
text += " (converted)";
title += ". This number was converted to facilitate bitwise operation with an operand of a different type";
}
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 calc from '../../core/calc';
import formatter from '../../core/formatter';
@@ -31,30 +31,30 @@ export default class BitwiseResultViewModel {
this.allowFlipBits = allowFlipBits === true;
}
static buildListOfNumbers(expr : ListOfNumbersExpression, config : Config) {
static buildListOfNumbers(expr : ListOfNumbers, config : Config) {
var model = new BitwiseResultViewModel(config);
expr.children.forEach(op => model.addScalarRow(op));
model.maxNumberOfBits = BitwiseResultViewModel.applyEmphasizeBytes(model.maxNumberOfBits, model.emphasizeBytes);
return model;
}
static buildBitwiseOperation (expr : BitwiseOperationExpression, config : Config) {
static buildBitwiseOperation (expr : BitwiseOperation, config : Config) {
var op = expr.children[0],
i = 0, len = expr.children.length,
ex, m = new BitwiseResultViewModel(config);
var prev : ScalarValue | null = null;
var prev : Operand | null = null;
for (;i<len;i++) {
ex = expr.children[i];
if(ex instanceof ScalarValue) {
if(ex instanceof Operand) {
m.addScalarRow(ex);
prev = ex;
continue;
}
var eo = ex as BitwiseOperator;
var eo = ex as Operator;
// If it a single NOT expression
if(eo.isNotExpression) {
@@ -64,11 +64,11 @@ export default class BitwiseResultViewModel {
prev = notResult;
}
else if(eo.isShiftExpression){
prev = eo.evaluate(prev as ScalarValue);
prev = eo.evaluate(prev as Operand);
m.addShiftExpressionResultRow(eo, prev);
} else {
prev = eo.evaluate(prev as ScalarValue);
prev = eo.evaluate(prev as Operand);
m.addOperatorRow(eo);
m.addExpressionResultRow(prev);
}
@@ -78,7 +78,7 @@ export default class BitwiseResultViewModel {
return m;
};
addScalarRow(expr: ScalarValue) {
addScalarRow(expr: Operand) {
const bits = calc.numberOfBitsDisplayed(expr.value);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
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);
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);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
const child = expr.operand.getUnderlyingScalarOperand();
const child = expr.operand.getUnderlyingOperand();
this.items.push({
sign: expr.operator + formatter.numberToString(child.value, child.base),
css: 'expression-result',
@@ -122,7 +122,7 @@ export default class BitwiseResultViewModel {
});
};
addExpressionResultRow(expr : ScalarValue) {
addExpressionResultRow(expr : Operand) {
const bits = calc.numberOfBitsDisplayed(expr.value);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
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)
}
@@ -155,14 +155,15 @@ export default class BitwiseResultViewModel {
};
static createModel(expr : Expression, emphasizeBytes: boolean) : BitwiseResultViewModel {
if(expr instanceof ListOfNumbersExpression) {
if(expr instanceof ListOfNumbers) {
return BitwiseResultViewModel.buildListOfNumbers(expr, {
emphasizeBytes: emphasizeBytes,
allowFlipBits: true
});
}
if(expr instanceof BitwiseOperationExpression) {
if(expr instanceof BitwiseOperation) {
return BitwiseResultViewModel.buildBitwiseOperation(expr, {
emphasizeBytes: emphasizeBytes,
allowFlipBits: true

View File

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

View File

@@ -1,15 +1,14 @@
import ScalarValue from './ScalarValue';
import BitwiseOperator from './BitwiseOperator'
import ListOfNumbersExpression from './ListOfNumbersExpression';
import BitwiseOperationExpression from './BitwiseOperationExpression';
import Operand from './Operand';
import Operator from './Operator'
import ListOfNumbers from './ListOfNumbers';
import BitwiseOperation from './BitwiseOperation';
import { Expression, ExpressionElement } from './expression-interfaces';
import { numberParser, numberRegexString } from './numberParser';
import { parse } from 'path';
export { default as ScalarValue } from './ScalarValue';
export { default as BitwiseOperator } from './BitwiseOperator';
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression';
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression';
export { default as Operand } from './Operand';
export { default as Operator } from './Operator';
export { default as ListOfNumbers } from './ListOfNumbers';
export { default as BitwiseOperation } from './BitwiseOperation';
interface IExpressionParserFactory {
canCreate: (input: string) => boolean;
@@ -76,7 +75,7 @@ class ListOfNumbersExpressionFactory implements IExpressionParserFactory
.filter(p => p.length > 0)
.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));
}
return new BitwiseOperationExpression(normalizedString, operands)
return new BitwiseOperation(normalizedString, operands)
};
parseMatch (m:RegExpExecArray): ExpressionElement {
@@ -117,16 +116,16 @@ class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
var parsed = null;
if(num.indexOf('~') == 0) {
parsed = new BitwiseOperator(parseScalarValue(num.substring(1)), '~');
parsed = new Operator(parseScalarValue(num.substring(1)), '~');
}
else {
parsed = parseScalarValue(num);
}
if(operator == null) {
return parsed as BitwiseOperator;
return parsed as Operator;
} 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);
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!");
return sv;
}

View File

@@ -37,7 +37,9 @@ code { font-size: 1.2em; font-weight: bold; }
.result .content { padding-left: 10px}
.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; }
a.hashLink { font-size: 1.1em;}
.hashLink:hover { text-decoration: underline; margin-left: 5px; background: none; }
.result:hover .hashLink { visibility: visible }
@@ -57,7 +59,8 @@ code { font-size: 1.2em; font-weight: bold; }
.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; }
.soft { opacity: 0.7 }
@@ -68,9 +71,12 @@ code { font-size: 1.2em; font-weight: bold; }
.cur { color: lightgray; }
button { border: none; text-decoration: underline;}
/* Light */
.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 .zero { color: #888; }
.light .indicator { color: #ddd; }
@@ -91,29 +97,29 @@ code { font-size: 1.2em; font-weight: bold; }
.dark { background: #121212; color: white;}
.dark .expression { 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 .on { color: white; }
.dark .zero { color: #999;}
.dark .prefix { color: #999}
.dark .other { color: #777;}
.dark .hashLink, .dark .hashLink:visited { color: #333 }
.dark .hashLink, .dark .hashLink:visited { color: #555 }
.dark .hashLink:hover { color: #999 }
.dark ul.top-links li:hover { background: #333 }
.dark .error { color: #da586d}
.dark button.btn { color: white}
.dark button.btn:hover { background: #333}
.dark button.btn:disabled { color: #999; background-color: inherit; }
.dark .accent1 { color:mediumseagreen}ч
.dark .accent1 { color:mediumseagreen}
/*
Midnight Theme
*/
.midnight .header-cmd { color: #85a0ad }
.midnight { background: #2c3e50; color: white }
.midnight .header-cmd { color: #7ea3b5 !important }
.midnight .expression { 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 .on { color: white; }
.midnight .zero { color: #85a0ad;}
@@ -177,7 +183,3 @@ button:focus {outline:0;}
opacity: 0.5;
}
.debug-indicators {
left: 1px;
opacity: 0.5;
}

View File

@@ -1,11 +1,11 @@
import log from 'loglevel';
const APP_VERSION = 9;
export const APP_VERSION = 9;
export type PersistedAppData = {
emphasizeBytes: boolean;
uiTheme: string;
version: number;
version: number | null;
debugMode: boolean | null;
pageVisistsCount: number;
donationClicked: boolean
@@ -41,7 +41,7 @@ export default class AppState {
this.uiTheme = persistData.uiTheme || 'midnight';
this.env = env;
this.emphasizeBytes = persistData.emphasizeBytes || true;
this.emphasizeBytes = !!persistData.emphasizeBytes;
this.persistedVersion = persistData.version || 0.1;
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
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 DEFAULT_DATA : PersistedAppData = {
uiTheme: 'midnight',
emphasizeBytes: true,
version: APP_VERSION,
debugMode: false,
pageVisistsCount: 0,
donationClicked: false
}
export default {
getPersistedData() : PersistedAppData {
var json = window.localStorage.getItem(storeKey);
if(!json) {
return {} as PersistedAppData;
return DEFAULT_DATA;
}
try {
@@ -14,7 +23,7 @@ export default {
}
catch(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;}

View File

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

View File

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