ART: Immutable `variables` dictionary in Checker
Python's lack of read-only references makes passing state information
to other functions unsafe. This patch adds an immutable dictionary
class to Checker and uses it when passing around current values of
variables.
Change-Id: I54f2eac54d4d59e16daa74364e6d91a6cc953f6f
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
index 2ed4aa7..116fe9a 100644
--- a/tools/checker/match/file.py
+++ b/tools/checker/match/file.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.immutables import ImmutableDict
from common.logger import Logger
from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
from file_format.checker.struct import CheckerFile, TestCase, TestAssertion
@@ -112,7 +113,7 @@
responsible for running the checks in the right order and scope, and
for propagating the variable state between the check lines.
"""
- varState = {}
+ varState = ImmutableDict()
checkLines = checkGroup.assertions
outputLines = outputGroup.body
startLineNo = outputGroup.startLineNo
diff --git a/tools/checker/match/line.py b/tools/checker/match/line.py
index 2097430..711d814 100644
--- a/tools/checker/match/line.py
+++ b/tools/checker/match/line.py
@@ -32,20 +32,16 @@
return splitExpressions
def matchWords(checkerWord, stringWord, variables, pos):
- """ Attempts to match a list of RegexExpressions against a string.
+ """ Attempts to match a list of RegexExpressions against a string.
Returns updated variable dictionary if successful and None otherwise.
"""
- # Create own copy of the variable dictionary, otherwise updates would change
- # the caller's state.
- variables = dict(variables)
-
for expression in checkerWord:
# If `expression` is a variable reference, replace it with the value.
if expression.variant == RegexExpression.Variant.VarRef:
if expression.name in variables:
pattern = re.escape(variables[expression.name])
else:
- Logger.testFailed("Multiple definitions of variable \"{}\"".format(expression.name),
+ Logger.testFailed("Multiple definitions of variable \"{}\"".format(expression.name),
pos.fileName, pos.lineNo)
else:
pattern = expression.pattern
@@ -59,9 +55,9 @@
# If `expression` was a variable definition, set the variable's value.
if expression.variant == RegexExpression.Variant.VarDef:
if expression.name not in variables:
- variables[expression.name] = stringWord[:match.end()]
+ variables = variables.copyWith(expression.name, stringWord[:match.end()])
else:
- Logger.testFailed("Multiple definitions of variable \"{}\"".format(expression.name),
+ Logger.testFailed("Multiple definitions of variable \"{}\"".format(expression.name),
pos.fileName, pos.lineNo)
# Move cursor by deleting the matched characters.
@@ -70,7 +66,7 @@
# Make sure the entire word matched, i.e. `stringWord` is empty.
if stringWord:
return None
-
+
return variables
def MatchLines(checkerLine, stringLine, variables):
diff --git a/tools/checker/match/test.py b/tools/checker/match/test.py
index 0215f50..97725ad 100644
--- a/tools/checker/match/test.py
+++ b/tools/checker/match/test.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.immutables import ImmutableDict
from common.testing import ToUnicode
from file_format.c1visualizer.parser import ParseC1visualizerStream
from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
@@ -33,7 +34,9 @@
return ParseCheckerAssertion(testCase, checkerString, TestAssertion.Variant.InOrder, 0)
def tryMatch(self, checkerString, c1String, varState={}):
- return MatchLines(self.createTestAssertion(checkerString), ToUnicode(c1String), varState)
+ return MatchLines(self.createTestAssertion(checkerString),
+ ToUnicode(c1String),
+ ImmutableDict(varState))
def matches(self, checkerString, c1String, varState={}):
return self.tryMatch(checkerString, c1String, varState) is not None