Line data Source code
1 : // Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2 : // of this source code is governed by a BSD-style license that can be found in
3 : // the LICENSE file.
4 :
5 : package manifest
6 :
7 : import (
8 : "fmt"
9 : "regexp"
10 : "strconv"
11 : "strings"
12 :
13 : "github.com/cockroachdb/errors"
14 : "github.com/cockroachdb/pebble/internal/base"
15 : )
16 :
17 : // debugParser is a helper used to implement parsing of debug strings, like
18 : // ParseFileMetadataDebug.
19 : //
20 : // It takes a string and splits it into tokens. Tokens are separated by
21 : // whitespace; in addition separators "_-[]()" are always separate tokens. For
22 : // example, the string `000001:[a - b]` results in tokens `000001`,
23 : // `:`, `[`, `a`, `-`, `b`, `]`, .
24 : //
25 : // All debugParser methods throw panics instead of returning errors. The code
26 : // that uses a debugParser can recover them and convert them to errors.
27 : type debugParser struct {
28 : original string
29 : tokens []string
30 : lastToken string
31 : }
32 :
33 : const debugParserSeparators = ":-[]()"
34 :
35 1 : func makeDebugParser(s string) debugParser {
36 1 : p := debugParser{
37 1 : original: s,
38 1 : }
39 1 : for _, f := range strings.Fields(s) {
40 1 : for f != "" {
41 1 : pos := strings.IndexAny(f, debugParserSeparators)
42 1 : if pos == -1 {
43 1 : p.tokens = append(p.tokens, f)
44 1 : break
45 : }
46 1 : if pos > 0 {
47 1 : p.tokens = append(p.tokens, f[:pos])
48 1 : }
49 1 : p.tokens = append(p.tokens, f[pos:pos+1])
50 1 : f = f[pos+1:]
51 : }
52 : }
53 1 : return p
54 : }
55 :
56 : // Done returns true if there are no more tokens.
57 1 : func (p *debugParser) Done() bool {
58 1 : return len(p.tokens) == 0
59 1 : }
60 :
61 : // Peek returns the next token, without consuming the token. Returns "" if there
62 : // are no more tokens.
63 1 : func (p *debugParser) Peek() string {
64 1 : if p.Done() {
65 0 : p.lastToken = ""
66 0 : return ""
67 0 : }
68 1 : p.lastToken = p.tokens[0]
69 1 : return p.tokens[0]
70 : }
71 :
72 : // Next returns the next token, or "" if there are no more tokens.
73 1 : func (p *debugParser) Next() string {
74 1 : res := p.Peek()
75 1 : if res != "" {
76 1 : p.tokens = p.tokens[1:]
77 1 : }
78 1 : return res
79 : }
80 :
81 : // Remaining returns all the remaining tokens, separated by spaces.
82 1 : func (p *debugParser) Remaining() string {
83 1 : res := strings.Join(p.tokens, " ")
84 1 : p.tokens = nil
85 1 : return res
86 1 : }
87 :
88 : // Expect consumes the next tokens, verifying that they exactly match the
89 : // arguments.
90 1 : func (p *debugParser) Expect(tokens ...string) {
91 1 : for _, tok := range tokens {
92 1 : if res := p.Next(); res != tok {
93 0 : p.Errf("expected %q, got %q", tok, res)
94 0 : }
95 : }
96 : }
97 :
98 : // TryLevel tries to parse a token as a level (e.g. L1, L0.2). If successful,
99 : // the token is consumed.
100 1 : func (p *debugParser) TryLevel() (level int, ok bool) {
101 1 : t := p.Peek()
102 1 : if regexp.MustCompile(`^L[0-9](|\.[0-9]+)$`).MatchString(t) {
103 1 : p.Next()
104 1 : return int(t[1] - '0'), true
105 1 : }
106 1 : return 0, false
107 : }
108 :
109 : // Level parses the next token as a level.
110 1 : func (p *debugParser) Level() int {
111 1 : level, ok := p.TryLevel()
112 1 : if !ok {
113 0 : p.Errf("cannot parse level")
114 0 : }
115 1 : return level
116 : }
117 :
118 : // Int parses the next token as an integer.
119 1 : func (p *debugParser) Int() int {
120 1 : x, err := strconv.Atoi(p.Next())
121 1 : if err != nil {
122 0 : p.Errf("cannot parse number: %v", err)
123 0 : }
124 1 : return x
125 : }
126 :
127 : // Uint64 parses the next token as an uint64.
128 1 : func (p *debugParser) Uint64() uint64 {
129 1 : x, err := strconv.ParseUint(p.Next(), 10, 64)
130 1 : if err != nil {
131 0 : p.Errf("cannot parse number: %v", err)
132 0 : }
133 1 : return x
134 : }
135 :
136 : // FileNum parses the next token as a FileNum.
137 1 : func (p *debugParser) FileNum() base.FileNum {
138 1 : return base.FileNum(p.Int())
139 1 : }
140 :
141 : // DiskFileNum parses the next token as a DiskFileNum.
142 1 : func (p *debugParser) DiskFileNum() base.DiskFileNum {
143 1 : return base.DiskFileNum(p.Int())
144 1 : }
145 :
146 : // InternalKey parses the next token as an internal key.
147 1 : func (p *debugParser) InternalKey() base.InternalKey {
148 1 : return base.ParsePrettyInternalKey(p.Next())
149 1 : }
150 :
151 : // Errf panics with an error which includes the original string and the last
152 : // token.
153 0 : func (p *debugParser) Errf(format string, args ...any) {
154 0 : msg := fmt.Sprintf(format, args...)
155 0 : panic(errors.Errorf("error parsing %q at token %q: %s", p.original, p.lastToken, msg))
156 : }
157 :
158 : // maybeRecover can be used in a defer to convert panics into errors.
159 1 : func maybeRecover() error {
160 1 : if r := recover(); r != nil {
161 0 : err, ok := r.(error)
162 0 : if !ok {
163 0 : err = errors.Errorf("%v", r)
164 0 : }
165 0 : return err
166 : }
167 1 : return nil
168 : }
|