private static SelectorExpression ParseAttributeSelector(Token token, TokensQueue tokens) { var attrName = tokens.Read(TokenType.Literal).StringValue; var operation = tokens.Read(); if (operation.Type == TokenType.CloseSquareBracket) return new AttributeExistsSelector(attrName); if (operation.Type == TokenType.Equal) { var val = tokens.Read(); if (val.Type != TokenType.Literal && val.Type != TokenType.String) { throw new TokenException("expected literal or string token", val); } tokens.Read(TokenType.CloseSquareBracket); return new AttributeEqualsSelector(attrName, val.StringValue); } throw new TokenException("unknown attribute operator", operation); }
protected static SelectorExpression ParsePseudoSelector(Token token, TokensQueue queue) { var next = queue.Read(TokenType.Literal); if (next.StringValue == "not") { var preview = queue.Peek(); if (preview.Type == TokenType.OpenParenthesis) { queue.Read(TokenType.OpenParenthesis); var expr = Parse(queue); if (!(expr is SimpleSelector)) throw new TokenException("simple selector expected", preview); queue.Read(TokenType.CloseParenthesis); return new NotExpression((SimpleSelector)expr); } } return new PseudoClassSelector(next.StringValue); }
protected static Expression Func(Token nameToken, TokensQueue tokens) { tokens.Read(TokenType.OpenParenthesis); var args = ParseArguments(tokens); tokens.Read(TokenType.CloseParenthesis); switch (nameToken.StringValue.ToLower()) { case "round": if (args.Count != 1) { throw new TokenException("expected 1 argument", nameToken); } return new RoundFunctionExpression(args.First()); default: throw new TokenException("unknown function " + nameToken.StringValue, nameToken); } }
public void MultilineCommentTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("/* comment\r\none*/\r\n/* comment\r\ntwo*/"); Assert.AreEqual(3, tokens.Count); var queue = new TokensQueue(tokens); var token = queue.Read(); Assert.AreEqual(TokenType.MultiLineComment, token.Type); Assert.AreEqual("/* comment\r\none*/", token.StringValue); queue.Read(TokenType.Whitespace); token = queue.Read(); Assert.AreEqual(TokenType.MultiLineComment, token.Type); Assert.AreEqual("/* comment\r\ntwo*/", token.StringValue); }
public void NumberTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("123 456 789.52"); Assert.AreEqual(5, tokens.Count); var queue = new TokensQueue(tokens); var token = queue.Read(TokenType.Number); Assert.AreEqual(123.0, token.NumberValue); queue.Read(TokenType.Whitespace); token = queue.Read(TokenType.Number); Assert.AreEqual(456.0, token.NumberValue); queue.Read(TokenType.Whitespace); token = queue.Read(TokenType.Number); Assert.AreEqual(789.52, token.NumberValue, 0.00000001); }
public static IList<Expression> ParseArguments(TokensQueue tokens) { var res = new List<Expression>(); while (!tokens.Empty) { var preview = tokens.Peek(); if (preview.Type == TokenType.CloseParenthesis) return res; var arg = Parse(tokens); res.Add(arg); preview = tokens.Peek(); if (preview.Type != TokenType.Comma) break; tokens.Read(TokenType.Comma); } return res; }
public void LiteralTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("abc def qwe;"); Assert.AreEqual(6, tokens.Count); var queue = new TokensQueue(tokens); Assert.AreEqual("abc", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("def", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("qwe", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(";", queue.Read(TokenType.Semicolon).StringValue); }
public void CssTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("p { color: red; }"); Assert.AreEqual(11, tokens.Count); var queue = new TokensQueue(tokens); Assert.AreEqual("p", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("{", queue.Read(TokenType.OpenCurlyBracket).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("color", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(":", queue.Read(TokenType.Colon).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("red", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(";", queue.Read(TokenType.Semicolon).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("}", queue.Read(TokenType.CloseCurlyBracket).StringValue); }
public void VendorPropertyTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("-webkit-property: value;"); Assert.AreEqual(5, tokens.Count); var queue = new TokensQueue(tokens); Assert.AreEqual("-webkit-property", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(":", queue.Read(TokenType.Colon).StringValue); Assert.AreEqual(" ", queue.Read(TokenType.Whitespace).StringValue); Assert.AreEqual("value", queue.Read(TokenType.Literal).StringValue); Assert.AreEqual(";", queue.Read(TokenType.Semicolon).StringValue); }
public void SingleCommentTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("//comment one\r\n//comment two"); Assert.AreEqual(2, tokens.Count); var queue = new TokensQueue(tokens); var token = queue.Read(); Assert.AreEqual(TokenType.SingleLineComment, token.Type); Assert.AreEqual("//comment one\r\n", token.StringValue); token = queue.Read(); Assert.AreEqual(TokenType.SingleLineComment, token.Type); Assert.AreEqual("//comment two", token.StringValue); }
private static Expression ParseHashColor(ref Token token, TokensQueue queue) { var val = queue.Read(TokenType.Literal); if (val.StringValue.Length == 6) { var rh = GetHexChar(ref val, 0); var rl = GetHexChar(ref val, 1); var gh = GetHexChar(ref val, 2); var gl = GetHexChar(ref val, 3); var bh = GetHexChar(ref val, 4); var bl = GetHexChar(ref val, 5); return new ColorExpression(rh * 16 + rl, gh * 16 + gl, bh * 16 + bl); } if (val.StringValue.Length == 3) { var rhl = GetHexChar(ref val, 0); var ghl = GetHexChar(ref val, 1); var bhl = GetHexChar(ref val, 2); return new ColorExpression(rhl * 17, ghl * 17, bhl * 17); } throw new TokenException("invalid hex color", token); }
protected static SelectorExpression ParseIdSelector(Token token, TokensQueue queue) { var next = queue.Read(TokenType.Literal); return new IdSelector(next.StringValue); }
private static Expression ParseWithPriority(TokensQueue tokens, int priority) { var left = ParseOperand(tokens); Token? whiteToken = null; while (!tokens.Empty) { tokens.SkipComments(); var preview = tokens.Peek(); switch (preview.Type) { case TokenType.Semicolon: case TokenType.CloseParenthesis: case TokenType.ExclamationPoint: return left; } var tokenPriority = GetPriority(preview.Type); if (tokenPriority < priority) { return left; } switch (preview.Type) { case TokenType.Plus: case TokenType.Minus: case TokenType.Multiply: case TokenType.Divide: case TokenType.Percentage: case TokenType.LeftShift: case TokenType.RightShift: case TokenType.Comma: var token = tokens.Read(); left = ProcessBinaryExpression(token, left, tokens); whiteToken = null; break; case TokenType.Whitespace: whiteToken = tokens.Read(); break; default: if (whiteToken.HasValue) { left = ProcessBinaryExpression(whiteToken.Value, left, tokens); whiteToken = null; break; } throw new TokenException("unexpected token " + preview.StringValue, preview); } } return left; }
private static Expression ParseOperand(TokensQueue tokens) { tokens.SkipWhiteAndComments(); var token = tokens.Read(); switch (token.Type) { case TokenType.Number: return ParseNumber(ref token, tokens); case TokenType.Literal: return ParseLiteral(ref token, tokens); case TokenType.Hash: return ParseHashColor(ref token, tokens); case TokenType.Minus: return new NegateExpression(ParseOperand(tokens)); case TokenType.OpenParenthesis: var inner = Parse(tokens); tokens.Read(TokenType.CloseParenthesis); return inner; default: throw new TokenException("unexpected token " + token.StringValue, token); } }
private static Expression ParseNumber(ref Token token, TokensQueue queue) { var inner = new NumberExpression(token.NumberValue); if (!queue.Empty) { var preview = queue.Peek(); if (preview.Type == TokenType.Literal || preview.Type == TokenType.Percentage) { var unitToken = queue.Read(); var unit = ParseUnit(ref unitToken); return new UnitExpression(inner, unit); } } return inner; }
private static SelectorExpression ParseOperand(TokensQueue tokens, SelectorExpression parent = null) { tokens.SkipWhiteAndComments(); var token = tokens.Read(); switch (token.Type) { case TokenType.Literal: return ParseTypeSelector(token, tokens); case TokenType.Dot: return ParseClassSelector(token, tokens); case TokenType.Hash: return ParseIdSelector(token, tokens); case TokenType.Colon: return ParsePseudoSelector(token, tokens); case TokenType.OpenSquareBracket: return ParseAttributeSelector(token, tokens); case TokenType.Ampersand: return new ParentSelector(parent); default: throw new TokenException("unexpected token " + token.StringValue, token); } }
public void NumberUnitTest() { var tokenizer = new Tokenizer(); var tokens = tokenizer.Read("123px 456em"); Assert.AreEqual(5, tokens.Count); var queue = new TokensQueue(tokens); Assert.AreEqual(123.0, queue.Read(TokenType.Number).NumberValue); Assert.AreEqual("px", queue.Read(TokenType.Literal).StringValue); queue.Read(TokenType.Whitespace); Assert.AreEqual(456.0, queue.Read(TokenType.Number).NumberValue); Assert.AreEqual("em", queue.Read(TokenType.Literal).StringValue); }
private static CombinatorType ReadCombinatorType(TokensQueue queue) { var hasWhite = false; while (!queue.Empty) { queue.SkipComments(); var preview = queue.Peek(); switch (preview.Type) { case TokenType.Whitespace: queue.Read(); hasWhite = true; break; case TokenType.Plus: queue.Read(); return CombinatorType.Sibling; case TokenType.Greater: queue.Read(); return CombinatorType.Child; case TokenType.Comma: queue.Read(); return CombinatorType.Group; case TokenType.OpenCurlyBracket: case TokenType.CloseParenthesis: return CombinatorType.Stop; default: return hasWhite ? CombinatorType.Descendant : CombinatorType.Combine; } } return CombinatorType.Stop; }