#!/usr/bin/env python3
"""Focused VC6 C89 regression checks for Objects/longobject.c."""
import pathlib
import re
import sys
source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
match = re.search(r"static PyObject \*\s*get_small_int\(sdigit ival\)\s*\{(?P
.*?)\n\}", source, re.S)
assert match, "get_small_int definition not found"
body = match.group("body")
assert re.search(r"\A\s*PyObject \*v;\s*assert\(IS_SMALL_INT\(ival\)\);\s*v = \(PyObject \*\)&_PyLong_SMALL_INTS\[_PY_NSMALLNEGINTS \+ ival\];\s*Py_INCREF\(v\);\s*return v;", body), "get_small_int must declare v at function entry and retain assertion, lookup, incref, and return order"
print("longobject get_small_int C89 declaration and evaluation order: PASS")
match = re.search(r"PyLongObject \*\s*_PyLong_New\(Py_ssize_t size\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "_PyLong_New definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
expected = """
PyLongObject *result;
Py_ssize_t ndigits;
if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
PyErr_SetString(PyExc_OverflowError,
"too many digits in integer");
return NULL;
}
ndigits = size ? size : 1;
result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) +
ndigits*sizeof(digit));
if (!result) {
PyErr_NoMemory();
return NULL;
}
_PyObject_InitVar((PyVarObject*)result, &PyLong_Type, size);
return result;
"""
assert body.split() == expected.split(), "_PyLong_New must declare ndigits at function entry and retain overflow check, digit count, allocation, error, initialization, and return order"
print("longobject _PyLong_New C89 declaration and allocation/error order: PASS")
match = re.search(r"static PyObject \*\s*_PyLong_FromMedium\(sdigit x\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "_PyLong_FromMedium definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
expected = """
PyLongObject *v;
Py_ssize_t sign;
digit abs_x;
assert(!IS_SMALL_INT(x));
assert(is_medium_int(x));
v = PyObject_Malloc(sizeof(PyLongObject));
if (v == NULL) {
PyErr_NoMemory();
return NULL;
}
sign = x < 0 ? -1: 1;
abs_x = x < 0 ? -x : x;
_PyObject_InitVar((PyVarObject*)v, &PyLong_Type, sign);
v->ob_digit[0] = abs_x;
return (PyObject*)v;
"""
assert body.split() == expected.split(), "_PyLong_FromMedium must declare v, sign, and abs_x at function entry and retain assertions, allocation, NULL/error/return, sign, abs_x, initialization, digit write, and return order"
print("longobject _PyLong_FromMedium C89 declarations and evaluation/error order: PASS")
match = re.search(r"static PyObject \*\s*_PyLong_FromLarge\(stwodigits ival\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "_PyLong_FromLarge definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
expected = """
twodigits abs_ival;
twodigits t;
Py_ssize_t ndigits;
PyLongObject *v;
digit *p;
int sign;
assert(!is_medium_int(ival));
if (ival < 0) {
abs_ival = 0U-(twodigits)ival;
sign = -1;
}
else {
abs_ival = (twodigits)ival;
sign = 1;
}
assert(abs_ival >> PyLong_SHIFT != 0);
t = abs_ival >> (PyLong_SHIFT * 2);
ndigits = 2;
while (t) {
++ndigits;
t >>= PyLong_SHIFT;
}
v = _PyLong_New(ndigits);
if (v != NULL) {
p = v->ob_digit;
Py_SET_SIZE(v, ndigits * sign);
t = abs_ival;
while (t) {
*p++ = Py_SAFE_DOWNCAST(
t & PyLong_MASK, twodigits, digit);
t >>= PyLong_SHIFT;
}
}
return (PyObject *)v;
"""
assert body.split() == expected.split(), "_PyLong_FromLarge must declare abs_ival, t, ndigits, v, p, and sign at function entry and retain assertions, sign/absolute-value branches, digit count, allocation, initialization, digit writes, and return order"
print("longobject _PyLong_FromLarge C89 declarations and evaluation order: PASS")
match = re.search(r"^#define PYLONG_FROM_UINT\(INT_TYPE, ival\) (?P.*?)^ \} while\(0\)$", source, re.S | re.M)
assert match, "PYLONG_FROM_UINT definition not found"
body = match.group("body") + " } while(0)"
body = re.sub(r"\\\n", "\n", body)
body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
assert re.search(r"\A\s*do\s*\{\s*Py_ssize_t ndigits;\s*INT_TYPE t;\s*PyLongObject \*v;\s*digit \*p;", body), "PYLONG_FROM_UINT must declare ndigits, t, v, and p at do-block entry"
expected = """
do {
Py_ssize_t ndigits;
INT_TYPE t;
PyLongObject *v;
digit *p;
if (IS_SMALL_UINT(ival)) {
return get_small_int((sdigit)(ival));
}
ndigits = 0;
t = (ival);
while (t) {
++ndigits;
t >>= PyLong_SHIFT;
}
v = _PyLong_New(ndigits);
if (v == NULL) {
return NULL;
}
p = v->ob_digit;
while ((ival)) {
*p++ = (digit)((ival) & PyLong_MASK);
(ival) >>= PyLong_SHIFT;
}
return (PyObject *)v;
} while(0)
"""
assert body.split() == expected.split(), "PYLONG_FROM_UINT must retain small-int early return, ndigits/t evaluation, digit count, allocation, NULL return, p initialization, ival evaluation/mutation, digit writes, and return order"
print("longobject PYLONG_FROM_UINT C89 declarations and evaluation/error order: PASS")
match = re.search(r"PyObject \*\s*PyLong_FromUnsignedLongLong\((?P[^)]*)\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "PyLong_FromUnsignedLongLong definition not found"
assert match.group("parameter").split() == "unsigned PY_LONG_LONG ival".split(), "PyLong_FromUnsignedLongLong must use unsigned PY_LONG_LONG to match the public declaration and VC6 __int64 spelling"
assert match.group("body").split() == "PYLONG_FROM_UINT(unsigned PY_LONG_LONG, ival);".split(), "PyLong_FromUnsignedLongLong must pass unsigned PY_LONG_LONG to PYLONG_FROM_UINT, preserving the unsigned 64-bit temporary"
print("longobject PyLong_FromUnsignedLongLong VC6 parameter and macro type spelling: PASS")
match = re.search(r"PyObject \*\s*PyLong_FromDouble\(double dval\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "PyLong_FromDouble definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
assert re.search(r"\A\s*PyLongObject \*v;\s*double frac;\s*int i, ndig, expo, neg;\s*const double int_max = \(unsigned long\)LONG_MAX \+ 1;", body), "PyLong_FromDouble locals must precede the fast-path statement"
ordered = (
"const double int_max = (unsigned long)LONG_MAX + 1;",
"if (-int_max < dval && dval < int_max)",
"return PyLong_FromLong((long)dval);",
"neg = 0;",
"if (Py_IS_INFINITY(dval))",
"if (Py_IS_NAN(dval))",
"if (dval < 0.0)",
"frac = frexp(dval, &expo);",
"ndig = (expo-1) / PyLong_SHIFT + 1;",
"v = _PyLong_New(ndig);",
"frac = ldexp(frac, (expo-1) % PyLong_SHIFT + 1);",
"for (i = ndig; --i >= 0; )",
"if (neg)",
"return (PyObject *)v;",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromDouble must preserve fast-path, validation, sign, conversion, allocation, digit-loop, and return order"
print("longobject PyLong_FromDouble C89 declarations and evaluation/error order: PASS")
code = re.sub(r"/\*.*?\*/|//[^\n]*|\"(?:\\.|[^\"\\])*\"", "", source, flags=re.S)
assert not re.search(r"\blong\s+long\b", code), "longobject must spell 64-bit integers with PY_LONG_LONG for VC6"
assert not re.search(r"\bLLONG_(?:MAX|MIN)\b", code), "longobject must use PY_LLONG_MAX/PY_LLONG_MIN instead of unavailable LLONG limits"
for signature in (
r"PyLong_FromLongLong\(PY_LONG_LONG ival\)",
r"PyLong_AsLongLong\(PyObject \*vv\)",
r"PyLong_AsUnsignedLongLong\(PyObject \*vv\)",
r"PyLong_AsUnsignedLongLongMask\(PyObject \*vv\)",
r"PyLong_AsLongLongAndOverflow\(PyObject \*vv, int \*overflow\)",
):
assert re.search(signature, source), "missing VC6-compatible long API declaration/definition: " + signature
print("longobject 64-bit API uses VC6 PY_LONG_LONG spellings: PASS")
match = re.search(r"static digit\s+inplace_divrem1\(digit \*pout, digit \*pin, Py_ssize_t size, digit n\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "inplace_divrem1 definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
loop = re.search(r"while \(--size >= 0\)\s*\{(?P.*?)\n \}", body, re.S)
assert loop, "inplace_divrem1 digit loop not found"
loop_body = loop.group("body")
assert re.search(r"\A\s*twodigits dividend;\s*digit quotient;\s*dividend = \(\(twodigits\)remainder << PyLong_SHIFT\) \| pin\[size\];\s*quotient = \(digit\)\(dividend / n\);\s*remainder = dividend % n;\s*pout\[size\] = quotient;", loop_body), "inplace_divrem1 loop temporaries must be declared before the executable dividend calculation, preserving divide/remainder/store order"
print("longobject inplace_divrem1 loop C89 declaration and division order: PASS")
late_ucs4 = "else {\n assert (kind == PyUnicode_4BYTE_KIND);\n Py_UCS4 *p;"
entry_ucs4 = "else {\n Py_UCS4 *p;\n assert (kind == PyUnicode_4BYTE_KIND);"
assert source.count(late_ucs4) == 0, "Py_UCS4 locals must be hoisted before their branch assertions"
assert source.count(entry_ucs4) == 2, "both long-formatting branches must declare Py_UCS4 before checking the kind"
print("longobject Unicode formatting Py_UCS4 C89 branch declarations: PASS")
match = re.search(r"PyObject \*\s*PyLong_FromString\(const char \*str, char \*\*pend, int base\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "PyLong_FromString definition not found"
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
assert re.search(r"\A\s*int sign = 1, error_if_nonzero = 0;\s*const char \*start, \*orig_str = str;\s*PyLongObject \*z = NULL;\s*PyObject \*strobj;\s*Py_ssize_t slen;\s*double fsize_z;", body), "PyLong_FromString must declare fsize_z in the function's opening declaration group"
ordered = (
"if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD)",
"fsize_z = (double)digits * log_base_BASE[base] + 1.0;",
"if (fsize_z > (double)MAX_LONG_DIGITS)",
"size_z = (Py_ssize_t)fsize_z;",
"z = _PyLong_New(size_z);",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromString must preserve digit-limit, size calculation, overflow, allocation, and conversion order"
print("longobject PyLong_FromString C89 size declaration and parsing order: PASS")
match = re.search(r"static int\s+long_divrem\(PyLongObject \*a, PyLongObject \*b,\s*PyLongObject \*\*pdiv, PyLongObject \*\*prem\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "long_divrem definition not found"
body = match.group("body")
short_case = re.search(r"if \(size_a < size_b.*?\) \{(?P.*?)\n \}", body, re.S)
assert short_case, "long_divrem smaller-dividend branch not found"
branch = short_case.group("body")
tokens = (
"PyObject *zero;",
"*prem = (PyLongObject *)long_long((PyObject *)a);",
"if (*prem == NULL)",
"zero = _PyLong_GetZero();",
"Py_INCREF(zero);",
"*pdiv = (PyLongObject*)zero;",
)
positions = [branch.find(token) for token in tokens]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_divrem must declare zero at branch entry and preserve remainder, error, singleton, incref, and quotient ordering"
print("longobject long_divrem C89 local and reference order: PASS")
match = re.search(r"PyObject \*\s*_PyLong_Add\(PyLongObject \*a, PyLongObject \*b\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "_PyLong_Add definition not found"
body = match.group("body")
assert re.match(r"\s*PyLongObject \*z;", body), "_PyLong_Add must declare z at function entry before the small-value fast path"
assert " PyLongObject *z;" not in body[body.find("if (IS_MEDIUM_VALUE(a)"):], "_PyLong_Add must not declare z after an executable fast-path statement"
ordered = (
"PyLongObject *z;",
"if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b))",
"return _PyLong_FromSTwoDigits(medium_value(a) + medium_value(b));",
"if (Py_SIZE(a) < 0)",
"z = x_add(a, b);",
"return (PyObject *)z;",
)
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_Add must preserve small-integer fast path and sign/operation order"
print("longobject _PyLong_Add C89 declaration and arithmetic order: PASS")
match = re.search(r"static PyObject \*\s*long_pow\(PyObject \*v, PyObject \*w, PyObject \*x\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "long_pow definition not found"
body = match.group("body")
assert 0 <= body.find("digit bi, bit;") < body.find("CHECK_BINOP(v, w);"), "long_pow must declare exponent digit temporaries with its opening locals"
assert "digit bi = i ?" not in body and "digit bit;" not in body[body.find("i = Py_SIZE(b);"):], "long_pow must not declare digit temporaries after executable statements"
ordered = ("i = Py_SIZE(b);", "bi = i ? b->ob_digit[i-1] : 0;", "if (i <= 1 && bi <= 3)")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve exponent digit setup and branch ordering"
print("longobject long_pow C89 declarations and exponent setup order: PASS")
branch_start = body.find("else {\n int pending;\n int blen;")
branch_end = body.find("\n }\n\n if (negativeOutput", branch_start)
assert branch_start >= 0 and branch_end > branch_start, "long_pow k-ary branch boundaries not found"
branch = body[branch_start:branch_end]
assert branch.find("int pending;") < branch.find("Py_INCREF(a);") and branch.find("int blen;") < branch.find("Py_INCREF(a);"), "long_pow pending and bit-length locals must be declared at branch entry"
assert "int pending = 0, blen = 0;" not in branch, "long_pow pending locals must be declared separately from their original initialization point"
ordered = ("Py_CLEAR(a2);", "pending = 0;", "blen = 0;", "#define ABSORB_PENDING")
positions = [branch.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve pending initialization after table setup and before the sliding-window macro"
print("longobject long_pow C89 pending declarations and initialization order: PASS")
match = re.search(r"static int\s+divmod_shift\(PyObject \*shiftby, Py_ssize_t \*wordshift, digit \*remshift\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "divmod_shift definition not found"
body = match.group("body")
assert re.match(r"\s*Py_ssize_t lshiftby;\s*PyLongObject \*wordshift_obj;", body), "divmod_shift must declare both conversion temporaries at function entry"
assert "Py_ssize_t lshiftby =" not in body and "PyLongObject *wordshift_obj =" not in body, "divmod_shift must preserve original conversion points as assignments"
ordered = ("assert(PyLong_Check(shiftby));", "assert(Py_SIZE(shiftby) >= 0);", "lshiftby = PyLong_AsSsize_t((PyObject *)shiftby);", "wordshift_obj = divrem1((PyLongObject *)shiftby, PyLong_SHIFT, remshift);", "*wordshift = PyLong_AsSsize_t((PyObject *)wordshift_obj);")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "divmod_shift must preserve validation, conversion, fallback division, and result extraction order"
print("longobject divmod_shift C89 locals and overflow-fallback order: PASS")
match = re.search(r"static PyObject \*\s*long_rshift1\(PyLongObject \*a, Py_ssize_t wordshift, digit remshift\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "long_rshift1 definition not found"
body = match.group("body")
assert "digit sticky = 0;" not in body and "for (Py_ssize_t j =" not in body and "for (Py_ssize_t i =" not in body, "long_rshift1 must not declare locals in executable branches or C99 for initializers"
negative_matches = list(re.finditer(r"if \(a_negative\)\s*\{(?P.*?)\n \}", body, re.S))
assert negative_matches, "long_rshift1 negative-input branch not found"
negative_body = negative_matches[-1].group("body")
assert negative_body.find("digit sticky;") < negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must declare sticky before branch statements"
assert negative_body.find("sticky = 0;") > negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must initialize sticky after setting the result sign"
assert "{\n Py_ssize_t j;\n for (j = 0; j < wordshift; j++)" in negative_body, "long_rshift1 must keep the sticky-scan counter block-scoped with C89 declarations"
assert "{\n Py_ssize_t i, j;\n i = 0;\n j = wordshift + 1;\n for (; j < size_a; i++, j++)" in body, "long_rshift1 must keep copy-loop counters block-scoped and preserve initial values"
print("longobject long_rshift1 C89 locals and shifted-digit order: PASS")
for name, op in (("long_and", "&"), ("long_xor", "^"), ("long_or", "|")):
match = re.search(r"static PyObject \*\s*" + name + r"\(PyObject \*a, PyObject \*b\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, name + " definition not found"
body = match.group("body")
expected = """
PyLongObject *x;
PyLongObject *y;
CHECK_BINOP(a, b);
x = (PyLongObject*)a;
y = (PyLongObject*)b;
"""
assert body.split()[:len(expected.split())] == expected.split(), name + " must declare x/y before type checking, then cast and preserve the small-value fast path"
assert "PyLongObject *x =" not in body and "PyLongObject *y =" not in body, name + " must not declare cast locals after CHECK_BINOP"
assert "return long_bitwise(x, '" + op + "', y);" in body, name + " must retain the expected general bitwise operation"
print("longobject bitwise entry points C89 locals and dispatch order: PASS")
match = re.search(r"PyObject \*\s*_PyLong_DivmodNear\(PyObject \*a, PyObject \*b\)\s*\{(?P.*?)\n\}", source, re.S)
assert match, "_PyLong_DivmodNear definition not found"
body = match.group("body")
assert re.match(r"\s*PyLongObject \*quo = NULL, \*rem = NULL;\s*PyObject \*twice_rem, \*result, \*temp;\s*PyObject \*one;\s*int quo_is_odd, quo_is_neg;\s*Py_ssize_t cmp;", body), "_PyLong_DivmodNear must declare one with the function's opening locals"
assert "PyObject *one =" not in body, "_PyLong_DivmodNear must preserve _PyLong_GetOne lookup at its original execution point"
ordered = ("if (!PyLong_Check(a) || !PyLong_Check(b))", "quo_is_neg =", "long_divrem(", "one = _PyLong_GetOne();", "twice_rem = long_lshift((PyObject *)rem, one);")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_DivmodNear must preserve validation, divrem, singleton lookup, and shift order"
print("longobject _PyLong_DivmodNear C89 local and singleton lookup order: PASS")
match = re.search(r"static PyObject \*\s*int_bit_count_impl\(PyObject \*self\)\s*/\*.*?\*/\s*\{(?P.*?)\n\}", source, re.S)
assert match, "int_bit_count_impl definition not found"
body = match.group("body")
expected = """
PyLongObject *z;
Py_ssize_t ndigits, bit_count, ndigits_fast, i;
PyObject *result;
assert(self != NULL);
assert(PyLong_Check(self));
z = (PyLongObject *)self;
ndigits = Py_ABS(Py_SIZE(z));
bit_count = 0;
"""
assert body.split()[:len(expected.split())] == expected.split(), "int_bit_count_impl must place declarations before assertions and preserve initialization order"
assert "Py_ssize_t ndigits =" not in body and "Py_ssize_t bit_count =" not in body and "for (Py_ssize_t i =" not in body, "int_bit_count_impl must not use late declarations or C99 loop variables"
assert "PyObject *result =" not in body and "PyObject *x =" not in body and "PyObject *y =" not in body, "int_bit_count_impl must initialize object locals at their original execution points"
assert "Py_ssize_t ndigits_fast =" not in body, "int_bit_count_impl must calculate the fast digit bound at the original point"
ordered = ("ndigits_fast = Py_MIN(ndigits, PY_SSIZE_T_MAX/PyLong_SHIFT);", "for (i = 0; i < ndigits_fast; i++)", "result = PyLong_FromSsize_t(bit_count);", "for (i = ndigits_fast; i < ndigits; i++)")
positions = [body.find(token) for token in ordered]
assert all(position >= 0 for position in positions) and positions == sorted(positions), "int_bit_count_impl must preserve fast and overflow-safe accumulation order"
loop = re.search(r"for \(i = ndigits_fast; i < ndigits; i\+\+\)\s*\{(?P.*?)\n \}", body, re.S)
assert loop and re.match(r"\s*PyObject \*x;\s*PyObject \*y;\s*x = PyLong_FromLong\(popcount_digit\(z->ob_digit\[i\]\)\);", loop.group("body")), "int_bit_count_impl must declare per-digit temporaries before the loop body statements"
assert 0 <= loop.group("body").find("y = long_add(") < loop.group("body").find("Py_DECREF(x);"), "int_bit_count_impl must preserve accumulation before releasing the temporary digit count"
print("longobject int_bit_count_impl C89 declarations and accumulation order: PASS")