github.com/influx6/npkg@v0.8.8/nlexing/compact_directives.go (about) 1 package nlexing 2 3 import ( 4 "strings" 5 "unicode" 6 7 "github.com/influx6/npkg/nerror" 8 ) 9 10 const ( 11 VariantToken TokenType = iota + 10 12 PrefixToken 13 GroupStartToken 14 GroupEndToken 15 TargetFinished 16 TargetToken 17 NegationToken 18 ) 19 20 const ( 21 dash = '-' 22 prefixer = '~' 23 underscore = '_' 24 colon = ':' 25 comma = ',' 26 apostrophe = '!' 27 leftBracketDelim = '(' 28 rightBracketDelim = ')' 29 ) 30 31 type intStack []int 32 33 func (th *intStack) Clear() { 34 *th = (*th)[:0] 35 } 36 37 func (th *intStack) ClearCount(n int) { 38 var count = len(*th) 39 if count == 0 { 40 return 41 } 42 *th = (*th)[0 : count-n] 43 } 44 45 func (th *intStack) Len() int { 46 return len(*th) 47 } 48 49 func (th *intStack) Pop() int { 50 var count = len(*th) 51 if count == 0 { 52 return -1 53 } 54 var last = (*th)[count-1] 55 *th = (*th)[0 : count-1] 56 return last 57 } 58 59 func (th *intStack) Push(t int) { 60 *th = append(*th, t) 61 } 62 63 type stack []string 64 65 func (th *stack) Join(with string) string { 66 return strings.Join(*th, with) 67 } 68 69 func (th *stack) Len() int { 70 return len(*th) 71 } 72 73 func (th *stack) Clear() { 74 *th = (*th)[:0] 75 } 76 77 func (th *stack) ClearCount(n int) { 78 var count = len(*th) 79 if count == 0 { 80 return 81 } 82 *th = (*th)[0 : count-n] 83 } 84 85 func (th *stack) Pop() string { 86 var count = len(*th) 87 if count == 0 { 88 return "" 89 } 90 var last = (*th)[count-1] 91 *th = (*th)[0 : count-1] 92 return last 93 } 94 95 func (th *stack) Push(t string) { 96 *th = append(*th, t) 97 } 98 99 // ParseVariantDirectives implements a parser which handles parsing 100 // tokens in the following formats: 101 // 1. Variant:Prefix-text-text* 102 // 2. Variant:(Prefix-text, Prefix2-text2) which expanded is Variant:Prefix-text and Variant:Prefix2-text2 103 // 3. Variant:(Variant2:Prefix-text, Prefix-text) which expanded is Variant:Variant2:Prefix-text and Variant:Prefix-text 104 // 4. Variant:(Variant2:Prefix-text, Variant3:Prefix-text) which expanded is Variant:Variant2:Prefix-text and Variant:Variant3:Prefix-text 105 // 5. Variant:(Variant2:(Prefix-text, Prefix2-text2), Variant3:Prefix-text3) which expanded is Variant:Variant2:Prefix-text, Variant:Variant2:Prefix2-text2 and Variant:Variant3:Prefix-text 106 // 6. Pf~(Prefix-text, Prefix2-text2) which expanded is Variant:Pf-Prefix-text and Variant:Pf-Prefix2-text2 107 // 108 func ParseVariantDirectives(v string) ([]string, error) { 109 var parsed []string 110 111 var cls stack 112 var gps intStack 113 114 var pls stack 115 var pgps intStack 116 117 var variantCount int 118 var prefixCount int 119 var groups int 120 121 var lexer = NewLexer(v) 122 var tokenizer = NewTokenizer(lexer, LexCompactDirective, func(b string, t TokenType) error { 123 switch t { 124 case TargetToken: 125 if len(b) == 0 { 126 break 127 } 128 129 gps.Push(variantCount) 130 variantCount = 0 131 132 pgps.Push(prefixCount) 133 prefixCount = 0 134 135 var prefixed = b 136 if pls.Len() > 0 { 137 prefixed = pls.Join("-") + "-" + prefixed 138 } 139 140 if cls.Len() > 0 { 141 prefixed = cls.Join(":") + ":" + prefixed 142 } 143 144 parsed = append(parsed, prefixed) 145 case TargetFinished: 146 if gps.Len() > 0 { 147 var grpVariantCount = gps.Pop() 148 cls.ClearCount(grpVariantCount) 149 } 150 if pgps.Len() > 0 { 151 var pgrpVariantCount = pgps.Pop() 152 pls.ClearCount(pgrpVariantCount) 153 } 154 case PrefixToken: 155 prefixCount++ 156 pls.Push(b) 157 case VariantToken: 158 variantCount++ 159 cls.Push(b) 160 case GroupStartToken: 161 groups++ 162 gps.Push(variantCount) 163 variantCount = 0 164 165 pgps.Push(prefixCount) 166 prefixCount = 0 167 case GroupEndToken: 168 groups-- 169 var grpVariantCount = gps.Pop() 170 cls.ClearCount(grpVariantCount) 171 172 var pgrpVariantCount = pgps.Pop() 173 pls.ClearCount(pgrpVariantCount) 174 } 175 return nil 176 }) 177 178 if err := tokenizer.Run(); err != nil { 179 return nil, nerror.WrapOnly(err) 180 } 181 182 if groups < 0 { 183 return parsed, nerror.New("seems grouping closer ')' is more than opener '('") 184 } 185 186 if groups > 0 { 187 return parsed, nerror.New("seems grouping opener '(' is more than closer ')'") 188 } 189 190 return parsed, nil 191 } 192 193 // LexCompactDirective implements a parser which handles parsing 194 // tokens in the following formats: 195 // 1. Variant:Prefix-text-text* 196 // 2. Variant:(Prefix-text, Prefix2-text2) which expanded is Variant:Prefix-text and Variant:Prefix2-text2 197 // 3. Variant:(Variant2:Prefix-text, Prefix-text) which expanded is Variant:Variant2:Prefix-text and Variant:Prefix-text 198 // 4. Variant:(Variant2:Prefix-text, Variant3:Prefix-text) which expanded is Variant:Variant2:Prefix-text and Variant:Variant3:Prefix-text 199 // 5. Variant:(Variant2:(Prefix-text, Prefix2-text2), Variant3:Prefix-text3) which expanded is Variant:Variant2:Prefix-text, Variant:Variant2:Prefix2-text2 and Variant:Variant3:Prefix-text 200 // 6. Pf~(Prefix-text, Prefix2-text2) which expanded is Variant:Pf-Prefix-text and Variant:Pf-Prefix2-text2 201 // 202 func LexCompactDirective(l *Lexer, result ResultFunc) (TokenFunc, error) { 203 if l.isAtEnd() { 204 return nil, nil 205 } 206 207 return lexVariant, nil 208 } 209 210 // This specifically searches for ([\w\d]+): matching tokens 211 // which then are sent as the variant token type 212 func lexVariant(l *Lexer, result ResultFunc) (TokenFunc, error) { 213 var pr rune 214 for { 215 if l.isAtEnd() { 216 return LexCompactDirective, nil 217 } 218 219 if lexSpaceUntil(l) { 220 l.ignore() 221 continue 222 } 223 224 var lexedText = lexTextUntil(l) 225 226 // if we lex text values, then check what is the next 227 // non text token 228 pr = l.peek() 229 switch pr { 230 case eof: 231 l.next() 232 if lexedText { 233 if err := result(l.slice(), TargetToken); err != nil { 234 return nil, nerror.WrapOnly(err) 235 } 236 } 237 return nil, nil 238 case comma: 239 if lexedText { 240 if err := result(l.slice(), TargetToken); err != nil { 241 return nil, nerror.WrapOnly(err) 242 } 243 } 244 if err := result("", TargetFinished); err != nil { 245 return nil, nerror.WrapOnly(err) 246 } 247 l.skipNext() 248 return lexVariant, nil 249 case prefixer: 250 if err := result(l.slice(), PrefixToken); err != nil { 251 return nil, nerror.WrapOnly(err) 252 } 253 l.skipNext() 254 return lexVariant, nil 255 case colon: 256 if err := result(l.slice(), VariantToken); err != nil { 257 return nil, nerror.WrapOnly(err) 258 } 259 l.skipNext() 260 return lexVariant, nil 261 case apostrophe: 262 if lexedText { 263 if err := result(l.slice(), TargetToken); err != nil { 264 return nil, nerror.WrapOnly(err) 265 } 266 } 267 268 if err := result("", NegationToken); err != nil { 269 return nil, nerror.WrapOnly(err) 270 } 271 l.skipNext() 272 return lexVariant, nil 273 case rightBracketDelim: 274 if lexedText { 275 if err := result(l.slice(), TargetToken); err != nil { 276 return nil, nerror.WrapOnly(err) 277 } 278 } 279 280 if err := result("", GroupEndToken); err != nil { 281 return nil, nerror.WrapOnly(err) 282 } 283 l.skipNext() 284 return lexVariant, nil 285 case leftBracketDelim: 286 if lexedText { 287 if err := result(l.slice(), TargetToken); err != nil { 288 return nil, nerror.WrapOnly(err) 289 } 290 } 291 292 if err := result("", GroupStartToken); err != nil { 293 return nil, nerror.WrapOnly(err) 294 } 295 l.skipNext() 296 return lexVariant, nil 297 default: 298 if !isAlphaNumeric(pr) { 299 return nil, nerror.New("undefined token %q found while lexing %q in %q", pr, l.slice(), l.input) 300 } 301 } 302 303 if err := result(l.slice(), TargetToken); err != nil { 304 return nil, nerror.WrapOnly(err) 305 } 306 } 307 } 308 309 // func lexGroupStart(l *Lexer, resultFunc ResultFunc) TokenFunc { 310 // 311 // return nil 312 // } 313 314 func isDash(r rune) bool { 315 return r == dash 316 } 317 318 func isUnderscore(r rune) bool { 319 return r == underscore 320 } 321 322 func isColon(r rune) bool { 323 return r == colon 324 } 325 326 func isLeftBracket(r rune) bool { 327 return r == leftBracketDelim 328 } 329 330 func isRightBracket(r rune) bool { 331 return r == rightBracketDelim 332 } 333 334 // lexTextUntil scans a run of alphaneumeric characters. 335 func lexTextUntil(l *Lexer) bool { 336 var found = false 337 var numSpaces int 338 for { 339 if l.isAtEnd() { 340 break 341 } 342 343 var r = l.peek() 344 if !isAlphaNumeric(r) { 345 break 346 } 347 l.next() 348 found = true 349 numSpaces++ 350 } 351 return found 352 } 353 354 // lexSpaceUntil scans a run of alphaneumeric characters. 355 func lexSpaceUntil(l *Lexer) bool { 356 var foundSpace = false 357 var r rune 358 for { 359 r = l.peek() 360 if !isSpace(r) { 361 break 362 } 363 foundSpace = true 364 l.next() 365 } 366 return foundSpace 367 } 368 369 // lexTextWith scans a run of alphanumeric characters. 370 func lexTextWith(fn TokenFunc) TokenFunc { 371 return func(l *Lexer, rs ResultFunc) (TokenFunc, error) { 372 var r rune 373 for { 374 r = l.peek() 375 if !isAlphaNumeric(r) { 376 break 377 } 378 l.next() 379 } 380 return fn, nil 381 } 382 } 383 384 // isSpace reports whether r is a space character. 385 func isSpace(r rune) bool { 386 return r == ' ' || r == '\t' 387 } 388 389 // isEndOfLine reports whether r is an end-of-line character. 390 func isEndOfLine(r rune) bool { 391 return r == '\r' || r == '\n' 392 } 393 394 // isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. 395 func isAlphaNumeric(r rune) bool { 396 return r == underscore || r == apostrophe || r == dash || unicode.IsLetter(r) || unicode.IsDigit(r) 397 } 398 399 // isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. 400 func isAlphaNumericAndDot(r rune) bool { 401 return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) || r == '.' 402 }