mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2025-12-21 20:22:48 +01:00
Do not pad negative numbers (#50)
This commit is contained in:
@@ -98,7 +98,7 @@ export class Integer {
|
||||
}
|
||||
|
||||
toString(base?:number) {
|
||||
return formatter.numberToString(this, base || 10);
|
||||
return formatter.numberToString(this, base || 10, this.maxBitSize);
|
||||
}
|
||||
|
||||
num() {
|
||||
|
||||
@@ -74,14 +74,20 @@ describe('calc.addSpace', () => {
|
||||
describe('calc.numberOfBitsDisplayed', () => {
|
||||
it('calculates number of bits', () => {
|
||||
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(3)).toBe(2);
|
||||
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', () => {
|
||||
|
||||
it("respects bit size", () => {
|
||||
@@ -167,10 +173,12 @@ describe("calc misc", () => {
|
||||
|
||||
it('promoteTo64Bit', () => {
|
||||
const n = asInteger(-1);
|
||||
expect(calc.toBinaryString(calc.promoteTo64Bit(n))).toBe("11111111111111111111111111111111");
|
||||
expect(calc.toBinaryString(calc.promoteTo64Bit(n))).toBe("1");
|
||||
});
|
||||
|
||||
it('binaryRepresentation', () => {
|
||||
|
||||
expect(calc.toBinaryString(Integer.int(-2147483647))).toBe("0000000000000000000000000000001");
|
||||
expect(calc.toBinaryString(asInteger(2147483647))).toBe("1111111111111111111111111111111");
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ export default {
|
||||
},
|
||||
|
||||
addSpace(number: Integer, requiredSpace: number) : Integer {
|
||||
|
||||
if(requiredSpace < 0)
|
||||
throw new Error("Required space cannot be negative");
|
||||
|
||||
const totalSpaceRequired = number.maxBitSize + requiredSpace;
|
||||
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}`)
|
||||
|
||||
const r = num.value < 0
|
||||
? this.engine.applyTwosComplement(bin.padStart(bitSize, '0'))
|
||||
? this.engine.applyTwosComplement(bin)
|
||||
: bin;
|
||||
|
||||
return r;
|
||||
return bin.length != bitSize ? r.substring(r.length-bin.length) : r;
|
||||
},
|
||||
|
||||
lshift (num: Integer, numBytes : JsNumber) : Integer {
|
||||
@@ -105,7 +109,7 @@ export default {
|
||||
|
||||
_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);
|
||||
|
||||
@@ -127,8 +131,8 @@ export default {
|
||||
|
||||
const [num1, num2] = equalizeSize(op1, op2);
|
||||
|
||||
let bin1 = this.toBinaryString(num1).padStart(num1.maxBitSize, '0');
|
||||
let bin2 = this.toBinaryString(num2).padStart(num2.maxBitSize, '0');
|
||||
let bin1 = this.toBinaryString(num1).padStart(num1.maxBitSize, num1.value < 0 ? '1' : '0');
|
||||
let bin2 = this.toBinaryString(num2).padStart(num2.maxBitSize, num2.value < 0 ? '1' : '0');
|
||||
|
||||
let resultBin = operation(bin1, bin2);
|
||||
|
||||
@@ -225,7 +229,9 @@ export default {
|
||||
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;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export type BinaryStringViewProps = {
|
||||
className?: string;
|
||||
disableHighlight?: boolean,
|
||||
signBitIndex?: number,
|
||||
integerBitSize?: number
|
||||
valueBitSize?: number
|
||||
};
|
||||
|
||||
export type FlipBitEventArg = {
|
||||
@@ -17,7 +17,7 @@ export type FlipBitEventArg = {
|
||||
binaryStringLength: number;
|
||||
$event: any;
|
||||
newBinaryString: any;
|
||||
isTypeExtend: boolean
|
||||
isExtraBit: boolean
|
||||
};
|
||||
|
||||
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,
|
||||
newBinaryString: newBinaryString,
|
||||
$event: e,
|
||||
isTypeExtend: isExtra
|
||||
isExtraBit: isExtra
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ export default class BinaryStringView extends React.Component<BinaryStringViewPr
|
||||
const css = allowFlipBits ? ' flipable' : ''
|
||||
|
||||
const disableHighlight = this.props.disableHighlight || false;
|
||||
const firstBitIndex = this.props.integerBitSize != null
|
||||
? bitChars.length - this.props.integerBitSize
|
||||
const firstBitIndex = this.props.valueBitSize != null
|
||||
? bitChars.length - this.props.valueBitSize
|
||||
: -1;
|
||||
|
||||
return bitChars.map((c, i) => {
|
||||
|
||||
@@ -12,8 +12,10 @@ describe("formatter", () => {
|
||||
const minusOne = BigInt(-1);
|
||||
const n32 = new Integer(minusOne, 32);
|
||||
const n64 = new Integer(minusOne, 64);
|
||||
expect(formatter.bin(n32)).toBe("11111111111111111111111111111111");
|
||||
expect(formatter.bin(n64)).toBe("1111111111111111111111111111111111111111111111111111111111111111");
|
||||
expect(formatter.bin(n32)).toBe("1");
|
||||
expect(formatter.bin(n64)).toBe("1");
|
||||
expect(formatter.fullBin(n32)).toBe("11111111111111111111111111111111");
|
||||
expect(formatter.fullBin(n64)).toBe("1111111111111111111111111111111111111111111111111111111111111111");
|
||||
});
|
||||
|
||||
it('formats large binary number correctly', () => {
|
||||
@@ -25,9 +27,10 @@ describe("formatter", () => {
|
||||
});
|
||||
|
||||
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(-2147483647, 'bin')).toBe("10000000000000000000000000000001");
|
||||
expect(formatter.numberToString(-2147483647, 'bin')).toBe("0000000000000000000000000000001");
|
||||
});
|
||||
|
||||
it('pads left', () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Integer, JsNumber, asInteger } from "./Integer";
|
||||
export type NumberBase = 'dec' | 'hex' | 'bin';
|
||||
|
||||
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);
|
||||
base = typeof base == "string" ? getBase(base) : base;
|
||||
@@ -13,7 +13,13 @@ const formatter = {
|
||||
var hexVal = calc.abs(num).value.toString(16);
|
||||
return num.value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
|
||||
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:
|
||||
return num.value.toString(10);
|
||||
default:
|
||||
@@ -36,6 +42,9 @@ const formatter = {
|
||||
bin(number: Integer | JsNumber) {
|
||||
return this.numberToString(number, 'bin');
|
||||
},
|
||||
fullBin(number: Integer) {
|
||||
return this.numberToString(number, 'bin', number.maxBitSize);
|
||||
},
|
||||
emBin(number: Integer | JsNumber) {
|
||||
return this.padLeft(this.bin(number), 8, '0');
|
||||
},
|
||||
|
||||
@@ -7,13 +7,15 @@ import { Operator, Operand, ListOfNumbers } from '../expression';
|
||||
import calc from '../../core/calc';
|
||||
import { Integer } from '../../core/Integer';
|
||||
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 IconWithToolTip from '../../shell/components/IconWithTooltip';
|
||||
|
||||
type BitwiseResultViewProps = {
|
||||
expression: Expression;
|
||||
emphasizeBytes: boolean;
|
||||
annotateTypes: boolean
|
||||
annotateTypes: boolean,
|
||||
dimExtrBits: boolean
|
||||
}
|
||||
|
||||
type BitwiseResultViewState = {
|
||||
@@ -31,31 +33,37 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
|
||||
render() {
|
||||
|
||||
let model : BitwiseResultViewModel | null = null
|
||||
let model: BitwiseResultViewModel | null = null
|
||||
const allowSignChange = this.props.expression instanceof ListOfNumbers;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
|
||||
try {
|
||||
model = BitwiseResultViewModel.createModel(this.props.expression, this.props.emphasizeBytes, this.props.annotateTypes);
|
||||
}
|
||||
catch(err) {
|
||||
catch (err) {
|
||||
const text = (err as any).message;
|
||||
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";
|
||||
|
||||
if(this.props.dimExtrBits)
|
||||
css += " dim-extra-bits";
|
||||
|
||||
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) =>
|
||||
<ExpressionElementTableRow
|
||||
@@ -68,7 +76,7 @@ export default class BitwiseResultView extends React.Component<BitwiseResultView
|
||||
expressionItem={itm.expressionElement}
|
||||
emphasizeBytes={this.props.emphasizeBytes}
|
||||
maxNumberOfBits={this.maxSeenLengthNumberOfBits}
|
||||
showInfoColumn={this.props.annotateTypes}
|
||||
annotateTypes={this.props.annotateTypes}
|
||||
onValueChanged={() => this.onValueChanged()} />);
|
||||
}
|
||||
|
||||
@@ -88,8 +96,8 @@ type ExpressionElementRowProps = {
|
||||
allowSignChange: boolean,
|
||||
expressionItem: ExpressionElement,
|
||||
onValueChanged: any,
|
||||
showInfoColumn: boolean,
|
||||
}
|
||||
annotateTypes: boolean
|
||||
};
|
||||
|
||||
class ExpressionElementTableRow extends React.Component<ExpressionElementRowProps> {
|
||||
|
||||
@@ -105,11 +113,11 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
|
||||
}
|
||||
|
||||
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 padChar = scalar.value.value >= 0 ? '0' : '1';
|
||||
const bin = formatter.numberToString(scalar.value, 'bin').padStart(maxNumberOfBits, padChar);
|
||||
const bin = formatter.numberToString(scalar.value, 'bin', maxNumberOfBits);
|
||||
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}>
|
||||
<td className="sign">{sign}</td>
|
||||
@@ -122,25 +130,37 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
|
||||
binaryString={bin}
|
||||
allowFlipBits={allowFlipBits}
|
||||
signBitIndex={signBitIndex}
|
||||
integerBitSize={this.scalar.value.maxBitSize}
|
||||
valueBitSize={valueSize}
|
||||
onBitClicked={args => this.onBitClicked(args)} />
|
||||
</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'>
|
||||
{this.getUndoButton()}
|
||||
{this.getControlButtons()}
|
||||
</td>
|
||||
</tr>;
|
||||
}
|
||||
|
||||
getUndoButton(): React.ReactNode {
|
||||
getControlButtons(): React.ReactNode {
|
||||
|
||||
return !this.originalValue.isTheSame(this.scalar.value)
|
||||
? <button title='Undo all changes' className='undo' data-control="undo" onClick={() => this.undo()}><FontAwesomeIcon icon={faUndo}/></button>
|
||||
: null;
|
||||
const buttons = [];
|
||||
|
||||
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
|
||||
// TODO: find a better way...
|
||||
@@ -177,23 +197,26 @@ getLabel(): string {
|
||||
|
||||
onBitClicked(args: BitClickedEventArg) {
|
||||
|
||||
const { bitIndex: index, binaryStringLength: totalLength } = args;
|
||||
const { bitIndex, binaryStringLength: binaryStringLength } = args;
|
||||
|
||||
const maxBitSize = this.scalar.value.maxBitSize;
|
||||
|
||||
if(!args.isTypeExtend)
|
||||
{
|
||||
const pad = this.scalar.value.maxBitSize - totalLength;
|
||||
const newValue = calc.flipBit(this.scalar.value, pad + index);
|
||||
const rightIndex = binaryStringLength - bitIndex;
|
||||
|
||||
if (rightIndex <= maxBitSize) {
|
||||
const pad = this.scalar.value.maxBitSize - binaryStringLength;
|
||||
const newValue = calc.flipBit(this.scalar.value, pad + bitIndex);
|
||||
this.changeValue(newValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const space = (totalLength - index - maxBitSize);
|
||||
|
||||
const space = (binaryStringLength - bitIndex - maxBitSize);
|
||||
this.changeValue(calc.addSpace(this.scalar.value, space));
|
||||
loglevel.debug("Operand size changed");
|
||||
}
|
||||
|
||||
onChangeSign () {
|
||||
onChangeSign() {
|
||||
|
||||
var op = this.props.expressionItem.getUnderlyingOperand();
|
||||
|
||||
@@ -222,27 +245,18 @@ getLabel(): string {
|
||||
const signedOther = op.value.signed ? 'unsigned' : 'signed';
|
||||
const signedButtonTitle = `Click to change to ${signedOther} preserving the same bits`;
|
||||
|
||||
if(op.label.length > 0)
|
||||
{
|
||||
if (op.label.length > 0) {
|
||||
text += " (converted)";
|
||||
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)
|
||||
children.push(<button className='accent1' title={signedButtonTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>);
|
||||
if (allowSignChange)
|
||||
children.push(<button className='accent1 link-button' title={signedButtonTitle} onClick={() => this.onChangeSign()}>{signedStr}</button>);
|
||||
else
|
||||
children.push(<span className='accent1'> {signedStr}</span>)
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import exp from 'constants';
|
||||
type Config = {
|
||||
emphasizeBytes: boolean;
|
||||
allowFlipBits: boolean;
|
||||
annotateDataTypes: boolean;
|
||||
}
|
||||
|
||||
type ExpressionRowModel = {
|
||||
@@ -24,9 +25,11 @@ export default class BitwiseResultViewModel {
|
||||
items: ExpressionRowModel[];
|
||||
maxNumberOfBits: number;
|
||||
allowFlipBits: boolean;
|
||||
annotateDataTypes: boolean;
|
||||
|
||||
constructor({ emphasizeBytes = false, allowFlipBits = false} : Config) {
|
||||
constructor({ emphasizeBytes = false, allowFlipBits = false, annotateDataTypes = false} : Config) {
|
||||
this.emphasizeBytes = emphasizeBytes;
|
||||
this.annotateDataTypes = annotateDataTypes;
|
||||
this.items = [];
|
||||
this.maxNumberOfBits = 0;
|
||||
this.allowFlipBits = allowFlipBits === true;
|
||||
@@ -80,7 +83,7 @@ export default class BitwiseResultViewModel {
|
||||
};
|
||||
|
||||
addScalarRow(expr: Operand) {
|
||||
const bits = calc.numberOfBitsDisplayed(expr.value);
|
||||
const bits = this.calcMaxNumberOfBits(expr);
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
this.items.push({
|
||||
sign:'',
|
||||
@@ -95,7 +98,7 @@ export default class BitwiseResultViewModel {
|
||||
addOperatorRow(expr: Operator) {
|
||||
|
||||
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);
|
||||
|
||||
@@ -110,7 +113,7 @@ export default class BitwiseResultViewModel {
|
||||
};
|
||||
|
||||
addShiftExpressionResultRow(expr : Operator, resultExpr : Operand) {
|
||||
const bits = calc.numberOfBitsDisplayed(resultExpr.value);
|
||||
const bits = this.calcMaxNumberOfBits(resultExpr);
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
const child = expr.operand.getUnderlyingOperand();
|
||||
this.items.push({
|
||||
@@ -124,8 +127,10 @@ export default class BitwiseResultViewModel {
|
||||
};
|
||||
|
||||
addExpressionResultRow(expr : Operand) {
|
||||
const bits = calc.numberOfBitsDisplayed(expr.value);
|
||||
|
||||
const bits = this.calcMaxNumberOfBits(expr);
|
||||
this.maxNumberOfBits = Math.max(bits, this.maxNumberOfBits);
|
||||
|
||||
this.items.push({
|
||||
sign:'=',
|
||||
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 {
|
||||
|
||||
return formatter.numberToString(op.value, op.base === 'bin' ? 'dec' : op.base)
|
||||
@@ -155,19 +164,21 @@ export default class BitwiseResultViewModel {
|
||||
return bits;
|
||||
};
|
||||
|
||||
static createModel(expr : Expression, emphasizeBytes: boolean) : BitwiseResultViewModel {
|
||||
static createModel(expr : Expression, emphasizeBytes: boolean, annotateDataTypes: boolean) : BitwiseResultViewModel {
|
||||
|
||||
if(expr instanceof ListOfNumbers) {
|
||||
return BitwiseResultViewModel.buildListOfNumbers(expr, {
|
||||
emphasizeBytes: emphasizeBytes,
|
||||
allowFlipBits: true
|
||||
allowFlipBits: true,
|
||||
annotateDataTypes: annotateDataTypes
|
||||
});
|
||||
}
|
||||
|
||||
if(expr instanceof BitwiseOperation) {
|
||||
return BitwiseResultViewModel.buildBitwiseOperation(expr, {
|
||||
emphasizeBytes: emphasizeBytes,
|
||||
allowFlipBits: true
|
||||
allowFlipBits: true,
|
||||
annotateDataTypes: annotateDataTypes
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const expressionAppModule = {
|
||||
canHandle: (input:string) => parser.canParse(input),
|
||||
handle: function(c: CommandInput) {
|
||||
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} />);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,7 +74,15 @@ a.hashLink { font-size: 1.1em;}
|
||||
|
||||
.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; }
|
||||
|
||||
@@ -86,9 +94,10 @@ a.hashLink { font-size: 1.1em;}
|
||||
|
||||
.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 {
|
||||
margin-left: -20px;
|
||||
@@ -97,10 +106,15 @@ button { border: none; text-decoration: underline;}
|
||||
.undo button {
|
||||
opacity: 0.4;
|
||||
padding: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.solid-border { border: solid 1px rgba(255, 255, 255, 0.8);}
|
||||
|
||||
|
||||
/* Light */
|
||||
.light { background: #fafafa; }
|
||||
.light .solid-background {background: #fafafa;}
|
||||
.light .header-cmd { color: #919191 }
|
||||
.light a, .light a:visited, .light button { color: #222; }
|
||||
.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 .button { border: solid 1px gray; }
|
||||
.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 { background: #121212; color: white;}
|
||||
.dark .solid-background {background: #121212;}
|
||||
.dark .expression { color: white;}
|
||||
.dark .expressionInput { color: white; }
|
||||
.dark a, .dark a:visited, .dark button { color: white; }
|
||||
@@ -144,6 +161,7 @@ button { border: none; text-decoration: underline;}
|
||||
Midnight Theme
|
||||
*/
|
||||
.midnight { background: #2c3e50; color: white }
|
||||
.midnight .solid-background {background: #2c3e50;}
|
||||
.midnight .header-cmd { color: #7ea3b5 !important }
|
||||
.midnight .expression { color: white;}
|
||||
.midnight .expressionInput { color: white;}
|
||||
|
||||
@@ -5,9 +5,8 @@ import { getNetworkAddress, getAddressSpaceSize } from '../subnet-utils';
|
||||
import IpAddressBinaryString from './IpAddressBinaryString';
|
||||
import { IpAddress, IpAddressWithSubnetMask, VpcCommand } from '../models';
|
||||
import formatter from '../../core/formatter';
|
||||
import Toggle from '../../shell/components/Toggle';
|
||||
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import IconWithToolTip from '../../shell/components/IconWithTooltip';
|
||||
|
||||
|
||||
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.hosts} disableHighlight={true} className="address-space host-part" />
|
||||
<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" />
|
||||
</Toggle>
|
||||
</div>
|
||||
<div style={{"display" : vpc.showLegend ? '' : 'none'}}>
|
||||
<p>
|
||||
Color Legend
|
||||
</p>
|
||||
|
||||
<IconWithToolTip icon={faInfoCircle}>
|
||||
<div className='accent1 tooltip-header'>Color Legend</div>
|
||||
<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
|
||||
</IconWithToolTip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,13 +125,11 @@ class VpcModel {
|
||||
cidr: IpAddressWithSubnetMask;
|
||||
subnetBits: number;
|
||||
subnetNum: number;
|
||||
showLegend: boolean;
|
||||
|
||||
constructor(cidr: IpAddressWithSubnetMask, subnetBits: number) {
|
||||
this.cidr = cidr;
|
||||
this.subnetBits = subnetBits;
|
||||
this.subnetNum = 0;
|
||||
this.showLegend = false;
|
||||
}
|
||||
|
||||
static create(vpc: VpcCommand) {
|
||||
@@ -154,10 +147,4 @@ class VpcModel {
|
||||
changeVpcCidr(newCidr: IpAddressWithSubnetMask) {
|
||||
return new VpcModel(newCidr, this.subnetBits);
|
||||
}
|
||||
|
||||
toggleLegend() {
|
||||
var n = new VpcModel(this.cidr, this.subnetBits);
|
||||
n.showLegend = !this.showLegend;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ export type PersistedAppData = {
|
||||
debugMode: boolean | null;
|
||||
pageVisistsCount: number;
|
||||
donationClicked: boolean;
|
||||
annotateTypes: boolean
|
||||
annotateTypes: boolean;
|
||||
dimExtrBits: boolean;
|
||||
}
|
||||
|
||||
export type CommandResultView = {
|
||||
@@ -37,8 +38,9 @@ export default class AppState {
|
||||
donationClicked: boolean;
|
||||
showSettings: boolean = false;
|
||||
annotateTypes: boolean = false;
|
||||
dimExtraBits: boolean = false;
|
||||
|
||||
constructor(persistData : PersistedAppData, env: string) {
|
||||
constructor(persistData: PersistedAppData, env: string) {
|
||||
|
||||
this.env = env;
|
||||
|
||||
@@ -49,11 +51,12 @@ export default class AppState {
|
||||
this.pageVisitsCount = persistData.pageVisistsCount || 0;
|
||||
this.donationClicked = persistData.donationClicked;
|
||||
this.annotateTypes = !!persistData.annotateTypes;
|
||||
this.dimExtraBits = !!persistData.dimExtrBits;
|
||||
}
|
||||
|
||||
addCommandResult(input : string, view : ViewFactory) {
|
||||
addCommandResult(input: string, view: ViewFactory) {
|
||||
const key = generateKey();
|
||||
this.commandResults.unshift({key, input, view});
|
||||
this.commandResults.unshift({ key, input, view });
|
||||
log.debug(`command result added: ${input}`);
|
||||
this.triggerChanged();
|
||||
}
|
||||
@@ -64,19 +67,19 @@ export default class AppState {
|
||||
}
|
||||
|
||||
removeResult(index: number) {
|
||||
if(index < 0 || index >= this.commandResults.length)
|
||||
if (index < 0 || index >= this.commandResults.length)
|
||||
return;
|
||||
|
||||
this.commandResults.splice(index, 1);
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleEmphasizeBytes() {
|
||||
this.emphasizeBytes = !this.emphasizeBytes;
|
||||
toggleEmphasizeBytes(value?: boolean) {
|
||||
this.emphasizeBytes = value != null ? value : !this.emphasizeBytes;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
onChange(handler : AppStateChangeHandler) {
|
||||
onChange(handler: AppStateChangeHandler) {
|
||||
this.changeHandlers.push(handler);
|
||||
}
|
||||
|
||||
@@ -99,8 +102,13 @@ export default class AppState {
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleAnnotateTypes() {
|
||||
this.annotateTypes = !this.annotateTypes;
|
||||
toggleAnnotateTypes(value?: boolean) {
|
||||
this.annotateTypes = value != null ? value : !this.annotateTypes;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
toggleDimExtrBits() {
|
||||
this.dimExtraBits = !this.dimExtraBits;
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
@@ -109,15 +117,15 @@ export default class AppState {
|
||||
this.triggerChanged();
|
||||
}
|
||||
|
||||
onDonationClicked() : boolean{
|
||||
if(this.donationClicked === true) return false;
|
||||
onDonationClicked(): boolean {
|
||||
if (this.donationClicked === true) return false;
|
||||
|
||||
this.donationClicked = true;
|
||||
this.triggerChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
getPersistData() : PersistedAppData {
|
||||
getPersistData(): PersistedAppData {
|
||||
return {
|
||||
emphasizeBytes: this.emphasizeBytes,
|
||||
uiTheme: this.uiTheme,
|
||||
@@ -125,11 +133,12 @@ export default class AppState {
|
||||
debugMode: this.debugMode,
|
||||
pageVisistsCount: this.pageVisitsCount,
|
||||
donationClicked: this.donationClicked,
|
||||
annotateTypes: this.annotateTypes
|
||||
annotateTypes: this.annotateTypes,
|
||||
dimExtrBits: this.dimExtraBits
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function generateKey() : number {
|
||||
return Math.ceil(Math.random()*10000000) ^ Date.now(); // Because why the hell not...
|
||||
function generateKey(): number {
|
||||
return Math.ceil(Math.random() * 10000000) ^ Date.now(); // Because why the hell not...
|
||||
}
|
||||
@@ -9,7 +9,8 @@ const DEFAULT_DATA : PersistedAppData = {
|
||||
debugMode: false,
|
||||
pageVisistsCount: 0,
|
||||
donationClicked: false,
|
||||
annotateTypes: false
|
||||
annotateTypes: false,
|
||||
dimExtrBits: true
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -7,7 +7,6 @@ import log from 'loglevel';
|
||||
import DebugIndicators from './DebugIndicators';
|
||||
import hash from '../../core/hash';
|
||||
import TopLinks from './TopLinks';
|
||||
import Toggle from './Toggle';
|
||||
import SettingsPane from './SettingsPane';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
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">
|
||||
<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>
|
||||
{this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null}
|
||||
|
||||
@@ -2,4 +2,3 @@
|
||||
.donate-result-view .qrcode-container { background: white; display: inline-block; padding: 10px}
|
||||
.donate-result-view .section { margin-top: 20px; }
|
||||
|
||||
.light .donate-result-view .paypal-button { border: solid 1px gray; }
|
||||
18
src/shell/components/IconWithTooltip.tsx
Normal file
18
src/shell/components/IconWithTooltip.tsx
Normal 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;
|
||||
@@ -11,11 +11,20 @@
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.settings-container .inner {
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.settings-container h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.settings-container .description {
|
||||
font-size: 0.85em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-left: 30px;
|
||||
padding-left: 40px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
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';
|
||||
|
||||
type SettingsPaneProps = {
|
||||
@@ -12,29 +12,38 @@ function SettingsPane(props : SettingsPaneProps) {
|
||||
const {appState} = props;
|
||||
|
||||
return <div id="settings" className='settings-container'>
|
||||
<div className="inner">
|
||||
<div className="inner solid-border">
|
||||
<h3>Settings</h3>
|
||||
<div className='setting'>
|
||||
<button onClick={() => appState.toggleEmphasizeBytes()}>
|
||||
<FontAwesomeIcon icon={appState.emphasizeBytes ? faToggleOn : faToggleOff} /> Emphasize Bytes
|
||||
<FontAwesomeIcon size='xl' icon={appState.emphasizeBytes ? faToggleOn : faToggleOff} /> Emphasize Bytes
|
||||
</button>
|
||||
<p className='description'>
|
||||
{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 not modified."}
|
||||
? "Binary strings are padded with extra bits to have a length that is multiple of 8."
|
||||
: "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>
|
||||
</div>
|
||||
<div className='setting'>
|
||||
<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>
|
||||
<p className='description'>
|
||||
{appState.annotateTypes
|
||||
? "BitwiseCmd shows the integer size and indicates whether the data type is signed or unsigned."
|
||||
: "Information about the size of integers used in the calculation is hidden."}
|
||||
? "Integers are displayed as they are stored in memory. Bit size is shown."
|
||||
: "Information about the size of integers is hidden."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ function TopLinks() {
|
||||
|
||||
return <ul className="top-links">
|
||||
<li>
|
||||
<button onClick={onDonate}>
|
||||
<button onClick={onDonate} className='link-button'>
|
||||
<FontAwesomeIcon className='icon' icon={faDonate} size="lg" /><span className='link-text'>donate</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -32,6 +32,10 @@ const shellModule = {
|
||||
appState.toggleDebugMode();
|
||||
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) => {
|
||||
const text = `Int32 ${INT32_MAX_VALUE}\nInt64 ${INT64_MAX_VALUE}`
|
||||
appState.addCommandResult(c.input, () => <TextResultView text={text} />)
|
||||
|
||||
Reference in New Issue
Block a user