├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── app.rb ├── bubble-to-pegjs.rb ├── bubble-to-pegjs_ex.rb ├── config.ru ├── public ├── index.html ├── js │ ├── peg-0.6.1.js │ ├── peg-0.6.1.min.js │ ├── qunit │ │ ├── qunit.css │ │ └── qunit.js │ └── sql.pegjs-head.js └── test │ ├── test-head.html │ ├── test-peg.html │ └── test-sample.html ├── sql-bubble.rb ├── sql-bubble.txt ├── sql.pegjs └── views └── index.erb /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | ./sql-bubble.rb > tmp/rules.rb 3 | ./bubble-to-pegjs.rb tmp/rules.rb > tmp/sql.pegjs 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SQL parser in JavaScript 2 | ======================== 3 | 4 | This project provides a PEG.js grammar for SQL syntax. Why? I wanted 5 | to properly parse SQL in JavaScript, knocking down yet another thing 6 | in that long list of things that will be reimplemented in JavaScript 7 | one day. 8 | 9 | See the ./sql.pegjs file, or... 10 | 11 | * http://github.com/steveyen/sqld3/blob/master/sql.pegjs 12 | 13 | Unlike previous hand-coded attempts at SQL parsing in JavaScript, such 14 | as my previous http://code.google.com/p/trimpath/wiki/TrimQuery, the 15 | parsing here is grammar (PEG) based. 16 | 17 | SQL syntax 18 | ---------- 19 | 20 | The SQL syntax follows sqlite 3.7 documented syntax, specifically from... 21 | 22 | * sqlite's art/syntax/bubble-generator-data.tcl 23 | * http://www.sqlite.org/docsrc/artifact?name=a7001f134e2f341c3b46fad9623556260530904b 24 | 25 | See also 26 | -------- 27 | 28 | * PEG.js: http://pegjs.majda.cz/ 29 | * sqlite: http://www.sqlite.org 30 | 31 | License 32 | ------- 33 | 34 | Apache 2.0 -- this was made for you and me. 35 | 36 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'erb' 4 | 5 | class App < Sinatra::Base 6 | set :static, true 7 | set :public, 'public' 8 | 9 | ["/", "/index", "/index.html"].each do |path| 10 | get path do 11 | erb :index 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /bubble-to-pegjs.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $grammar = ARGV[0] 4 | $grammarx = ARGV[1] || 'bubble-to-pegjs_ex.rb' 5 | $keywords = {} 6 | $override = {} 7 | 8 | # ---------------------------------------- 9 | 10 | def rule(name, *rest) 11 | print "#{name} =\n" 12 | if $override[name] 13 | print "#{$override[name]}\n" 14 | else 15 | rest.flatten.each do |x| 16 | $keywords[x] = true if x.class == String and x.match(/^[A-Z]+$/) 17 | print "#{x} " 18 | end 19 | print "\n\n" 20 | end 21 | end 22 | 23 | def either(*rest) 24 | r = [] 25 | rest.each {|x| r << x; r << '/' } 26 | ["("] + r[0..-2] + [")"] 27 | end 28 | 29 | def stack(*rest) 30 | return rest if rest.length <= 1 31 | return ["("] + rest + [")"] 32 | end 33 | 34 | def line(*rest) 35 | return stack(*rest) 36 | end 37 | 38 | def loop(*rest) 39 | return ["("] + rest + [")+"] 40 | end 41 | 42 | def toploop(*rest) 43 | return loop(*rest) 44 | end 45 | 46 | def tailbranch(*rest) # TODO. 47 | return rest 48 | end 49 | 50 | def opt(*rest) 51 | return ["("] + rest + [")?"] 52 | end 53 | 54 | def optx(*rest) 55 | return opt(*rest) 56 | end 57 | 58 | # ---------------------------------------- 59 | 60 | $header = <<-EOS 61 | // generated pegjs, from #{$grammar} and #{$grammarx} 62 | 63 | start = sql_stmt_list 64 | EOS 65 | 66 | # ---------------------------------------- 67 | 68 | $extra = <<-EOS 69 | dot = '.' 70 | comma = ',' 71 | semicolon = ';' 72 | minusminus = '--' 73 | minus = '-' 74 | plus = '+' 75 | lparen = '(' 76 | rparen = ')' 77 | star = '*' 78 | newline = '\\n' 79 | anything_except_newline = [^\\n]* 80 | comment_beg = '/*' 81 | comment_end = '*/' 82 | anything_except_comment_end = .* & '*/' 83 | string_literal = '\"' (escape_char / [^"])* '\"' 84 | escape_char = '\\\\' . 85 | nil = '' 86 | 87 | whitespace = [\\s]* 88 | whitespace1 = [\\s]+ 89 | 90 | unary_operator = '-' / '+' / '~' / 'NOT' 91 | binary_operator = 92 | '||' 93 | / '*' / '/' / '%' 94 | / '+' / '-' 95 | / '<<' / '>>' / '&' / '|' 96 | / '<' / '<=' / '>' / '>=' 97 | / '=' / '==' / '!=' / '<>' 98 | / 'IS' / 'IS NOT' / 'IN' / 'LIKE' / 'GLOB' / 'MATCH' / 'REGEXP' 99 | / 'AND' 100 | / 'OR' 101 | 102 | digit = [0-9] 103 | decimal_point = dot 104 | equal = '=' 105 | 106 | name = [A-Za-z0-9_]+ 107 | database_name = name 108 | table_name = name 109 | table_alias = name 110 | table_or_index_name = name 111 | new_table_name = name 112 | index_name = name 113 | column_name = name 114 | column_alias = name 115 | foreign_table = name 116 | savepoint_name = name 117 | collation_name = name 118 | trigger_name = name 119 | view_name = name 120 | module_name = name 121 | module_argument = name 122 | bind_parameter = name 123 | function_name = name 124 | pragma_name = name 125 | 126 | error_message = string_literal 127 | 128 | CURRENT_TIME = 'now' 129 | CURRENT_DATE = 'now' 130 | CURRENT_TIMESTAMP = 'now' 131 | 132 | blob_literal = string_literal 133 | 134 | end_of_input = '' 135 | EOS 136 | 137 | # ---------------------------------------- 138 | 139 | load $grammarx 140 | 141 | print "#{$header}\n" 142 | 143 | load $grammar 144 | 145 | print "#{$extra}\n" 146 | 147 | $keywords.keys.sort.each do |keyword| 148 | print "#{keyword} = whitespace1 \"#{keyword}\"\n" 149 | end 150 | 151 | -------------------------------------------------------------------------------- /bubble-to-pegjs_ex.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $keywords['EXPLAIN'] = true 4 | $keywords['QUERY'] = true 5 | $keywords['PLAN'] = true 6 | 7 | $override[:sql_stmt] = <<-EOS 8 | ( ( EXPLAIN ( QUERY PLAN )? )? ( 9 | // alter_table_stmt 10 | // / analyze_stmt 11 | // / attach_stmt / begin_stmt / commit_stmt 12 | // / create_index_stmt / create_table_stmt / create_trigger_stmt 13 | // / create_view_stmt / create_virtual_table_stmt 14 | // / delete_stmt / delete_stmt_limited 15 | // / detach_stmt / drop_index_stmt / drop_table_stmt / drop_trigger_stmt / drop_view_stmt 16 | // / insert_stmt 17 | // / pragma_stmt / reindex_stmt / release_stmt / rollback_stmt / savepoint_stmt 18 | select_stmt 19 | // / update_stmt / update_stmt_limited 20 | // / vacuum_stmt 21 | ) ) 22 | EOS 23 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'app' 2 | 3 | use Rack::ShowExceptions 4 | 5 | run App.new 6 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | 61 | 62 |
63 |

Test Suite

64 |

65 |
66 |

67 |
    68 |
    test markup
    69 |
    70 | 71 | 72 | -------------------------------------------------------------------------------- /public/js/peg-0.6.1.min.js: -------------------------------------------------------------------------------- 1 | /* PEG.js 0.6.1 (http://pegjs.majda.cz/) */(function(){function buildNodeVisitor(a){return function(b){return a[b.type].apply(null,arguments)}}function quoteForRegexpClass(a){return a.replace(/\\/g,"\\\\").replace(/\0/g,"\\0").replace(/\//g,"\\/").replace(/]/g,"\\]").replace(/-/g,"\\-").replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/[\x80-\uFFFF]/g,escape)}function quote(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/[\x80-\uFFFF]/g,escape)+'"'}function escape(a){var b=a.charCodeAt(0);if(b<=255)var c="x",d=2;else var c="u",d=4;return"\\"+c+padLeft(b.toString(16).toUpperCase(),"0",d)}function padLeft(a,b,c){var d=a,e=c-a.length;for(var f=0;fd){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=j}}else{var q=null;d=j}while(q!==null){i.push(q);var j=d,k=d,m=e;e=!1;if(a.substr(d,2)==="*/"){var n="*/";d+=2}else{var n=null;e&&l('"*/"')}e=m;if(n===null)var o="";else{var o=null;d=k}if(o!==null){if(a.length>d){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=j}}else{var q=null;d=j}}if(i!==null){if(a.substr(d,2)==="*/"){var r="*/";d+=2}else{var r=null;e&&l('"*/"')}if(r!==null)var s=[g,i,r];else{var s=null;d=f}}else{var s=null;d=f}}else{var s=null;d=f}h[b]={nextPos:d,result:s};return s}function bi(){var b="singleLineComment@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,2)==="//"){var g="//";d+=2}else{var g=null;e&&l('"//"')}if(g!==null){var i=[],j=d,k=d,m=e;e=!1;var n=bl();e=m;if(n===null)var o="";else{var o=null;d=k}if(o!==null){if(a.length>d){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=j}}else{var q=null;d=j}while(q!==null){i.push(q);var j=d,k=d,m=e;e=!1;var n=bl();e=m;if(n===null)var o="";else{var o=null;d=k}if(o!==null){if(a.length>d){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=j}}else{var q=null;d=j}}if(i!==null)var r=[g,i];else{var r=null;d=f}}else{var r=null;d=f}h[b]={nextPos:d,result:r};return r}function bh(){var a="comment@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=e;e=!1;var f=bi();if(f!==null)var g=f;else{var i=bj();if(i!==null)var g=i;else var g=null}e=c,e&&g===null&&l("comment"),h[a]={nextPos:d,result:g};return g}function bg(){var a="__@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=[],e=bm();if(e!==null)var f=e;else{var g=bk();if(g!==null)var f=g;else{var i=bh();if(i!==null)var f=i;else var f=null}}while(f!==null){c.push(f);var e=bm();if(e!==null)var f=e;else{var g=bk();if(g!==null)var f=g;else{var i=bh();if(i!==null)var f=i;else var f=null}}}h[a]={nextPos:d,result:c};return c}function bf(){var b="upperCaseLetter@"+d,c=h[b];if(c){d=c.nextPos;return c.result}if(a.substr(d).match(/^[A-Z]/)!==null){var f=a.charAt(d);d++}else{var f=null;e&&l("[A-Z]")}h[b]={nextPos:d,result:f};return f}function be(){var b="lowerCaseLetter@"+d,c=h[b];if(c){d=c.nextPos;return c.result}if(a.substr(d).match(/^[a-z]/)!==null){var f=a.charAt(d);d++}else{var f=null;e&&l("[a-z]")}h[b]={nextPos:d,result:f};return f}function bd(){var a="letter@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=be();if(c!==null)var e=c;else{var f=bf();if(f!==null)var e=f;else var e=null}h[a]={nextPos:d,result:e};return e}function bc(){var b="hexDigit@"+d,c=h[b];if(c){d=c.nextPos;return c.result}if(a.substr(d).match(/^[0-9a-fA-F]/)!==null){var f=a.charAt(d);d++}else{var f=null;e&&l("[0-9a-fA-F]")}h[b]={nextPos:d,result:f};return f}function bb(){var b="digit@"+d,c=h[b];if(c){d=c.nextPos;return c.result}if(a.substr(d).match(/^[0-9]/)!==null){var f=a.charAt(d);d++}else{var f=null;e&&l("[0-9]")}h[b]={nextPos:d,result:f};return f}function ba(){var b="eolEscapeSequence@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="\\"){var g="\\";d+=1}else{var g=null;e&&l('"\\\\"')}if(g!==null){var i=bk();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(a){return a}(j[1]):null;h[b]={nextPos:d,result:k};return k}function _(){var b="unicodeEscapeSequence@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,2)==="\\u"){var g="\\u";d+=2}else{var g=null;e&&l('"\\\\u"')}if(g!==null){var i=bc();if(i!==null){var j=bc();if(j!==null){var k=bc();if(k!==null){var m=bc();if(m!==null)var n=[g,i,j,k,m];else{var n=null;d=f}}else{var n=null;d=f}}else{var n=null;d=f}}else{var n=null;d=f}}else{var n=null;d=f}var o=n!==null?function(a,b,c,d){return String.fromCharCode(parseInt("0x"+a+b+c+d))}(n[1],n[2],n[3],n[4]):null;h[b]={nextPos:d,result:o};return o}function $(){var b="hexEscapeSequence@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,2)==="\\x"){var g="\\x";d+=2}else{var g=null;e&&l('"\\\\x"')}if(g!==null){var i=bc();if(i!==null){var j=bc();if(j!==null)var k=[g,i,j];else{var k=null;d=f}}else{var k=null;d=f}}else{var k=null;d=f}var m=k!==null?function(a,b){return String.fromCharCode(parseInt("0x"+a+b))}(k[1],k[2]):null;h[b]={nextPos:d,result:m};return m}function Z(){var b="zeroEscapeSequence@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,2)==="\\0"){var g="\\0";d+=2}else{var g=null;e&&l('"\\\\0"')}if(g!==null){var i=d,j=e;e=!1;var k=bb();e=j;if(k===null)var m="";else{var m=null;d=i}if(m!==null)var n=[g,m];else{var n=null;d=f}}else{var n=null;d=f}var o=n!==null?function(){return""}():null;h[b]={nextPos:d,result:o};return o}function Y(){var b="simpleEscapeSequence@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="\\"){var g="\\";d+=1}else{var g=null;e&&l('"\\\\"')}if(g!==null){var i=d,j=e;e=!1;var k=bb();if(k!==null)var m=k;else{if(a.substr(d,1)==="x"){var n="x";d+=1}else{var n=null;e&&l('"x"')}if(n!==null)var m=n;else{if(a.substr(d,1)==="u"){var o="u";d+=1}else{var o=null;e&&l('"u"')}if(o!==null)var m=o;else{var p=bl();if(p!==null)var m=p;else var m=null}}}e=j;if(m===null)var q="";else{var q=null;d=i}if(q!==null){if(a.length>d){var r=a.charAt(d);d++}else{var r=null;e&&l("any character")}if(r!==null)var s=[g,q,r];else{var s=null;d=f}}else{var s=null;d=f}}else{var s=null;d=f}var t=s!==null?function(a){return a.replace("b","\b").replace("f","\f").replace("n","\n").replace("r","\r").replace("t","\t").replace("v"," ")}(s[2]):null;h[b]={nextPos:d,result:t};return t}function X(){var b="simpleBracketDelimitedCharacter@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d,g=d,i=e;e=!1;if(a.substr(d,1)==="]"){var j="]";d+=1}else{var j=null;e&&l('"]"')}if(j!==null)var k=j;else{if(a.substr(d,1)==="\\"){var m="\\";d+=1}else{var m=null;e&&l('"\\\\"')}if(m!==null)var k=m;else{var n=bl();if(n!==null)var k=n;else var k=null}}e=i;if(k===null)var o="";else{var o=null;d=g}if(o!==null){if(a.length>d){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=f}}else{var q=null;d=f}var r=q!==null?function(a){return a}(q[1]):null;h[b]={nextPos:d,result:r};return r}function W(){var a="bracketDelimitedCharacter@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=X();if(c!==null)var e=c;else{var f=Y();if(f!==null)var e=f;else{var g=Z();if(g!==null)var e=g;else{var i=$();if(i!==null)var e=i;else{var j=_();if(j!==null)var e=j;else{var k=ba();if(k!==null)var e=k;else var e=null}}}}}h[a]={nextPos:d,result:e};return e}function V(){var a="classCharacter@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=W(),e=c!==null?function(a){return{data:a,rawText:quoteForRegexpClass(a)}}(c):null;h[a]={nextPos:d,result:e};return e}function U(){var b="classCharacterRange@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d,g=V();if(g!==null){if(a.substr(d,1)==="-"){var i="-";d+=1}else{var i=null;e&&l('"-"')}if(i!==null){var j=V();if(j!==null)var k=[g,i,j];else{var k=null;d=f}}else{var k=null;d=f}}else{var k=null;d=f}var m=k!==null?function(a,b){if(a.data.charCodeAt(0)>b.data.charCodeAt(0))throw new this.SyntaxError("Invalid character range: "+a.rawText+"-"+b.rawText+".");return{data:[a.data,b.data],rawText:a.rawText+"-"+b.rawText}}(k[0],k[2]):null;h[b]={nextPos:d,result:m};return m}function T(){var b="class@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=e;e=!1;var g=d;if(a.substr(d,1)==="["){var i="[";d+=1}else{var i=null;e&&l('"["')}if(i!==null){if(a.substr(d,1)==="^"){var j="^";d+=1}else{var j=null;e&&l('"^"')}var k=j!==null?j:"";if(k!==null){var m=[],n=U();if(n!==null)var o=n;else{var p=V();if(p!==null)var o=p;else var o=null}while(o!==null){m.push(o);var n=U();if(n!==null)var o=n;else{var p=V();if(p!==null)var o=p;else var o=null}}if(m!==null){if(a.substr(d,1)==="]"){var q="]";d+=1}else{var q=null;e&&l('"]"')}if(q!==null){var r=bg();if(r!==null)var s=[i,k,m,q,r];else{var s=null;d=g}}else{var s=null;d=g}}else{var s=null;d=g}}else{var s=null;d=g}}else{var s=null;d=g}var t=s!==null?function(a,b){var c=map(b,function(a){return a.data}),d="["+a+map(b,function(a){return a.rawText}).join("")+"]";return{type:"class",inverted:a==="^",parts:c,rawText:d}}(s[1],s[2]):null;e=f,e&&t===null&&l("character class"),h[b]={nextPos:d,result:t};return t}function S(){var b="simpleSingleQuotedCharacter@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d,g=d,i=e;e=!1;if(a.substr(d,1)==="'"){var j="'";d+=1}else{var j=null;e&&l('"\'"')}if(j!==null)var k=j;else{if(a.substr(d,1)==="\\"){var m="\\";d+=1}else{var m=null;e&&l('"\\\\"')}if(m!==null)var k=m;else{var n=bl();if(n!==null)var k=n;else var k=null}}e=i;if(k===null)var o="";else{var o=null;d=g}if(o!==null){if(a.length>d){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=f}}else{var q=null;d=f}var r=q!==null?function(a){return a}(q[1]):null;h[b]={nextPos:d,result:r};return r}function R(){var a="singleQuotedCharacter@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=S();if(c!==null)var e=c;else{var f=Y();if(f!==null)var e=f;else{var g=Z();if(g!==null)var e=g;else{var i=$();if(i!==null)var e=i;else{var j=_();if(j!==null)var e=j;else{var k=ba();if(k!==null)var e=k;else var e=null}}}}}h[a]={nextPos:d,result:e};return e}function Q(){var b="singleQuotedLiteral@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="'"){var g="'";d+=1}else{var g=null;e&&l('"\'"')}if(g!==null){var i=[],j=R();while(j!==null){i.push(j);var j=R()}if(i!==null){if(a.substr(d,1)==="'"){var k="'";d+=1}else{var k=null;e&&l('"\'"')}if(k!==null)var m=[g,i,k];else{var m=null;d=f}}else{var m=null;d=f}}else{var m=null;d=f}var n=m!==null?function(a){return a.join("")}(m[1]):null;h[b]={nextPos:d,result:n};return n}function P(){var b="simpleDoubleQuotedCharacter@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d,g=d,i=e;e=!1;if(a.substr(d,1)==='"'){var j='"';d+=1}else{var j=null;e&&l('"\\""')}if(j!==null)var k=j;else{if(a.substr(d,1)==="\\"){var m="\\";d+=1}else{var m=null;e&&l('"\\\\"')}if(m!==null)var k=m;else{var n=bl();if(n!==null)var k=n;else var k=null}}e=i;if(k===null)var o="";else{var o=null;d=g}if(o!==null){if(a.length>d){var p=a.charAt(d);d++}else{var p=null;e&&l("any character")}if(p!==null)var q=[o,p];else{var q=null;d=f}}else{var q=null;d=f}var r=q!==null?function(a){return a}(q[1]):null;h[b]={nextPos:d,result:r};return r}function O(){var a="doubleQuotedCharacter@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=P();if(c!==null)var e=c;else{var f=Y();if(f!==null)var e=f;else{var g=Z();if(g!==null)var e=g;else{var i=$();if(i!==null)var e=i;else{var j=_();if(j!==null)var e=j;else{var k=ba();if(k!==null)var e=k;else var e=null}}}}}h[a]={nextPos:d,result:e};return e}function N(){var b="doubleQuotedLiteral@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==='"'){var g='"';d+=1}else{var g=null;e&&l('"\\""')}if(g!==null){var i=[],j=O();while(j!==null){i.push(j);var j=O()}if(i!==null){if(a.substr(d,1)==='"'){var k='"';d+=1}else{var k=null;e&&l('"\\""')}if(k!==null)var m=[g,i,k];else{var m=null;d=f}}else{var m=null;d=f}}else{var m=null;d=f}var n=m!==null?function(a){return a.join("")}(m[1]):null;h[b]={nextPos:d,result:n};return n}function M(){var a="literal@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=e;e=!1;var f=d,g=N();if(g!==null)var i=g;else{var j=Q();if(j!==null)var i=j;else var i=null}if(i!==null){var k=bg();if(k!==null)var m=[i,k];else{var m=null;d=f}}else{var m=null;d=f}var n=m!==null?function(a){return a}(m[0]):null;e=c,e&&n===null&&l("literal"),h[a]={nextPos:d,result:n};return n}function L(){var b="identifier@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=e;e=!1;var g=d,i=bd();if(i!==null)var j=i;else{if(a.substr(d,1)==="_"){var k="_";d+=1}else{var k=null;e&&l('"_"')}if(k!==null)var j=k;else{if(a.substr(d,1)==="$"){var m="$";d+=1}else{var m=null;e&&l('"$"')}if(m!==null)var j=m;else var j=null}}if(j!==null){var n=[],o=bd();if(o!==null)var p=o;else{var q=bb();if(q!==null)var p=q;else{if(a.substr(d,1)==="_"){var r="_";d+=1}else{var r=null;e&&l('"_"')}if(r!==null)var p=r;else{if(a.substr(d,1)==="$"){var s="$";d+=1}else{var s=null;e&&l('"$"')}if(s!==null)var p=s;else var p=null}}}while(p!==null){n.push(p);var o=bd();if(o!==null)var p=o;else{var q=bb();if(q!==null)var p=q;else{if(a.substr(d,1)==="_"){var r="_";d+=1}else{var r=null;e&&l('"_"')}if(r!==null)var p=r;else{if(a.substr(d,1)==="$"){var s="$";d+=1}else{var s=null;e&&l('"$"')}if(s!==null)var p=s;else var p=null}}}}if(n!==null){var t=bg();if(t!==null)var u=[j,n,t];else{var u=null;d=g}}else{var u=null;d=g}}else{var u=null;d=g}var v=u!==null?function(a,b){return a+b.join("")}(u[0],u[1]):null;e=f,e&&v===null&&l("identifier"),h[b]={nextPos:d,result:v};return v}function K(){var b="dot@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="."){var g=".";d+=1}else{var g=null;e&&l('"."')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"."}():null;h[b]={nextPos:d,result:k};return k}function J(){var b="rparen@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)===")"){var g=")";d+=1}else{var g=null;e&&l('")"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return")"}():null;h[b]={nextPos:d,result:k};return k}function I(){var b="lparen@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="("){var g="(";d+=1}else{var g=null;e&&l('"("')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"("}():null;h[b]={nextPos:d,result:k};return k}function H(){var b="plus@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="+"){var g="+";d+=1}else{var g=null;e&&l('"+"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"+"}():null;h[b]={nextPos:d,result:k};return k}function G(){var b="star@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="*"){var g="*";d+=1}else{var g=null;e&&l('"*"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"*"}():null;h[b]={nextPos:d,result:k};return k}function F(){var b="question@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="?"){var g="?";d+=1}else{var g=null;e&&l('"?"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"?"}():null;h[b]={nextPos:d,result:k};return k}function E(){var b="not@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="!"){var g="!";d+=1}else{var g=null;e&&l('"!"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"!"}():null;h[b]={nextPos:d,result:k};return k}function D(){var b="and@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="&"){var g="&";d+=1}else{var g=null;e&&l('"&"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"&"}():null;h[b]={nextPos:d,result:k};return k}function C(){var b="slash@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="/"){var g="/";d+=1}else{var g=null;e&&l('"/"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"/"}():null;h[b]={nextPos:d,result:k};return k}function B(){var b="semicolon@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)===";"){var g=";";d+=1}else{var g=null;e&&l('";"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return";"}():null;h[b]={nextPos:d,result:k};return k}function A(){var b="colon@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)===":"){var g=":";d+=1}else{var g=null;e&&l('":"')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return":"}():null;h[b]={nextPos:d,result:k};return k}function z(){var b="equals@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="="){var g="=";d+=1}else{var g=null;e&&l('"="')}if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(){return"="}():null;h[b]={nextPos:d,result:k};return k}function y(){var b="nonBraceCharacter@"+d,c=h[b];if(c){d=c.nextPos;return c.result}if(a.substr(d).match(/^[^{}]/)!==null){var f=a.charAt(d);d++}else{var f=null;e&&l("[^{}]")}h[b]={nextPos:d,result:f};return f}function x(){var a="nonBraceCharacters@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=y();if(c!==null){var e=[];while(c!==null){e.push(c);var c=y()}}else var e=null;var f=e!==null?function(a){return a.join("")}(e):null;h[a]={nextPos:d,result:f};return f}function w(){var b="braced@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d;if(a.substr(d,1)==="{"){var g="{";d+=1}else{var g=null;e&&l('"{"')}if(g!==null){var i=[],j=w();if(j!==null)var k=j;else{var m=y();if(m!==null)var k=m;else var k=null}while(k!==null){i.push(k);var j=w();if(j!==null)var k=j;else{var m=y();if(m!==null)var k=m;else var k=null}}if(i!==null){if(a.substr(d,1)==="}"){var n="}";d+=1}else{var n=null;e&&l('"}"')}if(n!==null)var o=[g,i,n];else{var o=null;d=f}}else{var o=null;d=f}}else{var o=null;d=f}var p=o!==null?function(a){return"{"+a.join("")+"}"}(o[1]):null;h[b]={nextPos:d,result:p};return p}function v(){var a="action@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=e;e=!1;var f=d,g=w();if(g!==null){var i=bg();if(i!==null)var j=[g,i];else{var j=null;d=f}}else{var j=null;d=f}var k=j!==null?function(a){return a.substr(1,a.length-2)}(j[0]):null;e=c,e&&k===null&&l("action"),h[a]={nextPos:d,result:k};return k}function u(){var b="primary@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d,g=L();if(g!==null){var i=d,j=e;e=!1;var k=d,m=M();if(m!==null)var n=m;else{if(a.substr(d,0)===""){var o="";d+=0}else{var o=null;e&&l('""')}if(o!==null)var n=o;else var n=null}if(n!==null){var q=z();if(q!==null)var r=[n,q];else{var r=null;d=k}}else{var r=null;d=k}e=j;if(r===null)var s="";else{var s=null;d=i}if(s!==null)var t=[g,s];else{var t=null;d=f}}else{var t=null;d=f}var u=t!==null?function(a){return{type:"rule_ref",name:a}}(t[0]):null;if(u!==null)var v=u;else{var w=M(),x=w!==null?function(a){return{type:"literal",value:a}}(w):null;if(x!==null)var v=x;else{var y=K(),A=y!==null?function(){return{type:"any"}}():null;if(A!==null)var v=A;else{var B=T();if(B!==null)var v=B;else{var C=d,D=I();if(D!==null){var E=p();if(E!==null){var F=J();if(F!==null)var G=[D,E,F];else{var G=null;d=C}}else{var G=null;d=C}}else{var G=null;d=C}var H=G!==null?function(a){return a}(G[1]):null;if(H!==null)var v=H;else var v=null}}}}h[b]={nextPos:d,result:v};return v}function t(){var a="suffixed@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=u();if(e!==null){var f=F();if(f!==null)var g=[e,f];else{var g=null;d=c}}else{var g=null;d=c}var i=g!==null?function(a){return{type:"optional",expression:a}}(g[0]):null;if(i!==null)var j=i;else{var k=d,l=u();if(l!==null){var m=G();if(m!==null)var n=[l,m];else{var n=null;d=k}}else{var n=null;d=k}var o=n!==null?function(a){return{type:"zero_or_more",expression:a}}(n[0]):null;if(o!==null)var j=o;else{var p=d,q=u();if(q!==null){var r=H();if(r!==null)var s=[q,r];else{var s=null;d=p}}else{var s=null;d=p}var t=s!==null?function(a){return{type:"one_or_more",expression:a}}(s[0]):null;if(t!==null)var j=t;else{var v=u();if(v!==null)var j=v;else var j=null}}}h[a]={nextPos:d,result:j};return j}function s(){var a="prefixed@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=D();if(e!==null){var f=v();if(f!==null)var g=[e,f];else{var g=null;d=c}}else{var g=null;d=c}var i=g!==null?function(a){return{type:"semantic_and",code:a}}(g[1]):null;if(i!==null)var j=i;else{var k=d,l=D();if(l!==null){var m=t();if(m!==null)var n=[l,m];else{var n=null;d=k}}else{var n=null;d=k}var o=n!==null?function(a){return{type:"simple_and",expression:a}}(n[1]):null;if(o!==null)var j=o;else{var p=d,q=E();if(q!==null){var r=v();if(r!==null)var s=[q,r];else{var s=null;d=p}}else{var s=null;d=p}var u=s!==null?function(a){return{type:"semantic_not",code:a}}(s[1]):null;if(u!==null)var j=u;else{var w=d,x=E();if(x!==null){var y=t();if(y!==null)var z=[x,y];else{var z=null;d=w}}else{var z=null;d=w}var A=z!==null?function(a){return{type:"simple_not",expression:a}}(z[1]):null;if(A!==null)var j=A;else{var B=t();if(B!==null)var j=B;else var j=null}}}}h[a]={nextPos:d,result:j};return j}function r(){var a="labeled@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=L();if(e!==null){var f=A();if(f!==null){var g=s();if(g!==null)var i=[e,f,g];else{var i=null;d=c}}else{var i=null;d=c}}else{var i=null;d=c}var j=i!==null?function(a,b){return{type:"labeled",label:a,expression:b}}(i[0],i[2]):null;if(j!==null)var k=j;else{var l=s();if(l!==null)var k=l;else var k=null}h[a]={nextPos:d,result:k};return k}function q(){var a="sequence@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=[],f=r();while(f!==null){e.push(f);var f=r()}if(e!==null){var g=v();if(g!==null)var i=[e,g];else{var i=null;d=c}}else{var i=null;d=c}var j=i!==null?function(a,b){var c=a.length!=1?{type:"sequence",elements:a}:a[0];return{type:"action",expression:c,code:b}}(i[0],i[1]):null;if(j!==null)var k=j;else{var l=[],m=r();while(m!==null){l.push(m);var m=r()}var n=l!==null?function(a){return a.length!=1?{type:"sequence",elements:a}:a[0]}(l):null;if(n!==null)var k=n;else var k=null}h[a]={nextPos:d,result:k};return k}function p(){var a="choice@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=q();if(e!==null){var f=[],g=d,i=C();if(i!==null){var j=q();if(j!==null)var k=[i,j];else{var k=null;d=g}}else{var k=null;d=g}while(k!==null){f.push(k);var g=d,i=C();if(i!==null){var j=q();if(j!==null)var k=[i,j];else{var k=null;d=g}}else{var k=null;d=g}}if(f!==null)var l=[e,f];else{var l=null;d=c}}else{var l=null;d=c}var m=l!==null?function(a,b){if(b.length>0){var c=[a].concat(map(b,function(a){return a[1]}));return{type:"choice",alternatives:c}}return a}(l[0],l[1]):null;h[a]={nextPos:d,result:m};return m}function o(){var b="rule@"+d,c=h[b];if(c){d=c.nextPos;return c.result}var f=d,g=L();if(g!==null){var i=M();if(i!==null)var j=i;else{if(a.substr(d,0)===""){var k="";d+=0}else{var k=null;e&&l('""')}if(k!==null)var j=k;else var j=null}if(j!==null){var m=z();if(m!==null){var n=p();if(n!==null){var o=B(),q=o!==null?o:"";if(q!==null)var r=[g,j,m,n,q];else{var r=null;d=f}}else{var r=null;d=f}}else{var r=null;d=f}}else{var r=null;d=f}}else{var r=null;d=f}var s=r!==null?function(a,b,c){return{type:"rule",name:a,displayName:b!==""?b:null,expression:c}}(r[0],r[1],r[3]):null;h[b]={nextPos:d,result:s};return s}function n(){var a="initializer@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=v();if(e!==null){var f=B(),g=f!==null?f:"";if(g!==null)var i=[e,g];else{var i=null;d=c}}else{var i=null;d=c}var j=i!==null?function(a){return{type:"initializer",code:a}}(i[0]):null;h[a]={nextPos:d,result:j};return j}function m(){var a="grammar@"+d,b=h[a];if(b){d=b.nextPos;return b.result}var c=d,e=bg();if(e!==null){var f=n(),g=f!==null?f:"";if(g!==null){var i=o();if(i!==null){var j=[];while(i!==null){j.push(i);var i=o()}}else var j=null;if(j!==null)var k=[e,g,j];else{var k=null;d=c}}else{var k=null;d=c}}else{var k=null;d=c}var l=k!==null?function(a,b){var c={};each(b,function(a){c[a.name]=a});return{type:"grammar",initializer:a!==""?a:null,rules:c,startRule:b[0].name}}(k[1],k[2]):null;h[a]={nextPos:d,result:l};return l}function l(a){df&&(f=d,g=[]),g.push(a))}function k(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/[\x80-\uFFFF]/g,j)+'"'}function j(a){var b=a.charCodeAt(0);if(b<=255)var c="x",d=2;else var c="u",d=4;return"\\"+c+i(b.toString(16).toUpperCase(),"0",d)}function i(a,b,c){var d=a,e=c-a.length;for(var f=0;f0&&d(a.elements[0],b)},labeled:c,simple_and:c,simple_not:c,semantic_and:b,semantic_not:b,optional:c,zero_or_more:c,one_or_more:c,action:c,rule_ref:function(b,c){if(contains(c,b.name))throw new PEG.GrammarError('Left recursion detected for rule "'+b.name+'".');d(a.rules[b.name],c)},literal:b,any:b,"class":b});d(a,[])}},PEG.compiler.passes={proxyRules:function(a){function c(a,b,c){function f(a){return function(b,c,d){each(b[a],function(a){g(a,c,d)})}}function e(a,b,c){g(a.expression,b,c)}function d(){}var g=buildNodeVisitor({grammar:function(a,b,c){for(var d in a.rules)g(a.rules[d],b,c)},rule:e,choice:f("alternatives"),sequence:f("elements"),labeled:e,simple_and:e,simple_not:e,semantic_and:d,semantic_not:d,optional:e,zero_or_more:e,one_or_more:e,action:e,rule_ref:function(a,b,c){a.name===b&&(a.name=c)},literal:d,any:d,"class":d});g(a,b,c)}function b(a){return a.type==="rule"&&a.expression.type==="rule_ref"}for(var d in a.rules)b(a.rules[d])&&(c(a,a.rules[d].name,a.rules[d].expression.name),d===a.startRule&&(a.startRule=a.rules[d].expression.name),delete a.rules[d]);return a}},PEG.compiler.emitter=function(a){function b(){function b(a){return map(a,function(a){if(!/\n/.test(a))return a;var b=a.match(/^\s*/)[0],c=a.split("\n"),d=[c[0]].concat(map(c.slice(1),function(a){return b+a}));return d.join("\n")})}function a(a){return map(a,function(a){return a.replace(/\$\{([a-zA-Z_][a-zA-Z0-9_]*)(\|([a-zA-Z_][a-zA-Z0-9_]*))?\}/g,function(a,b,c,e){var f=d[b];if(f===undefined)throw new Error('Undefined variable: "'+b+'".');if(e!==undefined&&e!=""){if(e==="string")return quote(f);throw new Error('Unrecognized filter: "'+e+'".')}return f})})}var c=Array.prototype.slice.call(arguments),d=c[c.length-1]instanceof Object?c.pop():{};return b(a(c)).join("\n")}var c={_counters:{},next:function(a){this._counters[a]=this._counters[a]||0;return a+this._counters[a]++},reset:function(){this._counters={}}},d=buildNodeVisitor({grammar:function(a){var c=a.initializer!==null?d(a.initializer):"",e=[];for(var f in a.rules)e.push(quote(f)+": parse_"+f);e.sort();var g=[];for(var f in a.rules)g.push(d(a.rules[f]));return b("(function(){"," /* Generated by PEG.js 0.6.1 (http://pegjs.majda.cz/). */"," "," var result = {"," /*"," * Parses the input with a generated parser. If the parsing is successfull,"," * returns a value explicitly or implicitly specified by the grammar from"," * which the parser was generated (see |PEG.buildParser|). If the parsing is"," * unsuccessful, throws |PEG.parser.SyntaxError| describing the error."," */"," parse: function(input, startRule) {"," var parseFunctions = {"," ${parseFunctionTableItems}"," };"," "," if (startRule !== undefined) {" 2 | ," if (parseFunctions[startRule] === undefined) {",' throw new Error("Invalid rule name: " + quote(startRule) + ".");'," }"," } else {"," startRule = ${startRule|string};"," }"," "," var pos = 0;"," var reportMatchFailures = true;"," var rightmostMatchFailuresPos = 0;"," var rightmostMatchFailuresExpected = [];"," var cache = {};"," "," function padLeft(input, padding, length) {"," var result = input;"," "," var padLength = length - input.length;"," for (var i = 0; i < padLength; i++) {"," result = padding + result;"," }"," "," return result;"," }"," "," function escape(ch) {"," var charCode = ch.charCodeAt(0);"," "," if (charCode <= 0xFF) {"," var escapeChar = 'x';"," var length = 2;"," } else {"," var escapeChar = 'u';"," var length = 4;"," }"," "," return '\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length);"," }"," "," function quote(s) {"," /*"," * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a"," * string literal except for the closing quote character, backslash,"," * carriage return, line separator, paragraph separator, and line feed."," * Any character may appear in the form of an escape sequence."," */"," return '\"' + s"," .replace(/\\\\/g, '\\\\\\\\') // backslash"," .replace(/\"/g, '\\\\\"') // closing quote character"," .replace(/\\r/g, '\\\\r') // carriage return"," .replace(/\\n/g, '\\\\n') // line feed"," .replace(/[\\x80-\\uFFFF]/g, escape) // non-ASCII characters"," + '\"';"," }"," "," function matchFailed(failure) {"," if (pos < rightmostMatchFailuresPos) {"," return;"," }"," "," if (pos > rightmostMatchFailuresPos) {"," rightmostMatchFailuresPos = pos;"," rightmostMatchFailuresExpected = [];"," }"," "," rightmostMatchFailuresExpected.push(failure);"," }"," "," ${parseFunctionDefinitions}"," "," function buildErrorMessage() {"," function buildExpected(failuresExpected) {"," failuresExpected.sort();"," "," var lastFailure = null;"," var failuresExpectedUnique = [];"," for (var i = 0; i < failuresExpected.length; i++) {"," if (failuresExpected[i] !== lastFailure) {"," failuresExpectedUnique.push(failuresExpected[i]);"," lastFailure = failuresExpected[i];"," }"," }"," "," switch (failuresExpectedUnique.length) {"," case 0:"," return 'end of input';"," case 1:"," return failuresExpectedUnique[0];"," default:"," return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ')"," + ' or '"," + failuresExpectedUnique[failuresExpectedUnique.length - 1];"," }"," }"," "," var expected = buildExpected(rightmostMatchFailuresExpected);"," var actualPos = Math.max(pos, rightmostMatchFailuresPos);"," var actual = actualPos < input.length"," ? quote(input.charAt(actualPos))"," : 'end of input';"," "," return 'Expected ' + expected + ' but ' + actual + ' found.';"," }"," "," function computeErrorPosition() {"," /*"," * The first idea was to use |String.split| to break the input up to the"," * error position along newlines and derive the line and column from"," * there. However IE's |split| implementation is so broken that it was"," * enough to prevent it."," */"," "," var line = 1;"," var column = 1;"," var seenCR = false;"," "," for (var i = 0; i < rightmostMatchFailuresPos; i++) {"," var ch = input.charAt(i);"," if (ch === '\\n') {"," if (!seenCR) { line++; }"," column = 1;"," seenCR = false;"," } else if (ch === '\\r' | ch === '\\u2028' || ch === '\\u2029') {"," line++;"," column = 1;"," seenCR = true;"," } else {"," column++;"," seenCR = false;"," }"," }"," "," return { line: line, column: column };"," }"," "," ${initializerCode}"," "," var result = parseFunctions[startRule]();"," "," /*"," * The parser is now in one of the following three states:"," *"," * 1. The parser successfully parsed the whole input."," *"," * - |result !== null|"," * - |pos === input.length|"," * - |rightmostMatchFailuresExpected| may or may not contain something"," *"," * 2. The parser successfully parsed only a part of the input."," *"," * - |result !== null|"," * - |pos < input.length|"," * - |rightmostMatchFailuresExpected| may or may not contain something"," *"," * 3. The parser did not successfully parse any part of the input."," *"," * - |result === null|"," * - |pos === 0|"," * - |rightmostMatchFailuresExpected| contains at least one failure"," *"," * All code following this comment (including called functions) must"," * handle these states."," */"," if (result === null || pos !== input.length) {"," var errorPosition = computeErrorPosition();"," throw new this.SyntaxError("," buildErrorMessage(),"," errorPosition.line,"," errorPosition.column"," );"," }"," "," return result;"," },"," "," /* Returns the parser source code. */"," toSource: function() { return this._source; }"," };"," "," /* Thrown when a parser encounters a syntax error. */"," "," result.SyntaxError = function(message, line, column) {"," this.name = 'SyntaxError';"," this.message = message;"," this.line = line;"," this.column = column;"," };"," "," result.SyntaxError.prototype = Error.prototype;"," "," return result;","})()",{initializerCode:c,parseFunctionTableItems:e.join(",\n"),parseFunctionDefinitions:g.join("\n\n"),startRule:a.startRule})},initializer:function(a){return a.code},rule:function(a){c.reset();var e=c.next("result");if(a.displayName!==null)var f=b("var savedReportMatchFailures = reportMatchFailures;","reportMatchFailures = false;"),g=b("reportMatchFailures = savedReportMatchFailures;"),h=b("if (reportMatchFailures && ${resultVar} === null) {"," matchFailed(${displayName|string});","}",{displayName:a.displayName,resultVar:e});else var f="",g="",h="";return b("function parse_${name}() {"," var cacheKey = '${name}@' + pos;"," var cachedResult = cache[cacheKey];"," if (cachedResult) {"," pos = cachedResult.nextPos;"," return cachedResult.result;"," }"," "," ${setReportMatchFailuresCode}"," ${code}"," ${restoreReportMatchFailuresCode}"," ${reportMatchFailureCode}"," "," cache[cacheKey] = {"," nextPos: pos,"," result: ${resultVar}"," };"," return ${resultVar};","}",{name:a.name,setReportMatchFailuresCode:f,restoreReportMatchFailuresCode:g,reportMatchFailureCode:h,code:d(a.expression,e),resultVar:e})},choice:function(a,e){var f=b("var ${resultVar} = null;",{resultVar:e});for(var g=a.alternatives.length-1;g>=0;g--){var h=c.next("result");f=b("${alternativeCode}","if (${alternativeResultVar} !== null) {"," var ${resultVar} = ${alternativeResultVar};","} else {"," ${code};","}",{alternativeCode:d(a.alternatives[g],h),alternativeResultVar:h,code:f,resultVar:e})}return f},sequence:function(a,e){var f=c.next("savedPos"),g=map(a.elements,function(){return c.next("result")}),h=b("var ${resultVar} = ${elementResultVarArray};",{resultVar:e,elementResultVarArray:"["+g.join(", ")+"]"});for(var i=a.elements.length-1;i>=0;i--)h=b("${elementCode}","if (${elementResultVar} !== null) {"," ${code}","} else {"," var ${resultVar} = null;"," pos = ${savedPosVar};","}",{elementCode:d(a.elements[i],g[i]),elementResultVar:g[i],code:h,savedPosVar:f,resultVar:e});return b("var ${savedPosVar} = pos;","${code}",{code:h,savedPosVar:f})},labeled:function(a,b){return d(a.expression,b)},simple_and:function(a,e){var f=c.next("savedPos"),g=c.next("savedReportMatchFailuresVar"),h=c.next("result");return b("var ${savedPosVar} = pos;","var ${savedReportMatchFailuresVar} = reportMatchFailures;","reportMatchFailures = false;","${expressionCode}","reportMatchFailures = ${savedReportMatchFailuresVar};","if (${expressionResultVar} !== null) {"," var ${resultVar} = '';"," pos = ${savedPosVar};","} else {"," var ${resultVar} = null;","}",{expressionCode:d(a.expression,h),expressionResultVar:h,savedPosVar:f,savedReportMatchFailuresVar:g,resultVar:e})},simple_not:function(a,e){var f=c.next("savedPos"),g=c.next("savedReportMatchFailuresVar"),h=c.next("result");return b("var ${savedPosVar} = pos;","var ${savedReportMatchFailuresVar} = reportMatchFailures;","reportMatchFailures = false;","${expressionCode}","reportMatchFailures = ${savedReportMatchFailuresVar};","if (${expressionResultVar} === null) {"," var ${resultVar} = '';","} else {"," var ${resultVar} = null;"," pos = ${savedPosVar};","}",{expressionCode:d(a.expression,h),expressionResultVar:h,savedPosVar:f,savedReportMatchFailuresVar:g,resultVar:e})},semantic_and:function(a,c){return b("var ${resultVar} = (function() {${actionCode}})() ? '' : null;",{actionCode:a.code,resultVar:c})},semantic_not:function(a,c){return b("var ${resultVar} = (function() {${actionCode}})() ? null : '';",{actionCode:a.code,resultVar:c})},optional:function(a,e){var f=c.next("result");return b("${expressionCode}","var ${resultVar} = ${expressionResultVar} !== null ? ${expressionResultVar} : '';",{expressionCode:d(a.expression,f),expressionResultVar:f,resultVar:e})},zero_or_more:function(a,e){var f=c.next("result");return b("var ${resultVar} = [];","${expressionCode}","while (${expressionResultVar} !== null) {"," ${resultVar}.push(${expressionResultVar});"," ${expressionCode}","}",{expressionCode:d(a.expression,f),expressionResultVar:f,resultVar:e})},one_or_more:function(a,e){var f=c.next("result");return b("${expressionCode}","if (${expressionResultVar} !== null) {"," var ${resultVar} = [];"," while (${expressionResultVar} !== null) {"," ${resultVar}.push(${expressionResultVar});"," ${expressionCode}"," }","} else {"," var ${resultVar} = null;","}",{expressionCode:d(a.expression,f),expressionResultVar:f,resultVar:e})},action:function(a,e){var f=c.next("result");if(a.expression.type==="sequence"){var g=[],h=[],i=a.expression.elements,j=i.length;for(var k=0;k pos) {"," var ${resultVar} = input.charAt(pos);"," pos++;","} else {"," var ${resultVar} = null;"," if (reportMatchFailures) {"," matchFailed('any character');"," }","}",{resultVar:c})},"class":function(a,c){if(a.parts.length>0)var d="/^["+(a.inverted?"^":"")+map(a.parts,function(a){return a instanceof Array?quoteForRegexpClass(a[0])+"-"+quoteForRegexpClass(a[1]):quoteForRegexpClass(a)}).join("")+"]/";else var d=a.inverted?"/^[\\S\\s]/":"/^(?!)/";return b("if (input.substr(pos).match(${regexp}) !== null) {"," var ${resultVar} = input.charAt(pos);"," pos++;","} else {"," var ${resultVar} = null;"," if (reportMatchFailures) {"," matchFailed(${rawText|string});"," }","}",{regexp:d,rawText:a.rawText,resultVar:c})}});return d(a)};if(typeof module=="object")module.exports=PEG;else if(typeof window=="object")window.PEG=PEG;else throw new Error('Can\'t export PEG library (no "module" nor "window" object detected).')})() -------------------------------------------------------------------------------- /public/js/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** Font Family and Sizes */ 2 | 3 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 4 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 5 | } 6 | 7 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 8 | #qunit-tests { font-size: smaller; } 9 | 10 | 11 | /** Resets */ 12 | 13 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | 19 | /** Header */ 20 | 21 | #qunit-header { 22 | padding: 0.5em 0 0.5em 1em; 23 | 24 | color: #8699a4; 25 | background-color: #0d3349; 26 | 27 | font-size: 1.5em; 28 | line-height: 1em; 29 | font-weight: normal; 30 | 31 | border-radius: 15px 15px 0 0; 32 | -moz-border-radius: 15px 15px 0 0; 33 | -webkit-border-top-right-radius: 15px; 34 | -webkit-border-top-left-radius: 15px; 35 | } 36 | 37 | #qunit-header a { 38 | text-decoration: none; 39 | color: #c2ccd1; 40 | } 41 | 42 | #qunit-header a:hover, 43 | #qunit-header a:focus { 44 | color: #fff; 45 | } 46 | 47 | #qunit-banner { 48 | height: 5px; 49 | } 50 | 51 | #qunit-testrunner-toolbar { 52 | padding: 0.5em 0 0.5em 2em; 53 | color: #5E740B; 54 | background-color: #eee; 55 | } 56 | 57 | #qunit-userAgent { 58 | padding: 0.5em 0 0.5em 2.5em; 59 | background-color: #2b81af; 60 | color: #fff; 61 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 62 | } 63 | 64 | 65 | /** Tests: Pass/Fail */ 66 | 67 | #qunit-tests { 68 | list-style-position: inside; 69 | } 70 | 71 | #qunit-tests li { 72 | padding: 0.4em 0.5em 0.4em 2.5em; 73 | border-bottom: 1px solid #fff; 74 | list-style-position: inside; 75 | } 76 | 77 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 78 | display: none; 79 | } 80 | 81 | #qunit-tests li strong { 82 | cursor: pointer; 83 | } 84 | 85 | #qunit-tests li a { 86 | padding: 0.5em; 87 | color: #c2ccd1; 88 | text-decoration: none; 89 | } 90 | #qunit-tests li a:hover, 91 | #qunit-tests li a:focus { 92 | color: #000; 93 | } 94 | 95 | #qunit-tests ol { 96 | margin-top: 0.5em; 97 | padding: 0.5em; 98 | 99 | background-color: #fff; 100 | 101 | border-radius: 15px; 102 | -moz-border-radius: 15px; 103 | -webkit-border-radius: 15px; 104 | 105 | box-shadow: inset 0px 2px 13px #999; 106 | -moz-box-shadow: inset 0px 2px 13px #999; 107 | -webkit-box-shadow: inset 0px 2px 13px #999; 108 | } 109 | 110 | #qunit-tests table { 111 | border-collapse: collapse; 112 | margin-top: .2em; 113 | } 114 | 115 | #qunit-tests th { 116 | text-align: right; 117 | vertical-align: top; 118 | padding: 0 .5em 0 0; 119 | } 120 | 121 | #qunit-tests td { 122 | vertical-align: top; 123 | } 124 | 125 | #qunit-tests pre { 126 | margin: 0; 127 | white-space: pre-wrap; 128 | word-wrap: break-word; 129 | } 130 | 131 | #qunit-tests del { 132 | background-color: #e0f2be; 133 | color: #374e0c; 134 | text-decoration: none; 135 | } 136 | 137 | #qunit-tests ins { 138 | background-color: #ffcaca; 139 | color: #500; 140 | text-decoration: none; 141 | } 142 | 143 | /*** Test Counts */ 144 | 145 | #qunit-tests b.counts { color: black; } 146 | #qunit-tests b.passed { color: #5E740B; } 147 | #qunit-tests b.failed { color: #710909; } 148 | 149 | #qunit-tests li li { 150 | margin: 0.5em; 151 | padding: 0.4em 0.5em 0.4em 0.5em; 152 | background-color: #fff; 153 | border-bottom: none; 154 | list-style-position: inside; 155 | } 156 | 157 | /*** Passing Styles */ 158 | 159 | #qunit-tests li li.pass { 160 | color: #5E740B; 161 | background-color: #fff; 162 | border-left: 26px solid #C6E746; 163 | } 164 | 165 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 166 | #qunit-tests .pass .test-name { color: #366097; } 167 | 168 | #qunit-tests .pass .test-actual, 169 | #qunit-tests .pass .test-expected { color: #999999; } 170 | 171 | #qunit-banner.qunit-pass { background-color: #C6E746; } 172 | 173 | /*** Failing Styles */ 174 | 175 | #qunit-tests li li.fail { 176 | color: #710909; 177 | background-color: #fff; 178 | border-left: 26px solid #EE5757; 179 | } 180 | 181 | #qunit-tests > li:last-child { 182 | border-radius: 0 0 15px 15px; 183 | -moz-border-radius: 0 0 15px 15px; 184 | -webkit-border-bottom-right-radius: 15px; 185 | -webkit-border-bottom-left-radius: 15px; 186 | } 187 | 188 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 189 | #qunit-tests .fail .test-name, 190 | #qunit-tests .fail .module-name { color: #000000; } 191 | 192 | #qunit-tests .fail .test-actual { color: #EE5757; } 193 | #qunit-tests .fail .test-expected { color: green; } 194 | 195 | #qunit-banner.qunit-fail { background-color: #EE5757; } 196 | 197 | 198 | /** Result */ 199 | 200 | #qunit-testresult { 201 | padding: 0.5em 0.5em 0.5em 2.5em; 202 | 203 | color: #2b81af; 204 | background-color: #D2E0E6; 205 | 206 | border-bottom: 1px solid white; 207 | } 208 | 209 | /** Fixture */ 210 | 211 | #qunit-fixture { 212 | position: absolute; 213 | top: -10000px; 214 | left: -10000px; 215 | } 216 | -------------------------------------------------------------------------------- /public/js/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 | checkPollution(); 118 | this.testEnvironment.teardown.call(this.testEnvironment); 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.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.timeout ) { 404 | clearTimeout(config.timeout); 405 | } 406 | 407 | config.blocking = false; 408 | process(); 409 | }, 13); 410 | } else { 411 | config.blocking = false; 412 | process(); 413 | } 414 | }, 415 | 416 | stop: function(timeout) { 417 | config.semaphore++; 418 | config.blocking = true; 419 | 420 | if ( timeout && defined.setTimeout ) { 421 | clearTimeout(config.timeout); 422 | config.timeout = window.setTimeout(function() { 423 | QUnit.ok( false, "Test timed out" ); 424 | QUnit.start(); 425 | }, timeout); 426 | } 427 | } 428 | }; 429 | 430 | // Backwards compatibility, deprecated 431 | QUnit.equals = QUnit.equal; 432 | QUnit.same = QUnit.deepEqual; 433 | 434 | // Maintain internal state 435 | var config = { 436 | // The queue of tests to run 437 | queue: [], 438 | 439 | // block until document ready 440 | blocking: true, 441 | 442 | // by default, run previously failed tests first 443 | // very useful in combination with "Hide passed tests" checked 444 | reorder: true, 445 | 446 | noglobals: false, 447 | notrycatch: false 448 | }; 449 | 450 | // Load paramaters 451 | (function() { 452 | var location = window.location || { search: "", protocol: "file:" }, 453 | params = location.search.slice( 1 ).split( "&" ), 454 | length = params.length, 455 | urlParams = {}, 456 | current; 457 | 458 | if ( params[ 0 ] ) { 459 | for ( var i = 0; i < length; i++ ) { 460 | current = params[ i ].split( "=" ); 461 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 462 | // allow just a key to turn on a flag, e.g., test.html?noglobals 463 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 464 | urlParams[ current[ 0 ] ] = current[ 1 ]; 465 | if ( current[ 0 ] in config ) { 466 | config[ current[ 0 ] ] = current[ 1 ]; 467 | } 468 | } 469 | } 470 | 471 | QUnit.urlParams = urlParams; 472 | config.filter = urlParams.filter; 473 | 474 | // Figure out if we're running the tests from a server or not 475 | QUnit.isLocal = !!(location.protocol === 'file:'); 476 | })(); 477 | 478 | // Expose the API as global variables, unless an 'exports' 479 | // object exists, in that case we assume we're in CommonJS 480 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 481 | extend(window, QUnit); 482 | window.QUnit = QUnit; 483 | } else { 484 | extend(exports, QUnit); 485 | exports.QUnit = QUnit; 486 | } 487 | 488 | // define these after exposing globals to keep them in these QUnit namespace only 489 | extend(QUnit, { 490 | config: config, 491 | 492 | // Initialize the configuration options 493 | init: function() { 494 | extend(config, { 495 | stats: { all: 0, bad: 0 }, 496 | moduleStats: { all: 0, bad: 0 }, 497 | started: +new Date, 498 | updateRate: 1000, 499 | blocking: false, 500 | autostart: true, 501 | autorun: false, 502 | filter: "", 503 | queue: [], 504 | semaphore: 0 505 | }); 506 | 507 | var tests = id( "qunit-tests" ), 508 | banner = id( "qunit-banner" ), 509 | result = id( "qunit-testresult" ); 510 | 511 | if ( tests ) { 512 | tests.innerHTML = ""; 513 | } 514 | 515 | if ( banner ) { 516 | banner.className = ""; 517 | } 518 | 519 | if ( result ) { 520 | result.parentNode.removeChild( result ); 521 | } 522 | 523 | if ( tests ) { 524 | result = document.createElement( "p" ); 525 | result.id = "qunit-testresult"; 526 | result.className = "result"; 527 | tests.parentNode.insertBefore( result, tests ); 528 | result.innerHTML = 'Running...
     '; 529 | } 530 | }, 531 | 532 | /** 533 | * Resets the test setup. Useful for tests that modify the DOM. 534 | * 535 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 536 | */ 537 | reset: function() { 538 | if ( window.jQuery ) { 539 | jQuery( "#main, #qunit-fixture" ).html( config.fixture ); 540 | } else { 541 | var main = id( 'main' ) || id( 'qunit-fixture' ); 542 | if ( main ) { 543 | main.innerHTML = config.fixture; 544 | } 545 | } 546 | }, 547 | 548 | /** 549 | * Trigger an event on an element. 550 | * 551 | * @example triggerEvent( document.body, "click" ); 552 | * 553 | * @param DOMElement elem 554 | * @param String type 555 | */ 556 | triggerEvent: function( elem, type, event ) { 557 | if ( document.createEvent ) { 558 | event = document.createEvent("MouseEvents"); 559 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 560 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 561 | elem.dispatchEvent( event ); 562 | 563 | } else if ( elem.fireEvent ) { 564 | elem.fireEvent("on"+type); 565 | } 566 | }, 567 | 568 | // Safe object type checking 569 | is: function( type, obj ) { 570 | return QUnit.objectType( obj ) == type; 571 | }, 572 | 573 | objectType: function( obj ) { 574 | if (typeof obj === "undefined") { 575 | return "undefined"; 576 | 577 | // consider: typeof null === object 578 | } 579 | if (obj === null) { 580 | return "null"; 581 | } 582 | 583 | var type = Object.prototype.toString.call( obj ) 584 | .match(/^\[object\s(.*)\]$/)[1] || ''; 585 | 586 | switch (type) { 587 | case 'Number': 588 | if (isNaN(obj)) { 589 | return "nan"; 590 | } else { 591 | return "number"; 592 | } 593 | case 'String': 594 | case 'Boolean': 595 | case 'Array': 596 | case 'Date': 597 | case 'RegExp': 598 | case 'Function': 599 | return type.toLowerCase(); 600 | } 601 | if (typeof obj === "object") { 602 | return "object"; 603 | } 604 | return undefined; 605 | }, 606 | 607 | push: function(result, actual, expected, message) { 608 | var details = { 609 | result: result, 610 | message: message, 611 | actual: actual, 612 | expected: expected 613 | }; 614 | 615 | message = escapeHtml(message) || (result ? "okay" : "failed"); 616 | message = '' + message + ""; 617 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 618 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 619 | var output = message + ''; 620 | if (actual != expected) { 621 | output += ''; 622 | output += ''; 623 | } 624 | if (!result) { 625 | var source = sourceFromStacktrace(); 626 | if (source) { 627 | details.source = source; 628 | output += ''; 629 | } 630 | } 631 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + source +'
    "; 632 | 633 | QUnit.log(details); 634 | 635 | config.current.assertions.push({ 636 | result: !!result, 637 | message: output 638 | }); 639 | }, 640 | 641 | url: function( params ) { 642 | params = extend( extend( {}, QUnit.urlParams ), params ); 643 | var querystring = "?", 644 | key; 645 | for ( key in params ) { 646 | querystring += encodeURIComponent( key ) + "=" + 647 | encodeURIComponent( params[ key ] ) + "&"; 648 | } 649 | return window.location.pathname + querystring.slice( 0, -1 ); 650 | }, 651 | 652 | // Logging callbacks; all receive a single argument with the listed properties 653 | // run test/logs.html for any related changes 654 | begin: function() {}, 655 | // done: { failed, passed, total, runtime } 656 | done: function() {}, 657 | // log: { result, actual, expected, message } 658 | log: function() {}, 659 | // testStart: { name } 660 | testStart: function() {}, 661 | // testDone: { name, failed, passed, total } 662 | testDone: function() {}, 663 | // moduleStart: { name } 664 | moduleStart: function() {}, 665 | // moduleDone: { name, failed, passed, total } 666 | moduleDone: function() {} 667 | }); 668 | 669 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 670 | config.autorun = true; 671 | } 672 | 673 | addEvent(window, "load", function() { 674 | QUnit.begin({}); 675 | 676 | // Initialize the config, saving the execution queue 677 | var oldconfig = extend({}, config); 678 | QUnit.init(); 679 | extend(config, oldconfig); 680 | 681 | config.blocking = false; 682 | 683 | var userAgent = id("qunit-userAgent"); 684 | if ( userAgent ) { 685 | userAgent.innerHTML = navigator.userAgent; 686 | } 687 | var banner = id("qunit-header"); 688 | if ( banner ) { 689 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + 690 | '' + 691 | ''; 692 | addEvent( banner, "change", function( event ) { 693 | var params = {}; 694 | params[ event.target.name ] = event.target.checked ? true : undefined; 695 | window.location = QUnit.url( params ); 696 | }); 697 | } 698 | 699 | var toolbar = id("qunit-testrunner-toolbar"); 700 | if ( toolbar ) { 701 | var filter = document.createElement("input"); 702 | filter.type = "checkbox"; 703 | filter.id = "qunit-filter-pass"; 704 | addEvent( filter, "click", function() { 705 | var ol = document.getElementById("qunit-tests"); 706 | if ( filter.checked ) { 707 | ol.className = ol.className + " hidepass"; 708 | } else { 709 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 710 | ol.className = tmp.replace(/ hidepass /, " "); 711 | } 712 | if ( defined.sessionStorage ) { 713 | if (filter.checked) { 714 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 715 | } else { 716 | sessionStorage.removeItem("qunit-filter-passed-tests"); 717 | } 718 | } 719 | }); 720 | if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 721 | filter.checked = true; 722 | var ol = document.getElementById("qunit-tests"); 723 | ol.className = ol.className + " hidepass"; 724 | } 725 | toolbar.appendChild( filter ); 726 | 727 | var label = document.createElement("label"); 728 | label.setAttribute("for", "qunit-filter-pass"); 729 | label.innerHTML = "Hide passed tests"; 730 | toolbar.appendChild( label ); 731 | } 732 | 733 | var main = id('main') || id('qunit-fixture'); 734 | if ( main ) { 735 | config.fixture = main.innerHTML; 736 | } 737 | 738 | if (config.autostart) { 739 | QUnit.start(); 740 | } 741 | }); 742 | 743 | function done() { 744 | config.autorun = true; 745 | 746 | // Log the last module results 747 | if ( config.currentModule ) { 748 | QUnit.moduleDone( { 749 | name: config.currentModule, 750 | failed: config.moduleStats.bad, 751 | passed: config.moduleStats.all - config.moduleStats.bad, 752 | total: config.moduleStats.all 753 | } ); 754 | } 755 | 756 | var banner = id("qunit-banner"), 757 | tests = id("qunit-tests"), 758 | runtime = +new Date - config.started, 759 | passed = config.stats.all - config.stats.bad, 760 | html = [ 761 | 'Tests completed in ', 762 | runtime, 763 | ' milliseconds.
    ', 764 | '', 765 | passed, 766 | ' tests of ', 767 | config.stats.all, 768 | ' passed, ', 769 | config.stats.bad, 770 | ' failed.' 771 | ].join(''); 772 | 773 | if ( banner ) { 774 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 775 | } 776 | 777 | if ( tests ) { 778 | id( "qunit-testresult" ).innerHTML = html; 779 | } 780 | 781 | QUnit.done( { 782 | failed: config.stats.bad, 783 | passed: passed, 784 | total: config.stats.all, 785 | runtime: runtime 786 | } ); 787 | } 788 | 789 | function validTest( name ) { 790 | var filter = config.filter, 791 | run = false; 792 | 793 | if ( !filter ) { 794 | return true; 795 | } 796 | 797 | not = filter.charAt( 0 ) === "!"; 798 | if ( not ) { 799 | filter = filter.slice( 1 ); 800 | } 801 | 802 | if ( name.indexOf( filter ) !== -1 ) { 803 | return !not; 804 | } 805 | 806 | if ( not ) { 807 | run = true; 808 | } 809 | 810 | return run; 811 | } 812 | 813 | // so far supports only Firefox, Chrome and Opera (buggy) 814 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 815 | function sourceFromStacktrace() { 816 | try { 817 | throw new Error(); 818 | } catch ( e ) { 819 | if (e.stacktrace) { 820 | // Opera 821 | return e.stacktrace.split("\n")[6]; 822 | } else if (e.stack) { 823 | // Firefox, Chrome 824 | return e.stack.split("\n")[4]; 825 | } 826 | } 827 | } 828 | 829 | function escapeHtml(s) { 830 | if (!s) { 831 | return ""; 832 | } 833 | s = s + ""; 834 | return s.replace(/[\&"<>\\]/g, function(s) { 835 | switch(s) { 836 | case "&": return "&"; 837 | case "\\": return "\\\\"; 838 | case '"': return '\"'; 839 | case "<": return "<"; 840 | case ">": return ">"; 841 | default: return s; 842 | } 843 | }); 844 | } 845 | 846 | function synchronize( callback ) { 847 | config.queue.push( callback ); 848 | 849 | if ( config.autorun && !config.blocking ) { 850 | process(); 851 | } 852 | } 853 | 854 | function process() { 855 | var start = (new Date()).getTime(); 856 | 857 | while ( config.queue.length && !config.blocking ) { 858 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 859 | config.queue.shift()(); 860 | } else { 861 | window.setTimeout( process, 13 ); 862 | break; 863 | } 864 | } 865 | if (!config.blocking && !config.queue.length) { 866 | done(); 867 | } 868 | } 869 | 870 | function saveGlobal() { 871 | config.pollution = []; 872 | 873 | if ( config.noglobals ) { 874 | for ( var key in window ) { 875 | config.pollution.push( key ); 876 | } 877 | } 878 | } 879 | 880 | function checkPollution( name ) { 881 | var old = config.pollution; 882 | saveGlobal(); 883 | 884 | var newGlobals = diff( config.pollution, old ); 885 | if ( newGlobals.length > 0 ) { 886 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 887 | } 888 | 889 | var deletedGlobals = diff( old, config.pollution ); 890 | if ( deletedGlobals.length > 0 ) { 891 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 892 | } 893 | } 894 | 895 | // returns a new Array with the elements that are in a but not in b 896 | function diff( a, b ) { 897 | var result = a.slice(); 898 | for ( var i = 0; i < result.length; i++ ) { 899 | for ( var j = 0; j < b.length; j++ ) { 900 | if ( result[i] === b[j] ) { 901 | result.splice(i, 1); 902 | i--; 903 | break; 904 | } 905 | } 906 | } 907 | return result; 908 | } 909 | 910 | function fail(message, exception, callback) { 911 | if ( typeof console !== "undefined" && console.error && console.warn ) { 912 | console.error(message); 913 | console.error(exception); 914 | console.warn(callback.toString()); 915 | 916 | } else if ( window.opera && opera.postError ) { 917 | opera.postError(message, exception, callback.toString); 918 | } 919 | } 920 | 921 | function extend(a, b) { 922 | for ( var prop in b ) { 923 | if ( b[prop] === undefined ) { 924 | delete a[prop]; 925 | } else { 926 | a[prop] = b[prop]; 927 | } 928 | } 929 | 930 | return a; 931 | } 932 | 933 | function addEvent(elem, type, fn) { 934 | if ( elem.addEventListener ) { 935 | elem.addEventListener( type, fn, false ); 936 | } else if ( elem.attachEvent ) { 937 | elem.attachEvent( "on" + type, fn ); 938 | } else { 939 | fn(); 940 | } 941 | } 942 | 943 | function id(name) { 944 | return !!(typeof document !== "undefined" && document && document.getElementById) && 945 | document.getElementById( name ); 946 | } 947 | 948 | // Test for equality any JavaScript type. 949 | // Discussions and reference: http://philrathe.com/articles/equiv 950 | // Test suites: http://philrathe.com/tests/equiv 951 | // Author: Philippe Rathé 952 | QUnit.equiv = function () { 953 | 954 | var innerEquiv; // the real equiv function 955 | var callers = []; // stack to decide between skip/abort functions 956 | var parents = []; // stack to avoiding loops from circular referencing 957 | 958 | // Call the o related callback with the given arguments. 959 | function bindCallbacks(o, callbacks, args) { 960 | var prop = QUnit.objectType(o); 961 | if (prop) { 962 | if (QUnit.objectType(callbacks[prop]) === "function") { 963 | return callbacks[prop].apply(callbacks, args); 964 | } else { 965 | return callbacks[prop]; // or undefined 966 | } 967 | } 968 | } 969 | 970 | var callbacks = function () { 971 | 972 | // for string, boolean, number and null 973 | function useStrictEquality(b, a) { 974 | if (b instanceof a.constructor || a instanceof b.constructor) { 975 | // to catch short annotaion VS 'new' annotation of a declaration 976 | // e.g. var i = 1; 977 | // var j = new Number(1); 978 | return a == b; 979 | } else { 980 | return a === b; 981 | } 982 | } 983 | 984 | return { 985 | "string": useStrictEquality, 986 | "boolean": useStrictEquality, 987 | "number": useStrictEquality, 988 | "null": useStrictEquality, 989 | "undefined": useStrictEquality, 990 | 991 | "nan": function (b) { 992 | return isNaN(b); 993 | }, 994 | 995 | "date": function (b, a) { 996 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 997 | }, 998 | 999 | "regexp": function (b, a) { 1000 | return QUnit.objectType(b) === "regexp" && 1001 | a.source === b.source && // the regex itself 1002 | a.global === b.global && // and its modifers (gmi) ... 1003 | a.ignoreCase === b.ignoreCase && 1004 | a.multiline === b.multiline; 1005 | }, 1006 | 1007 | // - skip when the property is a method of an instance (OOP) 1008 | // - abort otherwise, 1009 | // initial === would have catch identical references anyway 1010 | "function": function () { 1011 | var caller = callers[callers.length - 1]; 1012 | return caller !== Object && 1013 | typeof caller !== "undefined"; 1014 | }, 1015 | 1016 | "array": function (b, a) { 1017 | var i, j, loop; 1018 | var len; 1019 | 1020 | // b could be an object literal here 1021 | if ( ! (QUnit.objectType(b) === "array")) { 1022 | return false; 1023 | } 1024 | 1025 | len = a.length; 1026 | if (len !== b.length) { // safe and faster 1027 | return false; 1028 | } 1029 | 1030 | //track reference to avoid circular references 1031 | parents.push(a); 1032 | for (i = 0; i < len; i++) { 1033 | loop = false; 1034 | for(j=0;j= 0) { 1179 | type = "array"; 1180 | } else { 1181 | type = typeof obj; 1182 | } 1183 | return type; 1184 | }, 1185 | separator:function() { 1186 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1187 | }, 1188 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1189 | if ( !this.multiline ) 1190 | return ''; 1191 | var chr = this.indentChar; 1192 | if ( this.HTML ) 1193 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1194 | return Array( this._depth_ + (extra||0) ).join(chr); 1195 | }, 1196 | up:function( a ) { 1197 | this._depth_ += a || 1; 1198 | }, 1199 | down:function( a ) { 1200 | this._depth_ -= a || 1; 1201 | }, 1202 | setParser:function( name, parser ) { 1203 | this.parsers[name] = parser; 1204 | }, 1205 | // The next 3 are exposed so you can use them 1206 | quote:quote, 1207 | literal:literal, 1208 | join:join, 1209 | // 1210 | _depth_: 1, 1211 | // This is the list of parsers, to modify them, use jsDump.setParser 1212 | parsers:{ 1213 | window: '[Window]', 1214 | document: '[Document]', 1215 | error:'[ERROR]', //when no parser is found, shouldn't happen 1216 | unknown: '[Unknown]', 1217 | 'null':'null', 1218 | 'undefined':'undefined', 1219 | 'function':function( fn ) { 1220 | var ret = 'function', 1221 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1222 | if ( name ) 1223 | ret += ' ' + name; 1224 | ret += '('; 1225 | 1226 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1227 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1228 | }, 1229 | array: array, 1230 | nodelist: array, 1231 | arguments: array, 1232 | object:function( map ) { 1233 | var ret = [ ]; 1234 | QUnit.jsDump.up(); 1235 | for ( var key in map ) 1236 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); 1237 | QUnit.jsDump.down(); 1238 | return join( '{', ret, '}' ); 1239 | }, 1240 | node:function( node ) { 1241 | var open = QUnit.jsDump.HTML ? '<' : '<', 1242 | close = QUnit.jsDump.HTML ? '>' : '>'; 1243 | 1244 | var tag = node.nodeName.toLowerCase(), 1245 | ret = open + tag; 1246 | 1247 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1248 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1249 | if ( val ) 1250 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1251 | } 1252 | return ret + close + open + '/' + tag + close; 1253 | }, 1254 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1255 | var l = fn.length; 1256 | if ( !l ) return ''; 1257 | 1258 | var args = Array(l); 1259 | while ( l-- ) 1260 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1261 | return ' ' + args.join(', ') + ' '; 1262 | }, 1263 | key:quote, //object calls it internally, the key part of an item in a map 1264 | functionCode:'[code]', //function calls it internally, it's the content of the function 1265 | attribute:quote, //node calls it internally, it's an html attribute value 1266 | string:quote, 1267 | date:quote, 1268 | regexp:literal, //regex 1269 | number:literal, 1270 | 'boolean':literal 1271 | }, 1272 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1273 | id:'id', 1274 | name:'name', 1275 | 'class':'className' 1276 | }, 1277 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1278 | indentChar:' ',//indentation unit 1279 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1280 | }; 1281 | 1282 | return jsDump; 1283 | })(); 1284 | 1285 | // from Sizzle.js 1286 | function getText( elems ) { 1287 | var ret = "", elem; 1288 | 1289 | for ( var i = 0; elems[i]; i++ ) { 1290 | elem = elems[i]; 1291 | 1292 | // Get the text from text nodes and CDATA nodes 1293 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1294 | ret += elem.nodeValue; 1295 | 1296 | // Traverse everything else, except comment nodes 1297 | } else if ( elem.nodeType !== 8 ) { 1298 | ret += getText( elem.childNodes ); 1299 | } 1300 | } 1301 | 1302 | return ret; 1303 | }; 1304 | 1305 | /* 1306 | * Javascript Diff Algorithm 1307 | * By John Resig (http://ejohn.org/) 1308 | * Modified by Chu Alan "sprite" 1309 | * 1310 | * Released under the MIT license. 1311 | * 1312 | * More Info: 1313 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1314 | * 1315 | * Usage: QUnit.diff(expected, actual) 1316 | * 1317 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1318 | */ 1319 | QUnit.diff = (function() { 1320 | function diff(o, n){ 1321 | var ns = new Object(); 1322 | var os = new Object(); 1323 | 1324 | for (var i = 0; i < n.length; i++) { 1325 | if (ns[n[i]] == null) 1326 | ns[n[i]] = { 1327 | rows: new Array(), 1328 | o: null 1329 | }; 1330 | ns[n[i]].rows.push(i); 1331 | } 1332 | 1333 | for (var i = 0; i < o.length; i++) { 1334 | if (os[o[i]] == null) 1335 | os[o[i]] = { 1336 | rows: new Array(), 1337 | n: null 1338 | }; 1339 | os[o[i]].rows.push(i); 1340 | } 1341 | 1342 | for (var i in ns) { 1343 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1344 | n[ns[i].rows[0]] = { 1345 | text: n[ns[i].rows[0]], 1346 | row: os[i].rows[0] 1347 | }; 1348 | o[os[i].rows[0]] = { 1349 | text: o[os[i].rows[0]], 1350 | row: ns[i].rows[0] 1351 | }; 1352 | } 1353 | } 1354 | 1355 | for (var i = 0; i < n.length - 1; i++) { 1356 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1357 | n[i + 1] == o[n[i].row + 1]) { 1358 | n[i + 1] = { 1359 | text: n[i + 1], 1360 | row: n[i].row + 1 1361 | }; 1362 | o[n[i].row + 1] = { 1363 | text: o[n[i].row + 1], 1364 | row: i + 1 1365 | }; 1366 | } 1367 | } 1368 | 1369 | for (var i = n.length - 1; i > 0; i--) { 1370 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1371 | n[i - 1] == o[n[i].row - 1]) { 1372 | n[i - 1] = { 1373 | text: n[i - 1], 1374 | row: n[i].row - 1 1375 | }; 1376 | o[n[i].row - 1] = { 1377 | text: o[n[i].row - 1], 1378 | row: i - 1 1379 | }; 1380 | } 1381 | } 1382 | 1383 | return { 1384 | o: o, 1385 | n: n 1386 | }; 1387 | } 1388 | 1389 | return function(o, n){ 1390 | o = o.replace(/\s+$/, ''); 1391 | n = n.replace(/\s+$/, ''); 1392 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1393 | 1394 | var str = ""; 1395 | 1396 | var oSpace = o.match(/\s+/g); 1397 | if (oSpace == null) { 1398 | oSpace = [" "]; 1399 | } 1400 | else { 1401 | oSpace.push(" "); 1402 | } 1403 | var nSpace = n.match(/\s+/g); 1404 | if (nSpace == null) { 1405 | nSpace = [" "]; 1406 | } 1407 | else { 1408 | nSpace.push(" "); 1409 | } 1410 | 1411 | if (out.n.length == 0) { 1412 | for (var i = 0; i < out.o.length; i++) { 1413 | str += '' + out.o[i] + oSpace[i] + ""; 1414 | } 1415 | } 1416 | else { 1417 | if (out.n[0].text == null) { 1418 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1419 | str += '' + out.o[n] + oSpace[n] + ""; 1420 | } 1421 | } 1422 | 1423 | for (var i = 0; i < out.n.length; i++) { 1424 | if (out.n[i].text == null) { 1425 | str += '' + out.n[i] + nSpace[i] + ""; 1426 | } 1427 | else { 1428 | var pre = ""; 1429 | 1430 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1431 | pre += '' + out.o[n] + oSpace[n] + ""; 1432 | } 1433 | str += " " + out.n[i].text + nSpace[i] + pre; 1434 | } 1435 | } 1436 | } 1437 | 1438 | return str; 1439 | }; 1440 | })(); 1441 | 1442 | })(this); 1443 | -------------------------------------------------------------------------------- /public/js/sql.pegjs-head.js: -------------------------------------------------------------------------------- 1 | // Header/utility functions for sql.pegjs grammar match bodies. 2 | // 3 | function append(arr, x) { 4 | arr[arr.length] = x; 5 | return arr; 6 | } 7 | 8 | function flatten(x, rejectSpace, acc) { 9 | acc = acc || []; 10 | if (x == null || x == undefined) { 11 | if (!rejectSpace) { 12 | return append(acc, x); 13 | } 14 | return acc; 15 | } 16 | if (x.length == undefined) { // Just an object, not a string or array. 17 | return append(acc, x); 18 | } 19 | if (rejectSpace && 20 | ((x.length == 0) || 21 | (typeof(x) == "string" && 22 | x.match(/^\s*$/)))) { 23 | return acc; 24 | } 25 | if (typeof(x) == "string") { 26 | return append(acc, x); 27 | } 28 | for (var i = 0; i < x.length; i++) { 29 | flatten(x[i], rejectSpace, acc); 30 | } 31 | return acc; 32 | } 33 | 34 | function flatstr(x, rejectSpace, joinChar) { 35 | return flatten(x, rejectSpace, []).join(joinChar || ''); 36 | } 37 | 38 | function filter(arr, x) { 39 | var acc = []; 40 | for (var i = 0; i < arr.length; i++) { 41 | if (arr[i] != x) { 42 | acc[length] = arr[i]; 43 | } 44 | } 45 | return acc; 46 | } 47 | 48 | function nonempty(x) { // Ex: nonempty("") == null; 49 | if (x == null || x.length > 0) { // Ex: nonempty(null) == null; 50 | return x; 51 | } 52 | return null; 53 | } 54 | 55 | function put_if_not_null(m, key, val) { 56 | if (val) { 57 | m[key] = val; 58 | } 59 | return m; 60 | } 61 | function merge(src, dst) { 62 | for (var k in src) { 63 | dst[k] = src[k]; 64 | } 65 | return dst; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /public/test/test-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 30 |
    31 |

    sql.pegjs-head.js Test Suite

    32 |

    33 |
    34 |

    35 |
      36 |
      test markup
      37 |
      38 | 39 | 40 | -------------------------------------------------------------------------------- /public/test/test-peg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | 61 | 62 |
      63 |

      Test Suite

      64 |

      65 |
      66 |

      67 |
        68 |
        test markup
        69 |
        70 | 71 | 72 | -------------------------------------------------------------------------------- /public/test/test-sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 325 | 326 |
        327 |

        Sample Qunit Test Suite

        328 |

        329 |
        330 |

        331 |
          332 |
          test markup
          333 |
          334 | 335 | 336 | -------------------------------------------------------------------------------- /sql-bubble.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Process the sql-bubble.txt into a ruby rules. 4 | # 5 | bubble = ARGV[0] || 'sql-bubble.txt' 6 | 7 | lines = File.open(bubble, "r").readlines 8 | lines = lines.reject {|x| x.match(/^\s*\#/)} # Comments. 9 | lines = lines.reject {|x| x.match(/^\s*$/)} # Whitespace. 10 | lines = lines.map {|x| x.gsub(/^set all_graphs \{/, '')} 11 | lines = lines.map {|x| x.gsub(/^\}/, 'nil')} 12 | lines = lines.map {|x| x.gsub(/\{\}/, 'nil')} 13 | lines = lines.map {|x| x.gsub(/\./, 'dot')} 14 | lines = lines.map {|x| x.gsub(/\=/, 'equal')} 15 | lines = lines.map {|x| x.gsub(/\-\-/, 'minusminus')} 16 | lines = lines.map {|x| x.gsub(/ \-/, ' minus')} 17 | lines = lines.map {|x| x.gsub(/\+/, 'plus')} 18 | lines = lines.map {|x| x.gsub(/\-/, '_')} 19 | lines = lines.map {|x| x.gsub(/,/, 'comma')} 20 | lines = lines.map {|x| x.gsub(/\/\*/, 'comment_beg')} 21 | lines = lines.map {|x| x.gsub(/\*\//, 'comment_end')} 22 | lines = lines.map {|x| x.gsub(/\*/, 'star')} 23 | lines = lines.map {|x| x.gsub(/\;/, 'semicolon')} 24 | lines = lines.map {|x| x.gsub(/\(/, 'lparen')} 25 | lines = lines.map {|x| x.gsub(/\)/, 'rparen')} 26 | lines = lines.map {|x| x.gsub(/\{/, '(')} 27 | lines = lines.map {|x| x.gsub(/\}/, ')')} 28 | lines = lines.map {|x| x.gsub(/\//, '')} 29 | lines = lines.map {|x| x.gsub(/([A-Z][A-Z_]*)/, '"\\1"')} 30 | lines = lines.map {|x| x.gsub(/([a-z][a-z_]+)/, ':\\1')} 31 | lines = lines.map {|x| x.gsub(/^ \)/, ' ))')} 32 | 33 | c = lines.join("") 34 | 35 | if true 36 | c = c.gsub(/\(\s*:toploop ?/, "toploop( ") 37 | c = c.gsub(/\(\s*:tailbranch ?/, "tailbranch( ") 38 | c = c.gsub(/\(\s*:opt(x?) ?/, "opt\\1( ") 39 | c = c.gsub(/\(\s*:or ?/, "either( ") 40 | c = c.gsub(/\(\s*:line ?/, "line( ") 41 | c = c.gsub(/\(\s*:loop ?/, "loop( ") 42 | c = c.gsub(/\(\s*:stack ?/, "stack( ") 43 | c = c.gsub(/\"(\s+)/, "\",\\1") 44 | c = c.gsub(/([a-z])(\s+)/, "\\1,\\2") 45 | c = c.gsub(/\)/, "),\\1") 46 | c = c.gsub(/,(\s*)\)/, "\\1)") 47 | c = c.gsub(/ \)\),/, " ))") 48 | c = c.gsub(/, \(:nil,/, ", line(:nil,") 49 | end 50 | 51 | lines = c.split("\n") 52 | lines = lines.map {|x| x.gsub(/^ ([a-z_:]+)/, ' rule( \\1')} 53 | 54 | print "# generated by './sql-bubble.rb #{bubble}'\n" 55 | print lines[0..-2].join("\n") 56 | print "\n" 57 | 58 | -------------------------------------------------------------------------------- /sql-bubble.txt: -------------------------------------------------------------------------------- 1 | # This file contains the data used by the three syntax diagram rendering 2 | # programs: 3 | # 4 | # bubble-generator.tcl 5 | # bubble-generator-bnf.tcl 6 | # bubble-generator-bnf.tcl 7 | # 8 | 9 | # Graphs: 10 | # 11 | set all_graphs { 12 | sql-stmt-list { 13 | toploop {optx sql-stmt} ; 14 | } 15 | sql-stmt { 16 | line 17 | {opt EXPLAIN {opt QUERY PLAN}} 18 | {or 19 | alter-table-stmt 20 | analyze-stmt 21 | attach-stmt 22 | begin-stmt 23 | commit-stmt 24 | create-index-stmt 25 | create-table-stmt 26 | create-trigger-stmt 27 | create-view-stmt 28 | create-virtual-table-stmt 29 | delete-stmt 30 | delete-stmt-limited 31 | detach-stmt 32 | drop-index-stmt 33 | drop-table-stmt 34 | drop-trigger-stmt 35 | drop-view-stmt 36 | insert-stmt 37 | pragma-stmt 38 | reindex-stmt 39 | release-stmt 40 | rollback-stmt 41 | savepoint-stmt 42 | select-stmt 43 | update-stmt 44 | update-stmt-limited 45 | vacuum-stmt 46 | } 47 | } 48 | alter-table-stmt { 49 | stack 50 | {line ALTER TABLE {optx /database-name .} /table-name} 51 | {tailbranch 52 | {line RENAME TO /new-table-name} 53 | {line ADD {optx COLUMN} column-def} 54 | } 55 | } 56 | analyze-stmt { 57 | line ANALYZE {or nil /database-name /table-or-index-name 58 | {line /database-name . /table-or-index-name}} 59 | } 60 | attach-stmt { 61 | line ATTACH {or DATABASE nil} expr AS /database-name 62 | } 63 | begin-stmt { 64 | line BEGIN {or nil DEFERRED IMMEDIATE EXCLUSIVE} 65 | {optx TRANSACTION} 66 | } 67 | commit-stmt { 68 | line {or COMMIT END} {optx TRANSACTION} 69 | } 70 | rollback-stmt { 71 | line ROLLBACK {optx TRANSACTION} 72 | {optx TO {optx SAVEPOINT} /savepoint-name} 73 | } 74 | savepoint-stmt { 75 | line SAVEPOINT /savepoint-name 76 | } 77 | release-stmt { 78 | line RELEASE {optx SAVEPOINT} /savepoint-name 79 | } 80 | create-index-stmt { 81 | stack 82 | {line CREATE {opt UNIQUE} INDEX {opt IF NOT EXISTS}} 83 | {line {optx /database-name .} /index-name 84 | ON /table-name ( {loop indexed-column ,} )} 85 | } 86 | indexed-column { 87 | line /column-name {optx COLLATE /collation-name} {or ASC DESC nil} 88 | } 89 | create-table-stmt { 90 | stack 91 | {line CREATE {or {} TEMP TEMPORARY} TABLE {opt IF NOT EXISTS}} 92 | {line {optx /database-name .} /table-name 93 | {tailbranch 94 | {line ( {loop column-def ,} {loop {} {line , table-constraint}} )} 95 | {line AS select-stmt} 96 | } 97 | } 98 | } 99 | column-def { 100 | line /column-name {or type-name nil} {loop nil {nil column-constraint nil}} 101 | } 102 | type-name { 103 | line {loop /name {}} {or {} 104 | {line ( signed-number )} 105 | {line ( signed-number , signed-number )} 106 | } 107 | } 108 | column-constraint { 109 | stack 110 | {optx CONSTRAINT /name} 111 | {or 112 | {line PRIMARY KEY {or nil ASC DESC} 113 | conflict-clause {opt AUTOINCREMENT} 114 | } 115 | {line NOT NULL conflict-clause} 116 | {line UNIQUE conflict-clause} 117 | {line CHECK ( expr )} 118 | {line DEFAULT 119 | {or 120 | signed-number 121 | literal-value 122 | {line ( expr )} 123 | } 124 | } 125 | {line COLLATE /collation-name} 126 | {line foreign-key-clause} 127 | } 128 | } 129 | signed-number { 130 | line {or nil + -} /numeric-literal 131 | } 132 | table-constraint { 133 | stack 134 | {optx CONSTRAINT /name} 135 | {or 136 | {line {or {line PRIMARY KEY} UNIQUE} 137 | ( {loop indexed-column ,} ) conflict-clause} 138 | {line CHECK ( expr )} 139 | {line FOREIGN KEY ( {loop /column-name ,} ) foreign-key-clause } 140 | } 141 | } 142 | foreign-key-clause { 143 | stack 144 | {line REFERENCES /foreign-table {optx ( {loop /column-name ,} )}} 145 | {optx 146 | {loop 147 | {or 148 | {line ON {or DELETE UPDATE} 149 | {or {line SET NULL} {line SET DEFAULT} 150 | CASCADE RESTRICT {line NO ACTION} 151 | } 152 | } 153 | {line MATCH /name} 154 | } 155 | {} 156 | } 157 | } 158 | {optx 159 | {line {optx NOT} DEFERRABLE 160 | {or 161 | {line INITIALLY DEFERRED} 162 | {line INITIALLY IMMEDIATE} 163 | {} 164 | } 165 | } 166 | nil 167 | } 168 | } 169 | conflict-clause { 170 | opt {line ON CONFLICT {or ROLLBACK ABORT FAIL IGNORE REPLACE}} 171 | } 172 | create-trigger-stmt { 173 | stack 174 | {line CREATE {or {} TEMP TEMPORARY} TRIGGER {opt IF NOT EXISTS}} 175 | {line {optx /database-name .} /trigger-name 176 | {or BEFORE AFTER {line INSTEAD OF} nil} 177 | } 178 | {line 179 | {or DELETE INSERT 180 | {line UPDATE {opt OF {loop /column-name ,} }} 181 | } 182 | ON /table-name 183 | } 184 | {line {optx FOR EACH ROW} 185 | {optx WHEN expr} 186 | } 187 | {line BEGIN 188 | {loop 189 | {line {or update-stmt insert-stmt delete-stmt select-stmt} ;} 190 | nil 191 | } 192 | END 193 | } 194 | } 195 | create-view-stmt { 196 | stack 197 | {line CREATE {or {} TEMP TEMPORARY} VIEW {opt IF NOT EXISTS}} 198 | {line {optx /database-name .} /view-name AS select-stmt} 199 | } 200 | create-virtual-table-stmt { 201 | stack 202 | {line CREATE VIRTUAL TABLE {optx /database-name .} /table-name} 203 | {line USING /module-name {optx ( {loop /module-argument ,} )}} 204 | } 205 | delete-stmt { 206 | line DELETE FROM qualified-table-name {optx WHERE expr} 207 | } 208 | delete-stmt-limited { 209 | stack 210 | {line DELETE FROM qualified-table-name {optx WHERE expr}} 211 | {optx 212 | {stack 213 | {optx ORDER BY {loop ordering-term ,}} 214 | {line LIMIT /expr {optx {or OFFSET ,} /expr}} 215 | } 216 | } 217 | } 218 | detach-stmt { 219 | line DETACH {optx DATABASE} /database-name 220 | } 221 | drop-index-stmt { 222 | line DROP INDEX {optx IF EXISTS} {optx /database-name .} /index-name 223 | } 224 | drop-table-stmt { 225 | line DROP TABLE {optx IF EXISTS} {optx /database-name .} /table-name 226 | } 227 | drop-trigger-stmt { 228 | line DROP TRIGGER {optx IF EXISTS} {optx /database-name .} /trigger-name 229 | } 230 | drop-view-stmt { 231 | line DROP VIEW {optx IF EXISTS} {optx /database-name .} /view-name 232 | } 233 | expr { 234 | or 235 | {line literal-value} 236 | {line bind-parameter} 237 | {line {optx {optx /database-name .} /table-name .} /column-name} 238 | {line /unary-operator expr} 239 | {line expr /binary-operator expr} 240 | {line /function-name ( {or {line {optx DISTINCT} {toploop expr ,}} {} *} )} 241 | {line ( expr )} 242 | {line CAST ( expr AS type-name )} 243 | {line expr COLLATE /collation-name} 244 | {line expr {optx NOT} {or LIKE GLOB REGEXP MATCH} expr 245 | {optx ESCAPE expr}} 246 | {line expr {or ISNULL NOTNULL {line NOT NULL}}} 247 | {line expr IS {optx NOT} expr} 248 | {line expr {optx NOT} BETWEEN expr AND expr} 249 | {line expr {optx NOT} IN 250 | {or 251 | {line ( {or {} select-stmt {loop expr ,}} )} 252 | {line {optx /database-name .} /table-name} 253 | } 254 | } 255 | {line {optx {optx NOT} EXISTS} ( select-stmt )} 256 | {line CASE {optx expr} {loop {line WHEN expr THEN expr} {}} 257 | {optx ELSE expr} END} 258 | {line raise-function} 259 | } 260 | raise-function { 261 | line RAISE ( 262 | {or IGNORE 263 | {line {or ROLLBACK ABORT FAIL} , /error-message } 264 | } ) 265 | } 266 | literal-value { 267 | or 268 | {line /numeric-literal} 269 | {line /string-literal} 270 | {line /blob-literal} 271 | {line NULL} 272 | {line CURRENT_TIME} 273 | {line CURRENT_DATE} 274 | {line CURRENT_TIMESTAMP} 275 | } 276 | numeric-literal { 277 | line {or 278 | {line {loop /digit nil} {opt /decimal-point {loop nil /digit}}} 279 | {line /decimal-point {loop /digit nil}} 280 | } 281 | {opt E {or nil + -} {loop /digit nil}} 282 | } 283 | insert-stmt { 284 | stack 285 | {line 286 | {or 287 | {line INSERT {opt OR {or ROLLBACK ABORT REPLACE FAIL IGNORE}}} 288 | REPLACE 289 | } 290 | INTO {optx /database-name .} /table-name 291 | } 292 | {tailbranch 293 | {line 294 | {optx ( {loop /column-name ,} )} 295 | {tailbranch 296 | {line VALUES ( {loop expr ,} )} 297 | select-stmt 298 | } 299 | } 300 | {line DEFAULT VALUES} 301 | } 302 | } 303 | pragma-stmt { 304 | line PRAGMA {optx /database-name .} /pragma-name 305 | {or 306 | nil 307 | {line = pragma-value} 308 | {line ( pragma-value )} 309 | } 310 | } 311 | pragma-value { 312 | or 313 | signed-number 314 | /name 315 | /string-literal 316 | } 317 | reindex-stmt { 318 | line REINDEX 319 | {tailbranch nil 320 | /collation-name 321 | {line {optx /database-name .} 322 | {tailbranch /table-name /index-name} 323 | } 324 | } 325 | } 326 | select-stmt { 327 | stack 328 | {loop {line select-core nil} {nil compound-operator nil}} 329 | {optx ORDER BY {loop ordering-term ,}} 330 | {optx LIMIT /expr {optx {or OFFSET ,} /expr}} 331 | } 332 | select-core { 333 | stack 334 | {line SELECT {or nil DISTINCT ALL} {loop result-column ,}} 335 | {optx FROM join-source} 336 | {optx WHERE expr} 337 | {optx GROUP BY {loop ordering-term ,} {optx HAVING expr}} 338 | 339 | } 340 | result-column { 341 | or 342 | * 343 | {line /table-name . *} 344 | {line expr {optx {optx AS} /column-alias}} 345 | } 346 | join-source { 347 | line 348 | single-source 349 | {opt {loop {line nil join-op single-source join-constraint nil} {}}} 350 | } 351 | single-source { 352 | or 353 | {line 354 | {optx /database-name .} /table-name 355 | {optx {optx AS} /table-alias} 356 | {or nil {line INDEXED BY /index-name} {line NOT INDEXED}} 357 | } 358 | {line 359 | ( select-stmt ) {optx {optx AS} /table-alias} 360 | } 361 | {line ( join-source )} 362 | } 363 | join-op { 364 | or 365 | {line ,} 366 | {line 367 | {opt NATURAL} 368 | {or nil {line LEFT {or OUTER nil}} INNER CROSS} 369 | JOIN 370 | } 371 | } 372 | join-constraint { 373 | or 374 | {line ON expr} 375 | {line USING ( {loop /column-name ,} )} 376 | nil 377 | } 378 | ordering-term { 379 | line expr {opt COLLATE /collation-name} {or nil ASC DESC} 380 | } 381 | compound-operator { 382 | or {line UNION {optx ALL}} INTERSECT EXCEPT 383 | } 384 | update-stmt { 385 | stack 386 | {line UPDATE {opt OR {or ROLLBACK ABORT REPLACE FAIL IGNORE}} 387 | qualified-table-name} 388 | {line SET {loop {line /column-name = expr} ,} {optx WHERE expr}} 389 | } 390 | update-stmt-limited { 391 | stack 392 | {line UPDATE {opt OR {or ROLLBACK ABORT REPLACE FAIL IGNORE}} 393 | qualified-table-name} 394 | {line SET {loop {line /column-name = expr} ,} {optx WHERE expr}} 395 | {optx 396 | {stack 397 | {optx ORDER BY {loop ordering-term ,}} 398 | {line LIMIT /expr {optx {or OFFSET ,} /expr}} 399 | } 400 | } 401 | } 402 | qualified-table-name { 403 | line {optx /database-name .} /table-name 404 | {or nil {line INDEXED BY /index-name} {line NOT INDEXED}} 405 | } 406 | vacuum-stmt { 407 | line VACUUM 408 | } 409 | comment-syntax { 410 | or 411 | {line -- {loop nil /anything-except-newline} 412 | {or /newline /end-of-input}} 413 | {line /* {loop nil /anything-except-*/} 414 | {or */ /end-of-input}} 415 | } 416 | } 417 | 418 | -------------------------------------------------------------------------------- /sql.pegjs: -------------------------------------------------------------------------------- 1 | // Originally generated from... 2 | // 1) sql-bubble.txt (from sqlite.org) 3 | // 2) ./sql-bubble.rb sql-bubble.txt > tmp/rules.rb 4 | // 3) ./bubble-to-pegjs.rb tmp/rules.rb bubble-to-pegjs_ex.rb > tmp/sql.pegjs 5 | // 6 | // Then, manually edited for pegjs suitability. 7 | // 8 | // Rules with indentation or with comments have manual edits. 9 | // 10 | start = sql_stmt_list 11 | 12 | sql_stmt_list = 13 | r: ( whitespace ( sql_stmt )? whitespace semicolon )+ 14 | { return filter(flatten(r, true), ';') } 15 | 16 | sql_stmt = 17 | ( explain: ( EXPLAIN ( QUERY PLAN )? )? 18 | stmt: select_stmt ) 19 | { return put_if_not_null(stmt, "explain", nonempty(flatstr(explain))) } 20 | 21 | // For now, just concentrate of SELECT statements only, although 22 | // we have all the machinery for all other statements, too. 23 | // 24 | // sql_stmt = 25 | // ( explain: ( EXPLAIN ( QUERY PLAN )? )? 26 | // stmt: ( 27 | // alter_table_stmt 28 | // / analyze_stmt 29 | // / attach_stmt 30 | // / begin_stmt / commit_stmt 31 | // / create_index_stmt 32 | // / create_table_stmt 33 | // / create_trigger_stmt 34 | // / create_view_stmt 35 | // / create_virtual_table_stmt 36 | // / delete_stmt / delete_stmt_limited 37 | // / detach_stmt 38 | // / drop_index_stmt / drop_table_stmt / drop_trigger_stmt / drop_view_stmt 39 | // / insert_stmt 40 | // / pragma_stmt / reindex_stmt / release_stmt / rollback_stmt / savepoint_stmt 41 | // / select_stmt 42 | // / update_stmt / update_stmt_limited 43 | // / vacuum_stmt 44 | // ) ) 45 | // { return { explain: flatstr(explain), 46 | // stmt: stmt } } 47 | 48 | alter_table_stmt = 49 | ( ( ALTER TABLE table_ref ) 50 | ( RENAME TO new_table_name ) 51 | ( ADD ( COLUMN )? column_def ) ) 52 | 53 | analyze_stmt = 54 | ( ANALYZE ( database_name 55 | / table_or_index_name 56 | / ( database_name dot table_or_index_name ) )? ) 57 | 58 | attach_stmt = 59 | ( ATTACH ( DATABASE )? expr AS database_name ) 60 | 61 | begin_stmt = 62 | ( BEGIN ( DEFERRED / IMMEDIATE / EXCLUSIVE )? ( TRANSACTION )? ) 63 | 64 | commit_stmt = 65 | ( ( COMMIT / END ) ( TRANSACTION )? ) 66 | 67 | rollback_stmt = 68 | ( ROLLBACK ( TRANSACTION )? ( TO ( SAVEPOINT )? savepoint_name )? ) 69 | 70 | savepoint_stmt = 71 | ( SAVEPOINT savepoint_name ) 72 | 73 | release_stmt = 74 | ( RELEASE ( SAVEPOINT )? savepoint_name ) 75 | 76 | create_index_stmt = 77 | ( ( CREATE ( UNIQUE )? INDEX ( IF NOT EXISTS )? ) ( ( database_name dot )? index_name ON table_name lparen ( indexed_column comma )+ rparen ) ) 78 | 79 | indexed_column = 80 | ( column_name ( COLLATE collation_name )? ( ASC / DESC )? ) 81 | 82 | create_table_stmt = 83 | ( CREATE ( TEMP / TEMPORARY )? TABLE ( IF NOT EXISTS )? ) 84 | ( table_ref 85 | ( lparen ( column_def comma )+ ( comma table_constraint )+ rparen ) 86 | ( AS select_stmt ) ) 87 | 88 | column_def = 89 | ( column_name ( type_name )? ( column_constraint )+ ) 90 | 91 | type_name = 92 | ( name )+ 93 | ( ( lparen signed_number rparen ) 94 | / ( lparen signed_number comma signed_number rparen ) )? 95 | 96 | column_constraint = 97 | ( ( CONSTRAINT name )? 98 | ( ( PRIMARY KEY ( ASC / DESC )? conflict_clause ( AUTOINCREMENT )? ) 99 | / ( NOT NULL conflict_clause ) 100 | / ( UNIQUE conflict_clause ) 101 | / ( CHECK lparen expr rparen ) 102 | / ( DEFAULT ( signed_number / literal_value / ( lparen expr rparen ) ) ) 103 | / ( COLLATE collation_name ) 104 | / foreign_key_clause ) ) 105 | 106 | signed_number = 107 | ( ( plus / minus )? numeric_literal ) 108 | 109 | table_constraint = 110 | ( ( CONSTRAINT name )? ( ( ( ( PRIMARY KEY ) / UNIQUE ) lparen ( indexed_column comma )+ rparen conflict_clause ) / ( CHECK lparen expr rparen ) / ( FOREIGN KEY lparen ( column_name comma )+ rparen foreign_key_clause ) ) ) 111 | 112 | foreign_key_clause = 113 | ( ( REFERENCES foreign_table ( lparen ( column_name comma )+ rparen )? ) 114 | ( ( ( ON ( DELETE / UPDATE ) 115 | ( ( SET NULL ) 116 | / ( SET DEFAULT ) 117 | / CASCADE 118 | / RESTRICT 119 | / ( NO ACTION ) ) ) 120 | / ( MATCH name ) )+ )? 121 | ( ( NOT )? DEFERRABLE ( ( INITIALLY DEFERRED ) / ( INITIALLY IMMEDIATE ) )? )? ) 122 | 123 | conflict_clause = 124 | ( ( ON CONFLICT ( ROLLBACK / ABORT / FAIL / IGNORE / REPLACE ) ) )? 125 | 126 | create_trigger_stmt = 127 | ( ( CREATE ( TEMP / TEMPORARY )? TRIGGER ( IF NOT EXISTS )? ) 128 | ( ( database_name dot )? trigger_name ( BEFORE / AFTER / ( INSTEAD OF ) )? ) 129 | ( ( DELETE 130 | / INSERT 131 | / ( UPDATE ( OF ( column_name comma )+ )? ) ) ON table_name ) 132 | ( ( FOR EACH ROW )? ( WHEN expr )? ) 133 | ( BEGIN ( ( update_stmt 134 | / insert_stmt 135 | / delete_stmt 136 | / select_stmt ) semicolon )+ END ) ) 137 | 138 | create_view_stmt = 139 | ( ( CREATE ( TEMP / TEMPORARY )? VIEW ( IF NOT EXISTS )? ) 140 | ( ( database_name dot )? view_name AS select_stmt ) ) 141 | 142 | create_virtual_table_stmt = 143 | ( ( CREATE VIRTUAL TABLE table_ref ) 144 | ( USING module_name ( lparen ( module_argument comma )+ rparen )? ) ) 145 | 146 | delete_stmt = 147 | ( DELETE FROM qualified_table_name ( WHERE expr )? ) 148 | 149 | delete_stmt_limited = 150 | ( DELETE FROM qualified_table_name ( WHERE expr )? 151 | ( ( ( ORDER BY ( ordering_term comma )+ )? 152 | ( LIMIT expr ( ( OFFSET / comma ) expr )? ) ) )? ) 153 | 154 | detach_stmt = 155 | ( DETACH ( DATABASE )? database_name ) 156 | 157 | drop_index_stmt = 158 | ( DROP INDEX ( IF EXISTS )? ( database_name dot )? index_name ) 159 | 160 | drop_table_stmt = 161 | ( DROP TABLE ( IF EXISTS )? table_ref ) 162 | 163 | drop_trigger_stmt = 164 | ( DROP TRIGGER ( IF EXISTS )? ( database_name dot )? trigger_name ) 165 | 166 | drop_view_stmt = 167 | ( DROP VIEW ( IF EXISTS )? ( database_name dot )? view_name ) 168 | 169 | value = 170 | v: ( whitespace 171 | ( ( x: literal_value 172 | { return { literal: x } } ) 173 | / ( b: bind_parameter 174 | { return { bind: b } } ) 175 | / ( t: ( table_name dot column_name ) 176 | { return { column: t[2], table: t[1] } } ) 177 | / ( c: column_name 178 | { return { column: c } } ) 179 | / ( unary_operator expr ) 180 | / call_function 181 | / ( whitespace lparen expr whitespace rparen ) 182 | / ( CAST lparen expr AS type_name rparen ) 183 | / ( ( NOT ? EXISTS )? lparen select_stmt rparen ) 184 | / ( CASE expr ? ( WHEN expr THEN expr )+ ( ELSE expr )? END ) 185 | / raise_function ) ) 186 | { return v[1] } 187 | 188 | expr = 189 | e: ( whitespace 190 | ( ( value binary_operator expr ) 191 | / ( value COLLATE collation_name ) 192 | / ( value NOT ? ( LIKE / GLOB / REGEXP / MATCH ) expr ( ESCAPE expr )? ) 193 | / ( value ( ISNULL / NOTNULL / ( NOT NULL ) ) ) 194 | / ( value IS NOT ? expr ) 195 | / ( value NOT ? BETWEEN expr AND expr ) 196 | / ( value NOT ? IN ( ( lparen ( select_stmt / ( expr comma )+ )? rparen ) 197 | / table_ref ) ) 198 | / value ) ) 199 | { return e[1]; } 200 | 201 | 202 | call_function = 203 | ( function_name 204 | whitespace lparen 205 | ( ( DISTINCT ? ( expr (whitespace comma expr)* )+ ) 206 | / whitespace star )? 207 | whitespace rparen ) 208 | 209 | raise_function = 210 | ( RAISE lparen ( IGNORE / ( ( ROLLBACK / ABORT / FAIL ) comma error_message ) ) rparen ) 211 | 212 | literal_value = 213 | ( numeric_literal / string_literal / blob_literal 214 | / NULL / CURRENT_TIME / CURRENT_DATE / CURRENT_TIMESTAMP ) 215 | 216 | numeric_literal = 217 | digits:( ( ( ( digit )+ ( decimal_point ( digit )+ )? ) 218 | / ( decimal_point ( digit )+ ) ) 219 | ( E ( plus / minus )? ( digit )+ )? ) 220 | { var x = flatstr(digits); 221 | if (x.indexOf('.') >= 0) { 222 | return parseFloat(x); 223 | } 224 | return parseInt(x); 225 | } 226 | 227 | insert_stmt = 228 | ( ( ( INSERT ( OR ( ROLLBACK / ABORT / REPLACE / FAIL / IGNORE ) )? ) 229 | / REPLACE ) 230 | INTO 231 | table_ref 232 | ( ( ( lparen ( column_name ( comma column_name )* ) rparen )? 233 | ( ( VALUES lparen ( expr comma )+ rparen ) 234 | / select_stmt ) ) 235 | / ( DEFAULT VALUES ) ) ) 236 | 237 | pragma_stmt = 238 | ( PRAGMA ( database_name dot )? pragma_name 239 | ( ( equal pragma_value ) / ( lparen pragma_value rparen ) )? ) 240 | 241 | pragma_value = 242 | ( signed_number / name / string_literal ) 243 | 244 | reindex_stmt = 245 | ( REINDEX collation_name ( table_ref index_name ) ) 246 | 247 | select_stmt = 248 | ( select_cores: ( select_core 249 | ( sc: ( compound_operator select_core )* 250 | { var acc = []; 251 | for (var i = 0; i < sc.length; i++) { 252 | acc[i] = merge(sc[i][0], sc[i][1]); 253 | } 254 | return acc; 255 | } ) ) 256 | order_by: ( ( ORDER BY ordering_term ( whitespace comma ordering_term )* )? ) 257 | limit: ( ( LIMIT expr ( ( OFFSET / comma ) expr )? )? ) ) 258 | { var res = { stmt: "select", 259 | select_cores: flatten(select_cores, true) }; 260 | res = put_if_not_null(res, "order_by", nonempty(order_by)); 261 | res = put_if_not_null(res, "limit", nonempty(limit)); 262 | return res; 263 | } 264 | 265 | select_core = 266 | ( SELECT d: ( ( DISTINCT / ALL )? ) 267 | c: ( select_result 268 | ( cx: ( whitespace comma select_result )* 269 | { var acc = []; 270 | for (var i = 0; i < cx.length; i++) { 271 | acc[i] = cx[i][2]; 272 | } 273 | return acc; 274 | } ) ) 275 | f: ( j: ( ( FROM join_source )? ) 276 | { return j ? j[1] : [] } ) 277 | w: ( e: ( ( WHERE expr )? ) 278 | { return e ? e[1] : [] } ) 279 | g: ( GROUP BY ( ordering_term comma )+ ( HAVING expr )? )? ) 280 | { c[1].unshift(c[0]); 281 | var res = { results: c[1] }; 282 | res = put_if_not_null(res, "distinct", nonempty(flatstr(d))); 283 | res = put_if_not_null(res, "from", nonempty(f)); 284 | res = put_if_not_null(res, "where", nonempty(w)); 285 | res = put_if_not_null(res, "group_by", nonempty(g)); 286 | return res; 287 | } 288 | 289 | select_result = 290 | r: ( whitespace 291 | ( ( c: ( column_ref ( a: ( AS whitespace column_alias ) 292 | { return { alias: a[2] } } )? ) 293 | { return merge(c[1], c[0]) } ) 294 | / ( c: ( table_name dot star ) 295 | { return { table: c[0], 296 | column: '*' } } ) 297 | / ( star 298 | { return { column: '*' } } ) ) ) 299 | { return r[1] } 300 | 301 | join_source = 302 | s: ( whitespace single_source 303 | ( join_op whitespace single_source join_constraint )* ) 304 | { var acc = [s[1]]; 305 | var rest = s[2]; 306 | for (var i = 0; rest != null && i < rest.length; i++) { 307 | acc[acc.length] = merge(merge(rest[i][0], rest[i][2]), rest[i][3]); 308 | } 309 | return acc; 310 | } 311 | 312 | single_source = 313 | ( ( x: ( database_name dot table_name AS whitespace1 table_alias ) 314 | { return { database: x[0], table: x[2], alias: x[5] } } ) 315 | / ( x: ( database_name dot table_name ) 316 | { return { database: x[0], table: x[2] } } ) 317 | / ( x: ( table_name AS whitespace1 table_alias ) 318 | { return { table: x[0], alias: x[3] } } ) 319 | / ( x: table_name 320 | { return { table: x } } ) 321 | / ( s: ( ( t: ( table_ref ( a: ( AS whitespace1 table_alias ) 322 | { return { alias: a[2] } } )? ) 323 | { return merge(t[1], t[0]) } ) 324 | ( ( idx: ( INDEXED BY whitespace index_name ) 325 | { return { indexed_by: idx[3] } } ) 326 | / ( ( NOT INDEXED ) 327 | { return { indexed_by: null } } ) )? ) 328 | { return merge(s[1], s[0]) } ) 329 | / ( p: ( lparen select_stmt rparen 330 | ( a: ( AS whitespace table_alias ) 331 | { return { alias: a[2] } } )? ) 332 | { return merge(p[3], p[1]) } ) 333 | / ( j: ( lparen join_source rparen ) 334 | { return j[1] } ) 335 | ) 336 | 337 | join_op = 338 | r: ( ( ( ( whitespace comma ) 339 | { return "JOIN" } ) 340 | / ( j: ( NATURAL ? 341 | ( ( LEFT ( OUTER )? ) 342 | / INNER 343 | / CROSS )? 344 | JOIN ) 345 | { return flatstr(j) } ) ) ) 346 | { return { join_op: r } } 347 | 348 | join_constraint = 349 | r: ( ( ( ON expr ) 350 | / ( USING 351 | whitespace lparen 352 | ( whitespace column_name ( whitespace comma whitespace column_name )* ) 353 | whitespace rparen ) )? ) 354 | { return { join_constraint: nonempty(r) } } 355 | 356 | ordering_term = 357 | ( whitespace 358 | ( expr ( COLLATE collation_name )? ( ASC / DESC )? ) ) 359 | 360 | compound_operator = 361 | o: ( ( UNION ALL ? ) 362 | / INTERSECT 363 | / EXCEPT ) 364 | { return { compound_operator: flatstr(o) } } 365 | 366 | update_stmt = 367 | ( ( UPDATE ( OR ( ROLLBACK 368 | / ABORT 369 | / REPLACE 370 | / FAIL 371 | / IGNORE ) )? qualified_table_name ) 372 | ( SET ( ( column_name equal expr ) comma )+ ( WHERE expr )? ) ) 373 | 374 | update_stmt_limited = 375 | ( ( UPDATE ( OR ( ROLLBACK 376 | / ABORT 377 | / REPLACE 378 | / FAIL 379 | / IGNORE ) )? qualified_table_name ) 380 | ( SET ( ( column_name equal expr ) comma )+ ( WHERE expr )? ) 381 | ( ( ( ORDER BY ( ordering_term comma )+ )? 382 | ( LIMIT expr ( ( OFFSET / comma ) expr )? ) ) )? ) 383 | 384 | qualified_table_name = 385 | ( table_ref ( ( INDEXED BY index_name ) / ( NOT INDEXED ) )? ) 386 | 387 | table_ref = 388 | r: ( ( d: ( database_name dot ) 389 | { return { database: d[0] } } )? 390 | ( x: table_name 391 | { return { table: x } } ) ) 392 | { return merge(r[1], r[0]) } 393 | 394 | column_ref = 395 | r: ( ( t: ( table_name dot ) 396 | { return { table: t[0] } } )? 397 | ( x: column_name 398 | { return { column: x } } ) ) 399 | { return merge(r[1], r[0]) } 400 | 401 | vacuum_stmt = 402 | VACUUM 403 | 404 | comment_syntax = 405 | ( ( minusminus ( anything_except_newline )+ ( newline / end_of_input ) ) 406 | / ( comment_beg ( anything_except_comment_end )+ ( comment_end / end_of_input ) ) ) 407 | 408 | dot = '.' 409 | comma = ',' 410 | semicolon = ';' 411 | minusminus = '--' 412 | minus = '-' 413 | plus = '+' 414 | lparen = '(' 415 | rparen = ')' 416 | star = '*' 417 | newline = '\n' 418 | anything_except_newline = [^\n]* 419 | comment_beg = '/*' 420 | comment_end = '*/' 421 | anything_except_comment_end = .* & '*/' 422 | string_literal = '"' (escape_char / [^"])* '"' 423 | escape_char = '\\' . 424 | nil = '' 425 | 426 | whitespace = 427 | [ \t\n\r]* 428 | whitespace1 = 429 | [ \t\n\r]+ 430 | 431 | unary_operator = 432 | x: ( whitespace 433 | ( '-' / '+' / '~' / 'NOT') ) 434 | { return x[1] } 435 | 436 | binary_operator = 437 | x: ( whitespace 438 | ('||' 439 | / '*' / '/' / '%' 440 | / '+' / '-' 441 | / '<<' / '>>' / '&' / '|' 442 | / '<=' / '>=' 443 | / '<' / '>' 444 | / '=' / '==' / '!=' / '<>' 445 | / 'IS' / 'IS NOT' / 'IN' / 'LIKE' / 'GLOB' / 'MATCH' / 'REGEXP' 446 | / 'AND' 447 | / 'OR') ) 448 | { return x[1] } 449 | 450 | digit = [0-9] 451 | decimal_point = dot 452 | equal = '=' 453 | 454 | name = 455 | str:[A-Za-z0-9_]+ 456 | { return str.join('') } 457 | 458 | database_name = name 459 | table_name = name 460 | table_alias = name 461 | table_or_index_name = name 462 | new_table_name = name 463 | index_name = name 464 | column_name = name 465 | column_alias = name 466 | foreign_table = name 467 | savepoint_name = name 468 | collation_name = name 469 | trigger_name = name 470 | view_name = name 471 | module_name = name 472 | module_argument = name 473 | bind_parameter = 474 | '?' name 475 | function_name = name 476 | pragma_name = name 477 | 478 | error_message = string_literal 479 | 480 | CURRENT_TIME = 'now' 481 | CURRENT_DATE = 'now' 482 | CURRENT_TIMESTAMP = 'now' 483 | 484 | blob_literal = string_literal 485 | 486 | end_of_input = '' 487 | 488 | ABORT = whitespace1 "ABORT" 489 | ACTION = whitespace1 "ACTION" 490 | ADD = whitespace1 "ADD" 491 | AFTER = whitespace1 "AFTER" 492 | ALL = whitespace1 "ALL" 493 | ALTER = whitespace1 "ALTER" 494 | ANALYZE = whitespace1 "ANALYZE" 495 | AND = whitespace1 "AND" 496 | AS = whitespace1 "AS" 497 | ASC = whitespace1 "ASC" 498 | ATTACH = whitespace1 "ATTACH" 499 | AUTOINCREMENT = whitespace1 "AUTOINCREMENT" 500 | BEFORE = whitespace1 "BEFORE" 501 | BEGIN = whitespace1 "BEGIN" 502 | BETWEEN = whitespace1 "BETWEEN" 503 | BY = whitespace1 "BY" 504 | CASCADE = whitespace1 "CASCADE" 505 | CASE = whitespace1 "CASE" 506 | CAST = whitespace1 "CAST" 507 | CHECK = whitespace1 "CHECK" 508 | COLLATE = whitespace1 "COLLATE" 509 | COLUMN = whitespace1 "COLUMN" 510 | COMMIT = whitespace1 "COMMIT" 511 | CONFLICT = whitespace1 "CONFLICT" 512 | CONSTRAINT = whitespace1 "CONSTRAINT" 513 | CREATE = 514 | whitespace "CREATE" 515 | CROSS = whitespace1 "CROSS" 516 | DATABASE = whitespace1 "DATABASE" 517 | DEFAULT = whitespace1 "DEFAULT" 518 | DEFERRABLE = whitespace1 "DEFERRABLE" 519 | DEFERRED = whitespace1 "DEFERRED" 520 | DELETE = 521 | whitespace "DELETE" 522 | DESC = whitespace1 "DESC" 523 | DETACH = whitespace1 "DETACH" 524 | DISTINCT = whitespace1 "DISTINCT" 525 | DROP = whitespace1 "DROP" 526 | E = 527 | "E" 528 | EACH = whitespace1 "EACH" 529 | ELSE = whitespace1 "ELSE" 530 | END = whitespace1 "END" 531 | ESCAPE = whitespace1 "ESCAPE" 532 | EXCEPT = whitespace1 "EXCEPT" 533 | EXCLUSIVE = whitespace1 "EXCLUSIVE" 534 | EXISTS = whitespace1 "EXISTS" 535 | EXPLAIN = 536 | whitespace "EXPLAIN" 537 | FAIL = whitespace1 "FAIL" 538 | FOR = whitespace1 "FOR" 539 | FOREIGN = whitespace1 "FOREIGN" 540 | FROM = whitespace1 "FROM" 541 | GLOB = whitespace1 "GLOB" 542 | GROUP = whitespace1 "GROUP" 543 | HAVING = whitespace1 "HAVING" 544 | IF = whitespace1 "IF" 545 | IGNORE = whitespace1 "IGNORE" 546 | IMMEDIATE = whitespace1 "IMMEDIATE" 547 | IN = whitespace1 "IN" 548 | INDEX = whitespace1 "INDEX" 549 | INDEXED = whitespace1 "INDEXED" 550 | INITIALLY = whitespace1 "INITIALLY" 551 | INNER = whitespace1 "INNER" 552 | INSERT = 553 | whitespace "INSERT" 554 | INSTEAD = whitespace1 "INSTEAD" 555 | INTERSECT = whitespace1 "INTERSECT" 556 | INTO = whitespace1 "INTO" 557 | IS = whitespace1 "IS" 558 | ISNULL = whitespace1 "ISNULL" 559 | JOIN = whitespace1 "JOIN" 560 | KEY = whitespace1 "KEY" 561 | LEFT = whitespace1 "LEFT" 562 | LIKE = whitespace1 "LIKE" 563 | LIMIT = whitespace1 "LIMIT" 564 | MATCH = whitespace1 "MATCH" 565 | NATURAL = whitespace1 "NATURAL" 566 | NO = whitespace1 "NO" 567 | NOT = whitespace1 "NOT" 568 | NOTNULL = whitespace1 "NOTNULL" 569 | NULL = whitespace1 "NULL" 570 | OF = whitespace1 "OF" 571 | OFFSET = whitespace1 "OFFSET" 572 | ON = whitespace1 "ON" 573 | OR = whitespace1 "OR" 574 | ORDER = whitespace1 "ORDER" 575 | OUTER = whitespace1 "OUTER" 576 | PLAN = whitespace1 "PLAN" 577 | PRAGMA = whitespace1 "PRAGMA" 578 | PRIMARY = whitespace1 "PRIMARY" 579 | QUERY = whitespace1 "QUERY" 580 | RAISE = whitespace1 "RAISE" 581 | REFERENCES = whitespace1 "REFERENCES" 582 | REGEXP = whitespace1 "REGEXP" 583 | REINDEX = whitespace1 "REINDEX" 584 | RELEASE = whitespace1 "RELEASE" 585 | RENAME = whitespace1 "RENAME" 586 | REPLACE = 587 | whitespace "REPLACE" 588 | RESTRICT = whitespace1 "RESTRICT" 589 | ROLLBACK = whitespace1 "ROLLBACK" 590 | ROW = whitespace1 "ROW" 591 | SAVEPOINT = whitespace1 "SAVEPOINT" 592 | SELECT = 593 | whitespace "SELECT" 594 | SET = whitespace1 "SET" 595 | TABLE = whitespace1 "TABLE" 596 | TEMP = whitespace1 "TEMP" 597 | TEMPORARY = whitespace1 "TEMPORARY" 598 | THEN = whitespace1 "THEN" 599 | TO = whitespace1 "TO" 600 | TRANSACTION = whitespace1 "TRANSACTION" 601 | TRIGGER = whitespace1 "TRIGGER" 602 | UNION = whitespace1 "UNION" 603 | UNIQUE = whitespace1 "UNIQUE" 604 | UPDATE = 605 | whitespace "UPDATE" 606 | USING = whitespace1 "USING" 607 | VACUUM = whitespace1 "VACUUM" 608 | VALUES = whitespace1 "VALUES" 609 | VIEW = whitespace1 "VIEW" 610 | VIRTUAL = whitespace1 "VIRTUAL" 611 | WHEN = whitespace1 "WHEN" 612 | WHERE = whitespace1 "WHERE" 613 | -------------------------------------------------------------------------------- /views/index.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 115 | 116 |
          117 |

          Test Suite

          118 |

          119 |
          120 |

          121 |
            122 |
            test markup
            123 |
            124 | 125 | 126 | --------------------------------------------------------------------------------