private static void BindParameter(Scope scope, Dictionary <string, MixinBlock> mixinReferences, Dictionary <string, Value> boundVariables, MixinParameter @param, Value value) { if (value is IncludeSelectorValue) { var toMap = (IncludeSelectorValue)value; mixinReferences[@param.Name] = CreateAnonMixin(toMap); return; } if (!(value is FuncValue)) { boundVariables[@param.Name] = value.Bind(scope); return; } var funcVal = value as FuncValue; var asMixin = scope.LookupMixin(funcVal.Name); if (asMixin == null) { var val = scope.LookupVariable(funcVal.Name, -1, -1, null); if (val is IncludeSelectorValue) { var toMap = (IncludeSelectorValue)val; mixinReferences[@param.Name] = CreateAnonMixin(toMap); return; } boundVariables[@param.Name] = funcVal.Bind(scope); } else { mixinReferences[@param.Name] = asMixin; } }
internal SelectorAndBlock BindAndEvaluateMixins(Scope scope = null, int depth = 0, LinkedList <MixinApplicationProperty> invokationChain = null) { if (depth > Scope.MAX_DEPTH) { Current.RecordError(ErrorType.Compiler, invokationChain.Last.Value, "Scope max depth exceeded, probably infinite recursion"); throw new StoppedCompilingException(); } var injectReset = IsReset && scope == null; scope = scope ?? Current.GlobalScope; if (injectReset) { var vars = new Dictionary <string, Value>(); foreach (var var in ResetContext) { vars[var.Name] = var.Value.Bind(scope); } scope = scope.Push(vars, new Dictionary <string, MixinBlock>(), Position.NoSite); } invokationChain = invokationChain ?? new LinkedList <MixinApplicationProperty>(); var retRules = new List <Property>(); var nestedBlocks = Properties.OfType <NestedBlockProperty>(); var mixinApplications = Properties.OfType <MixinApplicationProperty>(); var nameValueRules = Properties.OfType <NameValueProperty>().ToList(); var inclRules = Properties.OfType <IncludeSelectorProperty>(); var resetRules = Properties.Where(w => w is ResetProperty || w is ResetSelfProperty); var variableRules = Properties.OfType <VariableProperty>(); var innerMedia = Properties.OfType <InnerMediaProperty>(); if (variableRules.Count() > 0) { var vars = new Dictionary <string, Value>(); foreach (var var in variableRules) { vars[var.Name] = var.Value.Bind(scope); } scope = scope.Push(vars, new Dictionary <string, MixinBlock>(), this); } foreach (var nameValue in nameValueRules) { retRules.Add(new NameValueProperty(nameValue.Name, nameValue.Value.Bind(scope), nameValue.Start, nameValue.Stop, nameValue.FilePath)); } // Do the overrides last, so they can override everything foreach (var rule in mixinApplications.OrderBy(k => k.DoesOverride ? 1 : 0)) { var mixin = scope.LookupMixin(rule.Name); if (mixin == null) { if (!rule.IsOptional) { Current.RecordError(ErrorType.Compiler, rule, "No mixin of the name [" + rule.Name + "] found"); } // We can keep going, to possibly find more errors, so do so! continue; } var @params = rule.Parameters.ToList(); var passedCount = @params.Count; var maximum = mixin.Parameters.Count(); var minimum = mixin.Parameters.Count(c => c.DefaultValue is NotFoundValue); var iLastRaw = @params.FindLastIndex(a => a.Name.IsNullOrEmpty()); var iFirstByName = @params.FindIndex(a => a.Name.HasValue()); var byNames = @params.Where(s => s.Name.HasValue()); if (iFirstByName != -1 && iLastRaw > iFirstByName) { Current.RecordError(ErrorType.Compiler, rule, "Arguments passed by name must appear after those passed without"); continue; } var outerContinue = false; foreach (var byName in byNames) { bool found = false; int index = -1; for (var i = 0; i < mixin.Parameters.Count(); i++) { var p = mixin.Parameters.ElementAt(i); if (p.Name == byName.Name) { found = true; index = i; } } if (!found) { Current.RecordError(ErrorType.Compiler, rule, "Argument to mixin [" + rule.Name + "] passed with name [" + byName.Name + "] but no parameter with that name exists."); outerContinue = true; continue; } if (index <= iLastRaw) { Current.RecordError(ErrorType.Compiler, rule, "Argument [" + byName.Name + "] passed by name, but already passed earlier in mixin declaration"); outerContinue = true; continue; } } if (outerContinue) { continue; } if (iLastRaw != -1 && iLastRaw + 1 < minimum) { Current.RecordError(ErrorType.Compiler, rule, "Tried to invoke mixin [" + rule.Name + "] with " + passedCount + " parameters, when a minimum of " + minimum + " are needed."); continue; } if (passedCount > maximum) { Current.RecordError(ErrorType.Compiler, rule, "Tried to invoke mixin [" + rule.Name + "] with " + passedCount + " parameters, when a maximum of " + maximum + " are allowed."); continue; } var alreadyDefined = new List <string>(); var mixinReferences = new Dictionary <string, MixinBlock>(); var boundVariables = new Dictionary <string, Value>(); for (int i = 0; i <= iLastRaw; i++) { var @param = mixin.Parameters.ElementAt(i); var value = rule.Parameters.ElementAt(i); BindParameter(scope, mixinReferences, boundVariables, @param, value.Value); alreadyDefined.Add(@param.Name); } foreach (var byName in byNames) { var @param = mixin.Parameters.Single(p => p.Name == byName.Name); var value = byName.Value; BindParameter(scope, mixinReferences, boundVariables, @param, value); alreadyDefined.Add(@param.Name); } foreach (var withDefault in mixin.Parameters.Where(w => !(w.DefaultValue is NotFoundValue) && !alreadyDefined.Contains(w.Name))) { var @param = mixin.Parameters.Single(p => p.Name == withDefault.Name); var value = withDefault.DefaultValue; BindParameter(scope, mixinReferences, boundVariables, @param, value); } foreach (var mustBeDefined in mixin.Parameters.Where(w => w.DefaultValue is NotFoundValue)) { if (!alreadyDefined.Contains(mustBeDefined.Name)) { Current.RecordError(ErrorType.Compiler, rule, "No value passed for parameter [" + mustBeDefined.Name + "]"); continue; } } bool includeArguments = true; var argParts = new List <Value>(); foreach (var param in mixin.Parameters) { Value part; if (boundVariables.TryGetValue(param.Name, out part)) { if (!(part is ExcludeFromOutputValue)) { argParts.Add(part); } } else { includeArguments = false; } } // Only include arguments if all the variables are "simple", and thus found as variables if (includeArguments && argParts.Count > 0) { boundVariables["arguments"] = argParts.Count > 1 ? new CommaDelimittedValue(argParts) : argParts[0]; } var localScope = Current.GlobalScope.Push(boundVariables, mixinReferences, rule); invokationChain.AddLast(rule); var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, mixin.Properties, null, -1, -1, null); var boundMixin = blockEquiv.BindAndEvaluateMixins(localScope, depth + 1, invokationChain); invokationChain.RemoveLast(); var newRules = boundMixin.Properties; // Any rules that are defined in a @mixin()! clause need to override the outer ones if (rule.DoesOverride) { retRules.RemoveAll(r => r is NameValueProperty && newRules.OfType <NameValueProperty>().Any(a => a.Name == ((NameValueProperty)r).Name)); } retRules.AddRange(newRules); } foreach (var block in nestedBlocks) { retRules.Add(new NestedBlockProperty(block.Block.BindAndEvaluateMixins(scope), block.Start, block.Stop)); } foreach (var media in innerMedia) { retRules.Add(new InnerMediaProperty(media.MediaQuery, media.Block.BindAndEvaluateMixins(scope), media.Start, media.Stop, media.FilePath)); } retRules.AddRange(inclRules); retRules.AddRange(resetRules); return(new SelectorAndBlock(this.Selector, retRules, this.ResetContext, this.Start, this.Stop, this.FilePath)); }