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 rules: AST.Rule[] = []; 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; while (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; rules.push(new AST.Rule(property, value)); } const rParen = this.eat(TokenTypes.RPAREN); if (rParen instanceof ParserError) return rParen; return new AST.Mixin(name, parameters, rules); } 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.Rule | AST.RuleSet)[] = []; while ( [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes( this.currentToken().type ) ) { if (this.currentToken().type === TokenTypes.PROPERTY) { 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; children.push(new AST.Rule(property, value)); } else if (this.currentToken().type === TokenTypes.LPAREN) { const ruleSet = this.ruleSet(selectors); if (ruleSet instanceof ParserError) return ruleSet; children.push(ruleSet); } } 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 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); } }