Skip to main content
Module

x/pbkit/core/parser/proto.ts

Protobuf toolkit for modern web development
Go to Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526
import * as ast from "../ast/index.ts";import { createRecursiveDescentParser, Pattern, RecursiveDescentParser, Span, SyntaxError, Token,} from "./recursive-descent-parser.ts";
export interface ParseResult<T = ast.Proto> { ast: T; parser: RecursiveDescentParser;}
export function parse(text: string): ParseResult { const parser = createRecursiveDescentParser(text); const statements = acceptStatements<ast.TopLevelStatement>(parser, [ acceptSyntax, acceptImport, acceptPackage, acceptOption, acceptMessage, acceptEnum, acceptExtend, acceptService, acceptEmpty, ]); const ast: ast.Proto = { statements }; return { ast, parser };}
export function parseConstant(text: string): ParseResult<ast.Constant> { const parser = createRecursiveDescentParser(text); const constant = expectConstant(parser); return { ast: constant, parser };}
function mergeSpans(spans: (undefined | Span | (undefined | Span)[])[]): Span { let start = Infinity; let end = -Infinity; for (let i = 0; i < spans.length; ++i) { if (spans[i] == null) continue; const span = Array.isArray(spans[i]) ? mergeSpans(spans[i] as Span[]) : spans[i] as Span; start = Math.min(start, span.start); end = Math.max(end, span.end); } return { start, end };}
interface AcceptFn<T> { (parser: RecursiveDescentParser): T | undefined;}
function acceptPatternAndThen<T>( pattern: Pattern, then: (token: Token) => T,): AcceptFn<T> { return function accept(parser) { const token = parser.accept(pattern); if (!token) return; return then(token); };}
function choice<T>(acceptFns: AcceptFn<T>[]): AcceptFn<T> { return function accept(parser) { for (const acceptFn of acceptFns) { const node = acceptFn(parser); if (node) return node; } };}
function many<T>(parser: RecursiveDescentParser, acceptFn: AcceptFn<T>): T[] { const nodes: T[] = []; let node: ReturnType<typeof acceptFn>; while (node = acceptFn(parser)) nodes.push(node); return nodes;}
// https://dev.to/svehla/typescript-object-fromentries-389ctype ArrayElement<A> = A extends readonly (infer T)[] ? T : never;type Cast<X, Y> = X extends Y ? X : Y;type FromEntries<T> = T extends [infer Key, any][] ? { [K in Cast<Key, string>]: Extract<ArrayElement<T>, [K, any]>[1] } : { [key in string]: any };type DeepWriteable<T> = T extends Function ? T : { -readonly [key in keyof T]: DeepWriteable<T[key]> };type AcceptComplexSequenceResult< T extends Record<string, AcceptFn<any>>,> = { partial: false; result: { [key in keyof T]: ReturnType<T[key]> };} | { partial: true; result: { [key in keyof T]?: ReturnType<T[key]> };};function acceptComplexSequence< T extends readonly (readonly [string, AcceptFn<any>])[],>( parser: RecursiveDescentParser, expectFnSeq: T, escapePattern?: Pattern,): AcceptComplexSequenceResult<FromEntries<DeepWriteable<T>>> { const result: any = {}; let partial = false; let hasNewline = false; let recoveryPoint: { loc: number; result: any } | undefined; for (const [key, expectFn] of expectFnSeq) { const loc = parser.loc; hasNewline = skipWsAndComments2(parser); if (hasNewline && !recoveryPoint) { recoveryPoint = { loc: parser.loc, result: { ...result } }; } try { result[key] = expectFn(parser); } catch { parser.loc = loc; partial = true; if (escapePattern && parser.try(escapePattern)) break; } } if (partial && recoveryPoint) { parser.loc = recoveryPoint.loc; return { partial, result: recoveryPoint.result }; } return { partial, result };}
interface AcceptStatementFn<T extends ast.StatementBase> { ( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[], ): T | undefined;}function acceptStatements<T extends ast.StatementBase>( parser: RecursiveDescentParser, acceptStatementFns: AcceptStatementFn<T>[],) { const statements: T[] = []; statements: while (true) { const { commentGroups, trailingNewline } = skipWsAndSweepComments(parser); let leadingComments: ast.CommentGroup[]; let leadingDetachedComments: ast.CommentGroup[]; if (trailingNewline) { leadingComments = []; leadingDetachedComments = commentGroups; } else { if (commentGroups.length < 1) { leadingComments = []; leadingDetachedComments = []; } else { leadingComments = [commentGroups.pop()!]; leadingDetachedComments = commentGroups; } } for (const acceptStatementFn of acceptStatementFns) { const statement = acceptStatementFn( parser, leadingComments, leadingDetachedComments, ); if (statement) { statements.push(statement); continue statements; } } break; } return statements;}
const whitespacePattern = /^\s+/;const whitespaceWithoutNewlinePattern = /^[ \f\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/;const newlinePattern = /^\r?\n/;const multilineCommentPattern = /^\/\*(?:.|\r?\n)*?\*\//;const singlelineCommentPattern = /^\/\/.*(?:\r?\n|$)/;const intLitPattern = /^0(?:x[0-9a-f]+|[0-7]*)|^[1-9]\d*/i;const floatLitPattern = /^\d+\.\d*(?:e[-+]?\d+)?|^\d+e[-+]?\d+|^\.\d+(?:e[-+]?\d+)?|^inf|^nan/i;const boolLitPattern = /^true|^false/;const strLitPattern = /^'(?:\\x[0-9a-f]{2}|\\[0-7]{3}|\\[0-7]|\\[abfnrtv\\\?'"]|[^'\0\n\\])*'|^"(?:\\x[0-9a-f]{2}|\\[0-7]{3}|\\[0-7]|\\[abfnrtv\\\?'"]|[^"\0\n\\])*"/i;const identPattern = /^[a-z_][a-z0-9_]*/i;const messageBodyStatementKeywordPattern = /^(?:enum|message|extend|extensions|group|option|oneof|map|reserved)\b/;
const acceptDot = acceptPatternAndThen<ast.Dot>( ".", (dot) => ({ type: "dot", ...dot }),);const acceptComma = acceptPatternAndThen<ast.Comma>( ",", (comma) => ({ type: "comma", ...comma }),);const acceptSemi = acceptPatternAndThen<ast.Semi>( ";", (semi) => ({ type: "semi", ...semi }),);function expectSemi(parser: RecursiveDescentParser): ast.Semi { const semi = acceptSemi(parser); if (semi) return semi; throw new SyntaxError(parser, [";"]);}const acceptIdent = acceptPatternAndThen<ast.Ident>( identPattern, (ident) => ({ type: "ident", ...ident }),);
function acceptSpecialToken<TType extends string>( parser: RecursiveDescentParser, type: TType, pattern: Pattern = identPattern,): (Token & { type: TType }) | undefined { const token = parser.accept(pattern); if (!token) return; return { type, ...token };}
function acceptKeyword( parser: RecursiveDescentParser, pattern: Pattern = identPattern,): ast.Keyword | undefined { return acceptSpecialToken(parser, "keyword", pattern);}
function acceptCommentGroup( parser: RecursiveDescentParser,): ast.CommentGroup | undefined { const loc = parser.loc; const comments: ast.Comment[] = []; while (true) { const whitespace = parser.accept(whitespaceWithoutNewlinePattern); if (whitespace) continue; const multilineComment = acceptSpecialToken( parser, "multiline-comment", multilineCommentPattern, ); if (multilineComment) { comments.push(multilineComment); continue; } const singlelineComment = acceptSpecialToken( parser, "singleline-comment", singlelineCommentPattern, ); if (singlelineComment) { comments.push(singlelineComment); continue; } break; } if (comments.length < 1) { parser.loc = loc; return; } return { ...mergeSpans(comments), type: "comment-group", comments, };}
function acceptTrailingComments( parser: RecursiveDescentParser,): ast.CommentGroup[] { const loc = parser.loc; const comments: ast.Comment[] = []; while (true) { const whitespace = parser.accept(whitespaceWithoutNewlinePattern); if (whitespace) continue; const newline = parser.accept(newlinePattern); if (newline) break; const multilineComment = acceptSpecialToken( parser, "multiline-comment", multilineCommentPattern, ); if (multilineComment) { comments.push(multilineComment); continue; } const singlelineComment = acceptSpecialToken( parser, "singleline-comment", singlelineCommentPattern, ); if (singlelineComment) { comments.push(singlelineComment); break; } break; } if (comments.length < 1) { parser.loc = loc; return []; } return [{ ...mergeSpans(comments), type: "comment-group", comments, }];}
interface SkipWsAndSweepCommentsResult { commentGroups: ast.CommentGroup[]; trailingNewline: boolean;}function skipWsAndSweepComments( parser: RecursiveDescentParser,): SkipWsAndSweepCommentsResult { const commentGroups: ast.CommentGroup[] = []; let trailingNewline = false; parser.accept(whitespacePattern); while (true) { const commentGroup = acceptCommentGroup(parser); if (commentGroup) { commentGroups.push(commentGroup); trailingNewline = false; continue; } const whitespace = parser.accept(whitespaceWithoutNewlinePattern); if (whitespace) continue; const newline = parser.accept(newlinePattern); if (newline) { trailingNewline = true; continue; } break; } return { commentGroups, trailingNewline, };}
function skipWsAndComments(parser: RecursiveDescentParser): undefined { while (true) { const whitespace = parser.accept(whitespacePattern); if (whitespace) continue; const multilineComment = acceptSpecialToken( parser, "multiline-comment", multilineCommentPattern, ); if (multilineComment) continue; const singlelineComment = acceptSpecialToken( parser, "singleline-comment", singlelineCommentPattern, ); if (singlelineComment) continue; break; } return;}
function skipWsAndComments2(parser: RecursiveDescentParser): boolean { let hasNewline = false; while (true) { const whitespace = parser.accept(whitespaceWithoutNewlinePattern); if (whitespace) continue; const newline = parser.accept(newlinePattern); if (newline) { hasNewline = true; continue; } const multilineComment = acceptSpecialToken( parser, "multiline-comment", multilineCommentPattern, ); if (multilineComment) continue; const singlelineComment = acceptSpecialToken( parser, "singleline-comment", singlelineCommentPattern, ); if (singlelineComment) { hasNewline = true; continue; } break; } return hasNewline;}
function acceptFullIdent( parser: RecursiveDescentParser,): ast.FullIdent | undefined { const identOrDots = many( parser, choice<ast.Dot | ast.Ident>([ acceptDot, acceptIdent, ]), ); if (identOrDots.length < 1) return; return { ...mergeSpans(identOrDots), type: "full-ident", identOrDots, };}
function expectFullIdent(parser: RecursiveDescentParser): ast.FullIdent { const fullIdent = acceptFullIdent(parser); if (fullIdent) return fullIdent; throw new SyntaxError(parser, [".", identPattern]);}
function acceptType( parser: RecursiveDescentParser,): ast.Type | undefined { const identOrDots = many( parser, choice<ast.Dot | ast.Ident>([ acceptDot, acceptIdent, ]), ); if (identOrDots.length < 1) return; return { ...mergeSpans(identOrDots), type: "type", identOrDots, };}
function expectType(parser: RecursiveDescentParser): ast.Type { const type = acceptType(parser); if (type) return type; throw new SyntaxError(parser, [".", identPattern]);}
function acceptIntLit(parser: RecursiveDescentParser): ast.IntLit | undefined { const intLit = parser.accept(intLitPattern); if (!intLit) return; return { type: "int-lit", ...intLit };}
function expectIntLit(parser: RecursiveDescentParser): ast.IntLit { const intLit = acceptIntLit(parser); if (intLit) return intLit; throw new SyntaxError(parser, [intLitPattern]);}
function acceptSignedIntLit( parser: RecursiveDescentParser,): ast.SignedIntLit | undefined { const loc = parser.loc; const sign = parser.accept("-") ?? parser.accept("+"); const intLit = acceptIntLit(parser); if (!intLit) { parser.loc = loc; return; } return { ...mergeSpans([sign, intLit]), type: "signed-int-lit", sign, value: intLit, };}
function expectSignedIntLit(parser: RecursiveDescentParser): ast.SignedIntLit { const signedIntLit = acceptSignedIntLit(parser); if (signedIntLit) return signedIntLit; throw new SyntaxError(parser, ["-", intLitPattern]);}
function acceptFloatLit( parser: RecursiveDescentParser,): ast.FloatLit | undefined { const floatLit = parser.accept(floatLitPattern); if (!floatLit) return; return { type: "float-lit", ...floatLit };}
function acceptSignedFloatLit( parser: RecursiveDescentParser,): ast.SignedFloatLit | undefined { const loc = parser.loc; const sign = parser.accept("-") ?? parser.accept("+"); const floatLit = acceptFloatLit(parser); if (!floatLit) { parser.loc = loc; return; } return { ...mergeSpans([sign, floatLit]), type: "signed-float-lit", sign, value: floatLit, };}
function acceptBoolLit( parser: RecursiveDescentParser,): ast.BoolLit | undefined { const boolLit = parser.accept(boolLitPattern); if (!boolLit) return; return { type: "bool-lit", ...boolLit };}
function acceptStrLit(parser: RecursiveDescentParser): ast.StrLit | undefined { const strLit = parser.accept(strLitPattern); if (!strLit) return; const tokens = [strLit]; while (true) { skipWsAndComments(parser); const strLit = parser.accept(strLitPattern); if (!strLit) break; tokens.push(strLit); } return { ...mergeSpans(tokens), type: "str-lit", tokens };}
function expectStrLit(parser: RecursiveDescentParser): ast.StrLit { const strLit = acceptStrLit(parser); if (strLit) return strLit; throw new SyntaxError(parser, [strLitPattern]);}
// https://github.com/protocolbuffers/protobuf/blob/c2148566c7/src/google/protobuf/compiler/parser.cc#L1429-L1452function acceptAggregate( parser: RecursiveDescentParser,): ast.Aggregate | undefined { const parenthesisOpen = parser.accept("{"); if (!parenthesisOpen) return; let character = parenthesisOpen; let depth = 1; while (character = parser.expect(/^(?:\s|\S)/)) { switch (character.text) { case "{": ++depth; break; case "}": --depth; break; } if (depth === 0) { break; } } return { ...mergeSpans([parenthesisOpen, character]), type: "aggregate", };}
function acceptConstant( parser: RecursiveDescentParser,): ast.Constant | undefined { return acceptSignedFloatLit(parser) ?? acceptSignedIntLit(parser) ?? acceptStrLit(parser) ?? acceptBoolLit(parser) ?? acceptFullIdent(parser) ?? acceptAggregate(parser);}
function expectConstant(parser: RecursiveDescentParser): ast.Constant { const constant = acceptConstant(parser); if (constant) return constant; throw new SyntaxError(parser, [ identPattern, "-", "+", intLitPattern, strLitPattern, boolLitPattern, ]);}
function acceptOptionNameSegment( parser: RecursiveDescentParser,): ast.OptionNameSegment | undefined { const bracketOpen = parser.accept("("); const name = acceptFullIdent(parser); if (!name) { if (bracketOpen) throw new SyntaxError(parser, [identPattern]); return; } const bracketClose = parser[bracketOpen ? "expect" : "accept"](")"); return { ...mergeSpans([bracketOpen, name, bracketClose]), type: "option-name-segment", bracketOpen, name, bracketClose, };}
function acceptOptionName( parser: RecursiveDescentParser,): ast.OptionName | undefined { const optionNameSegmentOrDots = many( parser, choice<ast.Dot | ast.OptionNameSegment>([ acceptDot, acceptOptionNameSegment, ]), ); if (optionNameSegmentOrDots.length < 1) return; return { ...mergeSpans(optionNameSegmentOrDots), type: "option-name", optionNameSegmentOrDots, };}
function expectOptionName(parser: RecursiveDescentParser): ast.OptionName { const optionName = acceptOptionName(parser); if (optionName) return optionName; throw new SyntaxError(parser, ["(", identPattern]);}
function acceptSyntax( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Syntax | undefined { const keyword = acceptKeyword(parser, "syntax"); if (!keyword) return; skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const quoteOpen = parser.expect(/^['"]/); const syntax = parser.expect(/^[^'"]+/); const quoteClose = parser.expect(/^['"]/); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "syntax", keyword, eq, quoteOpen, syntax, quoteClose, semi, };}
function acceptImport( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Import | undefined { const keyword = acceptKeyword(parser, "import"); if (!keyword) return; skipWsAndComments(parser); const weakOrPublic = parser.accept(/^weak|^public/); skipWsAndComments(parser); const strLit = expectStrLit(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "import", keyword, weakOrPublic, strLit, semi, };}
function acceptPackage( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Package | undefined { const keyword = acceptKeyword(parser, "package"); if (!keyword) return; skipWsAndComments(parser); const fullIdent = expectFullIdent(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "package", keyword, fullIdent, semi, };}
function acceptOption( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Option | undefined { const keyword = acceptKeyword(parser, /^option\b/); if (!keyword) return; skipWsAndComments(parser); const optionName = expectOptionName(parser); skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const constant = expectConstant(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "option", keyword, optionName, eq, constant, semi, };}
function acceptEmpty( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Empty | undefined { const semi = acceptSemi(parser); if (!semi) return; const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "empty", semi, };}
function acceptFieldOption( parser: RecursiveDescentParser,): ast.FieldOption | undefined { const optionName = acceptOptionName(parser); if (!optionName) return; skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const constant = expectConstant(parser); return { ...mergeSpans([optionName, constant]), type: "field-option", optionName, eq, constant, };}
function acceptFieldOptions( parser: RecursiveDescentParser,): ast.FieldOptions | undefined { const bracketOpen = parser.accept("["); if (!bracketOpen) return; const fieldOptionOrCommas = many( parser, choice<ast.Comma | ast.FieldOption>([ skipWsAndComments, acceptComma, acceptFieldOption, ]), ); const bracketClose = parser.expect("]"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "field-options", bracketOpen, fieldOptionOrCommas, bracketClose, };}
function acceptEnumField( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.EnumField | undefined { const fieldName = parser.accept(identPattern); if (!fieldName) return; skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const fieldNumber = expectSignedIntLit(parser); skipWsAndComments(parser); const fieldOptions = acceptFieldOptions(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, fieldName, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "enum-field", fieldName, eq, fieldNumber, fieldOptions, semi, };}
function expectEnumBody(parser: RecursiveDescentParser): ast.EnumBody { const bracketOpen = parser.expect("{"); const statements = acceptStatements<ast.EnumBodyStatement>(parser, [ acceptOption, acceptReserved, acceptEnumField, acceptEmpty, ]); const bracketClose = parser.expect("}"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "enum-body", bracketOpen, statements, bracketClose, };}
function acceptEnum( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Enum | undefined { const keyword = acceptKeyword(parser, "enum"); if (!keyword) return; skipWsAndComments(parser); const enumName = parser.expect(identPattern); skipWsAndComments(parser); const enumBody = expectEnumBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, enumBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "enum", keyword, enumName, enumBody, };}
function acceptField( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Field | ast.MalformedField | undefined { const loc = parser.loc; const fieldLabel = acceptKeyword(parser, /^required|^optional|^repeated/); skipWsAndComments(parser); const fieldType = acceptType(parser); if (!fieldType) { parser.loc = loc; return; } const rest = acceptComplexSequence( parser, [ ["fieldName", (parser) => parser.expect(identPattern)], ["eq", (parser) => parser.expect("=")], ["fieldNumber", expectIntLit], ["fieldOptions", acceptFieldOptions], ["semi", expectSemi], ] as const, messageBodyStatementKeywordPattern, ); const trailingComments = rest.result.semi ? acceptTrailingComments(parser) : []; const type = rest.partial ? "malformed-field" : "field"; return { ...mergeSpans([ leadingDetachedComments, leadingComments, fieldLabel, fieldType, rest.result.semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: type as any, fieldLabel, fieldType, ...rest.result, };}
function acceptOneofField( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.OneofField | undefined { const fieldType = acceptType(parser); if (!fieldType) return; skipWsAndComments(parser); const fieldName = parser.expect(identPattern); skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const fieldNumber = expectIntLit(parser); skipWsAndComments(parser); const fieldOptions = acceptFieldOptions(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, fieldType, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "oneof-field", fieldType, fieldName, eq, fieldNumber, fieldOptions, semi, };}
function acceptMapField( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.MapField | undefined { const keyword = acceptKeyword(parser, "map"); if (!keyword) return; skipWsAndComments(parser); const typeBracketOpen = parser.expect("<"); skipWsAndComments(parser); const keyType = expectType(parser); skipWsAndComments(parser); const typeSep = parser.expect(","); skipWsAndComments(parser); const valueType = expectType(parser); skipWsAndComments(parser); const typeBracketClose = parser.expect(">"); skipWsAndComments(parser); const mapName = parser.expect(identPattern); skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const fieldNumber = expectIntLit(parser); skipWsAndComments(parser); const fieldOptions = acceptFieldOptions(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "map-field", keyword, typeBracketOpen, keyType, typeSep, valueType, typeBracketClose, mapName, eq, fieldNumber, fieldOptions, semi, };}
function expectOneofBody(parser: RecursiveDescentParser): ast.OneofBody { const bracketOpen = parser.expect("{"); const statements = acceptStatements<ast.OneofBodyStatement>(parser, [ acceptOneofGroup, acceptOption, acceptOneofField, acceptEmpty, ]); const bracketClose = parser.expect("}"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "oneof-body", bracketOpen, statements, bracketClose, };}
function acceptOneof( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Oneof | undefined { const keyword = acceptKeyword(parser, "oneof"); if (!keyword) return; skipWsAndComments(parser); const oneofName = parser.expect(identPattern); skipWsAndComments(parser); const oneofBody = expectOneofBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, oneofBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "oneof", keyword, oneofName, oneofBody, };}
const acceptMax = acceptPatternAndThen<ast.Max>( "max", (max) => ({ type: "max", ...max }),);
function acceptRange(parser: RecursiveDescentParser): ast.Range | undefined { const rangeStart = acceptIntLit(parser); if (!rangeStart) return; skipWsAndComments(parser); const to = acceptKeyword(parser, "to"); if (!to) { return { start: rangeStart.start, end: rangeStart.end, type: "range", rangeStart, }; } skipWsAndComments(parser); const rangeEnd = acceptIntLit(parser) ?? acceptMax(parser); if (!rangeEnd) throw new SyntaxError(parser, [intLitPattern, "max"]); return { ...mergeSpans([rangeStart, rangeEnd]), type: "range", rangeStart, to, rangeEnd, };}
function expectRanges(parser: RecursiveDescentParser): ast.Ranges { const rangeOrCommas = many( parser, choice<ast.Range | ast.Comma>([ skipWsAndComments, acceptComma, acceptRange, ]), ); return { ...mergeSpans(rangeOrCommas), type: "ranges", rangeOrCommas, };}
function acceptExtensions( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Extensions | undefined { const keyword = acceptKeyword(parser, "extensions"); if (!keyword) return; skipWsAndComments(parser); const ranges = expectRanges(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "extensions", keyword, ranges, semi, };}
function expectFieldNames(parser: RecursiveDescentParser): ast.FieldNames { const strLitOrCommas = many( parser, choice<ast.StrLit | ast.Comma>([ skipWsAndComments, acceptComma, acceptStrLit, ]), ); return { ...mergeSpans(strLitOrCommas), type: "field-names", strLitOrCommas, };}
function acceptReserved( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Reserved | undefined { const keyword = acceptKeyword(parser, "reserved"); if (!keyword) return; skipWsAndComments(parser); const reserved = parser.try(intLitPattern) ? expectRanges(parser) : expectFieldNames(parser); skipWsAndComments(parser); const semi = expectSemi(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semi, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "reserved", keyword, reserved, semi, };}
function expectExtendBody(parser: RecursiveDescentParser): ast.ExtendBody { const bracketOpen = parser.expect("{"); const statements = acceptStatements<ast.ExtendBodyStatement>(parser, [ acceptGroup, acceptField, acceptEmpty, ]); const bracketClose = parser.expect("}"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "extend-body", bracketOpen, statements, bracketClose, };}
function acceptExtend( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Extend | undefined { const keyword = acceptKeyword(parser, "extend"); if (!keyword) return; skipWsAndComments(parser); const messageType = expectType(parser); skipWsAndComments(parser); const extendBody = expectExtendBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, extendBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "extend", keyword, messageType, extendBody, };}
function acceptGroup( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Group | undefined { const loc = parser.loc; const groupLabel = acceptKeyword(parser, /^required|^optional|^repeated/); if (!groupLabel) { parser.loc = loc; return; } skipWsAndComments(parser); const keyword = acceptKeyword(parser, "group"); if (!keyword) { parser.loc = loc; return; } skipWsAndComments(parser); const groupName = parser.expect(identPattern); skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const fieldNumber = expectIntLit(parser); skipWsAndComments(parser); const fieldOptions = acceptFieldOptions(parser); skipWsAndComments(parser); const messageBody = expectMessageBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, groupLabel, messageBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "group", groupLabel, keyword, groupName, eq, fieldNumber, fieldOptions, messageBody, };}
function acceptOneofGroup( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.OneofGroup | undefined { const keyword = acceptKeyword(parser, "group"); if (!keyword) return; skipWsAndComments(parser); const groupName = parser.expect(identPattern); skipWsAndComments(parser); const eq = parser.expect("="); skipWsAndComments(parser); const fieldNumber = expectIntLit(parser); skipWsAndComments(parser); const messageBody = expectMessageBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, messageBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "oneof-group", keyword, groupName, eq, fieldNumber, messageBody, };}
function expectMessageBody(parser: RecursiveDescentParser): ast.MessageBody { const bracketOpen = parser.expect("{"); const statements = acceptStatements<ast.MessageBodyStatement>(parser, [ acceptGroup, acceptEnum, acceptMessage, acceptExtend, acceptExtensions, acceptOption, acceptOneof, acceptMapField, acceptReserved, acceptField, acceptEmpty, ]); const bracketClose = parser.expect("}"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "message-body", bracketOpen, statements, bracketClose, };}
function acceptMessage( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Message | undefined { const keyword = acceptKeyword(parser, "message"); if (!keyword) return; skipWsAndComments(parser); const messageName = parser.expect(identPattern); skipWsAndComments(parser); const messageBody = expectMessageBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, messageBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "message", keyword, messageName, messageBody, };}
function expectRpcType(parser: RecursiveDescentParser): ast.RpcType { const bracketOpen = parser.expect("("); skipWsAndComments(parser); const stream = acceptKeyword(parser, "stream"); skipWsAndComments(parser); const messageType = expectType(parser); skipWsAndComments(parser); const bracketClose = parser.expect(")"); return { ...mergeSpans([bracketOpen, bracketClose]), bracketOpen, stream, messageType, bracketClose, };}
function acceptRpc( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Rpc | undefined { const keyword = acceptKeyword(parser, "rpc"); if (!keyword) return; skipWsAndComments(parser); const rpcName = parser.expect(identPattern); skipWsAndComments(parser); const reqType = expectRpcType(parser); skipWsAndComments(parser); const returns = parser.expect("returns"); skipWsAndComments(parser); const resType = expectRpcType(parser); skipWsAndComments(parser); const semiOrRpcBody = acceptSemi(parser) ?? expectRpcBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, semiOrRpcBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "rpc", keyword, rpcName, reqType, returns, resType, semiOrRpcBody, };}
function expectRpcBody(parser: RecursiveDescentParser): ast.RpcBody { const bracketOpen = parser.expect("{"); const statements = acceptStatements<ast.RpcBodyStatement>(parser, [ acceptOption, acceptEmpty, ]); const bracketClose = parser.expect("}"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "rpc-body", bracketOpen, statements, bracketClose, };}
function expectServiceBody(parser: RecursiveDescentParser): ast.ServiceBody { const bracketOpen = parser.expect("{"); const statements = acceptStatements<ast.ServiceBodyStatement>(parser, [ acceptOption, acceptRpc, acceptEmpty, ]); const bracketClose = parser.expect("}"); return { ...mergeSpans([bracketOpen, bracketClose]), type: "service-body", bracketOpen, statements, bracketClose, };}
function acceptService( parser: RecursiveDescentParser, leadingComments: ast.CommentGroup[], leadingDetachedComments: ast.CommentGroup[],): ast.Service | undefined { const keyword = acceptKeyword(parser, "service"); if (!keyword) return; skipWsAndComments(parser); const serviceName = parser.expect(identPattern); skipWsAndComments(parser); const serviceBody = expectServiceBody(parser); const trailingComments = acceptTrailingComments(parser); return { ...mergeSpans([ leadingDetachedComments, leadingComments, keyword, serviceBody, trailingComments, ]), leadingComments, trailingComments, leadingDetachedComments, type: "service", keyword, serviceName, serviceBody, };}