A stylesheet language written in TypeScript that compiles to CSS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ast.ts 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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(name: Token, parameters: Identifier[], children: Child[]) {
  184. super();
  185. this.name = name;
  186. this.parameters = parameters;
  187. this.children = children;
  188. }
  189. public compile(env: Env, _opts: Opts) {
  190. env.set(this.name.value, this);
  191. return '';
  192. }
  193. }
  194. export class Rule extends Node {
  195. public property: Property;
  196. public value: Literal | Identifier | Application;
  197. public constructor(
  198. property: Property,
  199. value: Literal | Identifier | Application
  200. ) {
  201. super();
  202. this.property = property;
  203. this.value = value;
  204. }
  205. public compile(env: Env, opts: Opts): string | EnvError {
  206. const wordSpacer = opts.prettyPrint ? ' ' : '';
  207. const indentSpacer = opts.prettyPrint ? ' ' + spaces(opts.depth) : '';
  208. const property = this.property.compile(env, opts);
  209. if (property instanceof EnvError) return property;
  210. const value = this.value.compile(env, opts);
  211. if (value instanceof EnvError) return value;
  212. return `${indentSpacer}${property}:${wordSpacer}${value};`;
  213. }
  214. }
  215. export class RuleSet extends Node {
  216. public selectors: Selector[];
  217. public children: (Application | Rule | RuleSet)[];
  218. public constructor(
  219. selectors: Selector[],
  220. children: (Application | Rule | RuleSet)[] = []
  221. ) {
  222. super();
  223. this.selectors = selectors;
  224. this.children = children;
  225. }
  226. public compile(env: Env, opts: Opts): string | EnvError {
  227. const lineSpacer = opts.prettyPrint ? '\n' : '';
  228. const wordSpacer = opts.prettyPrint ? ' ' : '';
  229. const indentSpacer = opts.prettyPrint ? spaces(opts.depth) : '';
  230. const [lBrace, rBrace] = ['{', '}'];
  231. const lineages = ([] as string[]).concat(
  232. ...this.selectors.map((sel) => sel.getLineages())
  233. );
  234. const rules: (String | EnvError)[] = [];
  235. const children: (String | EnvError)[] = [];
  236. this.children.map((child) => {
  237. if (child instanceof Rule) {
  238. rules.push(child.compile(env, opts));
  239. } else if (child instanceof RuleSet) {
  240. children.push(child.compile(env, opts));
  241. } else if (child instanceof Application) {
  242. console.log(child.compile(env, opts));
  243. }
  244. });
  245. const rulesError = rules.find((node) => node instanceof EnvError);
  246. if (rulesError instanceof EnvError) return rulesError;
  247. const childrenError = children.find((node) => node instanceof EnvError);
  248. if (childrenError instanceof EnvError) return childrenError;
  249. const compiledRules = rules.join(lineSpacer);
  250. const compiledChildren = children.join(lineSpacer);
  251. const lineage = lineages.join(`,${wordSpacer}`);
  252. const frontMatter = `${indentSpacer}${lineage}${wordSpacer}${lBrace}${lineSpacer}`;
  253. const backMatter = `${lineSpacer}${indentSpacer}${rBrace}${
  254. compiledChildren.length ? lineSpacer : ''
  255. }`;
  256. return rules.length
  257. ? `${frontMatter}${compiledRules}${backMatter}${compiledChildren}`
  258. : compiledChildren;
  259. }
  260. }
  261. }