~jameinel/+junk/gobzr

« back to all changes in this revision

Viewing changes to parser.go

  • Committer: John Arbash Meinel
  • Date: 2011-05-27 14:32:51 UTC
  • Revision ID: john@arbash-meinel.com-20110527143251-tlqirkem2mnfrbhz
Remove the error returns in favor of panic().

The immediate feeling, less clear state because we no longer add extra
state info to the error message. Probably a worthy trade for getting
a real traceback that has *even more* error info. (At the expense that
Parse() suppresses getting a traceback.)
Much clearer code without all the 'err' checking statements.

Possibly marginally faster (183ms). Probably fewer if checks
in trade for panic which probably has to be runtime checked
anyway.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
        "bytes"
6
6
        "fmt"
7
7
        "hash"
8
 
//      "hash/crc32"
 
8
        //      "hash/crc32"
9
9
        "io"
10
10
        "os"
11
11
        "strconv"
21
21
        ReadSlice(delim byte) (line []byte, err os.Error)
22
22
}
23
23
 
24
 
func readPrelude(r ReadSlicer) (info headerInfo, err os.Error) {
 
24
func readPrelude(r ReadSlicer) (info headerInfo) {
25
25
        headerLine, err := r.ReadSlice('\n')
26
26
        if err != nil {
27
 
                return
 
27
                panic(err)
28
28
        }
29
29
        if !bytes.Equal(headerLine, []byte(HEADER_FORMAT_3)) {
30
 
                err = ParseError(fmt.Sprintf("Invalid header\n"+
 
30
                panic(ParseError(fmt.Sprintf("Invalid header\n"+
31
31
                        "should be: %s"+
32
32
                        "    found: %s",
33
 
                        HEADER_FORMAT_3, headerLine))
34
 
                return
 
33
                        HEADER_FORMAT_3, headerLine)))
35
34
        }
36
35
        line, err := r.ReadSlice('\n')
37
36
        if err != nil {
38
 
                err = ParseError(fmt.Sprintf("Unable to read crc32 line: %s",
39
 
                        err))
40
 
                return
 
37
                panic(ParseError(fmt.Sprintf("Unable to read crc32 line: %s", err)))
41
38
        }
42
39
        if !bytes.Equal(line[0:7], []byte("crc32: ")) {
43
 
                err = ParseError(fmt.Sprintf("Invalid 'crc32' line. Found: %s", line))
44
 
                return
 
40
                panic(ParseError(fmt.Sprintf("Invalid 'crc32' line. Found: %s", line)))
45
41
        }
46
42
        // I miss line[5:-1] syntax...
47
43
        info.Crc32, err = strconv.Atoui(string(line[7 : len(line)-1]))
48
44
        if err != nil {
49
 
                err = ParseError(fmt.Sprintf("Invalid crc32: %s", err))
50
 
                return
 
45
                panic(ParseError(fmt.Sprintf("Invalid crc32: %s", err)))
51
46
        }
52
47
        line, err = r.ReadSlice('\n')
53
48
        if err != nil {
54
 
                err = ParseError(fmt.Sprintf("Unable to read num_entries line: %s",
55
 
                        err))
56
 
                return
 
49
                panic(ParseError(fmt.Sprintf("Unable to read num_entries line: %s",
 
50
                        err)))
57
51
        }
58
52
        if !bytes.Equal(line[0:13], []byte("num_entries: ")) {
59
 
                err = ParseError(fmt.Sprintf("Invalid 'num_entries' line. Found: %s",
60
 
                        line))
61
 
                return
 
53
                panic(ParseError(fmt.Sprintf("Invalid 'num_entries' line. Found: %s",
 
54
                        line)))
62
55
        }
63
56
        info.NumEntries, err = strconv.Atoui(string(line[13 : len(line)-1]))
64
57
        if err != nil {
65
 
                err = ParseError(fmt.Sprintf("Invalid num_entries: %s", err))
 
58
                panic(ParseError(fmt.Sprintf("Invalid num_entries: %s", err)))
66
59
        }
67
60
        return
68
61
}
82
75
}
83
76
 
84
77
// Ideally this would be a hidden func, but it makes testing harder, so I'm
85
 
// making it exported
86
 
func ParseLenDelimitedLine(line []byte) (result []string, err os.Error) {
 
78
// making it exported. Because this is not meant to be exported, it uses
 
79
// "panic" for its error handlinng.
 
80
func ParseLenDelimitedLine(line []byte) (result []string) {
87
81
        if len(line) < 3 { // must have a value and a newline
88
 
                err = ParseError("Invalid len-delimited line: too short")
89
 
                return
 
82
                panic(ParseError("Invalid len-delimited line: too short"))
90
83
        }
91
84
        chunks := bytes.Split(line[:len(line)-2], []byte{0}, -1)
92
85
        num, err := strconv.Atoi(string(chunks[0]))
93
86
        if err != nil {
94
 
                err = ParseError(fmt.Sprintf("Invalid len-delimited line: %s", err))
95
 
                return
 
87
                panic(ParseError(fmt.Sprintf("Invalid len-delimited line: %s", err)))
96
88
        }
97
89
        result = make([]string, 0, num)
98
90
        for _, x := range chunks[1:] {
99
91
                result = append(result, string(x))
100
92
        }
101
93
        if num != len(result) {
102
 
                err = ParseError(fmt.Sprintf(
 
94
                panic(ParseError(fmt.Sprintf(
103
95
                        "Invalid len-delimited line: Expected %d entries, found %d",
104
 
                        num, len(result)))
 
96
                        num, len(result))))
105
97
                result = nil
106
98
        }
107
99
        return
108
100
}
109
101
 
110
 
func readParentsGhosts(r *bufio.Reader) (parents, ghosts []string, err os.Error) {
 
102
func readParentsGhosts(r *bufio.Reader) (parents, ghosts []string) {
111
103
        line, err := r.ReadSlice('\n')
112
104
        if err != nil {
113
 
                err = ParseError(fmt.Sprintf("Unable to read parents line: %s", err))
114
 
                return
115
 
        }
116
 
        parents, err = ParseLenDelimitedLine(line)
117
 
        if err != nil {
118
 
                return
119
 
        }
 
105
                panic(ParseError(fmt.Sprintf("Unable to read parents line: %s", err)))
 
106
        }
 
107
        parents = ParseLenDelimitedLine(line)
120
108
        r.ReadByte()
121
109
        line, err = r.ReadSlice('\n')
122
110
        if err != nil {
123
 
                err = ParseError(fmt.Sprintf("Unable to read ghosts line: %s", err))
124
 
                return
 
111
                panic(ParseError(fmt.Sprintf("Unable to read ghosts line: %s", err)))
125
112
        }
126
113
        r.ReadByte()
127
 
        ghosts, err = ParseLenDelimitedLine(line)
 
114
        ghosts = ParseLenDelimitedLine(line)
128
115
        return
129
116
}
130
117
 
131
118
func readHeader(r *bufio.Reader) (state *State, info headerInfo, hr *HashReader, err os.Error) {
132
 
        info, err = readPrelude(r)
133
 
        if err != nil {
134
 
                return
135
 
        }
 
119
        info = readPrelude(r)
136
120
        // hr = &HashReader{crc32.NewIEEE(), r}
137
 
        parents, ghosts, err := readParentsGhosts(r)
 
121
        parents, ghosts := readParentsGhosts(r)
138
122
        state = new(State)
139
123
        state.headerState = IN_MEMORY_UNMODIFIED
140
124
        state.parents = parents
143
127
}
144
128
 
145
129
type entryParser struct {
146
 
        text       []byte
147
 
        numParents int
148
 
        curDirname      []byte
149
 
        curDirnameS     string
 
130
        text        []byte
 
131
        numParents  int
 
132
        curDirname  []byte
 
133
        curDirnameS string
150
134
}
151
135
 
152
136
func newEntryParser(text []byte, numParents int) (e *entryParser) {
169
153
}
170
154
 
171
155
// Extract the next byte string from text
172
 
func (p *entryParser) getNext() (val []byte, err os.Error) {
 
156
func (p *entryParser) getNext() (val []byte) {
173
157
        // Index is shown as the #1 or #2 time spent, so inline it/specialize it.
174
158
        // compare to end := bytes.Index(p.text, []byte{'\x00'})
175
159
        // When parsing a 70k entry dirstate with 2 parents,
188
172
                //       to add that sort of code in. Is there anything like
189
173
                //       __file__/__line__ for go?
190
174
                //               seems it is in runtime.Caller(s) and runtime.FuncForPC
191
 
                err = ParseError("Could not find trailing null byte")
192
 
                return
 
175
                panic(ParseError("Could not find trailing null byte"))
193
176
        }
194
177
        val = p.text[:end]
195
178
        // I'm hoping this is cheap in go, as we're just moving the slice further
198
181
        return
199
182
}
200
183
 
201
 
func (p *entryParser) getKey(key *entryKey) (err os.Error) {
 
184
func (p *entryParser) getKey(key *entryKey) {
202
185
        // TODO: we probably want to share the dirname strings, so use some state
203
186
        //               to track that.
204
187
        // TODO: is there a way to test that objects are the same object, rather
205
188
        //               than just equality?
206
189
        var content []byte
207
 
        if content, err = p.getNext(); err != nil {
208
 
                err = ParseError(fmt.Sprintf("Unable to find dirname for key: %s", err))
209
 
                return
210
 
        }
 
190
        content = p.getNext()
211
191
        if bytes.Equal(content, p.curDirname) {
212
192
                key.dirname = p.curDirnameS
213
193
        } else {
215
195
                p.curDirnameS = key.dirname
216
196
                p.curDirname = []byte(key.dirname)
217
197
        }
218
 
        if content, err = p.getNext(); err != nil {
219
 
                err = ParseError(fmt.Sprintf("Unable to find basename for key: %s", err))
220
 
                return
221
 
        }
222
 
        key.basename = string(content)
223
 
        if content, err = p.getNext(); err != nil {
224
 
                err = ParseError(fmt.Sprintf("Unable to find fileID for key: %s", err))
225
 
                return
226
 
        }
227
 
        key.fileID = string(content)
 
198
        key.basename = string(p.getNext())
 
199
        key.fileID = string(p.getNext())
228
200
        return
229
201
}
230
202
 
231
 
func (p *entryParser) getDetails(d *details) (err os.Error) {
 
203
func (p *entryParser) getDetails(d *details) {
232
204
        var content []byte
233
 
        if content, err = p.getNext(); err != nil {
234
 
                err = ParseError(fmt.Sprintf("minikind missing: %s", err))
235
 
                return
236
 
        }
 
205
        content = p.getNext()
237
206
        if len(content) != 1 {
238
 
                err = ParseError("minikind must be a single byte")
239
 
                return
 
207
                panic(ParseError("minikind must be a single byte"))
240
208
        }
241
209
        d.minikind = content[0]
242
 
        if content, err = p.getNext(); err != nil {
243
 
                err = ParseError(fmt.Sprintf("fingerprint missing: %s", err))
244
 
                return
245
 
        }
246
 
        d.fingerprint = string(content)
247
 
        if content, err = p.getNext(); err != nil {
248
 
                err = ParseError(fmt.Sprintf("size missing: %s", err))
249
 
                return
250
 
        }
 
210
        d.fingerprint = string(p.getNext())
 
211
        content = p.getNext()
 
212
        var err os.Error
251
213
        if d.size, err = strconv.Atoui64(string(content)); err != nil {
252
 
                err = ParseError(fmt.Sprintf("invalid size: %s", err))
253
 
                return
254
 
        }
255
 
        if content, err = p.getNext(); err != nil {
256
 
                err = ParseError(fmt.Sprintf("executable missing: %s", err))
257
 
                return
258
 
        }
 
214
                panic(ParseError(fmt.Sprintf("invalid size: %s", err)))
 
215
        }
 
216
        content = p.getNext()
259
217
        if len(content) != 1 {
260
 
                err = ParseError("executable must be a single byte long")
261
 
                return
 
218
                panic(ParseError("executable must be a single byte long"))
262
219
        }
263
220
        switch content[0] {
264
221
        case 'y':
266
223
        case 'n':
267
224
                d.executable = false
268
225
        default:
269
 
                err = ParseError("executable must be either 'n' or 'y'")
 
226
                panic(ParseError("executable must be either 'n' or 'y'"))
270
227
        }
271
228
        return
272
229
}
273
230
 
274
 
func (p *entryParser) getWorkingDetails(d *workingDetails) (err os.Error) {
275
 
        var content []byte
276
 
        if err = p.getDetails(&d.details); err != nil {
277
 
                return
278
 
        }
279
 
        if content, err = p.getNext(); err != nil {
280
 
                err = ParseError(fmt.Sprintf("packedHash missing: %s", err))
281
 
                return
282
 
        }
 
231
func (p *entryParser) getWorkingDetails(d *workingDetails) {
 
232
        p.getDetails(&d.details)
283
233
        // TODO: Consider doing a hex decode here to save packedHash as a 20-byte
284
234
        //       array, rather than a string
285
 
        d.packedHash = string(content)
286
 
        return
287
 
}
288
 
 
289
 
func (p *entryParser) getParentDetails(d *parentDetails) (err os.Error) {
290
 
        var content []byte
291
 
        if err = p.getDetails(&d.details); err != nil {
292
 
                return
293
 
        }
294
 
        if content, err = p.getNext(); err != nil {
295
 
                err = ParseError(fmt.Sprintf("revisionID missing: %s", err))
296
 
                return
297
 
        }
298
 
        d.revisionID = string(content)
299
 
        return
300
 
}
301
 
 
302
 
func (p *entryParser) getEntry(e *entry) (err os.Error) {
303
 
        if err = p.getKey(&e.key); err != nil {
304
 
                return
305
 
        }
306
 
        if err = p.getWorkingDetails(&e.work); err != nil {
307
 
                return
308
 
        }
 
235
        d.packedHash = string(p.getNext())
 
236
        return
 
237
}
 
238
 
 
239
func (p *entryParser) getParentDetails(d *parentDetails) {
 
240
        p.getDetails(&d.details)
 
241
        d.revisionID = string(p.getNext())
 
242
        return
 
243
}
 
244
 
 
245
func (p *entryParser) getEntry(e *entry) {
 
246
        p.getKey(&e.key)
 
247
        p.getWorkingDetails(&e.work)
309
248
        if p.numParents > 0 {
310
 
                if err = p.getParentDetails(&e.basis); err != nil {
311
 
                        err = ParseError(fmt.Sprintf("failed to read basis: %s", err))
312
 
                        return
313
 
                }
 
249
                p.getParentDetails(&e.basis)
314
250
        }
315
251
        for i := 1; i < p.numParents; i++ {
316
252
                parent := parentDetails{}
317
 
                if err = p.getParentDetails(&parent); err != nil {
318
 
                        err = ParseError(fmt.Sprintf("failed to read parent %d: %s", i, err))
319
 
                        return
320
 
                }
 
253
                p.getParentDetails(&parent)
321
254
                e.extra = append(e.extra, parent)
322
255
        }
323
256
        if len(p.text) < 1 || p.text[0] != '\n' {
324
 
                err = ParseError("missing trailing \\n\\x00")
325
 
                return
 
257
                panic(ParseError("missing trailing \\n\\x00"))
326
258
        }
327
259
        return
328
260
}
329
261
 
330
 
func readEntries(state *State, r *bufio.Reader, numEntries uint) (err os.Error) {
331
 
        p := newEntryParser(make([]byte, 0), len(state.parents) - len(state.ghosts))
 
262
func readEntries(state *State, r *bufio.Reader, numEntries uint) {
 
263
        p := newEntryParser(make([]byte, 0), len(state.parents)-len(state.ghosts))
332
264
        entries := make([]entry, numEntries)
 
265
        var err os.Error
333
266
        for _, e := range entries {
334
267
                // The content is actually delimited with '\n\0'
335
268
                if p.text, err = r.ReadSlice('\n'); err != nil {
336
 
                        return
337
 
                }
338
 
                if err = p.getEntry(&e); err != nil {
339
 
                        return
340
 
                }
 
269
                        panic(err)
 
270
                }
 
271
                p.getEntry(&e)
341
272
                // Consume the next '\0'
342
273
                tail, err := r.ReadByte()
343
274
                if err != nil {
344
 
                        return
 
275
                        panic(err)
345
276
                }
346
277
                if tail != '\x00' {
347
 
                        err = ParseError("entry was missing final '\\x00'")
 
278
                        panic(ParseError("entry was missing final '\\x00'"))
348
279
                }
349
280
        }
350
281
        state.entries = entries
352
283
}
353
284
 
354
285
func Parse(r io.Reader) (state *State, err os.Error) {
 
286
        defer func() {
 
287
                if e := recover(); e != nil {
 
288
                        state = nil
 
289
                        err = e.(os.Error)
 
290
                }
 
291
        }()
355
292
        bufReader := bufio.NewReader(r)
356
293
        state, info, _, err := readHeader(bufReader)
357
294
        if err != nil {