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