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  }