import * as AST from '../ast'; import Env, { EnvError } from '../env'; import Token from '../token'; interface Expansion { rules: (string | EnvError)[]; children: (string | EnvError)[]; } export class Mixin { public name: Token; public parameters: AST.Identifier[]; public children: AST.Child[]; public constructor( name: Token, parameters: AST.Identifier[], children: AST.Child[] ) { this.name = name; this.parameters = parameters; this.children = children; } public compile(env: Env, _opts: AST.Opts) { env.set(this.name.value, this); return ''; } public expand( env: Env, opts: AST.Opts, parents: AST.Selector[], args: AST.Node[] ): Expansion | EnvError { if (this.parameters.length !== args.length) { return new EnvError( this.name.line, `Expected ${this.parameters.length} arguments but received ${ args.length }` ); } const expandedArgs: AST.Node[] = []; for (const arg of args) { if (arg instanceof AST.Identifier) { const lookup = env.get(arg.name); if (lookup instanceof EnvError) return lookup; expandedArgs.push(lookup); } expandedArgs.push(arg); } const mixinEnv = new Env(env); this.parameters.forEach((param, index) => { mixinEnv.set(param.name.value, expandedArgs[index]); }); const rules = this.children .filter((child: AST.Node): child is AST.Rule => child instanceof AST.Rule) .map((rule: AST.Rule) => rule.compile(mixinEnv, opts)); const children = this.children .filter( (child: AST.Node): child is AST.RuleSet => child instanceof AST.RuleSet ) .map((ruleSet: AST.RuleSet) => { ruleSet.selectors.forEach((sel) => (sel.parents = parents)); return ruleSet.compile(mixinEnv, opts); }); const applicationExpansions: (Expansion | EnvError)[] = this.children .filter( (child: AST.Node): child is AST.Application => child instanceof AST.Application ) .map((application: AST.Application) => { const mixin = mixinEnv.get(application.name.value); if (mixin instanceof EnvError) { return mixin; } else if (mixin instanceof AST.Mixin) { return mixin.expand(mixinEnv, opts, parents, application.arguments); } else { return new EnvError(1, 'Expected a mixin'); } }); const expansionError = applicationExpansions.find( (el) => el instanceof EnvError ); if (expansionError) { return expansionError; } applicationExpansions.forEach((el) => { if (!(el instanceof EnvError)) { el.rules.forEach((rule) => { if (!(rule instanceof EnvError)) { rules.push(rule); } }); } }); return { rules, children }; } }