Do not pad negative numbers (#50)

This commit is contained in:
Borys Levytskyi
2023-05-15 18:53:27 +03:00
committed by GitHub
parent 0b1f5fa862
commit 22f38e936f
20 changed files with 258 additions and 154 deletions

View File

@@ -98,7 +98,7 @@ export class Integer {
} }
toString(base?:number) { toString(base?:number) {
return formatter.numberToString(this, base || 10); return formatter.numberToString(this, base || 10, this.maxBitSize);
} }
num() { num() {

View File

@@ -74,14 +74,20 @@ describe('calc.addSpace', () => {
describe('calc.numberOfBitsDisplayed', () => { describe('calc.numberOfBitsDisplayed', () => {
it('calculates number of bits', () => { it('calculates number of bits', () => {
expect(calc.numberOfBitsDisplayed(1)).toBe(1); expect(calc.numberOfBitsDisplayed(1)).toBe(1);
expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(32); expect(calc.numberOfBitsDisplayed(BigInt(-1))).toBe(1);
expect(calc.numberOfBitsDisplayed(2)).toBe(2); expect(calc.numberOfBitsDisplayed(2)).toBe(2);
expect(calc.numberOfBitsDisplayed(3)).toBe(2); expect(calc.numberOfBitsDisplayed(3)).toBe(2);
expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36); expect(calc.numberOfBitsDisplayed(68719476735)).toBe(36);
expect(calc.numberOfBitsDisplayed(INT32_MIN_VALUE-1)).toBe(64); expect(calc.numberOfBitsDisplayed(INT32_MIN_VALUE-1)).toBe(32);
}); });
}); });
describe('calc.xor', () => {
it('positive and negative nubmer', () => {
expect(calc.xor(Integer.int(-1), Integer.int(10)).num()).toBe(-11);
});
})
describe('calc.lshift', () => { describe('calc.lshift', () => {
it("respects bit size", () => { it("respects bit size", () => {
@@ -167,10 +173,12 @@ describe("calc misc", () => {
it('promoteTo64Bit', () => { it('promoteTo64Bit', () => {
const n = asInteger(-1); const n = asInteger(-1);
expect(calc.toBinaryString(calc.promoteTo64Bit(n))).toBe("11111111111111111111111111111111"); expect(calc.toBinaryString(calc.promoteTo64Bit(n))).toBe("1");
}); });
it('binaryRepresentation', () => { it('binaryRepresentation', () => {
expect(calc.toBinaryString(Integer.int(-2147483647))).toBe("0000000000000000000000000000001");
expect(calc.toBinaryString(asInteger(2147483647))).toBe("1111111111111111111111111111111"); expect(calc.toBinaryString(asInteger(2147483647))).toBe("1111111111111111111111111111111");
}); });

View File

@@ -20,6 +20,10 @@ export default {
}, },
addSpace(number: Integer, requiredSpace: number) : Integer { addSpace(number: Integer, requiredSpace: number) : Integer {
if(requiredSpace < 0)
throw new Error("Required space cannot be negative");
const totalSpaceRequired = number.maxBitSize + requiredSpace; const totalSpaceRequired = number.maxBitSize + requiredSpace;
return new Integer(number.value, nextPowOfTwo(totalSpaceRequired)); return new Integer(number.value, nextPowOfTwo(totalSpaceRequired));
}, },
@@ -45,10 +49,10 @@ export default {
throw new Error(`Binary represenation '${bin}' is bigger than the given bit size ${bitSize}`) throw new Error(`Binary represenation '${bin}' is bigger than the given bit size ${bitSize}`)
const r = num.value < 0 const r = num.value < 0
? this.engine.applyTwosComplement(bin.padStart(bitSize, '0')) ? this.engine.applyTwosComplement(bin)
: bin; : bin;
return r; return bin.length != bitSize ? r.substring(r.length-bin.length) : r;
}, },
lshift (num: Integer, numBytes : JsNumber) : Integer { lshift (num: Integer, numBytes : JsNumber) : Integer {
@@ -105,7 +109,7 @@ export default {
_applySingle(num: Integer, operation: (bin:string) => string) : Integer { _applySingle(num: Integer, operation: (bin:string) => string) : Integer {
let bin = this.toBinaryString(num).padStart(num.maxBitSize, '0'); let bin = this.toBinaryString(num).padStart(num.maxBitSize, num.value < 0 ? '1' : '0');
bin = operation(bin); bin = operation(bin);
@@ -127,8 +131,8 @@ export default {
const [num1, num2] = equalizeSize(op1, op2); const [num1, num2] = equalizeSize(op1, op2);
let bin1 = this.toBinaryString(num1).padStart(num1.maxBitSize, '0'); let bin1 = this.toBinaryString(num1).padStart(num1.maxBitSize, num1.value < 0 ? '1' : '0');
let bin2 = this.toBinaryString(num2).padStart(num2.maxBitSize, '0'); let bin2 = this.toBinaryString(num2).padStart(num2.maxBitSize, num2.value < 0 ? '1' : '0');
let resultBin = operation(bin1, bin2); let resultBin = operation(bin1, bin2);
@@ -225,7 +229,9 @@ export default {
flipped.unshift(bin.charAt(i) == "1" ? "0" : "1"); flipped.unshift(bin.charAt(i) == "1" ? "0" : "1");
} }
return flipped.join('') + bin.substring(lastIndex) ; const result = flipped.join('') + bin.substring(lastIndex);
//logLines(bin + " " + bin.length, result + " " + result.length);
return result;
}, },
} }
}; };

View File

@@ -9,7 +9,7 @@ export type BinaryStringViewProps = {
className?: string; className?: string;
disableHighlight?: boolean, disableHighlight?: boolean,
signBitIndex?: number, signBitIndex?: number,
integerBitSize?: number valueBitSize?: number
}; };
export type FlipBitEventArg = { export type FlipBitEventArg = {
@@ -17,7 +17,7 @@ export type FlipBitEventArg = {
binaryStringLength: number; binaryStringLength: number;
$event: any; $event: any;
newBinaryString: any; newBinaryString: any;
isTypeExtend: boolean isExtraBit: boolean
}; };
export default class BinaryStringView extends React.Component<BinaryStringViewProps> { export default class BinaryStringView extends React.Component<BinaryStringViewProps> {
@@ -39,7 +39,7 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
binaryStringLength: this.props.binaryString.length, binaryStringLength: this.props.binaryString.length,
newBinaryString: newBinaryString, newBinaryString: newBinaryString,
$event: e, $event: e,
isTypeExtend: isExtra isExtraBit: isExtra
}); });
} }
@@ -58,8 +58,8 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
const css = allowFlipBits ? ' flipable' : '' const css = allowFlipBits ? ' flipable' : ''
const disableHighlight = this.props.disableHighlight || false; const disableHighlight = this.props.disableHighlight || false;
const firstBitIndex = this.props.integerBitSize != null const firstBitIndex = this.props.valueBitSize != null
? bitChars.length - this.props.integerBitSize ? bitChars.length - this.props.valueBitSize
: -1; : -1;
return bitChars.map((c, i) => { return bitChars.map((c, i) => {

View File

@@ -12,8 +12,10 @@ describe("formatter", () => {
const minusOne = BigInt(-1); const minusOne = BigInt(-1);
const n32 = new Integer(minusOne, 32); const n32 = new Integer(minusOne, 32);
const n64 = new Integer(minusOne, 64); const n64 = new Integer(minusOne, 64);
expect(formatter.bin(n32)).toBe("11111111111111111111111111111111"); expect(formatter.bin(n32)).toBe("1");
expect(formatter.bin(n64)).toBe("1111111111111111111111111111111111111111111111111111111111111111"); expect(formatter.bin(n64)).toBe("1");
expect(formatter.fullBin(n32)).toBe("11111111111111111111111111111111");
expect(formatter.fullBin(n64)).toBe("1111111111111111111111111111111111111111111111111111111111111111");
}); });
it('formats large binary number correctly', () => { it('formats large binary number correctly', () => {
@@ -25,9 +27,10 @@ describe("formatter", () => {
}); });
it('formats negative binary numbers', () => { it('formats negative binary numbers', () => {
expect(formatter.numberToString(-1, 'bin')).toBe("11111111111111111111111111111111"); expect(formatter.numberToString(-1, 'bin')).toBe("1");
expect(formatter.numberToString(-1, 'bin', 32)).toBe("11111111111111111111111111111111");
expect(formatter.numberToString(-0, 'bin')).toBe("0"); expect(formatter.numberToString(-0, 'bin')).toBe("0");
expect(formatter.numberToString(-2147483647, 'bin')).toBe("10000000000000000000000000000001"); expect(formatter.numberToString(-2147483647, 'bin')).toBe("0000000000000000000000000000001");
}); });
it('pads left', () => { it('pads left', () => {

View File

@@ -3,7 +3,7 @@ import { Integer, JsNumber, asInteger } from "./Integer";
export type NumberBase = 'dec' | 'hex' | 'bin'; export type NumberBase = 'dec' | 'hex' | 'bin';
const formatter = { const formatter = {
numberToString: function(num: Integer | JsNumber, base: NumberBase | number) : string { numberToString: function(num: Integer | JsNumber, base: NumberBase | number, padLength?: number) : string {
num = asInteger(num); num = asInteger(num);
base = typeof base == "string" ? getBase(base) : base; base = typeof base == "string" ? getBase(base) : base;
@@ -13,7 +13,13 @@ const formatter = {
var hexVal = calc.abs(num).value.toString(16); var hexVal = calc.abs(num).value.toString(16);
return num.value >= 0 ? '0x' + hexVal : '-0x' + hexVal; return num.value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
case 2: case 2:
return calc.toBinaryString(num); const bin = calc.toBinaryString(num);
if(padLength == null)
return bin;
const padChar = num.value >= 0 ? '0' : '1';
return bin.padStart(padLength, padChar);
case 10: case 10:
return num.value.toString(10); return num.value.toString(10);
default: default:
@@ -36,6 +42,9 @@ const formatter = {
bin(number: Integer | JsNumber) { bin(number: Integer | JsNumber) {
return this.numberToString(number, 'bin'); return this.numberToString(number, 'bin');
}, },
fullBin(number: Integer) {
return this.numberToString(number, 'bin', number.maxBitSize);
},
emBin(number: Integer | JsNumber) { emBin(number: Integer | JsNumber) {
return this.padLeft(this.bin(number), 8, '0'); return this.padLeft(this.bin(number), 8, '0');
}, },

View File

@@ -7,13 +7,15 @@ import { Operator, Operand, ListOfNumbers } from '../expression';
import calc from '../../core/calc'; import calc from '../../core/calc';
import { Integer } from '../../core/Integer'; import { Integer } from '../../core/Integer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUndo } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle, faTriangleExclamation, faUndo } from '@fortawesome/free-solid-svg-icons';
import loglevel from 'loglevel'; import loglevel from 'loglevel';
import IconWithToolTip from '../../shell/components/IconWithTooltip';
type BitwiseResultViewProps = { type BitwiseResultViewProps = {
expression: Expression; expression: Expression;
emphasizeBytes: boolean; emphasizeBytes: boolean;
annotateTypes: boolean annotateTypes: boolean,
dimExtrBits: boolean
} }
type BitwiseResultViewState = { type BitwiseResultViewState = {
@@ -31,31 +33,37 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
render() { render() {
let model : BitwiseResultViewModel | null = null let model: BitwiseResultViewModel | null = null
const allowSignChange = this.props.expression instanceof ListOfNumbers; const allowSignChange = this.props.expression instanceof ListOfNumbers;
try try {
{ model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes, this.props.annotateTypes);
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!, allowSignChange); const rows = this.getRows(model!, allowSignChange);
return <table className="expression"> let css = "expression";
<tbody>
{rows} if(this.props.dimExtrBits)
</tbody> css += " dim-extra-bits";
</table>
return <React.Fragment>
<table className={css}>
<tbody>
{rows}
</tbody>
</table>
</React.Fragment>
} }
getRows(model: BitwiseResultViewModel, allowSignChange : boolean): JSX.Element[] { getRows(model: BitwiseResultViewModel, allowSignChange: boolean): JSX.Element[] {
this.maxSeenLengthNumberOfBits = Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits); this.maxSeenLengthNumberOfBits = model.maxNumberOfBits; //Math.max(model.maxNumberOfBits, this.maxSeenLengthNumberOfBits);
return model.items.map((itm, i) => return model.items.map((itm, i) =>
<ExpressionElementTableRow <ExpressionElementTableRow
@@ -68,7 +76,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
expressionItem={itm.expressionElement} expressionItem={itm.expressionElement}
emphasizeBytes={this.props.emphasizeBytes} emphasizeBytes={this.props.emphasizeBytes}
maxNumberOfBits={this.maxSeenLengthNumberOfBits} maxNumberOfBits={this.maxSeenLengthNumberOfBits}
showInfoColumn={this.props.annotateTypes} annotateTypes={this.props.annotateTypes}
onValueChanged={() => this.onValueChanged()} />); onValueChanged={() => this.onValueChanged()} />);
} }
@@ -88,8 +96,8 @@ type ExpressionElementRowProps = {
allowSignChange: boolean, allowSignChange: boolean,
expressionItem: ExpressionElement, expressionItem: ExpressionElement,
onValueChanged: any, onValueChanged: any,
showInfoColumn: boolean, annotateTypes: boolean
} };
class ExpressionElementTableRow extends React.Component<ExpressionElementRowProps> { class ExpressionElementTableRow extends React.Component<ExpressionElementRowProps> {
@@ -105,11 +113,11 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
} }
render() { render() {
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props; const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits, annotateTypes } = this.props;
const scalar = this.props.expressionItem.evaluate(); const scalar = this.props.expressionItem.evaluate();
const padChar = scalar.value.value >= 0 ? '0' : '1'; const bin = formatter.numberToString(scalar.value, 'bin', maxNumberOfBits);
const bin = formatter.numberToString(scalar.value, 'bin').padStart(maxNumberOfBits, padChar);
const signBitIndex = scalar.value.signed && bin.length >= scalar.value.maxBitSize ? bin.length - scalar.value.maxBitSize : -1; const signBitIndex = scalar.value.signed && bin.length >= scalar.value.maxBitSize ? bin.length - scalar.value.maxBitSize : -1;
const valueSize = annotateTypes ? scalar.value.maxBitSize : calc.numberOfBitsDisplayed(scalar.value);
return <tr className={"row-with-bits " + css}> return <tr className={"row-with-bits " + css}>
<td className="sign">{sign}</td> <td className="sign">{sign}</td>
@@ -122,25 +130,37 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
binaryString={bin} binaryString={bin}
allowFlipBits={allowFlipBits} allowFlipBits={allowFlipBits}
signBitIndex={signBitIndex} signBitIndex={signBitIndex}
integerBitSize={this.scalar.value.maxBitSize} valueBitSize={valueSize}
onBitClicked={args => this.onBitClicked(args)} /> onBitClicked={args => this.onBitClicked(args)} />
</td> </td>
<td className="other">{this.getAlternative()}</td> <td className="other">{this.getAlternative()}</td>
<td className="info accent1" data-test-name='ignore'>{this.props.showInfoColumn ? this.getInfo() : null}</td> <td className="info accent1" data-test-name='ignore'>{this.props.annotateTypes ? this.getInfo() : null}</td>
<td className='undo' data-test-name='ignore'> <td className='undo' data-test-name='ignore'>
{this.getUndoButton()} {this.getControlButtons()}
</td> </td>
</tr>; </tr>;
} }
getUndoButton(): React.ReactNode { getControlButtons(): React.ReactNode {
return !this.originalValue.isTheSame(this.scalar.value) const buttons = [];
? <button title='Undo all changes' className='undo' data-control="undo" onClick={() => this.undo()}><FontAwesomeIcon icon={faUndo}/></button>
: null; if (this.scalar.value.value < 0)
buttons.push(<IconWithToolTip icon={faInfoCircle}>
<div className='accent1 tooltip-header'>Two's Complement</div>
<p>
This is a negative number. It's binary representation is <u>inverted</u> using <strong>Two's Complement</strong> operation.
</p>
{this.props.annotateTypes ? null : <p>To see full in-memory binary representation, go to <b>Settings</b> and enable <b>Annotate Data Types</b> toggle. </p>}
</IconWithToolTip>)
if (!this.originalValue.isTheSame(this.scalar.value))
buttons.push(<button title='Undo all changes' className='undo' data-control="undo" onClick={() => this.undo()}><FontAwesomeIcon icon={faUndo} /></button>);
return <React.Fragment>{buttons}</React.Fragment>
} }
getLabel(): string { getLabel(): string {
// For expressions like |~2 // For expressions like |~2
// TODO: find a better way... // TODO: find a better way...
@@ -177,23 +197,26 @@ getLabel(): string {
onBitClicked(args: BitClickedEventArg) { onBitClicked(args: BitClickedEventArg) {
const { bitIndex: index, binaryStringLength: totalLength } = args; const { bitIndex, binaryStringLength: binaryStringLength } = args;
const maxBitSize = this.scalar.value.maxBitSize; const maxBitSize = this.scalar.value.maxBitSize;
if(!args.isTypeExtend) const rightIndex = binaryStringLength - bitIndex;
{
const pad = this.scalar.value.maxBitSize - totalLength; if (rightIndex <= maxBitSize) {
const newValue = calc.flipBit(this.scalar.value, pad + index); const pad = this.scalar.value.maxBitSize - binaryStringLength;
const newValue = calc.flipBit(this.scalar.value, pad + bitIndex);
this.changeValue(newValue); this.changeValue(newValue);
return; return;
} }
const space = (totalLength - index - maxBitSize);
const space = (binaryStringLength - bitIndex - maxBitSize);
this.changeValue(calc.addSpace(this.scalar.value, space)); this.changeValue(calc.addSpace(this.scalar.value, space));
loglevel.debug("Operand size changed");
} }
onChangeSign () { onChangeSign() {
var op = this.props.expressionItem.getUnderlyingOperand(); var op = this.props.expressionItem.getUnderlyingOperand();
@@ -222,27 +245,18 @@ getLabel(): string {
const signedOther = op.value.signed ? 'unsigned' : 'signed'; const signedOther = op.value.signed ? 'unsigned' : 'signed';
const signedButtonTitle = `Click to change to ${signedOther} preserving the same bits`; const signedButtonTitle = `Click to change to ${signedOther} preserving the same bits`;
if(op.label.length > 0) if (op.label.length > 0) {
{
text += " (converted)"; text += " (converted)";
title += ". This number was converted to facilitate bitwise operation with an operand of a different type."; title += ". This number was converted to facilitate bitwise operation with an operand of a different type.";
} }
children.push(<span title={title} style={{cursor:"help"}}>{text.trim()}</span>); children.push(<span title={title} style={{ cursor: "help" }}>{text.trim()}</span>);
if(allowSignChange) if (allowSignChange)
children.push(<button className='accent1' title={signedButtonTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>); children.push(<button className='accent1 link-button' title={signedButtonTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>);
else else
children.push(<span className='accent1'>&nbsp;{signedStr}</span>) children.push(<span className='accent1'>&nbsp;{signedStr}</span>)
return <React.Fragment>{children}</React.Fragment> return <React.Fragment>{children}</React.Fragment>
} }
} }
function willInfoColumnBeVisible(expr: ExpressionElement, maxNumberOfBits: number, allowSignChange : boolean) {
const op = expr.getUnderlyingOperand();
const allBitsDisplayed = op.value.maxBitSize != 32 || op.value.maxBitSize <= maxNumberOfBits;
const hasLabel = op.label.length > 0;
return allBitsDisplayed || hasLabel;
}

View File

@@ -7,6 +7,7 @@ import exp from 'constants';
type Config = { type Config = {
emphasizeBytes: boolean; emphasizeBytes: boolean;
allowFlipBits: boolean; allowFlipBits: boolean;
annotateDataTypes: boolean;
} }
type ExpressionRowModel = { type ExpressionRowModel = {
@@ -24,9 +25,11 @@ export default class BitwiseResultViewModel {
items: ExpressionRowModel[]; items: ExpressionRowModel[];
maxNumberOfBits: number; maxNumberOfBits: number;
allowFlipBits: boolean; allowFlipBits: boolean;
annotateDataTypes: boolean;
constructor({ emphasizeBytes = false, allowFlipBits = false} : Config) { constructor({ emphasizeBytes = false, allowFlipBits = false, annotateDataTypes = false} : Config) {
this.emphasizeBytes = emphasizeBytes; this.emphasizeBytes = emphasizeBytes;
this.annotateDataTypes = annotateDataTypes;
this.items = []; this.items = [];
this.maxNumberOfBits = 0; this.maxNumberOfBits = 0;
this.allowFlipBits = allowFlipBits === true; this.allowFlipBits = allowFlipBits === true;
@@ -80,7 +83,7 @@ export default class BitwiseResultViewModel {
}; };
addScalarRow(expr: Operand) { addScalarRow(expr: Operand) {
const bits = calc.numberOfBitsDisplayed(expr.value); const bits = this.calcMaxNumberOfBits(expr);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
this.items.push({ this.items.push({
sign:'', sign:'',
@@ -95,7 +98,7 @@ export default class BitwiseResultViewModel {
addOperatorRow(expr: Operator) { addOperatorRow(expr: Operator) {
const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingOperand(); const resultNumber = expr.isNotExpression ? expr.evaluate() : expr.getUnderlyingOperand();
const bits = calc.numberOfBitsDisplayed(resultNumber.value); const bits = this.calcMaxNumberOfBits(resultNumber);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
@@ -110,7 +113,7 @@ export default class BitwiseResultViewModel {
}; };
addShiftExpressionResultRow(expr : Operator, resultExpr : Operand) { addShiftExpressionResultRow(expr : Operator, resultExpr : Operand) {
const bits = calc.numberOfBitsDisplayed(resultExpr.value); const bits = this.calcMaxNumberOfBits(resultExpr);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
const child = expr.operand.getUnderlyingOperand(); const child = expr.operand.getUnderlyingOperand();
this.items.push({ this.items.push({
@@ -124,8 +127,10 @@ export default class BitwiseResultViewModel {
}; };
addExpressionResultRow(expr : Operand) { addExpressionResultRow(expr : Operand) {
const bits = calc.numberOfBitsDisplayed(expr.value);
const bits = this.calcMaxNumberOfBits(expr);
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits); this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
this.items.push({ this.items.push({
sign:'=', sign:'=',
css: 'expression-result', css: 'expression-result',
@@ -136,6 +141,10 @@ export default class BitwiseResultViewModel {
}); });
}; };
calcMaxNumberOfBits (op: Operand) {
return this.annotateDataTypes ? op.value.maxBitSize : calc.numberOfBitsDisplayed(op.value);
}
getLabel (op: Operand) : 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,19 +164,21 @@ export default class BitwiseResultViewModel {
return bits; return bits;
}; };
static createModel(expr : Expression, emphasizeBytes: boolean) : BitwiseResultViewModel { static createModel(expr : Expression, emphasizeBytes: boolean, annotateDataTypes: boolean) : BitwiseResultViewModel {
if(expr instanceof ListOfNumbers) { if(expr instanceof ListOfNumbers) {
return BitwiseResultViewModel.buildListOfNumbers(expr, { return BitwiseResultViewModel.buildListOfNumbers(expr, {
emphasizeBytes: emphasizeBytes, emphasizeBytes: emphasizeBytes,
allowFlipBits: true allowFlipBits: true,
annotateDataTypes: annotateDataTypes
}); });
} }
if(expr instanceof BitwiseOperation) { if(expr instanceof BitwiseOperation) {
return BitwiseResultViewModel.buildBitwiseOperation(expr, { return BitwiseResultViewModel.buildBitwiseOperation(expr, {
emphasizeBytes: emphasizeBytes, emphasizeBytes: emphasizeBytes,
allowFlipBits: true allowFlipBits: true,
annotateDataTypes: annotateDataTypes
}); });
} }

View File

@@ -12,7 +12,7 @@ const expressionAppModule = {
canHandle: (input:string) => parser.canParse(input), canHandle: (input:string) => parser.canParse(input),
handle: function(c: CommandInput) { handle: function(c: CommandInput) {
var expr = parser.parse(c.input); var expr = parser.parse(c.input);
appState.addCommandResult(c.input, () => <BitwiseResultView expression={expr!} emphasizeBytes={appState.emphasizeBytes} annotateTypes={appState.annotateTypes} />); appState.addCommandResult(c.input, () => <BitwiseResultView expression={expr!} emphasizeBytes={appState.emphasizeBytes} annotateTypes={appState.annotateTypes} dimExtrBits={appState.dimExtraBits} />);
} }
}); });
} }

View File

@@ -74,7 +74,15 @@ a.hashLink { font-size: 1.1em;}
.hex .prefix { display: inline; } .hex .prefix { display: inline; }
.indicator { padding: 0px 5px; background: transparent; border: none; cursor: pointer; vertical-align: middle; margin-left: -3em; color:rgba(0, 0, 0, 0.25) } .indicator { padding: 0px 5px; background: transparent; border: none; cursor: pointer; vertical-align: middle; color:rgba(0, 0, 0, 0.25) }
.tooltip-holder { position: relative; display: inline}
.tooltip-holder .tooltip {display: none; font-size: 0.9em; position: absolute; margin-left: 10px; width: 300px; padding: 10px; border-radius: 5px;}
.tooltip-holder .tooltip p { margin-bottom: 0;}
.tooltip-holder .tooltip .tooltip-header { font-weight: bold; margin-bottom: 5px;}
.tooltip:hover { display: inline;}
.tooltip-holder:hover .tooltip { display: inline; }
.error { color: maroon; } .error { color: maroon; }
@@ -86,9 +94,10 @@ a.hashLink { font-size: 1.1em;}
.cur { color: lightgray; } .cur { color: lightgray; }
.extra-bit { opacity: 0.3;} .dim-extra-bits .extra-bit { opacity: 0.3;}
button { border: none; text-decoration: underline;} button { border: none; }
button.link-button {text-decoration: underline;}
.settings-button { .settings-button {
margin-left: -20px; margin-left: -20px;
@@ -97,10 +106,15 @@ button { border: none; text-decoration: underline;}
.undo button { .undo button {
opacity: 0.4; opacity: 0.4;
padding: 0; padding: 0;
margin-right: 5px;
} }
.solid-border { border: solid 1px rgba(255, 255, 255, 0.8);}
/* Light */ /* Light */
.light { background: #fafafa; } .light { background: #fafafa; }
.light .solid-background {background: #fafafa;}
.light .header-cmd { color: #919191 } .light .header-cmd { color: #919191 }
.light a, .light a:visited, .light button { color: #222; } .light a, .light a:visited, .light button { color: #222; }
.light .one { color: black; } .light .one { color: black; }
@@ -120,9 +134,12 @@ button { border: none; text-decoration: underline;}
.light .expressionInput { border-bottom: solid 1px rgba(0, 0, 0, 0.5);} .light .expressionInput { border-bottom: solid 1px rgba(0, 0, 0, 0.5);}
.light .button { border: solid 1px gray; } .light .button { border: solid 1px gray; }
.light .button:hover { background: rgba(0, 0, 0, 0.2);} .light .button:hover { background: rgba(0, 0, 0, 0.2);}
.light .solid-border { border: solid 1px gray;}
.light .donate-result-view .paypal-button { border: solid 1px gray; }
/* Dark */ /* Dark */
.dark { background: #121212; color: white;} .dark { background: #121212; color: white;}
.dark .solid-background {background: #121212;}
.dark .expression { color: white;} .dark .expression { color: white;}
.dark .expressionInput { color: white; } .dark .expressionInput { color: white; }
.dark a, .dark a:visited, .dark button { color: white; } .dark a, .dark a:visited, .dark button { color: white; }
@@ -144,6 +161,7 @@ button { border: none; text-decoration: underline;}
Midnight Theme Midnight Theme
*/ */
.midnight { background: #2c3e50; color: white } .midnight { background: #2c3e50; color: white }
.midnight .solid-background {background: #2c3e50;}
.midnight .header-cmd { color: #7ea3b5 !important } .midnight .header-cmd { color: #7ea3b5 !important }
.midnight .expression { color: white;} .midnight .expression { color: white;}
.midnight .expressionInput { color: white;} .midnight .expressionInput { color: white;}

View File

@@ -5,9 +5,8 @@ import { getNetworkAddress, getAddressSpaceSize } from '../subnet-utils';
import IpAddressBinaryString from './IpAddressBinaryString'; import IpAddressBinaryString from './IpAddressBinaryString';
import { IpAddress, IpAddressWithSubnetMask, VpcCommand } from '../models'; import { IpAddress, IpAddressWithSubnetMask, VpcCommand } from '../models';
import formatter from '../../core/formatter'; import formatter from '../../core/formatter';
import Toggle from '../../shell/components/Toggle'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; import IconWithToolTip from '../../shell/components/IconWithTooltip';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const MAX_NON_HOSTS_BITS = 30; // leave two bits for hosts min const MAX_NON_HOSTS_BITS = 30; // leave two bits for hosts min
@@ -41,17 +40,13 @@ function SubnetView(props: { vpc: VpcCommand }) {
<BinaryStringView binaryString={split.subnet} disableHighlight={true} className="address-space subnet-part" /> <BinaryStringView binaryString={split.subnet} disableHighlight={true} className="address-space subnet-part" />
<BinaryStringView binaryString={split.hosts} disableHighlight={true} className="address-space host-part" /> <BinaryStringView binaryString={split.hosts} disableHighlight={true} className="address-space host-part" />
<span className="address-space decimal-part">{networkAddress.toString()}</span> <span className="address-space decimal-part">{networkAddress.toString()}</span>
<Toggle text="[i]" isOn={vpc.showLegend} onClick={() => setVpc(vpc.toggleLegend())} title="Show/Hide Color Legend">
<FontAwesomeIcon className="icon" icon={faQuestionCircle} size="sm" /> <IconWithToolTip icon={faInfoCircle}>
</Toggle> <div className='accent1 tooltip-header'>Color Legend</div>
</div> <span className="address-space soft">000</span> - VPC address bits <br/>
<div style={{"display" : vpc.showLegend ? '' : 'none'}}> <span className="address-space subnet-part">000</span> - Bits dedicated for subnets address<br/>
<p> <span className="address-space host-part">000</span> - Bits dedicated to host addresses inside each subnet
Color Legend </IconWithToolTip>
</p>
<span className="address-space soft">000</span> - VPC address bits <br/>
<span className="address-space subnet-part">000</span> - Bits dedicated for subnets address<br/>
<span className="address-space host-part">000</span> - Bits dedicated to host addresses inside each subnet
</div> </div>
</div> </div>
@@ -130,13 +125,11 @@ class VpcModel {
cidr: IpAddressWithSubnetMask; cidr: IpAddressWithSubnetMask;
subnetBits: number; subnetBits: number;
subnetNum: number; subnetNum: number;
showLegend: boolean;
constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) { constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) {
this.cidr = cidr; this.cidr = cidr;
this.subnetBits = subnetBits; this.subnetBits = subnetBits;
this.subnetNum = 0; this.subnetNum = 0;
this.showLegend = false;
} }
static create(vpc: VpcCommand) { static create(vpc: VpcCommand) {
@@ -154,10 +147,4 @@ class VpcModel {
changeVpcCidr(newCidr: IpAddressWithSubnetMask) { changeVpcCidr(newCidr: IpAddressWithSubnetMask) {
return new VpcModel(newCidr, this.subnetBits); return new VpcModel(newCidr, this.subnetBits);
} }
toggleLegend() {
var n = new VpcModel(this.cidr, this.subnetBits);
n.showLegend = !this.showLegend;
return n;
}
} }

View File

@@ -9,7 +9,8 @@ export type PersistedAppData = {
debugMode: boolean | null; debugMode: boolean | null;
pageVisistsCount: number; pageVisistsCount: number;
donationClicked: boolean; donationClicked: boolean;
annotateTypes: boolean annotateTypes: boolean;
dimExtrBits: boolean;
} }
export type CommandResultView = { export type CommandResultView = {
@@ -37,23 +38,25 @@ export default class AppState {
donationClicked: boolean; donationClicked: boolean;
showSettings: boolean = false; showSettings: boolean = false;
annotateTypes: boolean = false; annotateTypes: boolean = false;
dimExtraBits: boolean = false;
constructor(persistData : PersistedAppData, env: string) { constructor(persistData: PersistedAppData, env: string) {
this.env = env; this.env = env;
this.emphasizeBytes = !!persistData.emphasizeBytes; 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;
this.pageVisitsCount = persistData.pageVisistsCount || 0; this.pageVisitsCount = persistData.pageVisistsCount || 0;
this.donationClicked = persistData.donationClicked; this.donationClicked = persistData.donationClicked;
this.annotateTypes = !!persistData.annotateTypes; this.annotateTypes = !!persistData.annotateTypes;
this.dimExtraBits = !!persistData.dimExtrBits;
} }
addCommandResult(input : string, view : ViewFactory) { addCommandResult(input: string, view: ViewFactory) {
const key = generateKey(); const key = generateKey();
this.commandResults.unshift({key, input, view}); this.commandResults.unshift({ key, input, view });
log.debug(`command result added: ${input}`); log.debug(`command result added: ${input}`);
this.triggerChanged(); this.triggerChanged();
} }
@@ -64,19 +67,19 @@ export default class AppState {
} }
removeResult(index: number) { removeResult(index: number) {
if(index < 0 || index >= this.commandResults.length) if (index < 0 || index >= this.commandResults.length)
return; return;
this.commandResults.splice(index, 1); this.commandResults.splice(index, 1);
this.triggerChanged(); this.triggerChanged();
} }
toggleEmphasizeBytes() { toggleEmphasizeBytes(value?: boolean) {
this.emphasizeBytes = !this.emphasizeBytes; this.emphasizeBytes = value != null ? value : !this.emphasizeBytes;
this.triggerChanged(); this.triggerChanged();
} }
onChange(handler : AppStateChangeHandler) { onChange(handler: AppStateChangeHandler) {
this.changeHandlers.push(handler); this.changeHandlers.push(handler);
} }
@@ -85,8 +88,8 @@ export default class AppState {
} }
setUiTheme(theme: string) { setUiTheme(theme: string) {
this.uiTheme = theme; this.uiTheme = theme;
this.triggerChanged(); this.triggerChanged();
} }
toggleDebugMode() { toggleDebugMode() {
@@ -99,8 +102,13 @@ export default class AppState {
this.triggerChanged(); this.triggerChanged();
} }
toggleAnnotateTypes() { toggleAnnotateTypes(value?: boolean) {
this.annotateTypes = !this.annotateTypes; this.annotateTypes = value != null ? value : !this.annotateTypes;
this.triggerChanged();
}
toggleDimExtrBits() {
this.dimExtraBits = !this.dimExtraBits;
this.triggerChanged(); this.triggerChanged();
} }
@@ -109,15 +117,15 @@ export default class AppState {
this.triggerChanged(); this.triggerChanged();
} }
onDonationClicked() : boolean{ onDonationClicked(): boolean {
if(this.donationClicked === true) return false; if (this.donationClicked === true) return false;
this.donationClicked = true; this.donationClicked = true;
this.triggerChanged(); this.triggerChanged();
return true; return true;
} }
getPersistData() : PersistedAppData { getPersistData(): PersistedAppData {
return { return {
emphasizeBytes: this.emphasizeBytes, emphasizeBytes: this.emphasizeBytes,
uiTheme: this.uiTheme, uiTheme: this.uiTheme,
@@ -125,11 +133,12 @@ export default class AppState {
debugMode: this.debugMode, debugMode: this.debugMode,
pageVisistsCount: this.pageVisitsCount, pageVisistsCount: this.pageVisitsCount,
donationClicked: this.donationClicked, donationClicked: this.donationClicked,
annotateTypes: this.annotateTypes annotateTypes: this.annotateTypes,
dimExtrBits: this.dimExtraBits
} }
} }
}; };
function generateKey() : number { function generateKey(): number {
return Math.ceil(Math.random()*10000000) ^ Date.now(); // Because why the hell not... return Math.ceil(Math.random() * 10000000) ^ Date.now(); // Because why the hell not...
} }

View File

@@ -9,7 +9,8 @@ const DEFAULT_DATA : PersistedAppData = {
debugMode: false, debugMode: false,
pageVisistsCount: 0, pageVisistsCount: 0,
donationClicked: false, donationClicked: false,
annotateTypes: false annotateTypes: false,
dimExtrBits: true
} }
export default { export default {

View File

@@ -7,7 +7,6 @@ import log from 'loglevel';
import DebugIndicators from './DebugIndicators'; import DebugIndicators from './DebugIndicators';
import hash from '../../core/hash'; import hash from '../../core/hash';
import TopLinks from './TopLinks'; import TopLinks from './TopLinks';
import Toggle from './Toggle';
import SettingsPane from './SettingsPane'; import SettingsPane from './SettingsPane';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faGear } from '@fortawesome/free-solid-svg-icons'; import { faGear } from '@fortawesome/free-solid-svg-icons';
@@ -63,7 +62,7 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
<div className="expressionInput-container"> <div className="expressionInput-container">
<InputBox onCommandEntered={(input) => cmd.execute(input)} /> <InputBox onCommandEntered={(input) => cmd.execute(input)} />
<button className={settingsCss}><FontAwesomeIcon icon={faGear} onClick={() => this.props.appState.toggleShowSettings()} /></button> <button className={settingsCss} title='Toggle Settings'><FontAwesomeIcon icon={faGear} onClick={() => this.props.appState.toggleShowSettings()} /></button>
</div> </div>
{this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null} {this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null}

View File

@@ -2,4 +2,3 @@
.donate-result-view .qrcode-container { background: white; display: inline-block; padding: 10px} .donate-result-view .qrcode-container { background: white; display: inline-block; padding: 10px}
.donate-result-view .section { margin-top: 20px; } .donate-result-view .section { margin-top: 20px; }
.light .donate-result-view .paypal-button { border: solid 1px gray; }

View File

@@ -0,0 +1,18 @@
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export type IconWithToolTipProps = {
icon: IconProp
}
function IconWithToolTip (props: React.PropsWithChildren<IconWithToolTipProps>) {
return <div className='tooltip-holder'>
<button><FontAwesomeIcon icon={props.icon} /></button>
<div className='tooltip solid-border solid-background'>
{props.children}
</div>
</div>;
}
export default IconWithToolTip;

View File

@@ -11,11 +11,20 @@
margin-right: 3px; margin-right: 3px;
} }
.settings-container .inner {
padding: 15px 20px;
border-radius: 5px;
}
.settings-container h3 {
margin-top: 0;
}
.settings-container .description { .settings-container .description {
font-size: 0.85em; font-size: 0.85em;
padding: 0; padding: 0;
margin: 0; margin: 0;
padding-left: 30px; padding-left: 40px;
opacity: 0.8; opacity: 0.8;
} }

View File

@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import './SettingsPane.css'; import './SettingsPane.css';
import { faToggleOff, faToggleOn } from '@fortawesome/free-solid-svg-icons'; import { faGear, faToggleOff, faToggleOn } from '@fortawesome/free-solid-svg-icons';
import AppState from '../AppState'; import AppState from '../AppState';
type SettingsPaneProps = { type SettingsPaneProps = {
@@ -12,29 +12,38 @@ function SettingsPane(props : SettingsPaneProps) {
const {appState} = props; const {appState} = props;
return <div id="settings" className='settings-container'> return <div id="settings" className='settings-container'>
<div className="inner"> <div className="inner solid-border">
<h3>Settings</h3> <h3>Settings</h3>
<div className='setting'> <div className='setting'>
<button onClick={() => appState.toggleEmphasizeBytes()}> <button onClick={() => appState.toggleEmphasizeBytes()}>
<FontAwesomeIcon icon={appState.emphasizeBytes ? faToggleOn : faToggleOff} /> Emphasize Bytes <FontAwesomeIcon size='xl' icon={appState.emphasizeBytes ? faToggleOn : faToggleOff} /> Emphasize Bytes
</button> </button>
<p className='description'> <p className='description'>
{appState.emphasizeBytes {appState.emphasizeBytes
? "Each binary string is extended to contain at least 8 bits. A white space is added between each group of 8 bits so it easy to tell bytes apart." ? "Binary strings are padded with extra bits to have a length that is multiple of 8."
: "Binary strings are not modified."} : "Binary strings are not padded with extra bits."}
</p>
</div>
<div className='setting'>
<button onClick={() => appState.toggleDimExtrBits()}>
<FontAwesomeIcon size='xl' icon={appState.dimExtraBits ? faToggleOn : faToggleOff} /> Dim Extra Bits
</button>
<p className='description'>
{appState.dimExtraBits
? "Extra bits used for padding are now dimmed."
: "No bits are dimmed."}
</p> </p>
</div> </div>
<div className='setting'> <div className='setting'>
<button onClick={() => appState.toggleAnnotateTypes()}> <button onClick={() => appState.toggleAnnotateTypes()}>
<FontAwesomeIcon icon={appState.annotateTypes ? faToggleOn : faToggleOff} /> Annotate Data Types <FontAwesomeIcon size='xl' icon={appState.annotateTypes ? faToggleOn : faToggleOff} /> Annotate Data Types
</button> </button>
<p className='description'> <p className='description'>
{appState.annotateTypes {appState.annotateTypes
? "BitwiseCmd shows the integer size and indicates whether the data type is signed or unsigned." ? "Integers are displayed as they are stored in memory. Bit size is shown."
: "Information about the size of integers used in the calculation is hidden."} : "Information about the size of integers is hidden."}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
} }

View File

@@ -9,7 +9,7 @@ function TopLinks() {
return <ul className="top-links"> return <ul className="top-links">
<li> <li>
<button onClick={onDonate}> <button onClick={onDonate} className='link-button'>
<FontAwesomeIcon className='icon' icon={faDonate} size="lg" /><span className='link-text'>donate</span> <FontAwesomeIcon className='icon' icon={faDonate} size="lg" /><span className='link-text'>donate</span>
</button> </button>
</li> </li>

View File

@@ -32,6 +32,10 @@ const shellModule = {
appState.toggleDebugMode(); appState.toggleDebugMode();
appState.addCommandResult(c.input, () => <TextResultView text={`Debug Mode: ${appState.debugMode}`}/>); appState.addCommandResult(c.input, () => <TextResultView text={`Debug Mode: ${appState.debugMode}`}/>);
}); });
cmd.command('-annotate:on', (c:CommandInput) => appState.toggleAnnotateTypes(true))
cmd.command('-annotate:off', (c:CommandInput) => appState.toggleAnnotateTypes(false))
cmd.command('-em:off', (c:CommandInput) => appState.toggleEmphasizeBytes(false))
cmd.command('-em:on', (c:CommandInput) => appState.toggleEmphasizeBytes(true))
cmd.command("-max", (c:CommandInput) => { cmd.command("-max", (c:CommandInput) => {
const text = `Int32 ${INT32_MAX_VALUE}\nInt64 ${INT64_MAX_VALUE}` const text = `Int32 ${INT32_MAX_VALUE}\nInt64 ${INT64_MAX_VALUE}`
appState.addCommandResult(c.input, () => <TextResultView text={text} />) appState.addCommandResult(c.input, () => <TextResultView text={text} />)