UNPKG

@player-ui/player

Version:

186 lines (163 loc) 5.15 kB
/* eslint-disable @typescript-eslint/no-use-before-define */ import { Grammars } from "ebnf"; import type { Parser, AnyNode, PathNode, ValueNode, ConcatenatedNode, QueryNode, ExpressionNode, } from "../../ast"; import { toValue, toQuery, toPath, toConcatenatedNode, toExpression, } from "../../ast"; import type { ValueToken, ModelRefToken, IdentifierToken, ConcatenatedToken, Token, OptionallyQuotedSegment, QueryToken, QuotedValueToken, ExpressionToken, } from "./types"; const parser = new Grammars.W3C.Parser(` value ::= segment_and_bracket (SEGMENT_SEPARATOR segment_and_bracket)* segment ::= concatenated | expression | modelRef | identifier concatenated ::= (expression | modelRef | identifier)+ modelRef ::= OPEN_CURL OPEN_CURL value CLOSE_CURL CLOSE_CURL identifier ::= [\\w\\-@]+ query ::= WHITESPACE* optionally_quoted_segment WHITESPACE* EQUALS EQUALS? EQUALS? WHITESPACE* optionally_quoted_segment WHITESPACE* brackets ::= OPEN_BRACKET WHITESPACE* (query | optionally_quoted_segment) WHITESPACE* CLOSE_BRACKET segment_and_bracket ::= segment brackets* quoted_value ::= [^"']* optionally_quoted_segment ::= WHITESPACE* SINGLE_QUOTE quoted_value SINGLE_QUOTE WHITESPACE* | WHITESPACE* DOUBLE_QUOTE quoted_value DOUBLE_QUOTE WHITESPACE* | WHITESPACE* segment WHITESPACE* expression_value ::= [^\`]* expression ::= BACK_TICK expression_value BACK_TICK EQUALS ::= "=" SEGMENT_SEPARATOR ::= "." SINGLE_QUOTE ::= "'" DOUBLE_QUOTE ::= '"' WHITESPACE ::= " " OPEN_CURL ::= "{" CLOSE_CURL ::= "}" OPEN_BRACKET ::= "[" CLOSE_BRACKET ::= "]" BACK_TICK ::= "\`" `); /** Map an identifier token to a value */ function convertIdentifierToken(token: IdentifierToken): ValueNode { return toValue(token.text); } /** Concert an expression token into a node */ function convertExpressionToken(token: ExpressionToken): ExpressionNode { return toExpression(token.children[0].text); } /** map a concatenated token to a node */ function convertConcatenatedToken( token: ConcatenatedToken, ): ConcatenatedNode | ValueNode | PathNode | ExpressionNode { return toConcatenatedNode( token.children.map((child) => { if (child.type === "identifier") { return convertIdentifierToken(child); } if (child.type === "expression") { return convertExpressionToken(child); } return convertModelRefToken(child); }), ); } /** map a quoted value token to a value node */ function convertQuotedValueToken(token: QuotedValueToken): ValueNode { return toValue(token.text); } /** map a quoted value token to a value node */ function convertOptionallyQuotedToken( token: OptionallyQuotedSegment, ): ValueNode | ConcatenatedNode | PathNode | ExpressionNode { const child = token.children[0]; if (child.type === "quoted_value") { return convertQuotedValueToken(child); } const grandChild = child.children[0]; if (grandChild.type === "identifier") { return convertIdentifierToken(grandChild); } return convertConcatenatedToken(grandChild); } /** map a query token to a value node */ function convertQueryToken(token: QueryToken): QueryNode { return toQuery( convertOptionallyQuotedToken(token.children[0]), convertOptionallyQuotedToken(token.children[1]), ); } /** Convert the IToken */ function convertValueToken(binding: ValueToken): PathNode { const path: AnyNode[] = []; /** Expand a token into it's path refs */ function expandPath(token: Token) { switch (token.type) { case "modelRef": path.push(convertModelRefToken(token)); break; case "identifier": path.push(convertIdentifierToken(token)); break; case "quoted_value": path.push(convertQuotedValueToken(token)); break; case "expression": path.push(convertExpressionToken(token)); break; case "query": path.push(convertQueryToken(token)); break; case "concatenated": path.push(convertConcatenatedToken(token)); break; default: token.children.forEach(expandPath); } } expandPath(binding); return toPath(path); } /** map a model ref token to a path node */ function convertModelRefToken(token: ModelRefToken): PathNode { return convertValueToken(token.children[0]); } /** Parse a binding using ebnf */ export const parse: Parser = (path) => { if (path === "") { return { status: true, path: toPath([]), }; } const ast = parser.getAST(path) as ValueToken; if (!ast) { return { status: false, error: "Unable to parse binding", }; } if (ast.errors.length > 0) { return { status: false, error: ast.errors[0].message, }; } return { status: true, path: convertValueToken(ast), }; };