internal NestedBlockProperty(SelectorAndBlock innerBlock, int start, int stop) { Block = innerBlock; Start = start; Stop = stop; }
public InnerMediaProperty(MediaQuery media, SelectorAndBlock block, int start, int stop, string file) { MediaQuery = media; Block = block; Start = start; Stop = stop; FilePath = file; }
private static void Verify(SelectorAndBlock block) { var nestedBlock = block.Properties.OfType<NestedBlockProperty>().ToList(); var innerMedia = block.Properties.OfType<InnerMediaProperty>().ToList(); if (nestedBlock.Count != 0) { throw new InvalidOperationException("It shouldn't be possible for nested blocks to remain here"); } foreach (var media in innerMedia) { Current.RecordError(ErrorType.Compiler, media, "@media blocks cannot be nested within each other"); } }
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); }
internal static InnerMediaProperty ParseInnerMediaDirective(ParserStream stream) { var start = stream.Position; var media = new StringBuilder(); stream.ScanUntil(media, '{'); var mediaStr = media.ToString().Trim(); if (mediaStr.IsNullOrEmpty()) { Current.RecordError(ErrorType.Parser, Position.Create(start, stream.Position, Current.CurrentFilePath), "Expected media list"); throw new StoppedParsingException(); } var mediaQuery = MediaQueryParser.Parse(mediaStr, Position.Create(start, stream.Position, Current.CurrentFilePath)); var props = ParseCssRules(InvalidSelector.Singleton, stream); var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, props, null, start, stream.Position, Current.CurrentFilePath); return new InnerMediaProperty(mediaQuery, blockEquiv, start, stream.Position, Current.CurrentFilePath); }
public static List<Block> Task(List<Block> blocks) { var ret = new List<Block>(); ret.AddRange(blocks.OfType<CssCharset>()); ret.AddRange(blocks.OfType<Model.Import>().Select(s => new Model.Import(MinifyValue(s.ToImport), ForQuery(s.MediaQuery), s.Start, s.Stop, s.FilePath))); ret.AddRange(blocks.Where(w => w is SelectorAndBlock && ((SelectorAndBlock)w).IsReset)); var remainder = blocks.Where(w => !ret.Contains(w)); foreach (var statement in remainder) { var block = statement as SelectorAndBlock; if (block != null) { var rules = new List<Property>(); foreach (var prop in block.Properties) { rules.Add(MinifyProperty(prop)); } rules = MinifyPropertyList(rules).ToList(); ret.Add(new SelectorAndBlock(block.Selector, rules, block.ResetContext, block.Start, block.Stop, block.FilePath)); continue; } var media = statement as MediaBlock; if (media != null) { var subStatements = Task(media.Blocks.ToList()); ret.Add(new MediaBlock(ForQuery(media.MediaQuery), subStatements, media.Start, media.Stop, media.FilePath)); continue; } var keyframes = statement as KeyFramesBlock; if (keyframes != null) { var frames = new List<KeyFrame>(); // minify each frame foreach (var frame in keyframes.Frames) { var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, frame.Properties, null, frame.Start, frame.Stop, frame.FilePath); var mind = Task(new List<Block>() { blockEquiv }); frames.Add(new KeyFrame(frame.Percentages.ToList(), ((SelectorAndBlock)mind[0]).Properties.ToList(), frame.Start, frame.Stop, frame.FilePath)); } // collapse frames if rules are identical var frameMap = frames.ToDictionary( d => { using (var str = new StringWriter()) using (var css = new MinimalCssWriter(str)) { foreach (var rule in d.Properties.Cast<NameValueProperty>()) { css.WriteRule(rule, lastRule: false); } return str.ToString(); } }, d => d ); frames.Clear(); foreach (var frame in frameMap.GroupBy(k => k.Key)) { var allPercents = new List<decimal>(); foreach (var f in frame) { allPercents.AddRange(f.Value.Percentages); } var urFrame = frame.First().Value; frames.Add(new KeyFrame(allPercents, urFrame.Properties.ToList(), urFrame.Start, urFrame.Stop, urFrame.FilePath)); } ret.Add(new KeyFramesBlock(keyframes.Prefix, keyframes.Name, frames, keyframes.Variables.ToList(), keyframes.Start, keyframes.Stop, keyframes.FilePath)); continue; } ret.Add(statement); } return ret; }
private static SelectorAndBlock PrefixBlock(SelectorAndBlock block) { var ret = new List<Property>(); var asNameValue = block.Properties.Cast<NameValueProperty>().ToList(); foreach (var prop in asNameValue) { var possible = PrefixProperty(prop); // No prefix versions, no point in any conflict checking; just put it back and continue if (possible == null) { ret.Add(prop); continue; } var alreadyPresent = asNameValue.Where( w => possible.Any(p => p.Name.Equals(w.Name, StringComparison.InvariantCultureIgnoreCase) && p.Name != prop.Name ) ).ToList(); foreach (var dupe in alreadyPresent) { var dupeOf = possible.Where(w => w.Name == dupe.Name).ToList(); if (dupeOf.All(d => d.Value.Equals(dupe.Value))) { Current.RecordInfo("Prefixed property " + dupe.Name + " in '" + block.Selector + "' could have been generated automatically"); } } possible.RemoveAll(x => alreadyPresent.Any(y => y.Name.Equals(x.Name, StringComparison.InvariantCultureIgnoreCase))); ret.AddRange(possible); } return new SelectorAndBlock(block.Selector, ret, block.ResetContext, block.Start, block.Stop, block.FilePath); }
private static IEnumerable<Block> Unroll(SelectorAndBlock block) { var ret = new List<Block>(); var props = new List<Property>(); foreach (var prop in block.Properties) { var media = prop as InnerMediaProperty; var nested = prop as NestedBlockProperty; if (media == null && nested == null) { props.Add(prop); continue; } if (nested != null) { var inner = Unroll(nested.Block); var innerMedia = inner.OfType<MediaBlock>(); var other = inner.Where(i => !(i is MediaBlock)).Cast<SelectorAndBlock>(); props.AddRange(other.Select(s => new NestedBlockProperty(s, s.Start, s.Stop))); foreach (var m in innerMedia) { var selBlock = new SelectorAndBlock( block.Selector, m.Blocks.Cast<SelectorAndBlock>().Select(s => new NestedBlockProperty(s, s.Start, s.Stop)), null, m.Start, m.Stop, m.FilePath ); var newMedia = new MediaBlock( m.MediaQuery, new List<Block> { selBlock }, m.Start, m.Stop, m.FilePath ); ret.Add(newMedia); } continue; } var unrolled = new MediaBlock( media.MediaQuery, new List<Block> { new SelectorAndBlock( block.Selector, media.Block.Properties, null, -1, -1, media.FilePath ) }, -1, -1, media.FilePath ); ret.Add(unrolled); } ret.Add(new SelectorAndBlock(block.Selector, props, null, block.Start, block.Stop, block.FilePath)); return ret; }
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)); }
private static SelectorAndBlock CacheBreakBlock(SelectorAndBlock block) { var ret = new List<Property>(); foreach (var prop in block.Properties.Cast<NameValueProperty>()) { ret.Add(CacheBreakProperty(prop)); } return new SelectorAndBlock(block.Selector, ret, block.ResetContext, block.Start, block.Stop, block.FilePath); }
internal NestedBlockProperty(SelectorAndBlock innerBlock, int start, int stop) { Block = innerBlock; Start = start; Stop = stop; }
public InnerMediaProperty(MediaQuery media, SelectorAndBlock block, int start, int stop, string file) { MediaQuery = media; Block = block; Start = start; Stop = stop; FilePath = file; }