github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/kconfig/parser.go (about)

     1  // Copyright 2020 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package kconfig
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"strings"
    10  )
    11  
    12  // parser is a helper for parsing simple text protocols tailored for kconfig syntax.
    13  type parser struct {
    14  	data    []byte
    15  	file    string
    16  	current string
    17  	col     int
    18  	line    int
    19  	err     error
    20  }
    21  
    22  func newParser(data []byte, file string) *parser {
    23  	return &parser{
    24  		data: data,
    25  		file: file,
    26  	}
    27  }
    28  
    29  // nextLine resets the parser to the next line.
    30  // Automatically concatenates lines split with \ at the end.
    31  func (p *parser) nextLine() bool {
    32  	if !p.eol() {
    33  		p.failf("tailing data at the end of line")
    34  		return false
    35  	}
    36  	if p.err != nil || len(p.data) == 0 {
    37  		return false
    38  	}
    39  	p.col = 0
    40  	p.line++
    41  	p.current = p.readNextLine()
    42  	for p.current != "" && p.current[len(p.current)-1] == '\\' && len(p.data) != 0 {
    43  		p.current = p.current[:len(p.current)-1] + p.readNextLine()
    44  		p.line++
    45  	}
    46  	p.skipSpaces()
    47  	return true
    48  }
    49  
    50  func (p *parser) readNextLine() string {
    51  	line := ""
    52  	nextLine := bytes.IndexByte(p.data, '\n')
    53  	if nextLine != -1 {
    54  		line = string(p.data[:nextLine])
    55  		p.data = p.data[nextLine+1:]
    56  	} else {
    57  		line = string(p.data)
    58  		p.data = nil
    59  	}
    60  	return line
    61  }
    62  
    63  func (p *parser) skipSpaces() {
    64  	for p.col < len(p.current) && (p.current[p.col] == ' ' || p.current[p.col] == '\t') {
    65  		p.col++
    66  	}
    67  }
    68  
    69  func (p *parser) identLevel() int {
    70  	level := 0
    71  	for i := 0; i < p.col; i++ {
    72  		level++
    73  		if p.current[i] == '\t' {
    74  			level = (level + 7) & ^7
    75  		}
    76  	}
    77  	return level
    78  }
    79  
    80  func (p *parser) failf(msg string, args ...interface{}) {
    81  	if p.err == nil {
    82  		p.err = fmt.Errorf("%v:%v:%v: %v\n%v", p.file, p.line, p.col, fmt.Sprintf(msg, args...), p.current)
    83  	}
    84  }
    85  
    86  func (p *parser) eol() bool {
    87  	return p.col == len(p.current)
    88  }
    89  
    90  func (p *parser) char() byte {
    91  	if p.err != nil {
    92  		return 0
    93  	}
    94  	if p.eol() {
    95  		p.failf("unexpected end of line")
    96  		return 0
    97  	}
    98  	v := p.current[p.col]
    99  	p.col++
   100  	return v
   101  }
   102  
   103  func (p *parser) peek() byte {
   104  	if p.err != nil || p.eol() {
   105  		return 0
   106  	}
   107  	return p.current[p.col]
   108  }
   109  
   110  func (p *parser) ConsumeLine() string {
   111  	res := p.current[p.col:]
   112  	p.col = len(p.current)
   113  	return res
   114  }
   115  
   116  func (p *parser) TryConsume(what string) bool {
   117  	if !strings.HasPrefix(p.current[p.col:], what) {
   118  		return false
   119  	}
   120  	p.col += len(what)
   121  	p.skipSpaces()
   122  	return true
   123  }
   124  
   125  func (p *parser) MustConsume(what string) {
   126  	if !p.TryConsume(what) {
   127  		p.failf("expected %q", what)
   128  	}
   129  }
   130  
   131  func (p *parser) QuotedString() string {
   132  	var str []byte
   133  	quote := p.char()
   134  	if quote != '"' && quote != '\'' {
   135  		p.failf("expect quoted string")
   136  	}
   137  	for ch := p.char(); ch != quote; ch = p.char() {
   138  		if ch == 0 {
   139  			p.failf("unterminated quoted string")
   140  			break
   141  		}
   142  		if ch == '\\' {
   143  			ch = p.char()
   144  			switch ch {
   145  			case '\'', '"', '\\', 'n':
   146  				str = append(str, ch)
   147  			default:
   148  				p.failf("bad quoted character")
   149  			}
   150  			continue
   151  		}
   152  		str = append(str, ch)
   153  		if ch == '$' && p.peek() == '(' {
   154  			str = append(str, p.Shell()...)
   155  		}
   156  	}
   157  	p.skipSpaces()
   158  	return string(str)
   159  }
   160  
   161  func (p *parser) TryQuotedString() (string, bool) {
   162  	if ch := p.peek(); ch == '"' || ch == '\'' {
   163  		return p.QuotedString(), true
   164  	}
   165  	return "", false
   166  }
   167  
   168  func (p *parser) Ident() string {
   169  	var str []byte
   170  	for !p.eol() {
   171  		ch := p.peek()
   172  		if ch >= 'a' && ch <= 'z' ||
   173  			ch >= 'A' && ch <= 'Z' ||
   174  			ch >= '0' && ch <= '9' ||
   175  			ch == '_' || ch == '-' {
   176  			str = append(str, ch)
   177  			p.col++
   178  			continue
   179  		}
   180  		break
   181  	}
   182  	if len(str) == 0 {
   183  		p.failf("expected an identifier")
   184  	}
   185  	p.skipSpaces()
   186  	return string(str)
   187  }
   188  
   189  func (p *parser) Shell() string {
   190  	start := p.col
   191  	p.MustConsume("(")
   192  	for !p.eol() && p.peek() != ')' {
   193  		if p.peek() == '"' {
   194  			p.QuotedString()
   195  		} else if p.peek() == '(' {
   196  			p.Shell()
   197  		} else {
   198  			p.col++
   199  		}
   200  	}
   201  	if ch := p.char(); ch != ')' {
   202  		p.failf("shell expression is not terminated")
   203  	}
   204  	res := p.current[start:p.col]
   205  	p.skipSpaces()
   206  	return res
   207  }