python: Refactor toNum to support a selection of units
authorAndreas Sandberg <andreas.sandberg@arm.com>
Mon, 18 Jan 2021 19:04:39 +0000 (19:04 +0000)
committerAndreas Sandberg <andreas.sandberg@arm.com>
Mon, 25 Jan 2021 15:04:48 +0000 (15:04 +0000)
Add support for matching one of several different units in toNum. The
units parameter can now either be a tuple or a string describing the
supported unit(s). The function now returns a (magnitude, unit) tuple.

Change-Id: I683819722a93ade91a6def2bfa77209c29b4b39e
Signed-off-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/39217
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Gabe Black <gabe.black@gmail.com>
Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br>
src/python/m5/util/convert.py

index ff452cdf71ff19015af040332a3970f09cc659ca..8f70d01a7fdb5377994370b8289b3bb2a636f5d1 100644 (file)
@@ -1,3 +1,15 @@
+# Copyright (c) 2021 Arm Limited
+# All rights reserved.
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder.  You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
 # Copyright (c) 2005 The Regents of The University of Michigan
 # Copyright (c) 2010 Advanced Micro Devices, Inc.
 # All rights reserved.
@@ -88,9 +100,40 @@ def assertStr(value):
     if not isinstance(value, str):
         raise TypeError("wrong type '%s' should be str" % type(value))
 
+def _split_suffix(value, suffixes):
+    '''Split a string based on a suffix from a list of suffixes.
+
+    :param value: String value to test for a matching suffix.
+    :param suffixes: Container of suffixes to test.
+
+    :returns: A tuple of (value, suffix). Suffix is the empty string
+              if there is no match.
+
+    '''
+    matches = [ sfx for sfx in suffixes if value.endswith(sfx) ]
+    assert len(matches) <= 1
+
+    return (value[:-len(matches[0])], matches[0]) if matches \
+        else (value, '')
+
 
-# memory size configuration stuff
 def toNum(value, target_type, units, prefixes, converter):
+    '''Convert a string using units and prefixes to (typically) a float or
+    integer.
+
+    String values are assumed to either be a naked magnitude without a
+    unit or prefix, or a magnitude with a unit and an optional prefix.
+
+    :param value: String value to convert.
+    :param target_type: Type name for error messages.
+    :param units: Unit (string) or list of valid units.
+    :param prefixes: Mapping of prefixes to multipliers.
+    :param converter: Helper function to convert magnitude to native
+                      type.
+
+    :returns: Tuple of (converted value, unit)
+
+    '''
     assertStr(value)
 
     def convert(val):
@@ -100,22 +143,28 @@ def toNum(value, target_type, units, prefixes, converter):
             raise ValueError(
                 "cannot convert '%s' to %s" % (value, target_type))
 
-    if units and not value.endswith(units):
-        units = None
+    # Units can be None, the empty string, or a list/tuple. Convert
+    # to a tuple for consistent handling.
     if not units:
-        return convert(value)
+        units = tuple()
+    elif isinstance(units, str):
+        units = (units,)
+    else:
+        units = tuple(units)
 
-    value = value[:-len(units)]
+    magnitude_prefix, unit = _split_suffix(value, units)
 
-    prefix = next((p for p in prefixes.keys() if value.endswith(p)), None)
-    if not prefix:
-        return convert(value)
-    value = value[:-len(prefix)]
+    # We only allow a prefix if there is a unit
+    if unit:
+        magnitude, prefix = _split_suffix(magnitude_prefix, prefixes)
+        scale = prefixes[prefix] if prefix else 1
+    else:
+        magnitude, prefix, scale = magnitude_prefix, '', 1
 
-    return convert(value) * prefixes[prefix]
+    return convert(magnitude) * scale, unit
 
 def toFloat(value, target_type='float', units=None, prefixes=[]):
-    return toNum(value, target_type, units, prefixes, float)
+    return toNum(value, target_type, units, prefixes, float)[0]
 
 def toMetricFloat(value, target_type='float', units=None):
     return toFloat(value, target_type, units, metric_prefixes)
@@ -124,8 +173,8 @@ def toBinaryFloat(value, target_type='float', units=None):
     return toFloat(value, target_type, units, binary_prefixes)
 
 def toInteger(value, target_type='integer', units=None, prefixes=[]):
-    intifier = lambda x: int(x, 0)
-    return toNum(value, target_type, units, prefixes, intifier)
+    return toNum(value, target_type, units, prefixes,
+                 lambda x: int(x, 0))[0]
 
 def toMetricInteger(value, target_type='integer', units=None):
     return toInteger(value, target_type, units, metric_prefixes)