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) {
return formatter.numberToString(this, base || 10);
return formatter.numberToString(this, base || 10, this.maxBitSize);
}
num() {

View File

@@ -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");
});

View File

@@ -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;
},
}
};

View File

@@ -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) => {

View File

@@ -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', () => {

View File

@@ -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');
},

View File

@@ -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'>&nbsp;{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;
}

View File

@@ -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
});
}

View File

@@ -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} />);
}
});
}

View File

@@ -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;}

View File

@@ -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;
}
}

View File

@@ -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...
}

View File

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

View File

@@ -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}

View File

@@ -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; }

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;
}
.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;
}

View File

@@ -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>
}

View File

@@ -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>

View File

@@ -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} />)