Migrate to typescript (#14)

* Move BitwiseCmd to typescript

* Add serve-build http command

* Add Env type

* Add CNAME and sitemap.xml files

* Add files via upload
This commit is contained in:
Borys Levytskyi
2021-01-12 16:41:03 +02:00
committed by GitHub
parent 83f49d258a
commit 271c58a980
91 changed files with 15026 additions and 1919 deletions

30
.gitignore vendored
View File

@@ -1,9 +1,23 @@
.idea/
node_modules
build
BitwiseCmdPages/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
bundle.js
bundle.js.map
npm-debug.log
react/
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -1,4 +1,44 @@
# BitwiseCmd
[Bitwise Calculator Online](http://bitwisecmd.com/)
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
Web App that helps better understand how bitwise operations are performed by displaying bytes in a way you can actually see what is going on there during AND, OR, XOR or shift operations.
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

View File

@@ -1,18 +0,0 @@
# Build Process
* Install node.js (reboot required)
* Install grunt `npm install -g grunt`
* Install grunt cli `npm install -g grunt-cli`
* In project dir - install all modules `npm install`
# End To End Tests
* Install java (reboot required)
* Install protractor `npm install -g protractor`
* Update webdriver-manager `webdriver-manager update --standalone`
# Run End To End Tests
* Start webdirver server `webdriver-manager update`
* Run tests: `e2e.bat` or just `e2e`

View File

@@ -1,40 +0,0 @@
module.exports = function (config) {
config.set({
browsers: [ 'Chrome' ],
// karma only needs to know about the test bundle
files: [
'./tests/unit/**/*.js'
],
frameworks: [ 'jasmine' ],
plugins: [
'karma-chrome-launcher',
'karma-jasmine',
'karma-webpack',
],
// run the bundle through the webpack and sourcemap plugins
preprocessors: {
'./tests/unit/**/*.js': [ 'webpack']
},
reporters: [ 'dots' ],
singleRun: true,
// webpack config object
webpack: {
module: {
loaders: [
{
exclude: /node_modules/,
loader: 'babel-loader',
test: /\.jsx?$/,
query: {
presets: [require.resolve('babel-preset-es2015'), require.resolve('babel-preset-react')],
plugins: [require.resolve('babel-plugin-transform-class-properties')]
},
}
],
}
},
webpackMiddleware: {
noInfo: true,
}
});
};

13312
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +1,48 @@
{
"name": "BitwiseCmd",
"version": "1.0.0",
"description": "Bitwise Operations Console",
"main": "index.js",
"scripts": {
"build": "webpack -p && rm -rf ./BitwiseCmdPages/* && cp ./src/index.html ./BitwiseCmdPages && cp ./src/*.js ./BitwiseCmdPages && cp ./src/bundle.js.map ./BitwiseCmdPages && cp -r ./src/css ./BitwiseCmdPages && cp -r ./src/img ./BitwiseCmdPages && cp -r ./src/css ./BitwiseCmdPages",
"stage": "npm run build && cp -r ./BitwiseCmdPages/* ../BitwiseCmdPages",
"stage:react": "npm run build && cp -r ./BitwiseCmdPages/* ../BitwiseCmdPages/react",
"serve": "webpack-dev-server --content-base ./src",
"e2e": "protractor ./tests/e2e.chrome.js --params.appUrl='http://localhost:8080/#clear'",
"e2e:stage": "protractor ./tests/e2e.chrome.js --params.appUrl='http://localhost:3000/#clear'",
"e2e:prod:old": "protractor ./tests/e2e.chrome.js --params.appUrl='http://bitwisecmd.com/old/#clear||-notrack'",
"e2e:prod": "protractor ./tests/e2e.chrome.js --params.appUrl='http://bitwisecmd.com//#clear||-notrack'",
"test": "karma start"
},
"repository": {
"type": "git",
"url": "https://github.com/BorysLevytskyi/BitwiseCmd.git"
},
"author": "Borys Levytskyi",
"license": "MIT",
"bugs": {
"url": "https://github.com/BorysLevytskyi/BitwiseCmd/issues"
},
"homepage": "https://github.com/BorysLevytskyi/BitwiseCmd",
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.19.0",
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"grunt-contrib-clean": "latest",
"grunt-contrib-copy": "latest",
"grunt-contrib-cssmin": "latest",
"grunt-contrib-uglify": "latest",
"grunt-processhtml": "latest",
"jasmine": "latest",
"karma": "latest",
"karma-jasmine": "latest",
"karma-webpack": "latest",
"source-map-loader": "^0.1.5",
"ts-loader": "^1.0.0"
},
"name": "bitwisecmd",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/react": "^0.14.44",
"@types/react-dom": "^0.14.18",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.8",
"babel-plugin-transform-class-properties": "^6.19.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"body-parser": "^1.15.2",
"loglevel": "^1.4.1",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"uuid": "^3.0.1"
"@types/enzyme": "^3.9.4",
"@types/jest": "^24.0.15",
"@types/node": "^12.0.10",
"@types/react": "^16.8.22",
"@types/react-dom": "^16.8.4",
"@types/uuid": "^3.4.4",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"react-test-renderer": "^16.8.6",
"typescript": "^3.5.2",
"uuid": "^3.3.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"serve-build": "npx http-server ./build -p 3030"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/enzyme-adapter-react-16": "^1.0.5",
"http-server": "^0.12.3",
"wdio-jasmine-framework": "^0.3.8"
}
}

View File

View File

@@ -1,14 +1,15 @@
(function() {
var disableAnalytics = localStorage.getItem('trackAnalytics') === 'false' || window.location.hash.indexOf('-notrack') > -1
var key = 'TrackAnalytics';
var disableAnalytics = localStorage.getItem(key) === 'false' || window.location.hash.indexOf('-notrack') > -1
if(disableAnalytics) {
localStorage.setItem("trackAnalytics", "false");
localStorage.setItem(key, "false");
console.log('Analytics tracking disabled.');
return;
}
if(window.location.host != 'bitwisecmd.com') {
if(window.location.host !== 'bitwisecmd.com') {
console.log("Analytics not tracked. Non-prod host")
return;
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

31
public/index.html Normal file
View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>BitwiseCmd</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" style="height: 100%"></div>
<script type="text/javascript" src="analytics.js"></script>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div class="social-container">
<div class="fb-like" data-href="http://bitwisecmd.com" data-layout="box_count" data-action="like" data-show-faces="false" data-share="true"></div>
</div>
</body>
</html>

15
public/manifest.json Normal file
View File

@@ -0,0 +1,15 @@
{
"short_name": "BitwiseCmd",
"name": "BitwiseCmd",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

13
public/sitemap.xml Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>http://bitwisecmd.com/</loc>
<lastmod>2015-04-08T11:00:53+00:00</lastmod>
<changefreq>weekly</changefreq>
</url>
</urlset>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

View File

@@ -1,34 +1,53 @@
import React from 'react';
import InputBox from './InputBox';
import DisplayResultView from './DisplayResultView';
import InputBox from './components/InputBox';
import DisplayResultView from './components/DisplayResultView';
import AppState from './core/AppState';
import cmd from './core/cmd';
import CommandResult from './models/CommandResult';
import log from 'loglevel';
import Indicators from './components/Indicators';
type AppRootProps = {
appState: AppState,
};
type AppRootState = {
uiTheme: string,
emphasizeBytes: boolean,
commandResults: CommandResult[]
}
export default class AppRoot extends React.Component<AppRootProps, AppRootState> {
export default class AppRoot extends React.Component {
componentWillMount() {
this.refresh();
this.props.appState.onChange(() => this.refresh());
}
refresh() {
this.setState(this.props.appState);
}
getIndicator(value) {
return value === true ? 'on' : 'off';
getIndicator(value : boolean) {
return value ? 'on' : 'off';
}
getResultViews() {
getResultViews() : JSX.Element[] {
log.debug('getting result views')
var results = this.state.commandResults.map((r, i) => <DisplayResultView key={i} content={r} input={r.input} inputHash={r.inputHash} appState={this.props.appState} />);
return results;
}
toggleEmphasizeBytes() {
console.log(this.props.appState);
this.props.appState.toggleEmphasizeBytes();
}
render() {
return <div className={`app-root ${this.state.uiTheme}`}>
<Indicators appState={this.props.appState} />
<div className="header">
<h1>Bitwise<span className="header-cmd">Cmd</span></h1>
<h1>Bitwise<span className="header-cmd">Cmd</span>
</h1>
<ul className="top-links">
<li>
<a href="https://github.com/BorisLevitskiy/BitwiseCmd"><i className="icon github">&nbsp;</i><span className="link-text">Project on GitHub</span></a>
@@ -43,7 +62,7 @@ export default class AppRoot extends React.Component {
</div>
<div className="expressionInput-container">
<InputBox />
<InputBox onCommandEntered={(input) => cmd.execute(input)} />
<span className="configPnl">
<span id="emphasizeBytes" data-cmd="em" className={"indicator " + this.getIndicator(this.state.emphasizeBytes)} title="Toggle Emphasize Bytes" onClick={e => this.toggleEmphasizeBytes()}>[em]</span>

View File

@@ -1,51 +0,0 @@
export default class AppState {
constructor(persistData) {
this.emphasizeBytes = persistData.emphasizeBytes || true;
this.commandResults = [];
this.handlers = [];
this.uiTheme = persistData.uiTheme || 'midnight';
this.debugMode = false;
this.version = 4;
this.persistedVersion = persistData.version || 0.1;
this.wasOldVersion = persistData.version && this.version > this.persistedVersion;
}
addCommandResult(result) {
this.commandResults.unshift(result);
this.triggerChanged();
}
clearCommandResults() {
this.commandResults = [];
this.triggerChanged();
}
toggleEmphasizeBytes() {
this.emphasizeBytes = !this.emphasizeBytes;
this.triggerChanged();
}
onChange(handler) {
this.handlers.push(handler);
}
triggerChanged() {
for(var h of this.handlers) {
h();
}
}
setUiTheme(theme) {
this.uiTheme = theme;
this.triggerChanged();
}
getPersistData() {
return {
emphasizeBytes: this.emphasizeBytes,
uiTheme: this.uiTheme,
version: this.version
}
}
};

View File

@@ -1,26 +0,0 @@
import should from './should';
export default calc = {
numberOfBits: function (num) {
if(num < 0) {
return 32;
}
should.bePositiveInteger(num);
return Math.floor(Math.log(num) / Math.log(2)) + 1;
},
maxNumberOfBits: function (arr) {
var counts = [], num;
for (var i = 0; i < arr.length; i++) {
num = arr[i];
counts.push(this.numberOfBits(num));
}
return Math.max.apply(null, counts);
},
calcExpression: function (expr) {
return eval(expr.expressionString);
}
};

View File

@@ -1,97 +0,0 @@
import is from './is';
var handlers = [];
var config = {
errorHandler: (input, err) => logError(err)
}
var cmd = {
debugMode: false,
execute: function(rawInput) {
var input = rawInput.trim().toLowerCase();
var handler = findHandler(input);
if(handler != null) {
if(this.debugMode) {
invokeHandler(input, handler);
} else {
try {
invokeHandler(input, handler);
} catch (e) {
config.errorHandler(input, e);
}
}
}
else {
logError(input, new Error("Unsupported expression: " + input.trim()));
}
},
commands: function(catalog) {
for(var key in catalog) {
if(catalog.hasOwnProperty(key)) {
this.command(key, catalog[key]);
}
}
},
command: function(cmd, handler) {
var h = createHandler(cmd, handler);
if(h == null){
console.warn('unexpected set of arguments: ', Array.prototype.splice.call(arguments));
return;
}
if(!is.aFunction(h.canHandle)) {
console.warn('handler is missing "canHandle" function. registration denied.');
return;
}
if(!is.aFunction(h.handle)) {
console.warn('handler is missing "handle" function. registration denied.');
return;
}
handlers.push(h);
},
clear: function() {
console.log('clear');
},
onError: function(handler) {
config.errorHandler = handler;
}
};
function logError(err) {
console.error(err)
}
function invokeHandler (input, handler) {
var cmdResult = handler.handle({ input: input});
if(cmdResult != null) {
console.log(cmdResult);
}
}
function createHandler (cmd, handler) {
if(is.plainObject(cmd)) {
return cmd;
}
if(is.string(cmd)) {
return { canHandle: function (input) { return input === cmd; }, handle: handler };
}
return null;
}
function findHandler (input) {
var i= 0;
for(i;i<handlers.length; i++) {
if(handlers[i].canHandle(input)) {
return handlers[i];
}
}
};
export default cmd;

View File

@@ -1,67 +0,0 @@
import HelpResult from './models/HelpResult';
import AboutResult from './models/AboutResult';
import UnknownCommandResult from './models/UnknownCommandResult';
import ExpressionResult from './models/ExpressionResult';
import ErrorResult from './models/ErrorResult';
import WahtsnewResult from './models/WhatsnewResult';
import StringResult from './models/StringResult';
import * as expression from './expression';
import uuid from 'uuid/v4';
var cmdConfig = {};
export default {
initialize (cmd, appState) {
cmd.commands({
'help': function(c) {
appState.addCommandResult(new HelpResult(c.input));
},
'clear': function() {
appState.clearCommandResults();
},
'em': function() {
appState.toggleEmphasizeBytes();
},
'dark': function() {
appState.setUiTheme('dark');
},
'light': function () {
appState.setUiTheme('light');
},
'midnight': function() {
appState.setUiTheme('midnight');
},
'about': function(c) {
appState.addCommandResult(new AboutResult(c.input));
},
'whatsnew': function(c) {
appState.addCommandResult(new WahtsnewResult(c.input));
},
'guid': function(c) {
appState.addCommandResult(new StringResult(c.input, uuid()))
},
'-notrack': function () {},
'-debug': function() {
console.log('Debug mode on')
cmd.debugMode = true;
}
});
cmd.command({
canHandle: (input) => expression.parser.canParse(input),
handle: function(c) {
var expr = expression.parser.parse(c.input);
appState.addCommandResult(new ExpressionResult(c.input, expr));
}
})
// Last command handler reports that input is unknown
cmd.command({
canHandle: () => true,
handle: (c) => appState.addCommandResult(new UnknownCommandResult(c.input))
});
cmd.onError((input, err) => appState.addCommandResult(new ErrorResult(input, err)));
}
}

View File

@@ -1,8 +0,0 @@
import React from 'react';
import cmd from '../../cmd';
export default class CommandLink extends React.Component {
render() {
return <a href="javascript:void(0)" onClick={e => cmd.execute(this.props.command || this.props.text)}>{this.props.text}</a>
}
}

View File

@@ -1,112 +0,0 @@
import React from 'react';
import { Operand, ListOfNumbersExpression, ExpressionOperand, MultipleOperandsExpression } from '../../expression';
import formatter from '../../formatter';
import BinaryStringView from './BinaryStringView';
import BitwiseExpressionViewModel from './models/BitwiseExpressionViewModel';
import log from 'loglevel';
export default class BitwiseOperationEpxressionView extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
var rows = this.getRows();
if(!rows) {
return null;
}
return <table className="expression">
<tbody>
{rows}
</tbody>
</table>
}
getRows() {
const expr = this.props.expression;
var model = null;
if(expr instanceof ListOfNumbersExpression) {
model = BitwiseExpressionViewModel.buildListOfNumbers(expr, {
emphasizeBytes: this.props.emphasizeBytes,
allowFlipBits: true
});
}
if(expr instanceof MultipleOperandsExpression) {
model = BitwiseExpressionViewModel.buildMultiple(expr, {
emphasizeBytes: this.props.emphasizeBytes,
allowFlipBits: false
});
}
log.info('Render model', model);
return model.items.map((itm, i) =>
<ExpressionRow
key={i}
{...itm}
emphasizeBytes={this.props.emphasizeBytes}
maxNumberOfBits={model.maxNumberOfBits}
onBitFlipped={() => this.onBitFlipped()} />);
}
onBitFlipped() {
console.log('bit flipped');
this.setState({d:new Date()});
}
}
class ExpressionRow extends React.Component {
constructor() {
super();
this.state = { operand: null };
}
render() {
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits, operand } = this.props;
return <tr className={css}>
<td className="sign">{sign}</td>
<td className="label">{this.getLabel(operand)}</td>
<td className="bin">
<BinaryStringView
emphasizeBytes={emphasizeBytes}
binaryString={formatter.padLeft(operand.apply().toBinaryString(), maxNumberOfBits, '0')}
allowFlipBits={allowFlipBits}
onFlipBit={idx => this.flipBit(idx)}/>
</td>
<td className="other">{this.getOther(operand)}</td>
</tr>;;
}
getLabel(op) {
if(op.isExpression) {
return op.toString();
}
return op.toString(op.kind == 'bin' ? 'dec' : op.kind);
}
getOther(op) {
if(op.isExpression) {
return op.apply().toString();
}
return op.toString(op.getOtherKind());
}
flipBit(args) {
const op = this.props.operand;
const { index, binaryString } = args;
var arr = binaryString.split('');
arr[index] = arr[index] == '0' ? '1' : '0';
var bin = arr.join('');
var newValue = parseInt(bin, 2);
console.log('flipped \n%s to \n%s from \n%s to \n%s', binaryString, bin, op.value, newValue);
op.setValue(newValue);
this.props.onBitFlipped();
}
}

View File

@@ -1,130 +0,0 @@
import { Operand, ExpressionOperand } from '../../../expression';
export default class BitwiseExpressionViewModel {
constructor({ emphasizeBytes = false, allowFlipBits = false } = {}) {
this.emphasizeBytes = emphasizeBytes;
this.items = [];
this.maxNumberOfBits = 0;
this.allowFlipBits = allowFlipBits === true;
}
static buildListOfNumbers(expr, config) {
var model = new BitwiseExpressionViewModel(config);
expr.numbers.forEach(op => model.addOperand(op));
model.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(model.maxNumberOfBits, model.emphasizeBytes);
return model;
}
static buildMultiple (expr, config) {
// console.log('build: ', expr);
var op = expr.expressions[0],
i = 0, l = expr.expressions.length,
ex, m = new BitwiseExpressionViewModel(config);
var cur = null;
for (;i<l;i++) {
var ex = expr.expressions[i];
if(ex instanceof Operand) {
m.addOperand(ex);
cur = ex;
// console.log('cur is ', cur)
continue;
}
// If it a single NOT expression
if(ex.isNotExpression) {
m.addExpression(ex);
var notResult = ex.apply();
m.addExpressionResult(notResult);
cur = notResult;
}
else if(ex.isShiftExpression){
// console.log('cur is ', cur)
cur = ex.apply(cur);
m.addShiftExpressionResult(ex, cur);
} else {
cur = ex.apply(cur);
m.addExpression(ex);
m.addExpressionResult(cur);
}
}
m.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(m.maxNumberOfBits, m.emphasizeBytes);
return m;
};
static buildNot (expression, config) {
var m = new BitwiseExpressionViewModel(config);
m.addExpression(expression);
m.addExpressionResult(expression.apply());
m.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(m.maxNumberOfBits, m.emphasizeBytes);
return m;
};
addOperand(operand) {
this.maxNumberOfBits = Math.max(operand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign:'',
css: '',
operand: operand,
allowFlipBits: this.allowFlipBits
});
};
addExpression(expression) {
this.maxNumberOfBits = Math.max(expression.operand.apply().getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign: expression.sign,
label: this.getLabel(expression.operand),
operand: expression.operand,
allowFlipBits: this.allowFlipBits
});
};
addShiftExpressionResult(expression, resultOperand) {
this.maxNumberOfBits = Math.max(resultOperand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign: expression.sign + expression.operand.toString(),
css: 'expression-result',
operand: resultOperand,
allowFlipBits: false
});
};
addExpressionResult(operand) {
this.maxNumberOfBits = Math.max(operand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign:'=',
css: 'expression-result',
operand: operand,
allowFlipBits: false
});
};
getLabel (op) {
if(op.kind == 'bin') {
return op.dec;
}
return op.toString();
}
// TODO: move this method elsewhere. It is also used in LisOfNumbersExpressionView.js
static getNumberOfBits = function (bits, emphasizeBytes) {
if(emphasizeBytes && bits % 8 != 0) {
if(bits < 8) {
return 8;
}
var n = bits - (bits % 8);
return n + 8;
}
return bits;
};
}

View File

@@ -1,132 +0,0 @@
import Operand from './expression/Operand';
import ExpressionOperand from './expression/ExpressionOperand'
import ListOfNumbersExpression from './expression/ListOfNumbersExpression';
import MultipleOperandsExpression from './expression/MultipleOperandsExpression';
export { default as Operand } from './expression/Operand';
export { default as ExpressionError } from './expression/ExpressionError';
export { default as ExpressionOperand } from './expression/ExpressionOperand';
export { default as ListOfNumbersExpression } from './expression/ListOfNumbersExpression';
export { default as MultipleOperandsExpression } from './expression/MultipleOperandsExpression';
var expression = {
factories:[],
canParse: function(string) {
var trimmed = string.replace(/^\s+|\s+$/, '');
var i = this.factories.length-1;
for(;i>=0;i--) {
if(this.factories[i].canCreate(trimmed) === true){
return true;
}
}
return false;
},
parse: function(string) {
var trimmed = string.replace(/^\s+|\s+$/, '');
var i = 0, l = this.factories.length, factory;
for(;i<l;i++) {
factory = this.factories[i];
if(factory.canCreate(trimmed) == true){
return factory.create(trimmed);
}
}
return null;
},
parseOperand: function(input) {
return Operand.parse(input);
},
createOperand: function(number, kind) {
return Operand.create(number, kind);
},
addFactory: function(factory) {
this.factories.push(factory);
}
};
// List of numbers
expression.addFactory({
regex: /^(-?(?:\d+|0x[\d,a-f]+|0b[0-1])\s?)+$/,
canCreate: function(string) {
return this.regex.test(string);
},
create: function (string) {
var matches = this.regex.exec(string),
numbers = [],
input = matches.input;
input.split(' ').forEach(function(n){
if(n.trim().length > 0) {
numbers.push(Operand.parse(n.trim()));
}
});
return new ListOfNumbersExpression(input, numbers);
}
});
// Multiple operands expression
expression.addFactory({
fullRegex: /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,a-f,0-9]+)))+$/,
regex: /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,a-f,0-9]+))/g,
canCreate: function(string) {
this.fullRegex.lastIndex = 0;
return this.fullRegex.test(this.normalizeString(string));
},
create: function (string) {
var m, operands = [],
normalizedString = this.normalizeString(string);
while ((m = this.regex.exec(normalizedString)) != null) {
operands.push(this.parseMatch(m));
}
return new MultipleOperandsExpression(normalizedString, operands)
},
parseMatch: function (m) {
// console.log('match');
// console.log(m);
var input = m[0],
sign = m[1],
num = m[2];
var op = null;
if(num.indexOf('~') == '0') {
op = new ExpressionOperand(input, Operand.parse(num.substring(1)), '~');
}
else {
op = Operand.parse(num);
}
if(sign == null) {
return op;
} else {
return new ExpressionOperand(input, op, sign);
}
},
normalizeString: function (string) {
return string.replace(/\s+/g,'');
}
});
export var parser = expression;
export class Parser {
constructor(input, pos) {
this.input = input;
this.pos = pos || 0;
this.buffer = [];
}
parse() {
console.log(this.input.length);
while(this.pos<this.input.length) {
this.buffer.push(this.input[this.pos]);
this.pos++;
}
console.log('exit');
}
}

View File

@@ -1,5 +0,0 @@
export default class ExpressionError extends Error {
constructor(message) {
super(message);
}
}

View File

@@ -1,39 +0,0 @@
import Operand from './Operand';
export default class ExpressionOperand {
constructor(expressionString, operand, sign) {
this.expressionString = expressionString;
this.operand = operand;
this.sign = sign;
this.isExpression = true;
this.isShiftExpression = this.sign.indexOf('<') >= 0 || this.sign.indexOf('>')>= 0;
this.isNotExpression = this.sign == '~';
}
apply(operand) {
if (operand instanceof ExpressionOperand) {
console.error("value shouldnt be expression", value);
throw new Error('value shouldnt be expression');
}
// console.log('operand', operand);
var str = '';
if(this.sign == '~'){
str = '~' + this.operand.apply().value;
} else {
str = operand.value + this.sign + this.operand.apply().value;
}
// console.log('eval:' + str, this);
const resultValue = eval(str);
var resultOp = Operand.create(eval(str), this.operand.kind || this.operand.operand.kind);
// console.log(resultValue, resultOp);
return resultOp;
};
toString() {
return this.sign + this.operand.toString();
}
}

View File

@@ -1,11 +0,0 @@
export default class ListOfNumbersExpression {
constructor(expressionString, numbers) {
this.expressionString = expressionString;
this.numbers = numbers;
this.maxBitsLegnth = numbers.map(n => n.lengthInBits).reduce((n , c) => n >= c ? n : c, 0);
}
toString() {
return this.numbers.map(n => n.value.toString()).join(' ');
}
}

View File

@@ -1,6 +0,0 @@
export default class MultipleOperandsExpression {
constructor(expressionString, expressions) {
this.expressionString = expressionString;
this.expressions = expressions;
}
}

View File

@@ -1,111 +0,0 @@
import numberParser from './numberParser';
import ExpressionError from './ExpressionError';
var id = 1;
// Represents numeric value
export default class Operand {
constructor(cfg) {
this.id = id++;
this.value = cfg.value;
this.kind = cfg.kind;
this.lengthInBits = Operand.getBitLength(this.value);
this.isExpression = false;
}
getLengthInBits() {
if(this.value < 0) {
return 32;
}
return Math.floor(Math.log(this.value) / Math.log(2)) + 1;
};
getOtherKind(kind) {
switch(kind || this.kind) {
case 'dec':
case 'bin':
return 'hex';
case 'hex': return 'dec';
default : throw new Error(kind + " kind doesn't have opposite kind")
}
};
toString(kind) {
return Operand.toKindString(this.value, kind || this.kind);
}
toOtherKindString() {
return this.toString(this.getOtherKind());
}
toDecimalString() {
return this.toString('dec');
}
toHexString() {
return this.toString('hex');
}
toBinaryString() {
return this.toString('bin');
}
setValue(value) {
console.log('Operand:%s.setValue: %s', this.id, this.value);
this.value = value;
}
apply() {
return this;
}
static getBitLength(num) {
return Math.floor(Math.log(num) / Math.log(2)) + 1;
}
static getBase(kind){
switch (kind){
case 'bin': return 2;
case 'hex': return 16;
case 'dec': return 10;
}
};
static create(value, kind) {
return new Operand({
value: value,
kind: kind,
input: Operand.toKindString(value, kind),
});
};
static parse(input) {
var parsed = numberParser.parse(input);
if(!parsed) {
throw new ExpressionError(input + " is not a valid number");
}
return new Operand(parsed);
}
static toKindString(value, kind) {
switch(kind) {
case 'hex':
var hexVal = Math.abs(value).toString(16);
return value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
case 'bin':
return (value>>>0).toString(2);
case 'dec':
return value.toString(10);
default:
throw new Error("Unexpected kind: " + kind)
}
};
static toHexString (hex) {
return hex.indexOf('-') == 0 ? '-0x' + hex.substr(1) : '0x' + hex;
};
}

View File

@@ -1,41 +0,0 @@
var decimalRegex = /^-?\d+$/;
var hexRegex = /^-?0x[0-9,a-f]+$/i;
var binRegex = /^-?0b[0-1]+$/i;
var operatorRegex = /^<<|>>|<<<|\&|\|\^|~$/;
var parsers = [
{ regex: decimalRegex, radix: 10, kind: 'dec', prefix: '^$' },
{ regex: hexRegex, radix: 16, kind: 'hex', prefix:/0x/i },
{ regex: binRegex, radix: 2, kind: 'bin', prefix:/0b/i }];
function applyParser(parser, rawInput) {
if(!parser.regex.test(rawInput)) {
return null;
}
var value = parseInt(rawInput.replace(parser.prefix, ''), parser.radix);
return {
value: value,
kind: parser.kind,
input: rawInput
}
}
var parser = {
parse: function(input) {
return parsers.map(p => applyParser(p, input)).reduce((c, n) => c || n);
},
parseOperator: function(input) {
var m = input.match(input);
if(m.length == 0) {
return null;
}
return m[0];
}
}
export default parser;

View File

@@ -1,26 +0,0 @@
export default {
formatString: function(num, kind) {
return num.toString(getBase(kind || "bin"));
},
padLeft: function (str, length, symbol) {
var sb = Array.prototype.slice.call(str), symbol = symbol || "0";
if(length == null) {
return str;
}
while(length > sb.length) {
sb.unshift(symbol);
}
return sb.join('');
}
};
function getBase(kind) {
switch (kind){
case 'bin': return 2;
case 'hex': return 16;
case 'dec': return 10;
}
}

View File

@@ -1,35 +0,0 @@
export default {
encodeHash: function(string) {
return encodeURI(string.trim().replace(/\s/g,','));
},
decodeHash: function(hashValue) {
return decodeURI(hashValue).replace(/^\#/, '').replace(/,/g,' ');
},
getArgs: function (hashValue) {
var decodedHash = this.decodeHash(hashValue),
args = { commands: [] };
splitHashList(decodedHash).forEach(function(value) {
args.commands.push(value);
});
return Object.freeze(args);
}
};
function splitHashList(str) {
var values = [];
if(str.indexOf('||')) {
str.split('||').forEach(function (v) {
if (v.length > 0) {
values.push(v);
}
});
} else {
values.push(str);
}
return values;
}

View File

@@ -1,33 +0,0 @@
export default {
plainObject: function(obj) {
return typeof obj == "object" && obj instanceof Object;
},
aFunction: function (obj) {
return typeof obj == "function";
},
string: function (obj) {
return typeof obj == "string";
},
regex: function (obj) {
return typeof obj == "object" && this.constructedFrom(RegExp);
},
constructedFrom: function (obj, ctor) {
return obj instanceof ctor;
},
htmlElement: function(obj) {
return obj instanceof HtmlElement;
},
array: function(obj) {
return obj instanceof Array;
},
number: function(num) {
return typeof num == "number" && !isNaN(num)
}
}

View File

@@ -1,10 +0,0 @@
export default class CommandResult {
constructor(input) {
this.input = input;
this.inputHash = this.encodeHash(input);
}
encodeHash (string) {
return encodeURI(string.trim().replace(/\s/g,','));
}
}

52
src/commands.ts Normal file
View File

@@ -0,0 +1,52 @@
import HelpResult from './models/HelpResult';
import AboutResult from './models/AboutResult';
import UnknownCommandResult from './models/UnknownCommandResult';
import ExpressionResult from './models/ExpressionResult';
import ErrorResult from './models/ErrorResult';
import WahtsnewResult from './models/WhatsnewResult';
import StringResult from './models/StringResult';
import * as expression from './expression/expression';
import uuid from 'uuid/v4';
import { CommandInput, CmdShell } from './core/cmd';
import { ExpressionInput } from './expression/expression-interfaces';
import AppState from './core/AppState';
export default {
initialize (cmd: CmdShell, appState: AppState) {
cmd.debugMode = appState.debugMode;
appState.onChange(() => cmd.debugMode = appState.debugMode);
cmd.command("help", (c: CommandInput) => appState.addCommandResult(new HelpResult(c.input)));
cmd.command("clear", (c: CommandInput) => appState.clearCommandResults());
cmd.command("em", (c: CommandInput) => appState.toggleEmphasizeBytes());
cmd.command("dark", (c: CommandInput) => appState.setUiTheme('dark'));
cmd.command("light", (c: CommandInput) => appState.setUiTheme('light'));
cmd.command("midnight", (c: CommandInput) => appState.setUiTheme('midnight'));
cmd.command("about", (c: CommandInput) => appState.addCommandResult(new AboutResult(c.input)));
cmd.command("whatsnew", (c: CommandInput) => appState.addCommandResult(new WahtsnewResult(c.input)));
cmd.command("guid", (c: CommandInput) => appState.addCommandResult(new StringResult(c.input, uuid())));
cmd.command("-notrack", (c: CommandInput) => {});
cmd.command("-debug", (c: CommandInput) => {
appState.toggleDebugMode();
appState.addCommandResult(new StringResult(c.input, `Debug Mode: ${appState.debugMode}`))
});
cmd.command({
canHandle: (input:string) => expression.parser.canParse(input),
handle: function(c: CommandInput) {
var expr = expression.parser.parse(c.input);
appState.toggleDebugMode();
appState.addCommandResult(new ExpressionResult(c.input, expr as ExpressionInput));
}
})
// Last command handler reports that input is unknown
cmd.command({
canHandle: () => true,
handle: (c: CommandInput) => appState.addCommandResult(new UnknownCommandResult(c.input))
});
cmd.onError((input: string, err: Error) => appState.addCommandResult(new ErrorResult(input, err)));
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import cmd from '../core/cmd';
type CommandLinkProps = {
command?:string;
text:string;
}
function CommandLink(props: CommandLinkProps) {
return <a href="javascript:void(0)" onClick={e => cmd.execute(props.command || props.text)}>{props.text}</a>
}
export default CommandLink;

View File

@@ -1,19 +1,27 @@
import React from 'react';
import HelpResult from '../models/HelpResult';
import AboutResult from '../models/AboutResult';
import UnknownCommandResult from '../models/UnknownCommandResult';
import HelpResultView from './results/HelpResultView';
import AboutResultView from './results/AboutResultView';
import ExpressionResult from '../models/ExpressionResult';
import BitwiseOperationExpressionView from './results/BitwiseOperationExpressionView';
import BitwiseOperationExpressionView from './results/expressions/BitwiseOperationExpressionView';
import WhatsnewResult from '../models/WhatsnewResult';
import WhatsnewResultView from './results/WhatsnewResultView';
import WhatsnewResultView from './results/WhatsNewResultView';
import ErrorResult from '../models/ErrorResult';
import StringResult from '../models/StringResult';
import * as expression from '../expression';
import CommandResult from '../models/CommandResult';
import AppState from '../core/AppState';
export default class DisplayResult extends React.Component {
type DisplayResultProps = {
content : CommandResult,
appState: AppState,
inputHash: string,
input: string,
key: number
}
export default class DisplayResult extends React.Component<DisplayResultProps> {
render() {
return <div className="result">
@@ -24,9 +32,10 @@ export default class DisplayResult extends React.Component {
</div>;
}
findResultComponent(result) {
findResultComponent(result: CommandResult) : JSX.Element {
if(result instanceof HelpResult) {
return <HelpResultView content={result} />
return <HelpResultView />
}
if(result instanceof AboutResult) {
@@ -52,7 +61,7 @@ export default class DisplayResult extends React.Component {
}
return <div className="result">
<div className="error">¯\_()_/¯ Sorry, i don&prime;t know what <strong>{this.props.input}</strong> is</div>
<div className="error">¯\_()_/¯ Sorry, i don&prime;t know what <strong>{this.props.content.input}</strong> is</div>
</div>
}
}

View File

@@ -0,0 +1,33 @@
import AppState from "../core/AppState";
import React from "react";
type IndicatorsProps = {
appState: AppState
};
function Indicators(props: IndicatorsProps) {
const list = [];
const state = props.appState;
if(props.appState.env != 'prod') {
list.push(state.env);
}
if(props.appState.debugMode) {
list.push("debug");
}
if(localStorage.getItem('TrackAnalytics') === 'false') {
list.push("notrack");
}
if(list.length == 0)
return null;
return <div>
{list.map(i => <span>{i}&nbsp;</span>)}
</div>
}
export default Indicators;

View File

@@ -1,14 +1,25 @@
import React from 'react';
import cmd from '../cmd';
import log from 'loglevel';
export default class InputBox extends React.Component {
constructor() {
super();
export interface IInputBoxProps
{
onCommandEntered: (input :string) => void;
}
export default class InputBox extends React.Component<IInputBoxProps> {
history: string[];
historyIndex: number;
nameInput: HTMLInputElement | null;
constructor(props: IInputBoxProps) {
super(props);
this.nameInput = null;
this.history = [];
this.historyIndex = -1;
}
componentDidMount(){
if(this.nameInput != null)
this.nameInput.focus();
}
@@ -21,21 +32,21 @@ export default class InputBox extends React.Component {
placeholder="type expression like '1>>2' or 'help' "/>;
}
onKeyUp(e) {
onKeyUp(e: any) {
var input = e.target;
if (e.keyCode != 13 || input.value.trim().length == 0) {
return;
}
var value = input.value;
this.history.unshift(value);
var commandInput = input.value;
this.history.unshift(commandInput);
this.historyIndex = -1;
input.value = '';
cmd.execute(value);
this.props.onCommandEntered(commandInput);
}
onKeyDown(args) {
onKeyDown(args: any) {
if(args.keyCode == 38) {
var newIndex = this.historyIndex+1;

View File

@@ -1,11 +1,12 @@
import React from 'react'
export default class AboutResultView extends React.Component {
render() {
return <div className="aboutTpl">
function AboutResultView() {
return <div className="aboutTpl" data-result-type="help">
<p> Created by <a href="http://boryslevytskyi.github.io/">Borys Levytskyi</a>. Please give it a like if BitwiseCmd has helped you in your work.</p>
<p>If you have an idea, suggestion or you've spotted a bug here, please send it to <a href="mailto:&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;?subject=Feedback">&#098;&#105;&#116;&#119;&#105;&#115;&#101;&#099;&#109;&#100;&#064;&#103;&#109;&#097;&#105;&#108;&#046;&#099;&#111;&#109;</a> or tweet on <a href="http://twitter.com/BitwiseCmd">@BitwiseCmd</a>. Your feedback is greatly appreciated.</p>
<p><a href="https://github.com/BorisLevitskiy/BitwiseCmd">Project on <strong>GitHub</strong></a></p>
</div>;
}
}
};
export default AboutResultView;

View File

@@ -1,11 +1,24 @@
import React from 'react';
export default class BinaryStringView extends React.Component {
export type BinaryStringViewProps = {
allowFlipBits: boolean;
binaryString: string;
onFlipBit: (input: FlipBitEventArg) => void;
emphasizeBytes: boolean;
};
export type FlipBitEventArg = {
index: number;
binaryString: string;
$event: any;
};
export default class BinaryStringView extends React.Component<BinaryStringViewProps> {
render() {
return <span>{this.getChildren()}</span>
}
onBitClick(index, e) {
onBitClick(index: number, e : any) {
if(!this.props.allowFlipBits) {
return;
}
@@ -25,14 +38,17 @@ export default class BinaryStringView extends React.Component {
return bits;
}
createBits(bitChars) {
createBits(bitChars:string[]) : JSX.Element[] {
const allowFlipBits = this.props.allowFlipBits || false;
const css = allowFlipBits ? ' flipable' : ''
const classNames = { '0': `zero${css}`, '1' : `one ${css}` };
return bitChars.map((c, i) => <span className={classNames[c]} key={i} onClick={e => this.onBitClick(i, e)}>{c}</span>);
return bitChars.map((c, i) => {
var className = c == '0' ? `zero${css}` : `one${css}`;
return <span className={className} key={i} onClick={e => this.onBitClick(i, e)}>{c}</span>
});
}
splitIntoBytes(bits) {
splitIntoBytes(bits: JSX.Element[]) {
const bytes = [];
var key = 0;

View File

@@ -1,9 +1,8 @@
import React from 'react';
import cmd from '../../cmd';
import CommandLink from '../misc/CommandLink';
import CommandLink from '../CommandLink';
function HelpResultView() {
export default class HelpResultView extends React.Component {
render() {
return <div className="help helpResultTpl">
<div style={{overflow: "hidden"}}>
<div style={{float: "left", "marginRight": "20px"}}>
@@ -46,4 +45,5 @@ export default class HelpResultView extends React.Component {
</div>
</div>;
}
}
export default HelpResultView;

View File

@@ -1,8 +1,8 @@
import React from 'react'
import CommandLink from '../misc/CommandLink'
import CommandLink from '../CommandLink'
function WhatsnewResultView() {
export default class WhatsnewResultView extends React.Component {
render() {
return <div className="changelog">
<h3>Changelog</h3>
<div className="item item-new">
@@ -23,4 +23,5 @@ export default class WhatsnewResultView extends React.Component {
</div>
</div>;
}
}
export default WhatsnewResultView;

View File

@@ -0,0 +1,173 @@
import { NumericOperand, ListOfNumbersExpression, BitwiseOperationExpression, ExpressionOperand } from '../../../expression/expression';
import { ExpressionInputItem, ExpressionInput } from '../../../expression/expression-interfaces';
type Config = {
emphasizeBytes: boolean;
allowFlipBits: boolean;
}
type ExpressionItemModel = {
sign: string;
css: string;
expressionItem: ExpressionInputItem;
allowFlipBits: boolean;
label: string;
}
export default class BitwiseExpressionViewModel {
emphasizeBytes: boolean;
items: ExpressionItemModel[];
maxNumberOfBits: number;
allowFlipBits: boolean;
constructor({ emphasizeBytes = false, allowFlipBits = false} : Config) {
this.emphasizeBytes = emphasizeBytes;
this.items = [];
this.maxNumberOfBits = 0;
this.allowFlipBits = allowFlipBits === true;
}
static buildListOfNumbers(expr : ListOfNumbersExpression, config : Config) {
var model = new BitwiseExpressionViewModel(config);
expr.numbers.forEach(op => model.addOperandRow(op));
model.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(model.maxNumberOfBits, model.emphasizeBytes);
return model;
}
static buildMultiple (expr : BitwiseOperationExpression, config : Config) {
var op = expr.expressionItems[0],
i = 0, len = expr.expressionItems.length,
ex, m = new BitwiseExpressionViewModel(config);
var prev : NumericOperand | null = null;
for (;i<len;i++) {
ex = expr.expressionItems[i];
if(ex instanceof NumericOperand) {
m.addOperandRow(ex);
prev = ex;
continue;
}
var eo = ex as ExpressionOperand;
// If it a single NOT expression
if(eo.isNotExpression) {
m.addExpressionOperandRow(eo);
var notResult = eo.evaluate();
m.addExpressionResultRow(notResult);
prev = notResult;
}
else if(eo.isShiftExpression){
prev = eo.evaluate(prev as NumericOperand);
m.addShiftExpressionResultRow(eo, prev);
} else {
prev = eo.evaluate(prev as NumericOperand);
m.addExpressionOperandRow(eo);
m.addExpressionResultRow(prev);
}
}
m.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(m.maxNumberOfBits, m.emphasizeBytes);
return m;
};
static buildNot (expression: ExpressionOperand, config : Config) {
var m = new BitwiseExpressionViewModel(config);
m.addExpressionOperandRow(expression);
m.addExpressionResultRow(expression.evaluate());
m.maxNumberOfBits = BitwiseExpressionViewModel.getNumberOfBits(m.maxNumberOfBits, m.emphasizeBytes);
return m;
};
addOperandRow(operand: NumericOperand) {
this.maxNumberOfBits = Math.max(operand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign:'',
css: '',
expressionItem: operand,
allowFlipBits: this.allowFlipBits,
label: ''
});
};
addExpressionOperandRow(expression: ExpressionOperand) {
const resultNumber = expression.isNotExpression ? expression.evaluate() : expression.getUnderlyingOperand();
this.maxNumberOfBits = Math.max(resultNumber.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign: expression.sign,
css: '',
label: this.getLabel(resultNumber),
expressionItem: expression.operand,
allowFlipBits: this.allowFlipBits
});
};
addShiftExpressionResultRow(expression : ExpressionOperand, resultOperand : NumericOperand) {
this.maxNumberOfBits = Math.max(resultOperand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign: expression.sign + expression.operand.toString(),
css: 'expression-result',
expressionItem: resultOperand,
allowFlipBits: false,
label: ''
});
};
addExpressionResultRow(operand : NumericOperand) {
this.maxNumberOfBits = Math.max(operand.getLengthInBits(), this.maxNumberOfBits);
this.items.push({
sign:'=',
css: 'expression-result',
expressionItem: operand,
allowFlipBits: false,
label: '',
});
};
getLabel (op: NumericOperand) : string {
if(op.base == 'bin') {
return op.toString("dec");
}
return op.toString();
}
// TODO: move this method elsewhere. It is also used in LisOfNumbersExpressionView.js
static getNumberOfBits = function (bits : number, emphasizeBytes : boolean) : number {
if(emphasizeBytes && bits % 8 != 0) {
if(bits < 8) {
return 8;
}
var n = bits - (bits % 8);
return n + 8;
}
return bits;
};
static createModel(expr : ExpressionInput, emphasizeBytes: boolean) : BitwiseExpressionViewModel {
if(expr instanceof ListOfNumbersExpression) {
return BitwiseExpressionViewModel.buildListOfNumbers(expr, {
emphasizeBytes: emphasizeBytes,
allowFlipBits: true
});
}
if(expr instanceof BitwiseOperationExpression) {
return BitwiseExpressionViewModel.buildMultiple(expr, {
emphasizeBytes: emphasizeBytes,
allowFlipBits: false
});
}
throw new Error("Cannot build BitwiseExpressionViewModel out of expression " + expr);
}
}

View File

@@ -0,0 +1,134 @@
import React from 'react';
import formatter from '../../../core/formatter';
import BinaryStringView, { FlipBitEventArg } from '../BinaryString';
import BitwiseExpressionViewModel from './BitwiseExpressionModel';
import { ExpressionInput, ExpressionInputItem } from '../../../expression/expression-interfaces';
import { ExpressionOperand, NumericOperand } from '../../../expression/expression';
type BitwiseOperationExpressionViewProps = {
expression: ExpressionInput;
emphasizeBytes: boolean;
}
type BitwiseOperationExpressionViewState = {
}
export default class BitwiseOperationExpressionView extends React.Component<BitwiseOperationExpressionViewProps, BitwiseOperationExpressionViewState> {
constructor(props: BitwiseOperationExpressionViewProps) {
super(props);
this.state = {};
}
render() {
var rows = this.getRows();
if(!rows) {
return null;
}
return <table className="expression">
<tbody>
{rows}
</tbody>
</table>
}
getRows() : JSX.Element[] | null {
var model = BitwiseExpressionViewModel.createModel(this.props.expression, this.props.emphasizeBytes);
return model.items.map((itm, i) =>
<ExpressionRow
key={i}
sign={itm.sign}
css={itm.css}
allowFlipBits={itm.allowFlipBits}
expressionItem={itm.expressionItem}
emphasizeBytes={this.props.emphasizeBytes}
maxNumberOfBits={model.maxNumberOfBits}
onBitFlipped={() => this.onBitFlipped()} />);
}
onBitFlipped() {
this.forceUpdate();
//this.setState({d:new Date()});
}
}
type ExpressionRowProps = {
sign: string,
css: string,
maxNumberOfBits: number,
emphasizeBytes: boolean,
allowFlipBits: boolean,
expressionItem: ExpressionInputItem,
onBitFlipped: any
}
class ExpressionRow extends React.Component<ExpressionRowProps> {
constructor(props: ExpressionRowProps) {
super(props);
this.state = { operand: null };
}
render() {
const { sign, css, maxNumberOfBits, emphasizeBytes, allowFlipBits } = this.props;
return <tr className={css}>
<td className="sign">{sign}</td>
<td className="label">{this.getLabel()}</td>
<td className="bin">
<BinaryStringView
emphasizeBytes={emphasizeBytes}
binaryString={formatter.padLeft(this.getBinaryString(), maxNumberOfBits, '0')}
allowFlipBits={allowFlipBits}
onFlipBit={args => this.flipBit(args)}/>
</td>
<td className="other">{this.getOther()}</td>
</tr>;;
}
getBinaryString() : string {
return this.props.expressionItem.evaluate().toBinaryString();
}
getLabel(): string {
// For expressions like |~2
// TODO: find a better way...
if(this.props.expressionItem.isExpression) {
const ex = this.props.expressionItem as ExpressionOperand;
return ex.sign + this.getLabelString(ex.getUnderlyingOperand());
}
return this.getLabelString(this.props.expressionItem.getUnderlyingOperand());
}
getOther() {
if(this.props.expressionItem.isExpression) {
const ex = this.props.expressionItem as ExpressionOperand;
const op = ex.evaluate();
return op.toString();
}
return this.props.expressionItem.evaluate().toOtherKindString();
}
getLabelString (op: NumericOperand) : string {
return op.toString(op.base == 'bin' ? 'dec' : op.base);
}
flipBit(args: FlipBitEventArg) {
const op = this.props.expressionItem.getUnderlyingOperand();
const { index, binaryString } = args;
var arr = binaryString.split('');
arr[index] = arr[index] == '0' ? '1' : '0';
var bin = arr.join('');
var newValue = parseInt(bin, 2);
op.setValue(newValue);
this.props.onBitFlipped();
}
}

78
src/core/AppState.ts Normal file
View File

@@ -0,0 +1,78 @@
import log from 'loglevel';
export type PersistedAppData = {
emphasizeBytes: boolean;
uiTheme: string;
version: number;
debugMode: boolean | null;
}
export type AppStateChangeHandler = (state: AppState) => void;
export default class AppState {
version: number = 4;
emphasizeBytes: boolean;
debugMode: boolean = false;
uiTheme: string;
handlers: AppStateChangeHandler[];
commandResults: any[];
persistedVersion: number;
wasOldVersion: boolean;
env: string;
constructor(persistData : PersistedAppData, env: string) {
this.commandResults = [];
this.handlers = [];
this.uiTheme = persistData.uiTheme || 'midnight';
this.env = env;
this.emphasizeBytes = persistData.emphasizeBytes || true;
this.persistedVersion = persistData.version || 0.1;
this.wasOldVersion = persistData.version != null && this.version > this.persistedVersion;
this.debugMode = env !== 'prod' || persistData.debugMode === true;
}
addCommandResult(result : any) {
this.commandResults.unshift(result);
log.debug("result added", result);
this.triggerChanged();
}
clearCommandResults() {
this.commandResults = [];
this.triggerChanged();
}
toggleEmphasizeBytes() {
this.emphasizeBytes = !this.emphasizeBytes;
this.triggerChanged();
}
onChange(handler : AppStateChangeHandler) {
this.handlers.push(handler);
}
triggerChanged() {
this.handlers.forEach(h => h(this));
}
setUiTheme(theme: string) {
this.uiTheme = theme;
this.triggerChanged();
}
toggleDebugMode() {
this.debugMode = !this.debugMode;
this.triggerChanged();
}
getPersistData() : PersistedAppData {
return {
emphasizeBytes: this.emphasizeBytes,
uiTheme: this.uiTheme,
version: this.version,
debugMode: this.debugMode
}
}
};

View File

@@ -1,26 +1,28 @@
import AppState, { PersistedAppData } from "./AppState";
const storeKey = 'AppState';
export default {
getPersistedData() {
getPersistedData() : PersistedAppData {
var json = window.localStorage.getItem(storeKey);
if(!json) {
return {};
return {} as PersistedAppData;
}
try {
return JSON.parse(json);
return JSON.parse(json) as PersistedAppData;
}
catch(ex) {
console.error('Failed to parse AppState json. Json Value: \n' + json, ex);
return {};
return {} as PersistedAppData;;
}
},
watch (appState) {
watch (appState: AppState) {
appState.onChange(() => this.persistData(appState));
},
persistData(appState) {
persistData(appState: AppState) {
localStorage.setItem(storeKey, JSON.stringify(appState.getPersistData()));
}
}

27
src/core/calc.test.ts Normal file
View File

@@ -0,0 +1,27 @@
import calc from './calc';
import { BitwiseOperationExpression, NumericOperand, ExpressionOperand } from '../expression/expression';
describe("calc", () => {
it('calculates number of bits', () => {
expect(calc.numberOfBits(1)).toBe(1);
expect(calc.numberOfBits(2)).toBe(2);
expect(calc.numberOfBits(3)).toBe(2);
});
it('calculates max number of bits', () => {
expect(calc.maxNumberOfBits([1, 2, 3, 10])).toBe(4);
});
it('calculates expression', () => {
var result = calc.calcExpression(new BitwiseOperationExpression(
"1|2&3",
[
new NumericOperand(1),
new ExpressionOperand("|2", new NumericOperand(2), "|"),
new ExpressionOperand("&3", new NumericOperand(3), "&"),
]
));
expect(result).toBe(3);
});
});

26
src/core/calc.ts Normal file
View File

@@ -0,0 +1,26 @@
import { ExpressionInput } from "../expression/expression-interfaces";
export default {
numberOfBits: function (num: number) : number {
if(num < 0) {
return 32;
}
return Math.floor(Math.log(num) / Math.log(2)) + 1;
},
maxNumberOfBits: function (arr: number[]) {
var counts = [], num;
for (var i = 0; i < arr.length; i++) {
num = arr[i];
counts.push(this.numberOfBits(num));
}
return Math.max.apply(null, counts);
},
calcExpression: function (expr: ExpressionInput) {
return eval(expr.expressionString);
}
};

49
src/core/cmd.test.ts Normal file
View File

@@ -0,0 +1,49 @@
import { CmdShell, ICommandHandler } from "./cmd";
import { func } from "prop-types";
describe("CmdShell", () => {
it("simple command", () => {
var handler = {
test1() { },
test2() { }
};
spyOn(handler, "test1");
spyOn(handler, "test2");
var sut = new CmdShell();
sut.command("test1", handler.test1);
sut.execute("test1");
expect(handler.test1).toHaveBeenCalled();
expect(handler.test2).not.toHaveBeenCalled();
});
it("unknown command", () => {
var sut = new CmdShell();
sut.execute("test1");
});
it("object handler", () => {
var handler = {
canHandle: function(input: string) : boolean { return input === "test2"; },
handle: function (input: string) { }
};
spyOn(handler, "handle");
var sut = new CmdShell();
sut.command(handler);
sut.execute("test1");
expect(handler.handle).not.toHaveBeenCalled();
sut.execute("test2");
expect(handler.handle).toHaveBeenCalled();
});
})

107
src/core/cmd.ts Normal file
View File

@@ -0,0 +1,107 @@
import is from './is';
import log from 'loglevel';
export type CommandInput = {
input: string;
}
type HandleFunction = (input: CommandInput) => void;
type InputErrorHandler = (input:string, error: Error) => void;
export interface ICommandHandler {
canHandle (input:string) : boolean;
handle: HandleFunction;
}
export class CmdShell {
debugMode: boolean;
handlers: ICommandHandler[];
errorHandler: InputErrorHandler | null;
constructor() {
this.handlers = [];
this.debugMode = false;
this.errorHandler = null;
};
execute (rawInput: string) {
log.debug(`Executing command: ${rawInput}`)
var input = rawInput.trim().toLowerCase();
var handler = this.findHandler(input);
if(handler != null) {
if(this.debugMode) {
this.invokeHandler(input, handler);
} else {
try {
this.invokeHandler(input, handler);
} catch (e) {
this.handleError(input, e);
}
}
}
else {
log.debug(`Handled is not found for command: ${rawInput}`)
this.handleError(input, new Error("Unsupported expression: " + input.trim()));
}
};
onError(h: InputErrorHandler) {
this.errorHandler = h;
}
command (cmd : string | object, handler? : any) {
var h = this.createHandler(cmd, handler);
if(h == null){
console.warn('unexpected set of arguments: ', JSON.stringify(arguments));
return;
}
if(!is.aFunction(h.canHandle)) {
console.warn('handler is missing "canHandle" function. registration denied.');
return;
}
if(!is.aFunction(h.handle)) {
console.warn('handler is missing "handle" function. registration denied.');
return;
}
this.handlers.push(h);
};
createHandler (cmd : string | object, handler : HandleFunction) : ICommandHandler | null {
if(is.plainObject(cmd)) {
return cmd as ICommandHandler;
}
if(is.string(cmd)) {
return { canHandle: function (input) { return input === cmd; }, handle: handler };
}
return null;
}
findHandler (input: string) : ICommandHandler | null {
return this.handlers.filter(h => h.canHandle(input))[0];
};
invokeHandler (input : string, handler : ICommandHandler) {
var cmdResult = handler.handle({ input: input});
if(cmdResult != null) {
log.debug(cmdResult);
}
};
handleError (input: string, err: Error) {
if(this.debugMode)
console.error(input, err);
if(this.errorHandler != null)
this.errorHandler(input, err);
}
}
export default new CmdShell();

View File

@@ -0,0 +1,13 @@
import formatter from './formatter';
describe("formatter", () => {
it('formats string', () => {
expect(formatter.formatString(15, "dec")).toBe("15");
expect(formatter.formatString(15, "hex")).toBe("f");
expect(formatter.formatString(15, "bin")).toBe("1111");
});
it('pads left', () => {
expect(formatter.padLeft("1", 3, " ")).toBe(" 1");
});
});

28
src/core/formatter.ts Normal file
View File

@@ -0,0 +1,28 @@
export default {
formatString: function(num: number, kind: string) : string {
return num.toString(getBase(kind || "bin"));
},
padLeft: function (str: string, length: number, symbol: string) : string {
var sb = Array.prototype.slice.call(str), symbol = symbol || "0";
if(length == null) {
return str;
}
while(length > sb.length) {
sb.unshift(symbol);
}
return sb.join('');
}
};
function getBase(kind:string) : number {
switch (kind){
case 'bin': return 2;
case 'hex': return 16;
case 'dec': return 10;
}
throw new Error("Unsupported kind: " + kind);
}

35
src/core/hash.ts Normal file
View File

@@ -0,0 +1,35 @@
export default {
encodeHash: function(input:string):string {
return encodeURI(input.trim().replace(/\s/g,','));
},
decodeHash: function(hashValue:string):string {
return decodeURI(hashValue).replace(/^\#/, '').replace(/,/g,' ');
},
getArgs: function (hashValue:string) : string[] {
var decodedHash = this.decodeHash(hashValue);
var args : string[] = [];
splitHashList(decodedHash).forEach(function(value) {
args.push(value);
});
return args;
}
};
function splitHashList(str: string) : string[] {
var values = [];
if(str.indexOf('||')) {
str.split('||').forEach(function (v) {
if (v.length > 0) {
values.push(v);
}
});
} else {
values.push(str);
}
return values;
}

16
src/core/is.test.ts Normal file
View File

@@ -0,0 +1,16 @@
import is from "./is";
describe("is", () => {
it("can detect array", () => {
expect(is.array([1,2,3])).toBe(true);
expect(is.array({})).toBe(false);
expect(is.array("123")).toBe(false);
});
it("can detect plain object", () => {
expect(is.plainObject({})).toBe(true);
expect(is.plainObject([1,2,3])).toBe(false);
expect(is.plainObject("123")).toBe(false);
});
});

21
src/core/is.ts Normal file
View File

@@ -0,0 +1,21 @@
export default {
plainObject: function(obj : any) : boolean {
return typeof obj == "object" && !(obj instanceof Array) && obj instanceof Object;
},
aFunction: function(obj : any) : boolean {
return typeof obj == "function";
},
string: function(obj : any) : boolean {
return typeof obj == "string";
},
array: function(obj : any) : boolean {
return obj instanceof Array;
},
number: function(obj : any) : boolean {
return typeof obj == "number" && !isNaN(obj)
}
}

View File

@@ -0,0 +1,12 @@
import { ExpressionInput, ExpressionInputItem } from "./expression-interfaces";
export default class BitwiseOperationExpression implements ExpressionInput {
expressionString: string;
expressionItems: ExpressionInputItem[];
constructor(expressionString: string, expressions: ExpressionInputItem[]) {
this.expressionString = expressionString;
this.expressionItems = expressions;
}
}

View File

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

View File

@@ -0,0 +1,48 @@
import NumericOperand from './NumericOperand';
import { ExpressionInputItem } from './expression-interfaces';
export default class ExpressionOperand implements ExpressionInputItem {
expressionString: string;
operand: ExpressionInputItem;
sign: string;
isExpression: boolean;
isShiftExpression: boolean;
isNotExpression: boolean;
constructor(expressionString : string, operand : ExpressionInputItem, sign : string) {
this.expressionString = expressionString;
this.operand = operand;
this.sign = sign;
this.isExpression = true;
this.isShiftExpression = this.sign.indexOf('<') >= 0 || this.sign.indexOf('>')>= 0;
this.isNotExpression = this.sign === '~';
}
evaluate(operand?: NumericOperand) : NumericOperand {
if (operand instanceof ExpressionOperand) {
throw new Error('value shouldnt be expression');
}
var evaluatedOperand = this.operand.evaluate();
var str = '';
if(this.sign == '~'){
str = '~' + evaluatedOperand.value;
} else {
if(operand == null)
throw new Error("Other is required for expression: " + this.expressionString)
str = operand.value + this.sign + evaluatedOperand.value;
}
return NumericOperand.create(eval(str), evaluatedOperand.base);
}
getUnderlyingOperand() : NumericOperand {
return this.operand.getUnderlyingOperand();
}
toString(): string {
return this.sign + this.operand.toString();
}
}

View File

@@ -0,0 +1,7 @@
import NumericOperand from "./NumericOperand";
import ListOfNumbersExpression from "./ListOfNumbersExpression";
it('calculates max bits length', () => {
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [NumericOperand.parse("10"), NumericOperand.parse("0xabef"), NumericOperand.parse("0b01010")])
expect(expr.maxBitsLength).toBe(16);
});

View File

@@ -0,0 +1,18 @@
import NumericOperand from "./NumericOperand";
import { ExpressionInput, ExpressionInputItem } from "./expression-interfaces";
export default class ListOfNumbersExpression implements ExpressionInput {
numbers: NumericOperand[];
expressionString: string;
maxBitsLength: number;
constructor(expressionString: string, numbers: NumericOperand[]) {
this.expressionString = expressionString;
this.numbers = numbers;
this.maxBitsLength = numbers.map(n => n.lengthInBits).reduce((n , c) => n >= c ? n : c, 0);
}
toString() {
return this.numbers.map(n => n.value.toString()).join(' ');
}
}

View File

@@ -0,0 +1,13 @@
import NumericOperand from './NumericOperand';
it('parsed from string', () => {
var op = NumericOperand.parse('123');
expect(op.base).toBe('dec');
expect(op.value).toBe(123);
});
it('can get other kind', () => {
var op = new NumericOperand(10, 'dec');
expect(op.getOtherBase('hex')).toBe('dec');
expect(op.getOtherBase('bin')).toBe('hex');
});

View File

@@ -0,0 +1,116 @@
import {numberParser} from './numberParser';
import { ExpressionInputItem, NumberBase } from './expression-interfaces';
var globalId : number = 1;
// Represents numeric value
export default class NumericOperand implements ExpressionInputItem {
id: number;
value: number;
base: NumberBase;
lengthInBits: number;
isExpression: boolean;
constructor(value : number, base?: NumberBase) {
this.id = globalId++;
this.value = value;
this.base = base || "dec";
this.lengthInBits = NumericOperand.getBitLength(this.value);
this.isExpression = false;
}
getLengthInBits() {
if(this.value < 0) {
return 32;
}
return Math.floor(Math.log(this.value) / Math.log(2)) + 1;
};
getOtherBase(kind?: NumberBase) : NumberBase {
switch(kind || this.base) {
case 'dec':
case 'bin':
return 'hex';
case 'hex': return 'dec';
default : throw new Error(kind + " kind doesn't have opposite kind")
}
};
toString(base?: NumberBase) : string {
return NumericOperand.toBaseString(this.value, base || this.base);
}
toOtherKindString() : string {
return this.toString(this.getOtherBase());
}
toDecimalString() {
return this.toString('dec');
}
toHexString() {
return this.toString('hex');
}
toBinaryString() : string {
return this.toString('bin');
}
setValue(value : number) {
this.value = value;
this.lengthInBits = NumericOperand.getBitLength(value);
}
evaluate() : NumericOperand {
return this;
}
getUnderlyingOperand() : NumericOperand {
return this
}
static getBitLength(num : number) {
return Math.floor(Math.log(num) / Math.log(2)) + 1;
}
static getBase(kind : string){
switch (kind){
case 'bin': return 2;
case 'hex': return 16;
case 'dec': return 10;
}
};
static create(value : number, base? : NumberBase) {
return new NumericOperand(value, base || "dec");
};
static parse(input: string) : NumericOperand {
var parsed = numberParser.parse(input);
if(!parsed) {
throw new Error(input + " is not a valid number");
}
return new NumericOperand(parsed.value, parsed.base);
}
static toBaseString(value : number, base : NumberBase) : string {
switch(base) {
case 'hex':
var hexVal = Math.abs(value).toString(16);
return value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
case 'bin':
return (value>>>0).toString(2);
case 'dec':
return value.toString(10);
default:
throw new Error("Unexpected kind: " + base)
}
};
static toHexString (hex : string) {
return hex.indexOf('-') === 0 ? '-0x' + hex.substr(1) : '0x' + hex;
};
}

View File

@@ -0,0 +1,17 @@
import { NumericOperand } from "./expression";
export interface ExpressionInput
{
expressionString: string;
}
export interface ExpressionInputItem
{
isExpression: boolean;
getUnderlyingOperand: () => NumericOperand;
evaluate(operand? : NumericOperand): NumericOperand;
}
export type NumberBase = 'dec' | 'hex' | 'bin';

View File

@@ -0,0 +1,35 @@
import { parser, ListOfNumbersExpression, BitwiseOperationExpression, NumericOperand, ExpressionOperand } from "./expression";
describe("expression parser", () => {
it("pares list of number expression", () => {
var result = parser.parse("1 2 3");
expect(result).toBeInstanceOf(ListOfNumbersExpression);
});
it("pares different operations expressions", () => {
expect(parser.parse("~1")).toBeInstanceOf(BitwiseOperationExpression);
expect(parser.parse("1^2")).toBeInstanceOf(BitwiseOperationExpression);
expect(parser.parse("1|2")).toBeInstanceOf(BitwiseOperationExpression);
});
it("pares multiple operand expression", () => {
const result = parser.parse("1^2") as BitwiseOperationExpression;
expect(result.expressionItems.length).toBe(2);
const first = result.expressionItems[0];
const second = result.expressionItems[1];
expect(first).toBeInstanceOf(NumericOperand);
expect(first.value).toBe(1);
expect(second).toBeInstanceOf(ExpressionOperand);
expect(second.sign).toBe("^");
expect(second.operand.value).toBe(2);
});
it("bug", () => {
var result = parser.parse("1|~2") as BitwiseOperationExpression;
expect(result.expressionItems.length).toBe(2);
});
})

View File

@@ -0,0 +1,143 @@
import NumericOperand from './NumericOperand';
import ExpressionOperand from './ExpressionOperand'
import ListOfNumbersExpression from './ListOfNumbersExpression';
import BitwiseOperationExpression from './BitwiseOperationExpression';
import { ExpressionInput, ExpressionInputItem, NumberBase } from './expression-interfaces';
export { default as NumericOperand } from './NumericOperand';
export { default as ExpressionOperand } from './ExpressionOperand';
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression';
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression';
interface IExpressionParserFactory {
canCreate: (input: string) => boolean;
create: (input: string) => ExpressionInput;
};
class ExpressionParser {
factories: IExpressionParserFactory[];
constructor() {
this.factories = [];
};
canParse (input: string) : boolean {
var trimmed = input.replace(/^\s+|\s+$/, '');
var i = this.factories.length-1;
for(;i>=0;i--) {
if(this.factories[i].canCreate(trimmed) === true){
return true;
}
}
return false;
};
parse (input: string) : ExpressionInput | null {
var trimmed = input.replace(/^\s+|\s+$/, '');
var i = 0, l = this.factories.length, factory;
for(;i<l;i++) {
factory = this.factories[i];
if(factory.canCreate(trimmed) == true){
return factory.create(trimmed);
}
}
return null;
};
parseOperand (input : string) : NumericOperand {
return NumericOperand.parse(input);
};
createOperand (number : number, base : NumberBase) : NumericOperand {
return NumericOperand.create(number, base);
};
addFactory (factory: IExpressionParserFactory) {
this.factories.push(factory);
}
}
class ListOfNumbersExpressionFactory implements IExpressionParserFactory
{
regex: RegExp;
constructor() {
this.regex = /^(-?(?:\d+|0x[\d,a-f]+|0b[0-1])\s?)+$/;
}
canCreate (input: string): boolean {
return this.regex.test(input);
};
create (input : string) : ExpressionInput {
var matches = this.regex.exec(input) as RegExpExecArray;
var numbers = [] as NumericOperand[];
var input = matches.input;
input.split(' ').forEach((n: string) => {
if(n.trim().length > 0) {
numbers.push(NumericOperand.parse(n.trim()));
}
});
return new ListOfNumbersExpression(input, numbers);
}
}
class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
fullRegex: RegExp;
regex: RegExp;
constructor() {
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,a-f,0-9]+)))+$/;
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,a-f,0-9]+))/g;
}
canCreate (input: string) : boolean {
this.fullRegex.lastIndex = 0;
return this.fullRegex.test(this.normalizeString(input));
};
create (input: string) : ExpressionInput {
var m, operands : ExpressionInputItem[] = [],
normalizedString = this.normalizeString(input);
while ((m = this.regex.exec(normalizedString)) != null) {
operands.push(this.parseMatch(m));
}
return new BitwiseOperationExpression(normalizedString, operands)
};
parseMatch (m:any): ExpressionInputItem {
var input = m[0],
sign = m[1],
num = m[2];
var parsed = null;
if(num.indexOf('~') == '0') {
parsed = new ExpressionOperand(num, NumericOperand.parse(num.substring(1)), '~');
}
else {
parsed = NumericOperand.parse(num);
}
if(sign == null) {
return parsed as ExpressionOperand;
} else {
return new ExpressionOperand(input, parsed as NumericOperand, sign);
}
};
normalizeString (input : string): string {
return input.replace(/\s+/g,'');
};
}
var parser = new ExpressionParser();
parser.addFactory(new ListOfNumbersExpressionFactory());
parser.addFactory(new BitwiseOperationExpressionFactory());
export {parser};

View File

@@ -0,0 +1,38 @@
import {numberParser, ParsedNumber} from './numberParser';
describe("parser", () => {
it('parses decimal number', () => {
const result = numberParser.parse('10');
expect(result).not.toBeNull();
var number = result as ParsedNumber;
expect(number.value).toBe(10);
expect(number.base).toBe('dec');
expect(number.input).toBe('10');
});
it('parses hex number', () => {
const result = numberParser.parse('0xab');
expect(result).not.toBeNull();
var number = result as ParsedNumber;
expect(number.value).toBe(171);
expect(number.base).toBe('hex');
expect(number.input).toBe('0xab');
});
it('parses bin number', () => {
var result = numberParser.parse('0b0110');
expect(result).not.toBeNull();
var number = result as ParsedNumber;
expect(number.value).toBe(6);
expect(number.base).toBe('bin');
expect(number.input).toBe('0b0110');
});
it('returns null on bad inputs', () => {
expect(numberParser.parse('abc')).toBeNull();
expect(numberParser.parse('')).toBeNull();
});
});

View File

@@ -0,0 +1,68 @@
import { NumberBase } from "./expression-interfaces";
const decimalRegex = /^-?\d+$/;
const hexRegex = /^-?0x[0-9,a-f]+$/i;
const binRegex = /^-?0b[0-1]+$/i;
const operatorRegex = /^<<|>>|<<<|\&|\|\^|~$/;
interface ParserConfig {
regex: RegExp,
radix: number,
base: NumberBase,
prefix: string|RegExp
}
export interface ParsedNumber {
value: number;
base: NumberBase;
input: string;
}
var knownParsers : ParserConfig[] = [
{ regex: decimalRegex, radix: 10, base: 'dec', prefix: '^$' },
{ regex: hexRegex, radix: 16, base: 'hex', prefix:/0x/i },
{ regex: binRegex, radix: 2, base: 'bin', prefix:/0b/i }];
class NumberParser {
parsers: ParserConfig[];
constructor(parsers: ParserConfig[])
{
this.parsers = parsers;
}
parse (input : string) : ParsedNumber | null {
return this.parsers.map(p => this.applyParser(p, input)).reduce((c, n) => c || n);
};
parseOperator (input: string) : string | null {
var m = input.match(input);
if(m == null || m.length == 0) {
return null;
}
return m[0];
};
applyParser(parser : ParserConfig, rawInput: string) : ParsedNumber | null {
if(!parser.regex.test(rawInput)) {
return null;
}
var value = parseInt(rawInput.replace(parser.prefix, ''), parser.radix);
return {
value: value,
base: parser.base,
input: rawInput
}
}
}
const numberParser = new NumberParser(knownParsers);
export {numberParser};

View File

@@ -54,17 +54,17 @@ code { font-size: 1.2em; font-weight: bold; }
.cur { color: lightgray; }
.icon { width: 16px; height: 16px; display: inline-block; }
.light .twitter { background: url('../img/twitter-light.png') }
.dark .twitter { background: url('../img/twitter-dark.png') }
.midnight .twitter { background: url('../img/twitter-dark.png') }
.light .twitter { background: url('./img/twitter-light.png') }
.dark .twitter { background: url('./img/twitter-dark.png') }
.midnight .twitter { background: url('./img/twitter-dark.png') }
.light .feedback { background: url('../img/feedback-light.png') }
.dark .feedback { background: url('../img/feedback-dark.png') }
.midnight .feedback { background: url('../img/feedback-dark.png') }
.light .feedback { background: url('./img/feedback-light.png') }
.dark .feedback { background: url('./img/feedback-dark.png') }
.midnight .feedback { background: url('./img/feedback-dark.png') }
.light .github { background: url('../img/github-light.png') }
.dark .github { background: url('../img/github-dark.png') }
.midnight .github { background: url('../img/github-dark.png') }
.light .github { background: url('./img/github-light.png') }
.dark .github { background: url('./img/github-dark.png') }
.midnight .github { background: url('./img/github-dark.png') }
/* Light */
.light { background: #fafafa; }

View File

@@ -1,39 +0,0 @@
<!DOCTYPE>
<html>
<head>
<meta charset="UTF-8">
<meta name="description" content="Free Text Online Bitwise Calculator">
<title>BitwiseCmd</title>
<link rel="shortcut icon" href="http://bitwisecmd.com/favicon.ico">
<link rel="stylesheet" type="text/css" href="css/styles.css" />
<meta property="og:url" content="http://bitwisecmd.com" />
<meta property="og:type" content="website" />
<meta property="og:title" content="BitwiseCmd" />
<meta property="og:description" content="Free Text Online Bitwise Calculator" />
<meta property="og:image" content="http://bitwisecmd.com/img/social-avatar.png" />
</head>
<body>
<div id="root">
<p>Ooops... Something went wrong.</p>
<p>Either you have an old browser or I screwed something up ¯\_(ツ)_/¯</p>
<p>No worries! There is a still old version avaliable at <a href="http://bitwisecmd.com/old">http://bitwisecmd.com/old</a>.</p>
</div>
<script src="bundle.js"></script>
<script src="analytics.js"></script>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div class="social-container">
<div class="fb-like" data-href="http://bitwisecmd.com" data-layout="box_count" data-action="like" data-show-faces="false" data-share="true"></div>
</div>
</body>
</html>

View File

@@ -1,17 +1,18 @@
import AppState from './core/AppState';
import appStateStore from './core/appStateStore';
import cmd from './core/cmd';
import commands from './commands';
import AppRoot from './AppRoot';
import hash from './core/hash';
import log from 'loglevel';
import React from 'react';
import ReactDOM from 'react-dom';
import InputBox from './components/InputBox.jsx';
import AppState from './AppState';
import appStateStore from './appStateStore';
import cmd from './cmd';
import commands from './commands';
import AppRoot from './components/AppRoot';
import hash from './hash';
import log from 'loglevel';
import './index.css';
setupLogger();
const env = window.location.host === "bitwisecmd.com" ? 'prod' : 'stage';
setupLogger(env);
const appState = createAppState();
const appState = createAppState(env);
commands.initialize(cmd, appState);
@@ -22,17 +23,18 @@ ReactDOM.render(root, document.getElementById('root'));
log.debug("started");
function createAppState() {
function createAppState(env:string) {
var stateData = appStateStore.getPersistedData();
const appState = new AppState(stateData);
const appState = new AppState(stateData, env);
appStateStore.watch(appState);
log.debug("appState", appState);
log.debug("appState initialized", appState);
return appState;
}
function setupLogger() {
if(window.location.host != 'bitwisecmd.com' || window.location.hash.indexOf('-debug') > -1) {
log.setLevel("trace");
function setupLogger(env: Env) {
if(env != 'prod'){
log.setLevel("debug");
log.debug(`Log level is set to debug. Env: ${env}`)
} else {
log.setLevel("warn");
}
@@ -47,9 +49,11 @@ function executeStartupCommands() {
startupCommands = ["whatsnew"];
}
if(hashArgs.commands.length > 0) {
startupCommands = hashArgs.commands;
if(hashArgs.length > 0) {
startupCommands = hashArgs;
}
startupCommands.forEach(cmd.execute.bind(cmd));
}
type Env = 'prod' | 'stage';

View File

@@ -1,7 +1,7 @@
import CommandResult from './CommandResult';
export default class AboutResult extends CommandResult {
constructor(input) {
constructor(input:string) {
super(input);
}
}

View File

@@ -0,0 +1,13 @@
export default class CommandResult {
input: string;
inputHash: string;
constructor(input: string) {
this.input = input;
this.inputHash = this.encodeHash(input);
}
encodeHash (input: string) {
return encodeURI(input.trim().replace(/\s/g,','));
}
}

View File

@@ -1,7 +1,8 @@
import CommandResult from './CommandResult';
export default class ErrorResult extends CommandResult {
constructor(input, error) {
error: Error;
constructor(input: string, error : Error) {
super(input);
this.error = error;
}

View File

@@ -1,7 +1,9 @@
import CommandResult from './CommandResult';
import { ExpressionInput } from '../expression/expression-interfaces';
export default class ExpressionResult extends CommandResult {
constructor(input, expression) {
expression: ExpressionInput;
constructor(input: string, expression: ExpressionInput) {
super(input);
this.expression = expression;
}

View File

@@ -1,7 +1,7 @@
import CommandResult from './CommandResult';
export default class HelpResult extends CommandResult {
constructor(input) {
constructor(input: string) {
super(input);
}
}

View File

@@ -1,8 +1,9 @@
import CommandResult from './CommandResult';
export default class StringResult extends CommandResult {
constructor(input, text) {
value:string;
constructor(input: string, value : string) {
super(input);
this.value = text;
this.value = value;
}
}

View File

@@ -1,7 +1,8 @@
import CommandResult from './CommandResult';
export default class UnknownCommandResult extends CommandResult {
constructor(input) {
message:string;
constructor(input : string) {
super(input);
this.message = `Sorry, i don''t know what ${input} is :(`;
}

View File

@@ -1,7 +1,7 @@
import CommandResult from './CommandResult';
export default class WhatsnewResult extends CommandResult {
constructor(input) {
constructor(input: string) {
super(input);
}
}

1
src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -1,13 +0,0 @@
exports.config = {
seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
specs: [
'./e2e/spec.js'
],
multiCapabilities: [{
'browserName': 'chrome'
},{
'browserName': 'firefox'
}]
};

View File

@@ -1,11 +0,0 @@
exports.config = {
seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
specs: [
'./e2e/spec.js'
],
capabilities: {
'browserName': 'chrome'
}
};

View File

@@ -1,120 +0,0 @@
var Key = protractor.Key;
var By = protractor.By;
var resultTableReader = require('./resultTableReader');
function BitwiseCmdPage(driver, appUrl) {
this.driver = driver;
this.appUrl = appUrl;
}
BitwiseCmdPage.prototype.goToApp = function (hashValue) {
var url = this.appUrl;
var hash = hashValue || '-notrack';
if(hash.indexOf('-notrack') < 0) {
hash += "||-notrack";
}
if(url.indexOf("#") < 0) {
url += "#" + hash;
} else {
url += "||" + hash;
}
return this.driver.get(url);
};
BitwiseCmdPage.prototype.sendCommand = function(cmd) {
return this.driver.findElement(By.id('in')).then(function (el) {
return el.sendKeys(cmd + Key.ENTER);
});
};
BitwiseCmdPage.prototype.clearResults = function () {
return this.sendCommand("clear");
};
BitwiseCmdPage.prototype.executeExpression = function(expr) {
var self = this;
return this.clearResults().then(function() {
return self.sendCommand(expr);
})
};
BitwiseCmdPage.prototype.getLasExpressionResult = function() {
return this.driver.findElement(By.css('.expression')).then(function(resultElement) {
return new ExpressionResultObject(resultElement);
});
};
BitwiseCmdPage.prototype.getAllResults = function() {
return this.driver.findElements(By.css('.result')).then(function(resultElements) {
var results = [], i= 0, len = resultElements.length;
// TODO: Use _.map
for(;i<len;i++) {
results.push(new ExpressionResultObject(resultElements[i]));
}
return results;
});
};
BitwiseCmdPage.prototype.shouldHaveNoErrors = function () {
return this.driver.findElements(By.css('.result .error')).then(function(els) {
expect(els.length).toBe(0, "There should be no errors");
});
};
function ExpressionResultObject(resultElement) {
this.resultElement = resultElement;
}
ExpressionResultObject.prototype.readResult = function () {
return resultTableReader.read(this.resultElement);
};
ExpressionResultObject.prototype.shouldBe = function(expectedResult) {
this.readResult().then(function(actualResult) {
expect(actualResult.length).toEqual(expectedResult.length, "Unexpected result length");
var expected, actual;
for(var i=0;i<expectedResult.length; i++) {
var actual = actualResult[i],
expected = convertToExpected(expectedResult[i]);
//console.log("actual.bin.length=" + actual.bin.length)
expect(actual).toEqual(jasmine.objectContaining(expected));
}
});
};
function convertToExpected(arg) {
if(arg.length) {
return convertExpectedFromArray(arg);
}
return arg;
}
function convertExpectedFromArray(arg) {
var start = 0;
if(arg.length == 4) {
start = 1
}
var obj = {
sign: arg.length == 4 ? arg[0] : '',
label: arg[start++],
bin: arg[start++],
other: arg[start++]
}
// console.log('convert: ' + JSON.stringify(arg) + " to " + JSON.stringify(obj));
return obj;
}
module.exports = {
BitwiseCmdPage : BitwiseCmdPage,
ExpressionResultObject: ExpressionResultObject
};

View File

@@ -1,56 +0,0 @@
var promise = protractor.promise;
var resultTableReader = {
read: function (table) {
var self = this;
return table.findElements(By.tagName('tr'))
.then(function(rows) {
var promises = [];
for (var i = 0; i < rows.length; i++) {
promises.push(self.readRow(rows[i]));
}
return promise.all(promises);
});
},
readRow: function (row) {
var def = promise.defer();
var dataList = [];
var promises = [];
var self = this;
row.findElements(By.tagName('td')).then(function(cols) {
for(var i=0; i<cols.length;i++){
promises.push(self.readColumn(cols[i]).then(function(colData) { dataList.push(colData); }))
}
});
promise.all(promises).then(function() {
var rowObj = {}, colObj;
for(var i=0; i<dataList.length; i++ ){
colObj = dataList[i];
rowObj[colObj.className] = colObj.text;
}
def.fulfill(rowObj);
});
return def.promise;
},
readColumn: function(col) {
var def = promise.defer();
var colData = {};
var promises = [];
promises.push(col.getAttribute('class').then(function(className) { colData.className = className; }))
promises.push(col.getText().then(function(text) { colData.text = text; }))
promise.all(promises).then(function () {
def.fulfill(colData);
});
return def.promise;
}
};
module.exports = resultTableReader;

View File

@@ -1,242 +0,0 @@
browser.ignoreSynchronization = true;
var pageObject = require('./pageObject.js')
var BitwiseCmdPage = pageObject.BitwiseCmdPage;
var By = protractor.By;
var driver = browser.driver;
var appUrl = browser.params.appUrl || 'http://localhost:63342/BitwiseCmd/src/#clear';
var sutPage = new BitwiseCmdPage(driver, appUrl);
describe('when application starts', function() {
it('should have title', function() {
sutPage.goToApp().then(function() {
expect(driver.getTitle()).toEqual('BitwiseCmd');
});
});
it('should have no errors upon loading', function() {
return sutPage.shouldHaveNoErrors();
});
it('should execute clear command', function() {
sutPage.clearResults().then(function () {
return driver.findElements(By.css('.result')).then(function(list) {
expect(list.length).toBe(0, "There should be no results after clear");
});
});
});
it('should execute list of commands without errors', function() {
sutPage.goToApp()
.then(function() { return sutPage.executeExpression('clear')})
.then(function() { return sutPage.executeExpression('1')})
.then(function() { return sutPage.executeExpression('1|2')})
.then(function() { return sutPage.executeExpression('1^2')})
.then(function() { return sutPage.executeExpression('1^0b10')})
.then(function() { return sutPage.executeExpression('0x1>>>0xf')})
.then(function() { return sutPage.executeExpression('0x1 0xf')})
.then(function() { return sutPage.executeExpression('0x1 | 0xf')})
.then(function() { return sutPage.executeExpression('0x1 ^ 123')})
.then(function() { return sutPage.executeExpression('1|2&3|5 |5')})
.then(function() { return sutPage.executeExpression('dark')})
.then(function() { return sutPage.executeExpression('light')})
.then(function() { return sutPage.executeExpression('midnight')})
.then(function() { return sutPage.executeExpression('guid')})
.then(function() { return sutPage.shouldHaveNoErrors(); });
});
it('should execute list of numbers', function() {
assertOperation('3 0xf 0b101',
[{ label: '3', bin:'00000011', other: '0x3'},
{ label: '0xf', bin:'00001111', other: '15'},
{ label: '5', bin: '00000101', other: '0x5' }]);
});
it('should do a shift operation', function() {
return assertOperation('1<<1',
[{ label: '1', bin:'00000001', other: '0x1'},
{ sign:'<<1', label: '2', bin:'00000010', other: '0x2'}])
});
it('should do a ignore sign RIGHT shift operation', function() {
return assertOperation('-1>>>1',
[{ label: '-1', bin:'11111111111111111111111111111111', other: '-0x1'},
{ sign: '>>>1', label: '2147483647', bin:'01111111111111111111111111111111', other: '0x7fffffff'}])
});
it('should do NOT operation', function() {
return assertOperation('~1',
[{ sign: '~', label: '1', bin:'00000000000000000000000000000001', other: '0x1'},
{ sign: '=', label: '-2', bin:'11111111111111111111111111111110', other: '-0x2'}])
});
it('should execute multiple expressions from hash arguments', function() {
return sutPage.goToApp("16,15||16&15")
.then(function() { return driver.navigate().refresh(); })
.then(function() { return sutPage.shouldHaveNoErrors(); })
.then(function() {
return assertMultipleExpressionResults([
//16&15
[{ label: '16', bin:'00010000', other: '0x10'},
{ sign:'&', label: '15', bin:'00001111', other: '0xf'},
{ sign:'=', label: '0', bin:'00000000', other: '0x0'}],
//16 15
[{ label: '16', bin:'00010000', other: '0x10'},
{ label: '15', bin:'00001111', other: '0xf'}]
])
})
});
it('should do OR operation', function() {
return assertOperation('1|2',
[{ label: '1', bin:'00000001', other: '0x1'},
{ sign: '|', label: '2', bin:'00000010', other: '0x2'},
{ sign: '=', label: '3', bin:'00000011', other: '0x3'}])
});
it('should do multiple operand expression', function() {
// actual { sign: '', label: '1', bin: '00000000000000000000000000000001', other: '0x1' }
// expected { sign: '', label: '1', bin: '0000000000000000000000000000001', other: '0x1' }
return assertOperation(
'1|2|4<<0x1|~2',
[
[ '1', '00000000000000000000000000000001', '0x1'],
[ '|', '2', '00000000000000000000000000000010', '0x2'],
[ '=', '3', '00000000000000000000000000000011', '0x3'],
[ '|', '4', '00000000000000000000000000000100', '0x4'],
[ '=', '7', '00000000000000000000000000000111', '0x7'],
[ '<<0x1','0xe', '00000000000000000000000000001110', '14' ],
[ '|', '~2', '11111111111111111111111111111101', '-3'],
[ '=', '-1', '11111111111111111111111111111111', '-0x1']
]);
});
it('should support nested not operations', function() {
return assertOperation(
'~3|~213&~12^~223',
[
['~', '3', '00000000000000000000000000000011', '0x3'],
['=', '-4', '11111111111111111111111111111100', '-0x4'],
['|', '~213', '11111111111111111111111100101010', '-214'],
['=', '-2', '11111111111111111111111111111110', '-0x2'],
['&', '~12', '11111111111111111111111111110011', '-13'],
['=', '-14', '11111111111111111111111111110010', '-0xe'],
['^', '~223', '11111111111111111111111100100000', '-224'],
['=', '210', '00000000000000000000000011010010', '0xd2']
]
)
})
it('should do or for binary numbers', function() {
return assertOperation('0b10|0b11',
[[ "2", "00000010", "0x2"],
["|", "3", "00000011", "0x3"],
["=", "3", "00000011", "0x3"]]
);
})
it('should do prefer hex result', function() {
return assertOperation('1|0x2',
[{ label: '1', bin:'00000001', other: '0x1'},
{ sign: '|', label: '0x2', bin:'00000010', other: '2'},
{ sign: '=', label: '0x3', bin:'00000011', other: '3'}])
});
// TODO: temporary disabled due to false positive on prod
xit('should create hashlink', function() {
var expression = '1|0x2';
var expected = [{ label: '1', bin:'00000001', other: '0x1'},
{ sign: '|', label: '0x2', bin:'00000010', other: '2'},
{ sign: '=', label: '0x3', bin:'00000011', other: '3'}];
return assertOperation(expression, expected).then(function(){
return driver.findElement(By.css('.hashLink'));
}).then(function(el) {
return el.getAttribute('href');
}).then(function(hrefUrl) {
console.log('haslink url: ' + hrefUrl);
return driver.get(hrefUrl); // TODO: temp solution. Need to implement better tracking handling logic
}).then(function() {
return driver.findElements(By.css('.result'));
}).then(function(list) {
expect(list.length).toBeGreaterThan(0);
return assertExpressionResult(expected);
});
});
it('should emphasize bytes', function() {
sutPage.goToApp()
.then(function() { return sutPage.executeExpression('1')})
.then(function() {
return assertExpressionResult([{ label: '1', bin:'00000001', other: '0x1'}])
})
.then(function() { return sutPage.executeExpression('clear')})
.then(function() { return sutPage.executeExpression('em')})
.then(function() { return sutPage.shouldHaveNoErrors(); })
.then(function() { return sutPage.executeExpression('1 3')})
.then(function() {
return assertExpressionResult([{ label: '1', bin:'01', other: '0x1'}, { label: '3', bin:'11', other: '0x3'}])
});
});
});
describe('interaction with results', function() {
it('should flip bits', function () {
// Given: 0x2a 00101010 42
// Expected: 0x6a 01101010 106
sutPage.goToApp()
.then(function() { return sutPage.executeExpression('0x2a')})
.then(function() { assertExpressionResult([{ label: "0x2a", bin: '00101010', other: '42' }]); })
.then(function() { return flipBit(2); })
.then(function() { return assertExpressionResult([{ label: "0x6a", bin: '01101010', other: '106' }]); })
.then(function() { return flipBit(6); })
.then(function() { return assertExpressionResult([{ label: "0x6e", bin: '01101110', other: '110' }]); });
})
});
function assertMultipleExpressionResults(array) {
return sutPage.getAllResults().then(function(results){
var all= null, cur;
for(var i=0; i<results.length;i++) {
var expected = array[i];
cur = results[i].shouldBe(expected);
all = all == null ? cur : all.then(cur);
}
return all;
});
}
function assertExpressionResult(expected) {
return sutPage.getLasExpressionResult().then(function(result){
result.shouldBe(expected);
});
}
function assertOperation(op, expected) {
console.log('\n' + op);
return sutPage.executeExpression(op)
.then(function() { return sutPage.shouldHaveNoErrors(); })
.then(function() {
return assertExpressionResult(expected)
});
}
function flipBit(bitNumber) {
return driver.findElements(By.css('.flipable'))
.then(function(rows) {
var bitElemet = rows[bitNumber-1]; // Flip 2nd bit;
return bitElemet.click();
});
}

View File

@@ -1,143 +0,0 @@
var expression = require('../../src/app/expression');
var parser = expression.parser;
describe("expression parser", function() {
var shouldParse = ['0x2>>1', '1 2 3', '0x1 1 2 3 5', '0x1>>0x2', '1|2', '-9', '-1|-2', '~3'];
it("should be able to parse", function() {
shouldParse.forEach(function(expr) {
expect(parser.canParse(expr)).toBe(true, 'expr: ' + expr);
})
});
var expressionCases = {
"0x2>>1": { operand: 2, operand2:1, "sign":">>", string:"0x2>>1" },
"123|111": { operand: 123, operand2:111, "sign":"|", string: "123|111" },
"23^0x1": { operand: 23, operand2:1, "sign":"^", string: "23^0x1" },
"0xf>>0xa": { operand: 15, operand2:10, "sign":">>", string:"0xf>>0xa" },
"0x10&0x11": { operand: 0x10, operand2:0x11, "sign":"&", string:"0x10&0x11" },
"0x1a^11": { operand: 0x1a, operand2:11, "sign":"^", string:"0x1a^11" },
"0x1a>>>11": { operand: 0x1a, operand2:11, "sign":">>>", string:"0x1a>>>11" },
'~3': { operand: 3, operand2: null, "sign":"~", string:"~3" },
'~0xa': { operand: 0xa, operand2: null, "sign":"~", string:"~0xa" },
'~-0xa': { operand: -0xa, operand2: null, "sign":"~", string:"~-0xa" }
};
// TODO: update to support multiple expressions
xit("should parse expressions", function() {
for(input in expressionCases) {
console.log('case: ' + input);
var actual = parser.parse(input);
var expected = expressionCases[input];
console.log('actual:' + actual.toString());
// expect(actual).toBeDefined();
// expect(actual).not.toBe(null);
// expect(actual.sign).toBe(expected.sign);
// expect(actual.operand.value).toBe(expected.operand);
// if(expected.operand2 != null) {
// expect(actual.operand2.value).toBe(expected.operand2);
// }
// else
// {
// expect(actual.operand2).not.toBeDefined();
// }
// expect(actual.expressionString).toBe(expected.string);
// console.log(actual.toString());
}
});
var listCases = {
'-0xa -9' : [-10, -9],
'1 2 3': [1, 2, 3],
'0x1 2 0xa6b': [0x1, 2, 0xa6b],
'0x11a': [0x11a]
};
it("should parse hexadecimal expressions", function() {
var input, i;
for(input in listCases) {
var actual = parser.parse(input);
var expected = listCases[input];
for(i =0; i<expected.length;i++) {
expect(actual.numbers[i].value).toBe(expected[i]);
expect(actual.expressionString).toBe(input)
}
}
});
it ("should parse multiple operands expression", function () {
var actual = parser.parse("1|2&3");
})
});
describe('parse operands', function() {
var hexOperand = parser.parseOperand('0x10');
var decOperand = parser.parseOperand('10');
rundOperandsTest(hexOperand, decOperand);
});
describe('create operands', function() {
var hexOperand = parser.createOperand(0x10, 'hex');
var decOperand = parser.createOperand(10, 'dec');
rundOperandsTest(hexOperand, decOperand);
});
describe('negative operands', function () {
var op = parser.parseOperand('-0xa');
it('shoold have correct values', function() {
expect(op.value).toBe(-10);
expect(op.toHexString()).toBe('-0xa');
expect(op.toBinaryString()).toBe('11111111111111111111111111110110');
expect(op.toDecimalString()).toBe('-10');
expect(op.kind).toBe('hex');
})
});
describe('should format to kind strings', function() {
var dec = expression.Operand.toKindString(15, 'dec'),
hexNegative = expression.Operand.toKindString(-2, 'hex'),
hex = expression.Operand.toKindString(11, 'hex'),
bin = expression.Operand.toKindString(10, 'bin');
it('should be correctly formatted', function() {
expect(dec).toBe('15');
expect(hexNegative).toBe('-0x2');
expect(hex).toBe('0xb');
expect(bin).toBe('1010')
});
});
function rundOperandsTest(hexOperand, decOperand) {
it('should remember input form', function() {
expect(hexOperand.input).toBe('0x10');
expect(decOperand.input).toBe('10');
});
it('should return integer value', function () {
expect(hexOperand.value).toBe(0x10);
expect(decOperand.value).toBe(10);
});
it('should have all kinds', function () {
expect(hexOperand.kind).toBe('hex');
expect(hexOperand.toDecimalString()).toBe('16');
expect(hexOperand.toBinaryString()).toBe('10000');
expect(hexOperand.toHexString()).toBe('0x10');
expect(hexOperand.toOtherKindString()).toBe('16');
expect(decOperand.kind).toBe('dec');
expect(decOperand.toDecimalString()).toBe('10');
expect(decOperand.toBinaryString()).toBe('1010');
expect(decOperand.toHexString()).toBe('0xa');
expect(decOperand.toOtherKindString()).toBe('0xa');
});
}

View File

@@ -1,20 +0,0 @@
var formatter = require('../../src/app/formatter');
describe('expression formatter', function () {
xit('should format number to binary by default', function() {
expect(formatter.formatString(10)).toBe("1010");
});
xit('should format number hexadecimal', function() {
expect(formatter.formatString(15, 'hex')).toBe("f");
});
xit('should format number decimal', function() {
expect(formatter.formatString(16, 'dec')).toBe('16');
});
xit('should respect padding', function() {
expect(formatter.padLeft("a", 6)).toBe("00000a");
});
});

View File

@@ -1,39 +0,0 @@
var hash = require('../../src/app/hash').default;
describe('hash arguments parser', function() {
it('should parse empty', function() {
var args = hash.getArgs('');
expect(args).not.toBe(null);
expect(args).toBeDefined();
expect(args.commands).toEqual([]);
});
it('should parse single command', function() {
var args = hash.getArgs('#cmd');
expect(args).not.toBe(null);
expect(args).toBeDefined();
expect(args.commands).toEqual(['cmd']);
});
it('should parse multiple commands', function() {
var args = hash.getArgs('#1|2||1^2||~2');
expect(args).not.toBe(null);
expect(args).toBeDefined();
expect(args.commands).toEqual(['1|2', '1^2', '~2']);
});
it('should parse multiple commands with clear', function() {
var args = hash.getArgs('#clear||16,15||16&15');
expect(args).not.toBe(null);
expect(args).toBeDefined();
expect(args.commands).toEqual(['clear', '16 15', '16&15']);
});
it('should parse multiple commands encoded', function() {
var args = hash.getArgs('#' + encodeURI('1|2||1^2||~2||-notrack||-debug'));
expect(args).not.toBe(null);
expect(args).toBeDefined();
expect(args.commands).toEqual(['1|2', '1^2', '~2','-notrack','-debug']);
});
});

View File

@@ -1,18 +0,0 @@
var expression = require('../../src/app/expression');
var parser = expression.parser;
describe("operand", function() {
it("should be able to create opearand from positive binary string", function() {
var input = "0b10";
var op = new expression.Operand.parse(input);
expect(op.value).toBe(2);
expect(op.kind).toBe('bin');
});
it("should be able to create opearand from negative binary string", function() {
var input = "-0b10";
var op = new expression.Operand.parse(input);
expect(op.value).toBe(-2);
expect(op.kind).toBe('bin');
})
});

View File

@@ -1,11 +0,0 @@
// var expression = require('../../src/app/expression');
// var parser = new expression.Parser();
// describe("Parser", function() {
// it("should be able to parse from start", function() {
// var sut = new expression.Parser("test", 0);
// sut.parse();
// console.log(sut);
// });
// });

View File

@@ -1,2 +1,2 @@
Unparsable expressions:
~2|11
- Bug: ~1|~2
- add Firefox tests

View File

@@ -1,14 +1,25 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react"
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"files": [
"./src.old/components/Hello.tsx",
"./src.old/index.tsx"
]
"include": [
"src"
, "public/analytics.js" ]
}

View File

@@ -1,29 +0,0 @@
module.exports = {
entry: __dirname + "/src/app/index.jsx",
output: {
filename: 'bundle.js',
path: __dirname + '/src',
publicPath: 'http://localhost:8080/'
},
devtool: 'source-maps',
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node-modules/,
//output: { path: __dirname + '/src', filename: 'bundle.js' },
query: {
presets: [require.resolve('babel-preset-es2015'), require.resolve('babel-preset-react')],
plugins: [require.resolve('babel-plugin-transform-class-properties')]
},
}]
},
// externals: {
// //don't bundle the 'react' npm package with our bundle.js
// //but get it from a global 'React' variable
// 'react': 'React'
// },
resolve: {
extensions: ['.js', '.jsx']
}
};