def parseUnit(units, conversions, tokens, overwrite=False): """ Convert the next series of tokens into a unit @param units: a map of unit symbols to unit objects to be modified @param conversions: a map of unit symbols to scale factors to be modified @param tokens: a list of tokens """ baseUnitMap = {} # Handle base unit sym = UC_Utils.parseSymbol(tokens) if not overwrite and (sym in units): raise UC_Common.FileFormatError( f"Duplicate definition of unit '{sym}'") # Handle derived unit nextToken = UC_Utils.getNextToken(tokens) if nextToken == UC_Common.MAP_DELIMITER: scaleFactor = UC_Utils.parseFloat(tokens) UC_Utils.getNextToken(tokens, UC_Common.SEP_DELIMITER) baseUnitMap = parseBaseUnitMap(tokens) conversions[sym] = scaleFactor # Handle other tokens elif nextToken != UC_Common.END_DELIMITER: raise UC_Common.FileFormatError( f"Expected delimiter; received '{nextToken}'") # Create unit units[sym] = UC_Unit.Unit(sym, baseUnitMap)
def parseBaseUnitMap(tokens): """ Convert the next series of tokens into a map of units to exponents @param tokens: a list of tokens @return pairs of units and their corresponding exponents """ baseUnitMap = {} baseSym = UC_Utils.parseSymbol(tokens) if baseSym not in baseUnitMap: baseUnitMap[baseSym] = 0 baseUnitMap[baseSym] += UC_Utils.parseInt(tokens) while UC_Utils.peekNextToken(tokens) == UC_Common.SEP_DELIMITER: UC_Utils.getNextToken(tokens) baseSym = UC_Utils.parseSymbol(tokens) if baseSym not in baseUnitMap: baseUnitMap[baseSym] = 0 baseUnitMap[baseSym] += UC_Utils.parseInt(tokens) UC_Utils.getNextToken(tokens, UC_Common.END_DELIMITER) return baseUnitMap
def parsePrefixMapping(prefixes, tokens, base, overwrite=False): """ Convert the next series of tokens into a prefix-exponent pair @param prefixes: the prefix-exponent map to modify @param tokens: a list of tokens @param base: the base for the exponent """ prefix = UC_Utils.getNextToken(tokens) if not overwrite and (prefix in prefixes): raise UC_Common.FileFormatError( f"Duplicate definition of prefix '{prefix}'") prefixes[prefix] = (base, Decimal(UC_Utils.parseInt(tokens)))
def parsePrefix(prefixes, tokens, overwrite=False): """ Convert the next series of tokens into prefix-exponent pairs @param prefixes: the prefix-exponent map to modify @param tokens: a list of tokens """ base = UC_Utils.parseFloat(tokens) UC_Utils.getNextToken(tokens, UC_Common.MAP_DELIMITER) parsePrefixMapping(prefixes, tokens, base, overwrite) while UC_Utils.peekNextToken(tokens) == UC_Common.SEP_DELIMITER: UC_Utils.getNextToken(tokens) parsePrefixMapping(prefixes, tokens, base, overwrite) UC_Utils.getNextToken(tokens, UC_Common.END_DELIMITER)
def test_parser(verbose=False): test_result = 0 # Test token queue tokens = ["A1", "B2", "C3"] if UC_Utils.getNextToken(tokens) != "A1": test_result += test_fail("Failed to get expected symbol", verbose) if UC_Utils.getNextToken(tokens) != "B2": test_result += test_fail("Failed to get expected symbol", verbose) if UC_Utils.getNextToken(tokens) != "C3": test_result += test_fail("Failed to get expected symbol", verbose) try: token = UC_Utils.getNextToken(tokens) test_result += test_fail(f"Received unexpected token {token}", verbose) except: pass # Test the parsing of basic datatypes res = UC_Utils.parseInt(["2"]) if res != Decimal("2"): test_result += test_fail("Incorrectly parsed int", verbose) res = UC_Utils.parseFloat(["2.7"]) if res != Decimal("2.7"): test_result += test_fail("Incorrectly parsed float", verbose) res = UC_Utils.parseSymbol(["sym"]) if res != "sym": test_result += test_fail("Incorrectly parsed symbol", verbose) # Test the parsing of unit dependencies baseUnitMap = UC_FileParser.parseBaseUnitMap( ["A", "1", ",", "B", "2", ";"]) if baseUnitMap["A"] != 1: test_result += test_fail("Incorrectly parsed base unit map", verbose) if baseUnitMap["B"] != 2: test_result += test_fail("Incorrectly parsed base unit map", verbose) if len(baseUnitMap) != 2: test_result += test_fail("Incorrectly parsed base unit map", verbose) try: baseUnitMap = UC_FileParser.parseBaseUnitMap( ["A", "1", ",", "B", "2", "C"]) test_result += test_fail( "Should fail to parse an incorrectly formatted dependency string", verbose) except: pass # Test the parsing of a base unit units = {} conversions = {} UC_FileParser.parseUnit(units, conversions, ["A", ";"]) if (("A" not in units) or ("A" in conversions) or (len(units) != 1)): test_result += test_fail("Incorrectly parsed base unit", verbose) try: UC_FileParser.parseUnit(["A", "B"]) test_result += test_fail( "Should fail to parse an incorrectly formatted base unit string", verbose) except: pass # Test the parsing of a derived unit UC_FileParser.parseUnit( units, conversions, ["H", ":", "12.4", ",", "A", "1", ",", "B", "2", ";"]) if (("H" not in units) or (units["H"].baseUnits["A"] != 1) or (units["H"].baseUnits["B"] != 2) or (len(units["H"].baseUnits) != 2) or (conversions["H"] != Decimal("12.4")) or (len(units) != 2)): test_result += test_fail("Incorrectly parsed derived unit", verbose) try: UC_FileParser.parseUnit( units, conversions, ["H", ":", "12.4", ",", "A", "1", ",", "B", "2", "C"]) test_result += test_fail( "Should fail to parse an incorrectly formatted derived unit string", verbose) except: pass try: UC_FileParser.parseUnit(units, conversions, ["H", ":", "12.4", ";"]) test_result += test_fail( "Should fail to parse an incorrectly formatted derived unit string", verbose) except: pass # Test the parsing of a prefix map prefixes = {} UC_FileParser.parsePrefix(prefixes, ["10", ":", "k", "3", ",", "M", "6", ";"]) if ((len(prefixes) != 2) or (prefixes["k"] != (10, 3)) or (prefixes["M"] != (10, 6))): test_result += test_fail("Incorrectly parsed prefix map", verbose) try: UC_FileParser.parsePrefix(prefixes, ["10", ":", "k", "3", ",", "M", "6", "C"]) test_result += test_fail( "Should fail to parse an incorrectly formatted prefix map string", verbose) except: pass try: UC_FileParser.parsePrefix(prefixes, ["10", ":", ";"]) test_result += test_fail( "Should fail to parse an incorrectly formatted prefix map string", verbose) except: pass return test_result
def aggregateUnits(tokens): """ Combine tokens which constitute compound units @param tokens: a list of tokens """ aggregatedTokens = [] unitTokens = [] parsingExp = 0 def appendUnitTokens(aggregatedTokens, unitTokens, token=None): # Append unit tokens to list of aggregated tokens if unitTokens: if unitTokens[-1] == UC_Common.OPERATOR_MUL or unitTokens[ -1] == UC_Common.OPERATOR_DIV: operator = unitTokens.pop() aggregatedTokens.append(unitTokens) aggregatedTokens.append(operator) else: aggregatedTokens.append(unitTokens) if token is not None: # Inject multiplication if needed if ((aggregatedTokens) and (not UC_Utils.isSpecialChar(aggregatedTokens[-1])) and (token == UC_Common.BRACKET_OPEN)): aggregatedTokens.append(UC_Common.OPERATOR_MUL) aggregatedTokens.append(token) return [] def handleParseExpDecrement(tokens, unitTokens, parsingExp): # Check if multiplication needs to be injected between adjacent units if parsingExp != 1: return parsingExp if tokens: if tokens[0] == UC_Common.OPERATOR_MUL or tokens[ 0] == UC_Common.OPERATOR_DIV: unitTokens.append(tokens.pop(0)) elif UC_Utils.isValidSymbol(tokens[0]): unitTokens.append(UC_Common.OPERATOR_MUL) return 0 def handleAppendUnitSymbol(tokens, unitTokens, parsingExp): if tokens: token = tokens[0] if token == UC_Common.OPERATOR_EXP: unitTokens.append(tokens.pop(0)) return 1 elif token == UC_Common.OPERATOR_MUL or token == UC_Common.OPERATOR_DIV: unitTokens.append(tokens.pop(0)) elif UC_Utils.isValidSymbol(token): unitTokens.append(UC_Common.OPERATOR_MUL) return parsingExp while tokens: token = UC_Utils.getNextToken(tokens) if token == UC_Common.BRACKET_OPEN: if parsingExp: unitTokens.append(token) parsingExp += 1 else: unitTokens = appendUnitTokens(aggregatedTokens, unitTokens, token) elif token == UC_Common.BRACKET_SHUT: if parsingExp: unitTokens.append(token) parsingExp = handleParseExpDecrement(tokens, unitTokens, parsingExp - 1) else: unitTokens = appendUnitTokens(aggregatedTokens, unitTokens, token) elif UC_Utils.isFloat(token): if parsingExp: if not UC_Utils.isInt(token): raise UC_Common.UnitError( f"Expected int; received '{token}'") unitTokens.append(token) parsingExp = handleParseExpDecrement(tokens, unitTokens, parsingExp) else: unitTokens = appendUnitTokens(aggregatedTokens, unitTokens, token) elif UC_Utils.isValidSymbol(token): if parsingExp: raise UC_Common.UnitError(f"Expected int; received '{token}'") unitTokens.append(token) parsingExp = handleAppendUnitSymbol(tokens, unitTokens, parsingExp) elif UC_Utils.isOperator(token): if parsingExp: raise UC_Common.UnitError(f"Expected int; received '{token}'") else: unitTokens = appendUnitTokens(aggregatedTokens, unitTokens, token) else: raise UC_Common.UnitError(f"Unknown token; received '{token}'") appendUnitTokens(aggregatedTokens, unitTokens) return aggregatedTokens