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 }