├── LICENSE
├── README
├── CREDITS
├── src
├── main
│ └── js
│ │ ├── uritemplates.ebnf
│ │ └── uritemplates.js
└── test
│ └── webenv
│ ├── index.html
│ ├── syntax.html
│ ├── qunit
│ ├── qunit.css
│ └── qunit.js
│ ├── test.html
│ └── js
│ ├── ebnfdiagram.js
│ └── ebnf-jq.js
└── Makefile
/LICENSE:
--------------------------------------------------------------------------------
1 | URI-templates.js is distributed under the APLv2. (http://opensource.org/licenses/Apache-2.0)
2 |
3 | Check the CREDITS files for the licenses of other open projects that were used to create this.
4 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Javascript implementation for the URI Templates draft spec maintained at http://code.google.com/p/uri-templates/
2 |
3 | steps for testing:
4 | -----------------
5 | * checkout from github
6 | * build with 'make'
7 | * check test-report at src/test/webenv/test.html
8 |
9 |
--------------------------------------------------------------------------------
/CREDITS:
--------------------------------------------------------------------------------
1 | uri-templates.js uses a number of different other projects.
2 | You might want to check those (and their licenses) out
3 |
4 | [jquery/qunit]
5 | * version
6 | Version: 1.5.2
7 |
8 | * live used or downloaded from
9 | http://code.jquery.com/*
10 |
11 | * by
12 | The jQuery Project. http://jquery.org/
13 |
14 | * license (see http://jquery.org/license)
15 | MIT (http://www.opensource.org/licenses/mit-license.php) and
16 | GPLv2 (http://www.opensource.org/licenses/gpl-2.0.php)
17 |
18 | * see docs at
19 | http://docs.jquery.com/Main_Page
20 |
21 |
--------------------------------------------------------------------------------
/src/main/js/uritemplates.ebnf:
--------------------------------------------------------------------------------
1 |
2 | "uri-templates syntax (draft 0.8)" {
3 |
4 | uri-template = { literals | expression } .
5 |
6 | expression = "{" [ operator ] variable_list "}" .
7 |
8 | operator = op_level2 | op_level3 | op_reserve .
9 |
10 | op_level2 = "+" | "#" .
11 |
12 | op_level3 = "." | "/" | ";" | "?" | "&" .
13 |
14 | op_reserve = "=" | "," | "!" | "@" | "|" .
15 |
16 | variable_list = varspec { "," varspec } .
17 |
18 | varspec = varname [ modifier_level4 ] .
19 |
20 | varname = varchar { ["."] varchar } .
21 |
22 | varchar = ALPHA | DIGIT | "_" | pct_encoded .
23 |
24 | modifier_level4 = prefix | explode .
25 |
26 | prefix = ":" max_length .
27 |
28 | max_length = DIGIT [ DIGIT [ DIGIT [ DIGIT ] ] ] .
29 |
30 | explode = "*" .
31 |
32 | } "See http://code.google.com/p/uri-templates/ for updates and work on the spec."
33 |
--------------------------------------------------------------------------------
/src/test/webenv/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
22 | URI Templates tester
23 |
24 | Provide some variables in javascript:
25 |
26 |
32 |
33 | Describe your template
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Automated tests
42 |
43 | - QUnit test page.
44 | - Syntax test page.
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/test/webenv/syntax.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | uri-templates syntax
15 |
16 |
17 | "uri-templates syntax (draft 0.8)" {
18 |
19 | uri-template = { literals | expression } .
20 |
21 | expression = "{" [ operator ] variable_list "}" .
22 |
23 | operator = op_level2 | op_level3 | op_reserve .
24 |
25 | op_level2 = "+" | "#" .
26 |
27 | op_level3 = "." | "/" | ";" | "?" | "&" .
28 |
29 | op_reserve = "=" | "," | "!" | "@" | "|" .
30 |
31 | variable_list = varspec { "," varspec } .
32 |
33 | varspec = varname [ modifier_level4 ] .
34 |
35 | varname = varchar { ["."] varchar } .
36 |
37 | varchar = ALPHA | DIGIT | "_" | pct_encoded .
38 |
39 | modifier_level4 = prefix | explode .
40 |
41 | prefix = ":" max_length .
42 |
43 | max_length = DIGIT [ DIGIT [ DIGIT [ DIGIT ] ] ] .
44 |
45 | explode = "*" .
46 |
47 | } "See http://code.google.com/p/uri-templates/ for updates and work on the spec."
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | VERSION=0.9
2 | DATE=$(shell date +%Y-%m-%d)
3 | SRC=./src/main
4 | BUILD=./build
5 | DIST=./dist/${VERSION}
6 | NAME = uritemplates
7 | UGLIFY_JS ?= `which uglifyjs`
8 | WATCHR ?= `which watchr`
9 |
10 | build: js
11 |
12 | js:
13 | @@if test ! -z ${UGLIFY_JS}; then \
14 | mkdir -p ${BUILD}; \
15 | sed -e 's/@VERSION/'"v${VERSION}"'/' -e 's/@DATE/'"${DATE}"'/' <${SRC}/js/${NAME}.js >${BUILD}/${NAME}.js; \
16 | uglifyjs -o ${BUILD}/${NAME}.min.js ${BUILD}/${NAME}.js;\
17 | echo "js compress and uglify sucessful! - `date`"; \
18 | else \
19 | echo "You must have the UGLIFYJS minifier installed in order to minify uritemplates' js."; \
20 | echo "You can install it by running: npm install uglify-js -g"; \
21 | fi
22 |
23 | makedist:
24 | @@if test -d ${BUILD}; then \
25 | mkdir -p ${DIST}; \
26 | cp ${BUILD}/${NAME}.js ${DIST}/${NAME}-${VERSION}.js; \
27 | cp ${BUILD}/${NAME}.min.js ${DIST}/${NAME}-${VERSION}.min.js; \
28 | echo "success building distro for version ${VERSION} - `date`; \
29 | echo "now test then add, commit, tag and push through git to publish"; \
30 | else \
31 | echo "No build is available. Run 'make' or explicitely 'make build' first."; \
32 | fi
33 |
34 | dist: build makedist
35 |
36 | watch:
37 | @@if test ! -z ${WATCHR}; then \
38 | echo "Watching files in src/main"; \
39 | watchr -e "watch('src/main/.*/.*js') { system 'make' }"; \
40 | else \
41 | echo "You must have the watchr installed in order to watch uritemplate js files."; \
42 | echo "You can install it by running: gem install watchr"; \
43 | fi
44 |
45 | .PHONY: build watch
46 |
--------------------------------------------------------------------------------
/src/test/webenv/qunit/qunit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 15px 15px 0 0;
42 | -moz-border-radius: 15px 15px 0 0;
43 | -webkit-border-top-right-radius: 15px;
44 | -webkit-border-top-left-radius: 15px;
45 | }
46 |
47 | #qunit-header a {
48 | text-decoration: none;
49 | color: #c2ccd1;
50 | }
51 |
52 | #qunit-header a:hover,
53 | #qunit-header a:focus {
54 | color: #fff;
55 | }
56 |
57 | #qunit-banner {
58 | height: 5px;
59 | }
60 |
61 | #qunit-testrunner-toolbar {
62 | padding: 0.5em 0 0.5em 2em;
63 | color: #5E740B;
64 | background-color: #eee;
65 | }
66 |
67 | #qunit-userAgent {
68 | padding: 0.5em 0 0.5em 2.5em;
69 | background-color: #2b81af;
70 | color: #fff;
71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
72 | }
73 |
74 |
75 | /** Tests: Pass/Fail */
76 |
77 | #qunit-tests {
78 | list-style-position: inside;
79 | }
80 |
81 | #qunit-tests li {
82 | padding: 0.4em 0.5em 0.4em 2.5em;
83 | border-bottom: 1px solid #fff;
84 | list-style-position: inside;
85 | }
86 |
87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
88 | display: none;
89 | }
90 |
91 | #qunit-tests li strong {
92 | cursor: pointer;
93 | }
94 |
95 | #qunit-tests li a {
96 | padding: 0.5em;
97 | color: #c2ccd1;
98 | text-decoration: none;
99 | }
100 | #qunit-tests li a:hover,
101 | #qunit-tests li a:focus {
102 | color: #000;
103 | }
104 |
105 | #qunit-tests ol {
106 | margin-top: 0.5em;
107 | padding: 0.5em;
108 |
109 | background-color: #fff;
110 |
111 | border-radius: 15px;
112 | -moz-border-radius: 15px;
113 | -webkit-border-radius: 15px;
114 |
115 | box-shadow: inset 0px 2px 13px #999;
116 | -moz-box-shadow: inset 0px 2px 13px #999;
117 | -webkit-box-shadow: inset 0px 2px 13px #999;
118 | }
119 |
120 | #qunit-tests table {
121 | border-collapse: collapse;
122 | margin-top: .2em;
123 | }
124 |
125 | #qunit-tests th {
126 | text-align: right;
127 | vertical-align: top;
128 | padding: 0 .5em 0 0;
129 | }
130 |
131 | #qunit-tests td {
132 | vertical-align: top;
133 | }
134 |
135 | #qunit-tests pre {
136 | margin: 0;
137 | white-space: pre-wrap;
138 | word-wrap: break-word;
139 | }
140 |
141 | #qunit-tests del {
142 | background-color: #e0f2be;
143 | color: #374e0c;
144 | text-decoration: none;
145 | }
146 |
147 | #qunit-tests ins {
148 | background-color: #ffcaca;
149 | color: #500;
150 | text-decoration: none;
151 | }
152 |
153 | /*** Test Counts */
154 |
155 | #qunit-tests b.counts { color: black; }
156 | #qunit-tests b.passed { color: #5E740B; }
157 | #qunit-tests b.failed { color: #710909; }
158 |
159 | #qunit-tests li li {
160 | margin: 0.5em;
161 | padding: 0.4em 0.5em 0.4em 0.5em;
162 | background-color: #fff;
163 | border-bottom: none;
164 | list-style-position: inside;
165 | }
166 |
167 | /*** Passing Styles */
168 |
169 | #qunit-tests li li.pass {
170 | color: #5E740B;
171 | background-color: #fff;
172 | border-left: 26px solid #C6E746;
173 | }
174 |
175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
176 | #qunit-tests .pass .test-name { color: #366097; }
177 |
178 | #qunit-tests .pass .test-actual,
179 | #qunit-tests .pass .test-expected { color: #999999; }
180 |
181 | #qunit-banner.qunit-pass { background-color: #C6E746; }
182 |
183 | /*** Failing Styles */
184 |
185 | #qunit-tests li li.fail {
186 | color: #710909;
187 | background-color: #fff;
188 | border-left: 26px solid #EE5757;
189 | }
190 |
191 | #qunit-tests > li:last-child {
192 | border-radius: 0 0 15px 15px;
193 | -moz-border-radius: 0 0 15px 15px;
194 | -webkit-border-bottom-right-radius: 15px;
195 | -webkit-border-bottom-left-radius: 15px;
196 | }
197 |
198 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
199 | #qunit-tests .fail .test-name,
200 | #qunit-tests .fail .module-name { color: #000000; }
201 |
202 | #qunit-tests .fail .test-actual { color: #EE5757; }
203 | #qunit-tests .fail .test-expected { color: green; }
204 |
205 | #qunit-banner.qunit-fail { background-color: #EE5757; }
206 |
207 |
208 | /** Result */
209 |
210 | #qunit-testresult {
211 | padding: 0.5em 0.5em 0.5em 2.5em;
212 |
213 | color: #2b81af;
214 | background-color: #D2E0E6;
215 |
216 | border-bottom: 1px solid white;
217 | }
218 |
219 | /** Fixture */
220 |
221 | #qunit-fixture {
222 | position: absolute;
223 | top: -10000px;
224 | left: -10000px;
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/js/uritemplates.js:
--------------------------------------------------------------------------------
1 | /*
2 | UriTemplates Template Processor - Version: @VERSION - Dated: @DATE
3 | (c) marc.portier@gmail.com - 2011-2012
4 | Licensed under APLv2 (http://opensource.org/licenses/Apache-2.0)
5 | */
6 |
7 | ;
8 | var uritemplate = (function() {
9 |
10 | // Below are the functions we originally used from jQuery.
11 | // The implementations below are often more naive then what is inside jquery, but they suffice for our needs.
12 |
13 | function isFunction(fn) {
14 | return typeof fn == 'function';
15 | }
16 |
17 | function isEmptyObject (obj) {
18 | for(var name in obj){
19 | return false;
20 | }
21 | return true;
22 | }
23 |
24 | function extend(base, newprops) {
25 | for (var name in newprops) {
26 | base[name] = newprops[name];
27 | }
28 | return base;
29 | }
30 |
31 | /**
32 | * Create a runtime cache around retrieved values from the context.
33 | * This allows for dynamic (function) results to be kept the same for multiple
34 | * occuring expansions within one template.
35 | * Note: Uses key-value tupples to be able to cache null values as well.
36 | */
37 | //TODO move this into prep-processing
38 | function CachingContext(context) {
39 | this.raw = context;
40 | this.cache = {};
41 | }
42 | CachingContext.prototype.get = function(key) {
43 | var val = this.lookupRaw(key);
44 | var result = val;
45 |
46 | if (isFunction(val)) { // check function-result-cache
47 | var tupple = this.cache[key];
48 | if (tupple !== null && tupple !== undefined) {
49 | result = tupple.val;
50 | } else {
51 | result = val(this.raw);
52 | this.cache[key] = {key: key, val: result};
53 | // NOTE: by storing tupples we make sure a null return is validly consistent too in expansions
54 | }
55 | }
56 | return result;
57 | };
58 |
59 | CachingContext.prototype.lookupRaw = function(key) {
60 | return CachingContext.lookup(this, this.raw, key);
61 | };
62 |
63 | CachingContext.lookup = function(me, context, key) {
64 | var result = context[key];
65 | if (result !== undefined) {
66 | return result;
67 | } else {
68 | var keyparts = key.split('.');
69 | var i = 0, keysplits = keyparts.length - 1;
70 | for (i = 0; i 0 ? "=" : "") + val;
140 | }
141 |
142 | function addNamed(name, key, val, noName) {
143 | noName = noName || false;
144 | if (noName) { name = ""; }
145 |
146 | if (!key || key.length === 0) {
147 | key = name;
148 | }
149 | return key + (key.length > 0 ? "=" : "") + val;
150 | }
151 |
152 | function addLabeled(name, key, val, noName) {
153 | noName = noName || false;
154 | if (noName) { name = ""; }
155 |
156 | if (!key || key.length === 0) {
157 | key = name;
158 | }
159 | return key + (key.length > 0 && val ? "=" : "") + val;
160 | }
161 |
162 |
163 | var simpleConf = {
164 | prefix : "", joiner : ",", encode : encodeNormal, builder : addUnNamed
165 | };
166 | var reservedConf = {
167 | prefix : "", joiner : ",", encode : encodeReserved, builder : addUnNamed
168 | };
169 | var fragmentConf = {
170 | prefix : "#", joiner : ",", encode : encodeReserved, builder : addUnNamed
171 | };
172 | var pathParamConf = {
173 | prefix : ";", joiner : ";", encode : encodeNormal, builder : addLabeled
174 | };
175 | var formParamConf = {
176 | prefix : "?", joiner : "&", encode : encodeNormal, builder : addNamed
177 | };
178 | var formContinueConf = {
179 | prefix : "&", joiner : "&", encode : encodeNormal, builder : addNamed
180 | };
181 | var pathHierarchyConf = {
182 | prefix : "/", joiner : "/", encode : encodeNormal, builder : addUnNamed
183 | };
184 | var labelConf = {
185 | prefix : ".", joiner : ".", encode : encodeNormal, builder : addUnNamed
186 | };
187 |
188 |
189 | function Expression(conf, vars ) {
190 | extend(this, conf);
191 | this.vars = vars;
192 | }
193 |
194 | Expression.build = function(ops, vars) {
195 | var conf;
196 | switch(ops) {
197 | case '' : conf = simpleConf; break;
198 | case '+' : conf = reservedConf; break;
199 | case '#' : conf = fragmentConf; break;
200 | case ';' : conf = pathParamConf; break;
201 | case '?' : conf = formParamConf; break;
202 | case '&' : conf = formContinueConf; break;
203 | case '/' : conf = pathHierarchyConf; break;
204 | case '.' : conf = labelConf; break;
205 | default : throw "Unexpected operator: '"+ops+"'";
206 | }
207 | return new Expression(conf, vars);
208 | };
209 |
210 | Expression.prototype.expand = function(context) {
211 | var joiner = this.prefix;
212 | var nextjoiner = this.joiner;
213 | var buildSegment = this.builder;
214 | var res = "";
215 | var i = 0, cnt = this.vars.length;
216 |
217 | for (i = 0 ; i< cnt; i++) {
218 | var varspec = this.vars[i];
219 | varspec.addValues(context, this.encode, function(key, val, noName) {
220 | var segm = buildSegment(varspec.name, key, val, noName);
221 | if (segm !== null && segm !== undefined) {
222 | res += joiner + segm;
223 | joiner = nextjoiner;
224 | }
225 | });
226 | }
227 | return res;
228 | };
229 |
230 | Expression.prototype.pushrefs = function(refs) {
231 | var i = 0, cnt = this.vars.length;
232 |
233 | for (i = 0 ; i< cnt; i++) {
234 | var varspec = this.vars[i];
235 | refs[varspec.name]=true;
236 | }
237 | }
238 |
239 | var UNBOUND = {};
240 |
241 | /**
242 | * Helper class to help grow a string of (possibly encoded) parts until limit is reached
243 | */
244 | function Buffer(limit) {
245 | this.str = "";
246 | if (limit === UNBOUND) {
247 | this.appender = Buffer.UnboundAppend;
248 | } else {
249 | this.len = 0;
250 | this.limit = limit;
251 | this.appender = Buffer.BoundAppend;
252 | }
253 | }
254 |
255 | Buffer.prototype.append = function(part, encoder) {
256 | return this.appender(this, part, encoder);
257 | };
258 |
259 | Buffer.UnboundAppend = function(me, part, encoder) {
260 | part = encoder ? encoder(part) : part;
261 | me.str += part;
262 | return me;
263 | };
264 |
265 | Buffer.BoundAppend = function(me, part, encoder) {
266 | part = part.substring(0, me.limit - me.len);
267 | me.len += part.length;
268 |
269 | part = encoder ? encoder(part) : part;
270 | me.str += part;
271 | return me;
272 | };
273 |
274 |
275 | function arrayToString(arr, encoder, maxLength) {
276 | var buffer = new Buffer(maxLength);
277 | var joiner = "";
278 |
279 | var i = 0, cnt = arr.length;
280 | for (i=0; i
3 |
4 |
5 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 | test markup, will be hidden
713 |
714 |
715 |
--------------------------------------------------------------------------------
/src/test/webenv/js/ebnfdiagram.js:
--------------------------------------------------------------------------------
1 | ;var G_vmlCanvasManager;
2 | (function($){
3 |
4 | $.extend({
5 | "mergedClone": function(base, spec) {
6 | return $.extend(true, $.extend({}, base), spec);
7 | }
8 | });
9 |
10 | function makeCanvas(w, h, fn, hidden) {
11 | fn = fn || function() {};
12 | h = h ? Math.floor(h) : 100;
13 | w = w ? Math.floor(w) : 300;
14 |
15 | var $cnv;
16 |
17 | // check up on ie support via explorercanvas to initialize properly
18 | if (G_vmlCanvasManager !== undefined && G_vmlCanvasManager !== null) {
19 | // explorercanvas kinda expects elms to be built this way, not the jquery way.
20 | var cnv = document.createElement('canvas');
21 | cnv.setAttribute('height', h);
22 | cnv.setAttribute('width', w);
23 | cnv = G_vmlCanvasManager.initElement(cnv);
24 | $cnv = $(cnv);
25 | } else {
26 | $cnv = $('');
27 | }
28 |
29 | fn($cnv);
30 |
31 | if (hidden) { $cnv.hide(); }
32 |
33 | return $cnv;
34 | }
35 |
36 | function makeDiagram($elm, styles) {
37 | var ebnf = $elm.text();
38 |
39 | //TODO perform hight/width calculations
40 | var h = $elm.height();
41 | var w = $elm.width();
42 |
43 | var $cnv = makeCanvas( $elm.width(), $elm.height(), function($cnv) {$cnv.insertAfter($elm);}, true);
44 |
45 | var dia = $cnv.ebnfcanvas(styles)[0];
46 |
47 | var showDiagram = function(dia) {
48 | $elm.hide("normal");
49 | dia.$cnv.show("normal");
50 | };
51 |
52 | $.ebnfParse( ebnf,
53 | function(syn){
54 | dia.setSyntax(syn);
55 | showDiagram(dia);
56 | },
57 | function(err) {
58 | dia.showErrors(err);
59 | showDiagram(dia);
60 | }
61 | );
62 | }
63 |
64 |
65 | //default style
66 | var STYLES = {
67 | "SYNTAX": {
68 | "margin" : 10,
69 | "round" : 10,
70 | "background-fill" : "rgba(200,200,100, 0.3)",
71 | "background-stroke" : "rgba(255,255,255, 0)",
72 | "background-weight" : 0,
73 | "separator-stroke" : "rgba( 0, 0, 0, 0.7)",
74 | "separator-weight" : 2,
75 | "title-font" : "30px Arial",
76 | "title-color" : "rgba( 0, 0, 0, 0.7)",
77 | "title-align" : "left",
78 | "comment-font" : "20px Optimer",
79 | "comment-color" : "rgba(0, 0, 0, 0.5)",
80 | "comment-align" : "left"
81 | },
82 | "PRODUCTION_SET": {
83 | },
84 | "PRODUCTION": {
85 | "margin" : 10,
86 | "round" : 10,
87 | "background-fill" : "rgba(255,255,255, 0.75)",
88 | "background-stroke" : "rgba(255,255,255, 0)",
89 | "background-weight" : 0,
90 | "font" : "10px Arial",
91 | "color" : "rgba( 0, 0, 0, 0.7)",
92 | "align" : "left",
93 | "baseline" : "bottom",
94 | "trail" : 75
95 | },
96 | "TERM_SET": {
97 | },
98 | "FACTOR_SET": {
99 | },
100 | "LITERAL": {
101 | "round" : 10,
102 | "background-fill" : "rgba( 20, 20,200, 0.5)",
103 | "background-stroke" : "rgba( 10, 10, 10, 0.8)",
104 | "background-weight" : 3
105 | },
106 | "IDENTIFIER": {
107 | "round" : 0,
108 | "background-fill" : "rgba(200, 20, 20, 0.5)",
109 | "background-stroke" : "rgba( 10, 10, 10, 0.8)",
110 | "background-weight" : 3
111 | },
112 | "GROUP": {
113 | },
114 | "REPEATING": {
115 | },
116 | "OPTIONAL": {
117 | },
118 | "DIAGRAM": {
119 | "line-stroke" : "rgba( 10, 10, 10, 1)",
120 | "line-weight" : 2,
121 | "grid" : 12,
122 | "font-width" : 10,
123 | "font-height" : 24,
124 | "font" : "12px Arial",
125 | "color" : "rgba( 0, 0, 0, 0.7)"
126 | }
127 | };
128 |
129 |
130 | function EbnfDiagram($elm, styles) {
131 | this.$cnv = $elm;
132 |
133 | this.styles = $.mergedClone(STYLES, styles);
134 | this.style = this.styles.DIAGRAM;
135 | this.init();
136 | }
137 |
138 | EbnfDiagram.prototype.getGC = function() {
139 | if (! this.gc) {
140 | this.gc = this.$cnv.get(0).getContext('2d');
141 | }
142 | return this.gc;
143 | };
144 |
145 |
146 |
147 | // Internal GCLIB with standard operations/drawing assistance..
148 | var GCLIB = {};
149 |
150 | /** Draws a centered (ie at 0,0) rounded rectangle with the specified width, height and percentage of round-ness on the corners */
151 | GCLIB.roundedRect = function(gc, width, height, r, atx, aty) {
152 | if (! (width && height)) {
153 | return;
154 | }
155 |
156 | r = r || 0;
157 | atx = atx || 0;
158 | aty = aty || 0;
159 |
160 | var w = width/2;
161 | var h = height/2;
162 |
163 | gc.beginPath();
164 | if (r) {
165 | gc.arc( atx -w + r, aty -h + r, r, -Math.PI , -Math.PI/2, false);
166 | gc.arc( atx +w - r, aty -h + r, r, -Math.PI/2, 0 , false);
167 | gc.arc( atx +w - r, aty +h - r, r, 0 , Math.PI/2 , false);
168 | gc.arc( atx -w + r, aty +h - r, r, Math.PI/2 , Math.PI , false);
169 | } else {
170 | gc.moveTo( atx -w, aty -h);
171 | gc.lineTo( atx +w, aty -h);
172 | gc.lineTo( atx +w, aty +h);
173 | gc.lineTo( atx -w, aty +h);
174 | }
175 | gc.closePath();
176 | gc.fill();
177 | gc.stroke();
178 | };
179 |
180 | /** Draws text centered at 0,0 */
181 | GCLIB.centeredText = function(gc, text, x, y) {
182 | x = x || 0;
183 | y = y || 0;
184 | gc.fillText(text, x, y);
185 | };
186 |
187 | /** Draws line from a to b */
188 | GCLIB.line = function(gc, ax, ay, bx, by) {
189 | gc.beginPath();
190 | gc.moveTo( ax, ay);
191 | gc.lineTo( bx, by);
192 | gc.closePath();
193 | gc.stroke();
194 | };
195 |
196 |
197 | /** Draws slide-in from a to b */
198 | GCLIB.slideIn = function(gc, g, atx , aty, w, h){
199 | w = Math.max(w , 2*g); // minimal width
200 | h = h || 0;
201 | if (h === 0) {
202 | GCLIB.line(gc, atx, aty, atx+w, aty);
203 | return;
204 | }
205 | gc.beginPath();
206 | gc.arc( atx , aty + g, g, -Math.PI/2, 0,false);
207 | gc.lineTo( atx + g, aty + h - g);
208 | gc.arc( atx +2*g, aty + h - g, g, Math.PI, Math.PI/2,true);
209 | gc.lineTo(atx + w, aty + h);
210 | gc.stroke();
211 | };
212 |
213 | GCLIB.slideOut = function(gc, g, tox, toy, w, h){
214 | w = Math.max(w , 2*g); // minimal width
215 | h = h || 0;
216 | if (h === 0) {
217 | gc.beginPath();
218 | gc.moveTo(tox -w, toy);
219 | gc.lineTo( tox , toy);
220 | gc.stroke();
221 | return;
222 | }
223 | gc.beginPath();
224 | gc.moveTo(tox -w, toy + h);
225 | gc.lineTo(tox - 2*g, toy + h);
226 | gc.arc( tox - 2 * g , toy + h - g, g, Math.PI/2, 0, true);
227 | gc.lineTo( tox -g , toy + g);
228 | gc.arc( tox, toy + g, g, -Math.PI, -Math.PI/2, false);
229 | gc.stroke();
230 | };
231 |
232 | GCLIB.loopDown = function(gc, g, atx, aty, h){
233 | h = h || 0;
234 | if (h === 0) {
235 | return;
236 | }
237 | gc.beginPath();
238 | gc.arc( atx , aty + g, g, -Math.PI/2, 0,false);
239 | gc.lineTo( atx + g, aty + h - g);
240 | gc.arc( atx , aty + h - g, g, 0, Math.PI/2,false);
241 | gc.stroke();
242 | };
243 |
244 | GCLIB.loopUp = function(gc, g, tox, toy, h){
245 | h = h || 0;
246 | if (h === 0) {
247 | return;
248 | }
249 | gc.beginPath();
250 | gc.arc( tox , toy + h - g, g, Math.PI/2, Math.PI, false);
251 | gc.lineTo( tox - g, toy + g);
252 | gc.arc( tox , toy + g, g, -Math.PI, -Math.PI/2,false);
253 | gc.stroke();
254 | };
255 |
256 | var DEFAULT_FILL = "rgb(255,255,255)";
257 | var DEFAULT_STROKE = "rgb( 0, 0, 0)";
258 | var DEFAULT_WEIGHT = 0;
259 | var DEFAULT_FONT = "8px Arial";
260 | var DEFAULT_COLOR = "rgb( 0, 0, 0)";
261 | var DEFAULT_ALIGN = "center";
262 | var DEFAULT_BASELINE = "middle";
263 |
264 | /** Sets drawstyles */
265 | GCLIB.setDrawStyle = function(gc, style, pfx) {
266 | pfx = pfx ? pfx + "-" : "";
267 | gc.fillStyle = style[pfx + "fill"] || DEFAULT_FILL;
268 | gc.strokeStyle = style[pfx + "stroke"] || DEFAULT_STROKE;
269 | gc.lineWidth = style[pfx + "weight"] || DEFAULT_WEIGHT;
270 | };
271 |
272 | /** Sets font-styles */
273 | GCLIB.setTextStyle = function(gc, style, pfx) {
274 | pfx = pfx ? pfx + "-" : "";
275 | gc.font = style[pfx + "font"] || DEFAULT_FONT;
276 | gc.fillStyle = style[pfx + "color"] || DEFAULT_COLOR;
277 | gc.textAlign = style[pfx + "align"] || DEFAULT_ALIGN;
278 | gc.textBaseline = style[pfx + "baseline"] || DEFAULT_BASELINE;
279 | };
280 |
281 |
282 | var NODEHANDLERS = {
283 | "SYNTAX": {
284 | "prepare": function(node, dia, gc) {
285 | var s = node.style;
286 | var m = s.margin;
287 |
288 | var c = node.set;
289 | dia.prepare(c);
290 |
291 | node.height = c.height + 2*m + 2*30 + 2*20; // TODO calculated room for title and comment
292 | node.width = c.width + 2*m;
293 | },
294 | "draw" : function(node, dia, gc) {
295 | var s = node.style;
296 | var m = s.margin;
297 |
298 | var ox = node.width / 2;
299 | var oy = node.height / 2;
300 | var ty = 30; //TODO calc
301 | var cy = 20; //TODO calc
302 |
303 | GCLIB.setDrawStyle(gc, s, "background");
304 | GCLIB.roundedRect(gc, (node.width - m), (node.height - m), s.round, ox, oy);
305 |
306 | GCLIB.setTextStyle(gc, s, "title");
307 | GCLIB.centeredText(gc, node.title, 2*m, ty);
308 |
309 | GCLIB.setDrawStyle(gc, node.style, "separator");
310 | GCLIB.line(gc, 2*m, 2*ty, node.width - 2*m, 2*ty);
311 |
312 | dia.draw(node.set, m, 2*ty + m);
313 |
314 | GCLIB.line(gc, 2*m, node.height - 2*cy, node.width - 2*m, node.height - 2*cy);
315 |
316 | GCLIB.setTextStyle(gc, s, "comment");
317 | GCLIB.centeredText(gc, node.comment, 2*m, node.height - cy);
318 | }
319 | },
320 | "PRODUCTION_SET": {
321 | "prepare": function(node, dia, gc) {
322 | var sumH = 0;
323 | var maxW = 0;
324 |
325 | var i = 0, c = node.children;
326 | for (i = 0; i< c.length; i++){
327 | var ci = c[i];
328 | dia.prepare(ci);
329 | sumH += ci.height;
330 | maxW = Math.max(maxW, ci.width);
331 | }
332 |
333 | // align all widths
334 | for ( i = 0; i < c.length; i++){
335 | c[i].width = maxW;
336 | }
337 |
338 | node.width = maxW;
339 | node.height = sumH;
340 | },
341 | "draw" : function(node, dia, gc) {
342 | var s = node.style;
343 |
344 | var offH = 0;
345 |
346 | var i = 0, c = node.children;
347 |
348 | for (i = 0; i < c.length; i++){
349 | var ci = c[i];
350 | dia.draw(ci, 0, offH);
351 | offH += ci.height;
352 | }
353 | }
354 | },
355 | "PRODUCTION": {
356 | "prepare": function(node, dia, gc) {
357 | var s = node.style;
358 | var ds = dia.style;
359 | var g = ds.grid;
360 | var m = s.margin;
361 |
362 | var c = node.expr;
363 | dia.prepare(c);
364 |
365 | node.width = c.width + 2*m + s.trail;
366 | node.height = c.height+ 2*m;
367 | },
368 | "draw" : function(node, dia, gc) {
369 | var s = node.style;
370 | var ds = dia.style;
371 | var g = ds.grid;
372 | var m = s.margin;
373 | var tx = 10;
374 | var ty = 10;
375 |
376 | var ox = node.width / 2;
377 | var oy = node.height / 2;
378 |
379 | GCLIB.setDrawStyle(gc, s, "background");
380 | GCLIB.roundedRect(gc, (node.width - m), (node.height - m), s.round, ox, oy);
381 |
382 | GCLIB.setTextStyle(gc, s);
383 | GCLIB.centeredText(gc, node.id, m + tx, m + ty);
384 |
385 | GCLIB.setDrawStyle(gc, ds, "line");
386 | GCLIB.line(gc, m , m+ 2*g, m + s.trail, m+ 2*g);
387 |
388 | // delegate to expression
389 | var c = node.expr;
390 | dia.draw(c, m + s.trail, m);
391 |
392 | GCLIB.line(gc, m + s.trail + node.expr.width , m+ 2*g, node.width -m, m + 2*g);
393 | GCLIB.line(gc, node.width - m, m+ 1.5 * g, node.width - m, m+ 2.5*g);
394 | }
395 | },
396 | "TERM_SET": {
397 | "prepare": function(node, dia, gc) {
398 | var ds = dia.style;
399 | var g = ds.grid;
400 |
401 | var sumH = 0;
402 | var maxW = 0;
403 |
404 | var i = 0, c = node.children;
405 | for (i = 0; i< c.length; i++){
406 | var ci = c[i];
407 | dia.prepare(ci);
408 | sumH += ci.height;
409 | maxW = Math.max(maxW, ci.width);
410 | }
411 |
412 | node.width = maxW + 6 * g; // make room for parallelization lines
413 | node.height = sumH + (c.length - 1) * g; // make room for space between terms
414 | },
415 | "draw" : function(node, dia, gc) {
416 | var s = node.style;
417 | var ds = dia.style;
418 | var g = ds.grid;
419 |
420 | var offH = 0;
421 |
422 | var i = 0, c = node.children;
423 |
424 | GCLIB.setDrawStyle(gc, ds, "line");
425 | for (i = 0; i < c.length; i++){
426 | var ci = c[i];
427 | GCLIB.slideIn (gc, g, 0 , 2*g, 3*g, offH);
428 |
429 | dia.draw(ci, 3*g, offH);
430 |
431 | GCLIB.line(gc, ci.width + 3*g , offH + 2*g, node.width - 3*g, offH + 2*g);
432 | GCLIB.slideOut(gc, g, node.width, 2*g, 3*g, offH);
433 |
434 | offH += ci.height + g; //add space between terms
435 | }
436 | }
437 | },
438 | "FACTOR_SET": {
439 | "prepare": function(node, dia, gc) {
440 | var ds = dia.style;
441 | var g = ds.grid;
442 |
443 | var sumW = 0;
444 | var maxH = 0;
445 |
446 | var i = 0, c = node.children;
447 | for (i = 0; i< c.length; i++){
448 | var ci = c[i];
449 | dia.prepare(ci);
450 | sumW += ci.width;
451 | maxH = Math.max(maxH, ci.height);
452 | }
453 |
454 | // no need to align the heights
455 |
456 | node.width = sumW;
457 | node.height = maxH;
458 | },
459 | "draw" : function(node, dia, gc) {
460 | var s = node.style;
461 | var ds = dia.style;
462 | var g = ds.grid;
463 |
464 | var offW = 0;
465 |
466 | var i = 0, c = node.children;
467 | for (i = 0; i < c.length; i++){
468 | var ci = c[i];
469 | dia.draw(ci, offW, 0);
470 | offW += ci.width;
471 | }
472 | }
473 | },
474 | "LITERAL": {
475 | "prepare": function(node, dia, gc) {
476 |
477 | var l = Math.max(3, node.txt.length);
478 | var ds = dia.style;
479 | var g = ds.grid;
480 |
481 | node.width = l * ds["font-width"] + 2 * g;
482 | node.height = 4 * g ;
483 | },
484 | "draw" : function(node, dia, gc) {
485 | var s = node.style;
486 | var ds = dia.style;
487 | var g = ds.grid;
488 | var ox = node.width / 2;
489 | var oy = node.height /2;
490 |
491 | GCLIB.setDrawStyle(gc, ds, "line");
492 | GCLIB.line(gc, 0, 2*g, g, 2*g);
493 | GCLIB.line(gc, node.width - g, 2*g, node.width, 2*g);
494 |
495 |
496 | GCLIB.setDrawStyle(gc,s, "background");
497 | GCLIB.roundedRect(gc, node.width - 2 * g, ds["font-height"], s.round, ox, oy);
498 |
499 | GCLIB.setTextStyle(gc, ds);
500 | GCLIB.centeredText(gc, node.txt, ox, oy);
501 | }
502 | },
503 | "IDENTIFIER": {
504 | "prepare": function(node, dia, gc) {
505 |
506 | var l = Math.max(3, node.id.length);
507 | var ds = dia.style;
508 | var g = ds.grid;
509 |
510 | node.width = l * dia.style["font-width"] + 2*g;
511 | node.height = 4*g ;
512 | },
513 | "draw" : function(node, dia, gc) {
514 | var s = node.style;
515 | var ds = dia.style;
516 | var g = ds.grid;
517 | var ox = node.width / 2;
518 | var oy = node.height /2;
519 |
520 | GCLIB.setDrawStyle(gc, ds, "line");
521 | GCLIB.line(gc, 0, 2*g, g, 2*g);
522 | GCLIB.line(gc, node.width - g, 2*g, node.width, 2*g);
523 |
524 |
525 | GCLIB.setDrawStyle(gc,s, "background");
526 | GCLIB.roundedRect(gc, node.width - 2 * g, ds["font-height"], s.round, ox, oy);
527 |
528 | GCLIB.setTextStyle(gc, ds);
529 | GCLIB.centeredText(gc, node.id , ox, oy);
530 | }
531 | },
532 | "GROUP": {
533 | "prepare": function(node, dia, gc) {
534 |
535 | var ds = dia.style;
536 | var g = ds.grid;
537 |
538 | var c = node.expr;
539 | dia.prepare(c);
540 |
541 | node.width = c.width + 2 * g;
542 | node.height = c.height;
543 | },
544 | "draw" : function(node, dia, gc) {
545 | var s = node.style;
546 | var ds = dia.style;
547 | var g = ds.grid;
548 |
549 | GCLIB.setDrawStyle(gc, ds, "line");
550 | GCLIB.line(gc, 0, 2*g, g, 2*g);
551 |
552 | dia.draw(node.expr, g, 0);
553 |
554 | GCLIB.line(gc, node.width - g, 2*g, node.width, 2*g);
555 | }
556 | },
557 | "REPEATING": {
558 | "prepare": function(node, dia, gc) {
559 | var ds = dia.style;
560 | var g = ds.grid;
561 |
562 | var c = node.expr;
563 | dia.prepare(c);
564 |
565 | node.width = c.width + 6*g;
566 | node.height = c.height;
567 | },
568 | "draw" : function(node, dia, gc) {
569 | var s = node.style;
570 | var ds = dia.style;
571 | var g = ds.grid;
572 |
573 | var c = node.expr;
574 |
575 | GCLIB.setDrawStyle(gc, ds, "line");
576 | GCLIB.slideOut(gc, g, 2*g, 0, 2*g, 2*g);
577 | GCLIB.line(gc, 2*g , 0, node.width - 2*g, 0);
578 | GCLIB.slideIn (gc, g, node.width - 2*g, 0, 2*g, 2*g);
579 |
580 | GCLIB.line(gc, 0, 2*g, 3*g, 2*g);
581 | dia.draw(c, 3*g, 0);
582 | GCLIB.line(gc, node.width - 3*g, 2*g, node.width, 2*g);
583 |
584 | GCLIB.loopDown(gc, g, 3*g + c.width, 2*g, - 2*g + c.height);
585 | GCLIB.line(gc, 3*g , c.height, node.width - 3*g, c.height);
586 | GCLIB.loopUp(gc, g, 3*g, 2*g, - 2*g + c.height);
587 | }
588 | },
589 | "OPTIONAL": {
590 | "prepare": function(node, dia, gc) {
591 | var ds = dia.style;
592 | var g = ds.grid;
593 |
594 | var c = node.expr;
595 | dia.prepare(c);
596 |
597 | node.width = c.width + 6*g;
598 | node.height = c.height + 2*g;
599 | },
600 | "draw" : function(node, dia, gc) {
601 | var s = node.style;
602 | var ds = dia.style;
603 | var g = ds.grid;
604 |
605 | GCLIB.setDrawStyle(gc, ds, "line");
606 | GCLIB.line(gc, 0 , 2*g, node.width, 2*g);
607 |
608 | GCLIB.slideIn (gc, g, 0 , 2*g, 3*g, 2*g);
609 |
610 | dia.draw(node.expr, 3*g, 2*g);
611 |
612 | GCLIB.slideOut(gc, g, node.width, 2*g, 3*g, 2*g);
613 | }
614 | }
615 | };
616 |
617 | //actual diagram code
618 | EbnfDiagram.prototype.init = function() {
619 |
620 | this.height = this.$cnv.attr('height');
621 | this.width = this.$cnv.attr('width');
622 |
623 | // clear the canvas
624 | this.getGC().clearRect( 0, 0, this.width, this.height);
625 | };
626 |
627 | EbnfDiagram.prototype.showErrors = function(err) {
628 | var i = 0, l = err.length;
629 | for (i = 0; i++; i
16 |
17 | This is in the public domain.
18 | */
19 |
20 |
21 | ;
22 | (function($){
23 |
24 | // node structs
25 | function Syntax(set, title, comment) {
26 | this.set = set;
27 | this.title = title || "";
28 | this.comment = comment || "";
29 | }
30 | Syntax.prototype.toString = function() {
31 | var l = "\n---------------------------------------------------------\n";
32 | return l + "-- " + this.title + l + this.set + l +"comment:\n" + this.comment + l;
33 | }
34 | Syntax.prototype.nodetype="SYNTAX";
35 |
36 |
37 | var PRODUCTION_SET = {
38 | "label" : "@@",
39 | "join" : function(i) { return "\n" + i + "| "; },
40 | "nodetype": "PRODUCTION_SET"
41 | };
42 | var TERM_SET = {
43 | "label" : "",
44 | "join" : function(i) { return (i==0) ? "" : "|";},
45 | "nodetype": "TERM_SET"
46 | };
47 | var FACTOR_SET = {
48 | "label" : "",
49 | "join" : function(i) { return ""; },
50 | "nodetype": "FACTOR_SET"
51 | };
52 | function Set(t) {
53 | this.label = t.label;
54 | this.join = t.join;
55 | this.nodetype = t.nodetype;
56 | this.children = new Array();
57 | }
58 | Set.prototype.addChild = function(node) {
59 | this.children.push(node);
60 | };;
61 | Set.prototype.toString = function() {
62 | var cnt = this.children.length;
63 | var o = this.label;
64 | for (var i = 0; i < cnt; i++)
65 | o += this.join(i) + this.children[i].toString();
66 | return o;
67 | }
68 |
69 |
70 | function Production(id, expr) {
71 | this.id = id;
72 | this.expr = expr;
73 | }
74 | Production.prototype.toString = function(ind) {
75 | ind = ind || "";
76 | return ind + this.id + "=" + this.expr + " ." ;
77 | }
78 | Production.prototype.nodetype="PRODUCTION";
79 |
80 |
81 | function Identifier(id) {
82 | this.id = id;
83 | }
84 | Identifier.prototype.toString = function() {
85 | return this.id;
86 | };
87 | Identifier.prototype.nodetype="IDENTIFIER";
88 |
89 |
90 | function Literal(txt) {
91 | this.txt = txt;
92 | }
93 | Literal.prototype.toString = function() {
94 | return "\"" + this.txt + "\"";
95 | };
96 | Literal.prototype.nodetype="LITERAL";
97 |
98 |
99 | function Optional(expr) {
100 | this.expr = expr;
101 | }
102 | Optional.prototype.toString = function() {
103 | return "[" + this.expr + "]";
104 | };
105 | Optional.prototype.nodetype="OPTIONAL";
106 |
107 |
108 | function Repeating(expr) {
109 | this.expr = expr;
110 | }
111 | Repeating.prototype.toString = function() {
112 | return "{" + this.expr + "}";
113 | };
114 | Repeating.prototype.nodetype="REPEATING";
115 |
116 |
117 | function Group(expr) {
118 | this.expr = expr;
119 | }
120 | Group.prototype.toString = function() {
121 | return "(" + this.expr + ")";
122 | };
123 | Group.prototype.nodetype="GROUP";
124 |
125 |
126 | // node factories
127 | function createNode(type, childs) {
128 | var children = new Array();
129 |
130 | for( var i = 2; i < arguments.length; i++ )
131 | children.push( arguments[i] );
132 |
133 | return new Node(type, children);
134 | };
135 |
136 | function createSyntax(set, title, comment) {
137 | return new Syntax(set, title, comment);
138 | };
139 |
140 | function createSet() {
141 | return new Set(PRODUCTION_SET);
142 | }
143 |
144 | function createProduction(id, expr) {
145 | return new Production(id, expr);
146 | }
147 |
148 | function createExpression() {
149 | return new Set(TERM_SET);
150 | }
151 |
152 | function createTerm() {
153 | return new Set(FACTOR_SET);
154 | }
155 |
156 | function createIdentifier(id) {
157 | return new Identifier(id);
158 | }
159 |
160 | function createLiteral(txt) {
161 | return new Literal(txt);
162 | }
163 |
164 | function createOptional(termset) {
165 | return new Optional(termset);
166 | }
167 |
168 | function createRepeating(termset) {
169 | return new Repeating(termset);
170 | }
171 |
172 | function createGroup(termset) {
173 | return new Group(termset);
174 | }
175 |
176 | var parseComplete = function(syn){};
177 |
178 |
179 |
180 | var _dbg_withparsetree = false;
181 | var _dbg_withtrace = false;
182 | var _dbg_withstepbystep = false;
183 |
184 | function __dbg_print( text )
185 | {
186 | print( text );
187 | }
188 |
189 | function __dbg_wait()
190 | {
191 | var kbd = new java.io.BufferedReader(
192 | new java.io.InputStreamReader( java.lang.System[ "in" ] ) );
193 |
194 | kbd.readLine();
195 | }
196 |
197 | function __lex( info )
198 | {
199 | var state = 0;
200 | var match = -1;
201 | var match_pos = 0;
202 | var start = 0;
203 | var pos = info.offset + 1;
204 |
205 | do
206 | {
207 | pos--;
208 | state = 0;
209 | match = -2;
210 | start = pos;
211 |
212 | if( info.src.length <= start )
213 | return 21;
214 |
215 | do
216 | {
217 |
218 | switch( state )
219 | {
220 | case 0:
221 | if( ( info.src.charCodeAt( pos ) >= 9 && info.src.charCodeAt( pos ) <= 10 ) || info.src.charCodeAt( pos ) == 13 || info.src.charCodeAt( pos ) == 32 ) state = 1;
222 | else if( info.src.charCodeAt( pos ) == 40 ) state = 2;
223 | else if( info.src.charCodeAt( pos ) == 41 ) state = 3;
224 | else if( info.src.charCodeAt( pos ) == 44 ) state = 4;
225 | else if( info.src.charCodeAt( pos ) == 46 || info.src.charCodeAt( pos ) == 59 ) state = 5;
226 | else if( info.src.charCodeAt( pos ) == 61 ) state = 6;
227 | else if( ( info.src.charCodeAt( pos ) >= 65 && info.src.charCodeAt( pos ) <= 90 ) || info.src.charCodeAt( pos ) == 95 || ( info.src.charCodeAt( pos ) >= 97 && info.src.charCodeAt( pos ) <= 122 ) ) state = 7;
228 | else if( info.src.charCodeAt( pos ) == 91 ) state = 8;
229 | else if( info.src.charCodeAt( pos ) == 93 ) state = 9;
230 | else if( info.src.charCodeAt( pos ) == 123 ) state = 10;
231 | else if( info.src.charCodeAt( pos ) == 124 ) state = 11;
232 | else if( info.src.charCodeAt( pos ) == 125 ) state = 12;
233 | else if( info.src.charCodeAt( pos ) == 34 ) state = 14;
234 | else if( info.src.charCodeAt( pos ) == 39 ) state = 16;
235 | else state = -1;
236 | break;
237 |
238 | case 1:
239 | state = -1;
240 | match = 1;
241 | match_pos = pos;
242 | break;
243 |
244 | case 2:
245 | state = -1;
246 | match = 9;
247 | match_pos = pos;
248 | break;
249 |
250 | case 3:
251 | state = -1;
252 | match = 10;
253 | match_pos = pos;
254 | break;
255 |
256 | case 4:
257 | state = -1;
258 | match = 3;
259 | match_pos = pos;
260 | break;
261 |
262 | case 5:
263 | state = -1;
264 | match = 11;
265 | match_pos = pos;
266 | break;
267 |
268 | case 6:
269 | state = -1;
270 | match = 2;
271 | match_pos = pos;
272 | break;
273 |
274 | case 7:
275 | if( ( info.src.charCodeAt( pos ) >= 48 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 65 && info.src.charCodeAt( pos ) <= 90 ) || info.src.charCodeAt( pos ) == 95 || ( info.src.charCodeAt( pos ) >= 97 && info.src.charCodeAt( pos ) <= 122 ) ) state = 7;
276 | else state = -1;
277 | match = 12;
278 | match_pos = pos;
279 | break;
280 |
281 | case 8:
282 | state = -1;
283 | match = 7;
284 | match_pos = pos;
285 | break;
286 |
287 | case 9:
288 | state = -1;
289 | match = 8;
290 | match_pos = pos;
291 | break;
292 |
293 | case 10:
294 | state = -1;
295 | match = 5;
296 | match_pos = pos;
297 | break;
298 |
299 | case 11:
300 | state = -1;
301 | match = 4;
302 | match_pos = pos;
303 | break;
304 |
305 | case 12:
306 | state = -1;
307 | match = 6;
308 | match_pos = pos;
309 | break;
310 |
311 | case 13:
312 | state = -1;
313 | match = 13;
314 | match_pos = pos;
315 | break;
316 |
317 | case 14:
318 | if( info.src.charCodeAt( pos ) == 34 ) state = 13;
319 | else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 14;
320 | else if( info.src.charCodeAt( pos ) == 92 ) state = 17;
321 | else state = -1;
322 | break;
323 |
324 | case 15:
325 | if( info.src.charCodeAt( pos ) == 34 ) state = 13;
326 | else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 14;
327 | else if( info.src.charCodeAt( pos ) == 92 ) state = 17;
328 | else state = -1;
329 | match = 13;
330 | match_pos = pos;
331 | break;
332 |
333 | case 16:
334 | if( info.src.charCodeAt( pos ) == 39 ) state = 13;
335 | else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 38 ) || ( info.src.charCodeAt( pos ) >= 40 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 16;
336 | else if( info.src.charCodeAt( pos ) == 92 ) state = 18;
337 | else state = -1;
338 | break;
339 |
340 | case 17:
341 | if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 14;
342 | else if( info.src.charCodeAt( pos ) == 34 ) state = 15;
343 | else if( info.src.charCodeAt( pos ) == 92 ) state = 17;
344 | else state = -1;
345 | break;
346 |
347 | case 18:
348 | if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 16;
349 | else state = -1;
350 | break;
351 |
352 | }
353 |
354 |
355 | pos++;
356 |
357 | }
358 | while( state > -1 );
359 |
360 | }
361 | while( 1 > -1 && match == 1 );
362 |
363 | if( match > -1 )
364 | {
365 | info.att = info.src.substr( start, match_pos - start );
366 | info.offset = match_pos;
367 |
368 | switch( match )
369 | {
370 | case 13:
371 | {
372 | ;
373 | info.att = info.att.substr( 1, info.att.length - 2 );
374 | info.att = info.att.replace(/\\\'/g,"\'").replace(/\\\"/g,"\"").replace(/\\\\/g,"\\");
375 | }
376 | break;
377 |
378 | }
379 |
380 |
381 | }
382 | else
383 | {
384 | info.att = new String();
385 | match = -1;
386 | }
387 |
388 | return match;
389 | }
390 |
391 |
392 | function __parse( src, err_off, err_la )
393 | {
394 | var sstack = new Array();
395 | var vstack = new Array();
396 | var err_cnt = 0;
397 | var act;
398 | var go;
399 | var la;
400 | var rval;
401 | var parseinfo = new Function( "", "var offset; var src; var att;" );
402 | var info = new parseinfo();
403 |
404 | //Visual parse tree generation
405 | var treenode = new Function( "", "var sym; var att; var child;" );
406 | var treenodes = new Array();
407 | var tree = new Array();
408 | var tmptree = null;
409 |
410 | /* Pop-Table */
411 | var pop_tab = new Array(
412 | new Array( 0/* root' */, 1 ),
413 | new Array( 15/* root */, 1 ),
414 | new Array( 15/* root */, 0 ),
415 | new Array( 14/* syntax */, 5 ),
416 | new Array( 14/* syntax */, 4 ),
417 | new Array( 14/* syntax */, 4 ),
418 | new Array( 14/* syntax */, 3 ),
419 | new Array( 16/* productionset */, 1 ),
420 | new Array( 16/* productionset */, 2 ),
421 | new Array( 17/* production */, 4 ),
422 | new Array( 18/* expression */, 1 ),
423 | new Array( 18/* expression */, 3 ),
424 | new Array( 19/* term */, 1 ),
425 | new Array( 19/* term */, 2 ),
426 | new Array( 20/* factor */, 1 ),
427 | new Array( 20/* factor */, 1 ),
428 | new Array( 20/* factor */, 3 ),
429 | new Array( 20/* factor */, 3 ),
430 | new Array( 20/* factor */, 3 )
431 | );
432 |
433 | /* Action-Table */
434 | var act_tab = new Array(
435 | /* State 0 */ new Array( 13/* "Literal" */,3 , 5/* "{" */,4 , 21/* "$" */,-2 ),
436 | /* State 1 */ new Array( 21/* "$" */,0 ),
437 | /* State 2 */ new Array( 21/* "$" */,-1 ),
438 | /* State 3 */ new Array( 5/* "{" */,5 ),
439 | /* State 4 */ new Array( 12/* "Identifier" */,8 ),
440 | /* State 5 */ new Array( 12/* "Identifier" */,8 ),
441 | /* State 6 */ new Array( 6/* "}" */,11 , 12/* "Identifier" */,8 ),
442 | /* State 7 */ new Array( 6/* "}" */,-7 , 12/* "Identifier" */,-7 ),
443 | /* State 8 */ new Array( 2/* "=" */,12 ),
444 | /* State 9 */ new Array( 6/* "}" */,13 , 12/* "Identifier" */,8 ),
445 | /* State 10 */ new Array( 6/* "}" */,-8 , 12/* "Identifier" */,-8 ),
446 | /* State 11 */ new Array( 13/* "Literal" */,14 , 21/* "$" */,-6 ),
447 | /* State 12 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ),
448 | /* State 13 */ new Array( 13/* "Literal" */,23 , 21/* "$" */,-4 ),
449 | /* State 14 */ new Array( 21/* "$" */,-5 ),
450 | /* State 15 */ new Array( 4/* "|" */,24 , 11/* "Terminator" */,25 ),
451 | /* State 16 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 , 11/* "Terminator" */,-10 , 4/* "|" */,-10 , 8/* "]" */,-10 , 6/* "}" */,-10 , 10/* ")" */,-10 ),
452 | /* State 17 */ new Array( 11/* "Terminator" */,-12 , 12/* "Identifier" */,-12 , 13/* "Literal" */,-12 , 7/* "[" */,-12 , 5/* "{" */,-12 , 9/* "(" */,-12 , 4/* "|" */,-12 , 8/* "]" */,-12 , 6/* "}" */,-12 , 10/* ")" */,-12 ),
453 | /* State 18 */ new Array( 11/* "Terminator" */,-14 , 12/* "Identifier" */,-14 , 13/* "Literal" */,-14 , 7/* "[" */,-14 , 5/* "{" */,-14 , 9/* "(" */,-14 , 4/* "|" */,-14 , 8/* "]" */,-14 , 6/* "}" */,-14 , 10/* ")" */,-14 ),
454 | /* State 19 */ new Array( 11/* "Terminator" */,-15 , 12/* "Identifier" */,-15 , 13/* "Literal" */,-15 , 7/* "[" */,-15 , 5/* "{" */,-15 , 9/* "(" */,-15 , 4/* "|" */,-15 , 8/* "]" */,-15 , 6/* "}" */,-15 , 10/* ")" */,-15 ),
455 | /* State 20 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ),
456 | /* State 21 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ),
457 | /* State 22 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ),
458 | /* State 23 */ new Array( 21/* "$" */,-3 ),
459 | /* State 24 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ),
460 | /* State 25 */ new Array( 6/* "}" */,-9 , 12/* "Identifier" */,-9 ),
461 | /* State 26 */ new Array( 11/* "Terminator" */,-13 , 12/* "Identifier" */,-13 , 13/* "Literal" */,-13 , 7/* "[" */,-13 , 5/* "{" */,-13 , 9/* "(" */,-13 , 4/* "|" */,-13 , 8/* "]" */,-13 , 6/* "}" */,-13 , 10/* ")" */,-13 ),
462 | /* State 27 */ new Array( 4/* "|" */,24 , 8/* "]" */,31 ),
463 | /* State 28 */ new Array( 4/* "|" */,24 , 6/* "}" */,32 ),
464 | /* State 29 */ new Array( 4/* "|" */,24 , 10/* ")" */,33 ),
465 | /* State 30 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 , 11/* "Terminator" */,-11 , 4/* "|" */,-11 , 8/* "]" */,-11 , 6/* "}" */,-11 , 10/* ")" */,-11 ),
466 | /* State 31 */ new Array( 11/* "Terminator" */,-16 , 12/* "Identifier" */,-16 , 13/* "Literal" */,-16 , 7/* "[" */,-16 , 5/* "{" */,-16 , 9/* "(" */,-16 , 4/* "|" */,-16 , 8/* "]" */,-16 , 6/* "}" */,-16 , 10/* ")" */,-16 ),
467 | /* State 32 */ new Array( 11/* "Terminator" */,-17 , 12/* "Identifier" */,-17 , 13/* "Literal" */,-17 , 7/* "[" */,-17 , 5/* "{" */,-17 , 9/* "(" */,-17 , 4/* "|" */,-17 , 8/* "]" */,-17 , 6/* "}" */,-17 , 10/* ")" */,-17 ),
468 | /* State 33 */ new Array( 11/* "Terminator" */,-18 , 12/* "Identifier" */,-18 , 13/* "Literal" */,-18 , 7/* "[" */,-18 , 5/* "{" */,-18 , 9/* "(" */,-18 , 4/* "|" */,-18 , 8/* "]" */,-18 , 6/* "}" */,-18 , 10/* ")" */,-18 )
469 | );
470 |
471 | /* Goto-Table */
472 | var goto_tab = new Array(
473 | /* State 0 */ new Array( 15/* root */,1 , 14/* syntax */,2 ),
474 | /* State 1 */ new Array( ),
475 | /* State 2 */ new Array( ),
476 | /* State 3 */ new Array( ),
477 | /* State 4 */ new Array( 16/* productionset */,6 , 17/* production */,7 ),
478 | /* State 5 */ new Array( 16/* productionset */,9 , 17/* production */,7 ),
479 | /* State 6 */ new Array( 17/* production */,10 ),
480 | /* State 7 */ new Array( ),
481 | /* State 8 */ new Array( ),
482 | /* State 9 */ new Array( 17/* production */,10 ),
483 | /* State 10 */ new Array( ),
484 | /* State 11 */ new Array( ),
485 | /* State 12 */ new Array( 18/* expression */,15 , 19/* term */,16 , 20/* factor */,17 ),
486 | /* State 13 */ new Array( ),
487 | /* State 14 */ new Array( ),
488 | /* State 15 */ new Array( ),
489 | /* State 16 */ new Array( 20/* factor */,26 ),
490 | /* State 17 */ new Array( ),
491 | /* State 18 */ new Array( ),
492 | /* State 19 */ new Array( ),
493 | /* State 20 */ new Array( 18/* expression */,27 , 19/* term */,16 , 20/* factor */,17 ),
494 | /* State 21 */ new Array( 18/* expression */,28 , 19/* term */,16 , 20/* factor */,17 ),
495 | /* State 22 */ new Array( 18/* expression */,29 , 19/* term */,16 , 20/* factor */,17 ),
496 | /* State 23 */ new Array( ),
497 | /* State 24 */ new Array( 19/* term */,30 , 20/* factor */,17 ),
498 | /* State 25 */ new Array( ),
499 | /* State 26 */ new Array( ),
500 | /* State 27 */ new Array( ),
501 | /* State 28 */ new Array( ),
502 | /* State 29 */ new Array( ),
503 | /* State 30 */ new Array( 20/* factor */,26 ),
504 | /* State 31 */ new Array( ),
505 | /* State 32 */ new Array( ),
506 | /* State 33 */ new Array( )
507 | );
508 |
509 |
510 |
511 | /* Symbol labels */
512 | var labels = new Array(
513 | "root'" /* Non-terminal symbol */,
514 | "WHITESPACE" /* Terminal symbol */,
515 | "=" /* Terminal symbol */,
516 | "," /* Terminal symbol */,
517 | "|" /* Terminal symbol */,
518 | "{" /* Terminal symbol */,
519 | "}" /* Terminal symbol */,
520 | "[" /* Terminal symbol */,
521 | "]" /* Terminal symbol */,
522 | "(" /* Terminal symbol */,
523 | ")" /* Terminal symbol */,
524 | "Terminator" /* Terminal symbol */,
525 | "Identifier" /* Terminal symbol */,
526 | "Literal" /* Terminal symbol */,
527 | "syntax" /* Non-terminal symbol */,
528 | "root" /* Non-terminal symbol */,
529 | "productionset" /* Non-terminal symbol */,
530 | "production" /* Non-terminal symbol */,
531 | "expression" /* Non-terminal symbol */,
532 | "term" /* Non-terminal symbol */,
533 | "factor" /* Non-terminal symbol */,
534 | "$" /* Terminal symbol */
535 | );
536 |
537 |
538 |
539 | info.offset = 0;
540 | info.src = src;
541 | info.att = new String();
542 |
543 | if( !err_off )
544 | err_off = new Array();
545 | if( !err_la )
546 | err_la = new Array();
547 |
548 | sstack.push( 0 );
549 | vstack.push( 0 );
550 |
551 | la = __lex( info );
552 |
553 | while( true )
554 | {
555 | act = 35;
556 | for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
557 | {
558 | if( act_tab[sstack[sstack.length-1]][i] == la )
559 | {
560 | act = act_tab[sstack[sstack.length-1]][i+1];
561 | break;
562 | }
563 | }
564 |
565 | /*
566 | _print( "state " + sstack[sstack.length-1] + " la = " + la + " info.att = >" +
567 | info.att + "< act = " + act + " src = >" + info.src.substr( info.offset, 30 ) + "..." + "<" +
568 | " sstack = " + sstack.join() );
569 | */
570 |
571 | if( _dbg_withtrace && sstack.length > 0 )
572 | {
573 | __dbg_print( "\nState " + sstack[sstack.length-1] + "\n" +
574 | "\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" +
575 | "\tAction: " + act + "\n" +
576 | "\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ?
577 | "..." : "" ) + "\"\n" +
578 | "\tStack: " + sstack.join() + "\n" +
579 | "\tValue stack: " + vstack.join() + "\n" );
580 |
581 | if( _dbg_withstepbystep )
582 | __dbg_wait();
583 | }
584 |
585 |
586 | //Panic-mode: Try recovery when parse-error occurs!
587 | if( act == 35 )
588 | {
589 | if( _dbg_withtrace )
590 | __dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] );
591 |
592 | err_cnt++;
593 | err_off.push( info.offset - info.att.length );
594 | err_la.push( new Array() );
595 | for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
596 | err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] );
597 |
598 | //Remember the original stack!
599 | var rsstack = new Array();
600 | var rvstack = new Array();
601 | for( var i = 0; i < sstack.length; i++ )
602 | {
603 | rsstack[i] = sstack[i];
604 | rvstack[i] = vstack[i];
605 | }
606 |
607 | while( act == 35 && la != 21 )
608 | {
609 | if( _dbg_withtrace )
610 | __dbg_print( "\tError recovery\n" +
611 | "Current lookahead: " + labels[la] + " (" + info.att + ")\n" +
612 | "Action: " + act + "\n\n" );
613 | if( la == -1 )
614 | info.offset++;
615 |
616 | while( act == 35 && sstack.length > 0 )
617 | {
618 | sstack.pop();
619 | vstack.pop();
620 |
621 | if( sstack.length == 0 )
622 | break;
623 |
624 | act = 35;
625 | for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
626 | {
627 | if( act_tab[sstack[sstack.length-1]][i] == la )
628 | {
629 | act = act_tab[sstack[sstack.length-1]][i+1];
630 | break;
631 | }
632 | }
633 | }
634 |
635 | if( act != 35 )
636 | break;
637 |
638 | for( var i = 0; i < rsstack.length; i++ )
639 | {
640 | sstack.push( rsstack[i] );
641 | vstack.push( rvstack[i] );
642 | }
643 |
644 | la = __lex( info );
645 | }
646 |
647 | if( act == 35 )
648 | {
649 | if( _dbg_withtrace )
650 | __dbg_print( "\tError recovery failed, terminating parse process..." );
651 | break;
652 | }
653 |
654 |
655 | if( _dbg_withtrace )
656 | __dbg_print( "\tError recovery succeeded, continuing" );
657 | }
658 |
659 | /*
660 | if( act == 35 )
661 | break;
662 | */
663 |
664 |
665 | //Shift
666 | if( act > 0 )
667 | {
668 | //Parse tree generation
669 | if( _dbg_withparsetree )
670 | {
671 | var node = new treenode();
672 | node.sym = labels[ la ];
673 | node.att = info.att;
674 | node.child = new Array();
675 | tree.push( treenodes.length );
676 | treenodes.push( node );
677 | }
678 |
679 | if( _dbg_withtrace )
680 | __dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" );
681 |
682 | sstack.push( act );
683 | vstack.push( info.att );
684 |
685 | la = __lex( info );
686 |
687 | if( _dbg_withtrace )
688 | __dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" );
689 | }
690 | //Reduce
691 | else
692 | {
693 | act *= -1;
694 |
695 | if( _dbg_withtrace )
696 | __dbg_print( "Reducing by producution: " + act );
697 |
698 | rval = void(0);
699 |
700 | if( _dbg_withtrace )
701 | __dbg_print( "\tPerforming semantic action..." );
702 |
703 | switch( act )
704 | {
705 | case 0:
706 | {
707 | rval = vstack[ vstack.length - 1 ];
708 | }
709 | break;
710 | case 1:
711 | {
712 | parseComplete(vstack[ vstack.length - 1 ]);
713 | }
714 | break;
715 | case 2:
716 | {
717 | rval = vstack[ vstack.length - 0 ];
718 | }
719 | break;
720 | case 3:
721 | {
722 | rval = createSyntax( vstack[ vstack.length - 3 ], vstack[ vstack.length - 5 ], vstack[ vstack.length - 1 ]);
723 | }
724 | break;
725 | case 4:
726 | {
727 | rval = createSyntax( vstack[ vstack.length - 2 ], vstack[ vstack.length - 4 ]);
728 | }
729 | break;
730 | case 5:
731 | {
732 | rval = createSyntax( vstack[ vstack.length - 3 ], undefined, vstack[ vstack.length - 1 ]);
733 | }
734 | break;
735 | case 6:
736 | {
737 | rval = createSyntax( vstack[ vstack.length - 2 ]);
738 | }
739 | break;
740 | case 7:
741 | {
742 | rval = createSet(); rval.addChild(vstack[ vstack.length - 1 ]);
743 | }
744 | break;
745 | case 8:
746 | {
747 | rval = vstack[ vstack.length - 2 ]; rval.addChild(vstack[ vstack.length - 1 ]);
748 | }
749 | break;
750 | case 9:
751 | {
752 | rval = createProduction(vstack[ vstack.length - 4 ], vstack[ vstack.length - 2 ]);
753 | }
754 | break;
755 | case 10:
756 | {
757 | rval = createExpression(); rval.addChild(vstack[ vstack.length - 1 ]);
758 | }
759 | break;
760 | case 11:
761 | {
762 | rval = vstack[ vstack.length - 3 ]; rval.addChild(vstack[ vstack.length - 1 ]);
763 | }
764 | break;
765 | case 12:
766 | {
767 | rval = createTerm(); rval.addChild(vstack[ vstack.length - 1 ]);
768 | }
769 | break;
770 | case 13:
771 | {
772 | rval = vstack[ vstack.length - 2 ]; vstack[ vstack.length - 2 ].addChild(vstack[ vstack.length - 1 ]);
773 | }
774 | break;
775 | case 14:
776 | {
777 | rval = createIdentifier(vstack[ vstack.length - 1 ]);
778 | }
779 | break;
780 | case 15:
781 | {
782 | rval = createLiteral(vstack[ vstack.length - 1 ]);
783 | }
784 | break;
785 | case 16:
786 | {
787 | rval = createOptional(vstack[ vstack.length - 2 ]);
788 | }
789 | break;
790 | case 17:
791 | {
792 | rval = createRepeating(vstack[ vstack.length - 2 ]);
793 | }
794 | break;
795 | case 18:
796 | {
797 | rval = createGroup(vstack[ vstack.length - 2 ]);
798 | }
799 | break;
800 | }
801 |
802 |
803 |
804 | if( _dbg_withparsetree )
805 | tmptree = new Array();
806 |
807 | if( _dbg_withtrace )
808 | __dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." );
809 |
810 | for( var i = 0; i < pop_tab[act][1]; i++ )
811 | {
812 | if( _dbg_withparsetree )
813 | tmptree.push( tree.pop() );
814 |
815 | sstack.pop();
816 | vstack.pop();
817 | }
818 |
819 | go = -1;
820 | for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 )
821 | {
822 | if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] )
823 | {
824 | go = goto_tab[sstack[sstack.length-1]][i+1];
825 | break;
826 | }
827 | }
828 |
829 | if( _dbg_withparsetree )
830 | {
831 | var node = new treenode();
832 | node.sym = labels[ pop_tab[act][0] ];
833 | node.att = new String();
834 | node.child = tmptree.reverse();
835 | tree.push( treenodes.length );
836 | treenodes.push( node );
837 | }
838 |
839 | if( act == 0 )
840 | break;
841 |
842 | if( _dbg_withtrace )
843 | __dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] );
844 |
845 | sstack.push( go );
846 | vstack.push( rval );
847 | }
848 | }
849 |
850 | if( _dbg_withtrace )
851 | __dbg_print( "\nParse complete." );
852 |
853 | if( _dbg_withparsetree )
854 | {
855 | if( err_cnt == 0 )
856 | {
857 | __dbg_print( "\n\n--- Parse tree ---" );
858 | __dbg_parsetree( 0, treenodes, tree );
859 | }
860 | else
861 | {
862 | __dbg_print( "\n\nParse tree cannot be viewed. There where parse errors." );
863 | }
864 | }
865 |
866 | return err_cnt;
867 | }
868 |
869 |
870 | function __dbg_parsetree( indent, nodes, tree )
871 | {
872 | var str = new String();
873 | for( var i = 0; i < tree.length; i++ )
874 | {
875 | str = "";
876 | for( var j = indent; j > 0; j-- )
877 | str += "\t";
878 |
879 | str += nodes[ tree[i] ].sym;
880 | if( nodes[ tree[i] ].att != "" )
881 | str += " >" + nodes[ tree[i] ].att + "<" ;
882 |
883 | __dbg_print( str );
884 | if( nodes[ tree[i] ].child.length > 0 )
885 | __dbg_parsetree( indent + 1, nodes, nodes[ tree[i] ].child );
886 | }
887 | }
888 |
889 |
890 |
891 | $.extend({
892 | "ebnfParse": function(str, fn, errfn) {
893 |
894 | parseComplete = fn || function(syn) {
895 | alert(syn.toString());
896 | };
897 |
898 | var parseErrors = errfn || function(err) {
899 | alert("errors#" + err.length);
900 | };
901 |
902 |
903 | var error_cnt = 0;
904 | var error_off = new Array();
905 | var error_la = new Array();
906 |
907 | var errors;
908 | if( ( error_cnt = __parse( str, error_off, error_la ) ) > 0 )
909 | {
910 | errors=new Array();
911 | for( var i = 0; i < error_cnt; i++ ) {
912 | var msg = "Parse error near >" + str.substr( error_off[i], 30 ) +
913 | "<, expecting \"" + error_la[i].join() + "\"" ;
914 | errors.push(msg);
915 | }
916 | parseErrors(errors);
917 | }
918 | }
919 | });
920 |
921 | })(jQuery);
922 |
923 |
--------------------------------------------------------------------------------
/src/test/webenv/qunit/qunit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | (function(window) {
12 |
13 | var defined = {
14 | setTimeout: typeof window.setTimeout !== "undefined",
15 | sessionStorage: (function() {
16 | try {
17 | return !!sessionStorage.getItem;
18 | } catch(e) {
19 | return false;
20 | }
21 | })()
22 | };
23 |
24 | var testId = 0;
25 |
26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
27 | this.name = name;
28 | this.testName = testName;
29 | this.expected = expected;
30 | this.testEnvironmentArg = testEnvironmentArg;
31 | this.async = async;
32 | this.callback = callback;
33 | this.assertions = [];
34 | };
35 | Test.prototype = {
36 | init: function() {
37 | var tests = id("qunit-tests");
38 | if (tests) {
39 | var b = document.createElement("strong");
40 | b.innerHTML = "Running " + this.name;
41 | var li = document.createElement("li");
42 | li.appendChild( b );
43 | li.className = "running";
44 | li.id = this.id = "test-output" + testId++;
45 | tests.appendChild( li );
46 | }
47 | },
48 | setup: function() {
49 | if (this.module != config.previousModule) {
50 | if ( config.previousModule ) {
51 | QUnit.moduleDone( {
52 | name: config.previousModule,
53 | failed: config.moduleStats.bad,
54 | passed: config.moduleStats.all - config.moduleStats.bad,
55 | total: config.moduleStats.all
56 | } );
57 | }
58 | config.previousModule = this.module;
59 | config.moduleStats = { all: 0, bad: 0 };
60 | QUnit.moduleStart( {
61 | name: this.module
62 | } );
63 | }
64 |
65 | config.current = this;
66 | this.testEnvironment = extend({
67 | setup: function() {},
68 | teardown: function() {}
69 | }, this.moduleTestEnvironment);
70 | if (this.testEnvironmentArg) {
71 | extend(this.testEnvironment, this.testEnvironmentArg);
72 | }
73 |
74 | QUnit.testStart( {
75 | name: this.testName
76 | } );
77 |
78 | // allow utility functions to access the current test environment
79 | // TODO why??
80 | QUnit.current_testEnvironment = this.testEnvironment;
81 |
82 | try {
83 | if ( !config.pollution ) {
84 | saveGlobal();
85 | }
86 |
87 | this.testEnvironment.setup.call(this.testEnvironment);
88 | } catch(e) {
89 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
90 | }
91 | },
92 | run: function() {
93 | if ( this.async ) {
94 | QUnit.stop();
95 | }
96 |
97 | if ( config.notrycatch ) {
98 | this.callback.call(this.testEnvironment);
99 | return;
100 | }
101 | try {
102 | this.callback.call(this.testEnvironment);
103 | } catch(e) {
104 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
105 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
106 | // else next test will carry the responsibility
107 | saveGlobal();
108 |
109 | // Restart the tests if they're blocking
110 | if ( config.blocking ) {
111 | start();
112 | }
113 | }
114 | },
115 | teardown: function() {
116 | try {
117 | this.testEnvironment.teardown.call(this.testEnvironment);
118 | checkPollution();
119 | } catch(e) {
120 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
121 | }
122 | },
123 | finish: function() {
124 | if ( this.expected && this.expected != this.assertions.length ) {
125 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
126 | }
127 |
128 | var good = 0, bad = 0,
129 | tests = id("qunit-tests");
130 |
131 | config.stats.all += this.assertions.length;
132 | config.moduleStats.all += this.assertions.length;
133 |
134 | if ( tests ) {
135 | var ol = document.createElement("ol");
136 |
137 | for ( var i = 0; i < this.assertions.length; i++ ) {
138 | var assertion = this.assertions[i];
139 |
140 | var li = document.createElement("li");
141 | li.className = assertion.result ? "pass" : "fail";
142 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
143 | ol.appendChild( li );
144 |
145 | if ( assertion.result ) {
146 | good++;
147 | } else {
148 | bad++;
149 | config.stats.bad++;
150 | config.moduleStats.bad++;
151 | }
152 | }
153 |
154 | // store result when possible
155 | if ( QUnit.config.reorder && defined.sessionStorage ) {
156 | if (bad) {
157 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
158 | } else {
159 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
160 | }
161 | }
162 |
163 | if (bad == 0) {
164 | ol.style.display = "none";
165 | }
166 |
167 | var b = document.createElement("strong");
168 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
169 |
170 | var a = document.createElement("a");
171 | a.innerHTML = "Rerun";
172 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
173 |
174 | addEvent(b, "click", function() {
175 | var next = b.nextSibling.nextSibling,
176 | display = next.style.display;
177 | next.style.display = display === "none" ? "block" : "none";
178 | });
179 |
180 | addEvent(b, "dblclick", function(e) {
181 | var target = e && e.target ? e.target : window.event.srcElement;
182 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
183 | target = target.parentNode;
184 | }
185 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
186 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
187 | }
188 | });
189 |
190 | var li = id(this.id);
191 | li.className = bad ? "fail" : "pass";
192 | li.removeChild( li.firstChild );
193 | li.appendChild( b );
194 | li.appendChild( a );
195 | li.appendChild( ol );
196 |
197 | } else {
198 | for ( var i = 0; i < this.assertions.length; i++ ) {
199 | if ( !this.assertions[i].result ) {
200 | bad++;
201 | config.stats.bad++;
202 | config.moduleStats.bad++;
203 | }
204 | }
205 | }
206 |
207 | try {
208 | QUnit.reset();
209 | } catch(e) {
210 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
211 | }
212 |
213 | QUnit.testDone( {
214 | name: this.testName,
215 | failed: bad,
216 | passed: this.assertions.length - bad,
217 | total: this.assertions.length
218 | } );
219 | },
220 |
221 | queue: function() {
222 | var test = this;
223 | synchronize(function() {
224 | test.init();
225 | });
226 | function run() {
227 | // each of these can by async
228 | synchronize(function() {
229 | test.setup();
230 | });
231 | synchronize(function() {
232 | test.run();
233 | });
234 | synchronize(function() {
235 | test.teardown();
236 | });
237 | synchronize(function() {
238 | test.finish();
239 | });
240 | }
241 | // defer when previous test run passed, if storage is available
242 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
243 | if (bad) {
244 | run();
245 | } else {
246 | synchronize(run);
247 | };
248 | }
249 |
250 | };
251 |
252 | var QUnit = {
253 |
254 | // call on start of module test to prepend name to all tests
255 | module: function(name, testEnvironment) {
256 | config.currentModule = name;
257 | config.currentModuleTestEnviroment = testEnvironment;
258 | },
259 |
260 | asyncTest: function(testName, expected, callback) {
261 | if ( arguments.length === 2 ) {
262 | callback = expected;
263 | expected = 0;
264 | }
265 |
266 | QUnit.test(testName, expected, callback, true);
267 | },
268 |
269 | test: function(testName, expected, callback, async) {
270 | var name = '' + testName + '', testEnvironmentArg;
271 |
272 | if ( arguments.length === 2 ) {
273 | callback = expected;
274 | expected = null;
275 | }
276 | // is 2nd argument a testEnvironment?
277 | if ( expected && typeof expected === 'object') {
278 | testEnvironmentArg = expected;
279 | expected = null;
280 | }
281 |
282 | if ( config.currentModule ) {
283 | name = '' + config.currentModule + ": " + name;
284 | }
285 |
286 | if ( !validTest(config.currentModule + ": " + testName) ) {
287 | return;
288 | }
289 |
290 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
291 | test.module = config.currentModule;
292 | test.moduleTestEnvironment = config.currentModuleTestEnviroment;
293 | test.queue();
294 | },
295 |
296 | /**
297 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
298 | */
299 | expect: function(asserts) {
300 | config.current.expected = asserts;
301 | },
302 |
303 | /**
304 | * Asserts true.
305 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
306 | */
307 | ok: function(a, msg) {
308 | a = !!a;
309 | var details = {
310 | result: a,
311 | message: msg
312 | };
313 | msg = escapeHtml(msg);
314 | QUnit.log(details);
315 | config.current.assertions.push({
316 | result: a,
317 | message: msg
318 | });
319 | },
320 |
321 | /**
322 | * Checks that the first two arguments are equal, with an optional message.
323 | * Prints out both actual and expected values.
324 | *
325 | * Prefered to ok( actual == expected, message )
326 | *
327 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
328 | *
329 | * @param Object actual
330 | * @param Object expected
331 | * @param String message (optional)
332 | */
333 | equal: function(actual, expected, message) {
334 | QUnit.push(expected == actual, actual, expected, message);
335 | },
336 |
337 | notEqual: function(actual, expected, message) {
338 | QUnit.push(expected != actual, actual, expected, message);
339 | },
340 |
341 | deepEqual: function(actual, expected, message) {
342 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
343 | },
344 |
345 | notDeepEqual: function(actual, expected, message) {
346 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
347 | },
348 |
349 | strictEqual: function(actual, expected, message) {
350 | QUnit.push(expected === actual, actual, expected, message);
351 | },
352 |
353 | notStrictEqual: function(actual, expected, message) {
354 | QUnit.push(expected !== actual, actual, expected, message);
355 | },
356 |
357 | raises: function(block, expected, message) {
358 | var actual, ok = false;
359 |
360 | if (typeof expected === 'string') {
361 | message = expected;
362 | expected = null;
363 | }
364 |
365 | try {
366 | block();
367 | } catch (e) {
368 | actual = e;
369 | }
370 |
371 | if (actual) {
372 | // we don't want to validate thrown error
373 | if (!expected) {
374 | ok = true;
375 | // expected is a regexp
376 | } else if (QUnit.objectType(expected) === "regexp") {
377 | ok = expected.test(actual);
378 | // expected is a constructor
379 | } else if (actual instanceof expected) {
380 | ok = true;
381 | // expected is a validation function which returns true is validation passed
382 | } else if (expected.call({}, actual) === true) {
383 | ok = true;
384 | }
385 | }
386 |
387 | QUnit.ok(ok, message);
388 | },
389 |
390 | start: function() {
391 | config.semaphore--;
392 | if (config.semaphore > 0) {
393 | // don't start until equal number of stop-calls
394 | return;
395 | }
396 | if (config.semaphore < 0) {
397 | // ignore if start is called more often then stop
398 | config.semaphore = 0;
399 | }
400 | // A slight delay, to avoid any current callbacks
401 | if ( defined.setTimeout ) {
402 | window.setTimeout(function() {
403 | if (config.semaphore > 0) {
404 | return;
405 | }
406 | if ( config.timeout ) {
407 | clearTimeout(config.timeout);
408 | }
409 |
410 | config.blocking = false;
411 | process();
412 | }, 13);
413 | } else {
414 | config.blocking = false;
415 | process();
416 | }
417 | },
418 |
419 | stop: function(timeout) {
420 | config.semaphore++;
421 | config.blocking = true;
422 |
423 | if ( timeout && defined.setTimeout ) {
424 | clearTimeout(config.timeout);
425 | config.timeout = window.setTimeout(function() {
426 | QUnit.ok( false, "Test timed out" );
427 | QUnit.start();
428 | }, timeout);
429 | }
430 | }
431 | };
432 |
433 | // Backwards compatibility, deprecated
434 | QUnit.equals = QUnit.equal;
435 | QUnit.same = QUnit.deepEqual;
436 |
437 | // Maintain internal state
438 | var config = {
439 | // The queue of tests to run
440 | queue: [],
441 |
442 | // block until document ready
443 | blocking: true,
444 |
445 | // when enabled, show only failing tests
446 | // gets persisted through sessionStorage and can be changed in UI via checkbox
447 | hidepassed: false,
448 |
449 | // by default, run previously failed tests first
450 | // very useful in combination with "Hide passed tests" checked
451 | reorder: true,
452 |
453 | // by default, modify document.title when suite is done
454 | altertitle: true,
455 |
456 | noglobals: false,
457 | notrycatch: false
458 | };
459 |
460 | // Load paramaters
461 | (function() {
462 | var location = window.location || { search: "", protocol: "file:" },
463 | params = location.search.slice( 1 ).split( "&" ),
464 | length = params.length,
465 | urlParams = {},
466 | current;
467 |
468 | if ( params[ 0 ] ) {
469 | for ( var i = 0; i < length; i++ ) {
470 | current = params[ i ].split( "=" );
471 | current[ 0 ] = decodeURIComponent( current[ 0 ] );
472 | // allow just a key to turn on a flag, e.g., test.html?noglobals
473 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
474 | urlParams[ current[ 0 ] ] = current[ 1 ];
475 | if ( current[ 0 ] in config ) {
476 | config[ current[ 0 ] ] = current[ 1 ];
477 | }
478 | }
479 | }
480 |
481 | QUnit.urlParams = urlParams;
482 | config.filter = urlParams.filter;
483 |
484 | // Figure out if we're running the tests from a server or not
485 | QUnit.isLocal = !!(location.protocol === 'file:');
486 | })();
487 |
488 | // Expose the API as global variables, unless an 'exports'
489 | // object exists, in that case we assume we're in CommonJS
490 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
491 | extend(window, QUnit);
492 | window.QUnit = QUnit;
493 | } else {
494 | extend(exports, QUnit);
495 | exports.QUnit = QUnit;
496 | }
497 |
498 | // define these after exposing globals to keep them in these QUnit namespace only
499 | extend(QUnit, {
500 | config: config,
501 |
502 | // Initialize the configuration options
503 | init: function() {
504 | extend(config, {
505 | stats: { all: 0, bad: 0 },
506 | moduleStats: { all: 0, bad: 0 },
507 | started: +new Date,
508 | updateRate: 1000,
509 | blocking: false,
510 | autostart: true,
511 | autorun: false,
512 | filter: "",
513 | queue: [],
514 | semaphore: 0
515 | });
516 |
517 | var tests = id( "qunit-tests" ),
518 | banner = id( "qunit-banner" ),
519 | result = id( "qunit-testresult" );
520 |
521 | if ( tests ) {
522 | tests.innerHTML = "";
523 | }
524 |
525 | if ( banner ) {
526 | banner.className = "";
527 | }
528 |
529 | if ( result ) {
530 | result.parentNode.removeChild( result );
531 | }
532 |
533 | if ( tests ) {
534 | result = document.createElement( "p" );
535 | result.id = "qunit-testresult";
536 | result.className = "result";
537 | tests.parentNode.insertBefore( result, tests );
538 | result.innerHTML = 'Running...
';
539 | }
540 | },
541 |
542 | /**
543 | * Resets the test setup. Useful for tests that modify the DOM.
544 | *
545 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
546 | */
547 | reset: function() {
548 | if ( window.jQuery ) {
549 | jQuery( "#qunit-fixture" ).html( config.fixture );
550 | } else {
551 | var main = id( 'qunit-fixture' );
552 | if ( main ) {
553 | main.innerHTML = config.fixture;
554 | }
555 | }
556 | },
557 |
558 | /**
559 | * Trigger an event on an element.
560 | *
561 | * @example triggerEvent( document.body, "click" );
562 | *
563 | * @param DOMElement elem
564 | * @param String type
565 | */
566 | triggerEvent: function( elem, type, event ) {
567 | if ( document.createEvent ) {
568 | event = document.createEvent("MouseEvents");
569 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
570 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
571 | elem.dispatchEvent( event );
572 |
573 | } else if ( elem.fireEvent ) {
574 | elem.fireEvent("on"+type);
575 | }
576 | },
577 |
578 | // Safe object type checking
579 | is: function( type, obj ) {
580 | return QUnit.objectType( obj ) == type;
581 | },
582 |
583 | objectType: function( obj ) {
584 | if (typeof obj === "undefined") {
585 | return "undefined";
586 |
587 | // consider: typeof null === object
588 | }
589 | if (obj === null) {
590 | return "null";
591 | }
592 |
593 | var type = Object.prototype.toString.call( obj )
594 | .match(/^\[object\s(.*)\]$/)[1] || '';
595 |
596 | switch (type) {
597 | case 'Number':
598 | if (isNaN(obj)) {
599 | return "nan";
600 | } else {
601 | return "number";
602 | }
603 | case 'String':
604 | case 'Boolean':
605 | case 'Array':
606 | case 'Date':
607 | case 'RegExp':
608 | case 'Function':
609 | return type.toLowerCase();
610 | }
611 | if (typeof obj === "object") {
612 | return "object";
613 | }
614 | return undefined;
615 | },
616 |
617 | push: function(result, actual, expected, message) {
618 | var details = {
619 | result: result,
620 | message: message,
621 | actual: actual,
622 | expected: expected
623 | };
624 |
625 | message = escapeHtml(message) || (result ? "okay" : "failed");
626 | message = '' + message + "";
627 | expected = escapeHtml(QUnit.jsDump.parse(expected));
628 | actual = escapeHtml(QUnit.jsDump.parse(actual));
629 | var output = message + '| Expected: | ' + expected + ' |
';
630 | if (actual != expected) {
631 | output += '| Result: | ' + actual + ' |
';
632 | output += '| Diff: | ' + QUnit.diff(expected, actual) +' |
';
633 | }
634 | if (!result) {
635 | var source = sourceFromStacktrace();
636 | if (source) {
637 | details.source = source;
638 | output += '| Source: | ' + escapeHtml(source) + ' |
';
639 | }
640 | }
641 | output += "
";
642 |
643 | QUnit.log(details);
644 |
645 | config.current.assertions.push({
646 | result: !!result,
647 | message: output
648 | });
649 | },
650 |
651 | url: function( params ) {
652 | params = extend( extend( {}, QUnit.urlParams ), params );
653 | var querystring = "?",
654 | key;
655 | for ( key in params ) {
656 | querystring += encodeURIComponent( key ) + "=" +
657 | encodeURIComponent( params[ key ] ) + "&";
658 | }
659 | return window.location.pathname + querystring.slice( 0, -1 );
660 | },
661 |
662 | extend: extend,
663 | id: id,
664 | addEvent: addEvent,
665 |
666 | // Logging callbacks; all receive a single argument with the listed properties
667 | // run test/logs.html for any related changes
668 | begin: function() {},
669 | // done: { failed, passed, total, runtime }
670 | done: function() {},
671 | // log: { result, actual, expected, message }
672 | log: function() {},
673 | // testStart: { name }
674 | testStart: function() {},
675 | // testDone: { name, failed, passed, total }
676 | testDone: function() {},
677 | // moduleStart: { name }
678 | moduleStart: function() {},
679 | // moduleDone: { name, failed, passed, total }
680 | moduleDone: function() {}
681 | });
682 |
683 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
684 | config.autorun = true;
685 | }
686 |
687 | QUnit.load = function() {
688 | QUnit.begin({});
689 |
690 | // Initialize the config, saving the execution queue
691 | var oldconfig = extend({}, config);
692 | QUnit.init();
693 | extend(config, oldconfig);
694 |
695 | config.blocking = false;
696 |
697 | var userAgent = id("qunit-userAgent");
698 | if ( userAgent ) {
699 | userAgent.innerHTML = navigator.userAgent;
700 | }
701 | var banner = id("qunit-header");
702 | if ( banner ) {
703 | banner.innerHTML = ' ' + banner.innerHTML + ' ' +
704 | '' +
705 | '';
706 | addEvent( banner, "change", function( event ) {
707 | var params = {};
708 | params[ event.target.name ] = event.target.checked ? true : undefined;
709 | window.location = QUnit.url( params );
710 | });
711 | }
712 |
713 | var toolbar = id("qunit-testrunner-toolbar");
714 | if ( toolbar ) {
715 | var filter = document.createElement("input");
716 | filter.type = "checkbox";
717 | filter.id = "qunit-filter-pass";
718 | addEvent( filter, "click", function() {
719 | var ol = document.getElementById("qunit-tests");
720 | if ( filter.checked ) {
721 | ol.className = ol.className + " hidepass";
722 | } else {
723 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
724 | ol.className = tmp.replace(/ hidepass /, " ");
725 | }
726 | if ( defined.sessionStorage ) {
727 | if (filter.checked) {
728 | sessionStorage.setItem("qunit-filter-passed-tests", "true");
729 | } else {
730 | sessionStorage.removeItem("qunit-filter-passed-tests");
731 | }
732 | }
733 | });
734 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
735 | filter.checked = true;
736 | var ol = document.getElementById("qunit-tests");
737 | ol.className = ol.className + " hidepass";
738 | }
739 | toolbar.appendChild( filter );
740 |
741 | var label = document.createElement("label");
742 | label.setAttribute("for", "qunit-filter-pass");
743 | label.innerHTML = "Hide passed tests";
744 | toolbar.appendChild( label );
745 | }
746 |
747 | var main = id('qunit-fixture');
748 | if ( main ) {
749 | config.fixture = main.innerHTML;
750 | }
751 |
752 | if (config.autostart) {
753 | QUnit.start();
754 | }
755 | };
756 |
757 | addEvent(window, "load", QUnit.load);
758 |
759 | function done() {
760 | config.autorun = true;
761 |
762 | // Log the last module results
763 | if ( config.currentModule ) {
764 | QUnit.moduleDone( {
765 | name: config.currentModule,
766 | failed: config.moduleStats.bad,
767 | passed: config.moduleStats.all - config.moduleStats.bad,
768 | total: config.moduleStats.all
769 | } );
770 | }
771 |
772 | var banner = id("qunit-banner"),
773 | tests = id("qunit-tests"),
774 | runtime = +new Date - config.started,
775 | passed = config.stats.all - config.stats.bad,
776 | html = [
777 | 'Tests completed in ',
778 | runtime,
779 | ' milliseconds.
',
780 | '',
781 | passed,
782 | ' tests of ',
783 | config.stats.all,
784 | ' passed, ',
785 | config.stats.bad,
786 | ' failed.'
787 | ].join('');
788 |
789 | if ( banner ) {
790 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
791 | }
792 |
793 | if ( tests ) {
794 | id( "qunit-testresult" ).innerHTML = html;
795 | }
796 |
797 | if ( config.altertitle && typeof document !== "undefined" && document.title ) {
798 | // show ✖ for good, ✔ for bad suite result in title
799 | // use escape sequences in case file gets loaded with non-utf-8-charset
800 | document.title = [
801 | (config.stats.bad ? "\u2716" : "\u2714"),
802 | document.title.replace(/^[\u2714\u2716] /i, "")
803 | ].join(" ");
804 | }
805 |
806 | QUnit.done( {
807 | failed: config.stats.bad,
808 | passed: passed,
809 | total: config.stats.all,
810 | runtime: runtime
811 | } );
812 | }
813 |
814 | function validTest( name ) {
815 | var filter = config.filter,
816 | run = false;
817 |
818 | if ( !filter ) {
819 | return true;
820 | }
821 |
822 | var not = filter.charAt( 0 ) === "!";
823 | if ( not ) {
824 | filter = filter.slice( 1 );
825 | }
826 |
827 | if ( name.indexOf( filter ) !== -1 ) {
828 | return !not;
829 | }
830 |
831 | if ( not ) {
832 | run = true;
833 | }
834 |
835 | return run;
836 | }
837 |
838 | // so far supports only Firefox, Chrome and Opera (buggy)
839 | // could be extended in the future to use something like https://github.com/csnover/TraceKit
840 | function sourceFromStacktrace() {
841 | try {
842 | throw new Error();
843 | } catch ( e ) {
844 | if (e.stacktrace) {
845 | // Opera
846 | return e.stacktrace.split("\n")[6];
847 | } else if (e.stack) {
848 | // Firefox, Chrome
849 | return e.stack.split("\n")[4];
850 | }
851 | }
852 | }
853 |
854 | function escapeHtml(s) {
855 | if (!s) {
856 | return "";
857 | }
858 | s = s + "";
859 | return s.replace(/[\&"<>\\]/g, function(s) {
860 | switch(s) {
861 | case "&": return "&";
862 | case "\\": return "\\\\";
863 | case '"': return '\"';
864 | case "<": return "<";
865 | case ">": return ">";
866 | default: return s;
867 | }
868 | });
869 | }
870 |
871 | function synchronize( callback ) {
872 | config.queue.push( callback );
873 |
874 | if ( config.autorun && !config.blocking ) {
875 | process();
876 | }
877 | }
878 |
879 | function process() {
880 | var start = (new Date()).getTime();
881 |
882 | while ( config.queue.length && !config.blocking ) {
883 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
884 | config.queue.shift()();
885 | } else {
886 | window.setTimeout( process, 13 );
887 | break;
888 | }
889 | }
890 | if (!config.blocking && !config.queue.length) {
891 | done();
892 | }
893 | }
894 |
895 | function saveGlobal() {
896 | config.pollution = [];
897 |
898 | if ( config.noglobals ) {
899 | for ( var key in window ) {
900 | config.pollution.push( key );
901 | }
902 | }
903 | }
904 |
905 | function checkPollution( name ) {
906 | var old = config.pollution;
907 | saveGlobal();
908 |
909 | var newGlobals = diff( config.pollution, old );
910 | if ( newGlobals.length > 0 ) {
911 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
912 | }
913 |
914 | var deletedGlobals = diff( old, config.pollution );
915 | if ( deletedGlobals.length > 0 ) {
916 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
917 | }
918 | }
919 |
920 | // returns a new Array with the elements that are in a but not in b
921 | function diff( a, b ) {
922 | var result = a.slice();
923 | for ( var i = 0; i < result.length; i++ ) {
924 | for ( var j = 0; j < b.length; j++ ) {
925 | if ( result[i] === b[j] ) {
926 | result.splice(i, 1);
927 | i--;
928 | break;
929 | }
930 | }
931 | }
932 | return result;
933 | }
934 |
935 | function fail(message, exception, callback) {
936 | if ( typeof console !== "undefined" && console.error && console.warn ) {
937 | console.error(message);
938 | console.error(exception);
939 | console.warn(callback.toString());
940 |
941 | } else if ( window.opera && opera.postError ) {
942 | opera.postError(message, exception, callback.toString);
943 | }
944 | }
945 |
946 | function extend(a, b) {
947 | for ( var prop in b ) {
948 | if ( b[prop] === undefined ) {
949 | delete a[prop];
950 | } else {
951 | a[prop] = b[prop];
952 | }
953 | }
954 |
955 | return a;
956 | }
957 |
958 | function addEvent(elem, type, fn) {
959 | if ( elem.addEventListener ) {
960 | elem.addEventListener( type, fn, false );
961 | } else if ( elem.attachEvent ) {
962 | elem.attachEvent( "on" + type, fn );
963 | } else {
964 | fn();
965 | }
966 | }
967 |
968 | function id(name) {
969 | return !!(typeof document !== "undefined" && document && document.getElementById) &&
970 | document.getElementById( name );
971 | }
972 |
973 | // Test for equality any JavaScript type.
974 | // Discussions and reference: http://philrathe.com/articles/equiv
975 | // Test suites: http://philrathe.com/tests/equiv
976 | // Author: Philippe Rathé
977 | QUnit.equiv = function () {
978 |
979 | var innerEquiv; // the real equiv function
980 | var callers = []; // stack to decide between skip/abort functions
981 | var parents = []; // stack to avoiding loops from circular referencing
982 |
983 | // Call the o related callback with the given arguments.
984 | function bindCallbacks(o, callbacks, args) {
985 | var prop = QUnit.objectType(o);
986 | if (prop) {
987 | if (QUnit.objectType(callbacks[prop]) === "function") {
988 | return callbacks[prop].apply(callbacks, args);
989 | } else {
990 | return callbacks[prop]; // or undefined
991 | }
992 | }
993 | }
994 |
995 | var callbacks = function () {
996 |
997 | // for string, boolean, number and null
998 | function useStrictEquality(b, a) {
999 | if (b instanceof a.constructor || a instanceof b.constructor) {
1000 | // to catch short annotaion VS 'new' annotation of a
1001 | // declaration
1002 | // e.g. var i = 1;
1003 | // var j = new Number(1);
1004 | return a == b;
1005 | } else {
1006 | return a === b;
1007 | }
1008 | }
1009 |
1010 | return {
1011 | "string" : useStrictEquality,
1012 | "boolean" : useStrictEquality,
1013 | "number" : useStrictEquality,
1014 | "null" : useStrictEquality,
1015 | "undefined" : useStrictEquality,
1016 |
1017 | "nan" : function(b) {
1018 | return isNaN(b);
1019 | },
1020 |
1021 | "date" : function(b, a) {
1022 | return QUnit.objectType(b) === "date"
1023 | && a.valueOf() === b.valueOf();
1024 | },
1025 |
1026 | "regexp" : function(b, a) {
1027 | return QUnit.objectType(b) === "regexp"
1028 | && a.source === b.source && // the regex itself
1029 | a.global === b.global && // and its modifers
1030 | // (gmi) ...
1031 | a.ignoreCase === b.ignoreCase
1032 | && a.multiline === b.multiline;
1033 | },
1034 |
1035 | // - skip when the property is a method of an instance (OOP)
1036 | // - abort otherwise,
1037 | // initial === would have catch identical references anyway
1038 | "function" : function() {
1039 | var caller = callers[callers.length - 1];
1040 | return caller !== Object && typeof caller !== "undefined";
1041 | },
1042 |
1043 | "array" : function(b, a) {
1044 | var i, j, loop;
1045 | var len;
1046 |
1047 | // b could be an object literal here
1048 | if (!(QUnit.objectType(b) === "array")) {
1049 | return false;
1050 | }
1051 |
1052 | len = a.length;
1053 | if (len !== b.length) { // safe and faster
1054 | return false;
1055 | }
1056 |
1057 | // track reference to avoid circular references
1058 | parents.push(a);
1059 | for (i = 0; i < len; i++) {
1060 | loop = false;
1061 | for (j = 0; j < parents.length; j++) {
1062 | if (parents[j] === a[i]) {
1063 | loop = true;// dont rewalk array
1064 | }
1065 | }
1066 | if (!loop && !innerEquiv(a[i], b[i])) {
1067 | parents.pop();
1068 | return false;
1069 | }
1070 | }
1071 | parents.pop();
1072 | return true;
1073 | },
1074 |
1075 | "object" : function(b, a) {
1076 | var i, j, loop;
1077 | var eq = true; // unless we can proove it
1078 | var aProperties = [], bProperties = []; // collection of
1079 | // strings
1080 |
1081 | // comparing constructors is more strict than using
1082 | // instanceof
1083 | if (a.constructor !== b.constructor) {
1084 | return false;
1085 | }
1086 |
1087 | // stack constructor before traversing properties
1088 | callers.push(a.constructor);
1089 | // track reference to avoid circular references
1090 | parents.push(a);
1091 |
1092 | for (i in a) { // be strict: don't ensures hasOwnProperty
1093 | // and go deep
1094 | loop = false;
1095 | for (j = 0; j < parents.length; j++) {
1096 | if (parents[j] === a[i])
1097 | loop = true; // don't go down the same path
1098 | // twice
1099 | }
1100 | aProperties.push(i); // collect a's properties
1101 |
1102 | if (!loop && !innerEquiv(a[i], b[i])) {
1103 | eq = false;
1104 | break;
1105 | }
1106 | }
1107 |
1108 | callers.pop(); // unstack, we are done
1109 | parents.pop();
1110 |
1111 | for (i in b) {
1112 | bProperties.push(i); // collect b's properties
1113 | }
1114 |
1115 | // Ensures identical properties name
1116 | return eq
1117 | && innerEquiv(aProperties.sort(), bProperties
1118 | .sort());
1119 | }
1120 | };
1121 | }();
1122 |
1123 | innerEquiv = function() { // can take multiple arguments
1124 | var args = Array.prototype.slice.apply(arguments);
1125 | if (args.length < 2) {
1126 | return true; // end transition
1127 | }
1128 |
1129 | return (function(a, b) {
1130 | if (a === b) {
1131 | return true; // catch the most you can
1132 | } else if (a === null || b === null || typeof a === "undefined"
1133 | || typeof b === "undefined"
1134 | || QUnit.objectType(a) !== QUnit.objectType(b)) {
1135 | return false; // don't lose time with error prone cases
1136 | } else {
1137 | return bindCallbacks(a, callbacks, [ b, a ]);
1138 | }
1139 |
1140 | // apply transition with (1..n) arguments
1141 | })(args[0], args[1])
1142 | && arguments.callee.apply(this, args.splice(1,
1143 | args.length - 1));
1144 | };
1145 |
1146 | return innerEquiv;
1147 |
1148 | }();
1149 |
1150 | /**
1151 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1152 | * http://flesler.blogspot.com Licensed under BSD
1153 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1154 | *
1155 | * @projectDescription Advanced and extensible data dumping for Javascript.
1156 | * @version 1.0.0
1157 | * @author Ariel Flesler
1158 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1159 | */
1160 | QUnit.jsDump = (function() {
1161 | function quote( str ) {
1162 | return '"' + str.toString().replace(/"/g, '\\"') + '"';
1163 | };
1164 | function literal( o ) {
1165 | return o + '';
1166 | };
1167 | function join( pre, arr, post ) {
1168 | var s = jsDump.separator(),
1169 | base = jsDump.indent(),
1170 | inner = jsDump.indent(1);
1171 | if ( arr.join )
1172 | arr = arr.join( ',' + s + inner );
1173 | if ( !arr )
1174 | return pre + post;
1175 | return [ pre, inner + arr, base + post ].join(s);
1176 | };
1177 | function array( arr, stack ) {
1178 | var i = arr.length, ret = Array(i);
1179 | this.up();
1180 | while ( i-- )
1181 | ret[i] = this.parse( arr[i] , undefined , stack);
1182 | this.down();
1183 | return join( '[', ret, ']' );
1184 | };
1185 |
1186 | var reName = /^function (\w+)/;
1187 |
1188 | var jsDump = {
1189 | parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
1190 | stack = stack || [ ];
1191 | var parser = this.parsers[ type || this.typeOf(obj) ];
1192 | type = typeof parser;
1193 | var inStack = inArray(obj, stack);
1194 | if (inStack != -1) {
1195 | return 'recursion('+(inStack - stack.length)+')';
1196 | }
1197 | //else
1198 | if (type == 'function') {
1199 | stack.push(obj);
1200 | var res = parser.call( this, obj, stack );
1201 | stack.pop();
1202 | return res;
1203 | }
1204 | // else
1205 | return (type == 'string') ? parser : this.parsers.error;
1206 | },
1207 | typeOf:function( obj ) {
1208 | var type;
1209 | if ( obj === null ) {
1210 | type = "null";
1211 | } else if (typeof obj === "undefined") {
1212 | type = "undefined";
1213 | } else if (QUnit.is("RegExp", obj)) {
1214 | type = "regexp";
1215 | } else if (QUnit.is("Date", obj)) {
1216 | type = "date";
1217 | } else if (QUnit.is("Function", obj)) {
1218 | type = "function";
1219 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
1220 | type = "window";
1221 | } else if (obj.nodeType === 9) {
1222 | type = "document";
1223 | } else if (obj.nodeType) {
1224 | type = "node";
1225 | } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
1226 | type = "array";
1227 | } else {
1228 | type = typeof obj;
1229 | }
1230 | return type;
1231 | },
1232 | separator:function() {
1233 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
1234 | },
1235 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1236 | if ( !this.multiline )
1237 | return '';
1238 | var chr = this.indentChar;
1239 | if ( this.HTML )
1240 | chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
1241 | return Array( this._depth_ + (extra||0) ).join(chr);
1242 | },
1243 | up:function( a ) {
1244 | this._depth_ += a || 1;
1245 | },
1246 | down:function( a ) {
1247 | this._depth_ -= a || 1;
1248 | },
1249 | setParser:function( name, parser ) {
1250 | this.parsers[name] = parser;
1251 | },
1252 | // The next 3 are exposed so you can use them
1253 | quote:quote,
1254 | literal:literal,
1255 | join:join,
1256 | //
1257 | _depth_: 1,
1258 | // This is the list of parsers, to modify them, use jsDump.setParser
1259 | parsers:{
1260 | window: '[Window]',
1261 | document: '[Document]',
1262 | error:'[ERROR]', //when no parser is found, shouldn't happen
1263 | unknown: '[Unknown]',
1264 | 'null':'null',
1265 | 'undefined':'undefined',
1266 | 'function':function( fn ) {
1267 | var ret = 'function',
1268 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1269 | if ( name )
1270 | ret += ' ' + name;
1271 | ret += '(';
1272 |
1273 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1274 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1275 | },
1276 | array: array,
1277 | nodelist: array,
1278 | arguments: array,
1279 | object:function( map, stack ) {
1280 | var ret = [ ];
1281 | QUnit.jsDump.up();
1282 | for ( var key in map ) {
1283 | var val = map[key];
1284 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
1285 | }
1286 | QUnit.jsDump.down();
1287 | return join( '{', ret, '}' );
1288 | },
1289 | node:function( node ) {
1290 | var open = QUnit.jsDump.HTML ? '<' : '<',
1291 | close = QUnit.jsDump.HTML ? '>' : '>';
1292 |
1293 | var tag = node.nodeName.toLowerCase(),
1294 | ret = open + tag;
1295 |
1296 | for ( var a in QUnit.jsDump.DOMAttrs ) {
1297 | var val = node[QUnit.jsDump.DOMAttrs[a]];
1298 | if ( val )
1299 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1300 | }
1301 | return ret + close + open + '/' + tag + close;
1302 | },
1303 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1304 | var l = fn.length;
1305 | if ( !l ) return '';
1306 |
1307 | var args = Array(l);
1308 | while ( l-- )
1309 | args[l] = String.fromCharCode(97+l);//97 is 'a'
1310 | return ' ' + args.join(', ') + ' ';
1311 | },
1312 | key:quote, //object calls it internally, the key part of an item in a map
1313 | functionCode:'[code]', //function calls it internally, it's the content of the function
1314 | attribute:quote, //node calls it internally, it's an html attribute value
1315 | string:quote,
1316 | date:quote,
1317 | regexp:literal, //regex
1318 | number:literal,
1319 | 'boolean':literal
1320 | },
1321 | DOMAttrs:{//attributes to dump from nodes, name=>realName
1322 | id:'id',
1323 | name:'name',
1324 | 'class':'className'
1325 | },
1326 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1327 | indentChar:' ',//indentation unit
1328 | multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1329 | };
1330 |
1331 | return jsDump;
1332 | })();
1333 |
1334 | // from Sizzle.js
1335 | function getText( elems ) {
1336 | var ret = "", elem;
1337 |
1338 | for ( var i = 0; elems[i]; i++ ) {
1339 | elem = elems[i];
1340 |
1341 | // Get the text from text nodes and CDATA nodes
1342 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1343 | ret += elem.nodeValue;
1344 |
1345 | // Traverse everything else, except comment nodes
1346 | } else if ( elem.nodeType !== 8 ) {
1347 | ret += getText( elem.childNodes );
1348 | }
1349 | }
1350 |
1351 | return ret;
1352 | };
1353 |
1354 | //from jquery.js
1355 | function inArray( elem, array ) {
1356 | if ( array.indexOf ) {
1357 | return array.indexOf( elem );
1358 | }
1359 |
1360 | for ( var i = 0, length = array.length; i < length; i++ ) {
1361 | if ( array[ i ] === elem ) {
1362 | return i;
1363 | }
1364 | }
1365 |
1366 | return -1;
1367 | }
1368 |
1369 | /*
1370 | * Javascript Diff Algorithm
1371 | * By John Resig (http://ejohn.org/)
1372 | * Modified by Chu Alan "sprite"
1373 | *
1374 | * Released under the MIT license.
1375 | *
1376 | * More Info:
1377 | * http://ejohn.org/projects/javascript-diff-algorithm/
1378 | *
1379 | * Usage: QUnit.diff(expected, actual)
1380 | *
1381 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over"
1382 | */
1383 | QUnit.diff = (function() {
1384 | function diff(o, n) {
1385 | var ns = new Object();
1386 | var os = new Object();
1387 |
1388 | for (var i = 0; i < n.length; i++) {
1389 | if (ns[n[i]] == null)
1390 | ns[n[i]] = {
1391 | rows: new Array(),
1392 | o: null
1393 | };
1394 | ns[n[i]].rows.push(i);
1395 | }
1396 |
1397 | for (var i = 0; i < o.length; i++) {
1398 | if (os[o[i]] == null)
1399 | os[o[i]] = {
1400 | rows: new Array(),
1401 | n: null
1402 | };
1403 | os[o[i]].rows.push(i);
1404 | }
1405 |
1406 | for (var i in ns) {
1407 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1408 | n[ns[i].rows[0]] = {
1409 | text: n[ns[i].rows[0]],
1410 | row: os[i].rows[0]
1411 | };
1412 | o[os[i].rows[0]] = {
1413 | text: o[os[i].rows[0]],
1414 | row: ns[i].rows[0]
1415 | };
1416 | }
1417 | }
1418 |
1419 | for (var i = 0; i < n.length - 1; i++) {
1420 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1421 | n[i + 1] == o[n[i].row + 1]) {
1422 | n[i + 1] = {
1423 | text: n[i + 1],
1424 | row: n[i].row + 1
1425 | };
1426 | o[n[i].row + 1] = {
1427 | text: o[n[i].row + 1],
1428 | row: i + 1
1429 | };
1430 | }
1431 | }
1432 |
1433 | for (var i = n.length - 1; i > 0; i--) {
1434 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1435 | n[i - 1] == o[n[i].row - 1]) {
1436 | n[i - 1] = {
1437 | text: n[i - 1],
1438 | row: n[i].row - 1
1439 | };
1440 | o[n[i].row - 1] = {
1441 | text: o[n[i].row - 1],
1442 | row: i - 1
1443 | };
1444 | }
1445 | }
1446 |
1447 | return {
1448 | o: o,
1449 | n: n
1450 | };
1451 | }
1452 |
1453 | return function(o, n) {
1454 | o = o.replace(/\s+$/, '');
1455 | n = n.replace(/\s+$/, '');
1456 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1457 |
1458 | var str = "";
1459 |
1460 | var oSpace = o.match(/\s+/g);
1461 | if (oSpace == null) {
1462 | oSpace = [" "];
1463 | }
1464 | else {
1465 | oSpace.push(" ");
1466 | }
1467 | var nSpace = n.match(/\s+/g);
1468 | if (nSpace == null) {
1469 | nSpace = [" "];
1470 | }
1471 | else {
1472 | nSpace.push(" ");
1473 | }
1474 |
1475 | if (out.n.length == 0) {
1476 | for (var i = 0; i < out.o.length; i++) {
1477 | str += '' + out.o[i] + oSpace[i] + "";
1478 | }
1479 | }
1480 | else {
1481 | if (out.n[0].text == null) {
1482 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1483 | str += '' + out.o[n] + oSpace[n] + "";
1484 | }
1485 | }
1486 |
1487 | for (var i = 0; i < out.n.length; i++) {
1488 | if (out.n[i].text == null) {
1489 | str += '' + out.n[i] + nSpace[i] + "";
1490 | }
1491 | else {
1492 | var pre = "";
1493 |
1494 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1495 | pre += '' + out.o[n] + oSpace[n] + "";
1496 | }
1497 | str += " " + out.n[i].text + nSpace[i] + pre;
1498 | }
1499 | }
1500 | }
1501 |
1502 | return str;
1503 | };
1504 | })();
1505 |
1506 | })(this);
1507 |
--------------------------------------------------------------------------------