diff --git a/.gitignore b/.gitignore index 772f6063..20bd5ece 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ index.ids deploy_key index.html +/node_modules/ diff --git a/.travis.yml b/.travis.yml index dd244e65..0e777890 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ -language: generic +language: node_js +node_js: + - "node" script: - bash ./deploy.sh diff --git a/check-grammar.js b/check-grammar.js new file mode 100644 index 00000000..cbd9acc3 --- /dev/null +++ b/check-grammar.js @@ -0,0 +1,50 @@ +"use strict"; +const Grammar = require("syntax-cli").Grammar; +const LLParsingTable = require("syntax-cli").LLParsingTable; +const jsdom = require("jsdom"); +const fs = require("fs"); + +function getRulesFromDOM(window) { + let rules = window.document.querySelectorAll("pre.grammar[id]"); + return [].map.call(rules, pre => pre.textContent); +} + +function processRules(rules) { + const REGEXP = /(\s*:\n\s*)|\b(integer|float|identifier|string|whitespace|comment|other)\b|(\s*\n\s*)|(ε)/g; + return rules.map(rule => { + return rule.trim().replace(REGEXP, m => { + if (/^(integer|float|identifier|string|whitespace|comment|other)$/.test(m)) { + return m.toUpperCase(); + } + if (/:\n/.test(m)) { return "\n : "; } + if (/\n/.test(m)) { return "\n | "; } + if (/ε/.test(m)) { return "/* epsilon */"; } + }) + "\n ;"; + }); +} + +function toBNF(rules) { + return "\n%%\n\n" + processRules(rules).join("\n"); +} + +let path = process.argv[2]; +let html = fs.readFileSync(path, "utf8"); +let dom = new jsdom.JSDOM(html); +let rules = getRulesFromDOM(dom.window); +let bnf = toBNF(rules); +let data = Grammar.dataFromString(bnf, "bnf"); +let grammar = Grammar.fromData(data, { mode: "LL1" }); +let table = new LLParsingTable({ grammar: grammar }); +let conflicts = table.getConflicts(); +if (table.hasConflicts()) { + console.log("The WebIDL grammar is NOT LL(1) due to the following conflicts:"); + Object.keys(conflicts).forEach((nt, i) => { + let conflict = conflicts[nt]; + let str = Object.keys(conflict).map(k => ` * ${k} (${ conflict[k] })`).join("\n"); + console.log(` ${i+1}. ${ nt }:\n${ str }`); + }); + process.exit(1); +} else { + console.log("The WebIDL grammar is LL(1)."); + process.exit(0); +} diff --git a/deploy.sh b/deploy.sh index 1d0fbc32..7e767dbe 100755 --- a/deploy.sh +++ b/deploy.sh @@ -6,6 +6,7 @@ TARGET_BRANCH="gh-pages" function doCompile { curl https://api.csswg.org/bikeshed/ -F file=@index.bs > out/index.html + node ./check-grammar.js ./out/index.html } # Pull requests and commits to other branches shouldn't try to deploy, just build to verify diff --git a/index.bs b/index.bs index 628de3cb..ffe995a5 100644 --- a/index.bs +++ b/index.bs @@ -1974,8 +1974,13 @@ The following extended attributes are applicable to operations:
     Argument :
-        ExtendedAttributeList "optional" TypeWithExtendedAttributes ArgumentName Default
-        ExtendedAttributeList Type Ellipsis ArgumentName
+        ExtendedAttributeList ArgumentRest
+
+ +
+    ArgumentRest :
+        "optional" TypeWithExtendedAttributes ArgumentName Default
+        Type Ellipsis ArgumentName
 
@@ -4232,8 +4237,13 @@ No [=extended attributes=] are applicable to dictionaries.
 
 
     DictionaryMember :
-        ExtendedAttributeList "required" TypeWithExtendedAttributes identifier Default ";"
-        ExtendedAttributeList Type identifier Default ";"
+        ExtendedAttributeList DictionaryMemberRest
+
+ +
+    DictionaryMemberRest :
+        "required" TypeWithExtendedAttributes identifier Default ";"
+        Type identifier Default ";"
 
@@ -5083,8 +5093,7 @@ type.
 
 
     TypeWithExtendedAttributes :
-        ExtendedAttributeList SingleType
-        ExtendedAttributeList UnionType Null
+        ExtendedAttributeList Type
 
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..854dafcb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+  "private": true,
+  "description": "Checks that the WebIDL grammar is LL(1)",
+  "devDependencies": {
+    "jsdom": "^11.3.0",
+    "syntax-cli": "0.0.97"
+  }
+}