@creditkarma/thrift-parser
Version:
A parser for Thrift written in TypeScript
772 lines • 31.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const factory_1 = require("./factory");
const debugger_1 = require("./debugger");
function isStatementBeginning(token) {
switch (token.type) {
case "NamespaceKeyword" /* NamespaceKeyword */:
case "IncludeKeyword" /* IncludeKeyword */:
case "ConstKeyword" /* ConstKeyword */:
case "StructKeyword" /* StructKeyword */:
case "UnionKeyword" /* UnionKeyword */:
case "ExceptionKeyword" /* ExceptionKeyword */:
case "ServiceKeyword" /* ServiceKeyword */:
case "TypedefKeyword" /* TypedefKeyword */:
case "EnumKeyword" /* EnumKeyword */:
return true;
default:
return false;
}
}
class ParseError extends Error {
constructor(msg, loc) {
super(msg);
this.message = msg;
this.loc = loc;
}
}
function createParser(tokens, report = debugger_1.noopReporter) {
let comments = [];
let currentIndex = 0;
// PUBLIC
function parse() {
const thrift = {
type: "ThriftDocument" /* ThriftDocument */,
body: [],
};
while (!isAtEnd()) {
try {
const statement = parseStatement();
if (statement !== null) {
thrift.body.push(statement);
}
}
catch (e) {
report(factory_1.createParseError(e.message, e.loc));
}
}
return thrift;
}
// Finds the beginning of the next statement so we can continue parse after error.
function synchronize() {
while (!isAtEnd() && !isStatementBeginning(currentToken())) {
advance();
}
}
function parseStatement() {
const next = currentToken();
// All Thrift statements must start with one of these types
switch (next.type) {
case "NamespaceKeyword" /* NamespaceKeyword */:
return parseNamespace();
case "IncludeKeyword" /* IncludeKeyword */:
return parseInclude();
case "ConstKeyword" /* ConstKeyword */:
return parseConst();
case "StructKeyword" /* StructKeyword */:
return parseStruct();
case "UnionKeyword" /* UnionKeyword */:
return parseUnion();
case "ExceptionKeyword" /* ExceptionKeyword */:
return parseException();
case "ServiceKeyword" /* ServiceKeyword */:
return parseService();
case "TypedefKeyword" /* TypedefKeyword */:
return parseTypedef();
case "EnumKeyword" /* EnumKeyword */:
return parseEnum();
case "CommentBlock" /* CommentBlock */:
case "CommentLine" /* CommentLine */:
consumeComments();
return null;
default:
reportError(`Invalid start to Thrift statement ${next.text}`);
}
}
// IncludeDefinition → 'include' StringLiteral
function parseInclude() {
const keywordToken = consume("IncludeKeyword" /* IncludeKeyword */);
const pathToken = consume("StringLiteral" /* StringLiteral */);
requireValue(pathToken, `Include statement must include a path as string literal`);
return {
type: "IncludeDefinition" /* IncludeDefinition */,
path: factory_1.createStringLiteral(pathToken.text, pathToken.loc),
comments: getComments(),
loc: factory_1.createTextLocation(keywordToken.loc.start, pathToken.loc.end),
};
}
// ServiceDefinition → 'service' Identifier ( 'extends' Identifier )? '{' Function* '}'
function parseService() {
const keywordToken = consume("ServiceKeyword" /* ServiceKeyword */);
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Unable to find identifier for service`);
const extendsId = parseExtends();
const openBrace = consume("LeftBraceToken" /* LeftBraceToken */);
requireValue(openBrace, `Expected opening curly brace`);
const leadingComments = getComments();
const functions = parseFunctions();
const closeBrace = consume("RightBraceToken" /* RightBraceToken */);
requireValue(closeBrace, `Expected closing curly brace`);
const location = factory_1.createTextLocation(keywordToken.loc.start, closeBrace.loc.end);
return {
type: "ServiceDefinition" /* ServiceDefinition */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
extends: extendsId,
functions,
comments: [
...leadingComments,
...getComments(),
],
loc: location,
};
}
function parseExtends() {
if (checkText('extends')) {
const keywordToken = consume("ExtendsKeyword" /* ExtendsKeyword */);
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Identifier expected after 'extends' keyword`);
return factory_1.createIdentifier(nameToken.text, factory_1.createTextLocation(keywordToken.loc.start, nameToken.loc.end));
}
else {
return null;
}
}
function parseFunctions() {
const functions = [];
while (!check("RightBraceToken" /* RightBraceToken */)) {
if (check("CommentBlock" /* CommentBlock */, "CommentLine" /* CommentLine */)) {
advance();
}
else {
functions.push(parseFunction());
if (isStatementBeginning(currentToken())) {
reportError(`Closing curly brace expected, but new statement found`);
}
else if (check("EOF" /* EOF */)) {
reportError(`Closing curly brace expected but reached end of file`);
}
}
}
return functions;
}
// Function → 'oneway'? FunctionType Identifier '(' Field* ')' Throws? ListSeparator?
function parseFunction() {
const onewayToken = consume("OnewayKeyword" /* OnewayKeyword */);
const returnType = parseFunctionType();
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Unable to find function identifier`);
const params = parseParameterFields();
requireValue(params, `List of zero or more fields expected`);
const throws = parseThrows();
const listSeparator = readListSeparator();
const endLoc = ((listSeparator !== null) ?
listSeparator.loc :
(throws !== null) ?
throws.loc :
params.loc);
return {
type: "FunctionDefinition" /* FunctionDefinition */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
returnType,
fields: params.fields,
throws: (throws !== null) ? throws.fields : [],
comments: getComments(),
oneway: (onewayToken !== null),
modifiers: ((onewayToken !== null) ?
[onewayToken] :
[]),
loc: {
start: returnType.loc.start,
end: endLoc.end,
},
};
}
function parseParameterFields() {
const fields = [];
const openParen = consume("LeftParenToken" /* LeftParenToken */);
requireValue(openParen, `Opening paren expected to start list of fields`);
while (!check("RightParenToken" /* RightParenToken */)) {
readListSeparator();
fields.push(parseField());
if (isStatementBeginning(currentToken())) {
reportError(`Closing paren ')' expected, but new statement found`);
}
else if (check("EOF" /* EOF */)) {
reportError(`Closing paren ')' expected but reached end of file`);
}
}
const closeParen = consume("RightParenToken" /* RightParenToken */);
requireValue(closeParen, `Closing paren expected to end list of fields`);
return {
type: "ParametersDefinition" /* ParametersDefinition */,
fields,
loc: {
start: openParen.loc.start,
end: closeParen.loc.end,
},
};
}
// Throws → 'throws' '(' Field* ')'
function parseThrows() {
if (check("ThrowsKeyword" /* ThrowsKeyword */)) {
const keywordToken = consume("ThrowsKeyword" /* ThrowsKeyword */);
const params = parseParameterFields();
return {
type: "ThrowsDefinition" /* ThrowsDefinition */,
fields: params.fields,
loc: {
start: keywordToken.loc.start,
end: params.loc.end,
},
};
}
return null;
}
// Namespace → 'namespace' ( NamespaceScope Identifier )
function parseNamespace() {
const keywordToken = consume("NamespaceKeyword" /* NamespaceKeyword */);
const scopeToken = consume("Identifier" /* Identifier */);
requireValue(scopeToken, `Unable to find scope identifier for namespace`);
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Unable to find name identifier for namespace`);
return {
type: "NamespaceDefinition" /* NamespaceDefinition */,
scope: factory_1.createIdentifier(scopeToken.text, scopeToken.loc),
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
comments: getComments(),
loc: factory_1.createTextLocation(keywordToken.loc.start, nameToken.loc.end),
};
}
// ConstDefinition → 'const' FieldType Identifier '=' ConstValue ListSeparator?
function parseConst() {
const keywordToken = consume("ConstKeyword" /* ConstKeyword */);
const fieldType = parseFieldType();
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Const definition must have a name`);
const initializer = parseValueAssignment();
requireValue(initializer, `Const must be initialized to a value`);
readListSeparator();
return {
type: "ConstDefinition" /* ConstDefinition */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
fieldType,
initializer,
comments: getComments(),
loc: {
start: keywordToken.loc.start,
end: initializer.loc.end,
},
};
}
function parseValueAssignment() {
if (check("EqualToken" /* EqualToken */)) {
advance();
return parseValue();
}
return null;
}
// TypedefDefinition → 'typedef' FieldType Identifier
function parseTypedef() {
const keywordToken = consume("TypedefKeyword" /* TypedefKeyword */);
const type = parseFieldType();
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Typedef is expected to have name and none found`);
return {
type: "TypedefDefinition" /* TypedefDefinition */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
definitionType: type,
comments: getComments(),
loc: {
start: keywordToken.loc.start,
end: nameToken.loc.end,
},
};
}
// EnumDefinition → 'enum' Identifier '{' EnumMember* '}'
function parseEnum() {
const keywordToken = consume("EnumKeyword" /* EnumKeyword */);
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Expected identifier for enum definition`);
const openBrace = consume("LeftBraceToken" /* LeftBraceToken */);
requireValue(openBrace, `Expected opening brace`);
const members = parseEnumMembers();
const closeBrace = consume("RightBraceToken" /* RightBraceToken */);
requireValue(closeBrace, `Expected closing brace`);
const loc = {
start: keywordToken.loc.start,
end: closeBrace.loc.end,
};
return {
type: "EnumDefinition" /* EnumDefinition */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
members,
comments: getComments(),
loc,
};
}
function parseEnumMembers() {
const members = [];
while (!check("RightBraceToken" /* RightBraceToken */)) {
if (check("CommentBlock" /* CommentBlock */, "CommentLine" /* CommentLine */)) {
advance();
}
else {
members.push(parseEnumMember());
// consume list separator if there is one
readListSeparator();
if (isStatementBeginning(currentToken())) {
reportError(`Closing curly brace expected, but new statement found`);
}
else if (check("EOF" /* EOF */)) {
reportError(`Closing curly brace expected but reached end of file`);
}
}
}
return members;
}
// EnumMember → (Identifier ('=' IntConstant)? ListSeparator?)*
function parseEnumMember() {
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `EnumMember must have identifier`);
let loc = null;
let initializer = null;
if (consume("EqualToken" /* EqualToken */) !== null) {
const numToken = consume("IntegerLiteral" /* IntegerLiteral */, "HexLiteral" /* HexLiteral */);
requireValue(numToken, `Equals token "=" must be followed by an Integer`);
initializer = parseIntValue(numToken);
loc = factory_1.createTextLocation(nameToken.loc.start, initializer.loc.end);
}
else {
loc = factory_1.createTextLocation(nameToken.loc.start, nameToken.loc.end);
}
return {
type: "EnumMember" /* EnumMember */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
initializer,
comments: getComments(),
loc,
};
}
// StructLike → ('struct' | 'union' | 'exception') Identifier 'xsd_all'? '{' Field* '}'
function parseStructLikeInterface() {
const keywordToken = consume("StructKeyword" /* StructKeyword */, "UnionKeyword" /* UnionKeyword */, "ExceptionKeyword" /* ExceptionKeyword */);
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Struct-like must have an identifier`);
const openBrace = consume("LeftBraceToken" /* LeftBraceToken */);
requireValue(openBrace, `Struct-like body must begin with opening curly brace '{'`);
const leadingComments = getComments();
const fields = parseFields();
const closeBrace = consume("RightBraceToken" /* RightBraceToken */);
requireValue(closeBrace, `Struct-like body must end with a closing curly brace '}'`);
return {
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
fields,
comments: [
...leadingComments,
...getComments(),
],
loc: {
start: keywordToken.loc.start,
end: closeBrace.loc.end,
},
};
}
// StructDefinition → 'struct' Identifier 'xsd_all'? '{' Field* '}'
function parseStruct() {
const parsedData = parseStructLikeInterface();
return {
type: "StructDefinition" /* StructDefinition */,
name: parsedData.name,
fields: parsedData.fields,
comments: parsedData.comments,
loc: parsedData.loc,
};
}
// UnioinDefinition → 'union' Identifier 'xsd_all'? '{' Field* '}'
function parseUnion() {
const parsedData = parseStructLikeInterface();
return {
type: "UnionDefinition" /* UnionDefinition */,
name: parsedData.name,
fields: parsedData.fields.map((next) => {
// As per the Thrift spec, all union fields are optional
next.requiredness = 'optional';
return next;
}),
comments: parsedData.comments,
loc: parsedData.loc,
};
}
// ExceptionDefinition → 'exception' Identifier '{' Field* '}'
function parseException() {
const parsedData = parseStructLikeInterface();
return {
type: "ExceptionDefinition" /* ExceptionDefinition */,
name: parsedData.name,
fields: parsedData.fields,
comments: parsedData.comments,
loc: parsedData.loc,
};
}
function parseFields() {
const fields = [];
while (!check("RightBraceToken" /* RightBraceToken */)) {
if (check("CommentBlock" /* CommentBlock */, "CommentLine" /* CommentLine */)) {
advance();
}
else {
fields.push(parseField());
if (isStatementBeginning(currentToken())) {
reportError(`Closing curly brace expected, but new statement found`);
}
else if (check("EOF" /* EOF */)) {
reportError(`Closing curly brace expected but reached end of file`);
}
}
}
return fields;
}
// Field → FieldID? FieldReq? FieldType Identifier ('= ConstValue)? XsdFieldOptions ListSeparator?
function parseField() {
const startLoc = currentToken().loc;
const fieldID = parseFieldId();
const fieldRequired = parserequireValuedness();
const fieldType = parseFieldType();
const nameToken = consume("Identifier" /* Identifier */);
requireValue(nameToken, `Unable to find identifier for field`);
const defaultValue = parseValueAssignment();
const listSeparator = readListSeparator();
const endLoc = ((listSeparator !== null) ?
listSeparator.loc :
(defaultValue !== null) ?
defaultValue.loc :
nameToken.loc);
const location = factory_1.createTextLocation(startLoc.start, endLoc.end);
return {
type: "FieldDefinition" /* FieldDefinition */,
name: factory_1.createIdentifier(nameToken.text, nameToken.loc),
fieldID,
fieldType,
requiredness: fieldRequired,
defaultValue,
comments: getComments(),
loc: location,
};
}
// ListSeparator → ',' | ';'
function readListSeparator() {
if (check("CommaToken" /* CommaToken */, "SemicolonToken" /* SemicolonToken */)) {
return advance();
}
return null;
}
// FieldRequired → 'required' | 'optional'
function parserequireValuedness() {
const current = currentToken();
if (current.text === 'required' || current.text === 'optional') {
advance();
return current.text;
}
return null;
}
// FieldID → IntConstant ':'
function parseFieldId() {
if (currentToken().type === "IntegerLiteral" /* IntegerLiteral */ &&
peek().type === "ColonToken" /* ColonToken */) {
const fieldIDToken = consume("IntegerLiteral" /* IntegerLiteral */);
const colonToken = consume("ColonToken" /* ColonToken */);
// return value of number token
return factory_1.createFieldID(parseInt(fieldIDToken.text), factory_1.createTextLocation(fieldIDToken.loc.start, colonToken.loc.end));
}
else {
return null;
}
}
// ConstValue → Literal | ConstMap | ConstList
function parseValue() {
const next = advance();
switch (next.type) {
case "Identifier" /* Identifier */:
return factory_1.createIdentifier(next.text, next.loc);
case "StringLiteral" /* StringLiteral */:
return factory_1.createStringLiteral(next.text, next.loc);
case "IntegerLiteral" /* IntegerLiteral */:
case "HexLiteral" /* HexLiteral */:
return parseIntValue(next);
case "FloatLiteral" /* FloatLiteral */:
case "ExponentialLiteral" /* ExponentialLiteral */:
return parseDoubleValue(next);
case "TrueKeyword" /* TrueKeyword */:
return factory_1.createBooleanLiteral(true, next.loc);
case "FalseKeyword" /* FalseKeyword */:
return factory_1.createBooleanLiteral(false, next.loc);
case "LeftBraceToken" /* LeftBraceToken */:
return parseMapValue();
case "LeftBracketToken" /* LeftBracketToken */:
return parseListValue();
default:
return null;
}
}
function parseIntValue(token) {
switch (token.type) {
case "IntegerLiteral" /* IntegerLiteral */:
return factory_1.createIntConstant(factory_1.createIntegerLiteral(token.text, token.loc), token.loc);
case "HexLiteral" /* HexLiteral */:
return factory_1.createIntConstant(factory_1.createHexLiteral(token.text, token.loc), token.loc);
default:
reportError(`IntConstant expected but found: ${token.type}`);
}
}
function parseDoubleValue(token) {
switch (token.type) {
case "FloatLiteral" /* FloatLiteral */:
return factory_1.createDoubleConstant(factory_1.createFloatLiteral(token.text, token.loc), token.loc);
case "ExponentialLiteral" /* ExponentialLiteral */:
return factory_1.createDoubleConstant(factory_1.createExponentialLiteral(token.text, token.loc), token.loc);
default:
reportError(`DoubleConstant expected but found: ${token.type}`);
}
}
// ConstMap → '{' (ConstValue ':' ConstValue ListSeparator?)* '}'
function parseMapValue() {
// The parseValue method has already advanced the cursor
const startLoc = currentToken().loc;
const properties = check("RightBraceToken" /* RightBraceToken */) ? [] : readMapValues();
const closeBrace = consume("RightBraceToken" /* RightBraceToken */);
requireValue(closeBrace, `Closing brace missing from map definition`);
const endLoc = closeBrace.loc;
const location = {
start: startLoc.start,
end: endLoc.end,
};
return factory_1.createConstMap(properties, location);
}
// ConstList → '[' (ConstValue ListSeparator?)* ']'
function parseListValue() {
// The parseValue method has already advanced the cursor
const startLoc = currentToken().loc;
const elements = check("RightBracketToken" /* RightBracketToken */) ? [] : readListValues();
const closeBrace = consume("RightBracketToken" /* RightBracketToken */);
requireValue(closeBrace, `Closing square-bracket missing from list definition`);
const endLoc = closeBrace.loc;
return factory_1.createConstList(elements, {
start: startLoc.start,
end: endLoc.end,
});
}
function readMapValues() {
const properties = [];
while (true) {
const key = parseValue();
const semicolon = consume("ColonToken" /* ColonToken */);
requireValue(semicolon, `Semicolon expected after key in map property assignment`);
const value = parseValue();
properties.push(factory_1.creataePropertyAssignment(key, value, {
start: key.loc.start,
end: value.loc.end,
}));
if (check("CommaToken" /* CommaToken */)) {
advance();
}
else {
break;
}
}
return properties;
}
function readListValues() {
const elements = [];
while (true) {
elements.push(parseValue());
if (check("CommaToken" /* CommaToken */, "SemicolonToken" /* SemicolonToken */)) {
advance();
}
else {
break;
}
}
return elements;
}
// FunctionType → FieldType | 'void'
function parseFunctionType() {
const typeToken = consume("VoidKeyword" /* VoidKeyword */);
if (typeToken !== null) {
return {
type: "VoidKeyword" /* VoidKeyword */,
loc: typeToken.loc,
};
}
else {
return parseFieldType();
}
}
// FieldType → Identifier | BaseType | ContainerType
function parseFieldType() {
const typeToken = advance();
switch (typeToken.type) {
case "Identifier" /* Identifier */:
return factory_1.createIdentifier(typeToken.text, typeToken.loc);
case "MapKeyword" /* MapKeyword */:
return parseMapType();
case "ListKeyword" /* ListKeyword */:
return parseListType();
case "SetKeyword" /* SetKeyword */:
return parseSetType();
case "BinaryKeyword" /* BinaryKeyword */:
case "BoolKeyword" /* BoolKeyword */:
case "ByteKeyword" /* ByteKeyword */:
case "StringKeyword" /* StringKeyword */:
case "I8Keyword" /* I8Keyword */:
case "I16Keyword" /* I16Keyword */:
case "I32Keyword" /* I32Keyword */:
case "I64Keyword" /* I64Keyword */:
case "DoubleKeyword" /* DoubleKeyword */:
return factory_1.createKeywordFieldType(typeToken.type, typeToken.loc);
default:
reportError(`FieldType expected but found: ${typeToken.type}`);
}
}
// MapType → 'map' CppType? '<' FieldType ',' FieldType '>'
function parseMapType() {
const openBracket = consume("LessThanToken" /* LessThanToken */);
requireValue(openBracket, `Map needs to defined contained types`);
const keyType = parseFieldType();
const commaToken = consume("CommaToken" /* CommaToken */);
requireValue(commaToken, `Comma expected to separate map types <key, value>`);
const valueType = parseFieldType();
const closeBracket = consume("GreaterThanToken" /* GreaterThanToken */);
requireValue(closeBracket, `Map needs to defined contained types`);
const location = {
start: openBracket.loc.start,
end: closeBracket.loc.end,
};
return factory_1.createMapFieldType(keyType, valueType, location);
}
// SetType → 'set' CppType? '<' FieldType '>'
function parseSetType() {
const openBracket = consume("LessThanToken" /* LessThanToken */);
requireValue(openBracket, `Map needs to defined contained types`);
const valueType = parseFieldType();
const closeBracket = consume("GreaterThanToken" /* GreaterThanToken */);
requireValue(closeBracket, `Map needs to defined contained types`);
return {
type: "SetType" /* SetType */,
valueType,
loc: {
start: openBracket.loc.start,
end: closeBracket.loc.end,
},
};
}
// ListType → 'list' '<' FieldType '>' CppType?
function parseListType() {
const openBracket = consume("LessThanToken" /* LessThanToken */);
requireValue(openBracket, `Map needs to defined contained types`);
const valueType = parseFieldType();
const closeBracket = consume("GreaterThanToken" /* GreaterThanToken */);
requireValue(closeBracket, `Map needs to defined contained types`);
return {
type: "ListType" /* ListType */,
valueType,
loc: {
start: openBracket.loc.start,
end: closeBracket.loc.end,
},
};
}
function consumeComments() {
while (true) {
const next = tokens[currentIndex];
switch (next.type) {
case "CommentBlock" /* CommentBlock */:
comments.push({
type: next.type,
value: next.text.split('\n'),
loc: next.loc,
});
currentIndex++;
break;
case "CommentLine" /* CommentLine */:
comments.push({
type: next.type,
value: next.text,
loc: next.loc,
});
currentIndex++;
break;
default:
return;
}
}
}
function currentToken() {
consumeComments();
return tokens[currentIndex];
}
function previousToken() {
return tokens[currentIndex - 1];
}
function peek() {
return tokens[currentIndex + 1];
}
// Does the current token match the given type
function check(...types) {
for (const type of types) {
if (type === currentToken().type) {
return true;
}
}
return false;
}
// Does the current token match the given text
function checkText(...strs) {
for (const str of strs) {
if (str === currentToken().text) {
return true;
}
}
return false;
}
// requireToken the current token to match given type and advance, otherwise return null
function consume(...types) {
for (const type of types) {
if (check(type)) {
return advance();
}
}
return null;
}
// Move the cursor forward and return the previous token
function advance() {
if (!isAtEnd()) {
currentIndex += 1;
}
return previousToken();
}
function isAtEnd() {
return (currentIndex >= tokens.length ||
currentToken().type === "EOF" /* EOF */);
}
function getComments() {
const current = comments;
comments = [];
return current;
}
function reportError(msg) {
throw new ParseError(msg, previousToken().loc);
}
// Throw if the given value doesn't exist.
function requireValue(val, msg) {
if (val === null || val === undefined) {
reportError(msg);
}
return val;
}
return {
parse,
synchronize,
};
}
exports.createParser = createParser;
//# sourceMappingURL=parser.js.map