Skip to content

Commit cc54d6c

Browse files
authored
added more documentation and fixed bugs with duplicate expressions and empty contexts
1 parent db7c6ff commit cc54d6c

File tree

2 files changed

+15
-5
lines changed

2 files changed

+15
-5
lines changed

check50/assertions/rewrite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def rewrite_enabled(path: str):
4747
# ENABLE_CHECK50_ASSERT = 1
4848
```
4949
50-
Ignores whitespace.
50+
Ignores whitespace and case.
5151
5252
:param path: The path to the file you wish to check.
5353
:type path: str

check50/assertions/runtime.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from check50 import Failure, Missing, Mismatch
2-
import re
32
import inspect
43
import tokenize
54
import types, builtins
@@ -56,6 +55,9 @@ def check50_assert(src, msg_or_exc=None, cond_type="unknown", left=None, right=N
5655
:raises check50.Failure: If msg_or_exc is a string, or if cond_type is \
5756
unrecognized.
5857
"""
58+
if context is None:
59+
context = {}
60+
5961
# Grab the global and local variables as of now
6062
caller_frame = inspect.currentframe().f_back
6163
caller_globals = caller_frame.f_globals
@@ -71,7 +73,9 @@ def check50_assert(src, msg_or_exc=None, cond_type="unknown", left=None, right=N
7173
except Exception as e:
7274
context[expr_str] = f"[error evaluating: {e}]"
7375

74-
# filter out modules, functions, and built-ins
76+
# filter out modules, functions, and built-ins, which is needed to avoid
77+
# overwriting function definitions in evaluaton and avoid useless string
78+
# output
7579
def is_irrelevant_value(v):
7680
return isinstance(v, (types.ModuleType, types.FunctionType, types.BuiltinFunctionType))
7781

@@ -87,7 +91,7 @@ def is_builtin_name(name):
8791
context_str = ", ".join(f"{k} = {repr(v)}" for k, v in filtered_context.items())
8892
else:
8993
filtered_context = {}
90-
94+
9195
# Since we've memoized the functions and variables once, now try and
9296
# evaluate the conditional by substituting the function calls/vars with
9397
# their results
@@ -147,11 +151,12 @@ def substitute_expressions(src: str, context: dict) -> tuple[str, dict]:
147151
}
148152
```
149153
"""
154+
# Parse the src into a stream of tokens
150155
tokens = tokenize.generate_tokens(StringIO(src).readline)
151156

152157
new_tokens = []
153158
new_context = {}
154-
placeholder_map = {}
159+
placeholder_map = {} # used for duplicates in src (i.e. x == x => __expr0 == __expr0)
155160
counter = 0
156161

157162
for tok_type, tok_string, start, end, line in tokens:
@@ -161,8 +166,13 @@ def substitute_expressions(src: str, context: dict) -> tuple[str, dict]:
161166
placeholder_map[tok_string] = placeholder
162167
new_context[placeholder] = context[tok_string]
163168
counter += 1
169+
else:
170+
# Avoid creating a new __expr{i} variable if it has already been seen
171+
placeholder = placeholder_map[tok_string]
164172
new_tokens.append((tok_type, placeholder))
165173
else:
174+
# Anything not found in the context dictionary is placed here,
175+
# including keywords, whitespace, operators, etc.
166176
new_tokens.append((tok_type, tok_string))
167177

168178
eval_src = tokenize.untokenize(new_tokens)

0 commit comments

Comments
 (0)