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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import * as 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 openParen = this.eat(TokenTypes.LPAREN);
  141. if (openParen instanceof ParserError) return openParen;
  142. const parameters: AST.Identifier[] = [];
  143. while (this.currentToken().type === TokenTypes.IDENTIFIER) {
  144. const param = this.identifier();
  145. if (param instanceof ParserError) return param;
  146. parameters.push(param);
  147. }
  148. const closeParen = this.eat(TokenTypes.RPAREN);
  149. if (closeParen instanceof ParserError) return closeParen;
  150. const children: AST.Child[] = [];
  151. while (
  152. [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes(
  153. this.currentToken().type
  154. )
  155. ) {
  156. const child = this.child();
  157. if (child instanceof ParserError) return child;
  158. children.push(child);
  159. }
  160. const rParen = this.eat(TokenTypes.RPAREN);
  161. if (rParen instanceof ParserError) return rParen;
  162. return new AST.Mixin(name, parameters, children);
  163. }
  164. private application(): AST.Application | ParserError {
  165. const lParen = this.eat(TokenTypes.LPAREN);
  166. if (lParen instanceof ParserError) return lParen;
  167. const functionToken = this.eat(TokenTypes.FUNCTION_NAME);
  168. if (functionToken instanceof ParserError) return functionToken;
  169. const functionName = new AST.Literal(functionToken);
  170. const args: AST.Node[] = [];
  171. while (this.currentToken().type !== TokenTypes.RPAREN) {
  172. const argument = this.expr();
  173. if (argument instanceof ParserError) return argument;
  174. args.push(argument);
  175. }
  176. const rParen = this.eat(TokenTypes.RPAREN);
  177. if (rParen instanceof ParserError) return rParen;
  178. return new AST.Application(functionName, args);
  179. }
  180. private ruleSet(parents: AST.Selector[] = []): AST.RuleSet | ParserError {
  181. const lParen = this.eat(TokenTypes.LPAREN);
  182. if (lParen instanceof ParserError) return lParen;
  183. const selectors: AST.Selector[] = [];
  184. const selector = this.selector(parents);
  185. if (selector instanceof ParserError) return selector;
  186. selectors.push(selector);
  187. while (this.currentToken().type === TokenTypes.COMMA) {
  188. this.eat(TokenTypes.COMMA);
  189. const selector = this.selector(parents);
  190. if (selector instanceof ParserError) return selector;
  191. selectors.push(selector);
  192. }
  193. const children: (AST.Child)[] = [];
  194. while (
  195. [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes(
  196. this.currentToken().type
  197. )
  198. ) {
  199. const child = this.child(selectors);
  200. if (child instanceof ParserError) return child;
  201. children.push(child);
  202. }
  203. const rParen = this.eat(TokenTypes.RPAREN);
  204. if (rParen instanceof ParserError) return rParen;
  205. return new AST.RuleSet(selectors, children);
  206. }
  207. private value():
  208. | AST.Literal
  209. | AST.Identifier
  210. | AST.Application
  211. | ParserError {
  212. const type = this.currentToken().type;
  213. switch (type) {
  214. case TokenTypes.LITERAL:
  215. return this.literal();
  216. case TokenTypes.IDENTIFIER:
  217. return this.identifier();
  218. case TokenTypes.LPAREN:
  219. if (this.nextToken().type === TokenTypes.FUNCTION_NAME)
  220. return this.application();
  221. default:
  222. return this.error(`Unexpected ${type}`, this.currentToken());
  223. }
  224. }
  225. private literal(): AST.Literal | ParserError {
  226. const literal = this.eat(TokenTypes.LITERAL);
  227. if (literal instanceof ParserError) return literal;
  228. return new AST.Literal(literal);
  229. }
  230. private property(): AST.Property | ParserError {
  231. const property = this.eat(TokenTypes.PROPERTY);
  232. if (property instanceof ParserError) return property;
  233. return new AST.Property(property);
  234. }
  235. private identifier(): AST.Identifier | ParserError {
  236. const identifier = this.eat(TokenTypes.IDENTIFIER);
  237. if (identifier instanceof ParserError) return identifier;
  238. return new AST.Identifier(identifier);
  239. }
  240. private selector(parents: AST.Selector[] = []): AST.Selector | ParserError {
  241. const literal = this.literal();
  242. if (literal instanceof ParserError) return literal;
  243. return new AST.Selector(literal.value, parents);
  244. }
  245. private child(parents: AST.Selector[] = []): AST.Child | ParserError {
  246. if (this.currentToken().type === TokenTypes.PROPERTY) {
  247. const property = this.property();
  248. if (property instanceof ParserError) return property;
  249. const values = [];
  250. const value = this.value();
  251. if (value instanceof ParserError) return value;
  252. values.push(value);
  253. while (this.currentToken().type === TokenTypes.COMMA) {
  254. this.eat(TokenTypes.COMMA);
  255. const value = this.value();
  256. if (value instanceof ParserError) return value;
  257. values.push(value);
  258. }
  259. return new AST.Rule(property, values);
  260. } else if (this.currentToken().type === TokenTypes.LPAREN) {
  261. const next = this.nextToken();
  262. if (next.type === TokenTypes.LITERAL) {
  263. return this.ruleSet(parents);
  264. } else if (next.type === TokenTypes.FUNCTION_NAME) {
  265. if (next.value === 'media') {
  266. return this.mediaQuery();
  267. } else if (next.value === 'mixin') {
  268. return this.mixin();
  269. }
  270. return this.application();
  271. }
  272. }
  273. return this.error(
  274. `Unexpected ${
  275. this.nextToken().type
  276. }; expected literal or function/macro application`,
  277. this.nextToken()
  278. );
  279. }
  280. private eat(type: TokenTypes): Token | ParserError {
  281. const token = this.currentToken();
  282. if (type === token.type) {
  283. this.position += 1;
  284. return token;
  285. }
  286. return this.error(`Unexpected ${token.type}; expected ${type}`, token);
  287. }
  288. private currentToken(): Token {
  289. return this.tokens[this.position];
  290. }
  291. private nextToken(): Token {
  292. return this.tokens[this.position + 1];
  293. }
  294. private error(message: string, token: Token) {
  295. return new ParserError(message, token.line, token.module);
  296. }
  297. }