@aire-ux/aire-condensation
Version:
Client-side serialization library for Aire-UX
202 lines (186 loc) • 5.22 kB
text/typescript
/**
* 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
}
};