|
41 | 41 | _LIB: Optional[ctypes.CDLL] = None |
42 | 42 |
|
43 | 43 |
|
| 44 | +_KEY_ORDER_PRIORITY = { |
| 45 | + # High-level rule structure. |
| 46 | + 'head': 0, |
| 47 | + 'body': 1, |
| 48 | + 'full_text': 99, |
| 49 | + |
| 50 | + # Common predicate/call shapes. |
| 51 | + 'predicate_name': 0, |
| 52 | + 'record': 1, |
| 53 | + 'field_value': 0, |
| 54 | + 'field': 0, |
| 55 | + 'value': 1, |
| 56 | + |
| 57 | + # Expressions. |
| 58 | + 'expression': 0, |
| 59 | + 'literal': 1, |
| 60 | + 'variable': 2, |
| 61 | + 'predicate': 3, |
| 62 | + |
| 63 | + # Bodies. |
| 64 | + 'conjunction': 0, |
| 65 | + 'conjunct': 1, |
| 66 | +} |
| 67 | + |
| 68 | + |
| 69 | +def _NormalizeKeyOrder(node): |
| 70 | + """Recursively normalizes dict insertion order for downstream determinism. |
| 71 | +
|
| 72 | + The C++ parser produces JSON objects backed by `std::map`, so keys are |
| 73 | + serialized in lexicographic order. The Python parser creates dicts with |
| 74 | + a more semantic insertion order (e.g. head, body, full_text). |
| 75 | +
|
| 76 | + Some downstream code (notably type inference) walks dicts in insertion order, |
| 77 | + which can affect outcomes like assigned `type_id`s. |
| 78 | +
|
| 79 | + This function rebuilds dictionaries to approximate the Python parser's key |
| 80 | + order while preserving values exactly. |
| 81 | + """ |
| 82 | + if isinstance(node, list): |
| 83 | + return [_NormalizeKeyOrder(x) for x in node] |
| 84 | + if isinstance(node, dict): |
| 85 | + items = list(node.items()) |
| 86 | + items.sort(key=lambda kv: (_KEY_ORDER_PRIORITY.get(kv[0], 50), kv[0])) |
| 87 | + return {k: _NormalizeKeyOrder(v) for k, v in items} |
| 88 | + return node |
| 89 | + |
| 90 | + |
44 | 91 | def _GetParseModule(): |
45 | 92 | global _PARSE_MOD |
46 | 93 | if _PARSE_MOD is not None: |
@@ -267,7 +314,7 @@ def ParseRules(program_text: str, |
267 | 314 | if rc != 0: |
268 | 315 | raise _CppParsingExceptionClass(exception_thrower)(err) |
269 | 316 | try: |
270 | | - return json.loads(out) |
| 317 | + return _NormalizeKeyOrder(json.loads(out)) |
271 | 318 | except Exception as e: |
272 | 319 | raise RuntimeError('Failed to json-parse C++ parser output: %s' % e) from e |
273 | 320 |
|
@@ -302,6 +349,6 @@ def ParseFile(program_text: str, |
302 | 349 | if rc != 0: |
303 | 350 | raise _CppParsingExceptionClass(exception_thrower)(err) |
304 | 351 | try: |
305 | | - return json.loads(out) |
| 352 | + return _NormalizeKeyOrder(json.loads(out)) |
306 | 353 | except Exception as e: |
307 | 354 | raise RuntimeError('Failed to json-parse C++ parser output: %s' % e) from e |
0 commit comments