def vars (self, request): asts = _sorted_vars () return {'vars': [{ 'tex': sym.ast2tex (ast), 'nat': sym.ast2nat (ast), 'py' : sym.ast2py (ast), } for ast in asts]}
def validate(self, request): ast, erridx, autocomplete, error = _PARSER.parse(request['text']) tex = nat = py = None if ast is not None: tex, xlattex = sym.ast2tex(ast, retxlat=True) nat, xlatnat = sym.ast2nat(ast, retxlat=True) py, xlatpy = sym.ast2py(ast, retxlat=True) if _SYMPAD_DEBUG: print('free:', list(v.var for v in ast.free_vars), file=sys.stderr) print('ast: ', ast, file=sys.stderr) if xlattex: print('astt:', repr(xlattex), file=sys.stderr) if xlatnat: print('astn:', repr(xlatnat), file=sys.stderr) if xlatpy: print('astp:', repr(xlatpy), file=sys.stderr) print('tex: ', tex, file=sys.stderr) print('nat: ', nat, file=sys.stderr) print('py: ', py, file=sys.stderr) print(file=sys.stderr) if isinstance(error, Exception): error = (f'{error.__class__.__name__}: ' if not isinstance(error, SyntaxError) else '') + error.args[0].replace('\n', ' ').strip() return { 'tex': tex, 'nat': nat, 'py': py, 'erridx': erridx, 'autocomplete': autocomplete, 'error': error, }
def test (argv = None): global DEPTH, CURLYS funcs = {'N', 'O', 'S', 'beta', 'gamma', 'Gamma', 'Lambda', 'zeta'} sym.set_sym_user_funcs (funcs) sparser.set_sp_user_funcs (funcs) sym.set_strict (True) # sxlat._SX_XLAT_AND = False # turn off py And translation because it mangles things depth = 3 single = None quick = False topexpr = None opts, _ = getopt (sys.argv [1:] if argv is None else argv, 'tnpiqScd:e:E:', ['tex', 'nat', 'py', 'dump', 'show', 'se', 'showerr', 'inf', 'infinite', 'nc', 'nocurlys', 'ns', 'nospaces', 'rs', 'randomspaces', 'tp', 'transpose', 'quick', 'nopyS', 'cross', 'depth=', 'expr=', 'topexpr=']) if ('-q', '') in opts or ('--quick', '') in opts: parser.set_quick (True) quick = True if ('-S', '') in opts or ('--nopyS', '') in opts: sym.set_pyS (False) for opt, arg in opts: if opt in ('-d', '--depth'): depth = int (arg) elif opt in ('-e', '--expr'): single = [arg] elif opt in ('-E', '--topexpr'): topexpr = globals ().get (f'expr_{arg}') if topexpr is None: topexpr = expr else: EXPRS.remove (topexpr) if ('--dump', '') in opts: DEPTH = 0 for e in EXPRS: print (e ()) sys.exit (0) dotex = ('--tex', '') in opts or ('-t', '') in opts donat = ('--nat', '') in opts or ('-n', '') in opts dopy = ('--py', '') in opts or ('-p', '') in opts showerr = ('--se', '') in opts or ('--showerr', '') in opts CURLYS = not (('--nc', '') in opts or ('--nocurlys', '') in opts) spaces = not (('--ns', '') in opts or ('--nospaces', '') in opts) rndspace = ('--rs', '') in opts or ('--randomspaces', '') in opts transpose = ('--tp', '') in opts or ('--transpose', '') in opts show = ('--show', '') in opts infinite = (('-i', '') in opts or ('--inf', '') in opts or ('--infinite', '') in opts) docross = ('--cross', '') in opts or ('-c', '') in opts if not (dotex or donat or dopy): dotex = donat = dopy = True if infinite and not single: expr_func = (lambda: topexpr ()) if spaces else (lambda: topexpr ().replace (' ', '')) else: expr_func = iter (single or filter (lambda s: s [0] != '#', _EXPRESSIONS)).__next__ try: while 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 check_double_curlys (ast): if not isinstance (ast, AST): return False elif ast.is_curly and ast.curly.is_curly: return True return any (check_double_curlys (a) for a in ast) # start here status = [] DEPTH = depth text = expr_func () if text and infinite and not single and rndspace: # insert a random space to break shit i = randrange (0, len (text)) text = f'{text [:i]} {text [i:]}' if transpose: # transpose random block of text to another location overwriting that location s0, s1, d0, d1 = (randrange (len (text)) for _ in range (4)) s0, s1 = sorted ((s0, s1)) d0, d1 = sorted ((d0, d1)) text = text [:d0] + text [s0 : s1] + text [d1:] status.append (f'text: {text}') ast = parse (text) # fixstuff (parse (text)) status.append (f'ast: {ast}') err = None if not ast: if single or (not infinite and not quick) or showerr: err = ValueError ("error parsing") if ast and not err: try: ast2 = validate (ast) except Exception as e: # make sure garbling functions did not create an invalid ast if single or showerr: err = e else: ast = None if ast and not err: if ast2 != ast: status.append (f'astv: {ast2}') ast = ast2 if dopy and any (a.is_ass for a in (ast.scolon if ast.is_scolon else (ast,))): # reject assignments at top level if doing py because all sorts of mangling goes on there, we just want expressions in that case if single or showerr: err = ValueError ("disallowed assignment") else: ast = None if err or not ast: if err and not showerr: raise err if showerr: print (f'\n{text} ... {err}') continue if show: print (f'{text}\n') sxlat._SX_XLAT_AND = False # turn off py And translation because it mangles things for rep in ('tex', 'nat', 'py'): if locals () [f'do{rep}']: symfunc = getattr (sym, f'ast2{rep}') status.append (f'sym.ast2{rep} ()') text1 = symfunc (ast) status [-1] = f'{rep}1: {" " if rep == "py" else ""}{text1}' status.append ('parse ()') rast, rpre = parse (text1, retprepost = True) # fixstuff (parse (text1)) if not rast: raise ValueError (f"error parsing") if check_double_curlys (rpre): status [-1] = f'astd: {rpre}' raise ValueError ("doubled curlys") status [-1:] = [f'ast: {rast}', f'sym.ast2{rep} ()'] text2 = symfunc (rast) status [-1] = f'{rep}2: {" " if rep == "py" else ""}{text2}' if text2 != text1: raise ValueError ("doesn't match") del status [-3:] if docross and dotex + donat + dopy > 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) sxlat._SX_XLAT_AND = True # turn on py And translation because it is needed here # start here ast = sanitize (ast) status.append (f'ast: {ast}') if dotex: tex1 = sym.ast2tex (ast) status.append (f'tex1: {tex1}') ast2 = ast = sanitize (parse (tex1)).flat if donat: status.append (f'ast: {ast2}') nat = sym.ast2nat (ast2) status.append (f'nat: {nat}') ast2 = parse (nat) if dopy: try: sym._SYM_MARK_PY_ASS_EQ = True # allow xlat of marked Eq functions which indicate assignment in python text status.append (f'ast: {ast2}') py = sym.ast2py (ast2, ass2cmp = False) status.append (f'py: {py}') ast2 = parse (py) finally: sym._SYM_MARK_PY_ASS_EQ = False # allow xlat of marked Eq functions which indicate assignment in python text try: if dopy: sxlat._SX_READ_PY_ASS_EQ = True # allow xlat of marked Eq functions which indicate assignment in python text status.append (f'ast: {ast2}') tex2 = sym.ast2tex (ast2) status.append (f'tex2: {tex2}') ast2 = sanitize (parse (tex2)).flat finally: sxlat._SX_READ_PY_ASS_EQ = False # allow xlat of marked Eq functions which indicate assignment in python text elif donat: # TODO: add more status updates for intermediate steps like above nat1 = sym.ast2nat (ast) status.append (f'nat1: {nat1}') ast2 = ast = sanitize (parse (nat1)).flat try: sym._SYM_MARK_PY_ASS_EQ = True # allow xlat of marked Eq functions which indicate assignment in python text status.append (f'ast: {ast2}') py = sym.ast2py (ast2, ass2cmp = False) status.append (f'py: {py}') ast2 = parse (py) finally: sym._SYM_MARK_PY_ASS_EQ = False # allow xlat of marked Eq functions which indicate assignment in python text try: sxlat._SX_READ_PY_ASS_EQ = True # allow xlat of marked Eq functions which indicate assignment in python text status.append (f'ast: {ast2}') nat2 = sym.ast2nat (ast2) status.append (f'nat2: {nat2}') ast2 = sanitize (parse (nat2)).flat finally: sxlat._SX_READ_PY_ASS_EQ = False # allow xlat of marked Eq functions which indicate assignment in python text if ast2 != ast: status.extend ([f'ast: {ast2}', f'org: {ast}']) raise ValueError ("doesn't match across representations") except (KeyboardInterrupt, StopIteration): pass except: print ('Exception!\n') print ('\n'.join (status)) print () raise finally: sxlat._SX_XLAT_AND = True return True
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