def _xlat_f2a_Subs(expr=None, src=None, dst=None): def parse_subs(src, dst): if src is None: return ((AST.VarNull, AST.VarNull), ) src = src.strip_paren.comma if src.strip_paren.is_comma else ( src, ) # (src.strip_paren,) if dst is None: return tuple(it.zip_longest(src, (), fillvalue=AST.VarNull)) dst = dst.strip_paren.comma if dst.strip_paren.is_comma else ( dst, ) # (dst.stip_paren,) if len(dst) > len(src): return None return tuple(it.zip_longest(src, dst, fillvalue=AST.VarNull)) # start here if expr is None: return AST('-subs', AST.VarNull, ((AST.VarNull, AST.VarNull), )) subs = parse_subs(src, dst) if subs is None: return None if expr.is_subs: return AST('-subs', expr.expr, expr.subs + subs) else: return AST('-subs', expr, subs)
def set_vars (vars): nvars = {} for v, a in vars.items (): v = v.var if a.is_ufunc: if v in sparser.RESERVED_FUNCS: raise NameError (f'cannot assign undefined function to concrete function name {v!r}') if a.is_ufunc_anonymous: a = AST (a.op, v, *a [2:]) elif a.is_sym_anonymous: if a.is_sym_unqualified: raise CircularReferenceError ('cannot asign unqualified anonymous symbol') a = AST (a.op, v, *a [2:]) nvars [v] = a try: # check for circular references AST.apply_vars (AST (',', tuple (('@', v) for v in nvars)), {**_VARS, **nvars}) except RecursionError: raise CircularReferenceError ("I'm sorry, Dave. I'm afraid I can't do that.") from None _VARS.update (nvars) return list (nvars.items ())
def _xlat_f2a_SymmetricDifference(*args): if len(args) != 2: return None if not args[0].is_sdiff: return AST('^^', args) return AST('^^', args[0].sdiff + (args[1], ))
def _present_vars (vars): asts = [] for v, e in vars: if v != '_': if e.is_lamb: asts.append (AST ('=', ('-ufunc', v, tuple (('@', vv) for vv in e.vars)), e.lamb)) else: asts.append (AST ('=', ('@', v), e)) return asts
def _xlat_f2a_slice(*args): if len(args) == 1: return AST('-slice', False, False if args[0] == AST.None_ else args[0], None) if len(args) == 2: return AST('-slice', False if args[0] == AST.None_ else args[0], False if args[1] == AST.None_ else args[1], None) else: return AST( '-slice', False if args[0] == AST.None_ else args[0], False if args[1] == AST.None_ else args[1], args[2] if args[2] != AST.None_ else None if args[1] == AST.None_ else False)
def _xlat_f2a_Sum(ast=AST.VarNull, ab=None, **kw): if ab is None: return AST('-sum', ast, AST.VarNull, AST.VarNull, AST.VarNull) ab = ab.strip_paren if ab.is_var: return AST('-sum', ast, ab, AST.VarNull, AST.VarNull) elif ab.is_comma and ab.comma and ab.comma.len <= 3 and ab.comma[0].is_var: return AST('-sum', ast, *ab.comma, *((AST.VarNull, ) * (3 - ab.comma.len))) return None
def xlat_funcs2asts( ast, xlat, func_call=None, recurse=True ): # translate eligible functions in tree to other AST representations if not isinstance(ast, AST): return ast if ast.is_func: xact = xlat.get(ast.func) args = ast.args ret = lambda: AST('-func', ast.func, args) elif ast.is_attr_func: xact = xlat.get(f'.{ast.attr}') args = (ast.obj, ) + ast.args ret = lambda: AST('.', args[0], ast.attr, tuple(args[1:])) else: xact = None if xact is not None: if recurse: args = AST(*(xlat_funcs2asts(a, xlat, func_call=func_call) for a in args)) try: if xact is True: # True means execute function and use return value for ast, only happens for -func return func_call( ast.func, args ) # not checking func_call None because that should never happen xargs, xkw = AST.args2kwargs(args) ast2 = xact(*xargs, **xkw) if ast2 is not None: return ast2 except: pass return ret() if recurse: return AST(*(xlat_funcs2asts(a, xlat, func_call=func_call) for a in ast)) #, **ast._kw) return ast
def _vars_updated(): global _VARS_FLAT vars = { v: a if a.is_lamb else AST.apply_vars(a, _VARS, mode=False) for v, a in _VARS.items() } # flattened vars so sym and sparser don't need to do apply_vars() one = (f for f in filter(lambda f: _ENV.get(f), _ONE_FUNCS) ) # hidden functions for stuff like Gamma lamb = (va[0] for va in filter(lambda va: va[1].is_lamb, vars.items()) ) # user lambda functions assfunc = (va[0] for va in filter( lambda va: va[1].is_var and va[1].var in AST.Func.PYBASE, vars.items()) ) # user variables assigned to concrete functions funcs = {*one, *lamb, *assfunc} sym.set_sym_user_vars(vars) sym.set_sym_user_funcs(funcs) sparser.set_sp_user_vars(vars) sparser.set_sp_user_funcs(funcs) _UFUNC_MAP.clear() _SYM_MAP.clear() _SYM_VARS.clear() _VARS_FLAT = vars for v, a in vars.items(): # build ufunc and sym mapback dict if v != '_': if a.is_ufunc: _UFUNC_MAP.setdefault(a, set()).add(v) elif a.is_sym: _SYM_MAP.setdefault(a, set()).add(v) _SYM_VARS.add(v)
def _xlat_f2a_Limit(ast=AST.VarNull, var=AST.VarNull, to=AST.VarNull, dir=_AST_StrPlus): if var.is_var_nonconst: return AST('-lim', ast, var, to, *_xlat_f2a_Limit_dirs[dir]) return None
def _xlat_f2a_Integral(ast=None, dvab=None, *args, **kw): if ast is None: return AST('-intg', AST.VarNull, AST.VarNull) if dvab is None: vars = ast.free_vars if len(vars) == 1: return AST('-intg', ast, ('@', f'd{vars.pop ().var}')) return AST('-intg', ast, AST.VarNull) dvab = dvab.strip_paren ast2 = None if dvab.is_comma: if dvab.comma and dvab.comma[0].is_var: #_nonconst: if dvab.comma.len == 1: ast2 = AST('-intg', ast, ('@', f'd{dvab.comma [0].var}')) elif dvab.comma.len == 2: ast2 = AST('-intg', ast, ('@', f'd{dvab.comma [0].var}'), AST.Zero, dvab.comma[1]) elif dvab.comma.len == 3: ast2 = AST('-intg', ast, ('@', f'd{dvab.comma [0].var}'), dvab.comma[1], dvab.comma[2]) elif dvab.is_var: ast2 = AST('-intg', ast, ('@', f'd{dvab.var}')) if ast2 is None: return None return _xlat_f2a_Integral(ast2, *args) if args else ast2
def _prepare_ass(ast): # check and prepare for simple or tuple assignment if not ast.ass_valid: vars = None elif ast.ass_valid.error: raise RealityRedefinitionError(ast.ass_valid.error) else: vars, ast = ast.ass_valid.lhs, ast.ass_valid.rhs vars = list(vars.comma) if vars.is_comma else [vars] return AST.apply_vars(ast, _VARS_FLAT), vars
def xlat_func2tex(ast, ast2tex): xact = _XLAT_FUNC2TEX.get(ast.func) if xact: args, kw = AST.args2kwargs(ast.args) try: return xact(ast2tex, *args, **kw) except: pass return None
def _xlat_f2a_Lambda(args, expr): args = args.strip_paren args = args.comma if args.is_comma else (args, ) vars = [] for v in args: if not v.is_var_nonconst: return None vars.append(v.var) return AST('-lamb', expr, tuple(vars))
def xlat_attr2tex(ast, ast2tex): if ast.is_attr_func: xact = _XLAT_ATTRFUNC2TEX.get(ast.attr) if xact: args, kw = AST.args2kwargs(ast.args) try: return xact(ast2tex, ast.obj, *args, **kw) except: pass return None
def _xlat_f2a_Matrix(ast=AST.VarNull): if ast.is_var_null: return AST.MatEmpty if ast.is_brack: if not ast.brack: return AST.MatEmpty elif not ast.brack[ 0].is_brack: # single layer or brackets, column matrix? return AST('-mat', tuple((c, ) for c in ast.brack)) elif ast.brack[0].brack: rows = [ast.brack[0].brack] cols = len(rows[0]) for row in ast.brack[1:-1]: if row.brack.len != cols: break rows.append(row.brack) else: l = ast.brack[-1].brack.len if l <= cols: if ast.brack.len > 1: rows.append(ast.brack[-1].brack + (AST.VarNull, ) * (cols - l)) if l != cols: return AST('-mat', tuple(rows)) elif cols > 1: return AST('-mat', tuple(rows)) else: return AST('-mat', tuple((r[0], ) for r in rows)) return None
def _xlat_f2a_Piecewise(*args): pcs = [] if not args or args[0].is_var_null: return AST('-piece', ((AST.VarNull, AST.VarNull), )) if len(args) > 1: for c in args[:-1]: c = c.strip if not c.is_comma or c.comma.len != 2: return None pcs.append(c.comma) ast = args[-1] if not ast.is_paren: return None ast = ast.strip pcs = tuple(pcs) if not ast.is_comma: return AST('-piece', pcs + ((ast, AST.VarNull), )) elif ast.comma.len == 0: return AST('-piece', pcs + ()) if not ast.comma[0].is_comma: if ast.comma.len == 1: return AST('-piece', pcs + ((ast.comma[0], AST.VarNull), )) elif ast.comma.len == 2: return AST( '-piece', pcs + ((ast.comma[0], True if ast.comma[1] == AST.True_ else ast.comma[1]), )) return None
def _xlat_f2a_Derivative(ast=AST.VarNull, *dvs, **kw): ds = [] if not dvs: if ast.is_diffp: return AST('-diffp', ast.diffp, ast.count + 1) else: return AST('-diffp', ast, 1) else: dvs = list(dvs[::-1]) while dvs: v = dvs.pop() if not v.is_var: return None ds.append( (v.var, dvs.pop().as_int if dvs and dvs[-1].is_num_pos_int else 1)) return AST('-diff', ast, 'd', tuple(ds))
def _xlat_f2a_subs(expr, src=AST.VarNull, dst=None): def parse_subs(src, dst): if dst is not None: return ((src, dst), ) src = src.strip_paren if src.is_dict: return src.dict elif src.op not in {',', '[', '-set'}: return None # ((src, AST.VarNull),) else: subs = [] for arg in src[1]: ast = arg.strip_paren if ast.op in {',', '['} and ast[1].len <= 2: subs.append((ast[1] + (AST.VarNull, AST.VarNull))[:2]) elif arg.is_paren and arg is src[1][-1]: subs.append((ast, AST.VarNull)) else: return None return tuple(subs) # start here subs = parse_subs(src, dst) if subs is None: return None if expr.is_subs: # collapse multiple subs into one return AST('-subs', expr.expr, expr.subs + subs) return AST('-subs', expr, subs)
def _mapback( ast, assvar=None, exclude=set() ): # map back ufuncs and symbols to the variables they are assigned to if possible if not isinstance(ast, AST): return ast if ast.is_var: if ast.var not in _SYM_VARS: return ast if ast.var == assvar: raise CircularReferenceError( 'trying to assign unqualified symbol to variable of the same name' ) return AST('-sym', ast.var) if ast.is_sym: vars = _SYM_MAP.get(ast) if not vars: return ast if ast.sym in vars: return AST('@', ast.sym) return AST('@', next(iter(vars))) if _UFUNC_MAPBACK: if ast.is_ass and ast.lhs.is_ufunc: return AST('=', ast.lhs, _mapback(ast.rhs, assvar, exclude)) elif not ast.is_ufunc: return AST(*(_mapback(a, assvar, exclude) for a in ast)) vars = _UFUNC_MAP.get(ast) if vars: # prevent mapping to self on assignment if ast.ufunc in vars and ast.ufunc not in exclude: return AST('@', ast.ufunc) for var in vars: if var not in exclude: return AST('@', var) return AST(*(_mapback(a, assvar, exclude) for a in ast))
def invert(ast): cmp = [] lhs = ast.lhs for c in ast.cmp: v = _xlat_f2a_Add_invert.get(c[0]) if v is None: return None cmp.append((v, lhs)) lhs = c[1] return AST('<>', lhs, tuple(cmp[::-1]))
def validate (ast): # validate ast rules have not been broken by garbling functions if not isinstance (ast, AST): return ast if ast.is_var: if ast.var in sparser.RESERVED_ALL or ast.var_name.startswith ('_'): return AST ('@', 'C') if ast.is_func: # the slice function is evil if ast.func == 'slice' and ast.args.len == 2 and ast.args [0] == AST.None_: # :x gets written as slice(x) but may come from slice(None, x) ast = AST ('-slice', AST.None_, ast.args [1], None) elif ast.func in _FORBIDDEN_SXLAT_FUNCS: # random spaces can create forbidden functions ast = AST ('-func', 'print', *ast [2:]) elif ast.is_diff: # reserved words can make it into diff via dif or partialelse if any (v [0] in sparser.RESERVED_ALL for v in ast.dvs): return AST ('@', 'C') elif ast.is_intg: # same if ast.dv.as_var.var in sparser.RESERVED_ALL: return AST ('@', 'C') elif ast.is_slice: # the slice object is evil if ast.start == AST.None_ or ast.stop == AST.None_ or ast.step == AST.None_: raise ValueError ('malformed slice') # ast = AST ('-slice', ast.start, ast.stop, None) elif ast.is_ufunc: # remove spaces inserted into ufunc name if ' ' in ast.ufunc: ast = AST ('-ufunc', ast.ufunc_full.replace (' ', ''), ast.vars, ast.kw) elif ast.is_subs: if ast.expr.is_comma: ast = AST ('-subs', ('(', ast.expr), ast.subs) elif ast.is_sym: # remove spaces inserted into ufunc name if ' ' in ast.sym: ast = AST ('-sym', ast.sym.replace (' ', ''), ast.kw) return AST (*(validate (a) for a in ast))
def _xlat_pyS(ast, need=False): # Python S(1)/2 escaping where necessary if not isinstance(ast, AST): return ast, False if ast.is_num: if need: return AST('-func', 'S', (ast, )), True else: return ast, False if ast.is_comma or ast.is_brack: return AST(ast.op, tuple(_xlat_pyS(a)[0] for a in ast[1])), False if ast.is_curly or ast.is_paren or ast.is_minus: expr, has = _xlat_pyS(ast[1], need) return AST(ast.op, expr), has if ast.is_add or ast.is_mul: es = [_xlat_pyS(a) for a in ast[1][1:]] has = any(e[1] for e in es) e0 = _xlat_pyS(ast[1][0], need and not has) es = (e0[0], ) + tuple(e[0] for e in es) return (AST('+', es) if ast.is_add else AST('*', es, ast.exp)), has or e0[1] if ast.is_div: denom, has = _xlat_pyS(ast.denom) numer = _xlat_pyS(ast.numer, not has)[0] return AST('/', numer, denom), True if ast.is_pow: exp, has = _xlat_pyS(ast.exp) base = _xlat_pyS(ast.base, not (has or exp.is_num_pos))[0] return AST('^', base, exp), True es = [_xlat_pyS(a) for a in ast] return AST (*tuple (e [0] for e in es)), \ ast.op in {'=', '<>', '@', '.', '|', '!', '-log', '-sqrt', '-func', '-lim', '-sum', '-diff', '-intg', '-mat', '-piece', '-lamb', '||', '^^', '&&', '-or', '-and', '-not', '-ufunc', '-subs'} or any (e [1] for e in es)
def start_server(logging=True): if not logging: Handler.log_message = lambda *args, **kwargs: None if ('--ugly', '') in __OPTS or ('-u', '') in __OPTS: _DISPLAYSTYLE[0] = 0 for opt, _ in __OPTS: opt = opt.lstrip('-') if opt in _ENV_OPTS_ALL: _admin_env(AST('@', opt)) _START_ENV.update(_ENV) _vars_updated() if not __ARGV: host, port = _DEFAULT_ADDRESS else: host, port = (re.split( r'(?<=\]):' if __ARGV[0].startswith('[') else ':', __ARGV[0]) + [_DEFAULT_ADDRESS[1]])[:2] host, port = host.strip('[]'), int(port) try: httpd = HTTPServer((host, port), Handler) thread = threading.Thread(target=httpd.serve_forever, daemon=True) thread.start() return httpd except OSError as e: if e.errno != 98: raise print( f'Port {port} seems to be in use, try specifying different port as a command line parameter, e.g. localhost:9001' ) sys.exit(-1)
def sanitize (ast): # prune or reformat information not encoded same across different representations and asts which are not possible from parsing if not isinstance (ast, AST): return ast # elif ast.is_ass: # return AST ('<>', sanitize (AST ('(', ast.lhs) if ast.lhs.is_comma else ast.lhs), (('==', sanitize (AST ('(', ast.rhs) if ast.rhs.is_comma else ast.rhs)),)) elif ast.is_minus: if ast.minus.is_num_pos: return AST ('#', f'-{ast.minus.num}') elif ast.is_paren: if not ast.paren.is_comma: return sanitize (ast.paren) elif ast.is_mul: return AST ('*', tuple (sanitize (a) for a in ast.mul)) elif ast.is_log: return AST ('-log', sanitize (ast.log)) elif ast.is_sqrt: return AST ('-sqrt', sanitize (ast.rad)) elif ast.is_func: if ast.func == 'And': args = sanitize (ast.args) ast2 = sxlat._xlat_f2a_And (*args, force = True) if ast2 is not None: ast = ast2 else: return AST ('-and', args) elif ast.is_sum: if ast.from_.is_comma: return AST ('-sum', sanitize (ast.sum), ast.svar, sanitize (AST ('(', ast.from_) if ast.from_.is_comma else ast.from_), ast.to) elif ast.is_diff: if len (set (dv [0] for dv in ast.dvs)) == 1 and ast.is_diff_partial: return AST ('-diff', sanitize (ast.diff), 'd', ast.dvs) elif ast.is_intg: if ast.intg is None: return AST ('-intg', AST.One, *tuple (sanitize (a) for a in ast [2:])) elif ast.is_piece: if ast.piece [-1] [1] == AST.True_: ast = AST ('-piece', ast.piece [:-1] + ((ast.piece [-1] [0], True),)) if ast.piece.len == 1 and ast.piece [0] [1] is True: ast = ast.piece [0] [0] elif ast.is_slice: ast = AST ('-slice', False if ast.start == AST.None_ else ast.start, False if ast.stop == AST.None_ else ast.stop, AST ('@', 'C') if ast.step == AST.None_ else False if ast.step is None else ast.step) elif ast.is_and: args = sanitize (ast.and_) ast2 = sxlat._xlat_f2a_And (*args, force = True) if ast2 is not None: ast = ast2 elif ast.is_ufunc: if ast.is_ufunc_explicit: ast = AST ('-ufunc', ast.ufunc, *ast [2:]) return AST (*tuple (sanitize (a) for a in ast))#, **ast._kw)
def evalexpr(ast): sym.ast2spt.set_precision(ast) if ast.is_func and ast.func in AST.Func.PLOT: # plotting? args, kw = AST.args2kwargs(AST.apply_vars(ast.args, _VARS), sym.ast2spt) ret = getattr(splot, ast.func)(*args, **kw) return { 'msg': [ 'Plotting not available because matplotlib is not installed.' ] } if ret is None else { 'img': ret } elif ast.op in { '@', '-func' } and ast[1] in AST.Func.ADMIN: # special admin function? asts = globals()[f'_admin_{ast [1]}']( *(ast.args if ast.is_func else ())) if isinstance(asts, str): return {'msg': [asts]} elif isinstance(asts, list) and isinstance(asts[0], str): return {'msg': asts} else: # not admin function, normal evaluation ast, vars = _prepare_ass(ast) if _SYMPAD_DEBUG: print('ast: ', ast, file=sys.stderr) try: spt, xlat = sym.ast2spt(ast, retxlat=True) # , _VARS) if _SYMPAD_DEBUG and xlat: print('xlat: ', xlat, file=sys.stderr) sptast = sym.spt2ast(spt) except: if _SYMPAD_DEBUG: print(file=sys.stderr) raise if _SYMPAD_DEBUG: try: print('spt: ', repr(spt), file=sys.stderr) except: pass print('spt type: ', type(spt), file=sys.stderr) try: print('spt args: ', repr(spt.args), file=sys.stderr) except: pass print('spt latex: ', sp.latex(spt), file=sys.stderr) print('spt ast: ', sptast, file=sys.stderr) print('spt tex: ', sym.ast2tex(sptast), file=sys.stderr) print('spt nat: ', sym.ast2nat(sptast), file=sys.stderr) print('spt py: ', sym.ast2py(sptast), file=sys.stderr) print(file=sys.stderr) asts = _execute_ass(sptast, vars) response = {} if asts and asts[0] != AST.None_: response.update({ 'math': [{ 'tex': sym.ast2tex(ast), 'nat': sym.ast2nat(ast), 'py': sym.ast2py(ast), } for ast in asts] }) return response
def _execute_ass(ast, vars): # execute assignment if it was detected def set_vars(vars): nvars = {} for v, a in vars.items(): v = v.var if a.is_ufunc: if v in sparser.RESERVED_FUNCS: raise NameError( f'cannot assign undefined function to concrete function name {v!r}' ) if a.is_ufunc_anonymous: a = AST(a.op, v, *a[2:]) elif a.is_sym_anonymous: if a.is_sym_unqualified: raise CircularReferenceError( 'cannot asign unqualified anonymous symbol') a = AST(a.op, v, *a[2:]) nvars[v] = a try: # check for circular references AST.apply_vars(AST(',', tuple(('@', v) for v in nvars)), { **_VARS, **nvars }) except RecursionError: raise CircularReferenceError( "I'm sorry, Dave. I'm afraid I can't do that.") from None _VARS.update(nvars) return list(nvars.items()) # start here if not vars: # no assignment if not ast.is_ufunc: ast = _mapback(ast) _VARS['_'] = ast _vars_updated() return [ast] if len(vars) == 1: # simple assignment if ast.op not in {'-ufunc', '-sym'}: ast = _mapback(ast, vars[0].var, {vars[0].var}) vars = set_vars({vars[0]: ast}) else: # tuple assignment ast = ast.strip_paren if ast.op in {',', '[', '-set'}: asts = ast[1] else: asts = [] itr = iter(sym.ast2spt(ast)) for i in range(len(vars) + 1): try: ast = sym.spt2ast(next(itr)) except StopIteration: break if vars[i].is_ufunc_named: asts.append(AST.Ass.ufunc2lamb(vars[i], ast)) vars[i] = AST('@', vars[i].ufunc) else: asts.append(ast) if len(vars) < len(asts): raise ValueError( f'too many values to unpack (expected {len (vars)})') elif len(vars) > len(asts): raise ValueError( f'not enough values to unpack (expected {len (vars)}, got {len (asts)})' ) vasts = list(zip(vars, asts)) exclude = set(va[0].var for va in filter(lambda va: va[1].is_ufunc, vasts)) asts = [ a if a.op in {'-ufunc', '-sym'} else _mapback(a, v.var, exclude) for v, a in vasts ] vars = set_vars(dict(zip(vars, asts))) _vars_updated() return _present_vars(vars)
def _xlat_f2a_And( *args, canon=False, force=False ): # patch together out of order extended comparison objects potentially inverting comparisons def concat(lhs, rhs): return AST('<>', lhs.lhs, lhs.cmp + rhs.cmp) def invert(ast): cmp = [] lhs = ast.lhs for c in ast.cmp: v = _xlat_f2a_Add_invert.get(c[0]) if v is None: return None cmp.append((v, lhs)) lhs = c[1] return AST('<>', lhs, tuple(cmp[::-1])) def match(ast): li, ll = None, 0 ri, rl = None, 0 for i in range(len(args)): if args[i].is_cmp: if ast.lhs == args[i].cmp[-1][1] and (li is None or args[i].cmp.len > ll): li, ll = i, args[i].cmp.len if ast.cmp[-1][1] == args[i].lhs and (ri is None or args[i].cmp.len > rl): ri, rl = i, args[i].cmp.len return li, ri, ll + rl def canonicalize(ast): return invert(ast) if (canon and ast.is_cmp and sum( (r[0] == '>') - (r[0] == '<') for r, c in ast.cmp) > 0) else ast def count_ops(ast): if ast.is_and: return ast.and_.len - 1 + sum(count_ops(a) for a in ast.and_) elif ast.is_cmp: return ast.cmp.len + count_ops(ast.lhs) + sum( count_ops(ra[1]) for ra in ast.cmp) return 0 # start here if not _SX_XLAT_AND and not force: return None # AST ('-func', 'And', args) and_ = AST('-and', tuple(args)) # simple and andc = [args[0]] # build concatenated and from comparisons for arg in args[1:]: if arg.is_cmp and andc[-1].is_cmp and arg.lhs == andc[-1].cmp[-1][1]: andc[-1] = AST('<>', andc[-1].lhs, andc[-1].cmp + arg.cmp) else: andc.append(arg) andc = AST('-and', tuple(andc)) if len(andc) > 1 else andc[0] itr = iter(args) args = [] for arg in itr: # build optimized and if not args or not arg.is_cmp: args.append(arg) else: while 1: li, ri, l = match(arg) argv = invert(arg) if argv is not None: liv, riv, lv = match(argv) if lv > l: li, ri = liv, riv arg = argv if li is None or li == ri: if ri is None: args.append(arg) break else: arg = concat(arg, args[ri]) del args[ri] elif ri is None: arg = concat(args[li], arg) del args[li] else: i1, i2 = min(li, ri), max(li, ri) arg = concat(concat(args[li], arg), args[ri]) del args[i2], args[i1] if len(args) == 1: ast = canonicalize(args[0]) else: ast = AST('-and', tuple(canonicalize(a) for a in args)) return min(andc, and_, ast, key=lambda a: count_ops(a))
def concat(lhs, rhs): return AST('<>', lhs.lhs, lhs.cmp + rhs.cmp)
def _admin_envreset(*args): return ['Environment has been reset.' ] + _admin_env(*(AST('@', var if state else f'no{var}') for var, state in _START_ENV.items()))
def _envop(env, apply): nonlocal vars_updated msgs = [] for var, state in env.items(): if apply: _ENV[var] = state if var == 'EI': msgs.append( f'Uppercase E and I is {"on" if state else "off"}.') if apply: AST.EI(state) for var in (AST.E.var, AST.I.var): if var in _VARS: del _VARS[var] elif var == 'quick': msgs.append(f'Quick input mode is {"on" if state else "off"}.') if apply: sym.set_quick(state) _PARSER.set_quick(state) vars_updated = True elif var == 'pyS': msgs.append( f'Python S escaping is {"on" if state else "off"}.') if apply: sym.set_pyS(state) elif var == 'simplify': msgs.append( f'Post-evaluation simplify is {"on" if state else "off"}.') if apply: sym.set_simplify(state) elif var == 'matsimp': msgs.append( f'Matrix simplify is {"broken" if not spatch.SPATCHED else "on" if state else "off"}.' ) if apply: spatch.set_matmulsimp(state) elif var == 'ufuncmap': msgs.append( f'Undefined function map to variable is {"on" if state else "off"}.' ) if apply: global _UFUNC_MAPBACK _UFUNC_MAPBACK = state elif var == 'prodrat': msgs.append( f'Leading product rational is {"on" if state else "off"}.') if apply: sym.set_prodrat(state) elif var == 'doit': msgs.append(f'Expression doit is {"on" if state else "off"}.') if apply: sym.set_doit(state) elif var == 'strict': msgs.append( f'Strict LaTeX formatting is {"on" if state else "off"}.') if apply: sym.set_strict(state) elif var in _ONE_FUNCS: msgs.append(f'Function {var} is {"on" if state else "off"}.') if apply: vars_updated = True return msgs