diff --git a/src/sindi/comparator.py b/src/sindi/comparator.py index 34906a6..ce4ed11 100644 --- a/src/sindi/comparator.py +++ b/src/sindi/comparator.py @@ -161,7 +161,9 @@ def _to_sympy_expr(self, ast): return base[index] args = [self._to_sympy_expr(child) for child in ast.children] - + + if ast.value == '&' and len(args) == 2: + return sp.Function('BITAND')(*args) # Normalize ==/!= with boolean literals to X / !X if ast.value in ('==', '!=') and len(args) == 2: diff --git a/src/sindi/parser.py b/src/sindi/parser.py index 4fe7c19..ad6591c 100644 --- a/src/sindi/parser.py +++ b/src/sindi/parser.py @@ -88,7 +88,7 @@ def term(self) -> ASTNode: node = self.factor() #debug_print(f"Parsed factor in term: {node}") - while self.position < len(self.tokens) and self.tokens[self.position][1] in ('MULTIPLY', 'DIVIDE', 'MODULUS'): + while self.position < len(self.tokens) and self.tokens[self.position][1] in ('MULTIPLY', 'DIVIDE', 'MODULUS', 'BITWISE_AND'): operator = self.tokens[self.position] #debug_print(f"Parsing operator in term: {operator}") self.position += 1 diff --git a/src/sindi/rewriter.py b/src/sindi/rewriter.py index 7acfd47..6d4d98c 100644 --- a/src/sindi/rewriter.py +++ b/src/sindi/rewriter.py @@ -28,9 +28,6 @@ class Rewriter: # Bare _owner / owner -> owner() _BARE__OWNER = re.compile(r"(? !MarketplaceLib.isFinalized(X) + _FINALIZED_CLEAR = re.compile( + r"\(\s*(?P[^()]+?)\s*&\s*MarketplaceLib\.FLAG_MASK_FINALIZED\s*\)\s*==\s*0" + ) + # ------------------------------------------------------------------------------------- + def apply(self, s: str) -> str: # 1) Trivial textual normalizations s = self._NOW.sub("block.timestamp", s) @@ -73,7 +81,6 @@ def apply(self, s: str) -> str: # 3) Interface IDs (hex -> type(...).interfaceId) for pat, repl in self._IFACE_MAP.items(): s = pat.sub(repl, s) - # Normalize any spaced `type(IERCX).interfaceId` s = self._TYPE_IFACE_NORMALIZER.sub(lambda m: f"type({m.group(1)}).interfaceId", s) # 4) Owner forms @@ -83,6 +90,19 @@ def apply(self, s: str) -> str: # 5) Ether unit canonicalization to raw wei integer s = self._canon_ether_units(s) + # ---------------- NEW: strip parenthesized assignments ---------------- + # Apply repeatedly in case there are multiple occurrences. + while True: + new_s = self._PAREN_ASSIGN.sub(lambda m: m.group(2), s) + if new_s == s: + break + s = new_s + # ---------------------------------------------------------------------- + + # ---------------- NEW: bitmask → library predicate canonicalization ---- + s = self._FINALIZED_CLEAR.sub(lambda m: f"!MarketplaceLib.isFinalized({m.group('x').strip()})", s) + # ---------------------------------------------------------------------- + # 6) SafeMath → operators (iterate a few times to catch nested cases) for _ in range(4): before = s @@ -104,27 +124,23 @@ def apply(self, s: str) -> str: return s # ----- helpers ----- - def _canon_ether_units(self, s: str) -> str: - # 10**K wei → integer def _pow10_to_int(m): k = int(m.group(1)) return str(10 ** k) s = self._WEI_POW10.sub(_pow10_to_int, s) - # 1e18 wei → integer def _sci_to_int(m): n = m.group(1) return str(int(Decimal(n))) s = self._WEI_SCI.sub(_sci_to_int, s) - # N ether/gwei/wei → integer wei def _simple_to_wei(m): n = int(m.group(1)) unit = m.group(2).lower() return str(n * self._ETH_MULT[unit]) s = self._ETHER_SIMPLE.sub(_simple_to_wei, s) - return s \ No newline at end of file + return s diff --git a/src/sindi/tokenizer.py b/src/sindi/tokenizer.py index c16b409..104b3ba 100644 --- a/src/sindi/tokenizer.py +++ b/src/sindi/tokenizer.py @@ -1,7 +1,6 @@ import re from typing import List, Tuple - class Tokenizer: def __init__(self): self.token_patterns = [ @@ -17,7 +16,7 @@ def __init__(self): (r'&&', 'AND'), (r'\|\|', 'OR'), (r'\!', 'NOT'), - (r'&', 'BITWISE_AND'), + (r'\&', 'BITWISE_AND'), (r'\?', 'QUESTION'), (r':', 'COLON'), (r'\(', 'LPAREN'), @@ -33,16 +32,19 @@ def __init__(self): (r'\[', 'LBRACKET'), (r'\]', 'RBRACKET'), (r'\"[^\"]*\"', 'STRING_LITERAL'), - (r'\b\d+\.\d+\b', 'FLOAT'), - (r'\b\d+\b', 'INTEGER'), + + # ---- Numbers (order matters: scientific before float/int) ---- + (r'\b\d(?:_?\d)*(?:\.\d(?:_?\d)*)?[eE][+-]?\d+(?:_?\d)*\b', 'SCIENTIFIC'), + (r'\b\d(?:_?\d)*\.\d(?:_?\d)*\b', 'FLOAT'), + (r'\b\d(?:_?\d)*\b', 'INTEGER'), + (r'\btrue\b', 'TRUE'), (r'\bfalse\b', 'FALSE'), (r'0x[0-9a-fA-F]{40}', 'ADDRESS_LITERAL'), (r'0x[0-9a-fA-F]+', 'BYTES_LITERAL'), - (r'\b\d+\s*(seconds|minutes|hours|days|weeks)\b', 'TIME_UNIT'), # Handle time units + (r'\b\d(?:_?\d)*\s*(seconds|minutes|hours|days|weeks)\b', 'TIME_UNIT'), (r'[a-zA-Z_]\w*', 'IDENTIFIER'), - (r'\d+e\d+', 'SCIENTIFIC'), # Handle scientific notation - (r'\s+', None), # Let's ignore whitespace(s) + (r'\s+', None), ] self.time_units = { 'seconds': 1, @@ -74,13 +76,28 @@ def tokenize(self, predicate: str) -> List[Tuple[str, str]]: if match: if tag: value = match.group(0) + if tag == 'TIME_UNIT': - number, unit = re.match(r'(\d+)\s*(\w+)', value).groups() - value = str(int(number) * self.time_units[unit]) - tag = 'INTEGER' - elif tag == 'SCIENTIFIC': - value = str(int(float(value))) + num, unit = re.match(r'(\d(?:_?\d)*)\s*(\w+)', value).groups() + num = int(num.replace('_', '')) + value = str(num * self.time_units[unit]) tag = 'INTEGER' + + elif tag in ('SCIENTIFIC', 'FLOAT', 'INTEGER'): + # Strip underscores from numeric tokens + value = value.replace('_', '') + if tag == 'SCIENTIFIC': + # Normalize to INTEGER by evaluating (safe enough for our use) + # Example: 9e18 -> 9000000000000000000 + try: + from decimal import Decimal + value = str(int(Decimal(value))) + tag = 'INTEGER' + except Exception: + # Fall back to float path if Decimal fails + value = str(int(float(value))) + tag = 'INTEGER' + tokens.append((value, tag)) position = match.end() break