UNPKG

@aire-ux/aire-condensation

Version:

Client-side serialization library for Aire-UX

202 lines (186 loc) 5.22 kB
/** * parse an invocation into a readable format * * invocation: (<namespace> '::')? <invocationPath> * invocationPath: <ws>? (<invocationOrProperty><separator>)+ * invocationOrProperty: invocation | property * invocation: <openParen>(ws?) paramList (ws?) <closeParam> * openParen: '(' * paramList: (<parameter>',')?<parameter> * closeParam: ')' * property: <identifier> */ import {PushbackIterator, stream, Token, TokenType,} from "@condensation/lexer"; export interface Lookup { lookup<T>(name: string | number): T; } export type Invocation = { path: Path; namespace: string | number // context: Lookup; // invoke: (...args: string[]) => any; }; const expectToken = ( ctx: string, token: Token, ...expected: TokenType[] ): void => { if (expected.indexOf(token.type) === -1) { throw new Error( `Expected one of [${[...expected] .map((e) => TokenType[e]) .join(",")}]. Got ${TokenType[token.type]} at (${ (token.start, token.end) }) '${ctx.substr(token.start, token.end)}'` ); } }; const expect = ( ctx: string, iter: PushbackIterator, ...expected: TokenType[] ): Token => { if (!iter.hasNext()) { throw new Error( `Unexpected end of token stream. Expected one of [${[...expected] .map((e) => TokenType[e]) .join(",")}]` ); } const next = iter.next(); expectToken(ctx, next, ...expected); return next; }; export enum SegmentType { Property, Invocation, Reference } export type Segment = { name: string; next: Segment | undefined; segmentType: SegmentType; formalParameterNames: string[] | undefined; }; type Path = { segment: Segment; }; const ws = (iter: PushbackIterator): void => { let token: Token; do { token = iter.next(); } while (token.type === TokenType.Whitespace); if (token.type !== TokenType.EOF) { iter.unread(token); } }; const readParameterNames = (seq: string, iter: PushbackIterator): string[] => { let result = []; for (; ;) { ws(iter); if (iter.peek().type === TokenType.CloseParenthesis) { break; } expect(seq, iter, TokenType.ParameterOpen); ws(iter); result.push(expect(seq, iter, TokenType.Identifier).value); ws(iter); expect(seq, iter, TokenType.ParameterClose); ws(iter); const next = iter.peek(); if (next.type === TokenType.ParameterSeparator) { expect(seq, iter, TokenType.ParameterSeparator); continue; } if (next.type === TokenType.CloseParenthesis) { break; } } ws(iter); return result; }; const readPath = (seq: string, iter: PushbackIterator): Path => { let root: Segment | null = null, current: Segment | null = null; while (iter.hasNext()) { ws(iter); const id = expect(seq, iter, TokenType.Identifier, TokenType.Number); ws(iter); let peek = iter.peek(); if (peek.type === TokenType.PropertySeparator) { expect(seq, iter, TokenType.PropertySeparator); const value = { name: id.value, next: undefined, formalParameterNames: undefined, segmentType: id.type == TokenType.Identifier ? SegmentType.Property : SegmentType.Reference, }; if (!root) { root = value as Segment; current = value as Segment; } else { (current as Segment).next = value as Segment; current = (current as Segment).next as Segment; } } else if (peek.type == TokenType.OpenParenthesis) { expect(seq, iter, TokenType.OpenParenthesis); ws(iter); const paramNames = readParameterNames(seq, iter); const value = { name: id.value, next: undefined, formalParameterNames: paramNames, segmentType: SegmentType.Invocation, }; if (!root) { root = value as Segment; current = value as Segment; } else { (current as Segment).next = value as Segment; current = (current as Segment).next as Segment; } ws(iter); expect(seq, iter, TokenType.CloseParenthesis); ws(iter); if (iter.peek().type === TokenType.PropertySeparator) { expect(seq, iter, TokenType.PropertySeparator); } } else if (peek.type == TokenType.EOF) { const value = { name: id.value, next: undefined, formalParameterNames: undefined, segmentType: SegmentType.Property, }; if (!root) { root = value as Segment; current = value as Segment; } else { (current as Segment).next = value as Segment; current = (current as Segment).next as Segment; } break; } } return { segment: root as Segment, }; }; export const parse = (seq: string): Invocation => { const iter = stream(seq); ws(iter); const namespace = expect(seq, iter, TokenType.Identifier, TokenType.Number); ws(iter); const next = iter.peek(); if (next.type === TokenType.NamespaceSeparator) { expect(seq, iter, TokenType.NamespaceSeparator); } const path = readPath(seq, iter); return { path: path, namespace: namespace.value } };