css - ast_parser.go
1 package css
2
3 import (
4 "fmt"
5 "slices"
6 "strings"
7
8 "vimagination.zapto.org/parser"
9 )
10
11 // Token represents a single parsed token with source positioning.
12 type Token struct {
13 parser.Token
14 Pos, Line, LinePos uint64
15 }
16
17 // Tokens is a collection of Token values.
18 type Tokens []Token
19
20 // Comments is a collection of Comment Tokens.
21 type Comments []*Token
22
23 type cssParser Tokens
24
25 func newCSSParser(t *parser.Tokeniser) (cssParser, error) {
26 var (
27 tokens cssParser
28 pos, line, linePos uint64
29 err error
30 )
31
32 for tk := range t.Iter {
33 typ := tk.Type
34
35 tokens = append(tokens, Token{
36 Token: parser.Token{
37 Type: typ,
38 Data: tk.Data,
39 },
40 Pos: pos,
41 Line: line,
42 LinePos: linePos,
43 })
44
45 switch typ {
46 case parser.TokenError:
47 err = Error{
48 Err: t.GetError(),
49 Parsing: "Tokens",
50 Token: tokens[len(tokens)-1],
51 }
52 case TokenWhitespace:
53 var (
54 lastLT int
55 lastChar rune
56 )
57
58 for n, c := range tk.Data {
59 if strings.ContainsRune(whitespace, c) {
60 lastLT = n + 1
61 linePos = 0
62
63 if lastChar != '\r' || c != '\n' {
64 line++
65 }
66 }
67
68 lastChar = c
69 }
70
71 linePos += uint64(len(tk.Data) - lastLT)
72 default:
73 linePos += uint64(len(tk.Data))
74 }
75
76 pos += uint64(len(tk.Data))
77 }
78
79 return tokens[0:0:len(tokens)], err
80 }
81
82 func (c cssParser) NewGoal() cssParser {
83 return c[len(c):]
84 }
85
86 func (c *cssParser) Score(k cssParser) {
87 *c = (*c)[:len(*c)+len(k)]
88 }
89
90 func (c *cssParser) next() *Token {
91 l := len(*c)
92 if l == cap(*c) {
93 return &(*c)[l-1]
94 }
95
96 *c = (*c)[:l+1]
97 tk := (*c)[l]
98
99 return &tk
100 }
101
102 func (c *cssParser) backup() {
103 *c = (*c)[:len(*c)-1]
104 }
105
106 func (c *cssParser) Peek() parser.Token {
107 tk := c.next().Token
108
109 c.backup()
110
111 return tk
112 }
113
114 func (c *cssParser) Accept(ts ...parser.TokenType) bool {
115 if slices.Contains(ts, c.next().Type) {
116 return true
117 }
118
119 c.backup()
120
121 return false
122 }
123
124 func (c *cssParser) AcceptRun(ts ...parser.TokenType) parser.TokenType {
125 Loop:
126 for {
127 tt := c.next().Type
128
129 for _, pt := range ts {
130 if pt == tt {
131 continue Loop
132 }
133 }
134
135 c.backup()
136
137 return tt
138 }
139 }
140
141 func (c *cssParser) Skip() {
142 c.next()
143 }
144
145 func (c *cssParser) Next() *Token {
146 return c.next()
147 }
148
149 var depths = [...][2]parser.Token{
150 {{Type: TokenOpenBracket, Data: "["}, {Type: TokenCloseBracket, Data: "]"}},
151 {{Type: TokenOpenParen, Data: "("}, {Type: TokenCloseParen, Data: ")"}},
152 {{Type: TokenOpenBrace, Data: "{"}, {Type: TokenCloseBrace, Data: "}"}},
153 }
154
155 func (c *cssParser) SkipDepth() bool {
156 var (
157 on = -1
158 depth = 1
159 )
160
161 for n, d := range depths {
162 if c.AcceptToken(d[0]) {
163 on = n
164
165 break
166 }
167 }
168
169 if on == -1 {
170 return false
171 }
172
173 for depth > 0 {
174 if c.AcceptToken(depths[on][0]) {
175 depth++
176 } else if c.AcceptToken(depths[on][1]) {
177 depth--
178 } else {
179 c.Skip()
180 }
181 }
182
183 return true
184 }
185
186 func (c *cssParser) AcceptToken(tk parser.Token) bool {
187 if c.next().Token == tk {
188 return true
189 }
190
191 c.backup()
192
193 return false
194 }
195
196 func (c *cssParser) ToTokens() Tokens {
197 return Tokens((*c)[:len(*c):len(*c)])
198 }
199
200 func (c *cssParser) AcceptRunWhitespace() parser.TokenType {
201 return c.AcceptRun(TokenWhitespace, TokenComment)
202 }
203
204 func (c *cssParser) AcceptRunWhitespaceNoNewLine() {
205 for {
206 d := c.NewGoal()
207
208 if d.Accept(TokenWhitespace) {
209 if l := d.GetLastToken().Data; l != "\n" && l != "\r\n" {
210 break
211 }
212 }
213
214 c.Skip()
215 }
216 }
217
218 func (c *cssParser) AcceptRunWhitespaceNoComment() parser.TokenType {
219 return c.AcceptRun(TokenWhitespace)
220 }
221
222 func (c *cssParser) AcceptRunWhitespaceComments() Comments {
223 var cs Comments
224
225 d := c.NewGoal()
226
227 Loop:
228 for {
229 switch d.AcceptRunWhitespaceNoComment() {
230 case TokenComment:
231 default:
232 break Loop
233 }
234
235 cs = append(cs, d.Next())
236
237 c.Score(d)
238
239 d = c.NewGoal()
240 }
241
242 return cs
243 }
244
245 func (c *cssParser) AcceptRunWhitespaceNoNewLineNoComment() parser.TokenType {
246 return c.AcceptRun(TokenWhitespace)
247 }
248
249 func (c *cssParser) AcceptRunWhitespaceNoNewlineComments() Comments {
250 var cs Comments
251
252 d := c.NewGoal()
253
254 Loop:
255 for {
256 switch d.AcceptRunWhitespaceNoNewLineNoComment() {
257 case TokenComment:
258 default:
259 break Loop
260 }
261
262 cs = append(cs, d.Next())
263
264 c.Score(d)
265
266 d = c.NewGoal()
267
268 d.AcceptRunWhitespaceNoNewLineNoComment()
269
270 if d.Accept(TokenWhitespace) {
271 if l := d.GetLastToken().Data; l != "\n" && l != "\r\n" {
272 break
273 }
274 }
275 }
276
277 return cs
278 }
279
280 func (c *cssParser) GetLastToken() *Token {
281 return &(*c)[len(*c)-1]
282 }
283
284 // Error is a parsing error with trace details.
285 type Error struct {
286 Err error
287 Parsing string
288 Token Token
289 }
290
291 // Error returns the error string.
292 func (e Error) Error() string {
293 return fmt.Sprintf("%s: error at position %d (%d:%d):\n%s", e.Parsing, e.Token.Pos+1, e.Token.Line+1, e.Token.LinePos+1, e.Err)
294 }
295
296 // Unwrap returns the wrapped error.
297 func (e Error) Unwrap() error {
298 return e.Err
299 }
300
301 func (c *cssParser) Error(parsingFunc string, err error) error {
302 tk := c.next()
303
304 c.backup()
305
306 return Error{
307 Err: err,
308 Parsing: parsingFunc,
309 Token: *tk,
310 }
311 }
312