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()
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
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('<', '<').replace('>', '>')) 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
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)
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)