github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/parser/parser.go (about) 1 package parser 2 3 //go:generate stringer -type=PipeToken 4 5 import ( 6 "regexp" 7 8 "github.com/lmorg/murex/utils/ansi/codes" 9 ) 10 11 // syntax highlighting 12 var ( 13 hlFunction = codes.Bold 14 hlVariable = codes.FgGreen 15 hlEscaped = codes.FgYellow 16 hlSingleQuote = codes.FgBlue 17 hlDoubleQuote = codes.FgBlue 18 hlBraceQuote = codes.FgBlue 19 hlBlock = []string{codes.FgGreen, codes.FgMagenta, codes.FgBlue, codes.FgYellow} 20 hlPipe = codes.FgMagenta 21 hlComment = codes.FgGreen + codes.Invert 22 hlError = codes.FgRed + codes.Invert 23 hlRedirect = codes.FgGreen 24 25 rxAllowedVarChars = regexp.MustCompile(`^[._a-zA-Z0-9]$`) 26 ) 27 28 // ParsedTokens is a struct that returns a tokenized version of the selected command 29 type ParsedTokens struct { 30 Source []rune 31 LastCharacter rune 32 Loc int 33 VarLoc int 34 VarBrace bool 35 VarSigil string 36 Escaped bool 37 Comment bool 38 CommentMsg string 39 commentMsg []rune 40 QuoteSingle bool 41 QuoteDouble bool 42 QuoteBrace int 43 NestedBlock int 44 SquareBracket bool 45 AngledBracket bool 46 ExpectFunc bool 47 ExpectParam bool 48 pop *string 49 LastFuncName string 50 FuncName string 51 Parameters []string 52 Unsafe bool // if the pipeline is estimated to be safe enough to dynamically preview 53 LastFlowToken int 54 PipeToken PipeToken 55 } 56 57 // PipeToken stores an integer value for the pipe token used in a pipeline 58 type PipeToken int 59 60 // These are different pipe tokens 61 const ( 62 PipeTokenNone PipeToken = 0 // No pipe token 63 PipeTokenPosix PipeToken = iota // `|` (POSIX style pipe) 64 PipeTokenArrow // `->` (murex style pipe) 65 PipeTokenGeneric // `=>` (reformat to generic) 66 PipeTokenRedirect // `?` (STDERR redirected to STDOUT and vice versa) 67 PipeTokenAppend // `>>` (append STDOUT to a file) 68 ) 69 70 // Parse a single line of code and return the tokens for a selected command 71 func Parse(block []rune, pos int) (pt ParsedTokens, syntaxHighlighted string) { 72 var readFunc bool 73 reset := []string{codes.Reset, hlFunction} 74 syntaxHighlighted = hlFunction 75 pt.Loc = -1 76 pt.ExpectFunc = true 77 pt.pop = &pt.FuncName 78 pt.Source = block 79 pt.Parameters = []string{} 80 81 ansiColour := func(colour string, r rune) { 82 syntaxHighlighted += colour + string(r) 83 reset = append(reset, colour) 84 } 85 86 ansiReset := func(r rune) { 87 if len(reset) > 1 { 88 reset = reset[:len(reset)-1] 89 } 90 syntaxHighlighted += string(r) + reset[len(reset)-1] 91 } 92 93 ansiResetNoChar := func() { 94 if len(reset) > 1 { 95 reset = reset[:len(reset)-1] 96 } 97 syntaxHighlighted += reset[len(reset)-1] 98 } 99 100 ansiChar := func(colour string, r ...rune) { 101 syntaxHighlighted += colour + string(r) + reset[len(reset)-1] 102 } 103 104 ansiStartFunction := func() { 105 ansiResetNoChar() 106 syntaxHighlighted += hlFunction 107 } 108 109 var i int 110 111 expectParam := func() { 112 pt.ExpectParam = false 113 pt.Parameters = append(pt.Parameters, "") 114 pt.pop = &pt.Parameters[len(pt.Parameters)-1] 115 } 116 117 escaped := func() { 118 pt.Escaped = false 119 *pt.pop += string(block[i]) 120 ansiReset(block[i]) 121 } 122 123 next := func(r rune) bool { 124 if i+1 < len(block) { 125 return block[i+1] == r 126 } 127 return false 128 } 129 130 for ; i < len(block); i++ { 131 if pt.Comment { 132 pt.commentMsg = append(pt.commentMsg, block[i]) 133 continue 134 } 135 136 if !pt.Escaped { 137 pt.LastCharacter = block[i] 138 } 139 140 if pt.VarSigil != "" { 141 if !pt.VarBrace { 142 if !rxAllowedVarChars.MatchString(string(block[i])) { 143 pt.VarSigil = "" 144 ansiResetNoChar() 145 } 146 } else { 147 *pt.pop += string(block[i]) 148 syntaxHighlighted += string(block[i]) 149 if block[i] == ')' { 150 pt.VarSigil = "" 151 ansiResetNoChar() 152 } 153 continue 154 } 155 } 156 157 switch block[i] { 158 case '#': 159 pt.Loc = i 160 switch { 161 case pt.Escaped: 162 escaped() 163 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0, pt.NestedBlock > 0: 164 *pt.pop += `#` 165 syntaxHighlighted += string(block[i]) 166 case pt.ExpectParam: 167 fallthrough 168 default: 169 pt.Comment = true 170 syntaxHighlighted += hlComment + string(block[i:]) + codes.Reset 171 //return 172 defer func() { pt.CommentMsg = string(pt.commentMsg) }() 173 } 174 175 case '\\': 176 switch { 177 case pt.QuoteSingle, pt.QuoteBrace > 0: 178 *pt.pop += `\` 179 syntaxHighlighted += string(block[i]) 180 case pt.Escaped: 181 escaped() 182 case pt.ExpectParam: 183 expectParam() 184 fallthrough 185 default: 186 pt.Escaped = true 187 ansiColour(hlEscaped, block[i]) 188 } 189 190 case '\'': 191 pt.Loc = i 192 switch { 193 case pt.Escaped: 194 escaped() 195 case pt.QuoteDouble, pt.QuoteBrace > 0: 196 *pt.pop += `'` 197 syntaxHighlighted += string(block[i]) 198 case pt.QuoteSingle: 199 pt.QuoteSingle = false 200 ansiReset(block[i]) 201 case pt.ExpectParam: 202 expectParam() 203 fallthrough 204 default: 205 pt.QuoteSingle = true 206 ansiColour(hlSingleQuote, block[i]) 207 } 208 209 case '"': 210 pt.Loc = i 211 switch { 212 case pt.Escaped: 213 escaped() 214 case pt.QuoteSingle, pt.QuoteBrace > 0: 215 *pt.pop += `"` 216 syntaxHighlighted += string(block[i]) 217 case pt.QuoteDouble: 218 pt.QuoteDouble = false 219 ansiReset(block[i]) 220 case pt.ExpectParam: 221 expectParam() 222 fallthrough 223 default: 224 pt.QuoteDouble = true 225 ansiColour(hlDoubleQuote, block[i]) 226 } 227 228 case '(': 229 pt.Loc = i 230 switch { 231 case pt.Escaped: 232 escaped() 233 case pt.QuoteSingle, pt.QuoteDouble: 234 *pt.pop += `(` 235 syntaxHighlighted += string(block[i]) 236 case pt.ExpectFunc: 237 pt.ExpectFunc = false 238 ansiColour(hlBraceQuote, block[i]) 239 pt.FuncName = "(" 240 pt.Parameters = append(pt.Parameters, "") 241 pt.pop = &pt.Parameters[0] 242 pt.QuoteBrace++ 243 case pt.QuoteBrace == 0: 244 ansiColour(hlBraceQuote, block[i]) 245 pt.QuoteBrace++ 246 if pt.ExpectParam { 247 expectParam() 248 } 249 case pt.ExpectParam: 250 expectParam() 251 fallthrough 252 default: 253 *pt.pop += `(` 254 syntaxHighlighted += string(block[i]) 255 pt.QuoteBrace++ 256 } 257 258 case ')': 259 pt.Loc = i 260 switch { 261 case pt.Escaped: 262 escaped() 263 case pt.QuoteSingle, pt.QuoteDouble: 264 *pt.pop += `)` 265 syntaxHighlighted += string(block[i]) 266 case pt.QuoteBrace == 1: 267 ansiReset(block[i]) 268 pt.QuoteBrace-- 269 case pt.QuoteBrace == 0: 270 ansiColour(hlError, block[i]) 271 pt.QuoteBrace-- 272 case pt.ExpectParam: 273 expectParam() 274 fallthrough 275 default: 276 *pt.pop += `)` 277 syntaxHighlighted += string(block[i]) 278 pt.QuoteBrace-- 279 } 280 281 case ' ': 282 switch { 283 case pt.Escaped: 284 escaped() 285 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 286 *pt.pop += ` ` 287 syntaxHighlighted += string(block[i]) 288 case readFunc: 289 pt.Loc = i 290 pt.ExpectFunc = false 291 readFunc = false 292 pt.ExpectParam = true 293 pt.Unsafe = isCmdUnsafe(pt.FuncName) || pt.Unsafe 294 ansiReset(block[i]) 295 case pt.ExpectFunc: 296 pt.Loc = i 297 syntaxHighlighted += string(block[i]) 298 case i > 0 && block[i-1] == ' ': 299 pt.Loc = i 300 syntaxHighlighted += " " 301 case i > 0 && block[i-1] == ':' && len(pt.Parameters) == 1: 302 pt.Loc = i 303 syntaxHighlighted += " " 304 default: 305 pt.Loc = i 306 syntaxHighlighted += string(block[i]) 307 pt.ExpectParam = true 308 } 309 310 case '=': 311 switch { 312 case pt.Escaped: 313 escaped() 314 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0, readFunc: 315 *pt.pop += `=` 316 syntaxHighlighted += string(block[i]) 317 case pt.ExpectFunc: 318 pt.Loc = i 319 syntaxHighlighted += string(block[i]) 320 default: 321 pt.Loc = i 322 syntaxHighlighted += string(block[i]) 323 pt.ExpectParam = true 324 } 325 326 case ':': 327 switch { 328 case pt.Escaped: 329 escaped() 330 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0, pt.SquareBracket: 331 *pt.pop += `:` 332 syntaxHighlighted += string(block[i]) 333 case !pt.ExpectFunc: 334 *pt.pop += `:` 335 syntaxHighlighted += string(block[i]) 336 case readFunc: 337 pt.Loc = i 338 pt.ExpectFunc = false 339 readFunc = false 340 pt.Parameters = append(pt.Parameters, "") 341 pt.pop = &pt.Parameters[0] 342 pt.Unsafe = isCmdUnsafe(pt.FuncName) || pt.Unsafe 343 ansiReset(block[i]) 344 default: 345 syntaxHighlighted += string(block[i]) 346 } 347 348 case '>': 349 switch { 350 case pt.Escaped: 351 escaped() 352 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 353 *pt.pop += ` ` 354 syntaxHighlighted += string(block[i]) 355 case i > 0 && (block[i-1] == '-' || block[i-1] == '='): 356 if pos != 0 && pt.Loc >= pos { 357 return 358 } 359 pt.Loc = i 360 pt.LastFlowToken = i - 1 361 pt.ExpectFunc = true 362 pt.SquareBracket = false 363 if block[i-1] == '-' { 364 pt.PipeToken = PipeTokenArrow 365 } else { 366 pt.PipeToken = PipeTokenGeneric 367 } 368 pt.pop = &pt.FuncName 369 pt.LastFuncName = pt.FuncName 370 pt.Parameters = make([]string, 0) 371 syntaxHighlighted = syntaxHighlighted[:len(syntaxHighlighted)-1] 372 ansiColour(hlPipe, block[i-1]) 373 ansiReset('>') 374 syntaxHighlighted += hlFunction 375 case i > 0 && (block[i-1] == '\t' || block[i-1] == ' ') && next('>'): 376 if pos != 0 && pt.Loc >= pos { 377 return 378 } 379 i++ 380 pt.Loc = i 381 pt.LastFlowToken = i - 1 382 pt.Unsafe = true 383 pt.ExpectFunc = false 384 readFunc = false 385 pt.ExpectParam = true 386 pt.SquareBracket = false 387 pt.PipeToken = PipeTokenAppend 388 pt.FuncName = ">>" 389 pt.Parameters = make([]string, 0) 390 ansiColour(hlPipe, '>') 391 ansiReset('>') 392 syntaxHighlighted += hlRedirect 393 case pt.ExpectFunc, readFunc: 394 readFunc = true 395 *pt.pop += `>` 396 pt.Loc = i 397 syntaxHighlighted += ">" 398 case pt.AngledBracket: 399 *pt.pop += `>` 400 pt.Loc = i 401 syntaxHighlighted += ">" + codes.Reset 402 403 default: 404 pt.Loc = i 405 syntaxHighlighted += ">" 406 } 407 408 case '|': 409 pt.Loc = i 410 switch { 411 case pt.Escaped: 412 escaped() 413 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 414 *pt.pop += string(block[i]) 415 syntaxHighlighted += string(block[i]) 416 default: 417 if pos != 0 && pt.Loc >= pos { 418 return 419 } 420 pt.LastFlowToken = i 421 pt.ExpectFunc = true 422 pt.SquareBracket = false 423 pt.PipeToken = PipeTokenPosix 424 pt.pop = &pt.FuncName 425 pt.LastFuncName = pt.FuncName 426 pt.Parameters = make([]string, 0) 427 if next('>') { 428 *pt.pop += `>` 429 pt.LastFlowToken = i - 1 430 pt.Unsafe = true 431 pt.ExpectFunc = false 432 readFunc = false 433 pt.ExpectParam = true 434 pt.SquareBracket = false 435 i++ 436 if next('>') { 437 pt.Loc = i 438 i++ 439 ansiChar(hlPipe, '|', '>', '>') 440 pt.PipeToken = PipeTokenAppend 441 } else { 442 ansiChar(hlPipe, '|', '>') 443 } 444 445 syntaxHighlighted += hlRedirect 446 } else { 447 ansiChar(hlPipe, block[i]) 448 ansiStartFunction() 449 } 450 } 451 452 case '&': 453 pt.Loc = i 454 switch { 455 case pt.Escaped: 456 escaped() 457 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 458 *pt.pop += string(block[i]) 459 syntaxHighlighted += string(block[i]) 460 case next('&'): 461 if pos != 0 && pt.Loc >= pos { 462 return 463 } 464 pt.LastFlowToken = i 465 pt.ExpectFunc = true 466 pt.SquareBracket = false 467 pt.PipeToken = PipeTokenNone 468 pt.pop = &pt.FuncName 469 pt.LastFuncName = pt.FuncName 470 pt.Parameters = make([]string, 0) 471 ansiChar(hlPipe, '&', '&') 472 ansiStartFunction() 473 i++ 474 default: 475 *pt.pop += string(block[i]) 476 syntaxHighlighted += string(block[i]) 477 } 478 479 case ';': 480 pt.Loc = i 481 switch { 482 case pt.Escaped: 483 escaped() 484 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 485 *pt.pop += string(block[i]) 486 syntaxHighlighted += string(block[i]) 487 default: 488 if pos != 0 && pt.Loc >= pos { 489 return 490 } 491 pt.LastFlowToken = i 492 pt.ExpectFunc = true 493 pt.SquareBracket = false 494 pt.PipeToken = PipeTokenNone 495 pt.pop = &pt.FuncName 496 pt.LastFuncName = pt.FuncName 497 pt.Parameters = make([]string, 0) 498 ansiChar(hlPipe, block[i]) 499 ansiStartFunction() 500 } 501 502 case '\n': 503 pt.Loc = i 504 switch { 505 case pt.Escaped: 506 escaped() 507 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 508 *pt.pop += string(block[i]) 509 syntaxHighlighted += string(block[i]) 510 default: 511 if pos != 0 && pt.Loc >= pos { 512 return 513 } 514 pt.LastFlowToken = i 515 pt.Unsafe = true 516 pt.ExpectFunc = true 517 pt.SquareBracket = false 518 pt.PipeToken = PipeTokenNone 519 pt.pop = &pt.FuncName 520 pt.LastFuncName = pt.FuncName 521 pt.Parameters = make([]string, 0) 522 ansiChar(hlPipe, block[i]) 523 ansiStartFunction() 524 } 525 526 case '?': 527 pt.Loc = i 528 switch { 529 case pt.Escaped: 530 escaped() 531 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 532 *pt.pop += "?" 533 syntaxHighlighted += string(block[i]) 534 case next(':'), next('?'): 535 if pos != 0 && pt.Loc >= pos { 536 return 537 } 538 pt.LastFlowToken = i 539 pt.ExpectFunc = true 540 pt.SquareBracket = false 541 pt.PipeToken = PipeTokenNone 542 pt.pop = &pt.FuncName 543 pt.LastFuncName = pt.FuncName 544 pt.Parameters = make([]string, 0) 545 ansiChar(hlPipe, block[i:i+2]...) 546 ansiStartFunction() 547 i++ 548 case i > 0 && block[i-1] == ' ': 549 if pos != 0 && pt.Loc >= pos { 550 return 551 } 552 pt.LastFlowToken = i 553 pt.ExpectFunc = true 554 pt.SquareBracket = false 555 pt.PipeToken = PipeTokenRedirect 556 pt.pop = &pt.FuncName 557 pt.LastFuncName = pt.FuncName 558 pt.Parameters = make([]string, 0) 559 pt.Unsafe = true 560 ansiChar(hlPipe, block[i]) 561 syntaxHighlighted += hlFunction 562 default: 563 *pt.pop += `?` 564 syntaxHighlighted += "?" 565 } 566 567 case '{': 568 pt.Loc = i 569 switch { 570 case pt.Escaped: 571 escaped() 572 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 573 *pt.pop += `{` 574 syntaxHighlighted += string(block[i]) 575 default: 576 pt.NestedBlock++ 577 pt.ExpectFunc = true 578 pt.PipeToken = PipeTokenNone 579 pt.pop = &pt.FuncName 580 pt.Parameters = make([]string, 0) 581 if pt.NestedBlock >= 0 { 582 i := pt.NestedBlock % len(hlBlock) 583 syntaxHighlighted += hlBlock[i] + "{" + codes.Reset + hlFunction 584 } else { 585 syntaxHighlighted += hlError + "{" 586 } 587 } 588 589 case '}': 590 switch { 591 case pt.Escaped: 592 escaped() 593 case pt.QuoteSingle, pt.QuoteDouble, pt.QuoteBrace > 0: 594 *pt.pop += `}` 595 syntaxHighlighted += "}" 596 default: 597 if pt.NestedBlock >= 1 { 598 i := pt.NestedBlock % len(hlBlock) 599 syntaxHighlighted += hlBlock[i] + "}" + codes.Reset 600 } else { 601 syntaxHighlighted += hlError + "}" 602 } 603 pt.NestedBlock-- 604 if pt.NestedBlock == 0 { 605 syntaxHighlighted += reset[len(reset)-1] 606 } 607 } 608 609 case '[': 610 switch { 611 case pt.Escaped: 612 escaped() 613 case readFunc: 614 *pt.pop += string(block[i]) 615 syntaxHighlighted += string(block[i]) 616 pt.SquareBracket = true 617 case pt.ExpectFunc: 618 *pt.pop = string(block[i]) 619 readFunc = true 620 syntaxHighlighted += string(block[i]) 621 pt.SquareBracket = true 622 default: 623 *pt.pop += string(block[i]) 624 syntaxHighlighted += string(block[i]) 625 pt.SquareBracket = true 626 } 627 628 case ']': 629 switch { 630 case pt.Escaped: 631 escaped() 632 case readFunc: 633 *pt.pop += string(block[i]) 634 syntaxHighlighted += string(block[i]) 635 case pt.ExpectFunc: 636 *pt.pop = string(block[i]) 637 readFunc = true 638 syntaxHighlighted += string(block[i]) 639 default: 640 *pt.pop += string(block[i]) 641 syntaxHighlighted += string(block[i]) 642 pt.SquareBracket = true 643 } 644 645 case '$': 646 switch { 647 case pt.Escaped: 648 escaped() 649 case pt.QuoteSingle: 650 *pt.pop += string(block[i]) 651 syntaxHighlighted += string(block[i]) 652 case pt.ExpectParam: 653 expectParam() 654 fallthrough 655 default: 656 pt.Unsafe = true 657 *pt.pop += string(block[i]) 658 pt.VarSigil = string(block[i]) 659 ansiColour(hlVariable, block[i]) 660 if next('(') { 661 pt.VarLoc = i + 1 662 pt.VarBrace = true 663 } else { 664 pt.VarLoc = i 665 pt.VarBrace = false 666 } 667 } 668 669 case '@': 670 switch { 671 case pt.Escaped: 672 escaped() 673 case pt.QuoteSingle, next(' '), next('\t'): 674 *pt.pop += string(block[i]) 675 syntaxHighlighted += string(block[i]) 676 case pt.ExpectParam: 677 expectParam() 678 fallthrough 679 default: 680 pt.Unsafe = true 681 *pt.pop += string(block[i]) 682 pt.VarSigil = string(block[i]) 683 ansiColour(hlVariable, block[i]) 684 if next('(') { 685 pt.VarLoc = i + 1 686 pt.VarBrace = true 687 } else { 688 pt.VarLoc = i 689 pt.VarBrace = false 690 } 691 } 692 693 case '<': 694 switch { 695 case pt.Escaped: 696 escaped() 697 case readFunc: 698 *pt.pop += "<" 699 syntaxHighlighted += "<" 700 case pt.ExpectFunc: 701 *pt.pop = "<" 702 readFunc = true 703 syntaxHighlighted += "<" 704 default: 705 pt.Unsafe = true 706 *pt.pop += "<" 707 syntaxHighlighted += hlRedirect + "<" 708 pt.AngledBracket = true 709 } 710 711 default: 712 switch { 713 case pt.Escaped: 714 pt.Escaped = false 715 ansiReset(block[i]) 716 switch block[i] { 717 case 'r': 718 *pt.pop = "\r" 719 case 'n': 720 *pt.pop = "\n" 721 case 's': 722 *pt.pop = " " 723 case 't': 724 *pt.pop = "\t" 725 default: 726 *pt.pop = string(block[i]) 727 } 728 case readFunc: 729 *pt.pop += string(block[i]) 730 syntaxHighlighted += string(block[i]) 731 case pt.ExpectFunc: 732 *pt.pop = string(block[i]) 733 readFunc = true 734 syntaxHighlighted += string(block[i]) 735 case pt.ExpectParam: 736 expectParam() 737 fallthrough 738 default: 739 *pt.pop += string(block[i]) 740 syntaxHighlighted += string(block[i]) 741 } 742 } 743 } 744 pt.Loc++ 745 pt.VarLoc++ 746 syntaxHighlighted += codes.Reset 747 return 748 }