A stylesheet language written in TypeScript that compiles to CSS
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

ast.ts 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import Env, { EnvError } from './env';
  2. import Token from './token';
  3. export namespace AST {
  4. export interface Opts {
  5. depth: number;
  6. prettyPrint: boolean;
  7. }
  8. const spaces = (depth: number): string => {
  9. return Array(depth)
  10. .fill(' ')
  11. .join('');
  12. };
  13. export class Node {
  14. public compile(_env: Env, _opts: Opts): string | EnvError {
  15. return new EnvError(1, "shouldn't call directly");
  16. }
  17. }
  18. export class Literal extends Node {
  19. public value: Token;
  20. public constructor(value: Token) {
  21. super();
  22. this.value = value;
  23. }
  24. public compile(_env: Env, _opts: Opts) {
  25. return this.value.value;
  26. }
  27. }
  28. export class Identifier extends Node {
  29. public name: Token;
  30. public constructor(name: Token) {
  31. super();
  32. this.name = name;
  33. }
  34. public compile(env: Env, opts: Opts): string | EnvError {
  35. const value = env.get(this.name);
  36. if (value instanceof EnvError) return value;
  37. return value.compile(env, opts);
  38. }
  39. }
  40. export class Selector extends Node {
  41. public name: Token;
  42. public parents: Selector[];
  43. public constructor(name: Token, parents: Selector[] = []) {
  44. super();
  45. this.name = name;
  46. this.parents = parents;
  47. }
  48. public getLineages(): string[] {
  49. if (this.parents.length === 0) return [this.name.value];
  50. return Array.prototype.concat(
  51. ...this.parents.map((parent) => {
  52. return parent.getLineages().map((lineage) => {
  53. if (this.name.value.match(/^&/)) {
  54. return lineage + this.name.value.slice(1);
  55. }
  56. return lineage + ' ' + this.name.value;
  57. });
  58. })
  59. );
  60. }
  61. }
  62. export class Property extends Node {
  63. public name: Token;
  64. public constructor(name: Token) {
  65. super();
  66. this.name = name;
  67. }
  68. public compile(_env: Env, _opts: Opts): string | EnvError {
  69. return this.name.value;
  70. }
  71. }
  72. export class Binding extends Node {
  73. public identifier: Identifier;
  74. public value: Node;
  75. public constructor(identifier: Identifier, value: Node) {
  76. super();
  77. this.identifier = identifier;
  78. this.value = value;
  79. }
  80. }
  81. export class Let extends Node {
  82. public bindings: Binding[];
  83. public children: Node[];
  84. public constructor(bindings: Binding[], children: Node[]) {
  85. super();
  86. this.bindings = bindings;
  87. this.children = children;
  88. }
  89. public compile(env: Env, opts: Opts): string | EnvError {
  90. this.bindings.forEach((binding) => {
  91. env.set(binding.identifier.name.value, binding.value);
  92. });
  93. const children = this.children.map((child) => child.compile(env, opts));
  94. const childrenError = children.find((node) => node instanceof EnvError);
  95. if (childrenError instanceof EnvError) return childrenError;
  96. return children.join(opts.prettyPrint ? '\n' : '');
  97. }
  98. }
  99. export class MediaQueryPredicate extends Node {
  100. public property: Property;
  101. public value: Literal | Identifier | Application;
  102. public constructor(
  103. property: Property,
  104. value: Literal | Identifier | Application
  105. ) {
  106. super();
  107. this.property = property;
  108. this.value = value;
  109. }
  110. public compile(env: Env, opts: Opts) {
  111. const wordSpacer = opts.prettyPrint ? ' ' : '';
  112. const property = this.property.compile(env, opts);
  113. const value = this.value.compile(env, opts);
  114. return `${property}:${wordSpacer}${value}`;
  115. }
  116. }
  117. export class MediaQuery extends Node {
  118. public predicate: MediaQueryPredicate;
  119. public children: Node[];
  120. public constructor(predicate: MediaQueryPredicate, children: Node[]) {
  121. super();
  122. this.predicate = predicate;
  123. this.children = children;
  124. }
  125. public compile(env: Env, opts: Opts): string | EnvError {
  126. const lineSpacer = opts.prettyPrint ? '\n' : '';
  127. const wordSpacer = opts.prettyPrint ? ' ' : '';
  128. const predicate = this.predicate.compile(env, opts);
  129. const children = this.children.map((child) =>
  130. child.compile(env, { ...opts, depth: opts.depth + 2 })
  131. );
  132. const childrenError = children.find((node) => node instanceof EnvError);
  133. if (childrenError instanceof EnvError) return childrenError;
  134. return `@media(${predicate})${wordSpacer}{${lineSpacer}${children.join(
  135. lineSpacer
  136. )}${lineSpacer}}`;
  137. }
  138. }
  139. export class Keyframes extends Node {
  140. public name: Literal;
  141. public children: Node[];
  142. public constructor(name: Literal, children: Node[]) {
  143. super();
  144. this.name = name;
  145. this.children = children;
  146. }
  147. public compile(env: Env, opts: Opts) {
  148. const lineSpacer = opts.prettyPrint ? '\n' : '';
  149. const wordSpacer = opts.prettyPrint ? ' ' : '';
  150. const name = this.name.compile(env, opts);
  151. const children = this.children.map((child) =>
  152. child.compile(env, { ...opts, depth: opts.depth + 2 })
  153. );
  154. const childrenError = children.find((node) => node instanceof EnvError);
  155. if (childrenError instanceof EnvError) return childrenError;
  156. return `@keyframes ${name}${wordSpacer}{${lineSpacer}${children.join(
  157. lineSpacer
  158. )}${lineSpacer}}`;
  159. }
  160. }
  161. export class Application extends Node {
  162. public name: Literal;
  163. public arguments: Node[];
  164. public constructor(name: Literal, args: Node[]) {
  165. super();
  166. this.name = name;
  167. this.arguments = args;
  168. }
  169. public compile(env: Env, opts: Opts) {
  170. const wordSpacer = opts.prettyPrint ? ' ' : '';
  171. const [lParen, rParen, comma] = ['(', ')', ','];
  172. const compiledArguments = this.arguments
  173. .map((arg) => arg.compile(env, opts))
  174. .join(`${comma}${wordSpacer}`);
  175. return `${this.name.value.value}${lParen}${compiledArguments}${rParen}`;
  176. }
  177. }
  178. export class Mixin extends Node {
  179. public name: Token;
  180. public parameters: Identifier[];
  181. public rules: Rule[];
  182. public constructor(name: Token, parameters: Identifier[], rules: Rule[]) {
  183. super();
  184. this.name = name;
  185. this.parameters = parameters;
  186. this.rules = rules;
  187. }
  188. public compile(env: Env, _opts: Opts) {
  189. env.set(this.name.value, this);
  190. return '';
  191. }
  192. }
  193. export class Rule extends Node {
  194. public property: Property;
  195. public value: Literal | Identifier | Application;
  196. public constructor(
  197. property: Property,
  198. value: Literal | Identifier | Application
  199. ) {
  200. super();
  201. this.property = property;
  202. this.value = value;
  203. }
  204. public compile(env: Env, opts: Opts): string | EnvError {
  205. const wordSpacer = opts.prettyPrint ? ' ' : '';
  206. const indentSpacer = opts.prettyPrint ? ' ' + spaces(opts.depth) : '';
  207. const property = this.property.compile(env, opts);
  208. if (property instanceof EnvError) return property;
  209. const value = this.value.compile(env, opts);
  210. if (value instanceof EnvError) return value;
  211. return `${indentSpacer}${property}:${wordSpacer}${value};`;
  212. }
  213. }
  214. export class RuleSet extends Node {
  215. public selectors: Selector[];
  216. public rules: Rule[];
  217. public children: RuleSet[];
  218. public constructor(
  219. selectors: Selector[],
  220. rules: Rule[],
  221. children: RuleSet[] = []
  222. ) {
  223. super();
  224. this.selectors = selectors;
  225. this.rules = rules;
  226. this.children = children;
  227. }
  228. public compile(env: Env, opts: Opts): string | EnvError {
  229. const lineSpacer = opts.prettyPrint ? '\n' : '';
  230. const wordSpacer = opts.prettyPrint ? ' ' : '';
  231. const indentSpacer = opts.prettyPrint ? spaces(opts.depth) : '';
  232. const [lBrace, rBrace] = ['{', '}'];
  233. const lineages = ([] as string[]).concat(
  234. ...this.selectors.map((sel) => sel.getLineages())
  235. );
  236. const rules = this.rules.map((rule: Rule) => rule.compile(env, opts));
  237. const rulesError = rules.find((rule) => rule instanceof EnvError);
  238. if (rulesError !== undefined) return rulesError;
  239. const compiledRules = rules.join(lineSpacer);
  240. const children: Array<string | EnvError> = this.children.map(
  241. (child: RuleSet): string | EnvError => {
  242. return child.compile(env, opts);
  243. }
  244. );
  245. const childrenError = children.find((child) => child instanceof EnvError);
  246. if (childrenError !== undefined) return childrenError;
  247. const compiledChildren = children.join(lineSpacer);
  248. const lineage = lineages.join(`,${wordSpacer}`);
  249. const frontMatter = `${indentSpacer}${lineage}${wordSpacer}${lBrace}${lineSpacer}`;
  250. const backMatter = `${lineSpacer}${indentSpacer}${rBrace}${
  251. compiledChildren.length ? lineSpacer : ''
  252. }`;
  253. return rules.length
  254. ? `${frontMatter}${compiledRules}${backMatter}${compiledChildren}`
  255. : compiledChildren;
  256. }
  257. }
  258. }