From 3afe26c32b1fcf8dca290aeab9871ed8847408ac Mon Sep 17 00:00:00 2001 From: James Graham Date: Mon, 24 Apr 2017 16:10:02 +0100 Subject: [PATCH] Squashed 'resources/webidl2/' changes from bd216bcd55..88c5c5b6bb 88c5c5b6bb chore(package): bump version 8b3bc38a24 chore(package): bump version c64fe13a42 chore(package): update deps a0bccdf485 fix "Trailing comma in extended attribute" error (#63) 62633a1025 Remove support for MapClass (no longer valid in WebIDL) (#62) 3a7d1bbe3e Merge pull request #61 from w3c/annotated_types 49ea372ba3 Fix spaces in JSON output for updated test 7488ee661f docs(README): fix markdown. 5530e9fecc Adapt test to support for annotated types a387b679ca Add support for annotated types ffe9400aba Merge pull request #58 from SaschaNaz/namespace 9a154bb963 implement namespace 830d19191f chore(package): bump version e5771f11e8 Revert "style: use U.S. English" 8c712c9cb8 chore(package): bump version 8ee9a85ad8 style: use U.S. English bc7b7c6897 style(lib): beautify code 96699646e1 chore(package): bump version 86ea904cbf chore(package): update dependencies 44d7b22851 Merge pull request #57 from TimothyGu/develop 0b5300ec76 Make writer less strict about generic idlType's type 064622bf59 Add support for records fcb88fb1ac Merge pull request #50 from w3c/refactor a23527043e refactor(webidl.js): move WebIDLParseError c64e97c3b8 Merge pull request #49 from w3c/amd_compat d1e704bd40 feat(lib): add AMD export support (closes #48) 5458831df8 style(writer.js): fix whitespace 481c29427a style(webidl2.js): fix whitespace ed24e286ee chore(package): update deps 2668a8be5f docs(README): fixup markdown highlighting 9df3430244 Merge pull request #47 from mkwtys/fix-default ec61c0a096 Merge branch 'halton-replace_expect' into develop fb59b13c2c Really be deterministic 128d7335bb Remove reliance on undeterministic object key order 7bf2df2276 Confirm support for union in typedef 1b5b83438e Remove test of now invalid nested typdef 2d8cd93bd4 Update location of expect.js library 1ed22de9b5 Correct jsondiffpatch location. a2e2f8e902 Bump microtime to 2.1.1 23b63723e9 Expand writer support 9cc1281a1c Accept wider (but still incomplete) set of allowed syntax for extended attributes. cb04f7777c Identifier allow `-` d9b4989425 Merge pull request #39 from markandrus/develop 1ab320df8d Merge pull request #37 from artillery/develop 524000bfd6 Merge pull request #31 from othree/in-draft 99127088c2 Fix `"default": undefined` 191685b05f Really be deterministic 300b1e0fae Remove reliance on undeterministic object key order 61c8e65035 Confirm support for union in typedef f0cd831c6c Remove test of now invalid nested typdef 77b5e3df87 Update location of expect.js library f84b8e684c Merge pull request #44 from halton/fix_jsondiffpatch b52117cf05 Merge pull request #43 from halton/bump_microtime 65382bf16b Replace expect.js with expct be6c2e21e9 Correct jsondiffpatch location. 0b0fa3e92d Bump microtime to 2.1.1 e470735423 Expand writer support 9e2ccfc940 Accept wider (but still incomplete) set of allowed syntax for extended attributes. 989591f675 Allow trailing comma in enum 9385d07a54 Identifier allow `-` git-subtree-dir: resources/webidl2 git-subtree-split: 88c5c5b6bb675d0d95ae3ec4db3258768d0c8fc0 --- README.md | 726 +++--- lib/webidl2.js | 2028 +++++++++-------- lib/writer.js | 494 ++-- package.json | 10 +- test/invalid.js | 12 +- test/invalid/idl/record-key.widl | 3 + test/invalid/json/record-key.json | 4 + test/syntax.js | 6 +- test/syntax/idl/extended-attributes.widl | 7 +- test/syntax/idl/map.widl | 5 - test/syntax/idl/namespace.widl | 10 + test/syntax/idl/record.widl | 8 + test/syntax/idl/typedef-nested.widl | 22 - test/syntax/idl/typedef-union.idl | 4 + test/syntax/idl/uniontype.widl | 1 + test/syntax/json/extended-attributes.json | 35 +- .../json/identifier-qualified-names.json | 3 +- test/syntax/json/map.json | 29 - test/syntax/json/namespace.json | 134 ++ test/syntax/json/record.json | 184 ++ test/syntax/json/typedef-nested.json | 226 -- test/syntax/json/typedef-union.json | 49 + test/syntax/json/typedef.json | 17 +- test/syntax/json/uniontype.json | 200 +- test/web/make-web-tests.js | 5 +- test/web/run-tests.js | 10 +- 26 files changed, 2287 insertions(+), 1945 deletions(-) create mode 100644 test/invalid/idl/record-key.widl create mode 100644 test/invalid/json/record-key.json delete mode 100644 test/syntax/idl/map.widl create mode 100644 test/syntax/idl/namespace.widl create mode 100644 test/syntax/idl/record.widl delete mode 100644 test/syntax/idl/typedef-nested.widl create mode 100644 test/syntax/idl/typedef-union.idl delete mode 100644 test/syntax/json/map.json create mode 100644 test/syntax/json/namespace.json create mode 100644 test/syntax/json/record.json delete mode 100644 test/syntax/json/typedef-nested.json create mode 100644 test/syntax/json/typedef-union.json diff --git a/README.md b/README.md index f7d03fcdef30c3..5d128ed27ca87c 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,15 @@ [![NPM version](https://badge.fury.io/js/webidl2.png)](http://badge.fury.io/js/webidl2) -Purpose -======= +## Purpose This is a parser for the [WebIDL](http://dev.w3.org/2006/webapi/WebIDL/) language. If you don't know what that is, then you probably don't need it. It is meant to be used both in Node and in the browser (the parser likely works in other JS environments, but not the test suite). -What of v1? ------------ +### What of v1? + There was a previous incarnation of this project. I had written it in the most quick and dirty manner that was handy because I required it as a dependency in an experiment. As these things tend to happen, some people started using that, which then had to be @@ -20,65 +19,72 @@ maintained. But since it was not built on solid foundations, it was painful to k up to date with the specification, which is a bit of a moving target. So I started from scratch. Compared to the previous version (which used a parser generator) -this one is about 6x less code (which translates to 4x smaller minified or 2x smaller +this one is about 6x less code (which translates to 4x smaller minified or 2x smaller minizipped) and 4x faster. The test suite is reasonably complete (95% coverage), much more than previously. This version is up to date with WebIDL, rather than a couple years' behind. It also has *far* better error reporting. -The AST you get from parsing is very similar to the one you got in v1, but some adjustments -have been made in order to be more systematic, and to map better to what's actually in the spec +The AST you get from parsing is very similar to the one you got in v1, but some adjustments +have been made in order to be more systematic, and to map better to what's actually in the spec now. If you used v1, you will need to tweak your code but the result ought to be simpler and -you ought to be able to be a fair bit less defensive against irregularities in the way +you ought to be able to be a fair bit less defensive against irregularities in the way information is represented. -Installation -============ +## Installation Just the usual. For Node: - npm install webidl2 - +```Bash +npm install webidl2 +``` + In the browser: - +```HTML + +``` + +## Documentation -Documentation -============= The API to WebIDL2 is trivial: you parse a string of WebIDL and it returns a syntax tree. -Parsing -------- +### Parsing + In Node, that happens with: - var WebIDL2 = require("webidl2"); - var tree = WebIDL2.parse("string of WebIDL"); +```JS +var WebIDL2 = require("webidl2"); +var tree = WebIDL2.parse("string of WebIDL"); +``` In the browser: +```HTML + + +``` - - - -Advanced Parsing ----------------- +### Advanced Parsing `parse()` can optionally accept a second parameter, an options object, which can be used to modify parsing behavior. The following options are recognized: -```javascript +```JS { - allowNestedTypedefs: false # + allowNestedTypedefs: false } ``` + And their meanings are as follows: -* `allowNestedTypedefs`: Boolean indicating whether the parser should accept `typedef`s as valid members of `interface`s. This is non-standard syntax and therefore the default is `false`. +* `allowNestedTypedefs`: Boolean indicating whether the parser should accept `typedef`s as valid members of `interface`s. +This is non-standard syntax and therefore the default is `false`. + +### Errors -Errors ------- When there is a syntax error in the WebIDL, it throws an exception object with the following properties: @@ -91,8 +97,8 @@ properties: The exception also has a `toString()` method that hopefully should produce a decent error message. -AST (Abstract Syntax Tree) --------------------------- +### AST (Abstract Syntax Tree) + The `parse()` method returns a tree object representing the parse tree of the IDL. Comment and white space are not represented in the AST. @@ -106,35 +112,34 @@ This structure is used in many other places (operation return types, argument ty It captures a WebIDL type with a number of options. Types look like this and are typically attached to a field called `idlType`: - { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "void" - } +```JS +{ + "array": false, + "generic": null, + "idlType": "void", + "nullable": false, + "union": false, +} +``` Where the fields are as follows: -* `sequence`: Boolean indicating whether this is a sequence or not. Deprecated. Use - `generic` instead. -* `generic`: String indicating the generic type (e.g. "Promise", "sequence"). `null` - otherwise. -* `nullable`: Boolean indicating whether this is nullable or not. * `array`: Either `false` to indicate that it is not an array, or a number for the level of array nesting. -* `union`: Boolean indicating whether this is a union type or not. +* `generic`: String indicating the generic type (e.g. "Promise", "sequence"). `null` + otherwise. * `idlType`: Can be different things depending on context. In most cases, this will just be a string with the type name. But the reason this field isn't called "typeName" is because it can take more complex values. If the type is a union, then this contains an array of the types it unites. If it is a generic type, it contains the IDL type description for the type in the sequence, the eventual value of the promise, etc. +* `nullable`: Boolean indicating whether this is nullable or not. +* `union`: Boolean indicating whether this is a union type or not. #### Interactions between `nullable` and `array` A more complex data model for our AST would likely represent `Foo[][][]` as a series of -nested types four levels deep with three anonymous array types eventually containing a +nested types four levels deep with three anonymous array types eventually containing a `Foo` type. But experience shows that such structures are cumbersome to use, and so we have a simpler model in which the depth of the array is specified with the `array` field. @@ -167,22 +172,23 @@ a `?` at the end. ### Interface Interfaces look like this: - { - "type": "interface", - "name": "Animal", - "partial": false, - "members": [...], - "inheritance": null, - "extAttrs": [...] - }, - { - "type": "interface", - "name": "Human", - "partial": false, - "members": [...], - "inheritance": "Animal", - "extAttrs": [...] - } +```JS +{ + "type": "interface", + "name": "Animal", + "partial": false, + "members": [...], + "inheritance": null, + "extAttrs": [...] +}, { + "type": "interface", + "name": "Human", + "partial": false, + "members": [...], + "inheritance": "Animal", + "extAttrs": [...] +} +``` The fields are as follows: @@ -204,20 +210,22 @@ their `type` field is "callback interface". A callback looks like this: - { - "type": "callback", - "name": "AsyncOperationCallback", - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "void" - }, - "arguments": [...], - "extAttrs": [] - } +```JS +{ + "type": "callback", + "name": "AsyncOperationCallback", + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "void" + }, + "arguments": [...], + "extAttrs": [] +} +``` The fields are as follows: @@ -231,33 +239,33 @@ The fields are as follows: A dictionary looks like this: - { - "type": "dictionary", - "name": "PaintOptions", - "partial": false, - "members": [ - { - "type": "field", - "name": "fillPattern", - "required": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": true, - "array": false, - "union": false, - "idlType": "DOMString" - }, - "extAttrs": [], - "default": { - "type": "string", - "value": "black" - } - } - ], - "inheritance": null, - "extAttrs": [] +```JS +{ + "type": "dictionary", + "name": "PaintOptions", + "partial": false, + "members": [{ + "type": "field", + "name": "fillPattern", + "required": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": true, + "array": false, + "union": false, + "idlType": "DOMString" + }, + "extAttrs": [], + "default": { + "type": "string", + "value": "black" } + }], + "inheritance": null, + "extAttrs": [] +} +``` The fields are as follows: @@ -281,27 +289,27 @@ All the members are fields as follows: An exception looks like this: - { - "type": "exception", - "name": "HierarchyRequestError", - "members": [ - { - "type": "field", - "name": "code", - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "unsigned short" - }, - "extAttrs": [] - } - ], - "inheritance": "DOMException", - "extAttrs": [] - } +```JS +{ + "type": "exception", + "name": "HierarchyRequestError", + "members": [{ + "type": "field", + "name": "code", + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "unsigned short" + }, + "extAttrs": [] + }], + "inheritance": "DOMException", + "extAttrs": [] +} +``` The fields are as follows: @@ -322,16 +330,18 @@ Members that aren't [constants](#constants) have the following fields: An enum looks like this: - { - "type": "enum", - "name": "MealType", - "values": [ - "rice", - "noodles", - "other" - ], - "extAttrs": [] - } +```JS +{ + "type": "enum", + "name": "MealType", + "values": [ + "rice", + "noodles", + "other" + ], + "extAttrs": [] +} +``` The fields are as follows: @@ -344,27 +354,30 @@ The fields are as follows: A typedef looks like this: - { - "type": "typedef", - "typeExtAttrs": [], - "idlType": { - "sequence": true, - "generic": "sequence", - "nullable": false, - "array": false, - "union": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Point" - } - }, - "name": "PointSequence", - "extAttrs": [] +```JS +{ + "type": "typedef", + "typeExtAttrs": [], + "idlType": { + "sequence": true, + "generic": "sequence", + "nullable": false, + "array": false, + "union": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Point" } + }, + "name": "PointSequence", + "extAttrs": [] +} +``` + The fields are as follows: @@ -379,12 +392,14 @@ type rather than to the typedef as a whole. An implements definition looks like this: - { - "type": "implements", - "target": "Node", - "implements": "EventTarget", - "extAttrs": [] - } +```JS +{ + "type": "implements", + "target": "Node", + "implements": "EventTarget", + "extAttrs": [] +} +``` The fields are as follows: @@ -396,43 +411,42 @@ The fields are as follows: ### Operation Member An operation looks like this: - - { - "type": "operation", - "getter": false, - "setter": false, - "creator": false, - "deleter": false, - "legacycaller": false, - "static": false, - "stringifier": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "void" - }, - "name": "intersection", - "arguments": [ - { - "optional": false, - "variadic": true, - "extAttrs": [], - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "long" - }, - "name": "ints" - } - ], - "extAttrs": [] - } +```JS +{ + "type": "operation", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "void" + }, + "name": "intersection", + "arguments": [{ + "optional": false, + "variadic": true, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "long" + }, + "name": "ints" + }], + "extAttrs": [] +} +``` The fields are as follows: @@ -453,24 +467,26 @@ The fields are as follows: An attribute member looks like this: - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "RegExp" - }, - "name": "regexp", - "extAttrs": [] - } - +```JS +{ + "type": "attribute", + "static": false, + "stringifier": false, + "inherit": false, + "readonly": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "RegExp" + }, + "name": "regexp", + "extAttrs": [] +} +``` + The fields are as follows: * `type`: Always "attribute". @@ -486,17 +502,19 @@ The fields are as follows: A constant member looks like this: - { - "type": "const", - "nullable": false, - "idlType": "boolean", - "name": "DEBUG", - "value": { - "type": "boolean", - "value": false - }, - "extAttrs": [] - } +```JS +{ + "type": "const", + "nullable": false, + "idlType": "boolean", + "name": "DEBUG", + "value": { + "type": "boolean", + "value": false + }, + "extAttrs": [] +} +``` The fields are as follows: @@ -512,60 +530,63 @@ The fields are as follows: Serializers come in many shapes, which are best understood by looking at the examples below that map the IDL to the produced AST. - // serializer; - { - "type": "serializer", - "extAttrs": [] - } +```JS +// serializer; +{ + "type": "serializer", + "extAttrs": [] +} - // serializer DOMString serialize(); - { - "type": "serializer", - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "DOMString" - }, - "operation": { - "name": "serialize", - "arguments": [] - }, - "extAttrs": [] - } +// serializer DOMString serialize(); +{ + "type": "serializer", + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "DOMString" + }, + "operation": { + "name": "serialize", + "arguments": [] + }, + "extAttrs": [] +} - // serializer = { from, to, amount, description }; - { - "type": "serializer", - "patternMap": true, - "names": [ - "from", - "to", - "amount", - "description" - ], - "extAttrs": [] - } +// serializer = { from, to, amount, description }; +{ + "type": "serializer", + "patternMap": true, + "names": [ + "from", + "to", + "amount", + "description" + ], + "extAttrs": [] +} - // serializer = number; - { - "type": "serializer", - "name": "number", - "extAttrs": [] - } +// serializer = number; +{ + "type": "serializer", + "name": "number", + "extAttrs": [] +} - // serializer = [ name, number ]; - { - "type": "serializer", - "patternList": true, - "names": [ - "name", - "number" - ], - "extAttrs": [] - } +// serializer = [ name, number ]; +{ + "type": "serializer", + "patternList": true, + "names": [ + "name", + "number" + ], + "extAttrs": [] +} + +``` The common fields are as follows: @@ -598,26 +619,28 @@ Finally, if the serializer is a named serializer: Iterator members look like this - { - "type": "iterator", - "getter": false, - "setter": false, - "creator": false, - "deleter": false, - "legacycaller": false, - "static": false, - "stringifier": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Session2" - }, - "iteratorObject": "SessionIterator", - "extAttrs": [] - } +```JS +{ + "type": "iterator", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Session2" + }, + "iteratorObject": "SessionIterator", + "extAttrs": [] +} +``` * `type`: Always "iterator". * `iteratorObject`: The string on the right-hand side; absent if there isn't one. @@ -627,22 +650,24 @@ Iterator members look like this The arguments (e.g. for an operation) look like this: - "arguments": [ - { - "optional": false, - "variadic": true, - "extAttrs": [], - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "long" - }, - "name": "ints" - } - ] +```JS +{ + "arguments": [{ + "optional": false, + "variadic": true, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "long" + }, + "name": "ints" + }] +} +``` The fields are as follows: @@ -656,16 +681,18 @@ The fields are as follows: Extended attributes are arrays of items that look like this: - "extAttrs": [ - { - "name": "TreatNullAs", - "arguments": null, - "rhs": { - "type": "identifier", - "value": "EmptyString" - } - } - ] +```JS +{ + "extAttrs": [{ + "name": "TreatNullAs", + "arguments": null, + "rhs": { + "type": "identifier", + "value": "EmptyString" + } + }] +} +``` The fields are as follows: @@ -700,12 +727,14 @@ For Infinity: These appear as members of interfaces that look like this: - { - "type": "maplike", // or "legacyiterable" / "iterable" / "setlike" - "idlType": /* One or two types */, - "readonly": false, // only for maplike and setlike - "extAttrs": [] - } +```JS +{ + "type": "maplike", // or "legacyiterable" / "iterable" / "setlike" + "idlType": /* One or two types */ , + "readonly": false, // only for maplike and setlike + "extAttrs": [] +} +``` The fields are as follows: @@ -715,46 +744,51 @@ The fields are as follows: * `extAttrs`: A list of [extended attributes](#extended-attributes). -Testing -======= +## Testing In order to run the tests you need to ensure that the widlproc submodule inside `test` is -initialised and up to date: +initialized and up to date: - git submodule init - git submodule update +```Bash +git submodule init +git submodule update +``` + +### Running -Running -------- The test runs with mocha and expect.js. Normally, running mocha in the root directory should be enough once you're set up. -Coverage --------- +### Coverage + Current test coverage, as documented in `coverage.html`, is 95%. You can run your own coverage analysis with: - jscoverage lib lib-cov - +```Bash +jscoverage lib lib-cov +``` + That will create the lib-cov directory with instrumented code; the test suite knows to use that if needed. You can then run the tests with: - JSCOV=1 mocha --reporter html-cov > coverage.html +```Bash +JSCOV=1 mocha --reporter html-cov > coverage.html +``` Note that I've been getting weirdly overescaped results from the html-cov reporter, so you might wish to try this instead: - JSCOV=1 mocha --reporter html-cov | sed "s/<//g" | sed "s/"/\"/g" > coverage.html +```Bash +JSCOV=1 mocha --reporter html-cov | sed "s/<//g" | sed "s/"/\"/g" > coverage.html +``` +### Browser tests -Browser tests -------------- In order to test in the browser, get inside `test/web` and run `make-web-tests.js`. This will generate a `browser-tests.html` file that you can open in a browser. As of this writing tests pass in the latest Firefox, Chrome, Opera, and Safari. Testing on IE and older versions will happen progressively. -TODO -==== +## TODO * add some tests to address coverage limitations * add a push API for processors that need to process things like comments diff --git a/lib/webidl2.js b/lib/webidl2.js index 9e504fc6e1fdef..0e76174a0836f2 100644 --- a/lib/webidl2.js +++ b/lib/webidl2.js @@ -1,1012 +1,1084 @@ +(function() { + var tokenise = function(str) { + var tokens = [], + re = { + "float": /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/, + "integer": /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/, + "identifier": /^[A-Z_a-z][0-9A-Z_a-z-]*/, + "string": /^"[^"]*"/, + "whitespace": /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/, + "other": /^[^\t\n\r 0-9A-Z_a-z]/ + }, + types = ["float", "integer", "identifier", "string", "whitespace", "other"]; + while (str.length > 0) { + var matched = false; + for (var i = 0, n = types.length; i < n; i++) { + var type = types[i]; + str = str.replace(re[type], function(tok) { + tokens.push({ type: type, value: tok }); + matched = true; + return ""; + }); + if (matched) break; + } + if (matched) continue; + throw new Error("Token stream not progressing"); + } + return tokens; + }; + function WebIDLParseError(str, line, input, tokens) { + this.message = str; + this.line = line; + this.input = input; + this.tokens = tokens; + }; -(function () { - var tokenise = function (str) { - var tokens = [] - , re = { - "float": /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/ - , "integer": /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/ - , "identifier": /^[A-Z_a-z][0-9A-Z_a-z]*/ - , "string": /^"[^"]*"/ - , "whitespace": /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/ - , "other": /^[^\t\n\r 0-9A-Z_a-z]/ - } - , types = [] - ; - for (var k in re) types.push(k); - while (str.length > 0) { + WebIDLParseError.prototype.toString = function() { + return this.message + ", line " + this.line + " (tokens: '" + this.input + "')\n" + + JSON.stringify(this.tokens, null, 4); + }; + + var parse = function(tokens, opt) { + var line = 1; + tokens = tokens.slice(); + + var FLOAT = "float", + INT = "integer", + ID = "identifier", + STR = "string", + OTHER = "other"; + + var error = function(str) { + var tok = ""; + var numTokens = 0; + var maxTokens = 5; + while (numTokens < maxTokens && tokens.length > numTokens) { + tok += tokens[numTokens].value; + numTokens++; + } + throw new WebIDLParseError(str, line, tok, tokens.slice(0, 5)); + }; + + var last_token = null; + + var consume = function(type, value) { + if (!tokens.length || tokens[0].type !== type) return; + if (typeof value === "undefined" || tokens[0].value === value) { + last_token = tokens.shift(); + if (type === ID) last_token.value = last_token.value.replace(/^_/, ""); + return last_token; + } + }; + + var ws = function() { + if (!tokens.length) return; + if (tokens[0].type === "whitespace") { + var t = tokens.shift(); + t.value.replace(/\n/g, function(m) { line++; + return m; }); + return t; + } + }; + + var all_ws = function(store, pea) { // pea == post extended attribute, tpea = same for types + var t = { type: "whitespace", value: "" }; + while (true) { + var w = ws(); + if (!w) break; + t.value += w.value; + } + if (t.value.length > 0) { + if (store) { + var w = t.value, + re = { + "ws": /^([\t\n\r ]+)/, + "line-comment": /^\/\/(.*)\n?/m, + "multiline-comment": /^\/\*((?:.|\n|\r)*?)\*\// + }, + wsTypes = []; + for (var k in re) wsTypes.push(k); + while (w.length) { var matched = false; - for (var i = 0, n = types.length; i < n; i++) { - var type = types[i]; - str = str.replace(re[type], function (tok) { - tokens.push({ type: type, value: tok }); - matched = true; - return ""; - }); - if (matched) break; + for (var i = 0, n = wsTypes.length; i < n; i++) { + var type = wsTypes[i]; + w = w.replace(re[type], function(tok, m1) { + store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 }); + matched = true; + return ""; + }); + if (matched) break; } if (matched) continue; - throw new Error("Token stream not progressing"); + throw new Error("Surprising white space construct."); // this shouldn't happen + } } - return tokens; + return t; + } }; - - var parse = function (tokens, opt) { - var line = 1; - tokens = tokens.slice(); - - var FLOAT = "float" - , INT = "integer" - , ID = "identifier" - , STR = "string" - , OTHER = "other" - ; - - var WebIDLParseError = function (str, line, input, tokens) { - this.message = str; - this.line = line; - this.input = input; - this.tokens = tokens; - }; - WebIDLParseError.prototype.toString = function () { - return this.message + ", line " + this.line + " (tokens: '" + this.input + "')\n" + - JSON.stringify(this.tokens, null, 4); - }; - - var error = function (str) { - var tok = "", numTokens = 0, maxTokens = 5; - while (numTokens < maxTokens && tokens.length > numTokens) { - tok += tokens[numTokens].value; - numTokens++; - } - throw new WebIDLParseError(str, line, tok, tokens.slice(0, 5)); - }; - - var last_token = null; - - var consume = function (type, value) { - if (!tokens.length || tokens[0].type !== type) return; - if (typeof value === "undefined" || tokens[0].value === value) { - last_token = tokens.shift(); - if (type === ID) last_token.value = last_token.value.replace(/^_/, ""); - return last_token; - } - }; - - var ws = function () { - if (!tokens.length) return; - if (tokens[0].type === "whitespace") { - var t = tokens.shift(); - t.value.replace(/\n/g, function (m) { line++; return m; }); - return t; - } - }; - - var all_ws = function (store, pea) { // pea == post extended attribute, tpea = same for types - var t = { type: "whitespace", value: "" }; - while (true) { - var w = ws(); - if (!w) break; - t.value += w.value; - } - if (t.value.length > 0) { - if (store) { - var w = t.value - , re = { - "ws": /^([\t\n\r ]+)/ - , "line-comment": /^\/\/(.*)\n?/m - , "multiline-comment": /^\/\*((?:.|\n|\r)*?)\*\// - } - , wsTypes = [] - ; - for (var k in re) wsTypes.push(k); - while (w.length) { - var matched = false; - for (var i = 0, n = wsTypes.length; i < n; i++) { - var type = wsTypes[i]; - w = w.replace(re[type], function (tok, m1) { - store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 }); - matched = true; - return ""; - }); - if (matched) break; - } - if (matched) continue; - throw new Error("Surprising white space construct."); // this shouldn't happen - } - } - return t; - } - }; - - var integer_type = function () { - var ret = ""; - all_ws(); - if (consume(ID, "unsigned")) ret = "unsigned "; - all_ws(); - if (consume(ID, "short")) return ret + "short"; - if (consume(ID, "long")) { - ret += "long"; - all_ws(); - if (consume(ID, "long")) return ret + " long"; - return ret; - } - if (ret) error("Failed to parse integer type"); - }; - - var float_type = function () { - var ret = ""; - all_ws(); - if (consume(ID, "unrestricted")) ret = "unrestricted "; - all_ws(); - if (consume(ID, "float")) return ret + "float"; - if (consume(ID, "double")) return ret + "double"; - if (ret) error("Failed to parse float type"); - }; - - var primitive_type = function () { - var num_type = integer_type() || float_type(); - if (num_type) return num_type; - all_ws(); - if (consume(ID, "boolean")) return "boolean"; - if (consume(ID, "byte")) return "byte"; - if (consume(ID, "octet")) return "octet"; - }; - - var const_value = function () { - if (consume(ID, "true")) return { type: "boolean", value: true }; - if (consume(ID, "false")) return { type: "boolean", value: false }; - if (consume(ID, "null")) return { type: "null" }; - if (consume(ID, "Infinity")) return { type: "Infinity", negative: false }; - if (consume(ID, "NaN")) return { type: "NaN" }; - var ret = consume(FLOAT) || consume(INT); - if (ret) return { type: "number", value: 1 * ret.value }; - var tok = consume(OTHER, "-"); - if (tok) { - if (consume(ID, "Infinity")) return { type: "Infinity", negative: true }; - else tokens.unshift(tok); - } - }; - - var type_suffix = function (obj) { - while (true) { - all_ws(); - if (consume(OTHER, "?")) { - if (obj.nullable) error("Can't nullable more than once"); - obj.nullable = true; - } - else if (consume(OTHER, "[")) { - all_ws(); - consume(OTHER, "]") || error("Unterminated array type"); - if (!obj.array) { - obj.array = 1; - obj.nullableArray = [obj.nullable]; - } - else { - obj.array++; - obj.nullableArray.push(obj.nullable); - } - obj.nullable = false; - } - else return; - } - }; - - var single_type = function () { - var prim = primitive_type() - , ret = { sequence: false, generic: null, nullable: false, array: false, union: false } - , name - , value - ; - if (prim) { - ret.idlType = prim; - } - else if (name = consume(ID)) { - value = name.value; - all_ws(); - // Generic types - if (consume(OTHER, "<")) { - // backwards compat - if (value === "sequence") { - ret.sequence = true; - } - ret.generic = value; - ret.idlType = type() || error("Error parsing generic type " + value); - all_ws(); - if (!consume(OTHER, ">")) error("Unterminated generic type " + value); - type_suffix(ret); - return ret; - } - else { - ret.idlType = value; - } - } - else { - return; - } - type_suffix(ret); - if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable"); - return ret; - }; - - var union_type = function () { - all_ws(); - if (!consume(OTHER, "(")) return; - var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] }; - var fst = type() || error("Union type with no content"); - ret.idlType.push(fst); - while (true) { - all_ws(); - if (!consume(ID, "or")) break; - var typ = type() || error("No type after 'or' in union type"); - ret.idlType.push(typ); - } - if (!consume(OTHER, ")")) error("Unterminated union type"); - type_suffix(ret); - return ret; - }; - - var type = function () { - return single_type() || union_type(); - }; - - var argument = function (store) { - var ret = { optional: false, variadic: false }; - ret.extAttrs = extended_attrs(store); - all_ws(store, "pea"); - var opt_token = consume(ID, "optional"); - if (opt_token) { - ret.optional = true; - all_ws(); - } - ret.idlType = type(); - if (!ret.idlType) { - if (opt_token) tokens.unshift(opt_token); - return; - } - var type_token = last_token; - if (!ret.optional) { - all_ws(); - if (tokens.length >= 3 && - tokens[0].type === "other" && tokens[0].value === "." && - tokens[1].type === "other" && tokens[1].value === "." && - tokens[2].type === "other" && tokens[2].value === "." - ) { - tokens.shift(); - tokens.shift(); - tokens.shift(); - ret.variadic = true; - } - } - all_ws(); - var name = consume(ID); - if (!name) { - if (opt_token) tokens.unshift(opt_token); - tokens.unshift(type_token); - return; - } - ret.name = name.value; - if (ret.optional) { - all_ws(); - ret["default"] = default_(); - } - return ret; - }; - - var argument_list = function (store) { - var ret = [] - , arg = argument(store ? ret : null) - ; - if (!arg) return; - ret.push(arg); - while (true) { - all_ws(store ? ret : null); - if (!consume(OTHER, ",")) return ret; - var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list"); - ret.push(nxt); - } - }; - - var type_pair = function () { - all_ws(); - var k = type(); - if (!k) return; - all_ws() - if (!consume(OTHER, ",")) return; - all_ws(); - var v = type(); - if (!v) return; - return [k, v]; - }; - - var simple_extended_attr = function (store) { - all_ws(); - var name = consume(ID); - if (!name) return; - var ret = { - name: name.value - , "arguments": null - }; - all_ws(); - var eq = consume(OTHER, "="); - if (eq) { - var rhs; - all_ws(); - if (rhs = consume(ID)) { - ret.rhs = rhs - } - else if (consume(OTHER, "(")) { - // [Exposed=(Window,Worker)] - rhs = []; - var id = consume(ID); - if (id) { - rhs = [id.value]; - } - identifiers(rhs); - consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair"); - ret.rhs = { - type: "identifier-list", - value: rhs - }; - } - if (!ret.rhs) return error("No right hand side to extended attribute assignment"); - } - all_ws(); - if (consume(OTHER, "(")) { - var args, pair; - // [Constructor(DOMString str)] - if (args = argument_list(store)) { - ret["arguments"] = args; - } - // [MapClass(DOMString, DOMString)] - else if (pair = type_pair()) { - ret.typePair = pair; - } - // [Constructor()] - else { - ret["arguments"] = []; - } - all_ws(); - consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair"); - } - return ret; - }; - - // Note: we parse something simpler than the official syntax. It's all that ever - // seems to be used - var extended_attrs = function (store) { - var eas = []; - all_ws(store); - if (!consume(OTHER, "[")) return eas; - eas[0] = simple_extended_attr(store) || error("Extended attribute with not content"); - all_ws(); - while (consume(OTHER, ",")) { - eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute")); - all_ws(); - } - consume(OTHER, "]") || error("No end of extended attribute"); - return eas; - }; - - var default_ = function () { - all_ws(); - if (consume(OTHER, "=")) { - all_ws(); - var def = const_value(); - if (def) { - return def; - } - else if (consume(OTHER, "[")) { - if (!consume(OTHER, "]")) error("Default sequence value must be empty"); - return { type: "sequence", value: [] }; - } - else { - var str = consume(STR) || error("No value for default"); - str.value = str.value.replace(/^"/, "").replace(/"$/, ""); - return str; - } - } - }; - - var const_ = function (store) { - all_ws(store, "pea"); - if (!consume(ID, "const")) return; - var ret = { type: "const", nullable: false }; - all_ws(); - var typ = primitive_type(); - if (!typ) { - typ = consume(ID) || error("No type for const"); - typ = typ.value; - } - ret.idlType = typ; - all_ws(); - if (consume(OTHER, "?")) { - ret.nullable = true; - all_ws(); - } - var name = consume(ID) || error("No name for const"); - ret.name = name.value; - all_ws(); - consume(OTHER, "=") || error("No value assignment for const"); - all_ws(); - var cnt = const_value(); - if (cnt) ret.value = cnt; - else error("No value for const"); - all_ws(); - consume(OTHER, ";") || error("Unterminated const"); - return ret; - }; - - var inheritance = function () { - all_ws(); - if (consume(OTHER, ":")) { - all_ws(); - var inh = consume(ID) || error ("No type in inheritance"); - return inh.value; - } - }; - - var operation_rest = function (ret, store) { - all_ws(); - if (!ret) ret = {}; - var name = consume(ID); - ret.name = name ? name.value : null; - all_ws(); - consume(OTHER, "(") || error("Invalid operation"); - ret["arguments"] = argument_list(store) || []; - all_ws(); - consume(OTHER, ")") || error("Unterminated operation"); - all_ws(); - consume(OTHER, ";") || error("Unterminated operation"); - return ret; - }; - - var callback = function (store) { - all_ws(store, "pea"); - var ret; - if (!consume(ID, "callback")) return; - all_ws(); - var tok = consume(ID, "interface"); - if (tok) { - tokens.unshift(tok); - ret = interface_(); - ret.type = "callback interface"; - return ret; - } - var name = consume(ID) || error("No name for callback"); - ret = { type: "callback", name: name.value }; - all_ws(); - consume(OTHER, "=") || error("No assignment in callback"); - all_ws(); - ret.idlType = return_type(); - all_ws(); - consume(OTHER, "(") || error("No arguments in callback"); - ret["arguments"] = argument_list(store) || []; - all_ws(); - consume(OTHER, ")") || error("Unterminated callback"); - all_ws(); - consume(OTHER, ";") || error("Unterminated callback"); - return ret; - }; - var attribute = function (store) { - all_ws(store, "pea"); - var grabbed = [] - , ret = { - type: "attribute" - , "static": false - , stringifier: false - , inherit: false - , readonly: false - }; - if (consume(ID, "static")) { - ret["static"] = true; - grabbed.push(last_token); - } - else if (consume(ID, "stringifier")) { - ret.stringifier = true; - grabbed.push(last_token); - } - var w = all_ws(); - if (w) grabbed.push(w); - if (consume(ID, "inherit")) { - if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit"); - ret.inherit = true; - grabbed.push(last_token); - var w = all_ws(); - if (w) grabbed.push(w); - } - if (consume(ID, "readonly")) { - ret.readonly = true; - grabbed.push(last_token); - var w = all_ws(); - if (w) grabbed.push(w); - } - if (!consume(ID, "attribute")) { - tokens = grabbed.concat(tokens); - return; - } - all_ws(); - ret.idlType = type() || error("No type in attribute"); - if (ret.idlType.sequence) error("Attributes cannot accept sequence types"); - all_ws(); - var name = consume(ID) || error("No name in attribute"); - ret.name = name.value; - all_ws(); - consume(OTHER, ";") || error("Unterminated attribute"); - return ret; - }; - - var return_type = function () { - var typ = type(); - if (!typ) { - if (consume(ID, "void")) { - return "void"; - } - else error("No return type"); - } - return typ; - }; - - var operation = function (store) { - all_ws(store, "pea"); - var ret = { - type: "operation" - , getter: false - , setter: false - , creator: false - , deleter: false - , legacycaller: false - , "static": false - , stringifier: false - }; - while (true) { - all_ws(); - if (consume(ID, "getter")) ret.getter = true; - else if (consume(ID, "setter")) ret.setter = true; - else if (consume(ID, "creator")) ret.creator = true; - else if (consume(ID, "deleter")) ret.deleter = true; - else if (consume(ID, "legacycaller")) ret.legacycaller = true; - else break; - } - if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) { - all_ws(); - ret.idlType = return_type(); - operation_rest(ret, store); - return ret; - } - if (consume(ID, "static")) { - ret["static"] = true; - ret.idlType = return_type(); - operation_rest(ret, store); - return ret; - } - else if (consume(ID, "stringifier")) { - ret.stringifier = true;- - all_ws(); - if (consume(OTHER, ";")) return ret; - ret.idlType = return_type(); - operation_rest(ret, store); - return ret; - } - ret.idlType = return_type(); + var integer_type = function() { + var ret = ""; + all_ws(); + if (consume(ID, "unsigned")) ret = "unsigned "; + all_ws(); + if (consume(ID, "short")) return ret + "short"; + if (consume(ID, "long")) { + ret += "long"; + all_ws(); + if (consume(ID, "long")) return ret + " long"; + return ret; + } + if (ret) error("Failed to parse integer type"); + }; + + var float_type = function() { + var ret = ""; + all_ws(); + if (consume(ID, "unrestricted")) ret = "unrestricted "; + all_ws(); + if (consume(ID, "float")) return ret + "float"; + if (consume(ID, "double")) return ret + "double"; + if (ret) error("Failed to parse float type"); + }; + + var primitive_type = function() { + var num_type = integer_type() || float_type(); + if (num_type) return num_type; + all_ws(); + if (consume(ID, "boolean")) return "boolean"; + if (consume(ID, "byte")) return "byte"; + if (consume(ID, "octet")) return "octet"; + }; + + var const_value = function() { + if (consume(ID, "true")) return { type: "boolean", value: true }; + if (consume(ID, "false")) return { type: "boolean", value: false }; + if (consume(ID, "null")) return { type: "null" }; + if (consume(ID, "Infinity")) return { type: "Infinity", negative: false }; + if (consume(ID, "NaN")) return { type: "NaN" }; + var ret = consume(FLOAT) || consume(INT); + if (ret) return { type: "number", value: 1 * ret.value }; + var tok = consume(OTHER, "-"); + if (tok) { + if (consume(ID, "Infinity")) return { type: "Infinity", negative: true }; + else tokens.unshift(tok); + } + }; + + var type_suffix = function(obj) { + while (true) { + all_ws(); + if (consume(OTHER, "?")) { + if (obj.nullable) error("Can't nullable more than once"); + obj.nullable = true; + } else if (consume(OTHER, "[")) { + all_ws(); + consume(OTHER, "]") || error("Unterminated array type"); + if (!obj.array) { + obj.array = 1; + obj.nullableArray = [obj.nullable]; + } else { + obj.array++; + obj.nullableArray.push(obj.nullable); + } + obj.nullable = false; + } else return; + } + }; + + var single_type = function() { + var prim = primitive_type(), + ret = { sequence: false, generic: null, nullable: false, array: false, union: false }, + name, value; + if (prim) { + ret.idlType = prim; + } else if (name = consume(ID)) { + value = name.value; + all_ws(); + // Generic types + if (consume(OTHER, "<")) { + // backwards compat + if (value === "sequence") { + ret.sequence = true; + } + ret.generic = value; + var types = []; + do { all_ws(); - if (consume(ID, "iterator")) { - all_ws(); - ret.type = "iterator"; - if (consume(ID, "object")) { - ret.iteratorObject = "object"; - } - else if (consume(OTHER, "=")) { - all_ws(); - var name = consume(ID) || error("No right hand side in iterator"); - ret.iteratorObject = name.value; - } - all_ws(); - consume(OTHER, ";") || error("Unterminated iterator"); - return ret; - } - else { - operation_rest(ret, store); - return ret; - } - }; - - var identifiers = function (arr) { - while (true) { - all_ws(); - if (consume(OTHER, ",")) { - all_ws(); - var name = consume(ID) || error("Trailing comma in identifiers list"); - arr.push(name.value); - } - else break; - } - }; - - var serialiser = function (store) { - all_ws(store, "pea"); - if (!consume(ID, "serializer")) return; - var ret = { type: "serializer" }; + types.push(type() || error("Error parsing generic type " + value)); all_ws(); - if (consume(OTHER, "=")) { - all_ws(); - if (consume(OTHER, "{")) { - ret.patternMap = true; - all_ws(); - var id = consume(ID); - if (id && id.value === "getter") { - ret.names = ["getter"]; - } - else if (id && id.value === "inherit") { - ret.names = ["inherit"]; - identifiers(ret.names); - } - else if (id) { - ret.names = [id.value]; - identifiers(ret.names); - } - else { - ret.names = []; - } - all_ws(); - consume(OTHER, "}") || error("Unterminated serializer pattern map"); - } - else if (consume(OTHER, "[")) { - ret.patternList = true; - all_ws(); - var id = consume(ID); - if (id && id.value === "getter") { - ret.names = ["getter"]; - } - else if (id) { - ret.names = [id.value]; - identifiers(ret.names); - } - else { - ret.names = []; - } - all_ws(); - consume(OTHER, "]") || error("Unterminated serializer pattern list"); - } - else { - var name = consume(ID) || error("Invalid serializer"); - ret.name = name.value; - } - all_ws(); - consume(OTHER, ";") || error("Unterminated serializer"); - return ret; - } - else if (consume(OTHER, ";")) { - // noop, just parsing - } - else { - ret.idlType = return_type(); - all_ws(); - ret.operation = operation_rest(null, store); + } + while (consume(OTHER, ",")); + if (value === "sequence") { + if (types.length !== 1) error("A sequence must have exactly one subtype"); + } else if (value === "record") { + if (types.length !== 2) error("A record must have exactly two subtypes"); + if (!/^(DOMString|USVString|ByteString)$/.test(types[0].idlType)) { + error("Record key must be DOMString, USVString, or ByteString"); } - return ret; + } + ret.idlType = types.length === 1 ? types[0] : types; + all_ws(); + if (!consume(OTHER, ">")) error("Unterminated generic type " + value); + type_suffix(ret); + return ret; + } else { + ret.idlType = value; + } + } else { + return; + } + type_suffix(ret); + if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable"); + return ret; + }; + + var union_type = function() { + all_ws(); + if (!consume(OTHER, "(")) return; + var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] }; + var fst = type_with_extended_attributes() || error("Union type with no content"); + ret.idlType.push(fst); + while (true) { + all_ws(); + if (!consume(ID, "or")) break; + var typ = type_with_extended_attributes() || error("No type after 'or' in union type"); + ret.idlType.push(typ); + } + if (!consume(OTHER, ")")) error("Unterminated union type"); + type_suffix(ret); + return ret; + }; + + var type = function() { + return single_type() || union_type(); + }; + + var type_with_extended_attributes = function() { + var extAttrs = extended_attrs(); + var ret = single_type() || union_type(); + if (extAttrs.length && ret) ret.extAttrs = extAttrs; + return ret; + }; + + var argument = function(store) { + var ret = { optional: false, variadic: false }; + ret.extAttrs = extended_attrs(store); + all_ws(store, "pea"); + var opt_token = consume(ID, "optional"); + if (opt_token) { + ret.optional = true; + all_ws(); + } + ret.idlType = type_with_extended_attributes(); + if (!ret.idlType) { + if (opt_token) tokens.unshift(opt_token); + return; + } + var type_token = last_token; + if (!ret.optional) { + all_ws(); + if (tokens.length >= 3 && + tokens[0].type === "other" && tokens[0].value === "." && + tokens[1].type === "other" && tokens[1].value === "." && + tokens[2].type === "other" && tokens[2].value === "." + ) { + tokens.shift(); + tokens.shift(); + tokens.shift(); + ret.variadic = true; + } + } + all_ws(); + var name = consume(ID); + if (!name) { + if (opt_token) tokens.unshift(opt_token); + tokens.unshift(type_token); + return; + } + ret.name = name.value; + if (ret.optional) { + all_ws(); + var dflt = default_(); + if (typeof dflt !== "undefined") { + ret["default"] = dflt; + } + } + return ret; + }; + + var argument_list = function(store) { + var ret = [], + arg = argument(store ? ret : null); + if (!arg) return; + ret.push(arg); + while (true) { + all_ws(store ? ret : null); + if (!consume(OTHER, ",")) return ret; + var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list"); + ret.push(nxt); + } + }; + + var simple_extended_attr = function(store) { + all_ws(); + var name = consume(ID); + if (!name) return; + var ret = { + name: name.value, + "arguments": null + }; + all_ws(); + var eq = consume(OTHER, "="); + if (eq) { + var rhs; + all_ws(); + if (rhs = consume(ID)) { + ret.rhs = rhs; + } else if (rhs = consume(FLOAT)) { + ret.rhs = rhs; + } else if (rhs = consume(INT)) { + ret.rhs = rhs; + } else if (rhs = consume(STR)) { + ret.rhs = rhs; + } else if (consume(OTHER, "(")) { + // [Exposed=(Window,Worker)] + rhs = []; + var id = consume(ID); + if (id) { + rhs = [id.value]; + } + identifiers(rhs); + consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair"); + ret.rhs = { + type: "identifier-list", + value: rhs + }; + } + if (!ret.rhs) return error("No right hand side to extended attribute assignment"); + } + all_ws(); + if (consume(OTHER, "(")) { + var args, pair; + // [Constructor(DOMString str)] + if (args = argument_list(store)) { + ret["arguments"] = args; + } + // [Constructor()] + else { + ret["arguments"] = []; + } + all_ws(); + consume(OTHER, ")") || error("Unexpected token in extended attribute argument list"); + } + return ret; + }; + + // Note: we parse something simpler than the official syntax. It's all that ever + // seems to be used + var extended_attrs = function(store) { + var eas = []; + all_ws(store); + if (!consume(OTHER, "[")) return eas; + eas[0] = simple_extended_attr(store) || error("Extended attribute with not content"); + all_ws(); + while (consume(OTHER, ",")) { + if (eas.length) { + eas.push(simple_extended_attr(store)); + } else { + eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute")); + } + } + consume(OTHER, "]") || error("No end of extended attribute"); + return eas; + }; + + var default_ = function() { + all_ws(); + if (consume(OTHER, "=")) { + all_ws(); + var def = const_value(); + if (def) { + return def; + } else if (consume(OTHER, "[")) { + if (!consume(OTHER, "]")) error("Default sequence value must be empty"); + return { type: "sequence", value: [] }; + } else { + var str = consume(STR) || error("No value for default"); + str.value = str.value.replace(/^"/, "").replace(/"$/, ""); + return str; + } + } + }; + + var const_ = function(store) { + all_ws(store, "pea"); + if (!consume(ID, "const")) return; + var ret = { type: "const", nullable: false }; + all_ws(); + var typ = primitive_type(); + if (!typ) { + typ = consume(ID) || error("No type for const"); + typ = typ.value; + } + ret.idlType = typ; + all_ws(); + if (consume(OTHER, "?")) { + ret.nullable = true; + all_ws(); + } + var name = consume(ID) || error("No name for const"); + ret.name = name.value; + all_ws(); + consume(OTHER, "=") || error("No value assignment for const"); + all_ws(); + var cnt = const_value(); + if (cnt) ret.value = cnt; + else error("No value for const"); + all_ws(); + consume(OTHER, ";") || error("Unterminated const"); + return ret; + }; + + var inheritance = function() { + all_ws(); + if (consume(OTHER, ":")) { + all_ws(); + var inh = consume(ID) || error("No type in inheritance"); + return inh.value; + } + }; + + var operation_rest = function(ret, store) { + all_ws(); + if (!ret) ret = {}; + var name = consume(ID); + ret.name = name ? name.value : null; + all_ws(); + consume(OTHER, "(") || error("Invalid operation"); + ret["arguments"] = argument_list(store) || []; + all_ws(); + consume(OTHER, ")") || error("Unterminated operation"); + all_ws(); + consume(OTHER, ";") || error("Unterminated operation"); + return ret; + }; + + var callback = function(store) { + all_ws(store, "pea"); + var ret; + if (!consume(ID, "callback")) return; + all_ws(); + var tok = consume(ID, "interface"); + if (tok) { + tokens.unshift(tok); + ret = interface_(); + ret.type = "callback interface"; + return ret; + } + var name = consume(ID) || error("No name for callback"); + ret = { type: "callback", name: name.value }; + all_ws(); + consume(OTHER, "=") || error("No assignment in callback"); + all_ws(); + ret.idlType = return_type(); + all_ws(); + consume(OTHER, "(") || error("No arguments in callback"); + ret["arguments"] = argument_list(store) || []; + all_ws(); + consume(OTHER, ")") || error("Unterminated callback"); + all_ws(); + consume(OTHER, ";") || error("Unterminated callback"); + return ret; + }; + + var attribute = function(store) { + all_ws(store, "pea"); + var grabbed = [], + ret = { + type: "attribute", + "static": false, + stringifier: false, + inherit: false, + readonly: false }; + if (consume(ID, "static")) { + ret["static"] = true; + grabbed.push(last_token); + } else if (consume(ID, "stringifier")) { + ret.stringifier = true; + grabbed.push(last_token); + } + var w = all_ws(); + if (w) grabbed.push(w); + if (consume(ID, "inherit")) { + if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit"); + ret.inherit = true; + grabbed.push(last_token); + var w = all_ws(); + if (w) grabbed.push(w); + } + if (consume(ID, "readonly")) { + ret.readonly = true; + grabbed.push(last_token); + var w = all_ws(); + if (w) grabbed.push(w); + } + var rest = attribute_rest(ret); + if (!rest) { + tokens = grabbed.concat(tokens); + } + return rest; + }; + + var attribute_rest = function(ret) { + if (!consume(ID, "attribute")) { + return; + } + all_ws(); + ret.idlType = type_with_extended_attributes() || error("No type in attribute"); + if (ret.idlType.sequence) error("Attributes cannot accept sequence types"); + if (ret.idlType.generic === "record") error("Attributes cannot accept record types"); + all_ws(); + var name = consume(ID) || error("No name in attribute"); + ret.name = name.value; + all_ws(); + consume(OTHER, ";") || error("Unterminated attribute"); + return ret; + }; + + var return_type = function() { + var typ = type(); + if (!typ) { + if (consume(ID, "void")) { + return "void"; + } else error("No return type"); + } + return typ; + }; - var iterable_type = function() { - if (consume(ID, "iterable")) return "iterable"; - else if (consume(ID, "legacyiterable")) return "legacyiterable"; - else if (consume(ID, "maplike")) return "maplike"; - else if (consume(ID, "setlike")) return "setlike"; - else return; + var operation = function(store) { + all_ws(store, "pea"); + var ret = { + type: "operation", + getter: false, + setter: false, + creator: false, + deleter: false, + legacycaller: false, + "static": false, + stringifier: false + }; + while (true) { + all_ws(); + if (consume(ID, "getter")) ret.getter = true; + else if (consume(ID, "setter")) ret.setter = true; + else if (consume(ID, "creator")) ret.creator = true; + else if (consume(ID, "deleter")) ret.deleter = true; + else if (consume(ID, "legacycaller")) ret.legacycaller = true; + else break; + } + if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) { + all_ws(); + ret.idlType = return_type(); + operation_rest(ret, store); + return ret; + } + if (consume(ID, "static")) { + ret["static"] = true; + ret.idlType = return_type(); + operation_rest(ret, store); + return ret; + } else if (consume(ID, "stringifier")) { + ret.stringifier = true; - + all_ws(); + if (consume(OTHER, ";")) return ret; + ret.idlType = return_type(); + operation_rest(ret, store); + return ret; + } + ret.idlType = return_type(); + all_ws(); + if (consume(ID, "iterator")) { + all_ws(); + ret.type = "iterator"; + if (consume(ID, "object")) { + ret.iteratorObject = "object"; + } else if (consume(OTHER, "=")) { + all_ws(); + var name = consume(ID) || error("No right hand side in iterator"); + ret.iteratorObject = name.value; } + all_ws(); + consume(OTHER, ";") || error("Unterminated iterator"); + return ret; + } else { + operation_rest(ret, store); + return ret; + } + }; + + var identifiers = function(arr) { + while (true) { + all_ws(); + if (consume(OTHER, ",")) { + all_ws(); + var name = consume(ID) || error("Trailing comma in identifiers list"); + arr.push(name.value); + } else break; + } + }; - var readonly_iterable_type = function() { - if (consume(ID, "maplike")) return "maplike"; - else if (consume(ID, "setlike")) return "setlike"; - else return; + var serialiser = function(store) { + all_ws(store, "pea"); + if (!consume(ID, "serializer")) return; + var ret = { type: "serializer" }; + all_ws(); + if (consume(OTHER, "=")) { + all_ws(); + if (consume(OTHER, "{")) { + ret.patternMap = true; + all_ws(); + var id = consume(ID); + if (id && id.value === "getter") { + ret.names = ["getter"]; + } else if (id && id.value === "inherit") { + ret.names = ["inherit"]; + identifiers(ret.names); + } else if (id) { + ret.names = [id.value]; + identifiers(ret.names); + } else { + ret.names = []; + } + all_ws(); + consume(OTHER, "}") || error("Unterminated serializer pattern map"); + } else if (consume(OTHER, "[")) { + ret.patternList = true; + all_ws(); + var id = consume(ID); + if (id && id.value === "getter") { + ret.names = ["getter"]; + } else if (id) { + ret.names = [id.value]; + identifiers(ret.names); + } else { + ret.names = []; + } + all_ws(); + consume(OTHER, "]") || error("Unterminated serializer pattern list"); + } else { + var name = consume(ID) || error("Invalid serializer"); + ret.name = name.value; } + all_ws(); + consume(OTHER, ";") || error("Unterminated serializer"); + return ret; + } else if (consume(OTHER, ";")) { + // noop, just parsing + } else { + ret.idlType = return_type(); + all_ws(); + ret.operation = operation_rest(null, store); + } + return ret; + }; - var iterable = function (store) { - all_ws(store, "pea"); - var grabbed = [], - ret = {type: null, idlType: null, readonly: false}; - if (consume(ID, "readonly")) { - ret.readonly = true; - grabbed.push(last_token); - var w = all_ws(); - if (w) grabbed.push(w); - } - var consumeItType = ret.readonly ? readonly_iterable_type : iterable_type; + var iterable_type = function() { + if (consume(ID, "iterable")) return "iterable"; + else if (consume(ID, "legacyiterable")) return "legacyiterable"; + else if (consume(ID, "maplike")) return "maplike"; + else if (consume(ID, "setlike")) return "setlike"; + else return; + }; - var ittype = consumeItType(); - if (!ittype) { - tokens = grabbed.concat(tokens); - return; - } + var readonly_iterable_type = function() { + if (consume(ID, "maplike")) return "maplike"; + else if (consume(ID, "setlike")) return "setlike"; + else return; + }; - var secondTypeRequired = ittype === "maplike"; - var secondTypeAllowed = secondTypeRequired || ittype === "iterable"; - ret.type = ittype; - if (ret.type !== 'maplike' && ret.type !== 'setlike') - delete ret.readonly; - all_ws(); - if (consume(OTHER, "<")) { - ret.idlType = type() || error("Error parsing " + ittype + " declaration"); - all_ws(); - if (secondTypeAllowed) { - var type2 = null; - if (consume(OTHER, ",")) { - all_ws(); - type2 = type(); - all_ws(); - } - if (type2) - ret.idlType = [ret.idlType, type2]; - else if (secondTypeRequired) - error("Missing second type argument in " + ittype + " declaration"); - } - if (!consume(OTHER, ">")) error("Unterminated " + ittype + " declaration"); - all_ws(); - if (!consume(OTHER, ";")) error("Missing semicolon after " + ittype + " declaration"); - } - else - error("Error parsing " + ittype + " declaration"); - - return ret; - } - - var interface_ = function (isPartial, store) { - all_ws(isPartial ? null : store, "pea"); - if (!consume(ID, "interface")) return; + var iterable = function(store) { + all_ws(store, "pea"); + var grabbed = [], + ret = { type: null, idlType: null, readonly: false }; + if (consume(ID, "readonly")) { + ret.readonly = true; + grabbed.push(last_token); + var w = all_ws(); + if (w) grabbed.push(w); + } + var consumeItType = ret.readonly ? readonly_iterable_type : iterable_type; + + var ittype = consumeItType(); + if (!ittype) { + tokens = grabbed.concat(tokens); + return; + } + + var secondTypeRequired = ittype === "maplike"; + var secondTypeAllowed = secondTypeRequired || ittype === "iterable"; + ret.type = ittype; + if (ret.type !== 'maplike' && ret.type !== 'setlike') + delete ret.readonly; + all_ws(); + if (consume(OTHER, "<")) { + ret.idlType = type_with_extended_attributes() || error("Error parsing " + ittype + " declaration"); + all_ws(); + if (secondTypeAllowed) { + var type2 = null; + if (consume(OTHER, ",")) { all_ws(); - var name = consume(ID) || error("No name for interface"); - var mems = [] - , ret = { - type: "interface" - , name: name.value - , partial: false - , members: mems - }; - if (!isPartial) ret.inheritance = inheritance() || null; + type2 = type_with_extended_attributes(); all_ws(); - consume(OTHER, "{") || error("Bodyless interface"); - while (true) { - all_ws(store ? mems : null); - if (consume(OTHER, "}")) { - all_ws(); - consume(OTHER, ";") || error("Missing semicolon after interface"); - return ret; - } - var ea = extended_attrs(store ? mems : null); - all_ws(); - var cnt = const_(store ? mems : null); - if (cnt) { - cnt.extAttrs = ea; - ret.members.push(cnt); - continue; - } - var mem = (opt.allowNestedTypedefs && typedef(store ? mems : null)) || - iterable(store ? mems : null) || - serialiser(store ? mems : null) || - attribute(store ? mems : null) || - operation(store ? mems : null) || - error("Unknown member"); - mem.extAttrs = ea; - ret.members.push(mem); - } - }; - - var partial = function (store) { - all_ws(store, "pea"); - if (!consume(ID, "partial")) return; - var thing = dictionary(true, store) || - interface_(true, store) || - error("Partial doesn't apply to anything"); - thing.partial = true; - return thing; + } + if (type2) + ret.idlType = [ret.idlType, type2]; + else if (secondTypeRequired) + error("Missing second type argument in " + ittype + " declaration"); + } + if (!consume(OTHER, ">")) error("Unterminated " + ittype + " declaration"); + all_ws(); + if (!consume(OTHER, ";")) error("Missing semicolon after " + ittype + " declaration"); + } else + error("Error parsing " + ittype + " declaration"); + + return ret; + }; + + var interface_ = function(isPartial, store) { + all_ws(isPartial ? null : store, "pea"); + if (!consume(ID, "interface")) return; + all_ws(); + var name = consume(ID) || error("No name for interface"); + var mems = [], + ret = { + type: "interface", + name: name.value, + partial: false, + members: mems }; - - var dictionary = function (isPartial, store) { - all_ws(isPartial ? null : store, "pea"); - if (!consume(ID, "dictionary")) return; - all_ws(); - var name = consume(ID) || error("No name for dictionary"); - var mems = [] - , ret = { - type: "dictionary" - , name: name.value - , partial: false - , members: mems - }; - if (!isPartial) ret.inheritance = inheritance() || null; - all_ws(); - consume(OTHER, "{") || error("Bodyless dictionary"); - while (true) { - all_ws(store ? mems : null); - if (consume(OTHER, "}")) { - all_ws(); - consume(OTHER, ";") || error("Missing semicolon after dictionary"); - return ret; - } - var ea = extended_attrs(store ? mems : null); - all_ws(store ? mems : null, "pea"); - var required = consume(ID, "required"); - var typ = type() || error("No type for dictionary member"); - all_ws(); - var name = consume(ID) || error("No name for dictionary member"); - var dflt = default_(); - if (required && dflt) error("Required member must not have a default"); - ret.members.push({ - type: "field" - , name: name.value - , required: !!required - , idlType: typ - , extAttrs: ea - , "default": dflt - }); - all_ws(); - consume(OTHER, ";") || error("Unterminated dictionary member"); - } + if (!isPartial) ret.inheritance = inheritance() || null; + all_ws(); + consume(OTHER, "{") || error("Bodyless interface"); + while (true) { + all_ws(store ? mems : null); + if (consume(OTHER, "}")) { + all_ws(); + consume(OTHER, ";") || error("Missing semicolon after interface"); + return ret; + } + var ea = extended_attrs(store ? mems : null); + all_ws(); + var cnt = const_(store ? mems : null); + if (cnt) { + cnt.extAttrs = ea; + ret.members.push(cnt); + continue; + } + var mem = (opt.allowNestedTypedefs && typedef(store ? mems : null)) || + iterable(store ? mems : null) || + serialiser(store ? mems : null) || + attribute(store ? mems : null) || + operation(store ? mems : null) || + error("Unknown member"); + mem.extAttrs = ea; + ret.members.push(mem); + } + }; + + var namespace = function(isPartial, store) { + all_ws(isPartial ? null : store, "pea"); + if (!consume(ID, "namespace")) return; + all_ws(); + var name = consume(ID) || error("No name for namespace"); + var mems = [], + ret = { + type: "namespace", + name: name.value, + partial: isPartial, + members: mems }; - - var exception = function (store) { - all_ws(store, "pea"); - if (!consume(ID, "exception")) return; - all_ws(); - var name = consume(ID) || error("No name for exception"); - var mems = [] - , ret = { - type: "exception" - , name: name.value - , members: mems - }; - ret.inheritance = inheritance() || null; - all_ws(); - consume(OTHER, "{") || error("Bodyless exception"); - while (true) { - all_ws(store ? mems : null); - if (consume(OTHER, "}")) { - all_ws(); - consume(OTHER, ";") || error("Missing semicolon after exception"); - return ret; - } - var ea = extended_attrs(store ? mems : null); - all_ws(store ? mems : null, "pea"); - var cnt = const_(); - if (cnt) { - cnt.extAttrs = ea; - ret.members.push(cnt); - } - else { - var typ = type(); - all_ws(); - var name = consume(ID); - all_ws(); - if (!typ || !name || !consume(OTHER, ";")) error("Unknown member in exception body"); - ret.members.push({ - type: "field" - , name: name.value - , idlType: typ - , extAttrs: ea - }); - } - } + all_ws(); + consume(OTHER, "{") || error("Bodyless namespace"); + while (true) { + all_ws(store ? mems : null); + if (consume(OTHER, "}")) { + all_ws(); + consume(OTHER, ";") || error("Missing semicolon after namespace"); + return ret; + } + var ea = extended_attrs(store ? mems : null); + all_ws(); + var mem = noninherited_attribute(store ? mems : null) || + nonspecial_operation(store ? mems : null) || + error("Unknown member"); + mem.extAttrs = ea; + ret.members.push(mem); + } + } + + var noninherited_attribute = function(store) { + var w = all_ws(store, "pea"), + grabbed = [], + ret = { + type: "attribute", + "static": false, + stringifier: false, + inherit: false, + readonly: false }; - - var enum_ = function (store) { - all_ws(store, "pea"); - if (!consume(ID, "enum")) return; - all_ws(); - var name = consume(ID) || error("No name for enum"); - var vals = [] - , ret = { - type: "enum" - , name: name.value - , values: vals - }; - all_ws(); - consume(OTHER, "{") || error("No curly for enum"); - var saw_comma = false; - while (true) { - all_ws(store ? vals : null); - if (consume(OTHER, "}")) { - all_ws(); - consume(OTHER, ";") || error("No semicolon after enum"); - return ret; - } - var val = consume(STR) || error("Unexpected value in enum"); - ret.values.push(val.value.replace(/"/g, "")); - all_ws(store ? vals : null); - if (consume(OTHER, ",")) { - if (store) vals.push({ type: "," }); - all_ws(store ? vals : null); - saw_comma = true; - } - else { - saw_comma = false; - } - } + if (w) grabbed.push(w); + if (consume(ID, "readonly")) { + ret.readonly = true; + grabbed.push(last_token); + var w = all_ws(); + if (w) grabbed.push(w); + } + var rest = attribute_rest(ret); + if (!rest) { + tokens = grabbed.concat(tokens); + } + return rest; + } + + var nonspecial_operation = function(store) { + all_ws(store, "pea"); + var ret = { + type: "operation", + getter: false, + setter: false, + creator: false, + deleter: false, + legacycaller: false, + "static": false, + stringifier: false + }; + ret.idlType = return_type(); + return operation_rest(ret, store); + } + + var partial = function(store) { + all_ws(store, "pea"); + if (!consume(ID, "partial")) return; + var thing = dictionary(true, store) || + interface_(true, store) || + namespace(true, store) || + error("Partial doesn't apply to anything"); + thing.partial = true; + return thing; + }; + + var dictionary = function(isPartial, store) { + all_ws(isPartial ? null : store, "pea"); + if (!consume(ID, "dictionary")) return; + all_ws(); + var name = consume(ID) || error("No name for dictionary"); + var mems = [], + ret = { + type: "dictionary", + name: name.value, + partial: false, + members: mems }; - - var typedef = function (store) { - all_ws(store, "pea"); - if (!consume(ID, "typedef")) return; - var ret = { - type: "typedef" - }; - all_ws(); - ret.typeExtAttrs = extended_attrs(); - all_ws(store, "tpea"); - ret.idlType = type() || error("No type in typedef"); - all_ws(); - var name = consume(ID) || error("No name in typedef"); - ret.name = name.value; - all_ws(); - consume(OTHER, ";") || error("Unterminated typedef"); - return ret; + if (!isPartial) ret.inheritance = inheritance() || null; + all_ws(); + consume(OTHER, "{") || error("Bodyless dictionary"); + while (true) { + all_ws(store ? mems : null); + if (consume(OTHER, "}")) { + all_ws(); + consume(OTHER, ";") || error("Missing semicolon after dictionary"); + return ret; + } + var ea = extended_attrs(store ? mems : null); + all_ws(store ? mems : null, "pea"); + var required = consume(ID, "required"); + var typ = type_with_extended_attributes() || error("No type for dictionary member"); + all_ws(); + var name = consume(ID) || error("No name for dictionary member"); + var dflt = default_(); + if (required && dflt) error("Required member must not have a default"); + var member = { + type: "field", + name: name.value, + required: !!required, + idlType: typ, + extAttrs: ea }; - - var implements_ = function (store) { - all_ws(store, "pea"); - var target = consume(ID); - if (!target) return; - var w = all_ws(); - if (consume(ID, "implements")) { - var ret = { - type: "implements" - , target: target.value - }; - all_ws(); - var imp = consume(ID) || error("Incomplete implements statement"); - ret["implements"] = imp.value; - all_ws(); - consume(OTHER, ";") || error("No terminating ; for implements statement"); - return ret; - } - else { - // rollback - tokens.unshift(w); - tokens.unshift(target); - } + if (typeof dflt !== "undefined") { + member["default"] = dflt; + } + ret.members.push(member); + all_ws(); + consume(OTHER, ";") || error("Unterminated dictionary member"); + } + }; + + var exception = function(store) { + all_ws(store, "pea"); + if (!consume(ID, "exception")) return; + all_ws(); + var name = consume(ID) || error("No name for exception"); + var mems = [], + ret = { + type: "exception", + name: name.value, + members: mems }; - - var definition = function (store) { - return callback(store) || - interface_(false, store) || - partial(store) || - dictionary(false, store) || - exception(store) || - enum_(store) || - typedef(store) || - implements_(store) - ; + ret.inheritance = inheritance() || null; + all_ws(); + consume(OTHER, "{") || error("Bodyless exception"); + while (true) { + all_ws(store ? mems : null); + if (consume(OTHER, "}")) { + all_ws(); + consume(OTHER, ";") || error("Missing semicolon after exception"); + return ret; + } + var ea = extended_attrs(store ? mems : null); + all_ws(store ? mems : null, "pea"); + var cnt = const_(); + if (cnt) { + cnt.extAttrs = ea; + ret.members.push(cnt); + } else { + var typ = type(); + all_ws(); + var name = consume(ID); + all_ws(); + if (!typ || !name || !consume(OTHER, ";")) error("Unknown member in exception body"); + ret.members.push({ + type: "field", + name: name.value, + idlType: typ, + extAttrs: ea + }); + } + } + }; + + var enum_ = function(store) { + all_ws(store, "pea"); + if (!consume(ID, "enum")) return; + all_ws(); + var name = consume(ID) || error("No name for enum"); + var vals = [], + ret = { + type: "enum", + name: name.value, + values: vals }; - - var definitions = function (store) { - if (!tokens.length) return []; - var defs = []; - while (true) { - var ea = extended_attrs(store ? defs : null) - , def = definition(store ? defs : null); - if (!def) { - if (ea.length) error("Stray extended attributes"); - break; - } - def.extAttrs = ea; - defs.push(def); - } - return defs; + all_ws(); + consume(OTHER, "{") || error("No curly for enum"); + var saw_comma = false; + while (true) { + all_ws(store ? vals : null); + if (consume(OTHER, "}")) { + all_ws(); + consume(OTHER, ";") || error("No semicolon after enum"); + return ret; + } + var val = consume(STR) || error("Unexpected value in enum"); + ret.values.push(val.value.replace(/"/g, "")); + all_ws(store ? vals : null); + if (consume(OTHER, ",")) { + if (store) vals.push({ type: "," }); + all_ws(store ? vals : null); + saw_comma = true; + } else { + saw_comma = false; + } + } + }; + + var typedef = function(store) { + all_ws(store, "pea"); + if (!consume(ID, "typedef")) return; + var ret = { + type: "typedef" + }; + all_ws(); + ret.idlType = type_with_extended_attributes() || error("No type in typedef"); + all_ws(); + var name = consume(ID) || error("No name in typedef"); + ret.name = name.value; + all_ws(); + consume(OTHER, ";") || error("Unterminated typedef"); + return ret; + }; + + var implements_ = function(store) { + all_ws(store, "pea"); + var target = consume(ID); + if (!target) return; + var w = all_ws(); + if (consume(ID, "implements")) { + var ret = { + type: "implements", + target: target.value }; - var res = definitions(opt.ws); - if (tokens.length) error("Unrecognised tokens"); - return res; + all_ws(); + var imp = consume(ID) || error("Incomplete implements statement"); + ret["implements"] = imp.value; + all_ws(); + consume(OTHER, ";") || error("No terminating ; for implements statement"); + return ret; + } else { + // rollback + tokens.unshift(w); + tokens.unshift(target); + } }; - var inNode = typeof module !== "undefined" && module.exports - , obj = { - parse: function (str, opt) { - if (!opt) opt = {}; - var tokens = tokenise(str); - return parse(tokens, opt); - } + var definition = function(store) { + return callback(store) || + interface_(false, store) || + partial(store) || + dictionary(false, store) || + exception(store) || + enum_(store) || + typedef(store) || + implements_(store) || + namespace(false, store); }; - if (inNode) module.exports = obj; - else self.WebIDL2 = obj; + var definitions = function(store) { + if (!tokens.length) return []; + var defs = []; + while (true) { + var ea = extended_attrs(store ? defs : null), + def = definition(store ? defs : null); + if (!def) { + if (ea.length) error("Stray extended attributes"); + break; + } + def.extAttrs = ea; + defs.push(def); + } + return defs; + }; + var res = definitions(opt.ws); + if (tokens.length) error("Unrecognised tokens"); + return res; + }; + + var obj = { + parse: function(str, opt) { + if (!opt) opt = {}; + var tokens = tokenise(str); + return parse(tokens, opt); + } + }; + + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = obj; + } else if (typeof define === 'function' && define.amd) { + define([], function() { + return obj; + }); + } else { + (self || window).WebIDL2 = obj; + } }()); diff --git a/lib/writer.js b/lib/writer.js index ba8363738235c3..f7c79f8289358a 100644 --- a/lib/writer.js +++ b/lib/writer.js @@ -1,236 +1,280 @@ +(function() { -(function () { + var write = function(ast, opt) { + var curPea = "", + curTPea = "", + opt = opt || {}, + noop = function(str) { + return str; }, + optNames = "type".split(" "), + context = []; + for (var i = 0, n = optNames.length; i < n; i++) { + var o = optNames[i]; + if (!opt[o]) opt[o] = noop; + } - var write = function (ast, opt) { - var curPea = "" - , curTPea = "" - , opt = opt || {} - , noop = function (str) { return str; } - , optNames = "type".split(" ") - , context = [] - ; - for (var i = 0, n = optNames.length; i < n; i++) { - var o = optNames[i]; - if (!opt[o]) opt[o] = noop; + var literal = function(it) { + return it.value; + }; + var wsPea = function(it) { + curPea += it.value; + return ""; + }; + var wsTPea = function(it) { + curTPea += it.value; + return ""; + }; + var lineComment = function(it) { + return "//" + it.value + "\n"; + }; + var multilineComment = function(it) { + return "/*" + it.value + "*/"; + }; + var type = function(it) { + if (typeof it === "string") return opt.type(it); // XXX should maintain some context + if (it.union) return "(" + it.idlType.map(type).join(" or ") + ")"; + var ret = ""; + if (it.generic) ret += it.generic + "<"; + else if (it.sequence) ret += "sequence<"; + if (Array.isArray(it.idlType)) ret += it.idlType.map(type).join(", "); + else ret += type(it.idlType); + if (it.array || it.generic === 'Array') { + for (var i = 0, n = it.nullableArray.length; i < n; i++) { + var val = it.nullableArray[i]; + if (val) ret += "?"; + ret += "[]"; } - - var literal = function (it) { - return it.value; - }; - var wsPea = function (it) { - curPea += it.value; - return ""; - }; - var wsTPea = function (it) { - curTPea += it.value; - return ""; - }; - var lineComment = function (it) { - return "//" + it.value + "\n"; - }; - var multilineComment = function (it) { - return "/*" + it.value + "*/"; - }; - var type = function (it) { - if (typeof it === "string") return opt.type(it); // XXX should maintain some context - if (it.union) return "(" + it.idlType.map(type).join(" or ") + ")"; - var ret = ""; - if (it.sequence) ret += "sequence<"; - ret += type(it.idlType); - if (it.array) { - for (var i = 0, n = it.nullableArray.length; i < n; i++) { - var val = it.nullableArray[i]; - if (val) ret += "?"; - ret += "[]"; - } - } - if (it.sequence) ret += ">"; - if (it.nullable) ret += "?"; + } + if (it.generic || it.sequence) ret += ">"; + if (it.nullable) ret += "?"; - return ret; - }; - var const_value = function (it) { - var tp = it. type; - if (tp === "boolean") return it.value ? "true" : "false"; - else if (tp === "null") return "null"; - else if (tp === "Infinity") return (it.negative ? "-" : "") + "Infinity"; - else if (tp === "NaN") return "NaN"; - else if (tp === "number") return it.value; - else return '"' + it.value + '"'; - }; - var argument = function (arg, pea) { - var ret = extended_attributes(arg.extAttrs, pea); - if (arg.optional) ret += "optional "; - ret += type(arg.idlType); - if (arg.variadic) ret += "..."; - ret += " " + arg.name; - if (arg["default"]) ret += " = " + const_value(arg["default"]); - return ret; - }; - var args = function (its) { - var res = "" - , pea = "" - ; - for (var i = 0, n = its.length; i < n; i++) { - var arg = its[i]; - if (arg.type === "ws") res += arg.value; - else if (arg.type === "ws-pea") pea += arg.value; - else { - res += argument(arg, pea); - if (i < n - 1) res += ","; - pea = ""; - } - } - return res; - }; - var make_ext_at = function (it) { - if (it["arguments"] === null) return it.name; - context.unshift(it); - var ret = it.name + "(" + (it["arguments"].length ? args(it["arguments"]) : "") + ")"; - context.shift(); // XXX need to add more contexts, but not more than needed for ReSpec - return ret; - }; - var extended_attributes = function (eats, pea) { - if (!eats || !eats.length) return ""; - return "[" + eats.map(make_ext_at).join(", ") + "]" + pea; - }; - - var modifiers = "getter setter creator deleter legacycaller stringifier static".split(" "); - var operation = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - if (it.stringifier && !it.idlType) return "stringifier;"; - for (var i = 0, n = modifiers.length; i < n; i++) { - var mod = modifiers[i]; - if (it[mod]) ret += mod + " "; - } - ret += type(it.idlType) + " "; - if (it.name) ret += it.name; - ret += "(" + args(it["arguments"]) + ");"; - return ret; - }; + return ret; + }; + var const_value = function(it) { + var tp = it.type; + if (tp === "boolean") return it.value ? "true" : "false"; + else if (tp === "null") return "null"; + else if (tp === "Infinity") return (it.negative ? "-" : "") + "Infinity"; + else if (tp === "NaN") return "NaN"; + else if (tp === "number") return it.value; + else return '"' + it.value + '"'; + }; + var argument = function(arg, pea) { + var ret = extended_attributes(arg.extAttrs, pea); + if (arg.optional) ret += "optional "; + ret += type(arg.idlType); + if (arg.variadic) ret += "..."; + ret += " " + arg.name; + if (arg["default"]) ret += " = " + const_value(arg["default"]); + return ret; + }; + var args = function(its) { + var res = "", + pea = ""; + for (var i = 0, n = its.length; i < n; i++) { + var arg = its[i]; + if (arg.type === "ws") res += arg.value; + else if (arg.type === "ws-pea") pea += arg.value; + else { + res += argument(arg, pea); + if (i < n - 1) res += ","; + pea = ""; + } + } + return res; + }; + var make_ext_at = function(it) { + if (it["arguments"] === null) return it.name; + context.unshift(it); + var ret = it.name + "(" + (it["arguments"].length ? args(it["arguments"]) : "") + ")"; + context.shift(); // XXX need to add more contexts, but not more than needed for ReSpec + return ret; + }; + var extended_attributes = function(eats, pea) { + if (!eats || !eats.length) return ""; + return "[" + eats.map(make_ext_at).join(", ") + "]" + pea; + }; + + var modifiers = "getter setter creator deleter legacycaller stringifier static".split(" "); + var operation = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + if (it.stringifier && !it.idlType) return "stringifier;"; + for (var i = 0, n = modifiers.length; i < n; i++) { + var mod = modifiers[i]; + if (it[mod]) ret += mod + " "; + } + ret += type(it.idlType) + " "; + if (it.name) ret += it.name; + ret += "(" + args(it["arguments"]) + ");"; + return ret; + }; + + var attribute = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + if (it["static"]) ret += "static "; + if (it.stringifier) ret += "stringifier "; + if (it.readonly) ret += "readonly "; + if (it.inherit) ret += "inherit "; + ret += "attribute " + type(it.idlType) + " " + it.name + ";"; + return ret; + }; - var attribute = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - if (it["static"]) ret += "static "; - if (it.stringifier) ret += "stringifier "; - if (it.readonly) ret += "readonly "; - if (it.inherit) ret += "inherit "; - ret += "attribute " + type(it.idlType) + " " + it.name + ";"; - return ret; - }; - - var interface_ = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - if (it.partial) ret += "partial "; - ret += "interface " + it.name + " "; - if (it.inheritance) ret += ": " + it.inheritance + " "; - ret += "{" + iterate(it.members) + "};"; - return ret; - }; - - var dictionary = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - if (it.partial) ret += "partial "; - ret += "dictionary " + it.name + " "; - ret += "{" + iterate(it.members) + "};"; - return ret; - }; - var field = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - ret += type(it.idlType) + " " + it.name; - if (it["default"]) ret += " = " + const_value(it["default"]); - ret += ";"; - return ret; - }; - var exception = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - ret += "exception " + it.name + " "; - if (it.inheritance) ret += ": " + it.inheritance + " "; - ret += "{" + iterate(it.members) + "};"; - return ret; - }; - var const_ = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - return ret + "const " + type(it.idlType) + " " + it.name + " = " + const_value(it.value) + ";"; - }; - var typedef = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - ret += "typedef " + extended_attributes(it.typeExtAttrs, curTPea); - curTPea = ""; - return ret + type(it.idlType) + " " + it.name + ";"; - }; - var implements_ = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - return ret + it.target + " implements " + it["implements"] + ";"; - }; - var callback = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - return ret + "callback " + it.name + " = " + type(it.idlType) + - "(" + args(it["arguments"]) + ");"; - }; - var enum_ = function (it) { - var ret = extended_attributes(it.extAttrs, curPea); - curPea = ""; - ret += "enum " + it.name + " {"; - for (var i = 0, n = it.values.length; i < n; i++) { - var v = it.values[i]; - if (typeof v === "string") ret += '"' + v + '"'; - else if (v.type === "ws") ret += v.value; - else if (v.type === ",") ret += ","; - } - return ret + "};"; - }; - - var table = { - ws: literal - , "ws-pea": wsPea - , "ws-tpea": wsTPea - , "line-comment": lineComment - , "multiline-comment": multilineComment - , "interface": interface_ - , operation: operation - , attribute: attribute - , dictionary: dictionary - , field: field - , exception: exception - , "const": const_ - , typedef: typedef - , "implements": implements_ - , callback: callback - , "enum": enum_ - }; - var dispatch = function (it) { - return table[it.type](it); - }; - var iterate = function (things) { - if (!things) return; - var ret = ""; - for (var i = 0, n = things.length; i < n; i++) ret += dispatch(things[i]); - return ret; - }; - return iterate(ast); + var interface_ = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + if (it.partial) ret += "partial "; + ret += "interface " + it.name + " "; + if (it.inheritance) ret += ": " + it.inheritance + " "; + ret += "{" + iterate(it.members) + "};"; + return ret; }; + var dictionary = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + if (it.partial) ret += "partial "; + ret += "dictionary " + it.name + " "; + ret += "{" + iterate(it.members) + "};"; + return ret; + }; + var field = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + if (it.required) ret += "required "; + ret += type(it.idlType) + " " + it.name; + if (it["default"]) ret += " = " + const_value(it["default"]); + ret += ";"; + return ret; + }; + var exception = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + ret += "exception " + it.name + " "; + if (it.inheritance) ret += ": " + it.inheritance + " "; + ret += "{" + iterate(it.members) + "};"; + return ret; + }; + var const_ = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + return ret + "const " + type(it.idlType) + " " + it.name + " = " + const_value(it.value) + ";"; + }; + var typedef = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + ret += "typedef " + extended_attributes(it.typeExtAttrs, curTPea); + curTPea = ""; + return ret + type(it.idlType) + " " + it.name + ";"; + }; + var implements_ = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + return ret + it.target + " implements " + it["implements"] + ";"; + }; + var callback = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + return ret + "callback " + it.name + " = " + type(it.idlType) + + "(" + args(it["arguments"]) + ");"; + }; + var enum_ = function(it) { + var ret = extended_attributes(it.extAttrs, curPea); + curPea = ""; + ret += "enum " + it.name + " {"; + for (var i = 0, n = it.values.length; i < n; i++) { + var v = it.values[i]; + if (typeof v === "string") ret += '"' + v + '"'; + else if (v.type === "ws") ret += v.value; + else if (v.type === ",") ret += ","; + } + return ret + "};"; + }; + var serializer = function(it) { + var ret = "serializer"; + if (it.name) { + ret += " = " + it.name + ";"; + } else if (it.patternList) { + ret += " = [ " + it.names.join(", ") + " ];"; + } else if (it.patternMap) { + ret += " = { " + it.names.join(", ") + " };"; + } else if (it.operation) { + ret += " " + operation(it); + } else { + ret += ";"; + } + return ret; + }; + var iterable = function(it) { + return "iterable<" + (it.idlType instanceof Array ? it.idlType.map(type).join(", ") : type(it.idlType)) + ">;"; + }; + var legacyiterable = function(it) { + return "legacyiterable<" + (it.idlType instanceof Array ? it.idlType.map(type).join(", ") : type(it.idlType)) + ">;"; + }; + var maplike = function(it) { + return (it.readonly ? "readonly " : "") + "maplike<" + + it.idlType.map(type).join(", ") + ">;"; + }; + var setlike = function(it) { + return (it.readonly ? "readonly " : "") + "setlike<" + + type(it.idlType) + ">;"; + }; + var callbackInterface = function(it) { + return 'callback ' + interface_(it); + }; - var inNode = typeof module !== "undefined" && module.exports - , obj = { - write: function (ast, opt) { - if (!opt) opt = {}; - return write(ast, opt); - } + var table = { + ws: literal, + "ws-pea": wsPea, + "ws-tpea": wsTPea, + "line-comment": lineComment, + "multiline-comment": multilineComment, + "interface": interface_, + operation: operation, + attribute: attribute, + dictionary: dictionary, + field: field, + exception: exception, + "const": const_, + typedef: typedef, + "implements": implements_, + callback: callback, + "enum": enum_, + serializer: serializer, + iterable: iterable, + legacyiterable: legacyiterable, + maplike: maplike, + setlike: setlike, + "callback interface": callbackInterface }; + var dispatch = function(it) { + return table[it.type](it); + }; + var iterate = function(things) { + if (!things) return; + var ret = ""; + for (var i = 0, n = things.length; i < n; i++) ret += dispatch(things[i]); + return ret; + }; + return iterate(ast); + }; + + + var obj = { + write: function(ast, opt) { + if (!opt) opt = {}; + return write(ast, opt); + } + }; - if (inNode) module.exports = obj; - else window.WebIDL2Writer = obj; - + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = obj; + } else if (typeof define === 'function' && define.amd) { + define([], function() { + return obj; + }); + } else { + (self || window).WebIDL2Writer = obj; + } }()); diff --git a/package.json b/package.json index c5ae7da2d59344..02b2a9d9b1644e 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { "name": "webidl2", "description": "A WebIDL Parser", - "version": "2.0.11", + "version": "2.4.0", "author": "Robin Berjon ", "license": "MIT", "dependencies": {}, "devDependencies": { - "mocha": "2.2.5", - "expect.js": "0.3.1", + "mocha": "3.2.0", + "expect": "1.20.2", "underscore": "1.8.3", - "jsondiffpatch": "0.1.31", + "jsondiffpatch": "0.2.4", "benchmark": "*", - "microtime": "1.4.2" + "microtime": "2.1.3" }, "scripts": { "test": "mocha" diff --git a/test/invalid.js b/test/invalid.js index c98218037264e8..b8ab3a1866de0f 100644 --- a/test/invalid.js +++ b/test/invalid.js @@ -4,7 +4,7 @@ // are fully correct interpretations of the IDLs var wp = process.env.JSCOV ? require("../lib-cov/webidl2") : require("../lib/webidl2") -, expect = require("expect.js") +, expect = require("expect") , pth = require("path") , fs = require("fs") ; @@ -16,7 +16,7 @@ describe("Parses all of the invalid IDLs to check that they blow up correctly", .map(function (it) { return pth.join(dir, it); }) , errors = idls.map(function (it) { return pth.join(__dirname, "invalid", "json", pth.basename(it).replace(/\.w?idl/, ".json")); }) ; - + for (var i = 0, n = idls.length; i < n; i++) { var idl = idls[i], error = JSON.parse(fs.readFileSync(errors[i], "utf8")); var func = (function (idl, err) { @@ -30,11 +30,11 @@ describe("Parses all of the invalid IDLs to check that they blow up correctly", error = e; } finally { - expect(error).to.be.ok(); - expect(error.message).to.equal(err.message); - expect(error.line).to.equal(err.line); + expect(error).toExist(); + expect(error.message).toEqual(err.message); + expect(error.line).toEqual(err.line); } - + }; }(idl, error)); it("should produce the right error for " + idl, func); diff --git a/test/invalid/idl/record-key.widl b/test/invalid/idl/record-key.widl new file mode 100644 index 00000000000000..39dc386182f809 --- /dev/null +++ b/test/invalid/idl/record-key.widl @@ -0,0 +1,3 @@ +interface Foo { + void foo(record param); +}; diff --git a/test/invalid/json/record-key.json b/test/invalid/json/record-key.json new file mode 100644 index 00000000000000..3b929b926a30a7 --- /dev/null +++ b/test/invalid/json/record-key.json @@ -0,0 +1,4 @@ +{ + "message": "Record key must be DOMString, USVString, or ByteString", + "line": 2 +} diff --git a/test/syntax.js b/test/syntax.js index 712542a6be8e5e..3b343e4229d502 100644 --- a/test/syntax.js +++ b/test/syntax.js @@ -1,6 +1,6 @@ var wp = process.env.JSCOV ? require("../lib-cov/webidl2") : require("../lib/webidl2") -, expect = require("expect.js") +, expect = require("expect") , pth = require("path") , fs = require("fs") , jdp = require("jsondiffpatch") @@ -14,7 +14,7 @@ describe("Parses all of the IDLs to produce the correct ASTs", function () { .map(function (it) { return pth.join(dir, it); }) , jsons = idls.map(function (it) { return pth.join(__dirname, "syntax/json", pth.basename(it).replace(".widl", ".json")); }) ; - + for (var i = 0, n = idls.length; i < n; i++) { var idl = idls[i], json = jsons[i]; @@ -28,7 +28,7 @@ describe("Parses all of the IDLs to produce the correct ASTs", function () { var diff = jdp.diff(JSON.parse(fs.readFileSync(json, "utf8")), wp.parse(fs.readFileSync(idl, "utf8"), opt)); if (diff && debug) console.log(JSON.stringify(diff, null, 4)); - expect(diff).to.be(undefined); + expect(diff).toBe(undefined); } catch (e) { console.log(e.toString()); diff --git a/test/syntax/idl/extended-attributes.widl b/test/syntax/idl/extended-attributes.widl index f769c2559cc799..c1df79e142d54d 100644 --- a/test/syntax/idl/extended-attributes.widl +++ b/test/syntax/idl/extended-attributes.widl @@ -3,4 +3,9 @@ [Global=(Worker,ServiceWorker), Exposed=ServiceWorker] interface ServiceWorkerGlobalScope : WorkerGlobalScope { -}; \ No newline at end of file +}; + +// Conformance with ExtendedAttributeList grammar in http://www.w3.org/TR/WebIDL/#idl-extended-attributes +// Section 3.11 +[IntAttr=0, FloatAttr=3.14, StringAttr="abc"] +interface IdInterface {}; diff --git a/test/syntax/idl/map.widl b/test/syntax/idl/map.widl deleted file mode 100644 index 19b54f80a61aff..00000000000000 --- a/test/syntax/idl/map.widl +++ /dev/null @@ -1,5 +0,0 @@ -// Extracted from https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ on 2014-05-06 - -[MapClass(DOMString, DOMString)] -interface HeaderMap { -}; diff --git a/test/syntax/idl/namespace.widl b/test/syntax/idl/namespace.widl new file mode 100644 index 00000000000000..d9610555e17ad2 --- /dev/null +++ b/test/syntax/idl/namespace.widl @@ -0,0 +1,10 @@ +// Extracted from Web IDL editors draft March 27 2017 +namespace VectorUtils { + readonly attribute Vector unit; + double dotProduct(Vector x, Vector y); + Vector crossProduct(Vector x, Vector y); +}; + +partial namespace SomeNamespace { + /* namespace_members... */ +}; \ No newline at end of file diff --git a/test/syntax/idl/record.widl b/test/syntax/idl/record.widl new file mode 100644 index 00000000000000..6cdedb219bece6 --- /dev/null +++ b/test/syntax/idl/record.widl @@ -0,0 +1,8 @@ +[Constructor(record init)] +interface Foo { + void foo(sequence> param); + record bar(); + + // Make sure record can still be registered as a type. + record baz(); +}; diff --git a/test/syntax/idl/typedef-nested.widl b/test/syntax/idl/typedef-nested.widl deleted file mode 100644 index 106f30b8f72ec7..00000000000000 --- a/test/syntax/idl/typedef-nested.widl +++ /dev/null @@ -1,22 +0,0 @@ - - interface Point { - attribute float x; - attribute float y; - }; - - - interface Rect { - attribute Point topleft; - attribute Point bottomright; - }; - - interface Widget { - typedef sequence PointSequence; - - readonly attribute Rect bounds; - - boolean pointWithinBounds(Point p); - boolean allPointsWithinBounds(PointSequence ps); - }; - - typedef [Clamp] octet value; \ No newline at end of file diff --git a/test/syntax/idl/typedef-union.idl b/test/syntax/idl/typedef-union.idl new file mode 100644 index 00000000000000..3048703e0c5541 --- /dev/null +++ b/test/syntax/idl/typedef-union.idl @@ -0,0 +1,4 @@ + typedef (ImageData or + HTMLImageElement or + HTMLCanvasElement or + HTMLVideoElement) TexImageSource; diff --git a/test/syntax/idl/uniontype.widl b/test/syntax/idl/uniontype.widl index 4d99f0196308ae..0d5fe9be428640 100644 --- a/test/syntax/idl/uniontype.widl +++ b/test/syntax/idl/uniontype.widl @@ -1,3 +1,4 @@ interface Union { attribute (float or (Date or Event) or (Node or DOMString)?) test; + attribute ([EnforceRange] long or Date) test2; }; \ No newline at end of file diff --git a/test/syntax/json/extended-attributes.json b/test/syntax/json/extended-attributes.json index 228c4b824be3e4..3b5a3b2e22b31a 100644 --- a/test/syntax/json/extended-attributes.json +++ b/test/syntax/json/extended-attributes.json @@ -26,5 +26,38 @@ } } ] + }, + { + "type": "interface", + "name": "IdInterface", + "partial": false, + "members": [], + "inheritance": null, + "extAttrs": [ + { + "name": "IntAttr", + "arguments": null, + "rhs": { + "type": "integer", + "value": "0" + } + }, + { + "name": "FloatAttr", + "arguments": null, + "rhs": { + "type": "float", + "value": "3.14" + } + }, + { + "name": "StringAttr", + "arguments": null, + "rhs": { + "type": "string", + "value": "\"abc\"" + } + } + ] } -] \ No newline at end of file +] diff --git a/test/syntax/json/identifier-qualified-names.json b/test/syntax/json/identifier-qualified-names.json index a91f8c8eb53e56..d87ea3b5f369db 100644 --- a/test/syntax/json/identifier-qualified-names.json +++ b/test/syntax/json/identifier-qualified-names.json @@ -1,7 +1,6 @@ [ { "type": "typedef", - "typeExtAttrs": [], "idlType": { "sequence": false, "generic": null, @@ -214,4 +213,4 @@ "inheritance": null, "extAttrs": [] } -] \ No newline at end of file +] diff --git a/test/syntax/json/map.json b/test/syntax/json/map.json deleted file mode 100644 index 03ce9412ed08d1..00000000000000 --- a/test/syntax/json/map.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "type": "interface", - "name": "HeaderMap", - "partial": false, - "members": [], - "inheritance": null, - "extAttrs": [{ - "name": "MapClass", - "arguments": null, - "typePair": [{ - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "DOMString" - }, - { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "DOMString" - }] - }] - } -] diff --git a/test/syntax/json/namespace.json b/test/syntax/json/namespace.json new file mode 100644 index 00000000000000..7c7ba771e1bf3a --- /dev/null +++ b/test/syntax/json/namespace.json @@ -0,0 +1,134 @@ +[ + { + "type": "namespace", + "name": "VectorUtils", + "partial": false, + "members": [ + { + "type": "attribute", + "static": false, + "stringifier": false, + "inherit": false, + "readonly": true, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Vector" + }, + "name": "unit", + "extAttrs": [] + }, + { + "type": "operation", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "double" + }, + "name": "dotProduct", + "arguments": [ + { + "optional": false, + "variadic": false, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Vector" + }, + "name": "x" + }, + { + "optional": false, + "variadic": false, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Vector" + }, + "name": "y" + } + ], + "extAttrs": [] + }, + { + "type": "operation", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Vector" + }, + "name": "crossProduct", + "arguments": [ + { + "optional": false, + "variadic": false, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Vector" + }, + "name": "x" + }, + { + "optional": false, + "variadic": false, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "Vector" + }, + "name": "y" + } + ], + "extAttrs": [] + } + ], + "extAttrs": [] + }, + { + "type": "namespace", + "name": "SomeNamespace", + "partial": true, + "members": [], + "extAttrs": [] + } +] \ No newline at end of file diff --git a/test/syntax/json/record.json b/test/syntax/json/record.json new file mode 100644 index 00000000000000..d2a21a4acd9993 --- /dev/null +++ b/test/syntax/json/record.json @@ -0,0 +1,184 @@ +[ + { + "type": "interface", + "name": "Foo", + "partial": false, + "members": [ + { + "type": "operation", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "void" + }, + "name": "foo", + "arguments": [ + { + "optional": false, + "variadic": false, + "extAttrs": [], + "idlType": { + "sequence": true, + "generic": "sequence", + "nullable": false, + "array": false, + "union": false, + "idlType": { + "sequence": false, + "generic": "record", + "nullable": false, + "array": false, + "union": false, + "idlType": [ + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "ByteString" + }, + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "any" + } + ] + } + }, + "name": "param" + } + ], + "extAttrs": [] + }, + { + "type": "operation", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": "record", + "nullable": false, + "array": false, + "union": false, + "idlType": [ + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "DOMString" + }, + { + "sequence": false, + "generic": null, + "nullable": true, + "array": false, + "union": true, + "idlType": [ + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "float" + }, + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "DOMString" + } + ] + } + ] + }, + "name": "bar", + "arguments": [], + "extAttrs": [] + }, + { + "type": "operation", + "getter": false, + "setter": false, + "creator": false, + "deleter": false, + "legacycaller": false, + "static": false, + "stringifier": false, + "idlType": { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "record" + }, + "name": "baz", + "arguments": [], + "extAttrs": [] + } + ], + "inheritance": null, + "extAttrs": [ + { + "name": "Constructor", + "arguments": [ + { + "optional": false, + "variadic": false, + "extAttrs": [], + "idlType": { + "sequence": false, + "generic": "record", + "nullable": false, + "array": false, + "union": false, + "idlType": [ + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "USVString" + }, + { + "sequence": false, + "generic": null, + "nullable": false, + "array": false, + "union": false, + "idlType": "USVString" + } + ] + }, + "name": "init" + } + ] + } + ] + } +] diff --git a/test/syntax/json/typedef-nested.json b/test/syntax/json/typedef-nested.json deleted file mode 100644 index 76d138408ddfce..00000000000000 --- a/test/syntax/json/typedef-nested.json +++ /dev/null @@ -1,226 +0,0 @@ -[ - { - "type": "interface", - "name": "Point", - "partial": false, - "members": [ - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "float" - }, - "name": "x", - "extAttrs": [] - }, - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "float" - }, - "name": "y", - "extAttrs": [] - } - ], - "inheritance": null, - "extAttrs": [] - }, - { - "type": "interface", - "name": "Rect", - "partial": false, - "members": [ - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Point" - }, - "name": "topleft", - "extAttrs": [] - }, - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Point" - }, - "name": "bottomright", - "extAttrs": [] - } - ], - "inheritance": null, - "extAttrs": [] - }, - { - "type": "interface", - "name": "Widget", - "partial": false, - "members": [ - { - "type": "typedef", - "typeExtAttrs": [], - "idlType": { - "sequence": true, - "generic": "sequence", - "nullable": false, - "array": false, - "union": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Point" - } - }, - "name": "PointSequence", - "extAttrs": [] - }, - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": true, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Rect" - }, - "name": "bounds", - "extAttrs": [] - }, - { - "type": "operation", - "getter": false, - "setter": false, - "creator": false, - "deleter": false, - "legacycaller": false, - "static": false, - "stringifier": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "boolean" - }, - "name": "pointWithinBounds", - "arguments": [ - { - "optional": false, - "variadic": false, - "extAttrs": [], - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Point" - }, - "name": "p" - } - ], - "extAttrs": [] - }, - { - "type": "operation", - "getter": false, - "setter": false, - "creator": false, - "deleter": false, - "legacycaller": false, - "static": false, - "stringifier": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "boolean" - }, - "name": "allPointsWithinBounds", - "arguments": [ - { - "optional": false, - "variadic": false, - "extAttrs": [], - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "PointSequence" - }, - "name": "ps" - } - ], - "extAttrs": [] - } - ], - "inheritance": null, - "extAttrs": [] - }, - { - "type": "typedef", - "typeExtAttrs": [ - { - "name": "Clamp", - "arguments": null - } - ], - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "octet" - }, - "name": "value", - "extAttrs": [] - } -] \ No newline at end of file diff --git a/test/syntax/json/typedef-union.json b/test/syntax/json/typedef-union.json new file mode 100644 index 00000000000000..9c87672c8aebb1 --- /dev/null +++ b/test/syntax/json/typedef-union.json @@ -0,0 +1,49 @@ +[ + { + "type" : "typedef", + "idlType" : { + "nullable" : false, + "generic" : null, + "union" : true, + "idlType" : [ + { + "union" : false, + "generic" : null, + "nullable" : false, + "array" : false, + "sequence" : false, + "idlType" : "ImageData" + }, + { + "generic" : null, + "union" : false, + "nullable" : false, + "array" : false, + "idlType" : "HTMLImageElement", + "sequence" : false + }, + { + "array" : false, + "sequence" : false, + "idlType" : "HTMLCanvasElement", + "generic" : null, + "union" : false, + "nullable" : false + }, + { + "union" : false, + "generic" : null, + "nullable" : false, + "array" : false, + "sequence" : false, + "idlType" : "HTMLVideoElement" + } + ], + "sequence" : false, + "array" : false + }, + "name" : "TexImageSource", + "extAttrs" : [], + "typeExtAttrs" : [] + } +] diff --git a/test/syntax/json/typedef.json b/test/syntax/json/typedef.json index ffdeea945d7f7a..d0854fa3d26268 100644 --- a/test/syntax/json/typedef.json +++ b/test/syntax/json/typedef.json @@ -44,7 +44,6 @@ }, { "type": "typedef", - "typeExtAttrs": [], "idlType": { "sequence": true, "generic": "sequence", @@ -206,21 +205,21 @@ }, { "type": "typedef", - "typeExtAttrs": [ - { - "name": "Clamp", - "arguments": null - } - ], "idlType": { "sequence": false, "generic": null, "nullable": false, "array": false, "union": false, - "idlType": "octet" + "idlType": "octet", + "extAttrs": [ + { + "name": "Clamp", + "arguments": null + } + ] }, "name": "value", "extAttrs": [] } -] \ No newline at end of file +] diff --git a/test/syntax/json/uniontype.json b/test/syntax/json/uniontype.json index 43e25fd6bfb849..9da5e79f3625ef 100644 --- a/test/syntax/json/uniontype.json +++ b/test/syntax/json/uniontype.json @@ -1,87 +1,127 @@ [ - { - "type": "interface", - "name": "Union", - "partial": false, - "members": [ - { - "type": "attribute", - "static": false, - "stringifier": false, - "inherit": false, - "readonly": false, - "idlType": { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": true, - "idlType": [ + { + "partial": false, + "members": [ + { + "idlType": { + "idlType": [ + { + "array": false, + "union": false, + "sequence": false, + "generic": null, + "idlType": "float", + "nullable": false + }, + { + "idlType": [ { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "float" + "nullable": false, + "idlType": "Date", + "sequence": false, + "generic": null, + "union": false, + "array": false }, { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": true, - "idlType": [ - { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Date" - }, - { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Event" - } - ] + "nullable": false, + "idlType": "Event", + "generic": null, + "sequence": false, + "array": false, + "union": false + } + ], + "nullable": false, + "sequence": false, + "generic": null, + "array": false, + "union": true + }, + { + "generic": null, + "sequence": false, + "idlType": [ + { + "array": false, + "union": false, + "sequence": false, + "generic": null, + "nullable": false, + "idlType": "Node" }, { - "sequence": false, - "generic": null, - "nullable": true, - "array": false, - "union": true, - "idlType": [ - { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "Node" - }, - { - "sequence": false, - "generic": null, - "nullable": false, - "array": false, - "union": false, - "idlType": "DOMString" - } - ] + "nullable": false, + "idlType": "DOMString", + "sequence": false, + "generic": null, + "array": false, + "union": false + } + ], + "nullable": true, + "union": true, + "array": false + } + ], + "nullable": false, + "generic": null, + "sequence": false, + "union": true, + "array": false + }, + "name": "test", + "inherit": false, + "type": "attribute", + "extAttrs": [], + "readonly": false, + "stringifier": false, + "static": false + }, + { + "readonly": false, + "extAttrs": [], + "stringifier": false, + "static": false, + "name": "test2", + "idlType": { + "nullable": false, + "idlType": [ + { + "extAttrs": [ + { + "name": "EnforceRange", + "arguments": null } - ] - }, - "name": "test", - "extAttrs": [] - } - ], - "inheritance": null, - "extAttrs": [] - } -] \ No newline at end of file + ], + "nullable": false, + "idlType": "long", + "generic": null, + "sequence": false, + "array": false, + "union": false + }, + { + "array": false, + "union": false, + "sequence": false, + "generic": null, + "idlType": "Date", + "nullable": false + } + ], + "generic": null, + "sequence": false, + "union": true, + "array": false + }, + "inherit": false, + "type": "attribute" + } + ], + "inheritance": null, + "name": "Union", + "extAttrs": [], + "type": "interface" + } +] diff --git a/test/web/make-web-tests.js b/test/web/make-web-tests.js index 34c5a5c2b46d25..1774806994e0a7 100644 --- a/test/web/make-web-tests.js +++ b/test/web/make-web-tests.js @@ -36,9 +36,10 @@ var pth = require("path") , " " , "
" , " " - , " " + , " " + , " " , " " - , " " + , " " , " " , " " , " " diff --git a/test/web/run-tests.js b/test/web/run-tests.js index a72800b8bdb8d6..452f799b2a6f03 100644 --- a/test/web/run-tests.js +++ b/test/web/run-tests.js @@ -10,7 +10,7 @@ describe("Parses all of the IDLs to produce the correct ASTs", function () { // so we compare based on that var diff = jsondiffpatch.diff(json, WebIDL2.parse(idl)); if (diff && debug) console.log(JSON.stringify(diff, null, 4)); - expect(diff).to.be(undefined); + expect(diff).toBe(undefined); } catch (e) { console.log(e.toString()); @@ -36,11 +36,11 @@ describe("Parses all of the invalid IDLs to check that they blow up correctly", error = e; } finally { - expect(error).to.be.ok(); - expect(error.message).to.equal(err.message); - expect(error.line).to.equal(err.line); + expect(error).toExist(); + expect(error.message).toEqual(err.message); + expect(error.line).toEqual(err.line); } - + }; }(idl, error)); it("should produce the right error for " + i, func);