示例#1
0
def calc(memory, commands, browser=None):
    '''

    :param memory: quantities already defined, given as repr()s line by line in a string
    :param commands: string of user input specifying math operations to define new quantities
    :param mob: device the output will be sent to (determines format)
    :return: everything needed to show the result in a browser and keep state for the next calculation
    '''
    state = State(memory)
    #rint('commands', commands)
    command_list = commands.replace('\r', '').split("\n")
    try:
        for command in command_list:
            if not command:
                continue
            #rint ("command: %s" % command)
            input_type, name, expression = classify_input(command, state)
            if input_type == Calculation:
                name = check_name(name, state)
                quantity = interpret(expression, state)
                state.printwork(show_work(quantity, name, state.flags))
                register_result(quantity, name, state)
            elif input_type == Comment:
                create_comment(name, state)
            elif input_type == Unknown:
                create_unknown(name, state)
            elif input_type in [ConversionUsing, ConversionIn]:
                convert_units(input_type, command, name, expression, state)
            elif input_type == Flags:
                change_flag(state.flags, name, expression)
                # else: pass because command is empty
            state.log_input(command)
    except (CalcError, OverflowError, QuantError) as err:
        deal_with_errors(err, command, state)
    return state.export()
示例#2
0
def calc2(command, state=State(), keep_provenance=True):
    '''

    :param command:
    :param state:
    :return: type, name, q, expression = calc2(line, state)
    '''
    quantity = None
    name = None
    type_text = 'Calc'
    try:
        input_type, name, expression = classify_input(command, state)
        if input_type == Calculation:
            name = check_name(name, state)
            quantity = interpret(expression, state, keep_onthefly=True)
            if quantity.provenance and any(name == q.name
                                           for q in quantity.provenance):
                print('uhoh')
            if not quantity.provenance:
                if quantity.name and not name.startswith('M['):
                    quantity = Q(number=quantity.number,
                                 name='is %s',
                                 units=quantity.units,
                                 uncert=quantity.uncert,
                                 provenance=(quantity, ))
                else:
                    if quantity.units != Units():
                        state.popsnuck()
                    type_text = 'Known'
            if quantity.name == '-%s' and not quantity.provenance[0].provenance:
                type_text = 'Known'
            register_result(quantity.copy(),
                            name,
                            state,
                            keep_provenance=keep_provenance)
        elif input_type in [ConversionUsing, ConversionIn]:
            convert_units(input_type, command, name, expression, state)
            return 'Conversion', None, None, None
        elif input_type == Unknown:
            return 'Unknown', name, None, None
        else:
            return 'Comment', None, None, None
    except (CalcError, OverflowError, QuantError) as err:
        return 'Error', None, None, None
    return type_text, name, quantity, expression
示例#3
0
def classify_input(a, state):
    '''
    :param a: the user input string containing a calculation, unit conversion or comment
    :param state: contains known quantities as ordered dict, along with flags and output
    :return: a tuple (type of input, symbol name, expression/units)

    Empty, Calculation, ConversionIn, ConversionUsing, Comment, Flags, Unknown = range(7)

    >>> classify_input(' ', State())
    (0, None, None)
    >>> classify_input('K[A<=>B] = 13', State())
    (1, u'K[A<=>B]', u' 13')
    >>> classify_input('R using J', State())
    (3, u'R', u'J')
    >>> classify_input('K[A<=>B] in mM', State())
    (2, u'K[A<=>B]', u'mM')
    >>> classify_input('5 + 6', State())
    (1, u'result', u'5 + 6')
    >>> classify_input('#comment', State())
    (4, None, None)
    >>> classify_input('!H2O', State())
    (4, None, None)

    '''
    #rint('a', a)
    if not a or not a.strip():
        return Empty, None, None
    #rint('wahh', '\n'.join(state.output[-3:]))
    if '__newby__' in state.flags and not a.startswith('__'):
        state.printit('<pre style="color:maroon"><b>>>>> %s</b></pre>' %
                      a.replace('<', '&lt;').replace('>', '&gt;'))
    elif not a.startswith('__'):
        state.printit("<br>" if not '/h' in ''.join(state.output[-1:]) else '')
    state.logit("<br>" if not '</h' in ''.join(state.logput[-1:]) else '')

    if a[0] in "!#@":
        return Comment, a, None
    a = a.strip()
    if a.startswith('__'):
        if not '=' in a:
            return Comment, a, None
        name, onoff = a.split('=', 1)
        return Flags, name.strip(), onoff
    m = extract_identifier(a)
    start = m.end() if m else 0
    rest = a[start:].strip()
    if m and rest.startswith('='):
        r2 = rest[1:].strip()
        if not r2:
            return Empty, None, None
        if r2[0] == '?':
            return Unknown, a[:start], None
        scanned, remainder = scan_it(rest[1:])
        if remainder and remainder.startswith('='):
            return Comment, a, None
        return Calculation, a[:start], rest[1:]  # Calculation #
    elif m and a[start:].startswith(' using') and a[:start].strip() in state:
        return ConversionUsing, a[:start], rest[len('using'):].strip()
    elif m and a[start:].startswith(' in ') and a[:start].strip() in state:
        return ConversionIn, a[:start], rest[len('in'):].strip()
    if '__newby__' in state.flags:
        raise CalcError(
            "Your tutor says: Please come up with a name for the quantity you are calculating"
        )
    try:
        interpret(a, state, warning=False)
        return Calculation, "result", a
    except:
        return Comment, a, None
示例#4
0
def convert_units(input_type, command, quant, units, state):
    """
    Shows the quantity in different units, either once only ('in') or from now on ('using')

    :param input_type: Whether conversion is with "using" or "in"
    :param command: user input
    :param quant: Q() to be converted
    :param units: requested units
    :param state: contains known quantities as ordered dict, along with flags and output
    :raise CalcError: if requested units are unknown
    """
    flags2 = set(i for i in state.flags if i != '__hideunits__')
    if input_type == ConversionUsing:
        print(repr(state[quant.strip()]))
        if units in ['°ΔC', '°aC']:
            prefu = [units]
            q = state[quant.strip()] + Q(0.0)
            if units == '°aC' and unitquant['K'].units != q.units:
                raise CalcError(
                    "Only quantities in kelvin may be converted to celsius")
        else:
            prefu = units.split()
            for p in prefu:
                if p not in unitquant:
                    raise CalcError(
                        "PQCalc does not recognize the unit '%s', so 'using' does not work. Try 'in' instead."
                        % p)
            try:
                q = state[quant.strip()] + Q(0.0)
            except KeyError:
                raise CalcError(
                    "The quantity '%s' is not defined yet. Check for typos." %
                    quant.strip())
        q.name = ''
        q.provenance = None
        q.comment = ''
        outp, _ = show_work(q, quant, flags2)
        output = (outp[:-1])
        state[quant.strip()].prefu = set(prefu)
        q_old = state[quant.strip()]
        if q_old.provenance:  # when called by calc2
            q_old.name = ''
            q_old.provenance = None
        q = q_old + Q(0.0)
        q.comment = ''
        outp, _ = show_work(q, quant, flags2)
        output.extend(outp[-2 if not 'plain math' in state.flags else -1:])
        q = state[quant.strip()] + Q(0.0)
        q.name = ""
        q.provenance = None
        _, logp = show_work(q, quant, flags2)
        state.printit('\n'.join(output))
        state.logit('\n'.join(logp))
        print(repr(state[quant.strip()]))
    else:
        tmp = interpret(units, state, warning=False)
        try:
            qq = state[quant.strip()] / tmp
        except KeyError:
            raise CalcError(
                "The quantity '%s' is not defined yet. Check for typos." %
                quant.strip())
        addon = (
            "\mathrm{\ %s}" %
            quantities.latex_name(units)) if state.mob != "ipud" else units
        work = show_work(qq, quant, flags2, addon=addon)
        state.printwork(work)
示例#5
0
def extract_knowns(text):
    """
    Generator for numerical data in a text
    :param text: The question text
    :yield: Commands to set numerical data
    """
    text = text.replace('<em>M</em>', 'M').\
        replace('×10', ' × 10').replace('−', '-').replace('<sup>', '^').\
        replace('10^', '10 ').replace('</sup>', '')
    while text:
        tokens, remainder = scan_it(text)
        if remainder and remainder.startswith('='):
            text = remainder[1:].strip()
        else:
            text = remainder
        ltokens = [[t[0], t[1]] for t in tokens]
        #rint(ltokens)
        for j, t in enumerate(ltokens):  #first pass to find units
            if t[0] == 'I':
                punit = t[1]
                while punit and punit[-1] in '),.?':
                    punit = punit[:-1]
                if punit in unitquant:
                    ltokens[j][0] = 'U'
                    ltokens[j][1] = punit
                elif punit == '°C' or punit == '°aC':
                    ltokens[j][0] = 'U'
                    ltokens[j][1] = '°aC'
        shift = 0
        for j, t in enumerate(ltokens):  #second pass to find number + unit
            if shift:
                shift -= 1
                continue
            if t[0] != 'N':
                continue
            known = t[1] + (' ' if '.' in t[1] else '. ')
            if len(ltokens) > j + 7 and ltokens[j + 1:j + 5] == [[
                    'O', ''
            ], ['I', '×'], ['O', ''], ['N', '10']]:
                known = known[:-1] + 'E' + ltokens[j + 5][1]
                if ltokens[j + 6][0] == 'N':
                    known += ltokens[j + 6][1]
                shift = 6
            units = False
            for j2, t2 in enumerate(ltokens[j + 1 + shift:]):
                if t2[0] in 'OU' and t2[1] != ',':
                    known = known + t2[1]
                    units = True
                elif known.endswith('-') and t2[0] == 'N' and len(
                        t2[1]) == 1 and t2[1] in '123':
                    known = known + t2[1]
                    shift += j2 + 1
                else:
                    break
            if units:
                while known and known[-1] in '),.?':
                    known = known[:-1]
                try:
                    q = interpret(known, BareState())
                    if q.units in typical_units_reverse:
                        yield (typical_units_reverse[q.units] + ' = ' + known)
                    else:
                        yield ('Q1 = ' + known)
                except:
                    yield ('Q2 = ' + known)
            else:
                yield ('N = ' + known)