├── app ├── robots.txt ├── favicon.ico ├── images │ └── github_ribbon.png ├── scripts │ ├── payload-sources.js │ ├── actions │ │ ├── ParserActionCreator.js │ │ ├── ActionTypes.js │ │ ├── UserInputActionCreator.js │ │ ├── CompiledGrammarActionCreator.js │ │ ├── GrammarActionCreator.js │ │ └── ParsedTextActionCreator.js │ ├── util │ │ ├── util.js │ │ ├── constants.js │ │ └── sample-grammars │ │ │ ├── toy-english.jison │ │ │ ├── happy.jison │ │ │ ├── calculator.jison │ │ │ ├── chords.jison │ │ │ └── bloop.jison │ ├── ui │ │ ├── GrammarOutput │ │ │ ├── GrammarErrorView.js │ │ │ ├── GrammarView.js │ │ │ └── GrammarSuccessView.js │ │ ├── ParserOutput │ │ │ ├── LexErrorView.js │ │ │ ├── ParsedResultView.js │ │ │ ├── ParseTreeRawView.js │ │ │ ├── ParseTreeView.js │ │ │ ├── ParserOutputView.js │ │ │ ├── LexOutputView.js │ │ │ └── ParseTreeGraphView.js │ │ ├── Editor │ │ │ ├── Editor.js │ │ │ ├── GrammarEditor.js │ │ │ ├── TextToParseView.js │ │ │ └── SampleGrammarLoader.js │ │ └── util │ │ │ ├── Colorizer.js │ │ │ ├── TreeBuilder.js │ │ │ └── TreeArtist.js │ ├── dispatcher.js │ ├── data │ │ ├── GrammarWorkerService.js │ │ └── ParserWorkerService.js │ ├── worker │ │ └── grammar-worker.js │ ├── stores │ │ ├── GrammarInputStore.js │ │ ├── GrammarOutputStore.js │ │ └── ParserOutputStore.js │ └── app.js ├── transforms │ └── jison-to-textify.js ├── index.html └── styles │ └── main.scss ├── .bowerrc ├── .gitignore ├── bin ├── build-jison.sh └── deploy.sh ├── bower.json ├── .editorconfig ├── .jshintrc ├── README.md ├── package.json ├── gulpfile.js └── LICENSE /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nolanlawson/jison-debugger/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .sass-cache/ 4 | bower_components 5 | app/scripts/worker/jison.js 6 | -------------------------------------------------------------------------------- /app/images/github_ribbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nolanlawson/jison-debugger/HEAD/app/images/github_ribbon.png -------------------------------------------------------------------------------- /app/scripts/payload-sources.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | VIEW_ACTION: 'VIEW_ACTION', 5 | WORKER_ACTION: 'WORKER_ACTION' 6 | }; 7 | -------------------------------------------------------------------------------- /bin/build-jison.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # build up the version of jison.js used in the web worker 4 | cd node_modules/jison 5 | npm install 6 | cd - 7 | ./node_modules/.bin/browserify --exports require node_modules/jison/entry.js > app/scripts/worker/jison.js 8 | -------------------------------------------------------------------------------- /bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -fr dist 4 | gulp clean build 5 | 6 | git checkout -b build 7 | mv dist/* . 8 | git add -A 9 | git commit -a -m 'build' 10 | git push --force origin build:gh-pages 11 | git checkout - 12 | git branch -D build 13 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jison-debugger", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "jquery": "~2.1.1", 6 | "bootstrap-sass-official": ">=3.3.0", 7 | "modernizr": "^2.8.3", 8 | "blob-util": "~1.1.1", 9 | "d3": "~3.5.5" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/transforms/jison-to-textify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through2'); 4 | 5 | module.exports = function (file) { 6 | if (!/\.jison$/.test(file)) { 7 | return through(); 8 | } 9 | 10 | return through(function (buf, enc, next) { 11 | var contents = buf.toString('utf8'); 12 | 13 | this.push('module.exports = ' + JSON.stringify(contents) + ';'); 14 | next(); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "strict": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "white": true, 20 | "browser": true 21 | } 22 | -------------------------------------------------------------------------------- /app/scripts/actions/ParserActionCreator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AppDispatcher = require('../dispatcher'); 4 | var ActionTypes = require('./ActionTypes'); 5 | var ParserWorkerService = require('../data/ParserWorkerService'); 6 | 7 | module.exports = { 8 | parseText(text) { 9 | AppDispatcher.handleViewAction({ 10 | type: ActionTypes.PARSE_TEXT, 11 | textToParse: text 12 | }); 13 | ParserWorkerService.parseText(text); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /app/scripts/actions/ActionTypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | COMPILE_GRAMMAR: 'COMPILE_GRAMMAR', 5 | GRAMMAR_COMPILED: 'GRAMMAR_COMPILED', 6 | GRAMMAR_ERRORED: 'GRAMMAR_ERRORED', 7 | PARSE_TEXT: 'PARSE_TEXT', 8 | TEXT_PARSED: 'TEXT_PARSED', 9 | TEXT_ERRORED: 'TEXT_ERRORED', 10 | TEXT_TO_PARSE_CHANGED: 'TEXT_TO_PARSE_CHANGED', 11 | GRAMMAR_CHANGED: 'GRAMMAR_CHANGED', 12 | GRAMMAR_CHANGED_SIGNIFICANTLY: 'GRAMMAR_CHANGED_SIGNIFICANTLY' 13 | }; 14 | -------------------------------------------------------------------------------- /app/scripts/actions/UserInputActionCreator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AppDispatcher = require('../dispatcher'); 4 | var ActionTypes = require('./ActionTypes'); 5 | 6 | module.exports = { 7 | updateTextToParse(text) { 8 | AppDispatcher.handleViewAction({ 9 | type: ActionTypes.TEXT_TO_PARSE_CHANGED, 10 | textToParse: text 11 | }); 12 | }, 13 | updateGrammar(grammar) { 14 | AppDispatcher.handleViewAction({ 15 | type: ActionTypes.GRAMMAR_CHANGED, 16 | grammar: grammar 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /app/scripts/util/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function debounce() { 4 | var timeout; 5 | return function debounce(delay, fun) { 6 | if (timeout) { 7 | clearTimeout(timeout); 8 | } 9 | timeout = setTimeout(function () { 10 | fun(); 11 | timeout = null; 12 | }, delay); 13 | }; 14 | } 15 | 16 | function safeStringify(obj, arg1, arg2) { 17 | try { 18 | return JSON.stringify(obj, arg1, arg2); 19 | } catch (err) { 20 | return obj.toString(); 21 | } 22 | } 23 | 24 | module.exports = { 25 | debounce: debounce, 26 | safeStringify: safeStringify 27 | }; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jison-debugger [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | Web UI for debugging [Jison](https://github.com/zaach/jison) grammars, written in React.js. 4 | 5 | Setup: `npm install && bower install` 6 | 7 | Developing: use `gulp watch`. This was created with [a Yeoman generator](https://github.com/randylien/generator-react-gulp-browserify). 8 | 9 | Recompiling Jison: `npm run build-jison`. I'm using a special build of Jison for debugging. 10 | 11 | Deploying to gh-pages: use `npm run deploy`. It will build the project and push to the `gh-pages` branch. 12 | -------------------------------------------------------------------------------- /app/scripts/actions/CompiledGrammarActionCreator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AppDispatcher = require('../dispatcher'); 4 | var ActionTypes = require('./ActionTypes'); 5 | 6 | module.exports = { 7 | grammarErrored: function (error) { 8 | AppDispatcher.handleWorkerAction({ 9 | type: ActionTypes.GRAMMAR_ERRORED, 10 | error: error 11 | }); 12 | }, 13 | grammarCompiled: function (data) { 14 | AppDispatcher.handleWorkerAction({ 15 | type: ActionTypes.GRAMMAR_COMPILED, 16 | compiledGrammar: data.compiledGrammar, 17 | compiledParser: data.compiledParser 18 | }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /app/scripts/ui/GrammarOutput/GrammarErrorView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | /* global blobUtil */ 6 | 7 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 8 | 9 | var GrammarErrorView = React.createClass({ 10 | mixins: [PureRenderMixin], 11 | render: function () { 12 | var style = { 13 | height: 150, 14 | width: '100%', 15 | fontSize: 10, 16 | border: '2px solid #D9534F' 17 | }; 18 | 19 | return ( 20 |
{this.props.compiledError.message}
21 | ); 22 | } 23 | }); 24 | 25 | module.exports = GrammarErrorView; 26 | -------------------------------------------------------------------------------- /app/scripts/actions/GrammarActionCreator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AppDispatcher = require('../dispatcher'); 4 | var ActionTypes = require('./ActionTypes'); 5 | var GrammarWorkerService = require('../data/GrammarWorkerService'); 6 | 7 | module.exports = { 8 | compileGrammar: function(grammar) { 9 | AppDispatcher.handleViewAction({ 10 | type: ActionTypes.COMPILE_GRAMMAR, 11 | grammar: grammar 12 | }); 13 | GrammarWorkerService.compileGrammar(grammar); 14 | }, 15 | changeGrammarSignificantly: function() { 16 | AppDispatcher.handleViewAction({ 17 | type: ActionTypes.GRAMMAR_CHANGED_SIGNIFICANTLY 18 | }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/LexErrorView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var LexErrorView = React.createClass({ 8 | mixins: [PureRenderMixin], 9 | render: function () { 10 | var preStyle = { 11 | width: '100%', 12 | fontSize: 11, 13 | border: '2px solid #D9534F' 14 | }; 15 | 16 | return ( 17 |
18 |
Parser error
19 |
{this.props.parsedError ? this.props.parsedError.message : ''}
20 |
21 | ); 22 | } 23 | }); 24 | 25 | module.exports = LexErrorView; 26 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/ParsedResultView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var ParsedResultView = React.createClass({ 8 | mixins: [PureRenderMixin], 9 | render: function () { 10 | var style = { 11 | width: '100%', 12 | fontSize: 12 13 | }; 14 | 15 | var parsedResult = this.props.parsedResult || ''; 16 | return ( 17 |
18 |
Parser result
19 |
{parsedResult}
20 |
21 | ); 22 | 23 | } 24 | }); 25 | 26 | module.exports = ParsedResultView; 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/scripts/dispatcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var dispatcher = require('flux-dispatcher'); 4 | var PayloadSources = require('./payload-sources'); 5 | var assign = require('object-assign'); 6 | 7 | var AppDispatcher = assign(dispatcher, { 8 | 9 | handleViewAction(action) { 10 | var payload = { 11 | source: PayloadSources.VIEW_ACTION, 12 | action: action 13 | }; 14 | this.dispatch(payload); 15 | }, 16 | 17 | handleWorkerAction(action) { 18 | var payload = { 19 | source: PayloadSources.WORKER_ACTION, 20 | action: action 21 | }; 22 | this.dispatch(payload); 23 | } 24 | 25 | }); 26 | 27 | module.exports = AppDispatcher; 28 | -------------------------------------------------------------------------------- /app/scripts/actions/ParsedTextActionCreator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AppDispatcher = require('../dispatcher'); 4 | var ActionTypes = require('./ActionTypes'); 5 | var safeStringify = require('../util/util').safeStringify; 6 | 7 | module.exports = { 8 | textParsed: function (data) { 9 | AppDispatcher.handleWorkerAction({ 10 | type: ActionTypes.TEXT_PARSED, 11 | parsedResult: safeStringify(data.parsedResult, null, ' '), 12 | lexDebugger: data.lexDebugger, 13 | parserDebugger: data.parserDebugger 14 | }); 15 | }, 16 | textErrored: function (error) { 17 | AppDispatcher.handleWorkerAction({ 18 | type: ActionTypes.TEXT_ERRORED, 19 | error: error 20 | }); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /app/scripts/ui/Editor/Editor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var React = require('react'); 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var GrammarEditor = require('./GrammarEditor'); 8 | var TextToParseView = require('./TextToParseView'); 9 | var SampleGrammarLoader = require('./SampleGrammarLoader'); 10 | 11 | var Editor = React.createClass({ 12 | mixins: [PureRenderMixin], 13 | render: function () { 14 | return ( 15 |
16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | }); 23 | 24 | module.exports = Editor; 25 | -------------------------------------------------------------------------------- /app/scripts/data/GrammarWorkerService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var worker = new Worker('worker/grammar-worker.js'); 4 | var CompiledGrammarActionCreator = require('../actions/CompiledGrammarActionCreator'); 5 | 6 | var debounce = require('../util/util').debounce(); 7 | 8 | var GrammarWorkerService = { 9 | compileGrammar: function (grammar) { 10 | debounce(300, function () { 11 | worker.addEventListener('error', function (e) { 12 | CompiledGrammarActionCreator.grammarErrored(e); 13 | }); 14 | worker.addEventListener('message', function (e) { 15 | CompiledGrammarActionCreator.grammarCompiled(e.data); 16 | }); 17 | // ask the web worker to parse the grammar for us 18 | worker.postMessage({grammar: grammar}); 19 | }.bind(this)); 20 | } 21 | }; 22 | 23 | module.exports = GrammarWorkerService; 24 | -------------------------------------------------------------------------------- /app/scripts/ui/GrammarOutput/GrammarView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | /* global blobUtil */ 6 | 7 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 8 | 9 | var GrammarSuccessView = require('./GrammarSuccessView'); 10 | var GrammarErrorView = require('./GrammarErrorView'); 11 | 12 | var GrammarView = React.createClass({ 13 | mixins: [PureRenderMixin], 14 | render: function () { 15 | if (this.props.compiledError) { 16 | return ( 17 |
18 |
Grammar error
19 | 20 |
21 | ) 22 | } else { 23 | return ( 24 |
25 |
Compiled grammar
26 | 30 |
31 | ) 32 | } 33 | } 34 | }); 35 | 36 | module.exports = GrammarView; 37 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/ParseTreeRawView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 5 | 6 | var safeStringify = require('../../util/util').safeStringify; 7 | 8 | var ParseTreeRawView = React.createClass({ 9 | mixins: [PureRenderMixin], 10 | render: function () { 11 | 12 | var preStyle = { 13 | width: '100%', 14 | fontSize: 11 15 | }; 16 | 17 | var parserDebugger = this.props.parserDebugger || []; 18 | 19 | var text = parserDebugger.map(function (step) { 20 | var res = ''; 21 | if (step.action === 'reduce') { 22 | res += ' --> '; 23 | } 24 | res += step.action + ': ' + safeStringify(step.text); 25 | if (step.action === 'reduce') { 26 | res += ' (' + step.nonterminal + ' -> ' + JSON.stringify(step.productions) + ')'; 27 | } else if (step.action === 'shift') { 28 | res += ' (' + step.terminal + ')'; 29 | } 30 | return res; 31 | }).join('\n'); 32 | return ( 33 |
{text}
34 | ); 35 | } 36 | }); 37 | 38 | module.exports = ParseTreeRawView; 39 | 40 | -------------------------------------------------------------------------------- /app/scripts/util/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grammars = [ 4 | { 5 | name: 'Calculator', 6 | grammar: require('./sample-grammars/calculator.jison'), 7 | 8 | sampleText: '2 + 7 * 5' 9 | }, 10 | { 11 | name: 'Happy Happy Joy Joy', 12 | grammar: require('./sample-grammars/happy.jison'), 13 | sampleText: 'happy happy joy joy' 14 | }, 15 | //{ 16 | // name: 'BlooP and FlooP', 17 | // grammar: require('./sample-grammars/bloop.jison'), 18 | // sampleText: "DEFINE PROCEDURE ''FACTORIAL'' [N]: BLOCK 0: BEGIN OUTPUT <= 1; CELL(0) <= 1; LOOP N TIMES: BLOCK 1: BEGIN OUTPUT <= OUTPUT * CELL(0); CELL(0) <= CELL(0) + 1; BLOCK 1: END; BLOCK 0: END." 19 | //} 20 | { 21 | grammar: require('./sample-grammars/toy-english.jison'), 22 | sampleText: 'I saw the astronomer with the telescope.', 23 | name: 'Toy English' 24 | }, 25 | { 26 | name: 'Musical Chords', 27 | sampleText: 'G#maj7/Bb', 28 | grammar: require('./sample-grammars/chords.jison') 29 | } 30 | ]; 31 | 32 | module.exports = { 33 | SAMPLE_GRAMMARS: grammars, 34 | INITIAL_GRAMMAR: grammars[0].grammar, 35 | INITIAL_TEXT_TO_PARSE: grammars[0].sampleText 36 | }; 37 | -------------------------------------------------------------------------------- /app/scripts/data/ParserWorkerService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var worker = new Worker('worker/grammar-worker.js'); 4 | var ParsedTextActionCreator = require('../actions/ParsedTextActionCreator'); 5 | var GrammarOutputStore = require('../stores/GrammarOutputStore'); 6 | var debounce = require('../util/util').debounce(); 7 | 8 | var ParserWorkerService = { 9 | parseText: function (text) { 10 | debounce(300, function () { 11 | if (!GrammarOutputStore.getActiveCompiledGrammar()) { 12 | return; // grammar is invalid, can't parse 13 | } 14 | 15 | worker.addEventListener('error', function (e) { 16 | ParsedTextActionCreator.textErrored(e); 17 | }); 18 | worker.addEventListener('message', function (e) { 19 | if (e.data.error) { 20 | ParsedTextActionCreator.textErrored({ 21 | message: e.data.message, 22 | lexDebugger: e.data.lexDebugger 23 | }); 24 | } else { 25 | ParsedTextActionCreator.textParsed(e.data); 26 | } 27 | }); 28 | // ask the web worker to parse the text for us 29 | worker.postMessage({ 30 | textToParse: text, 31 | compiledParser: GrammarOutputStore.getActiveCompiledParser() 32 | }); 33 | }.bind(this)); 34 | } 35 | }; 36 | 37 | module.exports = ParserWorkerService; 38 | -------------------------------------------------------------------------------- /app/scripts/ui/Editor/GrammarEditor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var React = require('react'); 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var GrammarActionCreator = require('../../actions/GrammarActionCreator'); 8 | var UserInputActionCreator = require('../../actions/UserInputActionCreator'); 9 | 10 | var GrammarEditor = React.createClass({ 11 | mixins: [PureRenderMixin], 12 | componentDidMount: function () { 13 | this._recompileGrammar(this.props.grammar); 14 | }, 15 | _recompileGrammar: function (text) { 16 | GrammarActionCreator.compileGrammar(text); 17 | }, 18 | handleGrammarChange: function (event) { 19 | var text = event.target.value; 20 | UserInputActionCreator.updateGrammar(text); 21 | this._recompileGrammar(text); 22 | }, 23 | render: function () { 24 | var textAreaStyle = { 25 | height: 400, 26 | width: '100%', 27 | fontSize: 12, 28 | fontFamily: 'monospace' 29 | }; 30 | 31 | return ( 32 |
33 |
Write your grammar
34 | 39 |
40 | ) 41 | } 42 | }); 43 | 44 | module.exports = GrammarEditor; 45 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/ParseTreeView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 5 | 6 | var ParseTreeRawView = require('./ParseTreeRawView'); 7 | var ParseTreeGraphView = require('./ParseTreeGraphView'); 8 | 9 | var ParseTreeView = React.createClass({ 10 | mixins: [PureRenderMixin], 11 | getInitialState: function () { 12 | return {raw: false} 13 | }, 14 | _toggleRaw: function () { 15 | this.setState({raw: !this.state.raw}); 16 | }, 17 | render: function () { 18 | if (this.state.raw) { 19 | return ( 20 |
21 |
22 |
Parse tree
23 | 24 | Show graph 25 | 26 |
27 | 28 |
29 | ) 30 | } 31 | 32 | return ( 33 |
34 |
35 |
Parse tree
36 | 37 | Show log 38 | 39 |
40 | 41 |
42 | ); 43 | } 44 | }); 45 | 46 | module.exports = ParseTreeView; 47 | 48 | -------------------------------------------------------------------------------- /app/scripts/ui/util/Colorizer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var random = require('lodash/number/random'); 4 | 5 | // give each token type a relatively unique color 6 | // generated by https://github.com/sterlingwes/RandomColor 7 | var colors = [ 8 | "rgb(97, 179, 60)", 9 | "rgb(179, 60, 132)", 10 | "rgb(60, 166, 179)", 11 | "rgb(179, 157, 60)", 12 | "rgb(122, 60, 179)", 13 | "rgb(60, 179, 88)", 14 | "rgb(179, 60, 68)", 15 | "rgb(60, 102, 179)", 16 | "rgb(137, 179, 60)", 17 | "rgb(179, 60, 171)", 18 | "rgb(60, 179, 152)", 19 | "rgb(179, 117, 60)", 20 | "rgb(83, 60, 179)", 21 | "rgb(73, 179, 60)", 22 | "rgb(179, 60, 107)", 23 | "rgb(60, 142, 179)", 24 | "rgb(176, 179, 60)", 25 | "rgb(147, 60, 179)", 26 | "rgb(60, 179, 112)", 27 | "rgb(179, 78, 60)", 28 | "rgb(60, 78, 179)", 29 | "rgb(112, 179, 60)", 30 | "rgb(179, 60, 147)", 31 | "rgb(60, 179, 176)", 32 | "rgb(179, 142, 60)", 33 | "rgb(107, 60, 179)" 34 | ]; 35 | // shuffled because it's more fun 36 | var colorIdx = random(0, colors.length - 1); 37 | var tokenNamesToColors = {}; 38 | 39 | function getColorFor(tokenName) { 40 | if (!tokenNamesToColors[tokenName]) { 41 | tokenNamesToColors[tokenName] = colors[colorIdx]; 42 | colorIdx++; 43 | if (colorIdx == colors.length) { 44 | colorIdx = 0; // wrap back around 45 | } 46 | } 47 | 48 | return tokenNamesToColors[tokenName]; 49 | } 50 | 51 | module.exports = { 52 | getColorFor: getColorFor 53 | }; 54 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/ParserOutputView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var LexOutputView = require('./LexOutputView'); 8 | var ParsedResultView = require('./ParsedResultView'); 9 | var LexErrorView = require('./LexErrorView'); 10 | var ParseTreeView = require('./ParseTreeView'); 11 | 12 | var ParserOutputView = React.createClass({ 13 | mixins: [PureRenderMixin], 14 | render: function () { 15 | var baseStyle = { 16 | width: '100%', 17 | fontSize: 12 18 | }; 19 | 20 | if (!this.props.compiledGrammar) { 21 | // current grammar is invalid 22 | return (
) 23 | } else if (this.props.parsedError) { 24 | return ( 25 |
26 | 27 | 28 |
29 | ); 30 | } else if (this.props.parsedResult) { 31 | return ( 32 |
33 | 34 | 35 | 36 |
37 | ); 38 | } else { 39 | return (
) 40 | } 41 | } 42 | }); 43 | 44 | module.exports = ParserOutputView; 45 | -------------------------------------------------------------------------------- /app/scripts/util/sample-grammars/toy-english.jison: -------------------------------------------------------------------------------- 1 | /* 2 | * Toy English grammar, by Nolan Lawson. 3 | * A very simple context-free grammar. 4 | * 5 | * It demonstrates the ambiguity of phrases like 6 | * "I saw the astronomer with the telescope." 7 | * You can toggle between the two interpretations 8 | * by removing rules. 9 | */ 10 | 11 | %lex 12 | 13 | %% 14 | 15 | \s+ /* skip whitespace */ 16 | // yytext means just the text itself 17 | \w+ return yytext.toLowerCase() 18 | . /* ignore any non-words */ 19 | <> return 'EOF' 20 | 21 | /lex 22 | 23 | %start root 24 | 25 | /* ENBF is used in order to give us syntax goodies 26 | * like '+' and '*' repeaters, groups with '(' ')', etc. 27 | * See https://gist.github.com/zaach/1659274 28 | */ 29 | %ebnf 30 | 31 | %% 32 | 33 | root 34 | : S EOF {return $1;} 35 | ; 36 | 37 | S 38 | : NP VP -> {sentence: [$NP, $VP]} 39 | ; 40 | 41 | NP 42 | : N -> {nounPhrase: $N} 43 | | D N -> {nounPhrase: [$D, $N]} 44 | | D N PP -> {nounPhrase: [$D, $N, $PP]} 45 | ; 46 | 47 | D 48 | : the -> {determiner: $1} 49 | ; 50 | 51 | N 52 | : i | astronomer | telescope | me -> {noun: $1} 53 | ; 54 | 55 | VP 56 | : V NP -> {verbPhrase: [$V, $NP]} 57 | | V NP PP -> {verbPhrase: [$V, $NP, $PP]} 58 | ; 59 | 60 | V 61 | : saw -> {verb: $1} 62 | ; 63 | 64 | PP 65 | : P NP -> {prepositionalPhrase: [$P, $NP]} 66 | ; 67 | 68 | P 69 | : with -> {preposition: $1} 70 | ; 71 | -------------------------------------------------------------------------------- /app/scripts/worker/grammar-worker.js: -------------------------------------------------------------------------------- 1 | /* jshint worker:true */ 2 | /* global Jison,ebnf,parser */ 3 | importScripts('./jison.js'); 4 | Jison.print = function () {}; 5 | 6 | function compileGrammar(self, grammar) { 7 | var compiledGrammar; 8 | 9 | try { 10 | compiledGrammar = JSON.parse(grammar); 11 | } catch (e) { 12 | // intentionally throw an error here if it fails to parse 13 | compiledGrammar = bnf.parse(grammar); 14 | } 15 | 16 | var compiledParser = new Jison.Parser(compiledGrammar).generate(); 17 | 18 | self.postMessage({compiledGrammar: compiledGrammar, compiledParser: compiledParser}); 19 | } 20 | 21 | function parseText(self, request) { 22 | var textToParse = request.textToParse; 23 | eval(request.compiledParser); // creates a global "parser" object 24 | var compiledParser = parser; 25 | 26 | Jison.lexDebugger = []; 27 | Jison.parserDebugger = []; 28 | var parsedResult 29 | try { 30 | parsedResult = compiledParser.parse(textToParse); 31 | } catch (e) { 32 | self.postMessage({ 33 | error: true, 34 | message: e.message, 35 | lexDebugger: Jison.lexDebugger 36 | }); 37 | return; 38 | } 39 | 40 | self.postMessage({ 41 | parsedResult: parsedResult, 42 | lexDebugger: Jison.lexDebugger, 43 | parserDebugger: Jison.parserDebugger 44 | }); 45 | } 46 | 47 | // request to parse a grammar 48 | self.addEventListener('message', function (e) { 49 | if (e.data.grammar) { 50 | compileGrammar(self, e.data.grammar); 51 | } else { 52 | parseText(self, e.data); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /app/scripts/stores/GrammarInputStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events'); 4 | var assign = require('object-assign'); 5 | var AppDispatcher = require('../dispatcher'); 6 | var ActionTypes = require('../actions/ActionTypes'); 7 | var constants = require('../util/constants'); 8 | 9 | var CHANGE_EVENT = 'change'; 10 | 11 | var activeGrammar = localStorage.grammar || constants.INITIAL_GRAMMAR; 12 | var activeTextToParse = localStorage.textToParse || constants.INITIAL_TEXT_TO_PARSE; 13 | 14 | var GrammarInputStore = assign({}, EventEmitter.prototype, { 15 | emitChange() { 16 | this.emit(CHANGE_EVENT); 17 | }, 18 | addChangeListener(callback) { 19 | this.on(CHANGE_EVENT, callback); 20 | }, 21 | removeChangeListener(callback) { 22 | this.removeListener(CHANGE_EVENT, callback); 23 | }, 24 | getActiveGrammar() { 25 | return activeGrammar; 26 | }, 27 | getActiveTextToParse() { 28 | return activeTextToParse; 29 | } 30 | }); 31 | 32 | GrammarInputStore.dispatchToken = AppDispatcher.register(function (payload) { 33 | var action = payload.action; 34 | 35 | switch (action.type) { 36 | case ActionTypes.GRAMMAR_CHANGED: 37 | activeGrammar = action.grammar; 38 | localStorage.grammar = action.grammar; 39 | GrammarInputStore.emitChange(); 40 | break; 41 | case ActionTypes.TEXT_TO_PARSE_CHANGED: 42 | activeTextToParse = action.textToParse; 43 | localStorage.textToParse = action.textToParse; 44 | GrammarInputStore.emitChange(); 45 | break; 46 | } 47 | }); 48 | 49 | module.exports = GrammarInputStore; 50 | -------------------------------------------------------------------------------- /app/scripts/ui/GrammarOutput/GrammarSuccessView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | /* global blobUtil */ 6 | 7 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 8 | 9 | var GrammarSuccessView = React.createClass({ 10 | mixins: [PureRenderMixin], 11 | download: function () { 12 | var parser = this.props.compiledParser; 13 | var blob = blobUtil.createBlob([parser], 14 | {type: 'text/javascript'} 15 | ); 16 | window.open(blobUtil.createObjectURL(blob)); 17 | }, 18 | downloadJSON: function () { 19 | var grammar = this.props.compiledGrammar; 20 | var blob = blobUtil.createBlob( 21 | [JSON.stringify(grammar, null, ' ')], 22 | {type: 'application/json'} 23 | ); 24 | window.open(blobUtil.createObjectURL(blob)); 25 | }, 26 | render: function () { 27 | return ( 28 |
29 | 37 | 46 |
47 | ); 48 | } 49 | }); 50 | 51 | module.exports = GrammarSuccessView; 52 | -------------------------------------------------------------------------------- /app/scripts/util/sample-grammars/happy.jison: -------------------------------------------------------------------------------- 1 | /* "Happy happy joy joy" grammar by Nolan Lawson. 2 | * Based on the song of the same name. 3 | * Inspired by true events. 4 | * 5 | * This demonstrates a language that is impossible 6 | * to express with (standard) regular expressions, 7 | * because it's not a regular language. 8 | * The language is: (happy)^n (joy)^n 9 | * i.e. an equal number of happy's and joy's. 10 | * 11 | * Valid sentences in this language include: 12 | * "happy happy joy joy" 13 | * "happy happy happy joy joy joy" 14 | * etc. 15 | * Whereas e.g. "happy joy joy" and "happy happy joy" 16 | * are invalid. 17 | */ 18 | 19 | %lex 20 | 21 | // you can be HAPPY as well as happy 22 | %options flex case-insensitive 23 | 24 | %% 25 | 26 | \s+ /* skip whitespace */ 27 | 28 | /* the enclosing parentheses ensure that 29 | * word boundaries don't matter 30 | * so e.g. happyhappyjoyjoy" is okay 31 | * See https://github.com/zaach/jison/issues/63 32 | */ 33 | ("happy") return 'happy' 34 | ("joy") return 'joy' 35 | . return 'INVALID' 36 | <> return 'EOF' 37 | 38 | /lex 39 | 40 | %start root 41 | 42 | /* ENBF is used in order to give us syntax goodies 43 | * like '+' and '*' repeaters, groups with '(' ')', etc. 44 | * See https://gist.github.com/zaach/1659274 45 | */ 46 | %ebnf 47 | 48 | %% 49 | 50 | // The variables $1, $2, $3, etc. refer to the nth 51 | // item in the list. You can also name them. 52 | // At the top, we return. 53 | root 54 | : e EOF {return $1.toLowerCase();} 55 | ; 56 | 57 | // Elsewhere, we use this arrow shorthand for 58 | // for {{ $$ = whatever }}, where $$ means 59 | // "give this to the parent." 60 | e 61 | : happy e joy -> [$1, $2, $3].join(' ') 62 | | happy joy -> [$1, $2].join(' ') 63 | ; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jison-debugger", 3 | "version": "0.0.0", 4 | "repository": "https://github.com/nolanlawson/jison-debugger.git", 5 | "license": "Apache-2.0", 6 | "dependencies": { 7 | "ebnf-parser": "^0.1.10", 8 | "flux": "^2.0.1", 9 | "flux-dispatcher": "^1.0.6", 10 | "jison": "nolanlawson/jison#debuggable", 11 | "keymirror": "^0.1.1", 12 | "lex-parser": "^0.1.4", 13 | "lodash": "^3.5.0", 14 | "object-assign": "^2.0.0", 15 | "through2": "^0.6.3" 16 | }, 17 | "devDependencies": { 18 | "browserify-shim": "^3.8.0", 19 | "del": "~0.1.3", 20 | "gulp": "^3.8.11", 21 | "gulp-autoprefixer": "^2.1.0", 22 | "gulp-bower": "0.0.10", 23 | "gulp-cache": "^0.2.8", 24 | "gulp-imagemin": "^2.2.1", 25 | "gulp-jade": "^1.0.0", 26 | "gulp-jest": "^0.4.0", 27 | "gulp-jshint": "^1.9.2", 28 | "gulp-load-plugins": "^0.8.1", 29 | "gulp-ruby-sass": "^1.0.0-alpha.3", 30 | "gulp-size": "^1.2.1", 31 | "gulp-useref": "^1.1.1", 32 | "gulp-util": "^3.0.4", 33 | "gulp-webserver": "^0.9.0", 34 | "jest": "latest", 35 | "react": "latest", 36 | "reactify": "latest", 37 | "vinyl-source-stream": "^1.0.0", 38 | "watchify": "~2.1" 39 | }, 40 | "engines": { 41 | "node": ">=0.10.0" 42 | }, 43 | "browserify": { 44 | "transform": [ 45 | "./app/transforms/jison-to-textify", 46 | "browserify-shim", 47 | [ 48 | "reactify", 49 | { 50 | "es6": true 51 | } 52 | ] 53 | ] 54 | }, 55 | "browser": { 56 | "jquery": "./app/bower_components/jquery/dist/jquery.js" 57 | }, 58 | "browserify-shim": { 59 | "jquery": "$" 60 | }, 61 | "scripts": { 62 | "postinstall": "npm run build-jison", 63 | "build-jison": "./bin/build-jison.sh", 64 | "deploy": "./bin/deploy.sh" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/LexOutputView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | var Colorizer = require('./../util/Colorizer'); 7 | var safeStringify = require('../../util/util').safeStringify; 8 | 9 | function createKey(token, i) { 10 | return JSON.stringify([safeStringify(token), i]); 11 | } 12 | 13 | var LexOutputView = React.createClass({ 14 | mixins: [PureRenderMixin], 15 | render: function () { 16 | 17 | var mainStyle = { 18 | }; 19 | 20 | var ulStyle = { 21 | listStyleType: 'none', 22 | fontFamily: 'monospace', 23 | paddingLeft: 0 24 | }; 25 | 26 | var lexDebugger = this.props.lexDebugger || []; 27 | 28 | return ( 29 |
30 |
Tokens
31 |
    32 | { 33 | lexDebugger.map(function (token, i) { 34 | var displayText = token.tokenText || 'EOF'; 35 | if (/^\s+$/.test(displayText)) { // whitespace 36 | displayText = '\u00A0'; // nbsp, so it gets the proper height 37 | } 38 | return ( 39 |
  • 40 | 42 | {displayText} 43 | 44 | 48 | {token.tokenName} 49 | 50 |
  • 51 | ) 52 | }) 53 | } 54 |
55 |
56 | ); 57 | } 58 | }); 59 | 60 | module.exports = LexOutputView; 61 | 62 | -------------------------------------------------------------------------------- /app/scripts/ui/Editor/TextToParseView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var React = require('react'); 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var ParserActionCreator = require('../../actions/ParserActionCreator'); 8 | var UserInputActionCreator = require('../../actions/UserInputActionCreator'); 9 | 10 | var TextToParseView = React.createClass({ 11 | mixins: [PureRenderMixin], 12 | getInitialState: function () { 13 | return {multiline: false}; 14 | }, 15 | _toggleMultiline: function () { 16 | this.setState({multiline: !this.state.multiline}); 17 | }, 18 | handleTextToParseChange: function (event) { 19 | var text = event.target.value; 20 | UserInputActionCreator.updateTextToParse(text); 21 | ParserActionCreator.parseText(text); 22 | }, 23 | render: function () { 24 | 25 | var inputStyle = { 26 | width: '100%' 27 | }; 28 | 29 | return ( 30 |
31 |
32 |
Parse some text
33 | 34 | {this.state.multiline ? 'Single-line' : 'Multiline'} 35 | 36 |
37 | { this.state.multiline ? 38 | ( 39 | 45 | ) 46 | : 47 | ( 48 | 53 | 54 | ) 55 | } 56 |
57 | ) 58 | } 59 | }); 60 | 61 | module.exports = TextToParseView; 62 | -------------------------------------------------------------------------------- /app/scripts/stores/GrammarOutputStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events'); 4 | var assign = require('object-assign'); 5 | var AppDispatcher = require('../dispatcher'); 6 | var ActionTypes = require('../actions/ActionTypes'); 7 | var constants = require('../util/constants'); 8 | 9 | var CHANGE_EVENT = 'change'; 10 | 11 | var activeCompiledGrammar = null; 12 | var activeCompiledParser = null; 13 | var activeCompiledError = null; 14 | 15 | var GrammarOutputStore = assign({}, EventEmitter.prototype, { 16 | emitChange() { 17 | this.emit(CHANGE_EVENT); 18 | }, 19 | addChangeListener(callback) { 20 | this.on(CHANGE_EVENT, callback); 21 | }, 22 | removeChangeListener(callback) { 23 | this.removeListener(CHANGE_EVENT, callback); 24 | }, 25 | getActiveCompiledGrammar() { 26 | return activeCompiledGrammar; 27 | }, 28 | getActiveCompiledParser() { 29 | return activeCompiledParser; 30 | }, 31 | getActiveCompiledError() { 32 | return activeCompiledError; 33 | } 34 | }); 35 | 36 | GrammarOutputStore.dispatchToken = AppDispatcher.register(function (payload) { 37 | var action = payload.action; 38 | 39 | switch (action.type) { 40 | case ActionTypes.GRAMMAR_CHANGED_SIGNIFICANTLY: 41 | activeCompiledGrammar = null; 42 | activeCompiledError = null; 43 | activeCompiledParser = null; 44 | GrammarOutputStore.emitChange(); 45 | break; 46 | case ActionTypes.GRAMMAR_COMPILED: 47 | activeCompiledGrammar = action.compiledGrammar; 48 | activeCompiledParser = action.compiledParser; 49 | activeCompiledError = null; 50 | GrammarOutputStore.emitChange(); 51 | break; 52 | case ActionTypes.GRAMMAR_ERRORED: 53 | activeCompiledGrammar = null; 54 | activeCompiledParser = null; 55 | activeCompiledError = action.error; 56 | GrammarOutputStore.emitChange(); 57 | break; 58 | } 59 | }); 60 | 61 | module.exports = GrammarOutputStore; 62 | -------------------------------------------------------------------------------- /app/scripts/ui/Editor/SampleGrammarLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var React = require('react'); 5 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 6 | 7 | var GrammarActionCreator = require('../../actions/GrammarActionCreator'); 8 | var UserInputActionCreator = require('../../actions/UserInputActionCreator'); 9 | 10 | var constants = require('../../util/constants'); 11 | var sampleGrammars = constants.SAMPLE_GRAMMARS; 12 | 13 | var SampleGrammarLoader = React.createClass({ 14 | mixins: [PureRenderMixin], 15 | handleChange: function (event) { 16 | var sampleGrammarName = event.target.value; 17 | var sampleGrammar = sampleGrammars.filter(function (grammar) { 18 | return grammar.name == sampleGrammarName; 19 | })[0]; 20 | 21 | if (!sampleGrammar) { 22 | // user chose the "choose a..." option 23 | return; 24 | } 25 | 26 | 27 | UserInputActionCreator.updateGrammar(sampleGrammar.grammar); 28 | UserInputActionCreator.updateTextToParse(sampleGrammar.sampleText); 29 | GrammarActionCreator.compileGrammar(sampleGrammar.grammar); 30 | GrammarActionCreator.changeGrammarSignificantly(); 31 | }, 32 | render: function () { 33 | var style = { 34 | width: '100%' 35 | }; 36 | 37 | return ( 38 |
39 |
Load a sample grammar
40 | 55 |
56 | ) 57 | } 58 | }); 59 | 60 | module.exports = SampleGrammarLoader; 61 | -------------------------------------------------------------------------------- /app/scripts/stores/ParserOutputStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events'); 4 | var assign = require('object-assign'); 5 | var AppDispatcher = require('../dispatcher'); 6 | var ActionTypes = require('../actions/ActionTypes'); 7 | var constants = require('../util/constants'); 8 | 9 | var CHANGE_EVENT = 'change'; 10 | 11 | var activeParsedResult = null; 12 | var activeParsedError = null; 13 | var activeLexDebugger = null; 14 | var activeParserDebugger = null; 15 | 16 | var ParserOutputStore = assign({}, EventEmitter.prototype, { 17 | emitChange() { 18 | this.emit(CHANGE_EVENT); 19 | }, 20 | addChangeListener(callback) { 21 | this.on(CHANGE_EVENT, callback); 22 | }, 23 | removeChangeListener(callback) { 24 | this.removeListener(CHANGE_EVENT, callback); 25 | }, 26 | getActiveParsedResult() { 27 | return activeParsedResult; 28 | }, 29 | getActiveParsedError() { 30 | return activeParsedError; 31 | }, 32 | getActiveLexDebugger() { 33 | return activeLexDebugger; 34 | }, 35 | getActiveParserDebugger() { 36 | return activeParserDebugger; 37 | } 38 | }); 39 | 40 | ParserOutputStore.dispatchToken = AppDispatcher.register(function (payload) { 41 | var action = payload.action; 42 | 43 | switch (action.type) { 44 | case ActionTypes.GRAMMAR_CHANGED_SIGNIFICANTLY: 45 | activeParsedResult = null; 46 | activeLexDebugger = null; 47 | activeParserDebugger = null; 48 | activeParsedError = null; 49 | ParserOutputStore.emitChange(); 50 | break; 51 | case ActionTypes.TEXT_PARSED: 52 | activeParsedResult = action.parsedResult; 53 | activeLexDebugger = action.lexDebugger; 54 | activeParserDebugger = action.parserDebugger; 55 | activeParsedError = null; 56 | ParserOutputStore.emitChange(); 57 | break; 58 | case ActionTypes.TEXT_ERRORED: 59 | activeParsedResult = null; 60 | activeLexDebugger = action.error.lexDebugger || []; 61 | activeParserDebugger = null; 62 | activeParsedError = action.error; 63 | ParserOutputStore.emitChange(); 64 | break; 65 | } 66 | }); 67 | 68 | module.exports = ParserOutputStore; 69 | -------------------------------------------------------------------------------- /app/scripts/ui/util/TreeBuilder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var safeStringify = require('../../util/util').safeStringify; 4 | 5 | function buildTree(parserDebugger) { 6 | var unresolvedProductions = []; 7 | var root; 8 | 9 | function handleShift(step) { 10 | for (var k = unresolvedProductions.length - 1; k >= 0; k--) { 11 | var unresolvedProduction = unresolvedProductions[k]; 12 | if (unresolvedProduction.name === step.terminal) { 13 | // resolve as terminal, no children 14 | unresolvedProductions.splice(k, 1); 15 | unresolvedProduction.subtitle = step.text || "<>"; 16 | } 17 | } 18 | } 19 | 20 | function handleReduce(step) { 21 | var node; 22 | if (!root) { 23 | // root node 24 | node = root = { 25 | name: step.nonterminal, 26 | output: safeStringify(step.result), 27 | children: [] 28 | } 29 | } else { 30 | // non-root, non-terminal 31 | for (var k = unresolvedProductions.length - 1; k >= 0; k--) { 32 | var unresolvedProduction = unresolvedProductions[k]; 33 | if (unresolvedProduction.name === step.nonterminal) { 34 | // resolve 35 | unresolvedProductions.splice(k, 1); 36 | node = unresolvedProduction; 37 | node.output = safeStringify(step.text); 38 | break; 39 | } 40 | } 41 | } 42 | 43 | for (var j = 0; j < step.productions.length; j++) { 44 | var production = { 45 | name: step.productions[j], 46 | children: [] 47 | }; 48 | node.children.push(production); 49 | unresolvedProductions.push(production); 50 | } 51 | } 52 | 53 | // the way the parserDebugger is built up, I can walk 54 | // through it backwards from the root node and 55 | // build up the hierarchy that way. The list of unresolvedProductions 56 | // can also be used to walk through backwards and find 57 | // the correct node in the case of ambiguities, due to 58 | // how LALR grammars are built up. 59 | for (var i = parserDebugger.length - 1; i >= 0; i--) { 60 | var step = parserDebugger[i]; 61 | if (step.action === 'shift') { 62 | handleShift(step); 63 | } else if (step.action === 'reduce') { 64 | handleReduce(step); 65 | } 66 | } 67 | 68 | return root; 69 | } 70 | 71 | module.exports = { 72 | buildTree: buildTree 73 | } 74 | -------------------------------------------------------------------------------- /app/scripts/util/sample-grammars/calculator.jison: -------------------------------------------------------------------------------- 1 | /* Calculator demo - 2 | * Parses and executes mathematical expressions. 3 | * Written by Zach Carter. Annotated by Nolan Lawson. 4 | */ 5 | 6 | /* lexical grammar */ 7 | %lex 8 | %% 9 | 10 | \s+ /* skip whitespace */ 11 | [0-9]+("."[0-9]+)?\b return 'NUMBER' 12 | "*" return '*' 13 | "/" return '/' 14 | "-" return '-' 15 | "+" return '+' 16 | "^" return '^' 17 | "(" return '(' 18 | ")" return ')' 19 | "PI" return 'PI' 20 | "E" return 'E' 21 | // EOF means "end of file" 22 | <> return 'EOF' 23 | // any other characters will throw an error 24 | . return 'INVALID' 25 | 26 | /lex 27 | 28 | // Operator associations and precedence. 29 | // 30 | // This is the part that defines rules like 31 | // e.g. multiplication/division apply before 32 | // subtraction/addition, etc. Of course you can 33 | // also be explicit by adding parentheses, as 34 | // we all learned in elementary school. 35 | // 36 | // Notice that there's this weird UMINUS thing. 37 | // This is a special Bison/Jison trick for preferring 38 | // the unary interpretation of the minus sign, e.g. 39 | // -2^2 should be interpreted as (-2)^2 and not -(2^2). 40 | // 41 | // Details here: 42 | // http://web.mit.edu/gnu/doc/html/bison_8.html#SEC76 43 | 44 | %left '+' '-' 45 | %left '*' '/' 46 | %left '^' 47 | %left UMINUS 48 | 49 | %start expressions 50 | 51 | %% /* language grammar */ 52 | 53 | // At the top level, you explicitly return 54 | // the result. $1 refers to the first child node, 55 | // i.e. the "e" 56 | 57 | expressions 58 | : e EOF 59 | {return $1;} 60 | ; 61 | 62 | // For everything below the top level, $$ refers to 63 | // the thing that should be returned. yytext refers 64 | // to the text of that node. 65 | // 66 | // This {$$ = ...} format is actually no 67 | // longer necessary, because Jison 0.3 68 | // introduced a -> shorthand. You can see the 69 | // shorthand illustrated in the other sample 70 | // grammars. 71 | 72 | e 73 | : e '+' e 74 | {$$ = $1+$3;} 75 | | e '-' e 76 | {$$ = $1-$3;} 77 | | e '*' e 78 | {$$ = $1*$3;} 79 | | e '/' e 80 | {$$ = $1/$3;} 81 | | e '^' e 82 | {$$ = Math.pow($1, $3);} 83 | | '-' e %prec UMINUS 84 | {$$ = -$2;} 85 | | '(' e ')' 86 | {$$ = $2;} 87 | | NUMBER 88 | {$$ = Number(yytext);} 89 | | E 90 | {$$ = Math.E;} 91 | | PI 92 | {$$ = Math.PI;} 93 | ; 94 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | jison debugger 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | Fork me on GitHub 27 | 28 | 29 | 30 |

Jison debugger!

31 |
32 |
33 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/scripts/ui/util/TreeArtist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function drawAbstractSvg(root) { 4 | 5 | var maxDepth = 0; 6 | var nodes = []; 7 | var paths = []; 8 | 9 | function calculateDepth(node, depth) { 10 | if (depth > maxDepth) { 11 | maxDepth = depth; 12 | } 13 | if (!node.children) { 14 | return; 15 | } 16 | node.children.forEach(function (child) { 17 | calculateDepth(child, depth + 1); 18 | }); 19 | } 20 | 21 | calculateDepth(root, 0); 22 | 23 | var xIncrement = 60; // really a minimum. may expand if nodes have verbose text 24 | var yIncrement = 50; 25 | 26 | function calculateNeededXIncrement(node) { 27 | var estimatedNeededWidth = Math.max(node.name.length, 28 | node.subtitle ? node.subtitle.length : 0); 29 | estimatedNeededWidth *= 8; // 14px / 2, with 1px padding 30 | if (estimatedNeededWidth > xIncrement) { 31 | xIncrement = estimatedNeededWidth; 32 | } 33 | if (node.children) { 34 | node.children.forEach(function (child, i) { 35 | calculateNeededXIncrement(child); 36 | }); 37 | } 38 | } 39 | 40 | calculateNeededXIncrement(root); 41 | 42 | function calculateSubtreeWidth(node) { 43 | node.width = 0; 44 | if (node.children) { 45 | node.children.forEach(function (child, i) { 46 | calculateSubtreeWidth(child); 47 | node.width += child.width; 48 | if (i > 0) { 49 | node.width += xIncrement; 50 | } 51 | }); 52 | } 53 | } 54 | 55 | calculateSubtreeWidth(root); 56 | 57 | function generate(treeNode, depth, x) { 58 | var y = depth * yIncrement; 59 | var children = treeNode.children; 60 | nodes.push({ 61 | name: treeNode.name, 62 | subtitle: treeNode.subtitle, 63 | output: treeNode.output, 64 | x: x, 65 | y: y, 66 | hasChildren: !!children 67 | }); 68 | if (!children) { 69 | return; 70 | } 71 | 72 | for (var i = 0; i < children.length; i++) { 73 | var child = children[i]; 74 | var childX = 0; 75 | for (var j = 0; j < i; j++) { 76 | childX += children[j].width; 77 | childX += xIncrement; 78 | } 79 | childX += (child.width / 2); 80 | childX = x + (childX - (treeNode.width / 2)); 81 | generate(child, depth + 1, childX); 82 | paths.push({ 83 | drawFrom: [x, y], 84 | drawTo: [childX, (depth + 1) * yIncrement] 85 | }); 86 | } 87 | } 88 | 89 | generate(root, 0, xIncrement + (root.width / 2)); 90 | 91 | var svgWidth = root.width + (xIncrement * 2); 92 | var svgHeight = ((maxDepth - 1) * yIncrement); 93 | var yPadding = 120; // padding for text on top and bottom 94 | var yOffset = 40; // offset for text on top 95 | var result = { 96 | paths: paths, 97 | nodes: nodes, 98 | svgHeight: svgHeight, 99 | yPadding: yPadding, 100 | svgWidth: svgWidth, 101 | yOffset: yOffset 102 | }; 103 | 104 | return result; 105 | } 106 | 107 | module.exports = { 108 | drawAbstractSvg: drawAbstractSvg 109 | }; 110 | -------------------------------------------------------------------------------- /app/styles/main.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: "/bower_components/bootstrap-sass-official/assets/fonts/"; 2 | 3 | @import "../bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap"; 4 | 5 | .browsehappy { 6 | margin: 0.2em 0; 7 | background: #ccc; 8 | color: #000; 9 | padding: 0.2em 0; 10 | } 11 | 12 | /* Space out content a bit */ 13 | body { 14 | padding-top: 20px; 15 | padding-bottom: 20px; 16 | } 17 | 18 | /* Everything but the jumbotron gets side spacing for mobile first views */ 19 | .header, 20 | .marketing, 21 | .footer { 22 | padding-left: 15px; 23 | padding-right: 15px; 24 | } 25 | 26 | /* Custom page header */ 27 | .header { 28 | border-bottom: 1px solid #e5e5e5; 29 | 30 | /* Make the masthead heading the same height as the navigation */ 31 | h3 { 32 | margin-top: 0; 33 | margin-bottom: 0; 34 | line-height: 40px; 35 | padding-bottom: 19px; 36 | } 37 | } 38 | 39 | /* Custom page footer */ 40 | .footer { 41 | padding-top: 19px; 42 | color: #777; 43 | border-top: 1px solid #e5e5e5; 44 | } 45 | 46 | .container-narrow > hr { 47 | margin: 30px 0; 48 | } 49 | 50 | /* Main marketing message and sign up button */ 51 | .jumbotron { 52 | text-align: center; 53 | border-bottom: 1px solid #e5e5e5; 54 | .btn { 55 | font-size: 21px; 56 | padding: 14px 24px; 57 | } 58 | } 59 | 60 | /* Supporting marketing content */ 61 | .marketing { 62 | margin: 40px 0; 63 | p + h4 { 64 | margin-top: 28px; 65 | } 66 | } 67 | 68 | .tree-node { 69 | circle { 70 | fill: #fff; 71 | stroke: steelblue; 72 | stroke-width: 3px; 73 | } 74 | text { 75 | font: 12px sans-serif; 76 | fill-opacity: 1; 77 | } 78 | } 79 | 80 | .tree-link { 81 | fill: none; 82 | stroke: #ccc; 83 | stroke-width: 2px; 84 | } 85 | 86 | .token-name { 87 | font-weight: normal; 88 | display: block; 89 | font-size: 13px; 90 | margin-left: 2px; 91 | margin-bottom: 2px; 92 | } 93 | 94 | .token-text { 95 | font-weight: normal; 96 | display: block; 97 | font-size: 13px; 98 | margin-left: 2px; 99 | margin-bottom: 2px; 100 | background-color: #dfdfdf; 101 | color: #333; 102 | } 103 | 104 | .text-muted.muted-link { 105 | cursor: pointer; 106 | margin-left: 10px; 107 | font-size: 12px; 108 | } 109 | 110 | .fullscreen-stroke { 111 | fill: #777; 112 | } 113 | 114 | button:hover.fullscreen-stroke { 115 | fill: #333; 116 | } 117 | 118 | button.btn.btn-fullscreen:focus { 119 | outline: 0; 120 | } 121 | 122 | .modal-backdrop { 123 | position: fixed; 124 | top: 0px; 125 | right: 0px; 126 | bottom: 0px; 127 | left: 0px; 128 | z-index: 1040; 129 | background-color: #000; 130 | opacity: 0.5; 131 | } 132 | 133 | /* Responsive: Portrait tablets and up */ 134 | @media screen and (min-width: 768px) { 135 | .container { 136 | max-width: 1060px; 137 | } 138 | 139 | /* Remove the padding we set earlier */ 140 | .header, 141 | .marketing, 142 | .footer { 143 | text-align: right; 144 | margin: 10px auto; 145 | padding-left: 0; 146 | padding-right: 0; 147 | } 148 | 149 | /* Space out the masthead */ 150 | .header { 151 | margin-bottom: 30px; 152 | } 153 | 154 | /* Remove the bottom border on the jumbotron for visual effect */ 155 | .jumbotron { 156 | border-bottom: 0; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/scripts/util/sample-grammars/chords.jison: -------------------------------------------------------------------------------- 1 | /* 2 | * Musical chord parser by Nolan Lawson. 3 | * Try strings like 4 | * "C/G" or "Fmaj7" or "Abminadd9" or "Dsus4" 5 | * or "C5" or "G#maj7/Bb" 6 | * 7 | * You don't actually need a context-free grammar to 8 | * parse chords (a regex will suffice), but this 9 | * demonstrates how succinct such an implementation can 10 | * be. For a comparison with the regex approach, see 11 | * https://github.com/nolanlawson/chord-magic 12 | */ 13 | 14 | %lex 15 | %% 16 | 17 | \s+ /* skip whitespace */ 18 | [A-G]b?\#? return 'CHORD_ROOT' 19 | // 20 | // qualities 21 | // 22 | (major|maj|M) return 'Major' 23 | (minor|min|m) return 'Minor' 24 | (aug|augmented|\+) return 'Augmented' 25 | (dim|diminished) return 'Diminished' 26 | // 27 | // extendeds 28 | // 29 | (Major|maj7|Maj7|M7|\+7) return 'Major7' 30 | (Minor|m7|Min7|min7|minor7) return 'Minor7' 31 | (Major|7|dom7|dominant7) return 'Dominant7' 32 | (Diminished|dim7|diminished7) return 'Diminished7' 33 | (Major|maj9|M9|9) return 'Major9' 34 | (Major|maj11|M11|11) return 'Major11' 35 | (Major|maj13|M13|13) return 'Major13' 36 | (Major|7#5|7\(#5\)) return 'AugmentedDominant7' 37 | (Major|maj7#5|maj7\(#5\)) return 'AugmentedMajor7' 38 | (Minor|min9|m9|minor9) return 'Minor9' 39 | // 40 | // addeds 41 | // 42 | (add9|2) return 'Add9' 43 | (add11|4) return 'Add4' 44 | (6/9) return 'SixNine' 45 | (6|maj6|major6|M6) return 'Major6' 46 | // duh duh DUH, duh duh DUH-duh, duh duh DUH, duh duh 47 | // ((c) Deep Purple) 48 | 5 return 'PowerChord' 49 | // 50 | // suspendeds 51 | // 52 | (sus2|suspended2) return 'Sus2' 53 | (sus4|suspended|sus) return 'Sus4' 54 | '/' return '/' 55 | <> return 'EOF' 56 | . return 'INVALID' 57 | 58 | /lex 59 | 60 | %ebnf 61 | 62 | // operator associations and precedence 63 | 64 | %start chord 65 | 66 | %% /* language grammar */ 67 | 68 | chord 69 | : c EOF {return $1;} 70 | ; 71 | 72 | // 73 | // This is a neat thing about the ebnf format: 74 | // you can throw in +'s and *'s and ()'s and ?'s 75 | // like we're used to with regular expressions. 76 | // And you can access those variables easily! 77 | // 78 | 79 | c 80 | : root quality? extended? added? suspended? override? 81 | {{ $$ = 82 | { 83 | root: $root, 84 | quality: $c_option0 || 'Major', 85 | extended: $c_option1, 86 | added: $c_option2, 87 | suspended: $c_option3, 88 | overridingRoot: $c_option4 89 | }; 90 | }} 91 | ; 92 | 93 | root 94 | : CHORD_ROOT -> $1 95 | ; 96 | 97 | quality 98 | : Major -> 'Major' 99 | | Minor -> 'Minor' 100 | | Augmented -> 'Augmented' 101 | | Diminished -> 'Diminished' 102 | ; 103 | 104 | extended 105 | : Major7 -> "Major7" 106 | | Minor7 -> "Minor7" 107 | | Dominant7 -> "Dominant7" 108 | | Diminished7 -> "Diminished7" 109 | | Major9 -> "Major9" 110 | | Major11 -> "Major11" 111 | | Major13 -> "Major13" 112 | | AugmentedDominant7 -> "AugmentedDominant7" 113 | | AugmentedMajor7 -> "AugmentedMajor7" 114 | | Minor9 -> "Minor9" 115 | ; 116 | 117 | added 118 | : Add9 -> 'Add9' 119 | | Add4 -> 'Add4' 120 | | SixNine -> 'SixNine' 121 | | Major6 -> 'Major6' 122 | | PowerChord -> 'PowerChord' 123 | ; 124 | 125 | suspended 126 | : Sus2 -> 'Sus2' 127 | | Sus4 -> 'Sus4' 128 | ; 129 | 130 | override 131 | : '/' CHORD_ROOT -> $2 132 | ; 133 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | 5 | var React = window.React = require('react/addons'); 6 | var PureRenderMixin = React.addons.PureRenderMixin; 7 | var mountNode = document.getElementById('app'); 8 | 9 | var Editor = require("./ui/Editor/Editor"); 10 | var GrammarView = require("./ui/GrammarOutput/GrammarView"); 11 | var ParserOutputView = require("./ui/ParserOutput/ParserOutputView"); 12 | var GrammarInputStore = require('./stores/GrammarInputStore'); 13 | var GrammarOutputStore = require('./stores/GrammarOutputStore'); 14 | var ParserOutputStore = require('./stores/ParserOutputStore'); 15 | var ParserWorkerService = require('./data/ParserWorkerService'); 16 | 17 | 18 | var App = React.createClass({ 19 | mixins: [PureRenderMixin], 20 | componentWillMount() { 21 | GrammarInputStore.addChangeListener(this._onGrammarInputChange); 22 | GrammarOutputStore.addChangeListener(this._onGrammarOutputChange); 23 | ParserOutputStore.addChangeListener(this._onParserOutputChange); 24 | }, 25 | componentWillUnmount() { 26 | GrammarInputStore.removeChangeListener(this._onGrammarInputChange); 27 | GrammarOutputStore.removeChangeListener(this._onGrammarOutputChange); 28 | ParserOutputStore.removeChangeListener(this._onParserOutputChange); 29 | }, 30 | getInitialState: function () { 31 | return this._getState(); 32 | }, 33 | _onGrammarInputChange: function () { 34 | this.replaceState(this._getState()); 35 | if (GrammarOutputStore.getActiveCompiledGrammar()) { 36 | // current grammar is valid 37 | ParserWorkerService.parseText(GrammarInputStore.getActiveTextToParse()); 38 | } 39 | }, 40 | _onGrammarOutputChange: function () { 41 | this.replaceState(this._getState()); 42 | if (GrammarOutputStore.getActiveCompiledGrammar()) { 43 | // current grammar is valid 44 | ParserWorkerService.parseText(GrammarInputStore.getActiveTextToParse()); 45 | } 46 | }, 47 | _onParserOutputChange: function () { 48 | this.replaceState(this._getState()); 49 | }, 50 | _getState: function () { 51 | return { 52 | grammar: GrammarInputStore.getActiveGrammar(), 53 | textToParse: GrammarInputStore.getActiveTextToParse(), 54 | compiledError: GrammarOutputStore.getActiveCompiledError(), 55 | compiledGrammar: GrammarOutputStore.getActiveCompiledGrammar(), 56 | compiledParser: GrammarOutputStore.getActiveCompiledParser(), 57 | parsedError: ParserOutputStore.getActiveParsedError(), 58 | parsedResult: ParserOutputStore.getActiveParsedResult(), 59 | parserDebugger: ParserOutputStore.getActiveParserDebugger(), 60 | lexDebugger: ParserOutputStore.getActiveLexDebugger() 61 | }; 62 | }, 63 | render: function () { 64 | var editorProps = { 65 | grammar: this.state.grammar, 66 | textToParse: this.state.textToParse 67 | }; 68 | 69 | var grammarViewProps = { 70 | compiledError: this.state.compiledError, 71 | compiledGrammar: this.state.compiledGrammar, 72 | compiledParser: this.state.compiledParser 73 | }; 74 | 75 | var parserOutputProps = { 76 | parsedError: this.state.parsedError, 77 | parsedResult: this.state.parsedResult, 78 | lexDebugger: this.state.lexDebugger, 79 | parserDebugger: this.state.parserDebugger, 80 | compiledGrammar: this.state.compiledGrammar 81 | }; 82 | 83 | return ( 84 |
85 |
86 | 87 |
88 |
89 | 90 | 91 |
92 |
93 | ); 94 | } 95 | }); 96 | 97 | React.render(, mountNode); 98 | 99 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var del = require('del'); 5 | 6 | 7 | var path = require('path'); 8 | 9 | 10 | // Load plugins 11 | var $ = require('gulp-load-plugins')(); 12 | var sass = require('gulp-ruby-sass'); 13 | var browserify = require('browserify'); 14 | var watchify = require('watchify'); 15 | var source = require('vinyl-source-stream'), 16 | 17 | sourceFile = './app/scripts/app.js', 18 | 19 | destFolder = './dist/scripts', 20 | destFileName = 'app.js'; 21 | 22 | 23 | // Styles 24 | gulp.task('styles', function () { 25 | return sass('app/styles/main.scss', { 26 | loadPath: 'app/', 27 | style: 'expanded' 28 | }) 29 | .pipe(gulp.dest('dist/styles')); 30 | }); 31 | 32 | 33 | // Scripts 34 | gulp.task('scripts', function () { 35 | var bundler =browserify({ 36 | entries: [sourceFile], 37 | insertGlobals: true, 38 | cache: {}, 39 | packageCache: {}, 40 | fullPaths: true 41 | }); 42 | 43 | return bundler.bundle() 44 | // log errors if they happen 45 | .on('error', $.util.log.bind($.util, 'Browserify Error')) 46 | .pipe(source(destFileName)) 47 | .pipe(gulp.dest(destFolder)); 48 | 49 | }); 50 | 51 | // Scripts 52 | gulp.task('scripts_debug', function () { 53 | var bundler = watchify(browserify({ 54 | entries: [sourceFile], 55 | insertGlobals: true, 56 | cache: {}, 57 | packageCache: {}, 58 | fullPaths: true, 59 | debug: true 60 | })); 61 | 62 | function rebundle() { 63 | return bundler.bundle() 64 | // log errors if they happen 65 | .on('error', $.util.log.bind($.util, 'Browserify Error')) 66 | .pipe(source(destFileName)) 67 | .pipe(gulp.dest(destFolder)); 68 | } 69 | 70 | bundler.on('update', rebundle); 71 | 72 | return rebundle(); 73 | }); 74 | 75 | 76 | 77 | 78 | gulp.task('jade', function () { 79 | return gulp.src('app/template/*.jade') 80 | .pipe($.jade({ pretty: true })) 81 | .pipe(gulp.dest('dist')); 82 | }); 83 | 84 | 85 | 86 | // HTML 87 | gulp.task('html', function () { 88 | return gulp.src('app/*.html') 89 | .pipe(gulp.dest('dist')) 90 | .pipe($.size()); 91 | }); 92 | 93 | // Images 94 | gulp.task('images', function () { 95 | return gulp.src('app/images/**/*') 96 | .pipe($.cache($.imagemin({ 97 | optimizationLevel: 3, 98 | progressive: true, 99 | interlaced: true 100 | }))) 101 | .pipe(gulp.dest('dist/images')) 102 | .pipe($.size()); 103 | }); 104 | 105 | 106 | 107 | gulp.task('jest', function () { 108 | var nodeModules = path.resolve('./node_modules'); 109 | return gulp.src('app/scripts/**/__tests__') 110 | .pipe($.jest({ 111 | scriptPreprocessor: nodeModules + '/gulp-jest/preprocessor.js', 112 | unmockedModulePathPatterns: [nodeModules + '/react'] 113 | })); 114 | }); 115 | 116 | 117 | 118 | // Clean 119 | gulp.task('clean', function (cb) { 120 | cb(del.sync(['dist/styles', 'dist/scripts', 'dist/images'])); 121 | }); 122 | 123 | 124 | // Bundle 125 | gulp.task('bundle', ['styles', 'scripts', 'bower', 'worker'], function(){ 126 | return gulp.src('./app/*.html') 127 | .pipe(gulp.dest('dist')); 128 | }); 129 | 130 | // Bundle 131 | gulp.task('bundle_debug', ['styles', 'scripts_debug', 'bower', 'worker'], function(){ 132 | return gulp.src('./app/*.html') 133 | .pipe(gulp.dest('dist')); 134 | }); 135 | 136 | // Webserver 137 | gulp.task('serve', function () { 138 | gulp.src('./dist') 139 | .pipe($.webserver({ 140 | port: 9000 141 | })); 142 | }); 143 | 144 | // Bower helper 145 | gulp.task('bower', function() { 146 | gulp.src('app/bower_components/**/*.js', {base: 'app/bower_components'}) 147 | .pipe(gulp.dest('dist/bower_components/')); 148 | 149 | }); 150 | 151 | gulp.task('worker', function() { 152 | gulp.src('app/scripts/worker/**/*.js', {base: 'app/scripts/worker'}) 153 | .pipe(gulp.dest('dist/worker')); 154 | }); 155 | 156 | gulp.task('json', function() { 157 | gulp.src('app/scripts/json/**/*.json', {base: 'app/scripts'}) 158 | .pipe(gulp.dest('dist/scripts/')); 159 | }); 160 | 161 | // Robots.txt and favicon.ico 162 | gulp.task('extras', function () { 163 | return gulp.src(['app/*.txt', 'app/*.ico']) 164 | .pipe(gulp.dest('dist/')) 165 | .pipe($.size()); 166 | }); 167 | 168 | // Watch 169 | gulp.task('watch', ['html', 'bundle_debug', 'images', 'serve'], function () { 170 | 171 | // Watch .json files 172 | gulp.watch('app/scripts/**/*.json', ['json']); 173 | 174 | // Watch .html files 175 | gulp.watch('app/*.html', ['html']); 176 | 177 | 178 | // Watch .scss files 179 | gulp.watch('app/styles/**/*.scss', ['styles']); 180 | 181 | 182 | 183 | // Watch .jade files 184 | gulp.watch('app/template/**/*.jade', ['jade', 'html']); 185 | 186 | 187 | // Watch image files 188 | gulp.watch('app/images/**/*', ['images']); 189 | }); 190 | 191 | // Build 192 | gulp.task('build', ['html', 'bundle', 'images', 'extras']); 193 | 194 | // Default task 195 | gulp.task('default', ['clean', 'build', 'jest' ]); 196 | -------------------------------------------------------------------------------- /app/scripts/util/sample-grammars/bloop.jison: -------------------------------------------------------------------------------- 1 | /* BlooP and FlooP parser - 2 | * http://en.wikipedia.org/wiki/BlooP_and_FlooP 3 | * author: Zach Carter 4 | */ 5 | 6 | %lex 7 | 8 | ID [A-Z-]+"?"? 9 | NUM ([1-9][0-9]+|[0-9]) 10 | 11 | %options flex case-insensitive 12 | 13 | % 14 | 15 | \s+ /* ignore */ 16 | {NUM} return 'NUMBER' 17 | 18 | DEFINE return 'DEFINE' 19 | PROCEDURE return 'PROCEDURE' 20 | BLOCK return 'BLOCK' 21 | BEGIN return 'BEGIN' 22 | OUTPUT return 'OUTPUT' 23 | CELL return 'CELL' 24 | IF return 'IF' 25 | THEN return 'THEN' 26 | LOOP return 'LOOP' 27 | "MU-LOOP" return yy.bloop ? 'INVALID' : 'MU_LOOP' 28 | AT return 'AT' 29 | MOST return 'MOST' 30 | TIMES return 'TIMES' 31 | ABORT return 'ABORT' 32 | END return 'END' 33 | QUIT return 'QUIT' 34 | AND return 'AND' 35 | YES return 'YES' 36 | NO return 'NO' 37 | {ID} return 'IDENT' 38 | "." return '.' 39 | "''" return 'QUOTE' 40 | "[" return '[' 41 | "]" return ']' 42 | "(" return '(' 43 | ")" return ')' 44 | "{" return '{' 45 | "}" return '}' 46 | ":" return ':' 47 | ";" return ';' 48 | "," return ',' 49 | "+" return '+' 50 | "*" return '*' 51 | "×" return '*' //non-ascii 52 | "<=" return '<=' 53 | "â‡" return '<=' //non-ascii 54 | "<" return '<' 55 | ">" return '>' 56 | "=" return '=' 57 | <> return 'EOF' 58 | . return 'INVALID' 59 | 60 | /lex 61 | 62 | /* Code blocks are inserted at the top of the generated module. */ 63 | %{ 64 | function AstNode (attr, children, loc) { 65 | for (var prop in attr) { 66 | this[prop] = attr[prop]; 67 | } 68 | this._children = children||[]; 69 | assignParent(this, this._children); 70 | this.loc = loc; 71 | } 72 | 73 | AstNode.prototype = { 74 | children: function (children) { 75 | if (children) { 76 | assignParent(this, this._children); 77 | this._children = children; 78 | } 79 | return this._children; 80 | }, 81 | append: function (node) { 82 | node.parent = this; 83 | this._children.push(node); 84 | return this; 85 | }, 86 | toAst: function (tab) { 87 | tab = tab || ''; 88 | var kids = this.children().map(function(kid) { 89 | return !Array.isArray(kid) ? kid.toAst(tab + ' ') : kid.map(function(k) { return k.toAst(tab + ' '); }).join(', '); 90 | }); 91 | return tab + this.type + ' 92 | ' + kids.join(' 93 | '); 94 | } 95 | }; 96 | 97 | function assignParent(parent, kids) { 98 | kids.forEach(function(kid) { 99 | kid.parent = parent; 100 | }); 101 | } 102 | 103 | var NODES = [ 104 | 'Program', 105 | 'ProcedureStmt', 106 | 'BlockStmt', 107 | 'LoopStmt', 108 | 'MuLoopStmt', 109 | 'NumberLit', 110 | 'BooleanLit', 111 | 'OutputExpr', 112 | 'Identifier', 113 | 'CellExpr', 114 | 'PlusExpr', 115 | 'TimesExpr', 116 | 'ApplyExpr', 117 | 'LessCond', 118 | 'GreaterCond', 119 | 'GreaterCond', 120 | 'EqualCond', 121 | 'CompoundCond', 122 | 'AssignStmt', 123 | 'IfThenStmt', 124 | 'QuitStmt', 125 | 'AbortStmt' 126 | ]; 127 | 128 | var ast = {}; 129 | 130 | NODES.forEach(function (type) { 131 | ast[type] = function (attr, a, b, c, d) { 132 | var obj = new AstNode(attr, a, b, c, d); 133 | obj.type = type; 134 | return obj; 135 | }; 136 | }); 137 | 138 | var Program = ast.Program, 139 | ProcedureStmt = ast.ProcedureStmt, 140 | BlockStmt = ast.BlockStmt, 141 | LoopStmt = ast.LoopStmt, 142 | MuLoopStmt = ast.MuLoopStmt, 143 | NumberLit = ast.NumberLit, 144 | BooleanLit = ast.BooleanLit, 145 | OutputExpr = ast.OutputExpr, 146 | Identifier = ast.Identifier, 147 | CellExpr = ast.CellExpr, 148 | PlusExpr = ast.PlusExpr, 149 | TimesExpr = ast.TimesExpr, 150 | ApplyExpr = ast.ApplyExpr, 151 | LessCond = ast.LessCond, 152 | GreaterCond = ast.GreaterCond, 153 | GreaterCond = ast.GreaterCond, 154 | EqualCond = ast.EqualCond, 155 | CompoundCond = ast.CompoundCond, 156 | AssignStmt = ast.AssignStmt, 157 | IfThenStmt = ast.IfThenStmt, 158 | QuitStmt = ast.QuitStmt, 159 | AbortStmt = ast.AbortStmt; 160 | 161 | %} 162 | 163 | %nonassoc '+' 164 | %nonassoc '*' 165 | 166 | /* enable EBNF grammar syntax */ 167 | %ebnf 168 | 169 | % 170 | 171 | program 172 | : procedure* EOF 173 | { return Program({},$1) } 174 | ; 175 | 176 | procedure 177 | : DEFINE PROCEDURE QUOTE IDENT QUOTE '[' (identifier ',')* identifier? ']' ':' block '.' 178 | -> ProcedureStmt({name:$4},[$7.concat([$8]),$11]) 179 | ; 180 | 181 | block 182 | : BLOCK NUMBER ':' BEGIN (statement ';')+ BLOCK NUMBER ':' END 183 | -> BlockStmt({id: $2},$5) 184 | ' ; 185 | 186 | statement 187 | : cell '<=' expression -> AssignStmt({}, [$1, $3]) 188 | | output '<=' expression -> AssignStmt({}, [$1, $3]) 189 | | LOOP (AT MOST)? expression TIMES ':' block -> LoopStmt({}, [$3, $6]) 190 | | MU_LOOP ':' block -> MuLoopStmt({}, [$3]) 191 | | IF condition ',' THEN ':' (statement | block) -> IfThenStmt({}, [$2, $6]) 192 | | QUIT BLOCK NUMBER -> QuitStmt({id: $3}) 193 | | ABORT LOOP NUMBER -> AbortStmt({id: $3}) 194 | ; 195 | 196 | condition 197 | : expression 198 | | expression '<' expression -> )LessCond({}, [$1, $3]) 199 | | expression '>' expression -> GreaterCond({}, [$1, $3]) 200 | | expression '=' expression -> EqualCond({}, [$1, $3]) 201 | | '{' condition AND condition '}' -> CompoundCond({}, [$1, $3]) 202 | ; 203 | 204 | expression 205 | : NUMBER -> NumberLit({value: $1}, []) 206 | | identifier 207 | | IDENT '[' (expression ',')* expression? ']' -> ApplyExpr({name:$1}, $3.concat([$4])) 208 | | cell 209 | | output 210 | | NO -> BooleanLit({value: false}, []) 211 | | YES -> BooleanLit({value: true}, []) 212 | | expression '+' expression -> PlusExpr({}, [$1, $3]) 213 | | expression '*' expression -> TimesExpr({}, [$1, $3]) 214 | ; 215 | 216 | output 217 | : OUTPUT -> OutputExpr({},[]) 218 | ; 219 | 220 | cell 221 | : CELL '(' NUMBER ')' -> CellExpr({id: $3}) 222 | ; 223 | 224 | identifier 225 | : IDENT -> Identifier({value: $1}) 226 | ; 227 | 228 | % 229 | 230 | // additional user code here 231 | -------------------------------------------------------------------------------- /app/scripts/ui/ParserOutput/ParseTreeGraphView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @jsx React.DOM */ 4 | var PureRenderMixin = require('React/addons').addons.PureRenderMixin; 5 | 6 | var TreeArtist = require('../util/TreeArtist'); 7 | var TreeBuilder = require('../util/TreeBuilder'); 8 | var Colorizer = require('./../util/Colorizer'); 9 | 10 | var ParseTreeGraphView = React.createClass({ 11 | mixins: [PureRenderMixin], 12 | getInitialState: function () { 13 | return {expanded: false}; 14 | }, 15 | _toggleExpanded: function () { 16 | this.setState({expanded: !this.state.expanded}); 17 | }, 18 | render: function () { 19 | var parserDebugger = this.props.parserDebugger || []; 20 | 21 | var root = TreeBuilder.buildTree(parserDebugger); 22 | 23 | var abstractSvg = TreeArtist.drawAbstractSvg(root); 24 | var svgHeight = abstractSvg.svgHeight + abstractSvg.yPadding; 25 | var svgWidth = abstractSvg.svgWidth; 26 | var yOffset = abstractSvg.yOffset; 27 | var allPathsAndNodes = abstractSvg.paths.slice().concat(abstractSvg.nodes); 28 | 29 | var transform = 'translate(0,' + yOffset + ')'; 30 | 31 | // viewBox basically makes the SVG auto-responsive. 2015 is amazing. 32 | var viewBox = "0 0 " + svgWidth + " " + svgHeight; 33 | 34 | function renderPath(path) { 35 | var halfwayY = (path.drawFrom[1] + path.drawTo[1]) / 2; 36 | var d = 'M ' + path.drawFrom[0] + ',' + path.drawFrom[1] + ' ' + 37 | 'C' + path.drawFrom[0] + ',' + halfwayY + ' ' + 38 | path.drawTo[0] + ',' + halfwayY + ' ' + 39 | path.drawTo[0] + ',' + path.drawTo[1]; 40 | 41 | return ( 42 | 43 | ) 44 | } 45 | 46 | function renderNode(node) { 47 | var translate = "translate(" + node.x + "," + node.y + ")"; 48 | 49 | var labels = [{ 50 | value: node.name, 51 | y: -20, 52 | style: { 53 | fontSize: 14, 54 | fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif" 55 | } 56 | }]; 57 | 58 | var nodeStyle = {}; 59 | if (node.subtitle) { 60 | // terminal 61 | nodeStyle.stroke = Colorizer.getColorFor(node.name); 62 | labels.push({ 63 | value: node.subtitle, 64 | y: 20, 65 | style: { 66 | fontSize: 14, 67 | fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif" 68 | } 69 | }); 70 | } 71 | 72 | return ( 73 | 76 | {node.output} 77 | 78 | { 79 | labels.map(function (label, i) { 80 | var labelKey = [label, i]; 81 | return ( 82 | 87 | {label.value} 88 | 89 | ) 90 | }) 91 | } 92 | 93 | ) 94 | } 95 | 96 | var svgStyle = { 97 | width: '100%', 98 | maxHeight: svgHeight 99 | }; 100 | 101 | var containerStyle = { 102 | position: 'relative' 103 | }; 104 | 105 | var buttonStyle = { 106 | position: 'absolute', 107 | right: 20, 108 | bottom: 0, 109 | padding: '3px 5px 0px 5px' 110 | }; 111 | 112 | var innerButtonStyle = { 113 | position: 'absolute', 114 | right: 20, 115 | top: 15, 116 | padding: '2px 9px 0px 9px', 117 | color: '#777' 118 | }; 119 | 120 | var modalStyle = { 121 | position: 'fixed', 122 | background: '#fff', 123 | border: '1px solid rgba(0, 0, 0, 0.2)', 124 | borderRadius: 6, 125 | top: 20, 126 | left: 20, 127 | right: 20, 128 | bottom: 20, 129 | zIndex: 999999, 130 | paddingTop: 60, 131 | paddingBottom: 60, 132 | textAlign: 'center' 133 | }; 134 | 135 | var modalSvgStyle = { 136 | height: '100%', 137 | maxWidth: '100%' 138 | }; 139 | 140 | return ( 141 |
142 | 146 | 147 | { 148 | allPathsAndNodes.map(function (el) { 149 | 150 | if (el.drawFrom) { // path 151 | return renderPath(el); 152 | } else { // node 153 | return renderNode(el); 154 | } 155 | }) 156 | } 157 | 158 | 159 | { this.state.expanded && 160 | ( 161 |
162 |
163 |
164 | 168 | 169 | { 170 | allPathsAndNodes.map(function (el) { 171 | return el.drawFrom ? renderPath(el) : renderNode(el); 172 | }) 173 | } 174 | 175 | 176 | 183 |
184 |
185 | ) 186 | } 187 | 188 | 200 |
201 | ); 202 | } 203 | }); 204 | 205 | module.exports = ParseTreeGraphView; 206 | 207 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------