-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbatch_proto.v
More file actions
102 lines (93 loc) · 3.63 KB
/
Copy pathbatch_proto.v
File metadata and controls
102 lines (93 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
module graphify
// Fast encode/decode for FileResult in the batch worker protocol.
// Uses ASCII control-char delimiters instead of JSON to avoid json2 overhead
// in the hot loop (5000+ calls per extraction).
//
// Format (one line per FileResult):
// <sym_section>\x03<edge_section>
// sym_section : sym\x02sym\x02... (empty if no symbols)
// edge_section: edge\x02edge\x02... (empty if no edges)
// sym : id\x01name\x01kind_int\x01sig\x01file\x01line\x01end_line\x01is_pub\x01parent\x01doc
// edge : from\x01to\x01kind_int\x01is_method\x01recv_type
//
// \x01 = field sep, \x02 = record sep, \x03 = section sep, \x04 = encoded
// newline. Signatures and doc strings may contain any printable char; the only
// chars scrubbed are \x01-\x04 (replaced with space, none of which appear in V
// source). Real newlines are encoded rather than scrubbed, so multi-line doc
// comments survive a protocol that is otherwise one-record-per-line.
const bp_fs = '\x01' // field separator
const bp_rs = '\x02' // record separator
const bp_ss = '\x03' // section separator
// Encoded newline. Doc comments are multi-line, but this protocol is strictly
// line-based -- the worker writes one line per FileResult and the parent reads
// it back with read_lines. A raw newline would split one record into several,
// silently truncating the doc AND inflating the line count the parent uses to
// identify which file killed a crashed worker.
const bp_nl = '\x04'
fn bp_clean(s string) string {
if !s.contains_any('\x01\x02\x03\x04\n\r') {
return s
}
return s.replace('\x01', ' ').replace('\x02', ' ').replace('\x03', ' ').replace('\x04',
' ').replace('\r\n', bp_nl).replace('\n', bp_nl).replace('\r', bp_nl)
}
// bp_restore turns encoded newlines back into real ones on decode.
fn bp_restore(s string) string {
return if s.contains(bp_nl) { s.replace(bp_nl, '\n') } else { s }
}
pub fn encode_file_result(fr FileResult) string {
mut sym_parts := []string{cap: fr.symbols.len}
for s in fr.symbols {
sym_parts << '${bp_clean(s.id)}${bp_fs}${bp_clean(s.name)}${bp_fs}${int(s.kind)}${bp_fs}${bp_clean(s.signature)}${bp_fs}${bp_clean(s.file)}${bp_fs}${s.line}${bp_fs}${s.end_line}${bp_fs}${if s.is_pub { '1' } else { '0' }}${bp_fs}${bp_clean(s.parent)}${bp_fs}${bp_clean(s.doc)}'
}
mut edge_parts := []string{cap: fr.edges.len}
for e in fr.edges {
edge_parts << '${bp_clean(e.from)}${bp_fs}${bp_clean(e.to)}${bp_fs}${int(e.kind)}${bp_fs}${if e.is_method {
'1'
} else {
'0'
}}${bp_fs}${bp_clean(e.recv_type)}'
}
return sym_parts.join(bp_rs) + bp_ss + edge_parts.join(bp_rs)
}
pub fn decode_file_result(line string) FileResult {
sections := line.split(bp_ss)
sym_sec := sections[0]
edge_sec := if sections.len > 1 { sections[1] } else { '' }
mut symbols := []Symbol{}
if sym_sec != '' {
for sr in sym_sec.split(bp_rs) {
f := sr.split(bp_fs)
if f.len < 10 { continue }
symbols << Symbol{
id: f[0]
name: f[1]
kind: unsafe { SymbolKind(f[2].int()) }
signature: bp_restore(f[3])
file: f[4]
line: f[5].int()
end_line: f[6].int()
is_pub: f[7] == '1'
parent: f[8]
doc: bp_restore(f[9])
}
}
}
mut edges := []Edge{}
if edge_sec != '' {
for er in edge_sec.split(bp_rs) {
f := er.split(bp_fs)
if f.len < 3 { continue }
edges << Edge{
from: f[0]
to: f[1]
kind: unsafe { EdgeKind(f[2].int()) }
// tolerate the older 3-field form, so a cache written by a
// previous build decodes instead of panicking on f[3]
is_method: f.len > 3 && f[3] == '1'
recv_type: if f.len > 4 { f[4] } else { '' }
}
}
}
return FileResult{ symbols: symbols, edges: edges }
}