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  }