250 lines
7.3 KiB
Plaintext
250 lines
7.3 KiB
Plaintext
|
|
// SeamInspector — analyze inlined code seam and duplicates
|
||
|
|
// Usage: import and call report(text) or analyze_dump_file(path)
|
||
|
|
|
||
|
|
static box SeamInspector {
|
||
|
|
// Count brace delta ignoring simple strings ("...") and escapes
|
||
|
|
_brace_delta_ignoring_strings(text, start, end) {
|
||
|
|
@i = start
|
||
|
|
@n = end
|
||
|
|
@delta = 0
|
||
|
|
loop (i < n) {
|
||
|
|
@ch = text.substring(i, i+1)
|
||
|
|
if ch == "\"" {
|
||
|
|
i = i + 1
|
||
|
|
loop (i < n) {
|
||
|
|
@c = text.substring(i, i+1)
|
||
|
|
if c == "\\" { i = i + 2 continue }
|
||
|
|
if c == "\"" { i = i + 1 break }
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if ch == "{" { delta = delta + 1 }
|
||
|
|
if ch == "}" { delta = delta - 1 }
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
return delta
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find index of exact token in plain text from position
|
||
|
|
_index_of_from(h, needle, pos) {
|
||
|
|
if pos < 0 { pos = 0 }
|
||
|
|
@n = h.length()
|
||
|
|
if pos >= n { return -1 }
|
||
|
|
@m = needle.length()
|
||
|
|
if m <= 0 { return pos }
|
||
|
|
@i = pos
|
||
|
|
@limit = n - m
|
||
|
|
loop (i <= limit) {
|
||
|
|
if h.substring(i, i+m) == needle { return i }
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
return -1
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find matching closing brace starting at '{'
|
||
|
|
_find_balanced_object_end(text, idx) {
|
||
|
|
if text.substring(idx, idx+1) != "{" { return -1 }
|
||
|
|
@i = idx
|
||
|
|
@n = text.length()
|
||
|
|
@depth = 0
|
||
|
|
loop (i < n) {
|
||
|
|
@ch = text.substring(i, i+1)
|
||
|
|
if ch == "\"" {
|
||
|
|
i = i + 1
|
||
|
|
loop (i < n) {
|
||
|
|
@c = text.substring(i, i+1)
|
||
|
|
if c == "\\" { i = i + 2 continue }
|
||
|
|
if c == "\"" { i = i + 1 break }
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if ch == "{" { depth = depth + 1 }
|
||
|
|
if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
return -1
|
||
|
|
}
|
||
|
|
|
||
|
|
// Scan boxes: return array of {name, start, end}
|
||
|
|
_scan_boxes(text) {
|
||
|
|
@i = 0
|
||
|
|
@n = text.length()
|
||
|
|
@res = new ArrayBox()
|
||
|
|
@tok = "static box "
|
||
|
|
loop (i < n) {
|
||
|
|
@p = _index_of_from(text, tok, i)
|
||
|
|
if p < 0 { break }
|
||
|
|
@j = p + tok.length()
|
||
|
|
// read identifier
|
||
|
|
@name = ""
|
||
|
|
loop (j < n) {
|
||
|
|
@c = text.substring(j, j+1)
|
||
|
|
// ASCII alpha-num or '_'
|
||
|
|
if c == "_" { name = name + c j = j + 1 continue }
|
||
|
|
// digits
|
||
|
|
if c == "0" or c == "1" or c == "2" or c == "3" or c == "4" or c == "5" or c == "6" or c == "7" or c == "8" or c == "9" { name = name + c j = j + 1 continue }
|
||
|
|
// letters
|
||
|
|
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") { name = name + c j = j + 1 continue }
|
||
|
|
break
|
||
|
|
}
|
||
|
|
// skip to '{'
|
||
|
|
loop (j < n) {
|
||
|
|
@c2 = text.substring(j, j+1)
|
||
|
|
if c2 == "{" { break }
|
||
|
|
j = j + 1
|
||
|
|
}
|
||
|
|
@end = _find_balanced_object_end(text, j)
|
||
|
|
if end < 0 { end = j }
|
||
|
|
@obj = new MapBox()
|
||
|
|
obj.set("name", name)
|
||
|
|
obj.set("start", _int_to_str(p))
|
||
|
|
obj.set("end", _int_to_str(end))
|
||
|
|
res.push(obj)
|
||
|
|
i = end + 1
|
||
|
|
}
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
|
||
|
|
// Print duplicate boxes by name
|
||
|
|
_report_duplicate_boxes(text) {
|
||
|
|
@boxes = _scan_boxes(text)
|
||
|
|
@cnt = new MapBox()
|
||
|
|
@names = new ArrayBox()
|
||
|
|
@i = 0
|
||
|
|
loop (i < boxes.size()) {
|
||
|
|
@name = boxes.get(i).get("name")
|
||
|
|
@cur = cnt.get(name)
|
||
|
|
if cur == null { cnt.set(name, "1") names.push(name) } else { cnt.set(name, _int_to_str(_str_to_int(cur) + 1)) }
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
@j = 0
|
||
|
|
loop (j < names.size()) {
|
||
|
|
@k = names.get(j)
|
||
|
|
@v = cnt.get(k)
|
||
|
|
if _str_to_int(v) > 1 { print("dup_box " + k + " x" + v) }
|
||
|
|
j = j + 1
|
||
|
|
}
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Inside a given box, count function name duplicates (simple scan: name(...){ )
|
||
|
|
_report_duplicate_functions_in_box(text, box_name) {
|
||
|
|
@boxes = _scan_boxes(text)
|
||
|
|
@i = 0
|
||
|
|
@fnmap = new MapBox()
|
||
|
|
@fnames = new ArrayBox()
|
||
|
|
loop (i < boxes.size()) {
|
||
|
|
@b = boxes.get(i)
|
||
|
|
if b.get("name") == box_name {
|
||
|
|
@s = _str_to_int(b.get("start"))
|
||
|
|
@e = _str_to_int(b.get("end"))
|
||
|
|
@j = s
|
||
|
|
loop (j < e) {
|
||
|
|
// find identifier start at line head-ish (naive)
|
||
|
|
// pattern: <spaces> ident '(' ... '{'
|
||
|
|
@k = j
|
||
|
|
// skip spaces/newlines
|
||
|
|
loop (k < e) {
|
||
|
|
@ch = text.substring(k, k+1)
|
||
|
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { k = k + 1 continue }
|
||
|
|
break
|
||
|
|
}
|
||
|
|
if k >= e { break }
|
||
|
|
// read ident
|
||
|
|
@name = ""
|
||
|
|
@p = k
|
||
|
|
@c0 = text.substring(p, p+1)
|
||
|
|
if (c0 >= "A" and c0 <= "Z") or (c0 >= "a" and c0 <= "z") or c0 == "_" {
|
||
|
|
loop (p < e) {
|
||
|
|
@c = text.substring(p, p+1)
|
||
|
|
if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") or (c >= "0" and c <= "9") or c == "_" { name = name + c p = p + 1 continue }
|
||
|
|
break
|
||
|
|
}
|
||
|
|
// must be a function definition: ident '(' ... ')' ws* '{'
|
||
|
|
if text.substring(p, p+1) == "(" {
|
||
|
|
// find matching ')' with a simple counter (strings ignored for simplicity)
|
||
|
|
@d = 0
|
||
|
|
@r = p
|
||
|
|
loop (r < e) {
|
||
|
|
@cc = text.substring(r, r+1)
|
||
|
|
if cc == "(" { d = d + 1 r = r + 1 continue }
|
||
|
|
if cc == ")" {
|
||
|
|
d = d - 1
|
||
|
|
r = r + 1
|
||
|
|
if d <= 0 { break }
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if cc == "\"" {
|
||
|
|
// skip string inside params
|
||
|
|
r = r + 1
|
||
|
|
loop (r < e) {
|
||
|
|
@c2 = text.substring(r, r+1)
|
||
|
|
if c2 == "\\" { r = r + 2 continue }
|
||
|
|
if c2 == "\"" { r = r + 1 break }
|
||
|
|
r = r + 1
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
r = r + 1
|
||
|
|
}
|
||
|
|
// skip ws
|
||
|
|
loop (r < e) {
|
||
|
|
@ws = text.substring(r, r+1)
|
||
|
|
if ws == " " or ws == "\t" or ws == "\r" or ws == "\n" { r = r + 1 continue }
|
||
|
|
break
|
||
|
|
}
|
||
|
|
// definition only if next is '{'
|
||
|
|
if r < e and text.substring(r, r+1) == "{" {
|
||
|
|
@cur = fnmap.get(name)
|
||
|
|
if cur == null { fnmap.set(name, "1") fnames.push(name) } else { fnmap.set(name, _int_to_str(_str_to_int(cur) + 1)) }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// advance to next line
|
||
|
|
@nl = _index_of_from(text, "\n", k+1)
|
||
|
|
if nl < 0 || nl > e { break }
|
||
|
|
j = nl + 1
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
i = i + 1
|
||
|
|
}
|
||
|
|
@x = 0
|
||
|
|
loop (x < fnames.size()) {
|
||
|
|
@nm = fnames.get(x)
|
||
|
|
@ct = fnmap.get(nm)
|
||
|
|
if _str_to_int(ct) > 1 { print("dup_fn " + box_name + "." + nm + " x" + ct) }
|
||
|
|
x = x + 1
|
||
|
|
}
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Report summary
|
||
|
|
report(text) {
|
||
|
|
// find Main
|
||
|
|
@m = _index_of_from(text, "static box Main {", 0)
|
||
|
|
@delta = -9999
|
||
|
|
if m > 0 { delta = _brace_delta_ignoring_strings(text, 0, m) }
|
||
|
|
print("prelude_brace_delta=" + _int_to_str(delta))
|
||
|
|
|
||
|
|
// duplicate boxes
|
||
|
|
_report_duplicate_boxes(text)
|
||
|
|
|
||
|
|
// specific hot-spot
|
||
|
|
_report_duplicate_functions_in_box(text, "MiniVmPrints")
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load dump file and report
|
||
|
|
analyze_dump_file(path) {
|
||
|
|
@fb = new FileBox()
|
||
|
|
@f = fb.open(path)
|
||
|
|
if f == null { print("warn: cannot open " + path) return 0 }
|
||
|
|
@text = f.read()
|
||
|
|
f.close()
|
||
|
|
return me.report(text)
|
||
|
|
}
|
||
|
|
}
|