1"use strict"; 2 3(() => { 4 // These regular expressions use the sticky flag so they will only match at 5 // the current location (ie. the offset of lastIndex). 6 const tokenRe = { 7 // This expression uses a lookahead assertion to catch false matches 8 // against integers early. 9 "float": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y, 10 "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y, 11 "identifier": /_?[A-Za-z][0-9A-Z_a-z-]*/y, 12 "string": /"[^"]*"/y, 13 "whitespace": /[\t\n\r ]+/y, 14 "comment": /((\/(\/.*|\*([^*]|\*[^\/])*\*\/)[\t\n\r ]*)+)/y, 15 "other": /[^\t\n\r 0-9A-Za-z]/y 16 }; 17 18 const stringTypes = [ 19 "ByteString", 20 "DOMString", 21 "USVString" 22 ]; 23 24 const argumentNameKeywords = [ 25 "attribute", 26 "callback", 27 "const", 28 "deleter", 29 "dictionary", 30 "enum", 31 "getter", 32 "includes", 33 "inherit", 34 "interface", 35 "iterable", 36 "maplike", 37 "namespace", 38 "partial", 39 "required", 40 "setlike", 41 "setter", 42 "static", 43 "stringifier", 44 "typedef", 45 "unrestricted" 46 ]; 47 48 const nonRegexTerminals = [ 49 "FrozenArray", 50 "Infinity", 51 "NaN", 52 "Promise", 53 "boolean", 54 "byte", 55 "double", 56 "false", 57 "float", 58 "implements", 59 "legacyiterable", 60 "long", 61 "mixin", 62 "null", 63 "octet", 64 "optional", 65 "or", 66 "readonly", 67 "record", 68 "sequence", 69 "short", 70 "true", 71 "unsigned", 72 "void" 73 ].concat(argumentNameKeywords, stringTypes); 74 75 const punctuations = [ 76 "(", 77 ")", 78 ",", 79 "-Infinity", 80 "...", 81 ":", 82 ";", 83 "<", 84 "=", 85 ">", 86 "?", 87 "[", 88 "]", 89 "{", 90 "}" 91 ]; 92 93 function tokenise(str) { 94 const tokens = []; 95 let lastIndex = 0; 96 let trivia = ""; 97 while (lastIndex < str.length) { 98 const nextChar = str.charAt(lastIndex); 99 let result = -1; 100 101 if (/[\t\n\r ]/.test(nextChar)) { 102 result = attemptTokenMatch("whitespace", { noFlushTrivia: true }); 103 } else if (nextChar === '/') { 104 result = attemptTokenMatch("comment", { noFlushTrivia: true }); 105 } 106 107 if (result !== -1) { 108 trivia += tokens.pop().value; 109 } else if (/[-0-9.]/.test(nextChar)) { 110 result = attemptTokenMatch("float"); 111 if (result === -1) { 112 result = attemptTokenMatch("integer"); 113 } 114 } else if (/[A-Z_a-z]/.test(nextChar)) { 115 result = attemptTokenMatch("identifier"); 116 const token = tokens[tokens.length - 1]; 117 if (result !== -1 && nonRegexTerminals.includes(token.value)) { 118 token.type = token.value; 119 } 120 } else if (nextChar === '"') { 121 result = attemptTokenMatch("string"); 122 } 123 124 for (const punctuation of punctuations) { 125 if (str.startsWith(punctuation, lastIndex)) { 126 tokens.push({ type: punctuation, value: punctuation, trivia }); 127 trivia = ""; 128 lastIndex += punctuation.length; 129 result = lastIndex; 130 break; 131 } 132 } 133 134 // other as the last try 135 if (result === -1) { 136 result = attemptTokenMatch("other"); 137 } 138 if (result === -1) { 139 throw new Error("Token stream not progressing"); 140 } 141 lastIndex = result; 142 } 143 return tokens; 144 145 function attemptTokenMatch(type, { noFlushTrivia } = {}) { 146 const re = tokenRe[type]; 147 re.lastIndex = lastIndex; 148 const result = re.exec(str); 149 if (result) { 150 tokens.push({ type, value: result[0], trivia }); 151 if (!noFlushTrivia) { 152 trivia = ""; 153 } 154 return re.lastIndex; 155 } 156 return -1; 157 } 158 } 159 160 class WebIDLParseError { 161 constructor(str, line, input, tokens) { 162 this.message = str; 163 this.line = line; 164 this.input = input; 165 this.tokens = tokens; 166 } 167 168 toString() { 169 const escapedInput = JSON.stringify(this.input); 170 const tokens = JSON.stringify(this.tokens, null, 4); 171 return `${this.message}, line ${this.line} (tokens: ${escapedInput})\n${tokens}`; 172 } 173 } 174 175 function parse(tokens) { 176 let line = 1; 177 tokens = tokens.slice(); 178 const names = new Map(); 179 let current = null; 180 181 const FLOAT = "float"; 182 const INT = "integer"; 183 const ID = "identifier"; 184 const STR = "string"; 185 const OTHER = "other"; 186 187 const EMPTY_OPERATION = Object.freeze({ 188 type: "operation", 189 getter: false, 190 setter: false, 191 deleter: false, 192 static: false, 193 stringifier: false 194 }); 195 196 const EMPTY_IDLTYPE = Object.freeze({ 197 generic: null, 198 nullable: false, 199 union: false, 200 idlType: null, 201 extAttrs: [] 202 }); 203 204 function error(str) { 205 const maxTokens = 5; 206 const tok = tokens 207 .slice(consume_position, consume_position + maxTokens) 208 .map(t => t.trivia + t.value).join(""); 209 // Count newlines preceding the actual erroneous token 210 if (tokens.length) { 211 line += count(tokens[consume_position].trivia, "\n"); 212 } 213 214 let message; 215 if (current) { 216 message = `Got an error during or right after parsing \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`: ${str}` 217 } 218 else { 219 // throwing before any valid definition 220 message = `Got an error before parsing any named definition: ${str}`; 221 } 222 223 throw new WebIDLParseError(message, line, tok, tokens.slice(0, maxTokens)); 224 } 225 226 function sanitize_name(name, type) { 227 if (names.has(name)) { 228 error(`The name "${name}" of type "${names.get(name)}" is already seen`); 229 } 230 names.set(name, type); 231 return name; 232 } 233 234 let consume_position = 0; 235 236 function probe(type) { 237 return tokens.length > consume_position && tokens[consume_position].type === type; 238 } 239 240 function consume(...candidates) { 241 // TODO: use const when Servo updates its JS engine 242 for (let type of candidates) { 243 if (!probe(type)) continue; 244 const token = tokens[consume_position]; 245 consume_position++; 246 line += count(token.trivia, "\n"); 247 return token; 248 } 249 } 250 251 function unescape(identifier) { 252 return identifier.startsWith('_') ? identifier.slice(1) : identifier; 253 } 254 255 function unconsume(position) { 256 while (consume_position > position) { 257 consume_position--; 258 line -= count(tokens[consume_position].trivia, "\n"); 259 } 260 } 261 262 function count(str, char) { 263 let total = 0; 264 for (let i = str.indexOf(char); i !== -1; i = str.indexOf(char, i + 1)) { 265 ++total; 266 } 267 return total; 268 } 269 270 function integer_type() { 271 let ret = ""; 272 if (consume("unsigned")) ret = "unsigned "; 273 if (consume("short")) return ret + "short"; 274 if (consume("long")) { 275 ret += "long"; 276 if (consume("long")) return ret + " long"; 277 return ret; 278 } 279 if (ret) error("Failed to parse integer type"); 280 } 281 282 function float_type() { 283 let ret = ""; 284 if (consume("unrestricted")) ret = "unrestricted "; 285 if (consume("float")) return ret + "float"; 286 if (consume("double")) return ret + "double"; 287 if (ret) error("Failed to parse float type"); 288 } 289 290 function primitive_type() { 291 const num_type = integer_type() || float_type(); 292 if (num_type) return num_type; 293 if (consume("boolean")) return "boolean"; 294 if (consume("byte")) return "byte"; 295 if (consume("octet")) return "octet"; 296 } 297 298 function const_value() { 299 if (consume("true")) return { type: "boolean", value: true }; 300 if (consume("false")) return { type: "boolean", value: false }; 301 if (consume("null")) return { type: "null" }; 302 if (consume("Infinity")) return { type: "Infinity", negative: false }; 303 if (consume("-Infinity")) return { type: "Infinity", negative: true }; 304 if (consume("NaN")) return { type: "NaN" }; 305 const ret = consume(FLOAT, INT); 306 if (ret) return { type: "number", value: ret.value }; 307 } 308 309 function type_suffix(obj) { 310 obj.nullable = !!consume("?"); 311 if (probe("?")) error("Can't nullable more than once"); 312 } 313 314 function generic_type(typeName) { 315 const name = consume("FrozenArray", "Promise", "sequence", "record"); 316 if (!name) { 317 return; 318 } 319 const ret = { generic: name.type }; 320 consume("<") || error(`No opening bracket after ${name.type}`); 321 switch (name.type) { 322 case "Promise": 323 if (probe("[")) error("Promise type cannot have extended attribute"); 324 ret.idlType = return_type(typeName); 325 break; 326 case "sequence": 327 case "FrozenArray": 328 ret.idlType = type_with_extended_attributes(typeName); 329 break; 330 case "record": 331 if (probe("[")) error("Record key cannot have extended attribute"); 332 ret.idlType = []; 333 const keyType = consume(...stringTypes); 334 if (!keyType) error(`Record key must be a string type`); 335 ret.idlType.push(Object.assign({ type: typeName }, EMPTY_IDLTYPE, { idlType: keyType.value })); 336 consume(",") || error("Missing comma after record key type"); 337 const valueType = type_with_extended_attributes(typeName) || error("Error parsing generic type record"); 338 ret.idlType.push(valueType); 339 break; 340 } 341 if (!ret.idlType) error(`Error parsing generic type ${name.type}`); 342 consume(">") || error(`Missing closing bracket after ${name.type}`); 343 if (name.type === "Promise" && probe("?")) { 344 error("Promise type cannot be nullable"); 345 } 346 type_suffix(ret); 347 return ret; 348 } 349 350 function single_type(typeName) { 351 const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE); 352 const generic = generic_type(typeName); 353 if (generic) { 354 return Object.assign(ret, generic); 355 } 356 const prim = primitive_type(); 357 let name; 358 if (prim) { 359 ret.idlType = prim; 360 } else if (name = consume(ID, ...stringTypes)) { 361 ret.idlType = name.value; 362 if (probe("<")) error(`Unsupported generic type ${name.value}`); 363 } else { 364 return; 365 } 366 type_suffix(ret); 367 if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable"); 368 return ret; 369 } 370 371 function union_type(typeName) { 372 if (!consume("(")) return; 373 const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE, { union: true, idlType: [] }); 374 do { 375 const typ = type_with_extended_attributes() || error("No type after open parenthesis or 'or' in union type"); 376 ret.idlType.push(typ); 377 } while (consume("or")); 378 if (ret.idlType.length < 2) { 379 error("At least two types are expected in a union type but found less"); 380 } 381 if (!consume(")")) error("Unterminated union type"); 382 type_suffix(ret); 383 return ret; 384 } 385 386 function type(typeName) { 387 return single_type(typeName) || union_type(typeName); 388 } 389 390 function type_with_extended_attributes(typeName) { 391 const extAttrs = extended_attrs(); 392 const ret = single_type(typeName) || union_type(typeName); 393 if (extAttrs.length && ret) ret.extAttrs = extAttrs; 394 return ret; 395 } 396 397 function argument() { 398 const start_position = consume_position; 399 const ret = { optional: false, variadic: false, default: null }; 400 ret.extAttrs = extended_attrs(); 401 const opt_token = consume("optional"); 402 if (opt_token) { 403 ret.optional = true; 404 } 405 ret.idlType = type_with_extended_attributes("argument-type"); 406 if (!ret.idlType) { 407 unconsume(start_position); 408 return; 409 } 410 if (!ret.optional && consume("...")) { 411 ret.variadic = true; 412 } 413 const name = consume(ID, ...argumentNameKeywords); 414 if (!name) { 415 unconsume(start_position); 416 return; 417 } 418 ret.name = unescape(name.value); 419 ret.escapedName = name.value; 420 if (ret.optional) { 421 ret.default = default_() || null; 422 } 423 return ret; 424 } 425 426 function argument_list() { 427 const ret = []; 428 const arg = argument(); 429 if (!arg) return ret; 430 ret.push(arg); 431 while (true) { 432 if (!consume(",")) return ret; 433 const nxt = argument() || error("Trailing comma in arguments list"); 434 ret.push(nxt); 435 } 436 } 437 438 function simple_extended_attr() { 439 const name = consume(ID); 440 if (!name) return; 441 const ret = { 442 name: name.value, 443 arguments: null, 444 type: "extended-attribute", 445 rhs: null 446 }; 447 const eq = consume("="); 448 if (eq) { 449 ret.rhs = consume(ID, FLOAT, INT, STR); 450 if (ret.rhs) { 451 // No trivia exposure yet 452 ret.rhs.trivia = undefined; 453 } 454 } 455 if (consume("(")) { 456 if (eq && !ret.rhs) { 457 // [Exposed=(Window,Worker)] 458 ret.rhs = { 459 type: "identifier-list", 460 value: identifiers() 461 }; 462 } 463 else { 464 // [NamedConstructor=Audio(DOMString src)] or [Constructor(DOMString str)] 465 ret.arguments = argument_list(); 466 } 467 consume(")") || error("Unexpected token in extended attribute argument list"); 468 } 469 if (eq && !ret.rhs) error("No right hand side to extended attribute assignment"); 470 return ret; 471 } 472 473 // Note: we parse something simpler than the official syntax. It's all that ever 474 // seems to be used 475 function extended_attrs() { 476 const eas = []; 477 if (!consume("[")) return eas; 478 eas[0] = simple_extended_attr() || error("Extended attribute with not content"); 479 while (consume(",")) { 480 eas.push(simple_extended_attr() || error("Trailing comma in extended attribute")); 481 } 482 consume("]") || error("No end of extended attribute"); 483 return eas; 484 } 485 486 function default_() { 487 if (consume("=")) { 488 const def = const_value(); 489 if (def) { 490 return def; 491 } else if (consume("[")) { 492 if (!consume("]")) error("Default sequence value must be empty"); 493 return { type: "sequence", value: [] }; 494 } else { 495 const str = consume(STR) || error("No value for default"); 496 str.value = str.value.slice(1, -1); 497 // No trivia exposure yet 498 str.trivia = undefined; 499 return str; 500 } 501 } 502 } 503 504 function const_() { 505 if (!consume("const")) return; 506 const ret = { type: "const", nullable: false }; 507 let typ = primitive_type(); 508 if (!typ) { 509 typ = consume(ID) || error("No type for const"); 510 typ = typ.value; 511 } 512 ret.idlType = Object.assign({ type: "const-type" }, EMPTY_IDLTYPE, { idlType: typ }); 513 type_suffix(ret); 514 const name = consume(ID) || error("No name for const"); 515 ret.name = name.value; 516 consume("=") || error("No value assignment for const"); 517 const cnt = const_value(); 518 if (cnt) ret.value = cnt; 519 else error("No value for const"); 520 consume(";") || error("Unterminated const"); 521 return ret; 522 } 523 524 function inheritance() { 525 if (consume(":")) { 526 const inh = consume(ID) || error("No type in inheritance"); 527 return inh.value; 528 } 529 } 530 531 function operation_rest(ret) { 532 if (!ret) ret = {}; 533 const name = consume(ID); 534 ret.name = name ? unescape(name.value) : null; 535 ret.escapedName = name ? name.value : null; 536 consume("(") || error("Invalid operation"); 537 ret.arguments = argument_list(); 538 consume(")") || error("Unterminated operation"); 539 consume(";") || error("Unterminated operation"); 540 return ret; 541 } 542 543 function callback() { 544 let ret; 545 if (!consume("callback")) return; 546 const tok = consume("interface"); 547 if (tok) { 548 ret = interface_rest(false, "callback interface"); 549 return ret; 550 } 551 const name = consume(ID) || error("No name for callback"); 552 ret = current = { type: "callback", name: sanitize_name(name.value, "callback") }; 553 consume("=") || error("No assignment in callback"); 554 ret.idlType = return_type() || error("Missing return type"); 555 consume("(") || error("No arguments in callback"); 556 ret.arguments = argument_list(); 557 consume(")") || error("Unterminated callback"); 558 consume(";") || error("Unterminated callback"); 559 return ret; 560 } 561 562 function attribute({ noInherit = false, readonly = false } = {}) { 563 const start_position = consume_position; 564 const ret = { 565 type: "attribute", 566 static: false, 567 stringifier: false, 568 inherit: false, 569 readonly: false 570 }; 571 if (!noInherit && consume("inherit")) { 572 ret.inherit = true; 573 } 574 if (consume("readonly")) { 575 ret.readonly = true; 576 } else if (readonly && probe("attribute")) { 577 error("Attributes must be readonly in this context"); 578 } 579 const rest = attribute_rest(ret); 580 if (!rest) { 581 unconsume(start_position); 582 } 583 return rest; 584 } 585 586 function attribute_rest(ret) { 587 if (!consume("attribute")) { 588 return; 589 } 590 ret.idlType = type_with_extended_attributes("attribute-type") || error("No type in attribute"); 591 if (ret.idlType.generic === "sequence") error("Attributes cannot accept sequence types"); 592 if (ret.idlType.generic === "record") error("Attributes cannot accept record types"); 593 const name = consume(ID, "required") || error("No name in attribute"); 594 ret.name = unescape(name.value); 595 ret.escapedName = name.value; 596 consume(";") || error("Unterminated attribute"); 597 return ret; 598 } 599 600 function return_type(typeName) { 601 const typ = type(typeName || "return-type"); 602 if (typ) { 603 return typ; 604 } 605 if (consume("void")) { 606 return Object.assign({ type: "return-type" }, EMPTY_IDLTYPE, { idlType: "void" }); 607 } 608 } 609 610 function operation({ regular = false } = {}) { 611 const ret = Object.assign({}, EMPTY_OPERATION); 612 while (!regular) { 613 if (consume("getter")) ret.getter = true; 614 else if (consume("setter")) ret.setter = true; 615 else if (consume("deleter")) ret.deleter = true; 616 else break; 617 } 618 ret.idlType = return_type() || error("Missing return type"); 619 operation_rest(ret); 620 return ret; 621 } 622 623 function static_member() { 624 if (!consume("static")) return; 625 const member = attribute({ noInherit: true }) || 626 operation({ regular: true }) || 627 error("No body in static member"); 628 member.static = true; 629 return member; 630 } 631 632 function stringifier() { 633 if (!consume("stringifier")) return; 634 if (consume(";")) { 635 return Object.assign({}, EMPTY_OPERATION, { stringifier: true }); 636 } 637 const member = attribute({ noInherit: true }) || 638 operation({ regular: true }) || 639 error("Unterminated stringifier"); 640 member.stringifier = true; 641 return member; 642 } 643 644 function identifiers() { 645 const arr = []; 646 const id = consume(ID); 647 if (id) { 648 arr.push(id.value); 649 } 650 else error("Expected identifiers but not found"); 651 while (true) { 652 if (consume(",")) { 653 const name = consume(ID) || error("Trailing comma in identifiers list"); 654 arr.push(name.value); 655 } else break; 656 } 657 return arr; 658 } 659 660 function iterable_type() { 661 if (consume("iterable")) return "iterable"; 662 else if (consume("legacyiterable")) return "legacyiterable"; 663 else if (consume("maplike")) return "maplike"; 664 else if (consume("setlike")) return "setlike"; 665 else return; 666 } 667 668 function readonly_iterable_type() { 669 if (consume("maplike")) return "maplike"; 670 else if (consume("setlike")) return "setlike"; 671 else return; 672 } 673 674 function iterable() { 675 const start_position = consume_position; 676 const ret = { type: null, idlType: null, readonly: false }; 677 if (consume("readonly")) { 678 ret.readonly = true; 679 } 680 const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type; 681 682 const ittype = consumeItType(); 683 if (!ittype) { 684 unconsume(start_position); 685 return; 686 } 687 688 const secondTypeRequired = ittype === "maplike"; 689 const secondTypeAllowed = secondTypeRequired || ittype === "iterable"; 690 ret.type = ittype; 691 if (ret.type !== 'maplike' && ret.type !== 'setlike') 692 delete ret.readonly; 693 if (consume("<")) { 694 ret.idlType = [type_with_extended_attributes()] || error(`Error parsing ${ittype} declaration`); 695 if (secondTypeAllowed) { 696 if (consume(",")) { 697 ret.idlType.push(type_with_extended_attributes()); 698 } 699 else if (secondTypeRequired) 700 error(`Missing second type argument in ${ittype} declaration`); 701 } 702 if (!consume(">")) error(`Unterminated ${ittype} declaration`); 703 if (!consume(";")) error(`Missing semicolon after ${ittype} declaration`); 704 } else 705 error(`Error parsing ${ittype} declaration`); 706 707 return ret; 708 } 709 710 function interface_rest(isPartial, typeName = "interface") { 711 const name = consume(ID) || error("No name for interface"); 712 const mems = []; 713 const ret = current = { 714 type: typeName, 715 name: isPartial ? name.value : sanitize_name(name.value, "interface"), 716 partial: isPartial, 717 members: mems 718 }; 719 if (!isPartial) ret.inheritance = inheritance() || null; 720 consume("{") || error("Bodyless interface"); 721 while (true) { 722 if (consume("}")) { 723 consume(";") || error("Missing semicolon after interface"); 724 return ret; 725 } 726 const ea = extended_attrs(); 727 const mem = const_() || 728 static_member() || 729 stringifier() || 730 iterable() || 731 attribute() || 732 operation() || 733 error("Unknown member"); 734 mem.extAttrs = ea; 735 ret.members.push(mem); 736 } 737 } 738 739 function mixin_rest(isPartial) { 740 if (!consume("mixin")) return; 741 const name = consume(ID) || error("No name for interface mixin"); 742 const mems = []; 743 const ret = current = { 744 type: "interface mixin", 745 name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"), 746 partial: isPartial, 747 members: mems 748 }; 749 consume("{") || error("Bodyless interface mixin"); 750 while (true) { 751 if (consume("}")) { 752 consume(";") || error("Missing semicolon after interface mixin"); 753 return ret; 754 } 755 const ea = extended_attrs(); 756 const mem = const_() || 757 stringifier() || 758 attribute({ noInherit: true }) || 759 operation({ regular: true }) || 760 error("Unknown member"); 761 mem.extAttrs = ea; 762 ret.members.push(mem); 763 } 764 } 765 766 function interface_(isPartial) { 767 if (!consume("interface")) return; 768 return mixin_rest(isPartial) || 769 interface_rest(isPartial) || 770 error("Interface has no proper body"); 771 } 772 773 function namespace(isPartial) { 774 if (!consume("namespace")) return; 775 const name = consume(ID) || error("No name for namespace"); 776 const mems = []; 777 const ret = current = { 778 type: "namespace", 779 name: isPartial ? name.value : sanitize_name(name.value, "namespace"), 780 partial: isPartial, 781 members: mems 782 }; 783 consume("{") || error("Bodyless namespace"); 784 while (true) { 785 if (consume("}")) { 786 consume(";") || error("Missing semicolon after namespace"); 787 return ret; 788 } 789 const ea = extended_attrs(); 790 const mem = attribute({ noInherit: true, readonly: true }) || 791 operation({ regular: true }) || 792 error("Unknown member"); 793 mem.extAttrs = ea; 794 ret.members.push(mem); 795 } 796 } 797 798 function partial() { 799 if (!consume("partial")) return; 800 const thing = dictionary(true) || 801 interface_(true) || 802 namespace(true) || 803 error("Partial doesn't apply to anything"); 804 return thing; 805 } 806 807 function dictionary(isPartial) { 808 if (!consume("dictionary")) return; 809 const name = consume(ID) || error("No name for dictionary"); 810 const mems = []; 811 const ret = current = { 812 type: "dictionary", 813 name: isPartial ? name.value : sanitize_name(name.value, "dictionary"), 814 partial: isPartial, 815 members: mems 816 }; 817 if (!isPartial) ret.inheritance = inheritance() || null; 818 consume("{") || error("Bodyless dictionary"); 819 while (true) { 820 if (consume("}")) { 821 consume(";") || error("Missing semicolon after dictionary"); 822 return ret; 823 } 824 const ea = extended_attrs(); 825 const required = consume("required"); 826 const typ = type_with_extended_attributes("dictionary-type") || error("No type for dictionary member"); 827 const name = consume(ID) || error("No name for dictionary member"); 828 const dflt = default_() || null; 829 if (required && dflt) error("Required member must not have a default"); 830 const member = { 831 type: "field", 832 name: unescape(name.value), 833 escapedName: name.value, 834 required: !!required, 835 idlType: typ, 836 extAttrs: ea, 837 default: dflt 838 }; 839 ret.members.push(member); 840 consume(";") || error("Unterminated dictionary member"); 841 } 842 } 843 844 function enum_() { 845 if (!consume("enum")) return; 846 const name = consume(ID) || error("No name for enum"); 847 const vals = []; 848 const ret = current = { 849 type: "enum", 850 name: sanitize_name(name.value, "enum"), 851 values: vals 852 }; 853 consume("{") || error("No curly for enum"); 854 let value_expected = true; 855 while (true) { 856 if (consume("}")) { 857 if (!ret.values.length) error("No value in enum"); 858 consume(";") || error("No semicolon after enum"); 859 return ret; 860 } 861 else if (!value_expected) { 862 error("No comma between enum values"); 863 } 864 const val = consume(STR) || error("Unexpected value in enum"); 865 val.value = val.value.slice(1, -1); 866 // No trivia exposure yet 867 val.trivia = undefined; 868 ret.values.push(val); 869 value_expected = !!consume(","); 870 } 871 } 872 873 function typedef() { 874 if (!consume("typedef")) return; 875 const ret = { 876 type: "typedef" 877 }; 878 ret.idlType = type_with_extended_attributes("typedef-type") || error("No type in typedef"); 879 const name = consume(ID) || error("No name in typedef"); 880 ret.name = sanitize_name(name.value, "typedef"); 881 current = ret; 882 consume(";") || error("Unterminated typedef"); 883 return ret; 884 } 885 886 function implements_() { 887 const start_position = consume_position; 888 const target = consume(ID); 889 if (!target) return; 890 if (consume("implements")) { 891 const ret = { 892 type: "implements", 893 target: target.value 894 }; 895 const imp = consume(ID) || error("Incomplete implements statement"); 896 ret.implements = imp.value; 897 consume(";") || error("No terminating ; for implements statement"); 898 return ret; 899 } else { 900 // rollback 901 unconsume(start_position); 902 } 903 } 904 905 function includes() { 906 const start_position = consume_position; 907 const target = consume(ID); 908 if (!target) return; 909 if (consume("includes")) { 910 const ret = { 911 type: "includes", 912 target: target.value 913 }; 914 const imp = consume(ID) || error("Incomplete includes statement"); 915 ret.includes = imp.value; 916 consume(";") || error("No terminating ; for includes statement"); 917 return ret; 918 } else { 919 // rollback 920 unconsume(start_position); 921 } 922 } 923 924 function definition() { 925 return callback() || 926 interface_(false) || 927 partial() || 928 dictionary(false) || 929 enum_() || 930 typedef() || 931 implements_() || 932 includes() || 933 namespace(false); 934 } 935 936 function definitions() { 937 if (!tokens.length) return []; 938 const defs = []; 939 while (true) { 940 const ea = extended_attrs(); 941 const def = definition(); 942 if (!def) { 943 if (ea.length) error("Stray extended attributes"); 944 break; 945 } 946 def.extAttrs = ea; 947 defs.push(def); 948 } 949 return defs; 950 } 951 const res = definitions(); 952 if (consume_position < tokens.length) error("Unrecognised tokens"); 953 return res; 954 } 955 956 const obj = { 957 parse(str) { 958 const tokens = tokenise(str); 959 return parse(tokens); 960 } 961 }; 962 963 if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 964 module.exports = obj; 965 } else if (typeof define === 'function' && define.amd) { 966 define([], () => obj); 967 } else { 968 (self || window).WebIDL2 = obj; 969 } 970})(); 971