11from check50 import Failure , Missing , Mismatch
2- import re
32import inspect
43import tokenize
54import 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