github.com/holoplot/go-evdev@v0.0.0-20220721205823-d31c64b9d636/build/gen-codes/parser/process.go (about)

     1  package parser
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"io"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  )
    11  
    12  type EncodingType string
    13  
    14  const (
    15  	EncodingUnknown  EncodingType = ""
    16  	EncodingHex      EncodingType = "hex"
    17  	EncodingInteger  EncodingType = "int"
    18  	EncodingText     EncodingType = "text"     // e.g. "SOME_OTHER_CONST_NAME"
    19  	EncodingEquation EncodingType = "equation" // e.g. "(CONST_NAME + 1)"
    20  )
    21  
    22  const (
    23  	stateNeutral = iota
    24  	stateReadingMultilineComment
    25  	stateReadingDefineWithMultilineComment
    26  )
    27  
    28  var (
    29  	regexHex      = regexp.MustCompile(`^0x[0-9a-fA-F]+$`)
    30  	regexInteger  = regexp.MustCompile(`^[0-9]+$`)
    31  	regexText     = regexp.MustCompile(`^\w+$`)
    32  	regexEquation = regexp.MustCompile(`^\((\w+)\s*(\+)\s*(\d)+\)$`)
    33  )
    34  
    35  func detectEncodingType(value string) (EncodingType, int) {
    36  	var convertedValue int64
    37  	var err error
    38  
    39  	switch {
    40  	case regexHex.MatchString(value):
    41  		convertedValue, err = strconv.ParseInt(value[2:], 16, 64)
    42  		if err != nil {
    43  			panic(err)
    44  		}
    45  		return EncodingHex, int(convertedValue)
    46  	case regexInteger.MatchString(value):
    47  		convertedValue, err = strconv.ParseInt(value, 10, 64)
    48  		if err != nil {
    49  			panic(err)
    50  		}
    51  		return EncodingInteger, int(convertedValue)
    52  	case regexEquation.MatchString(value):
    53  		return EncodingEquation, 0 // can't resolve value that points on different constant here
    54  	case regexText.MatchString(value):
    55  		return EncodingText, 0 // can't resolve value that points on different constant here
    56  	default:
    57  		return EncodingUnknown, 0
    58  	}
    59  }
    60  
    61  type separator string // one line
    62  
    63  type comment string // may be bigger than one line
    64  
    65  type constant struct {
    66  	name         string
    67  	value        string
    68  	decodedValue int
    69  	comment      string // oneliner
    70  	encodingType EncodingType
    71  }
    72  
    73  func (c *constant) resolveEquation(group *Group) (EncodingType, int, error) {
    74  	out := regexEquation.FindStringSubmatch(c.value)
    75  	rawA, sign, rawB := out[1], out[2], out[3]
    76  	if sign != "+" {
    77  		return EncodingUnknown, 0, errors.New("e1")
    78  	}
    79  	i, err := strconv.ParseInt(rawB, 10, 64)
    80  	if err != nil {
    81  		return EncodingUnknown, 0, errors.New("e2")
    82  	}
    83  
    84  	valName, valDecoded := rawA, int(i)
    85  
    86  	var found, ok bool
    87  	var v2 constant
    88  	for _, e := range group.elements {
    89  		v2, ok = e.(constant)
    90  		if !ok {
    91  			continue
    92  		}
    93  		if v2.name == valName {
    94  			found = true
    95  			break
    96  		}
    97  	}
    98  	if !found {
    99  		return EncodingUnknown, 0, errors.New("value not found")
   100  	}
   101  
   102  	return v2.encodingType, v2.decodedValue + valDecoded, nil
   103  }
   104  
   105  func (c *constant) resolveText(group *Group) (EncodingType, int, error) {
   106  	var found, ok bool
   107  	var v2 constant
   108  	for _, e := range group.elements {
   109  		v2, ok = e.(constant)
   110  		if !ok {
   111  			continue
   112  		}
   113  		if v2.name == c.value {
   114  			found = true
   115  			break
   116  		}
   117  	}
   118  	if !found {
   119  		return EncodingUnknown, 0, errors.New("value not found")
   120  	}
   121  
   122  	return v2.encodingType, v2.decodedValue, nil
   123  }
   124  
   125  // Group contains list of elements (separator, comment or constant)
   126  type Group struct {
   127  	comment  string
   128  	elements []interface{}
   129  }
   130  
   131  func NewGroup() Group {
   132  	return Group{
   133  		comment:  "",
   134  		elements: make([]interface{}, 0),
   135  	}
   136  }
   137  
   138  var (
   139  	reDefineWithComment      = regexp.MustCompile(`^#define[ \t]+(?P<name>[A-Z0-9_]+)[ \t]+(?P<value>[0-9a-fA-Zx()_+ ]+)[ \t]+/\*(?P<comment>.*)\*/[ \t]*$`)
   140  	reDefineWithCommentStart = regexp.MustCompile(`^#define[ \t]+(?P<name>[A-Z0-9_]+)[ \t]+(?P<value>[0-9a-fA-Zx()_+ ]+)[ \t]+/\*(?P<comment>.*)[ \t]*$`)
   141  	reDefine                 = regexp.MustCompile(`^#define[ \t]+(?P<name>[A-Z0-9_]+)[ \t]+(?P<value>[0-9a-fA-Zx()_+ ]+)`)
   142  	reOnelineComment         = regexp.MustCompile(`^[ \t]*/\*(?P<text>.*)\*/[ \t]*$`)
   143  	reMultilineCommentStart  = regexp.MustCompile(`^[ \t]*/\*(?P<text>.*)[ \t]*$`)
   144  	reMultilineCommentEnd    = regexp.MustCompile(`^[ \t](?:\*)?(?P<text>.*)\*/[ \t]*$`)
   145  	reMultilineCommentMid    = regexp.MustCompile(`^[ \t]*(?:\*)?(?P<text>.*)[ \t]*$`)
   146  )
   147  
   148  func hasPrefixInSlice(v string, s []string) bool {
   149  	for _, vs := range s {
   150  		if strings.HasPrefix(v, vs) {
   151  			return true
   152  		}
   153  	}
   154  	return false
   155  }
   156  
   157  type CodeProcessor struct {
   158  	prefixesGroups [][]string
   159  
   160  	elements    []interface{}
   161  	lastComment string
   162  	state       int
   163  }
   164  
   165  func NewCodeProcessor(prefixesGroups [][]string) CodeProcessor {
   166  	return CodeProcessor{
   167  		prefixesGroups: prefixesGroups,
   168  
   169  		elements:    make([]interface{}, 0),
   170  		lastComment: "",
   171  		state:       stateNeutral,
   172  	}
   173  }
   174  
   175  func (p *CodeProcessor) ProcessFile(file io.Reader) ([]Group, error) {
   176  	var groups = make([]Group, 0)
   177  	var group = NewGroup()
   178  
   179  	var variable constant
   180  	var suffixCounter int
   181  	var lastComment string
   182  	var sepAfterComment bool
   183  	var groupCommentAdded bool
   184  	var seeking = true
   185  	var currentPrefixes = p.prefixesGroups[suffixCounter]
   186  
   187  	scanner := bufio.NewScanner(file)
   188  
   189  scanning:
   190  	for scanner.Scan() {
   191  		s := scanner.Text()
   192  
   193  		switch p.state {
   194  		case stateNeutral:
   195  			if match := reDefine.FindStringSubmatch(s); match != nil {
   196  				if !hasPrefixInSlice(match[1], currentPrefixes) { // exiting type group
   197  					if seeking {
   198  						continue // seeking through #define elements and preserving last comment in the meantime
   199  					}
   200  					groups = append(groups, group)
   201  					suffixCounter++
   202  					if suffixCounter == len(p.prefixesGroups) {
   203  						break scanning
   204  					}
   205  					group = NewGroup()
   206  					groupCommentAdded = false
   207  					currentPrefixes = p.prefixesGroups[suffixCounter]
   208  				}
   209  				seeking = false
   210  
   211  				if lastComment != "" && !groupCommentAdded {
   212  					group.comment = lastComment
   213  					groupCommentAdded = true
   214  					lastComment = ""
   215  				}
   216  
   217  				if lastComment != "" {
   218  					group.elements = append(group.elements, comment(lastComment))
   219  					lastComment = ""
   220  					if sepAfterComment {
   221  						group.elements = append(group.elements, separator(""))
   222  					}
   223  				}
   224  			}
   225  
   226  			if match := reDefineWithComment.FindStringSubmatch(s); match != nil {
   227  				variable.name, variable.value, variable.comment = match[1], match[2], strings.Trim(match[3], " \t")
   228  				variable.value = strings.Trim(variable.value, " \t")
   229  				variable.encodingType, variable.decodedValue = detectEncodingType(variable.value)
   230  				group.elements = append(group.elements, variable)
   231  				continue
   232  			}
   233  
   234  			if match := reDefineWithCommentStart.FindStringSubmatch(s); match != nil {
   235  				variable.name, variable.value, variable.comment = match[1], match[2], strings.Trim(match[3], " \t")
   236  				variable.value = strings.Trim(variable.value, " \t")
   237  				variable.encodingType, variable.decodedValue = detectEncodingType(variable.value)
   238  				p.state = stateReadingDefineWithMultilineComment
   239  				continue
   240  			}
   241  
   242  			if match := reDefine.FindStringSubmatch(s); match != nil {
   243  				variable.name, variable.value, variable.comment = match[1], match[2], ""
   244  				variable.value = strings.Trim(variable.value, " \t")
   245  				variable.encodingType, variable.decodedValue = detectEncodingType(variable.value)
   246  				group.elements = append(group.elements, variable)
   247  				continue
   248  			}
   249  
   250  			if match := reOnelineComment.FindStringSubmatch(s); match != nil {
   251  				lastComment = strings.Trim(match[1], " \t")
   252  				continue
   253  			}
   254  
   255  			if match := reMultilineCommentStart.FindStringSubmatch(s); match != nil {
   256  				lastComment = ""
   257  				if len(match[1]) > 0 {
   258  					lastComment += strings.Trim(match[1], " \t")
   259  				}
   260  				p.state = stateReadingMultilineComment
   261  				continue
   262  			}
   263  
   264  			if s == "" {
   265  				sepAfterComment = true
   266  				group.elements = append(group.elements, separator(""))
   267  				continue
   268  			} else {
   269  				sepAfterComment = false
   270  			}
   271  
   272  		case stateReadingDefineWithMultilineComment:
   273  			if match := reMultilineCommentEnd.FindStringSubmatch(s); match != nil {
   274  				if len(match[1]) > 0 {
   275  					variable.comment += strings.Trim(match[1], " \t") + "\n"
   276  				}
   277  				group.elements = append(group.elements, variable)
   278  				p.state = stateNeutral
   279  				continue
   280  			}
   281  
   282  			if match := reMultilineCommentMid.FindStringSubmatch(s); match != nil {
   283  				if len(match[1]) > 0 {
   284  					variable.comment += strings.Trim(match[1], " \t")
   285  				}
   286  				continue
   287  			}
   288  
   289  			return groups, errors.New("processing should not reach this point: #1")
   290  
   291  		case stateReadingMultilineComment:
   292  			if match := reMultilineCommentEnd.FindStringSubmatch(s); match != nil {
   293  				if len(match[1]) > 0 {
   294  					lastComment += " " + strings.Trim(match[1], " \t")
   295  				}
   296  				lastComment += match[1]
   297  				p.state = stateNeutral
   298  				continue
   299  			}
   300  
   301  			if match := reMultilineCommentMid.FindStringSubmatch(s); match != nil {
   302  				if len(match[1]) > 0 {
   303  					lastComment += strings.Trim(match[1], " \t") + "\n"
   304  				}
   305  				continue
   306  			}
   307  
   308  			return groups, errors.New("processing should not reach this point: #2")
   309  		}
   310  	}
   311  	groups = append(groups, group)
   312  	return groups, nil
   313  }