import re
import sys
def fn_name_to_human(name: str) -> str:
s = name.removeprefix("builtin_")
return s.replace("_", " ")
def infer_doc(fn_name: str, body: str, sig: str) -> str:
human = fn_name_to_human(fn_name)
words = human.split()
returns_bool = "bool_iv(" in body
returns_float = "PerlValue::float(" in body
returns_int = "PerlValue::integer(" in body and not returns_bool
returns_string = "PerlValue::string(" in body
returns_array = "PerlValue::array(" in body or "PerlValue::array_ref(" in body
returns_undef_on_fail = "PerlValue::UNDEF" in body
uses_topic = "first_arg_or_topic" in body
uses_interp = "interp:" in sig
if "unit_scale" in body:
m = re.search(r'unit_scale\(interp,\s*args,\s*\|(\w+)\|\s*(.+?)\)', body)
if m:
var, expr = m.group(1), m.group(2).strip()
return f"Unit conversion: `{human}`. Computes `{expr}` from the input.{' Defaults to `$_`.' if uses_topic else ''}"
return f"Unit conversion: `{human}`.{' Defaults to `$_`.' if uses_topic else ''}"
if words[0] == "is" and returns_bool:
what = " ".join(words[1:])
return f"Test whether the argument is {what}. Returns 1 (true) or 0 (false).{' Defaults to `$_`.' if uses_topic else ''}"
if "_to_" in human:
parts = human.split(" to ")
if len(parts) == 2:
return f"Convert {parts[0]} to {parts[1]}.{' Defaults to `$_`.' if uses_topic else ''}"
if words[0] == "from":
what = " ".join(words[1:])
return f"Parse a {what} string and return the numeric value. Returns `undef` on invalid input."
if words[0] == "to" and not "_to_" in human:
what = " ".join(words[1:])
return f"Format the input as {what}.{' Defaults to `$_`.' if uses_topic else ''}"
if words[0] == "has" and returns_bool:
what = " ".join(words[1:])
return f"Test whether the argument has {what}. Returns 1 or 0."
if words[0] == "count":
what = " ".join(words[1:])
return f"Count {what} in the input.{' Defaults to `$_`.' if uses_topic else ''}"
if words[0] == "extract":
what = " ".join(words[1:])
return f"Extract all {what} from the input string. Returns a list."
if words[0] == "format":
what = " ".join(words[1:])
return f"Format the input as a human-readable {what} string.{' Defaults to `$_`.' if uses_topic else ''}"
if words[0] == "random":
what = " ".join(words[1:])
return f"Generate a random {what}."
type_hint = ""
if returns_bool:
type_hint = " Returns 1 (true) or 0 (false)."
elif returns_float:
type_hint = " Returns a float."
elif returns_int:
type_hint = " Returns an integer."
elif returns_string:
type_hint = " Returns a string."
elif returns_array:
type_hint = " Returns a list."
topic_hint = " Defaults to `$_` when called with no args." if uses_topic else ""
desc = human[0].upper() + human[1:] if human else "Builtin function"
return f"{desc}.{type_hint}{topic_hint}"
def process(path: str) -> int:
with open(path, "r") as f:
lines = f.readlines()
added = 0
out = []
i = 0
while i < len(lines):
line = lines[i]
m = re.match(r'^(pub\s+)?fn (builtin_\w+)\(', line.strip())
if m:
fn_name = m.group(2)
has_doc = False
j = len(out) - 1
while j >= 0 and out[j].strip() == "":
j -= 1
if j >= 0 and out[j].strip().startswith("///"):
has_doc = True
if not has_doc:
body_lines = []
brace_depth = 0
started = False
for k in range(i, min(i + 50, len(lines))):
body_lines.append(lines[k])
brace_depth += lines[k].count("{") - lines[k].count("}")
if "{" in lines[k]:
started = True
if started and brace_depth <= 0:
break
body = "".join(body_lines)
doc = infer_doc(fn_name, body, line)
dispatch_name = fn_name.removeprefix("builtin_")
doc_line = f"/// `{dispatch_name}` — {doc}\n"
out.append(doc_line)
added += 1
out.append(line)
i += 1
with open(path, "w") as f:
f.writelines(out)
return added
if __name__ == "__main__":
path = sys.argv[1] if len(sys.argv) > 1 else "src/builtins.rs"
n = process(path)
print(f"added {n} /// doc comments to {path}")