├── .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 |
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 |
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 |
--------------------------------------------------------------------------------