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 0 : func makeDebugParser(s string) debugParser {
36 0 : p := debugParser{
37 0 : original: s,
38 0 : }
39 0 : for _, f := range strings.Fields(s) {
40 0 : for f != "" {
41 0 : pos := strings.IndexAny(f, debugParserSeparators)
42 0 : if pos == -1 {
43 0 : p.tokens = append(p.tokens, f)
44 0 : break
45 : }
46 0 : if pos > 0 {
47 0 : p.tokens = append(p.tokens, f[:pos])
48 0 : }
49 0 : p.tokens = append(p.tokens, f[pos:pos+1])
50 0 : f = f[pos+1:]
51 : }
52 : }
53 0 : return p
54 : }
55 :
56 : // Done returns true if there are no more tokens.
57 0 : func (p *debugParser) Done() bool {
58 0 : return len(p.tokens) == 0
59 0 : }
60 :
61 : // Peek returns the next token, without consuming the token. Returns "" if there
62 : // are no more tokens.
63 0 : func (p *debugParser) Peek() string {
64 0 : if p.Done() {
65 0 : p.lastToken = ""
66 0 : return ""
67 0 : }
68 0 : p.lastToken = p.tokens[0]
69 0 : return p.tokens[0]
70 : }
71 :
72 : // Next returns the next token, or "" if there are no more tokens.
73 0 : func (p *debugParser) Next() string {
74 0 : res := p.Peek()
75 0 : if res != "" {
76 0 : p.tokens = p.tokens[1:]
77 0 : }
78 0 : return res
79 : }
80 :
81 : // Remaining returns all the remaining tokens, separated by spaces.
82 0 : func (p *debugParser) Remaining() string {
83 0 : res := strings.Join(p.tokens, " ")
84 0 : p.tokens = nil
85 0 : return res
86 0 : }
87 :
88 : // Expect consumes the next tokens, verifying that they exactly match the
89 : // arguments.
90 0 : func (p *debugParser) Expect(tokens ...string) {
91 0 : for _, tok := range tokens {
92 0 : 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 0 : func (p *debugParser) TryLevel() (level int, ok bool) {
101 0 : t := p.Peek()
102 0 : if regexp.MustCompile(`^L[0-9](|\.[0-9]+)$`).MatchString(t) {
103 0 : p.Next()
104 0 : return int(t[1] - '0'), true
105 0 : }
106 0 : return 0, false
107 : }
108 :
109 : // Level parses the next token as a level.
110 0 : func (p *debugParser) Level() int {
111 0 : level, ok := p.TryLevel()
112 0 : if !ok {
113 0 : p.Errf("cannot parse level")
114 0 : }
115 0 : return level
116 : }
117 :
118 : // Int parses the next token as an integer.
119 0 : func (p *debugParser) Int() int {
120 0 : x, err := strconv.Atoi(p.Next())
121 0 : if err != nil {
122 0 : p.Errf("cannot parse number: %v", err)
123 0 : }
124 0 : return x
125 : }
126 :
127 : // Uint64 parses the next token as an uint64.
128 0 : func (p *debugParser) Uint64() uint64 {
129 0 : x, err := strconv.ParseUint(p.Next(), 10, 64)
130 0 : if err != nil {
131 0 : p.Errf("cannot parse number: %v", err)
132 0 : }
133 0 : return x
134 : }
135 :
136 : // Uint64 parses the next token as a sequence number.
137 0 : func (p *debugParser) SeqNum() base.SeqNum {
138 0 : return base.ParseSeqNum(p.Next())
139 0 : }
140 :
141 : // FileNum parses the next token as a FileNum.
142 0 : func (p *debugParser) FileNum() base.FileNum {
143 0 : return base.FileNum(p.Int())
144 0 : }
145 :
146 : // DiskFileNum parses the next token as a DiskFileNum.
147 0 : func (p *debugParser) DiskFileNum() base.DiskFileNum {
148 0 : return base.DiskFileNum(p.Int())
149 0 : }
150 :
151 : // InternalKey parses the next token as an internal key.
152 0 : func (p *debugParser) InternalKey() base.InternalKey {
153 0 : return base.ParsePrettyInternalKey(p.Next())
154 0 : }
155 :
156 : // Errf panics with an error which includes the original string and the last
157 : // token.
158 0 : func (p *debugParser) Errf(format string, args ...any) {
159 0 : msg := fmt.Sprintf(format, args...)
160 0 : panic(errors.Errorf("error parsing %q at token %q: %s", p.original, p.lastToken, msg))
161 : }
162 :
163 : // maybeRecover can be used in a defer to convert panics into errors.
164 0 : func maybeRecover() error {
165 0 : if r := recover(); r != nil {
166 0 : err, ok := r.(error)
167 0 : if !ok {
168 0 : err = errors.Errorf("%v", r)
169 0 : }
170 0 : return err
171 : }
172 0 : return nil
173 : }
|