BindAndEvaluateMixins() private method

private BindAndEvaluateMixins ( Scope scope = null, int depth, LinkedList invokationChain = null ) : SelectorAndBlock
scope Scope
depth int
invokationChain LinkedList
return SelectorAndBlock
Beispiel #1
0
        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>();

            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));
            }

            retRules.AddRange(inclRules);
            retRules.AddRange(resetRules);

            return new SelectorAndBlock(this.Selector, retRules, this.ResetContext, this.Start, this.Stop, this.FilePath);
        }
Beispiel #2
0
        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));
        }