123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- import { AST } from './ast';
- import { Error } from './error';
- import Token, { TokenTypes } from './token';
-
- export class ParserError implements Error {
- public message: string;
- public line: number;
- public module?: string;
-
- public constructor(message: string, line: number, module?: string) {
- this.message = message;
- this.line = line;
- this.module = module;
- }
- }
-
- export type ParserResult = { tree: AST.Node[]; module?: string } | ParserError;
-
- export default class Parser {
- private tokens: Token[];
- private module?: string;
- private tree: AST.Node[];
- private position: number;
-
- public constructor(lexerResult: { tokens: Token[]; module?: string }) {
- this.tokens = lexerResult.tokens;
- this.module = lexerResult.module;
- this.tree = [];
- this.position = 0;
- }
-
- public parse(): ParserResult {
- while (this.currentToken().type != TokenTypes.EOF) {
- const expr = this.expr();
- if (expr instanceof ParserError) return expr;
- this.tree.push(expr);
- }
-
- this.eat(TokenTypes.EOF);
-
- return {
- tree: this.tree,
- module: this.module,
- };
- }
-
- private expr(): AST.Node | ParserError {
- const currentToken = this.currentToken();
- switch (currentToken.type) {
- case TokenTypes.LITERAL:
- case TokenTypes.IDENTIFIER:
- return this.value();
- case TokenTypes.LPAREN:
- if (
- this.nextToken() &&
- this.nextToken().type === TokenTypes.FUNCTION_NAME
- ) {
- switch (this.nextToken().value) {
- case 'let':
- return this.let();
- case 'media':
- return this.mediaQuery();
- case 'keyframes':
- return this.keyframes();
- case 'mixin':
- return this.mixin();
- default:
- return this.application();
- }
- return this.error(
- `Undefined function '${this.nextToken().value}`,
- this.nextToken()
- );
- }
- return this.ruleSet();
- default:
- return this.error(`Unexpected ${currentToken.type}`, currentToken);
- }
-
- return this.error(`Unexpected ${currentToken.type}`, currentToken);
- }
-
- private let(): AST.Let | ParserError {
- const lParen = this.eat(TokenTypes.LPAREN);
- if (lParen instanceof ParserError) return lParen;
-
- const letToken = this.eat(TokenTypes.FUNCTION_NAME);
- if (letToken instanceof ParserError) return letToken;
-
- const bindings: AST.Binding[] = [];
-
- while (this.currentToken().type === TokenTypes.IDENTIFIER) {
- const identifier = this.identifier();
- if (identifier instanceof ParserError) return identifier;
- const value = this.value();
- if (value instanceof ParserError) return value;
- bindings.push(new AST.Binding(identifier, value));
- }
-
- const children: AST.Node[] = [];
-
- while (this.currentToken().type === TokenTypes.LPAREN) {
- const body = this.expr();
- if (body instanceof ParserError) return body;
- children.push(body);
- }
-
- const rParen = this.eat(TokenTypes.RPAREN);
- if (rParen instanceof ParserError) return rParen;
-
- return new AST.Let(bindings, children);
- }
-
- private mediaQuery(): AST.MediaQuery | ParserError {
- const lParen = this.eat(TokenTypes.LPAREN);
- if (lParen instanceof ParserError) return lParen;
-
- const mediaToken = this.eat(TokenTypes.FUNCTION_NAME);
- if (mediaToken instanceof ParserError) return mediaToken;
-
- const propertyToken = this.eat(TokenTypes.PROPERTY);
- if (propertyToken instanceof ParserError) return propertyToken;
- const property = new AST.Property(propertyToken);
- const value = this.value();
- if (value instanceof ParserError) return value;
-
- const predicate = new AST.MediaQueryPredicate(property, value);
-
- const children: AST.Node[] = [];
-
- while (this.currentToken().type === TokenTypes.LPAREN) {
- const body = this.expr();
- if (body instanceof ParserError) return body;
- children.push(body);
- }
-
- const rParen = this.eat(TokenTypes.RPAREN);
- if (rParen instanceof ParserError) return rParen;
-
- return new AST.MediaQuery(predicate, children);
- }
-
- private keyframes(): AST.Keyframes | ParserError {
- const lParen = this.eat(TokenTypes.LPAREN);
- if (lParen instanceof ParserError) return lParen;
-
- const keyframesToken = this.eat(TokenTypes.FUNCTION_NAME);
- if (keyframesToken instanceof ParserError) return keyframesToken;
-
- const name = this.literal();
- if (name instanceof ParserError) return name;
-
- const children: AST.Node[] = [];
-
- while (this.currentToken().type === TokenTypes.LPAREN) {
- const body = this.expr();
- if (body instanceof ParserError) return body;
- children.push(body);
- }
-
- const rParen = this.eat(TokenTypes.RPAREN);
- if (rParen instanceof ParserError) return rParen;
-
- return new AST.Keyframes(name, children);
- }
-
- private mixin(): AST.Mixin | ParserError {
- const lParen = this.eat(TokenTypes.LPAREN);
- if (lParen instanceof ParserError) return lParen;
-
- const mixinToken = this.eat(TokenTypes.FUNCTION_NAME);
- if (mixinToken instanceof ParserError) return mixinToken;
-
- const name = this.eat(TokenTypes.LITERAL);
- if (name instanceof ParserError) return name;
-
- const openParen = this.eat(TokenTypes.LPAREN);
- if (openParen instanceof ParserError) return openParen;
-
- const parameters: AST.Identifier[] = [];
-
- while (this.currentToken().type === TokenTypes.IDENTIFIER) {
- const param = this.identifier();
- if (param instanceof ParserError) return param;
- parameters.push(param);
- }
-
- const closeParen = this.eat(TokenTypes.RPAREN);
- if (closeParen instanceof ParserError) return closeParen;
-
- const children: AST.Child[] = [];
- while (
- [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes(
- this.currentToken().type
- )
- ) {
- const child = this.child();
- if (child instanceof ParserError) return child;
- children.push(child);
- }
-
- const rParen = this.eat(TokenTypes.RPAREN);
- if (rParen instanceof ParserError) return rParen;
-
- return new AST.Mixin(name, parameters, children);
- }
-
- private application(): AST.Application | ParserError {
- const lParen = this.eat(TokenTypes.LPAREN);
- if (lParen instanceof ParserError) return lParen;
-
- const functionToken = this.eat(TokenTypes.FUNCTION_NAME);
- if (functionToken instanceof ParserError) return functionToken;
-
- const functionName = new AST.Literal(functionToken);
-
- const args: AST.Node[] = [];
- while (this.currentToken().type !== TokenTypes.RPAREN) {
- const argument = this.expr();
- if (argument instanceof ParserError) return argument;
-
- args.push(argument);
- }
-
- const rParen = this.eat(TokenTypes.RPAREN);
- if (rParen instanceof ParserError) return rParen;
-
- return new AST.Application(functionName, args);
- }
-
- private ruleSet(parents: AST.Selector[] = []): AST.RuleSet | ParserError {
- const lParen = this.eat(TokenTypes.LPAREN);
- if (lParen instanceof ParserError) return lParen;
-
- const selectors: AST.Selector[] = [];
-
- const selector = this.selector(parents);
- if (selector instanceof ParserError) return selector;
- selectors.push(selector);
-
- while (this.currentToken().type === TokenTypes.COMMA) {
- this.eat(TokenTypes.COMMA);
- const selector = this.selector(parents);
- if (selector instanceof ParserError) return selector;
- selectors.push(selector);
- }
-
- const children: (AST.Application | AST.Rule | AST.RuleSet)[] = [];
-
- while (
- [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes(
- this.currentToken().type
- )
- ) {
- const child = this.child(selectors);
- if (child instanceof ParserError) return child;
- children.push(child);
- }
-
- const rParen = this.eat(TokenTypes.RPAREN);
- if (rParen instanceof ParserError) return rParen;
-
- return new AST.RuleSet(selectors, children);
- }
-
- private value():
- | AST.Literal
- | AST.Identifier
- | AST.Application
- | ParserError {
- const type = this.currentToken().type;
- switch (type) {
- case TokenTypes.LITERAL:
- return this.literal();
- case TokenTypes.IDENTIFIER:
- return this.identifier();
- case TokenTypes.LPAREN:
- if (this.nextToken().type === TokenTypes.FUNCTION_NAME)
- return this.application();
- default:
- return this.error(`Unexpected ${type}`, this.currentToken());
- }
- }
-
- private literal(): AST.Literal | ParserError {
- const literal = this.eat(TokenTypes.LITERAL);
- if (literal instanceof ParserError) return literal;
- return new AST.Literal(literal);
- }
-
- private property(): AST.Property | ParserError {
- const property = this.eat(TokenTypes.PROPERTY);
- if (property instanceof ParserError) return property;
- return new AST.Property(property);
- }
-
- private identifier(): AST.Identifier | ParserError {
- const identifier = this.eat(TokenTypes.IDENTIFIER);
- if (identifier instanceof ParserError) return identifier;
- return new AST.Identifier(identifier);
- }
-
- private selector(parents: AST.Selector[] = []): AST.Selector | ParserError {
- const literal = this.literal();
- if (literal instanceof ParserError) return literal;
- return new AST.Selector(literal.value, parents);
- }
-
- private child(parents: AST.Selector[] = []): AST.Child | ParserError {
- if (this.currentToken().type === TokenTypes.PROPERTY) {
- const property = this.property();
- if (property instanceof ParserError) return property;
-
- const value = this.value();
- if (value instanceof ParserError) return value;
-
- return new AST.Rule(property, value);
- } else if (this.currentToken().type === TokenTypes.LPAREN) {
- if (this.nextToken().type === TokenTypes.LITERAL) {
- const ruleSet = this.ruleSet(parents);
- if (ruleSet instanceof ParserError) return ruleSet;
- return ruleSet;
- } else if (this.nextToken().type === TokenTypes.FUNCTION_NAME) {
- const application = this.application();
- if (application instanceof ParserError) return application;
- return application;
- }
- }
-
- return this.error(
- `Unexpected ${
- this.nextToken().type
- }; expected literal or function/macro application`,
- this.nextToken()
- );
- }
-
- private eat(type: TokenTypes): Token | ParserError {
- const token = this.currentToken();
- if (type === token.type) {
- this.position += 1;
- return token;
- }
- return this.error(`Unexpected ${token.type}; expected ${type}`, token);
- }
-
- private currentToken(): Token {
- return this.tokens[this.position];
- }
-
- private nextToken(): Token {
- return this.tokens[this.position + 1];
- }
-
- private error(message: string, token: Token) {
- return new ParserError(message, token.line, token.module);
- }
- }
|