Merge pull request #3 from alexsaveliev/master

Dealing with CSS comments
diff --git a/css/declaration.go b/css/declaration.go
index 2560c71..eeb2118 100644
--- a/css/declaration.go
+++ b/css/declaration.go
@@ -18,25 +18,43 @@
 
 // Returns string representation of the Declaration
 func (decl *Declaration) String() string {
-	return decl.StringWithImportant(true)
+	return decl.Str(false)
+}
+
+// Returns string representation of the Declaration
+func (decl *Declaration) Str(diff bool) string {
+	return decl.stringWithImportant(true, diff)
 }
 
 // StringWithImportant returns string representation with optional !important part
 func (decl *Declaration) StringWithImportant(option bool) string {
+	return decl.stringWithImportant(option, false)
+}
+
+// StringWithImportant returns string representation with optional !important part
+func (decl *Declaration) stringWithImportant(option bool, diff bool) string {
 	result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
 
 	if option && decl.Important {
 		result += " !important"
 	}
 
-	result += ";"
+	if diff {
+		result += fmt.Sprintf(" (%d, %d)", decl.Line, decl.Column)
+	} else {
+		result += ";"
+	}
 
 	return result
 }
 
 // Equal returns true if both Declarations are equals
 func (decl *Declaration) Equal(other *Declaration) bool {
-	return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
+	return decl.Property == other.Property &&
+		decl.Value == other.Value &&
+		decl.Important == other.Important &&
+		decl.Line == other.Line &&
+		decl.Column == other.Column
 }
 
 //
diff --git a/css/rule.go b/css/rule.go
index 44e3111..067c452 100644
--- a/css/rule.go
+++ b/css/rule.go
@@ -31,7 +31,15 @@
 }
 
 func (selector Selector) String() string {
-	return fmt.Sprintf("Selector: %s (%d, %d)", selector.Value, selector.Line, selector.Column)
+	return selector.str(false)
+}
+
+func (selector Selector) str(diff bool) string {
+	if diff {
+		return fmt.Sprintf("Selector: %s (%d, %d)", selector.Value, selector.Line, selector.Column)
+	} else {
+		return selector.Value
+	}
 }
 
 // Rule represents a parsed CSS rule
@@ -147,7 +155,7 @@
 	} else {
 		for i, sel := range rule.Selectors {
 			if sel != other.Selectors[i] {
-				result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
+				result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel.str(true), other.Selectors[i].str(true)))
 			}
 		}
 	}
@@ -157,7 +165,7 @@
 	} else {
 		for i, decl := range rule.Declarations {
 			if !decl.Equal(other.Declarations[i]) {
-				result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
+				result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.Str(true), other.Declarations[i].Str(true)))
 			}
 		}
 	}
@@ -168,7 +176,7 @@
 
 		for i, rule := range rule.Rules {
 			if !rule.Equal(other.Rules[i]) {
-				result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
+				result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.str(true), other.Rules[i].str(true)))
 			}
 		}
 	}
@@ -178,6 +186,11 @@
 
 // Returns the string representation of a rule
 func (rule *Rule) String() string {
+	return rule.str(false)
+}
+
+// Returns the string representation of a rule
+func (rule *Rule) str(diff bool) string {
 	result := ""
 
 	if rule.Kind == QualifiedRule {
@@ -206,11 +219,11 @@
 
 		if rule.EmbedsRules() {
 			for _, subRule := range rule.Rules {
-				result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
+				result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.str(diff))
 			}
 		} else {
 			for _, decl := range rule.Declarations {
-				result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
+				result += fmt.Sprintf("%s%s\n", rule.indent(), decl.Str(diff))
 			}
 		}
 
@@ -222,24 +235,12 @@
 
 // Returns identation spaces for declarations and rules
 func (rule *Rule) indent() string {
-	result := ""
-
-	for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
-		result += " "
-	}
-
-	return result
+	return strings.Repeat(" ", (rule.EmbedLevel+1)*indentSpace)
 }
 
 // Returns identation spaces for end of block character
 func (rule *Rule) indentEndBlock() string {
-	result := ""
-
-	for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
-		result += " "
-	}
-
-	return result
+	return strings.Repeat(" ", rule.EmbedLevel*indentSpace)
 }
 
 func (rule *Rule) Sel() []string {
diff --git a/parser/parser.go b/parser/parser.go
index 95a9bf3..a0132ea 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -127,7 +127,6 @@
 	if parser.tokenAtKeyword() {
 		return parser.parseAtRule()
 	}
-
 	return parser.parseQualifiedRule()
 }
 
@@ -195,11 +194,16 @@
 
 			// finished
 			break
-		} else {
+		} else if !parser.tokenIgnorable() {
 			token := parser.shiftToken()
 			curValue += token.Value
 			curColumn = token.Column
 			curLine = token.Line
+		} else {
+			token := parser.shiftToken()
+			if token.Type != scanner.TokenComment {
+				curValue += token.Value
+			}
 		}
 	}
 
@@ -281,6 +285,10 @@
 
 			// finished
 			break
+		} else if parser.tokenChar(";") {
+			parser.shiftToken()
+			// finished
+			break
 		} else {
 			// parse prelude
 			prelude, err, tokens := parser.parsePrelude()
@@ -310,7 +318,7 @@
 				selectorStart = true
 				selectorValue = ""
 			}
-		case tok.Type != scanner.TokenS:
+		case tok.Type != scanner.TokenS && tok.Type != scanner.TokenComment:
 			{
 				selectorValue += tok.Value
 				if selectorStart {
diff --git a/parser/parser_test.go b/parser/parser_test.go
index baed705..89d0ecc 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -11,11 +11,11 @@
 func MustParse(t *testing.T, txt string, nbRules int) *css.Stylesheet {
 	stylesheet, err := Parse(txt)
 	if err != nil {
-		t.Fatal("Failed to parse css", err, txt)
+		t.Fatal("Failed to parse CSS", err, txt)
 	}
 
 	if len(stylesheet.Rules) != nbRules {
-		t.Fatal("Failed to parse Qualified Rules", txt)
+		t.Fatal(fmt.Sprintf("Failed to parse CSS \"%s\", expected %d rules but got %d", txt, nbRules, len(stylesheet.Rules)))
 	}
 
 	return stylesheet
@@ -46,7 +46,7 @@
 		Kind:    css.QualifiedRule,
 		Prelude: "p > a",
 		Selectors: []*css.Selector{
-			&css.Selector{
+			{
 				Value:  "p > a",
 				Line:   2,
 				Column: 1,
@@ -56,10 +56,14 @@
 			{
 				Property: "color",
 				Value:    "blue",
+				Line:     3,
+				Column:   5,
 			},
 			{
 				Property: "text-decoration",
 				Value:    "underline",
+				Line:     4,
+				Column:   5,
 			},
 		},
 	}
@@ -89,7 +93,7 @@
 		Kind:    css.QualifiedRule,
 		Prelude: "p > a",
 		Selectors: []*css.Selector{
-			&css.Selector{
+			{
 				Value:  "p > a",
 				Line:   2,
 				Column: 1,
@@ -100,16 +104,22 @@
 				Property:  "color",
 				Value:     "blue",
 				Important: false,
+				Line:      3,
+				Column:    5,
 			},
 			{
 				Property:  "text-decoration",
 				Value:     "underline",
 				Important: true,
+				Line:      4,
+				Column:    5,
 			},
 			{
 				Property:  "font-weight",
 				Value:     "normal",
 				Important: true,
+				Line:      5,
+				Column:    5,
 			},
 		},
 	}
@@ -143,17 +153,17 @@
 		Kind:    css.QualifiedRule,
 		Prelude: "table, tr, td",
 		Selectors: []*css.Selector{
-			&css.Selector{
+			{
 				Value:  "table",
 				Line:   1,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  "tr",
 				Line:   1,
 				Column: 8,
 			},
-			&css.Selector{
+			{
 				Value:  "td",
 				Line:   1,
 				Column: 12,
@@ -163,6 +173,8 @@
 			{
 				Property: "padding",
 				Value:    "0",
+				Line:     2,
+				Column:   3,
 			},
 		},
 	}
@@ -173,33 +185,34 @@
   h1,   h2,
     h3`,
 		Selectors: []*css.Selector{
-			&css.Selector{
+			{
 				Value:  "body",
 				Line:   5,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  "h1",
 				Line:   6,
 				Column: 3,
 			},
-			&css.Selector{
+			{
 				Value:  "h2",
 				Line:   6,
 				Column: 9,
 			},
-			&css.Selector{
+			{
 				Value:  "h3",
 				Line:   7,
 				Column: 5,
 			},
 		},
 
-
 		Declarations: []*css.Declaration{
 			{
 				Property: "color",
 				Value:    "#fff",
+				Line:     8,
+				Column:   3,
 			},
 		},
 	}
@@ -253,14 +266,20 @@
 			{
 				Property: "system",
 				Value:    "symbolic",
+				Line:     2,
+				Column:   3,
 			},
 			{
 				Property: "symbols",
 				Value:    "'*' ⁑ † ‡",
+				Line:     3,
+				Column:   3,
 			},
 			{
 				Property: "suffix",
 				Value:    "''",
+				Line:     4,
+				Column:   3,
 			},
 		},
 	}
@@ -302,7 +321,7 @@
 				Kind:    css.QualifiedRule,
 				Prelude: "body",
 				Selectors: []*css.Selector{
-					&css.Selector{
+					{
 						Value:  "body",
 						Line:   14,
 						Column: 3,
@@ -312,10 +331,14 @@
 					{
 						Property: "color",
 						Value:    "purple",
+						Line:     14,
+						Column:   10,
 					},
 					{
 						Property: "background",
 						Value:    "yellow",
+						Line:     14,
+						Column:   25,
 					},
 				},
 			},
@@ -356,16 +379,22 @@
 			{
 				Property: "font-family",
 				Value:    "MyHelvetica",
+				Line:     2,
+				Column:   3,
 			},
 			{
 				Property: "src",
 				Value: `local("Helvetica Neue Bold"),
        local("HelveticaNeue-Bold"),
        url(MgOpenModernaBold.ttf)`,
+				Line:   3,
+				Column: 3,
 			},
 			{
 				Property: "font-weight",
 				Value:    "bold",
+				Line:     6,
+				Column:   3,
 			},
 		},
 	}
@@ -396,6 +425,8 @@
 					{
 						Property: "nice-style",
 						Value:    "4",
+						Line:     3,
+						Column:   5,
 					},
 				},
 			},
@@ -454,7 +485,7 @@
 				Kind:    css.QualifiedRule,
 				Prelude: "0%",
 				Selectors: []*css.Selector{
-					&css.Selector{
+					{
 						Value:  "0%",
 						Line:   2,
 						Column: 3,
@@ -464,10 +495,14 @@
 					{
 						Property: "top",
 						Value:    "0",
+						Line:     2,
+						Column:   8,
 					},
 					{
 						Property: "left",
 						Value:    "0",
+						Line:     2,
+						Column:   16,
 					},
 				},
 			},
@@ -475,7 +510,7 @@
 				Kind:    css.QualifiedRule,
 				Prelude: "100%",
 				Selectors: []*css.Selector{
-					&css.Selector{
+					{
 						Value:  "100%",
 						Line:   3,
 						Column: 3,
@@ -485,10 +520,14 @@
 					{
 						Property: "top",
 						Value:    "100px",
+						Line:     3,
+						Column:   10,
 					},
 					{
 						Property: "left",
 						Value:    "100%",
+						Line:     3,
+						Column:   22,
 					},
 				},
 			},
@@ -527,7 +566,7 @@
 				Kind:    css.QualifiedRule,
 				Prelude: "body",
 				Selectors: []*css.Selector{
-					&css.Selector{
+					{
 						Value:  "body",
 						Line:   2,
 						Column: 3,
@@ -537,6 +576,8 @@
 					{
 						Property: "line-height",
 						Value:    "1.2",
+						Line:     2,
+						Column:   10,
 					},
 				},
 			},
@@ -586,10 +627,14 @@
 			{
 				Property: "margin-left",
 				Value:    "4cm",
+				Line:     2,
+				Column:   3,
 			},
 			{
 				Property: "margin-right",
 				Value:    "3cm",
+				Line:     3,
+				Column:   3,
 			},
 		},
 	}
@@ -623,7 +668,7 @@
 						Kind:    css.QualifiedRule,
 						Prelude: "0%",
 						Selectors: []*css.Selector{
-							&css.Selector{
+							{
 								Value:  "0%",
 								Line:   4,
 								Column: 7,
@@ -633,10 +678,14 @@
 							{
 								Property: "top",
 								Value:    "0",
+								Line:     4,
+								Column:   12,
 							},
 							{
 								Property: "left",
 								Value:    "0",
+								Line:     4,
+								Column:   20,
 							},
 						},
 					},
@@ -644,7 +693,7 @@
 						Kind:    css.QualifiedRule,
 						Prelude: "100%",
 						Selectors: []*css.Selector{
-							&css.Selector{
+							{
 								Value:  "100%",
 								Line:   5,
 								Column: 7,
@@ -654,10 +703,14 @@
 							{
 								Property: "top",
 								Value:    "100px",
+								Line:     5,
+								Column:   14,
 							},
 							{
 								Property: "left",
 								Value:    "100%",
+								Line:     5,
+								Column:   26,
 							},
 						},
 					},
@@ -699,10 +752,14 @@
 		{
 			Property: "color",
 			Value:    "blue",
+			Line:     1,
+			Column:   1,
 		},
 		{
 			Property: "text-decoration",
 			Value:    "underline",
+			Line:     1,
+			Column:   14,
 		},
 	}
 
@@ -712,7 +769,7 @@
 
 	for i, decl := range declarations {
 		if !decl.Equal(expectedOutput[i]) {
-			t.Fatal("Failed to parse Declarations: ", decl.String(), expectedOutput[i].String())
+			t.Fatal("Failed to parse Declarations: ", decl.Str(true), expectedOutput[i].Str(true))
 		}
 	}
 }
@@ -726,7 +783,7 @@
 .btn.active.focus {
 }`
 	expectedRule := &css.Rule{
-		Kind:    css.QualifiedRule,
+		Kind: css.QualifiedRule,
 		Prelude: `.btn:focus,
 .btn:active:focus,
 .btn.active:focus,
@@ -734,32 +791,32 @@
 .btn:active.focus,
 .btn.active.focus`,
 		Selectors: []*css.Selector{
-			&css.Selector{
+			{
 				Value:  ".btn:focus",
 				Line:   1,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  ".btn:active:focus",
 				Line:   2,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  ".btn.active:focus",
 				Line:   3,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  ".btn.focus",
 				Line:   4,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  ".btn:active.focus",
 				Line:   5,
 				Column: 1,
 			},
-			&css.Selector{
+			{
 				Value:  ".btn.active.focus",
 				Line:   6,
 				Column: 1,
@@ -773,3 +830,53 @@
 
 	MustEqualRule(t, rule, expectedRule)
 }
+
+func TestComments(t *testing.T) {
+	input := "td /* © */ { color /* © */: red; }"
+	expectedRule := &css.Rule{
+		Kind:    css.QualifiedRule,
+		Prelude: "td /* © */",
+		Selectors: []*css.Selector{
+			{
+				Value:  "td",
+				Line:   1,
+				Column: 1,
+			},
+		},
+		Declarations: []*css.Declaration{
+			{
+				Property: "color",
+				Value:    "red",
+				Line:     1,
+				Column:   14,
+			},
+		},
+	}
+
+	stylesheet := MustParse(t, input, 1)
+	rule := stylesheet.Rules[0]
+
+	MustEqualRule(t, rule, expectedRule)
+}
+
+func TestInfiniteLoop(t *testing.T) {
+	input := "{;}"
+
+	expectedRule := &css.Rule{
+		Kind:    css.QualifiedRule,
+		Prelude: "",
+		Selectors: []*css.Selector{
+			{
+				Value:  "",
+				Line:   0,
+				Column: 0,
+			},
+		},
+		Declarations: []*css.Declaration{},
+	}
+
+	stylesheet := MustParse(t, input, 1)
+	rule := stylesheet.Rules[0]
+
+	MustEqualRule(t, rule, expectedRule)
+}