├── .gitignore ├── README.md ├── app ├── css │ ├── bootstrap.css │ └── main.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── js │ ├── components │ ├── App.js │ ├── Code.js │ ├── ExpressionViewer.js │ ├── Figure.js │ ├── InteractiveExpression.js │ ├── InteractiveFigure.js │ ├── InteractiveNfa.js │ ├── InteractiveProgram.js │ ├── NfaViewer.js │ ├── ProgramViewer.js │ ├── RegexInput.js │ ├── RegexInspector.js │ └── RegexTestBox.js │ ├── main.js │ └── visualization.js ├── gulpfile.js ├── index.js ├── lib ├── CharRegex.js ├── CharRegex │ ├── env.js │ └── parser.js ├── baseparser.js ├── env.js ├── fragment.js ├── nfa.js ├── parsers.js ├── predicates.js ├── syntax.js └── vm.js ├── package.json └── test └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | *.swp 30 | build/ 31 | .publish 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # thompson-regex-js 2 | ## [Click here for a demo](http://afader.github.io/thompson-regex-js/) 3 | This code implements a regular expression library, including: 4 | 5 | * A parser that maps regular expressions to an abstract syntax tree 6 | * A compiler that evaluates the abstract syntax tree to an automaton 7 | * A simulator that runs the automaton against input strings 8 | 9 | This is largely a reimplementation of [Russ Cox's work](https://swtch.com/~rsc/regexp/regexp1.html) 10 | in javascript. 11 | -------------------------------------------------------------------------------- /app/css/main.css: -------------------------------------------------------------------------------- 1 | .code { 2 | font-family: monospace; 3 | background-color: #f1ede4; 4 | border-radius: 5px; 5 | padding: 2px; 6 | } 7 | 8 | em { 9 | background-color: #FF9; 10 | font-style: normal; 11 | padding: 2px; 12 | } 13 | 14 | .program { 15 | overflow: auto; 16 | } 17 | 18 | .figure > .panel > .panel-body { 19 | width: 100%; 20 | margin-bottom: 5px; 21 | display: flex; 22 | flex-direction: column; 23 | height: 400px; 24 | } 25 | 26 | .figure > .panel > .panel-body > div { 27 | width: 100%; 28 | flex-shrink: 1; 29 | } 30 | 31 | .figure > .panel > .panel-body > div:last-child { 32 | width: 100%; 33 | flex-grow: 1; 34 | flex-direction: column; 35 | } 36 | 37 | .caption { 38 | text-align: center; 39 | width: 100%; 40 | padding-left: 10px; 41 | padding-right: 10px; 42 | font-style: italic; 43 | } 44 | 45 | body { 46 | } 47 | 48 | p { 49 | line-height: 1.75; 50 | margin-top: 20px; 51 | margin-bottom: 20px; 52 | } 53 | -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afader/thompson-regex-js/652aff243eeeab143c2c0695dc8f782e74f180d9/app/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afader/thompson-regex-js/652aff243eeeab143c2c0695dc8f782e74f180d9/app/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afader/thompson-regex-js/652aff243eeeab143c2c0695dc8f782e74f180d9/app/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afader/thompson-regex-js/652aff243eeeab143c2c0695dc8f782e74f180d9/app/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/js/components/App.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var InteractiveExpression = require('./InteractiveExpression.js'); 4 | var InteractiveNfa = require('./InteractiveNfa.js'); 5 | var InteractiveProgram = require('./InteractiveProgram.js'); 6 | 7 | var App = React.createClass({ 8 | render: function() { 9 | return ( 10 | 11 | 12 |

Visualizing Regular Expressions

13 |

The first visualization is the parse tree view of regular 14 | expressions. This this visualizes the syntax of a regular expression 15 | as a tree.

16 | 17 |

The second visualization is the NFA view of regular expressions. 18 | This visualizes the semantics of a regular expression as a graph. 19 | A string that matches the regular expression can be mapped to a path 20 | through the graph starting at the start node and ending at the 21 | final bolded node.

22 | 23 |

The third visualization is the machine code view of regular 24 | expressions. This visualizes the semantics of a regular expression as 25 | a sequence of instructions. A string that matches the regular 26 | expression can be mapped to an execution path through the machine 27 | code, terminating at the match instruction.

28 | 29 |
30 |
31 | ); 32 | } 33 | }); 34 | module.exports = App; 35 | -------------------------------------------------------------------------------- /app/js/components/Code.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | module.exports = React.createClass({ 3 | render: function() { 4 | return {this.props.children}; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /app/js/components/ExpressionViewer.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var vis = require('vis'); 4 | var visualization = require('../visualization.js'); 5 | var CharRegex = require('../../../lib/CharRegex.js'); 6 | var ExpressionViewer = React.createClass({ 7 | drawGraph: function() { 8 | var cont = React.findDOMNode(this.refs.container); 9 | var data = visualization.expressionGraph(this.regex.expression); 10 | var net = new vis.Network(cont, data, visualization.config.expression); 11 | var parentHeight = React.findDOMNode(this).offsetHeight; 12 | net.setOptions({height: String(parentHeight)}); 13 | }, 14 | componentDidMount: function() { 15 | if (this.didCompile) this.drawGraph(); 16 | }, 17 | componentDidUpdate: function() { 18 | if (this.didCompile) this.drawGraph(); 19 | }, 20 | render: function() { 21 | try { 22 | this.regex = new CharRegex(this.props.regex); 23 | this.didCompile = true; 24 | return
; 25 | } catch(err) { 26 | this.didCompile = false; 27 | return {err}; 28 | } 29 | } 30 | }); 31 | module.exports = ExpressionViewer; 32 | -------------------------------------------------------------------------------- /app/js/components/Figure.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var Figure = React.createClass({ 4 | render: function() { 5 | var caption = this.props.caption; 6 | var captionComponent; 7 | if (caption) { 8 | captionComponent =
{caption}
; 9 | } 10 | var content = this.props.children; 11 | return ( 12 |
13 | 14 | {content} 15 | 16 | {captionComponent} 17 |
18 | ); 19 | } 20 | }); 21 | module.exports = Figure; 22 | -------------------------------------------------------------------------------- /app/js/components/InteractiveExpression.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ExpressionViewer = require('./ExpressionViewer.js'); 3 | var InteractiveFigure = require('./InteractiveFigure.js'); 4 | module.exports = InteractiveFigure(ExpressionViewer, 'regex'); 5 | -------------------------------------------------------------------------------- /app/js/components/InteractiveFigure.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var Input = Bootstrap.Input; 4 | var Figure = require('./Figure.js'); 5 | 6 | var wrapInteractiveFigure = function(Component, propName) { 7 | return React.createClass({ 8 | getInitialState: function() { 9 | return { value: this.props[propName] }; 10 | }, 11 | componentDidMount: function() { 12 | this.setState({value: this.props.defaultValue }); 13 | }, 14 | setChildState: function(val) { 15 | var state = this.state; 16 | state[propName] = val; 17 | this.setState(state); 18 | }, 19 | getInputValue: function() { 20 | var node = React.findDOMNode(this.refs.input); 21 | var val = node.getElementsByTagName('input')[0].value 22 | return val; 23 | }, 24 | handleSubmit: function(e) { 25 | e.preventDefault(); 26 | this.setChildState(this.getInputValue()); 27 | }, 28 | render: function() { 29 | var caption = this.props.caption; 30 | var captionComponent; 31 | var input = ; 32 | var button = Submit; 33 | input = React.cloneElement(input, {buttonAfter: button}); 34 | if (caption) { 35 | captionComponent =
{caption}
; 36 | } 37 | return ( 38 |
39 |
{input}
40 | 41 |
42 | ); 43 | } 44 | }); 45 | }; 46 | 47 | module.exports = wrapInteractiveFigure; 48 | -------------------------------------------------------------------------------- /app/js/components/InteractiveNfa.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var InteractiveFigure = require('./InteractiveFigure.js'); 3 | var NfaViewer = require('./NfaViewer.js'); 4 | module.exports = InteractiveFigure(NfaViewer, 'regex'); 5 | -------------------------------------------------------------------------------- /app/js/components/InteractiveProgram.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var InteractiveFigure = require('./InteractiveFigure.js'); 3 | var ProgramViewer = require('./ProgramViewer.js'); 4 | module.exports = InteractiveFigure(ProgramViewer, 'regex'); 5 | -------------------------------------------------------------------------------- /app/js/components/NfaViewer.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var vis = require('vis'); 4 | var visualization = require('../visualization.js'); 5 | var CharRegex = require('../../../lib/CharRegex.js'); 6 | var NfaViewer = React.createClass({ 7 | drawGraph: function() { 8 | var cont = React.findDOMNode(this.refs.container); 9 | var data = visualization.nfaGraph(this.regex.nfa); 10 | var net = new vis.Network(cont, data, visualization.config.nfa); 11 | var parentHeight = React.findDOMNode(this).offsetHeight; 12 | net.setOptions({height: String(parentHeight)}); 13 | }, 14 | componentDidMount: function() { 15 | if (this.didCompile) this.drawGraph(); 16 | }, 17 | componentDidUpdate: function() { 18 | if (this.didCompile) this.drawGraph(); 19 | }, 20 | render: function() { 21 | try { 22 | this.regex = new CharRegex(this.props.regex); 23 | this.didCompile = true; 24 | return
; 25 | } catch(err) { 26 | this.didCompile = false; 27 | return {err}; 28 | } 29 | } 30 | }); 31 | module.exports = NfaViewer; 32 | -------------------------------------------------------------------------------- /app/js/components/ProgramViewer.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var CharRegex = require('../../../lib/CharRegex.js'); 4 | 5 | var render = {}; 6 | render.predicate = function(instr) { 7 | return 'equals ' + instr.name; 8 | }; 9 | render.jump = function(instr, i) { 10 | var target = i + instr.increment; 11 | return 'jump to ' + target; 12 | }; 13 | render.split = function(instr, i) { 14 | var target1 = i + 1; 15 | var target2 = i + instr.increment; 16 | return 'split to ' + target1 + ', ' + target2; 17 | }; 18 | render.match = function(instr, i) { 19 | return 'match'; 20 | }; 21 | 22 | var ProgramViewer = React.createClass({ 23 | renderRow: function(instr, i) { 24 | var string = render[instr.type](instr, i); 25 | return ( 26 | 27 | {i} 28 | {string} 29 | 30 | ); 31 | }, 32 | render: function() { 33 | try { 34 | var regex = new CharRegex(this.props.regex); 35 | var rows = regex.program.map(this.renderRow); 36 | return ( 37 |
38 | 39 | 40 | 41 | Program Counter 42 | Instruction 43 | 44 | 45 | 46 | {rows} 47 | 48 | 49 |
50 | ); 51 | } catch (err) { 52 | return {err}; 53 | } 54 | } 55 | }); 56 | module.exports = ProgramViewer; 57 | -------------------------------------------------------------------------------- /app/js/components/RegexInput.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var RegexInput = React.createClass({ 4 | getInitialState: function() { 5 | return {value: this.props.value}; 6 | }, 7 | handleSubmit: function(e) { 8 | e.preventDefault(); 9 | this.props.onSubmit(this.state.value); 10 | }, 11 | handleChange: function(e) { 12 | this.setState({value: e.target.value}); 13 | }, 14 | render: function() { 15 | var button = Inspect; 16 | return ( 17 |
18 | 24 | 25 | ); 26 | return

Input

; 27 | } 28 | }); 29 | module.exports = RegexInput; 30 | -------------------------------------------------------------------------------- /app/js/components/RegexInspector.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var ExpressionViewer = require('./ExpressionViewer.js'); 4 | var NfaViewer = require('./NfaViewer.js'); 5 | var ProgramViewer = require('./ProgramViewer.js'); 6 | var Figure = require('./Figure.js'); 7 | var RegexInput = require('./RegexInput.js'); 8 | var CharRegex = require('../../../lib/CharRegex.js'); 9 | 10 | var RegexInspector = React.createClass({ 11 | getInitialState: function() { 12 | return { 13 | input: 'a(bb)+a' 14 | }; 15 | }, 16 | handleSubmit: function(value) { 17 | this.setState({input: value}); 18 | }, 19 | render: function() { 20 | var regex = new CharRegex(this.state.input); 21 | var exprView = ; 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 | ); 36 | } 37 | }); 38 | module.exports = RegexInspector; 39 | -------------------------------------------------------------------------------- /app/js/components/RegexTestBox.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Bootstrap = require('react-bootstrap'); 3 | var CharRegex = require('../../../lib/CharRegex.js'); 4 | var RegexTextBox = React.createClass({ 5 | getInitialState: function() { 6 | return { value: this.props.initial }; 7 | }, 8 | handleChange: function(e) { 9 | this.setState({value: e.target.value}); 10 | }, 11 | render: function() { 12 | var pattern = this.props.pattern; 13 | var regex = new CharRegex(pattern); 14 | var value = this.state.value; 15 | var matches = regex.match(value); 16 | var style = matches ? 'success' : 'error'; 17 | return 18 | } 19 | }); 20 | module.exports = RegexTextBox; 21 | -------------------------------------------------------------------------------- /app/js/main.js: -------------------------------------------------------------------------------- 1 | require('../css/bootstrap.css'); 2 | require('../css/main.css'); 3 | var React = require('react'); 4 | var App = require('./components/App.js'); 5 | React.render(, document.body); 6 | -------------------------------------------------------------------------------- /app/js/visualization.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | var noInteraction = { 3 | dragNodes: false, 4 | dragView: true, 5 | selectable: false 6 | }; 7 | config.expression = { 8 | layout: { 9 | hierarchical: { 10 | direction: 'UD', 11 | levelSeparation: 100 12 | } 13 | }, 14 | edges: { 15 | arrows: 'to', 16 | color: 'black' 17 | }, 18 | nodes: { 19 | color: { 20 | border: 'black', 21 | background: 'white' 22 | }, 23 | }, 24 | interaction: noInteraction 25 | }; 26 | config.nfa = { 27 | layout: { 28 | randomSeed: 2 29 | }, 30 | edges: { 31 | arrows: 'to', 32 | color: 'grey', 33 | font: { 34 | align: 'horizontal' 35 | } 36 | }, 37 | nodes: { 38 | color: { 39 | border: 'black', 40 | background: 'white' 41 | } 42 | }, 43 | interaction: noInteraction 44 | }; 45 | 46 | var expressionGraph = function(rootExpr) { 47 | var nextId = 0; 48 | var makeNode = function(expr, level) { 49 | var label = expr.type == 'predicate' ? expr.data.name : expr.type; 50 | return { 51 | id: nextId++, 52 | label: label, 53 | expr: expr, 54 | level: level 55 | }; 56 | }; 57 | var rootNode = makeNode(rootExpr, 0); 58 | var nodes = [rootNode]; 59 | var edges = []; 60 | var frontier = [rootNode]; 61 | while (frontier.length > 0) { 62 | var previous = frontier.pop(); 63 | previous.expr.children.map(function(expr) { 64 | var next = makeNode(expr, previous.level + 1); 65 | var edge = {from: previous.id, to: next.id}; 66 | nodes.push(next); 67 | edges.push(edge); 68 | frontier.push(next); 69 | }); 70 | }; 71 | return {nodes: nodes, edges: edges}; 72 | }; 73 | 74 | var nfaGraph = function(nfa) { 75 | var nodes = [{id: 'start', color: 'rgba(0,0,0,0)'}]; 76 | var edges = [{from: 'start', to: '0', label: 'start', color: 'black'}]; 77 | for (var i = 0; i < nfa.numStates; i++) { 78 | var thisId = String(i); 79 | var node = { id: thisId, label: thisId }; 80 | if (i == nfa.finalState) { 81 | node.borderWidth = 5; 82 | } 83 | nodes.push(node); 84 | var trans = nfa.transitions[i]; 85 | Object.keys(trans).forEach(function(target) { 86 | var nextId = String(target); 87 | var label = trans[target]; 88 | edges.push({from: thisId, to: nextId, label: label}); 89 | }); 90 | } 91 | return {nodes: nodes, edges: edges}; 92 | }; 93 | 94 | module.exports = { 95 | expressionGraph: expressionGraph, 96 | config: config, 97 | nfaGraph: nfaGraph 98 | }; 99 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var gutil = require("gulp-util"); 3 | var del = require('del'); 4 | var path = require('path'); 5 | var webpack = require("webpack"); 6 | var WebpackDevServer = require("webpack-dev-server"); 7 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | var ghPages = require('gulp-gh-pages'); 9 | 10 | var config = { 11 | buildDir: 'build', 12 | entry: './app/js/main.js', 13 | bundle: 'app.js', 14 | title: 'thompson-regex-js' 15 | } 16 | 17 | var webpackConfig = { 18 | entry: config.entry, 19 | output: { 20 | path: path.join(__dirname, config.buildDir), 21 | filename: config.bundle 22 | }, 23 | module: { 24 | loaders: [ 25 | { test: /\.css$/, loader: "style!css" }, 26 | { test: /\.js$/, loader: "jsx-loader" }, 27 | { test: /\.json$/, loader: "json-loader" }, 28 | { test: /\.jsx$/, loader: "jsx-loader?insertPragma=React.DOM" }, 29 | { test: /\.woff$/, loader: "url-loader?prefix=font/&limit=5000&mimetype=application/font-woff" }, 30 | { test: /\.woff2$/, loader: "url-loader?prefix=font/&limit=5000&mimetype=application/font-woff2" }, 31 | { test: /\.ttf$/, loader: "file-loader?prefix=font/" }, 32 | { test: /\.eot$/, loader: "file-loader?prefix=font/" }, 33 | { test: /\.svg$/, loader: "file-loader?prefix=font/" } 34 | ] 35 | }, 36 | plugins: [ 37 | new HtmlWebpackPlugin({ 38 | title: config.title, 39 | filename: 'index.html' 40 | }) 41 | ] 42 | }; 43 | 44 | gulp.task('clean', function(callback) { 45 | del([config.buildDir], callback); 46 | }); 47 | 48 | gulp.task('webpack', function(callback) { 49 | webpack(webpackConfig, function(err, stats) { 50 | if (err) throw new gutil.PluginError("webpack", err); 51 | gutil.log("[webpack]", stats.toString()); 52 | callback(); 53 | }); 54 | }); 55 | 56 | gulp.task('webpack-dev-server', function(callback) { 57 | var myConfig = Object.create(webpackConfig); 58 | myConfig.devtool = "eval"; 59 | myConfig.debug = true; 60 | var wp = webpack(myConfig); 61 | new WebpackDevServer(wp).listen(8080, "localhost", function(err) { 62 | if(err) throw new gutil.PluginError("webpack-dev-server", err); 63 | gutil.log("[webpack-dev-server]", "http://localhost:8080"); 64 | }); 65 | }); 66 | 67 | gulp.task('default', ['webpack-dev-server']); 68 | 69 | gulp.task('ghPages', ['webpack'], function() { 70 | return gulp.src('./build/**/*') 71 | .pipe(ghPages()); 72 | }); 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var nfa = require('./lib/nfa.js'); 2 | var parsers = require('./lib/parsers.js'); 3 | var predicates = require('./lib/predicates.js'); 4 | module.exports = { 5 | nfa: nfa, 6 | parsers: parsers, 7 | predicates: predicates 8 | }; 9 | -------------------------------------------------------------------------------- /lib/CharRegex.js: -------------------------------------------------------------------------------- 1 | var parser = require('./CharRegex/parser.js'); 2 | var env = require('./CharRegex/env.js'); 3 | var vm = require('./vm.js'); 4 | var nfa = require('./nfa.js'); 5 | var Parsimmon = require('Parsimmon'); 6 | 7 | var CharRegex = function(pattern) { 8 | this.pattern = pattern; 9 | this.parseResults = parser.parse(pattern); 10 | if (!this.parseResults.status) { 11 | var error = Parsimmon.formatError(pattern, this.parseResults) 12 | throw "Could not parse: " + error; 13 | } 14 | this.expression = this.parseResults.value; 15 | this.program = vm.compile(this.expression); 16 | this.nfa = nfa.compile(this.expression); 17 | this.match = function(input) { 18 | return vm.run(this.program, input, env); 19 | } 20 | }; 21 | 22 | module.exports = CharRegex; 23 | -------------------------------------------------------------------------------- /lib/CharRegex/env.js: -------------------------------------------------------------------------------- 1 | var predicates = require('../predicates.js'); 2 | var env = function(symbol) { 3 | if (symbol == 'wildcard') { 4 | return predicates.alwaysTrue; 5 | } else if (symbol == 'space') { 6 | return predicates.equals(' '); 7 | } else { 8 | return predicates.equals(symbol); 9 | } 10 | }; 11 | module.exports = env; 12 | -------------------------------------------------------------------------------- /lib/CharRegex/parser.js: -------------------------------------------------------------------------------- 1 | var syntax = require('../syntax.js'); 2 | var parsers = require('../parsers.js'); 3 | var seq = parsers.seq; 4 | var string = parsers.string; 5 | var alt = parsers.alt; 6 | var lazy = parsers.lazy; 7 | var regexParser = parsers.regex; 8 | var sepBy = parsers.sepBy; 9 | var pipe = parsers.pipe; 10 | var dot = parsers.dot; 11 | var parenthesized = parsers.parenthesized; 12 | var lsqr = parsers.lsqr; 13 | var rsqr = parsers.rsqr; 14 | var caret = parsers.caret; 15 | var lparen = parsers.lparen; 16 | var rparen = parsers.rparen; 17 | var star = parsers.star; 18 | var plus = parsers.plus; 19 | var qmark = parsers.qmark; 20 | var starred = parsers.starred(syntax.zeroOrMore); 21 | var plussed = parsers.plussed(syntax.oneOrMore); 22 | var qmarked = parsers.qmarked(syntax.zeroOrOne); 23 | var root = lazy('root expression', function() { 24 | return regex.map(syntax.root); 25 | }); 26 | var regex = lazy('regex', function() { 27 | return sepBy(branch, pipe).map(syntax.alternation); 28 | }); 29 | var wildcard = dot.map(function() { 30 | return syntax.predicate('wildcard'); 31 | }).desc('wildcard'); 32 | var escaped = function(str, name) { 33 | if (!name) name = str; 34 | return string('\\' + str).result(syntax.predicate(name)).desc(name); 35 | }; 36 | var character = regexParser(/[^\\*+?()\[\].|-]/).map(syntax.predicate).desc('character'); 37 | var space = string(' ').result(syntax.predicate('space')).desc('space'); 38 | var dash = string('-').result(syntax.predicate('-')).desc('-'); 39 | var escapedBackslash = escaped('\\'); 40 | var whitespace = escaped('s', 'whitespace'); 41 | var newline = escaped('n', 'newline'); 42 | var word = escaped('w', 'wordChar'); 43 | var escapedLparen = escaped('('); 44 | var escapedRparen = escaped(')'); 45 | var escapedLsqr = escaped('['); 46 | var escapedRsqr = escaped(']'); 47 | var escapedStar = escaped('*'); 48 | var escapedPlus = escaped('+'); 49 | var escapedQmark = escaped('?'); 50 | var escapedPipe = escaped('|'); 51 | var escapedDot = escaped('.'); 52 | var escapedSymbol = alt(escapedBackslash, escapedLparen, escapedRparen, escapedPlus, escapedQmark, 53 | escapedRsqr, escapedLsqr, word, newline, whitespace, escapedDot, escapedPipe); 54 | var literal = alt(escapedBackslash, whitespace, newline, word, space, character, lparen, rparen, star, plus, qmark, pipe, dot); 55 | var literals = sepBy(literal, string('')).map(syntax.alternation); 56 | //var rangeValue = alt(escapedSymbol, nonDashCharacter); 57 | //var ranges = alt(positiveRange, negativeRange); 58 | var charClass = lsqr.then(literals).skip(rsqr).desc('character class'); 59 | var atom = alt(parenthesized(regex), charClass, wildcard, escapedSymbol, space, dash, character).desc('atom'); 60 | var piece = alt(starred(atom), plussed(atom), qmarked(atom), atom).desc('piece'); 61 | var branch = piece.atLeast(1).map(syntax.concatenation).desc('branch'); 62 | module.exports = root; 63 | -------------------------------------------------------------------------------- /lib/baseparser.js: -------------------------------------------------------------------------------- 1 | var syntax = require('./syntax.js'); 2 | var Parsimmon = require('Parsimmon'); 3 | Parsimmon.sepBy = function(parser, separator) { 4 | var pairs = separator.then(parser).many(); 5 | return parser.chain(function(r) { 6 | return pairs.map(function(rs) { 7 | return [r].concat(rs); 8 | }) 9 | }) 10 | }; 11 | 12 | var pipe = Parsimmon.string('|'); 13 | var lparen = Parsimmon.string('('); 14 | var rparen = Parsimmon.string(')'); 15 | var star = Parsimmon.string('*'); 16 | var plus = Parsimmon.string('+'); 17 | var qmark = Parsimmon.string('?'); 18 | var dot = Parsimmon.string('.'); 19 | 20 | var parenthesized = function(parser) { 21 | return lparen.then(parser).skip(rparen); 22 | }; 23 | 24 | var suffixOperator = function(operator, fn) { 25 | return function(parser) { 26 | return parser.skip(operator).map(fn); 27 | } 28 | }; 29 | 30 | var plussed = suffixOperator(plus, syntax.oneOrMore); 31 | var starred = suffixOperator(star, syntax.zeroOrMore); 32 | var qmarked = suffixOperator(qmark, syntax.zeroOrOne); 33 | 34 | var baseParser = function(name, atoms) { 35 | var root = Parsimmon.lazy(name, function() { 36 | return regex.map(syntax.root); 37 | }); 38 | var regex = Parsimmon.lazy('regex', function() { 39 | return Parsimmon.sepBy(branch, pipe).map(syntax.alternation); 40 | }); 41 | // var character = Parsimmon.regex(/[A-z]/).map(function(ch) { 42 | // return syntax.predicate(ch); 43 | // }).desc('character literal'); 44 | var any = dot.map(function() { 45 | return syntax.predicate('.'); 46 | }).desc('wildcard'); 47 | var atomTypes = [parenthesized(regex), any].concat(atoms); 48 | var atom = Parsimmon.alt.apply(this, atomTypes).desc('atom'); 49 | //var atom = Parsimmon.alt(parenthesized(regex), character, any).desc('atom'); 50 | var piece = Parsimmon.alt(starred(atom), plussed(atom), qmarked(atom), atom).desc('piece'); 51 | var branch = piece.atLeast(1).map(syntax.concatenation).desc('branch'); 52 | return root; 53 | // return function(input) { 54 | // var result = root.parse(input); 55 | // if (result.status) { 56 | // result.value.input = input; 57 | // } 58 | // return result; 59 | // } 60 | }; 61 | 62 | module.exports = baseParser; 63 | //module.exports = { 64 | // simpleCharRegex: simpleCharRegex 65 | //}; 66 | -------------------------------------------------------------------------------- /lib/env.js: -------------------------------------------------------------------------------- 1 | var fromObject = function(obj) { 2 | return function(symbol) { 3 | if (symbol in obj) { 4 | return obj[symbol]; 5 | } else { 6 | throw 'Unknown symbol: "' + symbol + '"'; 7 | } 8 | }; 9 | }; 10 | 11 | module.exports = { 12 | fromObject: fromObject 13 | }; 14 | -------------------------------------------------------------------------------- /lib/fragment.js: -------------------------------------------------------------------------------- 1 | var predicates = require('./predicates.js'); 2 | var epsilon = "\u03B5"; 3 | 4 | var fragmentState = function(transitions, index) { 5 | return { 6 | transitions: transitions ? transitions : [], 7 | index: index 8 | }; 9 | }; 10 | 11 | var fragmentTransition = function(name, target) { 12 | return { 13 | name: name, 14 | target: target 15 | }; 16 | }; 17 | 18 | var fragment = function(head, tails) { 19 | return { 20 | head: head, 21 | tails: tails 22 | } 23 | }; 24 | 25 | var patch = function(tails, state) { 26 | tails.forEach(function(tail) { 27 | tail.target = state; 28 | }); 29 | }; 30 | 31 | var build = {}; 32 | 33 | build.predicate = function(name) { 34 | var trans = fragmentTransition(name, null); 35 | var head = fragmentState([trans]); 36 | var tails = [trans]; 37 | return fragment(head, tails); 38 | }; 39 | 40 | build.concatenation = function(frags) { 41 | var binaryConcat = function(frag1, frag2) { 42 | patch(frag1.tails, frag2.head); 43 | var head = frag1.head; 44 | var tails = frag2.tails; 45 | return fragment(head, tails); 46 | }; 47 | return frags.reduce(binaryConcat); 48 | }; 49 | 50 | build.alternation = function(frags) { 51 | var binaryAlt = function(frag1, frag2) { 52 | var trans1 = fragmentTransition(epsilon, frag1.head); 53 | var trans2 = fragmentTransition(epsilon, frag2.head); 54 | var head = fragmentState([trans1, trans2]); 55 | var tails = frag1.tails.concat(frag2.tails); 56 | return fragment(head, tails); 57 | }; 58 | return frags.reduce(binaryAlt); 59 | }; 60 | 61 | build.zeroOrMore = function(frag) { 62 | var loopTrans = fragmentTransition(epsilon, frag.head); 63 | var breakTrans = fragmentTransition(epsilon, null); 64 | var head = fragmentState([loopTrans, breakTrans]); 65 | patch(frag.tails, head); 66 | return fragment(head, [breakTrans]); 67 | }; 68 | 69 | build.oneOrMore = function(frag) { 70 | var loopTrans = fragmentTransition(epsilon, frag.head); 71 | var breakTrans = fragmentTransition(epsilon, null); 72 | var state = fragmentState([loopTrans, breakTrans]); 73 | patch(frag.tails, state); 74 | return fragment(frag.head, [breakTrans]); 75 | }; 76 | 77 | build.zeroOrOne = function(frag) { 78 | var matchTrans = fragmentTransition(epsilon, frag.head); 79 | var skipTrans = fragmentTransition(epsilon, null); 80 | var head = fragmentState([matchTrans, skipTrans]); 81 | var tails = frag.tails.concat([skipTrans]); 82 | return fragment(head, tails); 83 | }; 84 | 85 | build.root = function(frag) { 86 | var finalState = fragmentState(); 87 | patch(frag.tails, finalState); 88 | return fragment(frag.head, []); 89 | }; 90 | 91 | module.exports = build; 92 | -------------------------------------------------------------------------------- /lib/nfa.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var fragment = require('./fragment.js'); 3 | 4 | var indexedFragmentStates = function(fragment) { 5 | var nextIndex = 0; 6 | var frontier = [fragment.head]; 7 | var states = []; 8 | while (frontier.length > 0) { 9 | var state = frontier.pop(); 10 | if (state.index == null) { 11 | state.index = nextIndex; 12 | nextIndex++; 13 | state.transitions.forEach(function(transition) { 14 | frontier.push(transition.target); 15 | }); 16 | states.push(state); 17 | }; 18 | }; 19 | return states; 20 | }; 21 | 22 | var evalFunctions = {}; 23 | var evalExpression = function(expr) { 24 | if (expr.type == null) { 25 | var exprString = util.inspect(expr); 26 | throw "Expression has no type: " + exprString; 27 | } else if (!(expr.type in evalFunctions)) { 28 | throw "No evaluation function for expression type '" + expr.type + "'"; 29 | } else { 30 | return evalFunctions[expr.type](expr); 31 | } 32 | }; 33 | 34 | var evalChildThen = function(wrapper) { 35 | return function(expr) { 36 | var childFrag = evalExpression(expr.children[0]); 37 | return wrapper(childFrag); 38 | }; 39 | }; 40 | 41 | var evalChildrenThen = function(wrapper) { 42 | return function(expr) { 43 | var evalChild = function(child) { return evalExpression(child); }; 44 | var childFrags = expr.children.map(evalChild); 45 | return wrapper(childFrags); 46 | }; 47 | }; 48 | 49 | evalFunctions.root = evalChildThen(fragment.root); 50 | evalFunctions.concatenation = evalChildrenThen(fragment.concatenation); 51 | evalFunctions.alternation = evalChildrenThen(fragment.alternation); 52 | evalFunctions.zeroOrMore = evalChildThen(fragment.zeroOrMore); 53 | evalFunctions.oneOrMore = evalChildThen(fragment.oneOrMore); 54 | evalFunctions.zeroOrOne = evalChildThen(fragment.zeroOrOne); 55 | evalFunctions.predicate = function(expr) { 56 | var name = expr.data.name; 57 | return fragment.predicate(name); 58 | }; 59 | 60 | var compile = function(parsedRegex) { 61 | var fragment = evalExpression(parsedRegex); 62 | var util=require('util'); 63 | var states = indexedFragmentStates(fragment); 64 | var numStates = states.length; 65 | var nfaTransitions = {}; 66 | var finalState; 67 | states.forEach(function(state) { 68 | if (state.transitions.length == 0) { 69 | finalState = state.index; 70 | }; 71 | var outTrans = {}; 72 | state.transitions.map(function(fragTrans) { 73 | outTrans[fragTrans.target.index] = fragTrans.name; 74 | }); 75 | nfaTransitions[state.index] = outTrans; 76 | }); 77 | return { 78 | initialState: 0, 79 | numStates: numStates, 80 | finalState: finalState, 81 | transitions: nfaTransitions, 82 | expression: parsedRegex 83 | }; 84 | }; 85 | 86 | var simulate = function(nfa, input) { 87 | var initial = { state: 0, offset: 0 }; 88 | var frontier = [initial]; 89 | while (frontier.length > 0) { 90 | var current = frontier.shift(); 91 | if (current.state == nfa.finalState && current.offset == input.length) { 92 | return true; 93 | } 94 | for (nextState in nfa.transitions[current.state]) { 95 | var observed = input[current.offset]; 96 | var transition = nfa.transitions[current.state][nextState]; 97 | var nextOffset = current.offset + transition.increment; 98 | if (transition.predicate(observed) && nextOffset <= input.length) { 99 | var next = { state: nextState, offset: nextOffset }; 100 | frontier.push(next); 101 | } 102 | } 103 | } 104 | return false; 105 | }; 106 | 107 | module.exports = { 108 | compile: compile, 109 | simulate: simulate 110 | }; 111 | -------------------------------------------------------------------------------- /lib/parsers.js: -------------------------------------------------------------------------------- 1 | var parsers = require('Parsimmon'); 2 | var syntax = require('./syntax.js'); 3 | parsers.sepBy = function(parser, separator) { 4 | var pairs = separator.then(parser).many(); 5 | return parser.chain(function(r) { 6 | return pairs.map(function(rs) { 7 | return [r].concat(rs); 8 | }) 9 | }) 10 | }; 11 | var pipe = parsers.pipe = parsers.string('|'); 12 | var lparen = parsers.lparen = parsers.string('('); 13 | var rparen = parsers.rparen = parsers.string(')'); 14 | var star = parsers.star = parsers.string('*'); 15 | var plus = parsers.plus = parsers.string('+'); 16 | var qmark = parsers.qmark = parsers.string('?'); 17 | var dot = parsers.dot = parsers.string('.'); 18 | var lsqr = parsers.lsqr = parsers.string('['); 19 | var rsqr = parsers.rsqr = parsers.string(']'); 20 | var caret = parsers.caret = parsers.string('^'); 21 | var parenthesized = parsers.parenthesized = function(parser) { 22 | return lparen.then(parser).skip(rparen); 23 | }; 24 | var suffixOperator = parsers.suffixOperator = function(operator) { 25 | return function(fn) { 26 | return function(parser) { 27 | return parser.skip(operator).map(fn); 28 | } 29 | } 30 | }; 31 | parsers.plussed = suffixOperator(plus); 32 | parsers.starred = suffixOperator(star); 33 | parsers.qmarked = suffixOperator(qmark); 34 | module.exports = parsers; 35 | -------------------------------------------------------------------------------- /lib/predicates.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | alwaysTrue: function() { 3 | return true; 4 | }, 5 | equals: function(x) { 6 | return function(y) { 7 | return x == y; 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /lib/syntax.js: -------------------------------------------------------------------------------- 1 | var expression = function(type, children, data) { 2 | return { 3 | type: type, 4 | children: children ? children : [], 5 | data: data ? data : {} 6 | }; 7 | }; 8 | 9 | var withOneChild = function(name) { 10 | return function(expr, data) { 11 | return expression(name, [expr], data); 12 | }; 13 | }; 14 | 15 | var withMultipleChildren = function(name) { 16 | return function(exprs, data) { 17 | if (exprs.length == 0) { 18 | throw name + ' takes at least one child node'; 19 | } else if (exprs.length == 1) { 20 | return exprs[0]; 21 | } else { 22 | return expression(name, exprs, data); 23 | } 24 | }; 25 | }; 26 | 27 | var disjunction = function(exprs, negated) { 28 | var notPred = function(x) { return !('type' in x) || x.type != 'predicate' }; 29 | if (exprs.some(notPred)) { 30 | var first = notPred.find(notPred); 31 | throw 'disjunction must take predicates as arguments, got: ' + first.type; 32 | } else if (exprs.length < 1) { 33 | throw 'disjunction must take at least one predicate'; 34 | } 35 | var children = exprs; 36 | var data = {negated: negated}; 37 | return expression('disjunction', children, data); 38 | }; 39 | 40 | var expressions = { 41 | predicate: function(name) { 42 | var data = {name: name}; 43 | var children = []; 44 | return expression('predicate', children, data); 45 | }, 46 | concatenation: withMultipleChildren('concatenation'), 47 | alternation: withMultipleChildren('alternation'), 48 | zeroOrMore: withOneChild('zeroOrMore'), 49 | oneOrMore: withOneChild('oneOrMore'), 50 | zeroOrOne: withOneChild('zeroOrOne'), 51 | root: withOneChild('root'), 52 | disjunction: disjunction 53 | } 54 | 55 | module.exports = expressions; 56 | -------------------------------------------------------------------------------- /lib/vm.js: -------------------------------------------------------------------------------- 1 | var instruction = {}; 2 | instruction.predicate = function(name) { 3 | return { type: 'predicate', name: name }; 4 | }; 5 | instruction.match = function() { 6 | return { type: 'match' }; 7 | }; 8 | instruction.jump = function(increment) { 9 | return { type: 'jump', increment: increment }; 10 | }; 11 | instruction.split = function(increment) { 12 | return { type: 'split', increment: increment }; 13 | }; 14 | 15 | var concat = function(array1, array2) { return array1.concat(array2); }; 16 | var codes = {}; 17 | var compile = function(expr) { 18 | if (expr.type in codes) { 19 | return codes[expr.type](expr); 20 | } else { 21 | throw 'Unexpected expression type: "' + expr.type + '"'; 22 | } 23 | }; 24 | 25 | codes.predicate = function(expr) { 26 | var predName = expr.data.name; 27 | var pred = instruction.predicate(predName); 28 | return [pred]; 29 | }; 30 | 31 | codes.concatenation = function(expr) { 32 | var childCodes = expr.children.map(compile); 33 | return childCodes.reduce(concat); 34 | }; 35 | 36 | codes.alternation = function(expr) { 37 | var childCodes = expr.children.map(compile); 38 | var reducer = function(codes1, codes2) { 39 | var split = [instruction.split(codes1.length + 2)]; 40 | var jump = [instruction.jump(codes2.length + 1)]; 41 | var parts = [ split, codes1, jump, codes2 ]; 42 | return parts.reduce(concat); 43 | }; 44 | return childCodes.reduce(reducer); 45 | }; 46 | 47 | codes.zeroOrOne = function(expr) { 48 | var codes = compile(expr.children[0]); 49 | var split = [instruction.split(codes.length + 1)]; 50 | return concat(split, codes); 51 | }; 52 | 53 | codes.zeroOrMore = function(expr) { 54 | var codes = compile(expr.children[0]); 55 | var split = [instruction.split(codes.length + 2)]; 56 | var jump = [instruction.jump(-codes.length - 1)]; 57 | var parts = [split, codes, jump]; 58 | return parts.reduce(concat); 59 | }; 60 | 61 | codes.oneOrMore = function(expr) { 62 | var codes = compile(expr.children[0]); 63 | var split = [instruction.split(-codes.length)]; 64 | return concat(codes, split); 65 | }; 66 | 67 | codes.root = function(expr) { 68 | var childCodes = compile(expr.children[0]); 69 | var match = instruction.match(); 70 | return childCodes.concat([match]); 71 | }; 72 | 73 | var thread = function(position) { 74 | return { position: position }; 75 | }; 76 | 77 | var addThread = function(t, threads) { 78 | var positions = threads.map(function(x) { return x.position; }); 79 | if (positions.indexOf(t.position) == -1) { 80 | threads.push(t); 81 | } 82 | }; 83 | 84 | var threadList = function() { 85 | var positions = {}; 86 | var threads = []; 87 | var addThread = function(t) { 88 | if (!(t.position in positions)) { 89 | threads.push(t); 90 | positions[t.position] = t; 91 | } 92 | }; 93 | return { positions: positions, threads: threads, addThread: addThread }; 94 | }; 95 | 96 | var thompson = function(program, input, env) { 97 | var currentThreads = threadList(); 98 | currentThreads.addThread(thread(0)); 99 | for (var i = 0; i <= input.length; i++) { 100 | var nextThreads = threadList(); 101 | for (var j = 0; j < currentThreads.threads.length; j++) { 102 | var t = currentThreads.threads[j]; 103 | var instr = program[t.position]; 104 | switch(instr.type) { 105 | case 'predicate': 106 | var pred = env(instr.name); 107 | if (pred(input[i])) { 108 | nextThreads.addThread(thread(t.position + 1)); 109 | } 110 | break; 111 | case 'match': 112 | if (i == input.length) { 113 | return true; 114 | } 115 | break; 116 | case 'split': 117 | currentThreads.addThread(thread(t.position + 1)); 118 | currentThreads.addThread(thread(t.position + instr.increment)); 119 | break; 120 | case 'jump': 121 | currentThreads.addThread(thread(t.position + instr.increment)); 122 | break; 123 | } 124 | } 125 | currentThreads = nextThreads; 126 | } 127 | return false; 128 | }; 129 | 130 | module.exports = { 131 | compile: compile, 132 | run: thompson 133 | }; 134 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thompson-regex-js", 3 | "version": "1.0.0", 4 | "description": "Thompson's NFA construction algorithm in javascript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/afader/thompson-regex-js.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/afader/thompson-regex-js/issues" 17 | }, 18 | "homepage": "https://github.com/afader/thompson-regex-js#readme", 19 | "devDependencies": { 20 | "css-loader": "0.18.0", 21 | "del": "2.0.2", 22 | "file-loader": "0.8.4", 23 | "gulp": "3.9.0", 24 | "gulp-gh-pages": "0.5.2", 25 | "gulp-mocha": "2.1.3", 26 | "gulp-util": "3.0.6", 27 | "html-webpack-plugin": "1.6.1", 28 | "json-loader": "0.5.2", 29 | "jsx-loader": "0.13.2", 30 | "mocha": "2.3.2", 31 | "path": "0.12.7", 32 | "style-loader": "0.12.3", 33 | "unit.js": "2.0.0", 34 | "url-loader": "0.5.6", 35 | "webpack": "1.12.1", 36 | "webpack-dev-server": "1.10.1" 37 | }, 38 | "dependencies": { 39 | "parsimmon": "0.7.0", 40 | "react": "0.13.3", 41 | "react-bootstrap": "0.25.2", 42 | "vis": "4.8.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var nfa = require('../lib/nfa.js'); 3 | var parsers = require('../lib/parsers.js'); 4 | var predicates = require('../lib/predicates.js'); 5 | var vm = require('../lib/vm.js'); 6 | 7 | describe('regex tests', function() { 8 | it('should correctly parse, compile, simulate', function(done) { 9 | var pattern = '(abba+)|b.*'; 10 | var environment = function(symbol) { 11 | if (symbol.match(/[a-z]/)) { 12 | return predicates.equals(symbol); 13 | } else if (symbol == '.') { 14 | return predicates.alwaysTrue; 15 | } 16 | } 17 | var parsed = parsers.simpleCharRegex(pattern); 18 | var compiled = nfa.build(parsed.value, environment); 19 | var match = function(string) { return nfa.simulate(compiled, string); }; 20 | var matches = function(string) { 21 | assert(match(string), string + ' should match'); 22 | }; 23 | var doesntMatch = function(string) { 24 | assert(!match(string), string + ' should not match'); 25 | } 26 | matches('abba'); 27 | matches('abbaaa'); 28 | doesntMatch('abb'); 29 | matches('b'); 30 | doesntMatch(''); 31 | matches('bbbbb'); 32 | done(); 33 | }); 34 | }); 35 | 36 | describe('vm tests', function() { 37 | it('should comile', function(done) { 38 | var expr = parsers.simpleCharRegex('(ab*)*').value; 39 | var compiled = vm.compile(expr); 40 | var env = function(symbol) { return predicates.equals(symbol); }; 41 | var run = function(input) { 42 | console.log(input + ' => ' + vm.run(compiled, input, env)); 43 | }; 44 | compiled.forEach(function(inst, i) { 45 | console.log(i + ' ' + JSON.stringify(inst)); 46 | }); 47 | run('a'); 48 | run(''); 49 | run('b'); 50 | run('ab'); 51 | run('ba'); 52 | run('aa'); 53 | run('abcx'); 54 | run('abcxx'); 55 | run('abc'); 56 | run('abcabc'); 57 | run('abcabcx'); 58 | run('abcab'); 59 | run('bc'); 60 | run('aaabc'); 61 | done(); 62 | }); 63 | }); 64 | --------------------------------------------------------------------------------