1package css
2
3import (
4 "strings"
5)
6
7type Diagnostic struct {
8 Line, Col int
9 Message string
10}
11type Declaration struct {
12 Property, Value string
13 Important bool
14 Line, Col int
15}
16type Rule struct {
17 Selectors []Selector
18 Declarations []Declaration
19}
20type Sheet struct {
21 Rules []Rule
22 Diagnostics []Diagnostic
23}
24
25func Parse(src string) Sheet {
26 ts := Tokenize(src)
27 sheet := Sheet{}
28 diag := func(t Token, msg string) {
29 sheet.Diagnostics = append(sheet.Diagnostics, Diagnostic{t.Line, t.Col, msg})
30 }
31 for i := 0; i < len(ts); {
32 for i < len(ts) && ts[i].Kind == "space" {
33 i++
34 }
35 if i >= len(ts) {
36 break
37 }
38 start := i
39 for i < len(ts) && ts[i].Kind != "{" && ts[i].Kind != "}" && ts[i].Kind != ";" {
40 i++
41 }
42 if i >= len(ts) {
43 diag(ts[start], "malformed rule")
44 break
45 }
46 if ts[i].Kind != "{" {
47 diag(ts[start], "malformed rule")
48 i++
49 continue
50 }
51 sels, ok := parseSelectors(ts[start:i])
52 i++
53 depth := 1
54 body := i
55 for i < len(ts) && depth > 0 {
56 switch ts[i].Kind {
57 case "{":
58 depth++
59 case "}":
60 depth--
61 }
62 i++
63 }
64 if depth > 0 {
65 diag(ts[start], "unclosed rule")
66 break
67 }
68 if !ok {
69 diag(ts[start], "invalid selector")
70 continue
71 }
72 r := Rule{Selectors: sels}
73 contents := ts[body : i-1]
74 for j := 0; j < len(contents); {
75 for j < len(contents) && (contents[j].Kind == "space" || contents[j].Kind == ";") {
76 j++
77 }
78 if j >= len(contents) {
79 break
80 }
81 b := j
82 // Consume component values: delimiters inside functions or blocks are not
83 // declaration terminators. A stray block is an invalid declaration.
84 depthParen, depthBracket, depthBrace := 0, 0, 0
85 malformedBlock := false
86 for j < len(contents) {
87 switch contents[j].Kind {
88 case "function", "(":
89 depthParen++
90 case ")":
91 if depthParen > 0 {
92 depthParen--
93 }
94 case "[":
95 depthBracket++
96 case "]":
97 if depthBracket > 0 {
98 depthBracket--
99 }
100 case "{":
101 depthBrace++
102 malformedBlock = true
103 case "}":
104 if depthBrace > 0 {
105 depthBrace--
106 }
107 }
108 if contents[j].Kind == ";" && depthParen == 0 && depthBracket == 0 && depthBrace == 0 {
109 break
110 }
111 j++
112 }
113 end := j
114 if malformedBlock {
115 diag(contents[b], "malformed declaration")
116 if j < len(contents) {
117 j++
118 }
119 continue
120 }
121 if j < len(contents) {
122 j++
123 }
124 colon := -1
125 for k := b; k < end; k++ {
126 if contents[k].Kind == ":" {
127 colon = k
128 break
129 }
130 }
131 if colon < 0 {
132 diag(contents[b], "malformed declaration")
133 continue
134 }
135 name := strings.TrimSpace(raw(contents[b:colon]))
136 value := strings.TrimSpace(raw(contents[colon+1 : end]))
137 if name == "" || value == "" && !strings.HasPrefix(name, "--") {
138 diag(contents[b], "malformed declaration")
139 continue
140 }
141 important := false
142 v := Tokenize(value)
143 e := len(v) - 1
144 for e >= 0 && v[e].Kind == "space" {
145 e--
146 }
147 if e >= 0 && strings.EqualFold(v[e].Value, "important") {
148 k := e - 1
149 for k >= 0 && v[k].Kind == "space" {
150 k--
151 }
152 if k >= 0 && v[k].Kind == "!" {
153 important = true
154 value = strings.TrimSpace(raw(v[:k]))
155 }
156 }
157 if !strings.HasPrefix(name, "--") {
158 name = strings.ToLower(name)
159 }
160 d := Declaration{name, value, important, contents[b].Line, contents[b].Col}
161 if len(compileDecls([]Declaration{d})) == 0 {
162 msg := "invalid value for " + name
163 if !strings.HasPrefix(name, "--") && len(shorthandTargets(name)) == 0 {
164 if _, known := propertyIndex(name); !known {
165 msg = "unknown property: " + name
166 }
167 }
168 diag(contents[b], msg)
169 }
170 r.Declarations = append(r.Declarations, d)
171 }
172 sheet.Rules = append(sheet.Rules, r)
173 }
174 return sheet
175}
176func raw(ts []Token) string {
177 var b strings.Builder
178 for _, t := range ts {
179 b.WriteString(t.Raw)
180 }
181 return b.String()
182}