123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import Env, { EnvError } from './env';
- import Token from './token';
-
- export namespace AST {
- export interface Opts {
- depth: number;
- prettyPrint: boolean;
- }
-
- const spaces = (depth: number): string => {
- return Array(depth)
- .fill(' ')
- .join('');
- };
-
- export class Node {
- public compile(_env: Env, _opts: Opts): string | EnvError {
- return new EnvError(1, "shouldn't call directly");
- }
- }
-
- export class Literal extends Node {
- public value: Token;
-
- public constructor(value: Token) {
- super();
- this.value = value;
- }
-
- public compile(_env: Env, _opts: Opts) {
- return this.value.value;
- }
- }
-
- export class Identifier extends Node {
- public name: Token;
-
- public constructor(name: Token) {
- super();
- this.name = name;
- }
-
- public compile(env: Env, opts: Opts): string | EnvError {
- const value = env.get(this.name);
- if (value instanceof EnvError) return value;
- return value.compile(env, opts);
- }
- }
-
- export class Selector extends Node {
- public name: Token;
- public parents: Selector[];
-
- public constructor(name: Token, parents: Selector[] = []) {
- super();
- this.name = name;
- this.parents = parents;
- }
-
- public getLineages(): string[] {
- if (this.parents.length === 0) return [this.name.value];
- return Array.prototype.concat(
- ...this.parents.map((parent) => {
- return parent.getLineages().map((lineage) => {
- if (this.name.value.match(/^&/)) {
- return lineage + this.name.value.slice(1);
- }
- return lineage + ' ' + this.name.value;
- });
- })
- );
- }
- }
-
- export class Property extends Node {
- public name: Token;
-
- public constructor(name: Token) {
- super();
- this.name = name;
- }
-
- public compile(_env: Env, _opts: Opts): string | EnvError {
- return this.name.value;
- }
- }
-
- export class Binding extends Node {
- public identifier: Identifier;
- public value: Node;
-
- public constructor(identifier: Identifier, value: Node) {
- super();
- this.identifier = identifier;
- this.value = value;
- }
- }
-
- export class Let extends Node {
- public bindings: Binding[];
- public children: Node[];
-
- public constructor(bindings: Binding[], children: Node[]) {
- super();
- this.bindings = bindings;
- this.children = children;
- }
-
- public compile(env: Env, opts: Opts): string | EnvError {
- this.bindings.forEach((binding) => {
- env.set(binding.identifier.name.value, binding.value);
- });
-
- const children = this.children.map((child) => child.compile(env, opts));
- const childrenError = children.find((node) => node instanceof EnvError);
- if (childrenError instanceof EnvError) return childrenError;
-
- return children.join(opts.prettyPrint ? '\n' : '');
- }
- }
-
- export class MediaQueryPredicate extends Node {
- public property: Property;
- public value: Literal | Identifier | Application;
-
- public constructor(
- property: Property,
- value: Literal | Identifier | Application
- ) {
- super();
- this.property = property;
- this.value = value;
- }
-
- public compile(env: Env, opts: Opts) {
- const wordSpacer = opts.prettyPrint ? ' ' : '';
- const property = this.property.compile(env, opts);
- const value = this.value.compile(env, opts);
- return `${property}:${wordSpacer}${value}`;
- }
- }
-
- export class MediaQuery extends Node {
- public predicate: MediaQueryPredicate;
- public children: Node[];
-
- public constructor(predicate: MediaQueryPredicate, children: Node[]) {
- super();
- this.predicate = predicate;
- this.children = children;
- }
-
- public compile(env: Env, opts: Opts): string | EnvError {
- const lineSpacer = opts.prettyPrint ? '\n' : '';
- const wordSpacer = opts.prettyPrint ? ' ' : '';
- const predicate = this.predicate.compile(env, opts);
-
- const children = this.children.map((child) =>
- child.compile(env, { ...opts, depth: opts.depth + 2 })
- );
- const childrenError = children.find((node) => node instanceof EnvError);
- if (childrenError instanceof EnvError) return childrenError;
-
- return `@media(${predicate})${wordSpacer}{${lineSpacer}${children.join(
- lineSpacer
- )}${lineSpacer}}`;
- }
- }
-
- export class Keyframes extends Node {
- public name: Literal;
- public children: Node[];
-
- public constructor(name: Literal, children: Node[]) {
- super();
- this.name = name;
- this.children = children;
- }
-
- public compile(env: Env, opts: Opts) {
- const lineSpacer = opts.prettyPrint ? '\n' : '';
- const wordSpacer = opts.prettyPrint ? ' ' : '';
- const name = this.name.compile(env, opts);
-
- const children = this.children.map((child) =>
- child.compile(env, { ...opts, depth: opts.depth + 2 })
- );
- const childrenError = children.find((node) => node instanceof EnvError);
- if (childrenError instanceof EnvError) return childrenError;
-
- return `@keyframes ${name}${wordSpacer}{${lineSpacer}${children.join(
- lineSpacer
- )}${lineSpacer}}`;
- }
- }
-
- export class Application extends Node {
- public name: Literal;
- public arguments: Node[];
-
- public constructor(name: Literal, args: Node[]) {
- super();
- this.name = name;
- this.arguments = args;
- }
-
- public compile(env: Env, opts: Opts) {
- const wordSpacer = opts.prettyPrint ? ' ' : '';
- const [lParen, rParen, comma] = ['(', ')', ','];
- const compiledArguments = this.arguments
- .map((arg) => arg.compile(env, opts))
- .join(`${comma}${wordSpacer}`);
- return `${this.name.value.value}${lParen}${compiledArguments}${rParen}`;
- }
- }
-
- export class Mixin extends Node {
- public name: Token;
- public parameters: Identifier[];
- public rules: Rule[];
-
- public constructor(name: Token, parameters: Identifier[], rules: Rule[]) {
- super();
- this.name = name;
- this.parameters = parameters;
- this.rules = rules;
- }
- public compile(env: Env, _opts: Opts) {
- env.set(this.name.value, this);
- return '';
- }
- }
-
- export class Rule extends Node {
- public property: Property;
- public value: Literal | Identifier | Application;
-
- public constructor(
- property: Property,
- value: Literal | Identifier | Application
- ) {
- super();
- this.property = property;
- this.value = value;
- }
-
- public compile(env: Env, opts: Opts): string | EnvError {
- const wordSpacer = opts.prettyPrint ? ' ' : '';
- const indentSpacer = opts.prettyPrint ? ' ' + spaces(opts.depth) : '';
- const property = this.property.compile(env, opts);
- if (property instanceof EnvError) return property;
- const value = this.value.compile(env, opts);
- if (value instanceof EnvError) return value;
- return `${indentSpacer}${property}:${wordSpacer}${value};`;
- }
- }
-
- export class RuleSet extends Node {
- public selectors: Selector[];
- public children: (Application | Rule | RuleSet)[];
-
- public constructor(
- selectors: Selector[],
- children: (Application | Rule | RuleSet)[] = []
- ) {
- super();
- this.selectors = selectors;
- this.children = children;
- }
-
- public compile(env: Env, opts: Opts): string | EnvError {
- const lineSpacer = opts.prettyPrint ? '\n' : '';
- const wordSpacer = opts.prettyPrint ? ' ' : '';
- const indentSpacer = opts.prettyPrint ? spaces(opts.depth) : '';
- const [lBrace, rBrace] = ['{', '}'];
-
- const lineages = ([] as string[]).concat(
- ...this.selectors.map((sel) => sel.getLineages())
- );
-
- const rules: Array<string | EnvError> = this.children
- .filter((child) => child instanceof Rule)
- .map(
- (child): string | EnvError => {
- return child.compile(env, opts);
- }
- );
- const rulesError = rules.find((child) => child instanceof EnvError);
- if (rulesError !== undefined) return rulesError;
-
- const compiledRules = rules.join(lineSpacer);
-
- const children: Array<string | EnvError> = this.children
- .filter((child) => child instanceof RuleSet).map(
- (child): string | EnvError => {
- return child.compile(env, opts);
- }
- );
- const childrenError = children.find((child) => child instanceof EnvError);
- if (childrenError !== undefined) return childrenError;
-
- const compiledChildren = children.join(lineSpacer);
-
- const lineage = lineages.join(`,${wordSpacer}`);
- const frontMatter = `${indentSpacer}${lineage}${wordSpacer}${lBrace}${lineSpacer}`;
- const backMatter = `${lineSpacer}${indentSpacer}${rBrace}${
- compiledChildren.length ? lineSpacer : ''
- }`;
- return rules.length
- ? `${frontMatter}${compiledRules}${backMatter}${compiledChildren}`
- : compiledChildren;
- }
- }
- }
|