Browse Source

Allow rulesets inside mixins

master
Dylan Baker 5 years ago
parent
commit
7f333455f9
3 changed files with 88 additions and 53 deletions
  1. 23
    23
      src/ast.ts
  2. 42
    30
      src/parser.ts
  3. 23
    0
      src/tests/parser.test.ts

+ 23
- 23
src/ast.ts View File

@@ -1,7 +1,10 @@
1 1
 import Env, { EnvError } from './env';
2 2
 import Token from './token';
3 3
 
4
+
4 5
 export namespace AST {
6
+  export type Child = Rule | RuleSet | Application;
7
+
5 8
   export interface Opts {
6 9
     depth: number;
7 10
     prettyPrint: boolean;
@@ -217,13 +220,13 @@ export namespace AST {
217 220
   export class Mixin extends Node {
218 221
     public name: Token;
219 222
     public parameters: Identifier[];
220
-    public rules: Rule[];
223
+    public children: Child[];
221 224
 
222
-    public constructor(name: Token, parameters: Identifier[], rules: Rule[]) {
225
+    public constructor(name: Token, parameters: Identifier[], children: Child[]) {
223 226
       super();
224 227
       this.name = name;
225 228
       this.parameters = parameters;
226
-      this.rules = rules;
229
+      this.children = children;
227 230
     }
228 231
     public compile(env: Env, _opts: Opts) {
229 232
       env.set(this.name.value, this);
@@ -278,28 +281,25 @@ export namespace AST {
278 281
         ...this.selectors.map((sel) => sel.getLineages())
279 282
       );
280 283
 
281
-      const rules: Array<string | EnvError> = this.children
282
-        .filter((child) => child instanceof Rule)
283
-        .map(
284
-          (child): string | EnvError => {
285
-            return child.compile(env, opts);
286
-          }
287
-        );
288
-      const rulesError = rules.find((child) => child instanceof EnvError);
289
-      if (rulesError !== undefined) return rulesError;
290
-
291
-      const compiledRules = rules.join(lineSpacer);
284
+      const rules: (String | EnvError)[] = [];
285
+      const children: (String | EnvError)[] = [];
286
+
287
+      this.children.map((child) => {
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
+          console.log(child.compile(env, opts));
294
+        }
295
+      });
292 296
 
293
-      const children: Array<string | EnvError> = this.children
294
-        .filter((child) => child instanceof RuleSet)
295
-        .map(
296
-          (child): string | EnvError => {
297
-            return child.compile(env, opts);
298
-          }
299
-        );
300
-      const childrenError = children.find((child) => child instanceof EnvError);
301
-      if (childrenError !== undefined) return childrenError;
297
+      const rulesError = rules.find((node) => node instanceof EnvError);
298
+      if (rulesError instanceof EnvError) return rulesError;
299
+      const childrenError = children.find((node) => node instanceof EnvError);
300
+      if (childrenError instanceof EnvError) return childrenError;
302 301
 
302
+      const compiledRules = rules.join(lineSpacer);
303 303
       const compiledChildren = children.join(lineSpacer);
304 304
 
305 305
       const lineage = lineages.join(`,${wordSpacer}`);

+ 42
- 30
src/parser.ts View File

@@ -174,8 +174,6 @@ export default class Parser {
174 174
     const name = this.eat(TokenTypes.LITERAL);
175 175
     if (name instanceof ParserError) return name;
176 176
 
177
-    const rules: AST.Rule[] = [];
178
-
179 177
     const openParen = this.eat(TokenTypes.LPAREN);
180 178
     if (openParen instanceof ParserError) return openParen;
181 179
 
@@ -190,18 +188,21 @@ export default class Parser {
190 188
     const closeParen = this.eat(TokenTypes.RPAREN);
191 189
     if (closeParen instanceof ParserError) return closeParen;
192 190
 
193
-    while (this.currentToken().type === TokenTypes.PROPERTY) {
194
-      const property = this.property();
195
-      if (property instanceof ParserError) return property;
196
-      const value = this.value();
197
-      if (value instanceof ParserError) return value;
198
-      rules.push(new AST.Rule(property, value));
191
+    const children: AST.Child[] = [];
192
+    while (
193
+      [TokenTypes.PROPERTY, TokenTypes.LPAREN].includes(
194
+        this.currentToken().type
195
+      )
196
+    ) {
197
+      const child = this.child();
198
+      if (child instanceof ParserError) return child;
199
+      children.push(child);
199 200
     }
200 201
 
201 202
     const rParen = this.eat(TokenTypes.RPAREN);
202 203
     if (rParen instanceof ParserError) return rParen;
203 204
 
204
-    return new AST.Mixin(name, parameters, rules);
205
+    return new AST.Mixin(name, parameters, children);
205 206
   }
206 207
 
207 208
   private application(): AST.Application | ParserError {
@@ -251,27 +252,9 @@ export default class Parser {
251 252
         this.currentToken().type
252 253
       )
253 254
     ) {
254
-      if (this.currentToken().type === TokenTypes.PROPERTY) {
255
-        const propertyToken = this.eat(TokenTypes.PROPERTY);
256
-        if (propertyToken instanceof ParserError) return propertyToken;
257
-
258
-        const property = new AST.Property(propertyToken);
259
-
260
-        const value = this.value();
261
-        if (value instanceof ParserError) return value;
262
-
263
-        children.push(new AST.Rule(property, value));
264
-      } else if (this.currentToken().type === TokenTypes.LPAREN) {
265
-        if (this.nextToken().type === TokenTypes.LITERAL) {
266
-          const ruleSet = this.ruleSet(selectors);
267
-          if (ruleSet instanceof ParserError) return ruleSet;
268
-          children.push(ruleSet);
269
-        } else {
270
-          const application = this.application();
271
-          if (application instanceof ParserError) return application;
272
-          children.push(application);
273
-        }
274
-      }
255
+      const child = this.child(selectors);
256
+      if (child instanceof ParserError) return child;
257
+      children.push(child);
275 258
     }
276 259
 
277 260
     const rParen = this.eat(TokenTypes.RPAREN);
@@ -323,6 +306,35 @@ export default class Parser {
323 306
     return new AST.Selector(literal.value, parents);
324 307
   }
325 308
 
309
+  private child(parents: AST.Selector[] = []): AST.Child | ParserError {
310
+    if (this.currentToken().type === TokenTypes.PROPERTY) {
311
+      const property = this.property();
312
+      if (property instanceof ParserError) return property;
313
+
314
+      const value = this.value();
315
+      if (value instanceof ParserError) return value;
316
+
317
+      return new AST.Rule(property, value);
318
+    } else if (this.currentToken().type === TokenTypes.LPAREN) {
319
+      if (this.nextToken().type === TokenTypes.LITERAL) {
320
+        const ruleSet = this.ruleSet(parents);
321
+        if (ruleSet instanceof ParserError) return ruleSet;
322
+        return ruleSet;
323
+      } else if (this.nextToken().type === TokenTypes.FUNCTION_NAME) {
324
+        const application = this.application();
325
+        if (application instanceof ParserError) return application;
326
+        return application;
327
+      }
328
+    }
329
+
330
+    return this.error(
331
+      `Unexpected ${
332
+        this.nextToken().type
333
+      }; expected literal or function/macro application`,
334
+      this.nextToken()
335
+    );
336
+  }
337
+
326 338
   private eat(type: TokenTypes): Token | ParserError {
327 339
     const token = this.currentToken();
328 340
     if (type === token.type) {

+ 23
- 0
src/tests/parser.test.ts View File

@@ -356,3 +356,26 @@ test('parse mixin application', (t) => {
356 356
     )
357 357
   }
358 358
 });
359
+
360
+test('parse mixin with children', (t) => {
361
+  t.plan(2);
362
+  const result = parse('(@mixin m () (div :color blue))');
363
+  t.false(result instanceof ParserError);
364
+  if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
365
+    t.deepEqual(
366
+      result.tree[0],
367
+      new AST.Mixin(
368
+        literalToken('m'),
369
+        [],
370
+        [
371
+          new AST.RuleSet(
372
+            [
373
+              new AST.Selector(literalToken('div')),
374
+            ],
375
+            [new AST.Rule(property('color'), literalNode('blue'))]
376
+          )
377
+        ]
378
+      ),
379
+    )
380
+  }
381
+});

Loading…
Cancel
Save