CharlesCNorton commited on
Commit ·
7b8a56c
1
Parent(s): ce521af
Add implicit multiplication to expression parser
Browse files- Tokenize infix expressions with identifiers and scientific notation\n- Insert implicit '*' between adjacent values (e.g., 2pi, 3(4+5))\n- Keep function calls intact when followed by parentheses
- calculator.py +71 -0
calculator.py
CHANGED
|
@@ -11,6 +11,7 @@ import argparse
|
|
| 11 |
import ast
|
| 12 |
import json
|
| 13 |
import math
|
|
|
|
| 14 |
import struct
|
| 15 |
import time
|
| 16 |
from dataclasses import dataclass, field
|
|
@@ -91,12 +92,82 @@ def resolve_alias_target(name: str, gates: set) -> Optional[str]:
|
|
| 91 |
return None
|
| 92 |
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
def normalize_expr(expr: str) -> str:
|
| 95 |
"""Normalize user-facing calculator syntax to Python AST syntax."""
|
| 96 |
expr = expr.replace("\u03c0", "pi")
|
| 97 |
expr = expr.replace("\u00d7", "*").replace("\u00f7", "/").replace("\u2212", "-")
|
| 98 |
if "^" in expr:
|
| 99 |
expr = expr.replace("^", "**")
|
|
|
|
| 100 |
return expr
|
| 101 |
|
| 102 |
|
|
|
|
| 11 |
import ast
|
| 12 |
import json
|
| 13 |
import math
|
| 14 |
+
import re
|
| 15 |
import struct
|
| 16 |
import time
|
| 17 |
from dataclasses import dataclass, field
|
|
|
|
| 92 |
return None
|
| 93 |
|
| 94 |
|
| 95 |
+
_NUM_RE = re.compile(r"(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?")
|
| 96 |
+
_IDENT_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
| 97 |
+
_FUNC_NAMES = {
|
| 98 |
+
"sqrt", "rsqrt", "exp", "ln", "log", "log2", "log10",
|
| 99 |
+
"deg2rad", "rad2deg",
|
| 100 |
+
"isnan", "is_nan", "isinf", "is_inf", "isfinite", "is_finite",
|
| 101 |
+
"iszero", "is_zero", "issubnormal", "is_subnormal",
|
| 102 |
+
"isnormal", "is_normal", "isneg", "is_negative", "signbit",
|
| 103 |
+
"sin", "cos", "tan", "tanh",
|
| 104 |
+
"asin", "acos", "atan", "sinh", "cosh",
|
| 105 |
+
"floor", "ceil", "round", "abs", "neg",
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _tokenize_expr(expr: str) -> List[str]:
|
| 110 |
+
tokens: List[str] = []
|
| 111 |
+
i = 0
|
| 112 |
+
while i < len(expr):
|
| 113 |
+
ch = expr[i]
|
| 114 |
+
if ch.isspace():
|
| 115 |
+
i += 1
|
| 116 |
+
continue
|
| 117 |
+
if expr.startswith("**", i):
|
| 118 |
+
tokens.append("**")
|
| 119 |
+
i += 2
|
| 120 |
+
continue
|
| 121 |
+
if ch in "+-*/(),":
|
| 122 |
+
tokens.append(ch)
|
| 123 |
+
i += 1
|
| 124 |
+
continue
|
| 125 |
+
num_match = _NUM_RE.match(expr, i)
|
| 126 |
+
if num_match:
|
| 127 |
+
tokens.append(num_match.group(0))
|
| 128 |
+
i = num_match.end()
|
| 129 |
+
continue
|
| 130 |
+
ident_match = _IDENT_RE.match(expr, i)
|
| 131 |
+
if ident_match:
|
| 132 |
+
tokens.append(ident_match.group(0))
|
| 133 |
+
i = ident_match.end()
|
| 134 |
+
continue
|
| 135 |
+
raise RuntimeError(f"bad token near: {expr[i:]}")
|
| 136 |
+
return tokens
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def _needs_implicit_mul(left: str, right: str) -> bool:
|
| 140 |
+
if left in {"+", "-", "*", "/", "**", ",", "("}:
|
| 141 |
+
return False
|
| 142 |
+
if right in {"+", "-", "*", "/", "**", ",", ")"}:
|
| 143 |
+
return False
|
| 144 |
+
if left in _FUNC_NAMES and right == "(":
|
| 145 |
+
return False
|
| 146 |
+
return True
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _insert_implicit_mul(expr: str) -> str:
|
| 150 |
+
tokens = _tokenize_expr(expr)
|
| 151 |
+
if not tokens:
|
| 152 |
+
return expr
|
| 153 |
+
out: List[str] = []
|
| 154 |
+
for idx, tok in enumerate(tokens):
|
| 155 |
+
out.append(tok)
|
| 156 |
+
if idx + 1 >= len(tokens):
|
| 157 |
+
continue
|
| 158 |
+
nxt = tokens[idx + 1]
|
| 159 |
+
if _needs_implicit_mul(tok, nxt):
|
| 160 |
+
out.append("*")
|
| 161 |
+
return "".join(out)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
def normalize_expr(expr: str) -> str:
|
| 165 |
"""Normalize user-facing calculator syntax to Python AST syntax."""
|
| 166 |
expr = expr.replace("\u03c0", "pi")
|
| 167 |
expr = expr.replace("\u00d7", "*").replace("\u00f7", "/").replace("\u2212", "-")
|
| 168 |
if "^" in expr:
|
| 169 |
expr = expr.replace("^", "**")
|
| 170 |
+
expr = _insert_implicit_mul(expr)
|
| 171 |
return expr
|
| 172 |
|
| 173 |
|