From e35b87f668791d0d3c672ab0c6001265d6b3c7ed Mon Sep 17 00:00:00 2001 From: Borys_Levytskyi Date: Sun, 20 Nov 2016 23:01:20 +0200 Subject: [PATCH] Support of help and clear commands. Add view models --- package.json | 2 + src.old/js/app/cmd/cmd.js | 8 +- src.old/js/app/cmd/commands.js | 36 ----- src/app/appState.js | 35 +++++ src/app/cmd.js | 91 ++++++++++++ src/app/commands.js | 44 ++++++ src/app/components/AppRoot.jsx | 55 ++++++++ src/app/components/HelpView.jsx | 45 ++++++ src/app/components/InputBox.jsx | 15 +- src/app/expression.js | 237 ++++++++++++++++++++++++++++++++ src/app/index.jsx | 37 +---- src/app/is.js | 33 +++++ src/app/result/result.js | 2 + webpack.config.js | 10 +- 14 files changed, 570 insertions(+), 80 deletions(-) create mode 100644 src/app/appState.js create mode 100644 src/app/cmd.js create mode 100644 src/app/commands.js create mode 100644 src/app/components/AppRoot.jsx create mode 100644 src/app/components/HelpView.jsx create mode 100644 src/app/expression.js create mode 100644 src/app/is.js create mode 100644 src/app/result/result.js diff --git a/package.json b/package.json index ed0482e..cb9aed4 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "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", diff --git a/src.old/js/app/cmd/cmd.js b/src.old/js/app/cmd/cmd.js index fae49ce..c4949a1 100644 --- a/src.old/js/app/cmd/cmd.js +++ b/src.old/js/app/cmd/cmd.js @@ -6,12 +6,13 @@ app.set('cmd', function() { var cmdController = app.controller('cmdController'); return { + debugMode: true, execute: function(rawInput) { var input = rawInput.trim().toLowerCase(); var handler = findHandler(input); if(handler != null) { - if(app.debugMode) { + if(this.debugMode) { invokeHandler(input, handler); } else { try { @@ -52,13 +53,12 @@ app.set('cmd', function() { handlers.push(h); }, clear: function() { - cmdController.clear(); + console.error('[displayCommandError] not implemented'); } }; function displayCommandError(input, message) { - var error = new app.models.ErrorResult(message); - cmdController.display(new app.models.DisplayResult(input, error)); + console.error('[displayCommandError] not implemented'); } function invokeHandler (input, handler) { diff --git a/src.old/js/app/cmd/commands.js b/src.old/js/app/cmd/commands.js index 434ee6a..661b1b8 100644 --- a/src.old/js/app/cmd/commands.js +++ b/src.old/js/app/cmd/commands.js @@ -6,42 +6,6 @@ app.run(function() { var rootView = app.get('rootView'); var expression = app.get('expression'); - cmd.commands({ - 'help': function() { - var helpResult = document.querySelector('.result .helpResultTpl'); - if(helpResult != null) { - moveResultUp(helpResult); - return; - } - return new app.models.ViewResult('helpResultTpl'); - }, - 'clear': function() { - cmd.clear(); - }, - 'em': function() { - cmdConfig.emphasizeBytes = !cmdConfig.emphasizeBytes; - }, - 'dark': function() { - cmdConfig.theme = 'dark'; - }, - light: function () { - cmdConfig.theme = 'light'; - }, - about: function() { - var aboutResult = document.querySelector('.result .aboutTpl'); - if(aboutResult != null) { - moveResultUp(aboutResult); - return; - } - return new app.models.ViewResult('aboutTpl'); - }, - '-debug': function() { - app.debugMode = true; - console.log('debug is on'); - }, - '-notrack': function () {} - }); - // TODO: Make as function cmd.command({ canHandle: function(input) { return app.get('expression').canParse(input); }, diff --git a/src/app/appState.js b/src/app/appState.js new file mode 100644 index 0000000..7c98587 --- /dev/null +++ b/src/app/appState.js @@ -0,0 +1,35 @@ +class AppState { + constructor() { + this.emphasizeBytes = true; + this.commandResults = []; + this.handlers = []; + } + + addCommandResult(result) { + this.commandResults.unshift(result); + this.triggerChanged(); + } + + clearCommmandResults() { + 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(); + } + } +}; + +var appState = new AppState(); +export default appState; diff --git a/src/app/cmd.js b/src/app/cmd.js new file mode 100644 index 0000000..8df297d --- /dev/null +++ b/src/app/cmd.js @@ -0,0 +1,91 @@ +import is from './is'; + + var handlers = []; + +var cmd = { + debugMode: true, + 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) { + displayCommandError(input, "Error: " + e); + } + } + } + else { + displayCommandError(input, "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'); + } + }; + + function displayCommandError(input, message) { + var error = new app.models.ErrorResult(message); + cmdController.display(new app.models.DisplayResult(input, error)); + } + + function invokeHandler (input, handler) { + console.log('[invokeHandler]: ' + input); + var cmdResult = handler.handle(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 this.refresh()); + } + refresh() { + console.log(this.props.appState.commandResults); + this.setState(this.props.appState); + } + render() { + var results = this.state.commandResults.map((r, i) => this.findResultComponent(r, i)); + return
+
+

BitwiseCmd

+
State: {JSON.stringify(this.state)}
+ +
+ +
+ + + + [em] + +
+ +
+ {results} +
+
; + } + + findResultComponent(result, key) { + if(result instanceof results.HelpResult) { + return + } + + return Unknown result {typeof result} + } +} \ No newline at end of file diff --git a/src/app/components/HelpView.jsx b/src/app/components/HelpView.jsx new file mode 100644 index 0000000..5279821 --- /dev/null +++ b/src/app/components/HelpView.jsx @@ -0,0 +1,45 @@ +import React from 'react' + +export default class HelpView extends React.Component { + render() { + return
+
+
+

+ Supported Commands +

    +
  • 23 ^ 34 — type bitwise expression to see result in binary (only positive integers are supported now)
  • +
  • 23 34 — type one or more numbers to see their binary representations
  • +
  • clear — clear output pane
  • +
  • help — display this help
  • +
  • em — turn On/Off Emphasize Bytes
  • +
  • dark — set Dark theme
  • +
  • light — set Light theme
  • +
  • about — about the app
  • +
+

+
+
+

+ Supported Bitwise Operations
+ + + as implemented in JavaScript engine + + +

    +
  • & — bitwise AND
  • +
  • | — bitwise inclusive OR
  • +
  • ^ — bitwise exclusive XOR
  • +
  • ~ — bitwise NOT
  • +
  • << — left shift
  • +
  • >> — sign propagating right shift
  • +
  • >>> — zero-fill right shift
  • + +
+

+
+
+
; + } +} \ No newline at end of file diff --git a/src/app/components/InputBox.jsx b/src/app/components/InputBox.jsx index bea2e37..a32aa70 100644 --- a/src/app/components/InputBox.jsx +++ b/src/app/components/InputBox.jsx @@ -1,8 +1,13 @@ import React from 'react'; +import cmd from '../cmd'; export default class InputBox extends React.Component { - history = []; - historyIndex = 0; + constructor() { + super(); + this.history = []; + this.historyIndex = 0; + } + render() { return this.onKeyUp(e)} @@ -18,10 +23,10 @@ export default class InputBox extends React.Component { } var value = input.value; - console.log(value); this.history.unshift(value); - input.value = ''; - console.log(this.history); + + input.value = ''; + cmd.execute(value); } onKeyDown(args) { diff --git a/src/app/expression.js b/src/app/expression.js new file mode 100644 index 0000000..541df1a --- /dev/null +++ b/src/app/expression.js @@ -0,0 +1,237 @@ +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 0) { + numbers.push(new Operand(n.trim())); + } + }); + + return new ListOfNumbersExpression(input, numbers); + } + }); + + // Not Expression + expression.addFactory({ + regex: /^(~)(-?(?:\d+|0x[\d,a-f]+))$/, + canCreate: function(string) { + return this.regex.test(string); + }, + create: function (string) { + var matches = this.regex.exec(string), + operand = new Operand(matches[2]); + + return new SingleOperandExpression(matches.input, operand, matches[1]); + } + }); + + // Multiple operands expression + expression.addFactory({ + fullRegex: /^((<<|>>|>>>|\||\&|\^)?(-?((?:\d+(?!x))|(?:0x[\d,a-f]+))))+$/, + regex: /(<<|>>|>>>|\||\&|\^)?(-?((?:\d+(?!x))|(?:0x[\d,a-f]+)))/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) { + var input = m[0], + sign = m[1], + num = m[2]; + + if(sign == null) { + return new Operand(num); + } else { + return new SingleOperandExpression(input, new Operand(num), sign); + } + }, + normalizeString: function (string) { + return string.replace(/\s+/g,''); + } + }); + +export class Operand { + constructor(input) { + this.input = input; + this.value = parseInt(input); + this.hex = Operand.toHexString(this.value.toString(16)); + this.dec = this.value.toString(10); + // >>> 0 makes negative numbers like -1 to be displayed as '11111111111111111111111111111111' in binary instead of -1 + this.bin = this.value < 0 ? (this.value >>> 0).toString(2) : this.value.toString(2); + this.kind = this.input.indexOf('0x') > -1 ? 'hex' : 'dec'; + this.other = this.kind == 'dec' ? this.hex : this.dec; + } + + toHexString (hex) { + return hex.indexOf('-') == 0 ? '-0x' + hex.substr(1) : '0x' + hex; + }; + + getLengthInBits() { + if(this.value < 0) { + return 32; + } + return Math.floor(Math.log(this.value) / Math.log(2)) + 1; + }; + + 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) + } + }; + + getOtherKind(kind) { + switch(kind) { + case 'dec': return 'hex'; + case 'hex': return 'dec'; + default : throw new Error(kind + " kind doesn't have opposite kind") + } + }; + + static getBase(kind){ + switch (kind){ + case 'bin': return 2; + case 'hex': return 16; + case 'dec': return 10; + } + }; + + static create(number, kind) { + var str = number.toString(Operand.getBase(kind)); + if(kind == 'hex') { + str = Operand.toHexString(str); + } + + return new Operand(str); + }; + + toString() { + return this.input; + } +} + +export class SingleOperandExpression { + constructor(expressionString, operand, sign) { + this.expressionString = expressionString; + this.operand1 = operand; + this.sign = sign; + } + + apply(value) { + var str = ''; + if(this.sign == '~'){ + str = '~' + this.operand1.value; + } else { + str = value + this.sign + this.operand1.value + } + + return Operand.create(eval(str), this.operand1.kind); + }; + + isShiftExpression() { + return this.sign.indexOf('<') >= 0 || this.sign.indexOf('>')>= 0; + }; + + toString() { + return this.sign + this.operand1.toString(); + } +} + +export class TwoOperandExpression { + constructor(expressionString, operand1, operand2, sign) { + this.expressionString = expressionString; + this.operand1 = operand1; + this.operand2 = operand2; + this.sign = sign; + } +} + +export class MultipleOperandsExpression { + constructor(expressionString, expressions) { + this.expressionString = expressionString; + this.expressions = expressions; + } +} + +export class ListOfNumbersExpression { + constructor(expressionString, numbers) { + this.expressionString = expressionString; + this.numbers = numbers; + } +} + +export class Expression { + toString() { + return this.expressionString ? "Expression: " + this.expressionString : this.toString(); + }; +} + + + +export default expression; \ No newline at end of file diff --git a/src/app/index.jsx b/src/app/index.jsx index bac32f0..d7ee281 100644 --- a/src/app/index.jsx +++ b/src/app/index.jsx @@ -1,35 +1,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import InputBox from './components/InputBox.jsx'; -var rootView =
-
-

BitwiseCmd

+import appState from './appState'; +import cmd from './cmd'; +import commands from './commands'; +import AppRoot from './components/AppRoot'; - -
+commands.initialize(cmd); -
- - - - [em] - -
- -
-
- -
; - - -ReactDOM.render(rootView, document.getElementById('root')); \ No newline at end of file +var root = ; +ReactDOM.render(root, document.getElementById('root')); \ No newline at end of file diff --git a/src/app/is.js b/src/app/is.js new file mode 100644 index 0000000..15c9960 --- /dev/null +++ b/src/app/is.js @@ -0,0 +1,33 @@ +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) + } +} \ No newline at end of file diff --git a/src/app/result/result.js b/src/app/result/result.js new file mode 100644 index 0000000..ac0c209 --- /dev/null +++ b/src/app/result/result.js @@ -0,0 +1,2 @@ +export class HelpResult { +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index ef24c75..04d5aa4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,14 +8,14 @@ module.exports = { devtool: 'source-maps', module: { loaders: [{ - test: /\.jsx$/, + test: /\.jsx?$/, loader: 'babel', - //exclude: /node-modules/, + exclude: /node-modules/, output: { path: __dirname + '/src', filename: 'bundle.js' }, query: { - presets: ['es2015', 'react'], - plugins: ['transform-class-properties'] - } + presets: [require.resolve('babel-preset-es2015'), require.resolve('babel-preset-react')], + plugins: [require.resolve('babel-plugin-transform-class-properties')] + }, }] }, // externals: {