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 10.0KB

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