A stylesheet language written in TypeScript that compiles to CSS
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

parser.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import { AST } from './ast';
  2. import { Error } from './error';
  3. import Token, { TokenTypes } from './token';
  4. export class ParserError implements Error {
  5. public message: string;
  6. public line: number;
  7. public module?: string;
  8. public constructor(message: string, line: number, module?: string) {
  9. this.message = message;
  10. this.line = line;
  11. this.module = module;
  12. }
  13. }
  14. export type ParserResult = { tree: AST.Node[]; module?: string } | ParserError;
  15. export default class Parser {
  16. private tokens: Token[];
  17. private module?: string;
  18. private tree: AST.Node[];
  19. private position: number;
  20. public constructor(lexerResult: { tokens: Token[]; module?: string }) {
  21. this.tokens = lexerResult.tokens;
  22. this.module = lexerResult.module;
  23. this.tree = [];
  24. this.position = 0;
  25. }
  26. public parse(): ParserResult {
  27. while (this.currentToken().type != TokenTypes.EOF) {
  28. const expr = this.expr();
  29. if (expr instanceof ParserError) return expr;
  30. this.tree.push(expr);
  31. }
  32. this.eat(TokenTypes.EOF);
  33. return {
  34. tree: this.tree,
  35. module: this.module,
  36. };
  37. }
  38. private expr(): AST.Node | ParserError {
  39. const currentToken = this.currentToken();
  40. switch (currentToken.type) {
  41. case TokenTypes.LITERAL:
  42. case TokenTypes.IDENTIFIER:
  43. return this.value();
  44. case TokenTypes.LPAREN:
  45. if (
  46. this.nextToken() &&
  47. this.nextToken().type === TokenTypes.FUNCTION_NAME
  48. ) {
  49. switch (this.nextToken().value) {
  50. case 'let':
  51. return this.let();
  52. case 'media':
  53. return this.mediaQuery();
  54. case 'keyframes':
  55. return this.keyframes();
  56. case 'mixin':
  57. return this.mixin();
  58. default:
  59. return this.application();
  60. }
  61. return this.error(
  62. `Undefined function '${this.nextToken().value}`,
  63. this.nextToken()
  64. );
  65. }
  66. return this.ruleSet();
  67. default:
  68. return this.error(`Unexpected ${currentToken.type}`, currentToken);
  69. }
  70. return this.error(`Unexpected ${currentToken.type}`, currentToken);
  71. }
  72. private let(): AST.Let | ParserError {
  73. const lParen = this.eat(TokenTypes.LPAREN);
  74. if (lParen instanceof ParserError) return lParen;
  75. const letToken = this.eat(TokenTypes.FUNCTION_NAME);
  76. if (letToken instanceof ParserError) return letToken;
  77. const bindings: AST.Binding[] = [];
  78. while (this.currentToken().type === TokenTypes.IDENTIFIER) {
  79. const identifier = this.identifier();
  80. if (identifier instanceof ParserError) return identifier;
  81. const value = this.value();
  82. if (value instanceof ParserError) return value;
  83. bindings.push(new AST.Binding(identifier, value));
  84. }
  85. const children: AST.Node[] = [];
  86. while (this.currentToken().type === TokenTypes.LPAREN) {
  87. const body = this.expr();
  88. if (body instanceof ParserError) return body;
  89. children.push(body);
  90. }
  91. const rParen = this.eat(TokenTypes.RPAREN);
  92. if (rParen instanceof ParserError) return rParen;
  93. return new AST.Let(bindings, children);
  94. }
  95. private mediaQuery(): AST.MediaQuery | ParserError {
  96. const lParen = this.eat(TokenTypes.LPAREN);
  97. if (lParen instanceof ParserError) return lParen;
  98. const mediaToken = this.eat(TokenTypes.FUNCTION_NAME);
  99. if (mediaToken instanceof ParserError) return mediaToken;
  100. const propertyToken = this.eat(TokenTypes.PROPERTY);
  101. if (propertyToken instanceof ParserError) return propertyToken;
  102. const property = new AST.Property(propertyToken);
  103. const value = this.value();
  104. if (value instanceof ParserError) return value;
  105. const predicate = new AST.MediaQueryPredicate(property, value);
  106. const children: AST.Node[] = [];
  107. while (this.currentToken().type === TokenTypes.LPAREN) {
  108. const body = this.expr();
  109. if (body instanceof ParserError) return body;
  110. children.push(body);
  111. }
  112. const rParen = this.eat(TokenTypes.RPAREN);
  113. if (rParen instanceof ParserError) return rParen;
  114. return new AST.MediaQuery(predicate, children);
  115. }
  116. private keyframes(): AST.Keyframes | ParserError {
  117. const lParen = this.eat(TokenTypes.LPAREN);
  118. if (lParen instanceof ParserError) return lParen;
  119. const keyframesToken = this.eat(TokenTypes.FUNCTION_NAME);
  120. if (keyframesToken instanceof ParserError) return keyframesToken;
  121. const name = this.literal();
  122. if (name instanceof ParserError) return name;
  123. const children: AST.Node[] = [];
  124. while (this.currentToken().type === TokenTypes.LPAREN) {
  125. const body = this.expr();
  126. if (body instanceof ParserError) return body;
  127. children.push(body);
  128. }
  129. const rParen = this.eat(TokenTypes.RPAREN);
  130. if (rParen instanceof ParserError) return rParen;
  131. return new AST.Keyframes(name, children);
  132. }
  133. private mixin(): AST.Mixin | ParserError {
  134. const lParen = this.eat(TokenTypes.LPAREN);
  135. if (lParen instanceof ParserError) return lParen;
  136. const mixinToken = this.eat(TokenTypes.FUNCTION_NAME);
  137. if (mixinToken instanceof ParserError) return mixinToken;
  138. const name = this.eat(TokenTypes.LITERAL);
  139. if (name instanceof ParserError) return name;
  140. const rules: AST.Rule[] = [];
  141. const openParen = this.eat(TokenTypes.LPAREN);
  142. if (openParen instanceof ParserError) return openParen;
  143. const parameters: AST.Identifier[] = [];
  144. while (this.currentToken().type === TokenTypes.IDENTIFIER) {
  145. const param = this.identifier();
  146. if (param instanceof ParserError) return param;
  147. parameters.push(param);
  148. }
  149. const closeParen = this.eat(TokenTypes.RPAREN);
  150. if (closeParen instanceof ParserError) return closeParen;
  151. while (this.currentToken().type === TokenTypes.PROPERTY) {
  152. const property = this.property();
  153. if (property instanceof ParserError) return property;
  154. const value = this.value();
  155. if (value instanceof ParserError) return value;
  156. rules.push(new AST.Rule(property, value));
  157. }
  158. const rParen = this.eat(TokenTypes.RPAREN);
  159. if (rParen instanceof ParserError) return rParen;
  160. return new AST.Mixin(name, parameters, rules);
  161. }
  162. private application(): AST.Application | ParserError {
  163. const lParen = this.eat(TokenTypes.LPAREN);
  164. if (lParen instanceof ParserError) return lParen;
  165. const functionToken = this.eat(TokenTypes.FUNCTION_NAME);
  166. if (functionToken instanceof ParserError) return functionToken;
  167. const functionName = new AST.Literal(functionToken);
  168. const args: AST.Node[] = [];
  169. while (this.currentToken().type !== TokenTypes.RPAREN) {
  170. const argument = this.expr();
  171. if (argument instanceof ParserError) return argument;
  172. args.push(argument);
  173. }
  174. const rParen = this.eat(TokenTypes.RPAREN);
  175. if (rParen instanceof ParserError) return rParen;
  176. return new AST.Application(functionName, args);
  177. }
  178. private ruleSet(parents: AST.Selector[] = []): AST.RuleSet | ParserError {
  179. const lParen = this.eat(TokenTypes.LPAREN);
  180. if (lParen instanceof ParserError) return lParen;
  181. const selectors: AST.Selector[] = [];
  182. const selector = this.selector(parents);
  183. if (selector instanceof ParserError) return selector;
  184. selectors.push(selector);
  185. while (this.currentToken().type === TokenTypes.COMMA) {
  186. this.eat(TokenTypes.COMMA);
  187. const selector = this.selector(parents);
  188. if (selector instanceof ParserError) return selector;
  189. selectors.push(selector);
  190. }
  191. const children: (AST.Rule | AST.RuleSet)[] = [];
  192. while (
  193. [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes(
  194. this.currentToken().type
  195. )
  196. ) {
  197. if (this.currentToken().type === TokenTypes.PROPERTY) {
  198. const propertyToken = this.eat(TokenTypes.PROPERTY);
  199. if (propertyToken instanceof ParserError) return propertyToken;
  200. const property = new AST.Property(propertyToken);
  201. const value = this.value();
  202. if (value instanceof ParserError) return value;
  203. children.push(new AST.Rule(property, value));
  204. } else if (this.currentToken().type === TokenTypes.LPAREN) {
  205. const ruleSet = this.ruleSet(selectors);
  206. if (ruleSet instanceof ParserError) return ruleSet;
  207. children.push(ruleSet);
  208. }
  209. }
  210. const rParen = this.eat(TokenTypes.RPAREN);
  211. if (rParen instanceof ParserError) return rParen;
  212. return new AST.RuleSet(selectors, children);
  213. }
  214. private value():
  215. | AST.Literal
  216. | AST.Identifier
  217. | AST.Application
  218. | ParserError {
  219. const type = this.currentToken().type;
  220. switch (type) {
  221. case TokenTypes.LITERAL:
  222. return this.literal();
  223. case TokenTypes.IDENTIFIER:
  224. return this.identifier();
  225. case TokenTypes.LPAREN:
  226. if (this.nextToken().type === TokenTypes.FUNCTION_NAME)
  227. return this.application();
  228. default:
  229. return this.error(`Unexpected ${type}`, this.currentToken());
  230. }
  231. }
  232. private literal(): AST.Literal | ParserError {
  233. const literal = this.eat(TokenTypes.LITERAL);
  234. if (literal instanceof ParserError) return literal;
  235. return new AST.Literal(literal);
  236. }
  237. private property(): AST.Property | ParserError {
  238. const property = this.eat(TokenTypes.PROPERTY);
  239. if (property instanceof ParserError) return property;
  240. return new AST.Property(property);
  241. }
  242. private identifier(): AST.Identifier | ParserError {
  243. const identifier = this.eat(TokenTypes.IDENTIFIER);
  244. if (identifier instanceof ParserError) return identifier;
  245. return new AST.Identifier(identifier);
  246. }
  247. private selector(parents: AST.Selector[] = []): AST.Selector | ParserError {
  248. const literal = this.literal();
  249. if (literal instanceof ParserError) return literal;
  250. return new AST.Selector(literal.value, parents);
  251. }
  252. private eat(type: TokenTypes): Token | ParserError {
  253. const token = this.currentToken();
  254. if (type === token.type) {
  255. this.position += 1;
  256. return token;
  257. }
  258. return this.error(`Unexpected ${token.type}; expected ${type}`, token);
  259. }
  260. private currentToken(): Token {
  261. return this.tokens[this.position];
  262. }
  263. private nextToken(): Token {
  264. return this.tokens[this.position + 1];
  265. }
  266. private error(message: string, token: Token) {
  267. return new ParserError(message, token.line, token.module);
  268. }
  269. }