├── LICENSE ├── README.markdown ├── pb.py ├── pitybas ├── __init__.py ├── cli.py ├── common.py ├── expression.py ├── interpret.py ├── io │ ├── __init__.py │ ├── simple.py │ └── vt100.py ├── parse.py └── tokens.py └── tests ├── blocks.bas ├── circle.bas ├── generic.bas ├── getkey.bas ├── listmat.bas ├── logic.bas ├── math.bas ├── menu.bas └── trig.bas /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Ryan Hileman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | pitybas 2 | ======= 3 | A working TI-BASIC interpreter, written in Python. 4 | 5 | Currently, all `.bas` files in tests/ run except circle.bas (due to lack of graph screen functions) 6 | 7 | Use `pb.py -i vt100` to run programs which need a working home screen. 8 | 9 | If you run `pb.py` with no filename, it launches an interactive shell. 10 | 11 | Usage: pb.py [options] [filename] 12 | 13 | Options: 14 | -h, --help show this help message and exit 15 | -a, --ast parse, print ast, and quit 16 | -d, --dump dump variables in stacktrace 17 | -s, --stacktrace always stacktrace 18 | -v, --verbose verbose output 19 | -i IO, --io=IO select an IO system: simple (default), vt100 20 | 21 | -------------------------------------------------------------------------------- /pb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pitybas.cli 3 | -------------------------------------------------------------------------------- /pitybas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunixbochs/pitybas/d637eadf6f01bb5d4e9645c70d4a9e1ffb9eabce/pitybas/__init__.py -------------------------------------------------------------------------------- /pitybas/cli.py: -------------------------------------------------------------------------------- 1 | import sys, traceback 2 | from optparse import OptionParser 3 | from interpret import Interpreter, Repl 4 | from common import Error 5 | from pitybas.io.vt100 import IO as vt100 6 | 7 | parser = OptionParser(usage='Usage: pb.py [options] filename') 8 | parser.add_option('-a', '--ast', dest="ast", action="store_true", help="parse, print ast, and quit") 9 | parser.add_option('-d', '--dump', dest="vardump", action="store_true", help="dump variables in stacktrace") 10 | parser.add_option('-s', '--stacktrace', dest="stacktrace", action="store_true", help="always stacktrace") 11 | parser.add_option('-v', '--verbose', dest="verbose", action="store_true", help="verbose output") 12 | parser.add_option('-i', '--io', dest="io", help="select an IO system: simple (default), vt100") 13 | 14 | (options, args) = parser.parse_args() 15 | 16 | if len(args) > 1: 17 | parser.print_help() 18 | sys.exit(1) 19 | 20 | io = None 21 | if options.io == 'vt100': 22 | io = vt100 23 | 24 | if args: 25 | vm = Interpreter.from_file(args[0], history=20, io=io) 26 | else: 27 | print 'Welcome to pitybas. Press Ctrl-D to exit.' 28 | print 29 | vm = Repl(history=20, io=io) 30 | 31 | if options.verbose: 32 | vm.print_tokens() 33 | print 34 | print '-===[ Running %s ]===-' % args[0] 35 | 36 | if options.ast: 37 | print_ast(vm) 38 | sys.exit(0) 39 | 40 | try: 41 | vm.execute() 42 | if options.stacktrace: 43 | vm.print_stacktrace(options.vardump) 44 | except KeyboardInterrupt: 45 | print 46 | vm.print_stacktrace(options.vardump) 47 | except Exception, e: 48 | print 49 | print 50 | vm.print_stacktrace(options.vardump) 51 | 52 | print '%s on line %i:' % (e.__class__.__name__, vm.line), 53 | 54 | if isinstance(e, Error): 55 | print e.msg 56 | else: 57 | print 58 | print '-===[ Python traceback ]===-' 59 | print traceback.format_exc() 60 | -------------------------------------------------------------------------------- /pitybas/common.py: -------------------------------------------------------------------------------- 1 | class Error(Exception): 2 | def __init__(self, msg): 3 | self.msg = msg 4 | 5 | def __str__(self): 6 | return self.msg 7 | 8 | class StopError(Exception): pass 9 | class ReturnError(Exception): pass 10 | 11 | class ParseError(Error): pass 12 | class ExecutionError(Error): pass 13 | class ExpressionError(Error): pass 14 | 15 | class Pri: 16 | # evaluation happens in the following order: 17 | # skip: expressions, functions, variables 18 | # 1. exponents, factorials 19 | # 2. multiplication, division 20 | # 3. addition, subtraction 21 | # 4. logic operators 22 | # 5. boolean operators 23 | # 6. variable setting 24 | 25 | # these won't be parsed into expressions at all 26 | INVALID = -2 27 | # NONE means store but don't execute directly 28 | # used for variables, lazy loading functions and expressions 29 | NONE = -1 30 | 31 | PROB = 0 32 | EXPONENT = 1 33 | MULTDIV = 2 34 | ADDSUB = 3 35 | 36 | LOGIC = 4 37 | BOOL = 5 38 | SET = 6 39 | 40 | def is_number(num): 41 | return str(num).lstrip('-').replace('.', '', 1).isdigit() 42 | -------------------------------------------------------------------------------- /pitybas/expression.py: -------------------------------------------------------------------------------- 1 | import tokens 2 | from common import ExpressionError, Pri, is_number 3 | 4 | class Base: 5 | priority = Pri.NONE 6 | 7 | can_run = False 8 | can_set = False 9 | can_get = True 10 | can_fill_left = False 11 | can_fill_right = False 12 | absorbs = () 13 | 14 | end = None 15 | 16 | def __init__(self, *elements): 17 | self.contents = [] 18 | self.raw = [] 19 | self.finished = False 20 | 21 | for e in elements: 22 | self.append(e) 23 | 24 | def append(self, token): 25 | if self.contents: 26 | prev = self.contents[-1] 27 | 28 | # the minus sign implies a * -1 when used by itself 29 | if isinstance(prev, tokens.Minus): 30 | # TODO: fix this the rest of the way 31 | if len(self.contents) == 1: 32 | self.contents.pop() 33 | self.contents += [tokens.Value(-1), tokens.Mult()] 34 | 35 | # absorb: tokens can absorb the next token from the expression if it matches a list of types 36 | elif isinstance(token, prev.absorbs): 37 | if isinstance(token, Base): 38 | token = token.flatten() 39 | 40 | prev.absorb(token) 41 | return 42 | 43 | # implied multiplication 44 | elif prev.priority == token.priority == tokens.Pri.NONE: 45 | 46 | # negative numbers actually have implied addition 47 | if isinstance(token, tokens.Value)\ 48 | and is_number(token.value) and int(token.value) < 0: 49 | self.contents.append(tokens.Plus()) 50 | else: 51 | self.contents.append(tokens.Mult()) 52 | 53 | self.raw.append(token) 54 | self.contents.append(token) 55 | 56 | def extend(self, array): 57 | for x in array: 58 | self.append(x) 59 | 60 | def flatten(self): 61 | if len(self.contents) == 1: 62 | first = self.contents[0] 63 | if isinstance(first, Base): 64 | return first.flatten() 65 | elif first.can_get: 66 | return first 67 | 68 | return self 69 | 70 | def fill(self): 71 | # TODO: instead of this system, perhaps tokens should be able to specify whether they need/want left/right params 72 | if not self.contents: return 73 | 74 | # if we don't have a proper variable:token:variable pairing in the token list, 75 | # this method will allow tokens to fill in an implied variable to their left or right 76 | new = [] 77 | for i in xrange(len(self.contents)): 78 | t = self.contents[i] 79 | if (i % 2 == 0 and not t.can_get): 80 | left = None 81 | right = None 82 | 83 | if i > 0: 84 | left = self.contents[i-1] 85 | if not left.can_fill_right: 86 | left = None 87 | 88 | right = self.contents[i] 89 | if not right.can_fill_left: 90 | right = None 91 | 92 | if left is not None and right is not None: 93 | if left < right: 94 | left = None 95 | else: 96 | right = None 97 | 98 | if left is not None: 99 | new.append(left.fill_right()) 100 | elif right is not None: 101 | new.append(right.fill_left()) 102 | 103 | new.append(t) 104 | 105 | last = new[-1] 106 | if not last.can_get: 107 | if last.can_fill_right: 108 | new.append(last.fill_right()) 109 | 110 | self.contents = new 111 | 112 | def validate(self): 113 | if not self.contents: return 114 | 115 | # figure out how to handle in-place tokens like the symbol for ^3 116 | # perhaps replace it with a ^3 so we can enforce (value, token, value) 117 | # or we can pad "in-place" tokens with a null to be passed as right 118 | 119 | # make sure expression is ordered (value, token, value, token, value) 120 | for i in xrange(len(self.contents)): 121 | t = self.contents[i] 122 | 123 | if (i % 2 == 0 and not t.can_get) or ( i % 2 == 1 and not t.can_run): 124 | raise ExpressionError('bad token order: %s' % self) 125 | 126 | # determine whether we have any tokens after a -> 127 | found_stor = False 128 | for i in xrange(len(self.contents)): 129 | t = self.contents[i] 130 | odd = i % 2 131 | 132 | if isinstance(t, tokens.Store): 133 | found_stor = True 134 | stor_odd = odd 135 | elif found_stor and (odd == stor_odd): 136 | raise ExpressionError('Store cannot be followed by non-Store tokens in expression: %s' % self) 137 | 138 | def order(self): 139 | # this step returns a list of ordered indicies 140 | # to help reduce tokens to a single value 141 | # see common.Pri for an ordering explanation 142 | 143 | order = {} 144 | 145 | for i in xrange(len(self.contents)): 146 | token = self.contents[i] 147 | p = token.priority 148 | if p >= 0: 149 | # anything below zero is to be ignored 150 | if p in order: 151 | order[p].append(i) 152 | else: 153 | order[p] = [i] 154 | 155 | ret = [] 156 | for p in order: 157 | ret += order[p] 158 | 159 | return ret 160 | 161 | def get(self, vm): 162 | self.fill() 163 | self.validate() 164 | 165 | sub = [] 166 | expr = self.contents[:] 167 | for i in self.order(): 168 | n = 0 169 | for s in sub: 170 | if s < i: 171 | n += 1 172 | 173 | sub += [i, i+1] 174 | i -= n 175 | 176 | right = expr.pop(i+1) 177 | left = expr.pop(i-1) 178 | 179 | token = expr[i-1] 180 | expr[i-1] = tokens.Value(token.run(vm, left, right)) 181 | 182 | return vm.get(expr[0]) 183 | 184 | def finish(self): 185 | self.finished = True 186 | 187 | def close(self, char): 188 | for stack in reversed(self.contents): 189 | if isinstance(stack, Base): 190 | if stack.close(char): 191 | return False 192 | 193 | if char == self.end and not self.finished: 194 | self.finish() 195 | return True 196 | 197 | def __str__(self): 198 | return ''.join([a.token for a in self.raw]) 199 | 200 | def __len__(self): 201 | return len(self.contents) 202 | 203 | def __repr__(self): 204 | return 'E(%s)' % (' '.join(repr(token) for token in self.contents)) 205 | 206 | bracket_map = {'(':')', '{':'}', '[':']'} 207 | 208 | class Expression(Base): 209 | def set(self, vm, value): 210 | if len(self.contents) == 1: 211 | self.contents[0].set(vm, value) 212 | 213 | class Bracketed(Base): 214 | def __init__(self, end): 215 | self.end = bracket_map[end] 216 | Base.__init__(self) 217 | 218 | def __repr__(self): 219 | return 'B(%s)' % (' '.join(repr(token) for token in self.contents)) 220 | 221 | class ParenExpr(Bracketed): 222 | end = ')' 223 | 224 | class Tuple(Base): 225 | priority = Pri.INVALID 226 | 227 | def __init__(self): 228 | Base.__init__(self) 229 | 230 | def append(self, expr): 231 | if isinstance(expr, Base): 232 | expr = expr.flatten() 233 | 234 | if self.contents: 235 | last = self.contents[-1] 236 | if isinstance(last, Base) and not last.finished: 237 | last.append(expr) 238 | return 239 | 240 | expr = Expression(expr) 241 | self.contents.append(expr) 242 | 243 | def sep(self): 244 | if self.contents: 245 | self.contents[-1].finish() 246 | 247 | def get(self, vm): 248 | return [vm.get(arg) for arg in self.contents] 249 | 250 | def __len__(self): 251 | return len(self.contents) 252 | 253 | def __repr__(self): 254 | def expr_repr(e): 255 | if not isinstance(e, Expression): 256 | return '({})'.format(e) 257 | else: 258 | return repr(e) 259 | 260 | return 'T(%s)' % (', '.join(expr_repr(expr) for expr in self.contents)) 261 | 262 | class Arguments(Tuple, Bracketed): 263 | def __init__(self, end): 264 | Bracketed.__init__(self, end) 265 | 266 | def __repr__(self): 267 | return 'A(%s)' % (', '.join(repr(expr) for expr in self.contents)) 268 | 269 | class FunctionArgs(Arguments): 270 | end = ')' 271 | 272 | class ListExpr(Arguments): 273 | priority = Pri.NONE 274 | end = '}' 275 | 276 | def __repr__(self): 277 | return 'L{%s}' % (', '.join(repr(expr) for expr in self.contents)) 278 | 279 | class MatrixExpr(Arguments): 280 | priority = Pri.NONE 281 | end = ']' 282 | 283 | def __repr__(self): 284 | return 'M[%s]' % (', '.join(repr(expr) for expr in self.contents)) 285 | 286 | -------------------------------------------------------------------------------- /pitybas/interpret.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import decimal 3 | import os 4 | import time 5 | import traceback 6 | 7 | from parse import Parser, ParseError 8 | from tokens import EOF, Value, REPL 9 | from common import ExecutionError, StopError, ReturnError 10 | 11 | from pitybas.io.simple import IO 12 | from expression import Base 13 | 14 | class Interpreter(object): 15 | @classmethod 16 | def from_string(cls, string, *args, **kwargs): 17 | code = Parser(string).parse() 18 | return Interpreter(code, *args, **kwargs) 19 | 20 | @classmethod 21 | def from_file(cls, filename, *args, **kwargs): 22 | string = open(filename, 'r').read().decode('utf8') 23 | vm = Interpreter.from_string(string, *args, **kwargs) 24 | vm.name = os.path.basename(filename) 25 | return vm 26 | 27 | def __init__(self, code, history=10, io=None, name=None): 28 | if not io: io = IO 29 | self.io = io(self) 30 | 31 | self.name = name 32 | self.code = code 33 | self.code.append([EOF()]) 34 | self.line = 0 35 | self.col = 0 36 | self.expression = None 37 | self.blocks = [] 38 | self.running = [] 39 | self.history = [] 40 | self.hist_len = history 41 | 42 | self.vars = {} 43 | self.lists = defaultdict(list) 44 | self.matrix = {} 45 | self.fixed = -1 46 | 47 | self.serial = 0 48 | self.repl_serial = 0 49 | 50 | def cur(self): 51 | return self.code[self.line][self.col] 52 | 53 | def inc(self): 54 | self.col += 1 55 | if self.col >= len(self.code[self.line]): 56 | self.col = 0 57 | return self.inc_row() 58 | 59 | return self.cur() 60 | 61 | def inc_row(self): 62 | self.line = min(self.line+1, len(self.code)-1) 63 | self.expression = None 64 | return self.cur() 65 | 66 | def get_var(self, var, default=None): 67 | if var not in self.vars and default is not None: 68 | return default 69 | return self.vars[var] 70 | 71 | def set_var(self, var, value): 72 | if isinstance(value, (Value, Base)): 73 | value = value.get(self) 74 | 75 | self.vars[var] = value 76 | return value 77 | 78 | def get_matrix(self, name): 79 | return self.matrix[name] 80 | 81 | def set_matrix(self, name, value): 82 | self.matrix[name] = value 83 | 84 | def get_list(self, name): 85 | return self.lists[name] 86 | 87 | def set_list(self, name, value): 88 | self.lists[name] = value 89 | 90 | def push_block(self, block=None): 91 | if not block and self.running: 92 | block = self.running[-1] 93 | 94 | if block: 95 | self.blocks.append(block) 96 | else: 97 | raise ExecutionError('tried to push an invalid block to the stack') 98 | 99 | def pop_block(self): 100 | if self.blocks: 101 | return self.blocks.pop() 102 | else: 103 | raise ExecutionError('tried to pop an empty block stack') 104 | 105 | def find(self, *types, **kwargs): 106 | if 'wrap' in kwargs: 107 | wrap = kwargs['wrap'] 108 | else: 109 | wrap = False 110 | 111 | if 'pos' in kwargs: 112 | pos = kwargs['pos'] 113 | else: 114 | pos = self.line 115 | 116 | def y(i): 117 | line = self.code[i] 118 | if line: 119 | cur = line[0] 120 | if isinstance(cur, types): 121 | return i, 0, cur 122 | 123 | for i in xrange(pos, len(self.code)): 124 | ret = y(i) 125 | if ret: yield ret 126 | 127 | if wrap: 128 | for i in xrange(0, pos): 129 | ret = y(i) 130 | if ret: yield ret 131 | 132 | def goto(self, row, col): 133 | if row >= 0 and row < len(self.code)\ 134 | and col >= 0 and col < len(self.code[row]): 135 | self.line = row 136 | self.col = col 137 | else: 138 | raise ExecutionError('cannot goto (%i, %i)' % (row, col)) 139 | 140 | def get(self, *var): 141 | ret = [] 142 | for v in var: 143 | val = v.get(self) 144 | if isinstance(val, complex): 145 | if not val.imag: 146 | val = val.real 147 | 148 | if isinstance(val, (float)): 149 | # TODO: perhaps limit precision here 150 | i = int(val) 151 | if val == i: 152 | val = i 153 | 154 | ret.append(val) 155 | 156 | if len(ret) == 1: 157 | return ret[0] 158 | 159 | return ret 160 | 161 | def disp_round(self, num): 162 | if not isinstance(num, (decimal.Decimal, int, long, float, complex)): 163 | return num 164 | 165 | if self.fixed < 0: 166 | return num 167 | else: 168 | return round(num, self.fixed) 169 | 170 | def run(self, cur): 171 | self.history.append((self.line, self.col, cur)) 172 | self.history = self.history[-self.hist_len:] 173 | 174 | cur.line, cur.col = self.line, self.col 175 | 176 | if cur.can_run: 177 | self.running.append((self.line, self.col, cur)) 178 | self.inc() 179 | cur.run(self) 180 | self.running.pop() 181 | elif cur.can_get: 182 | self.inc() 183 | self.set_var('Ans', cur.get(self)) 184 | self.serial = time.time() 185 | else: 186 | raise ExecutionError('cannot seem to run token: %s' % cur) 187 | 188 | def execute(self): 189 | with self.io: 190 | try: 191 | while not isinstance(self.cur(), EOF): 192 | cur = self.cur() 193 | self.run(cur) 194 | except StopError, e: 195 | if e.message: 196 | print 197 | print 'Stopped:', e.message 198 | except ReturnError, e: 199 | if e.message: 200 | print 201 | print 'Returned:', e.message 202 | 203 | def print_tokens(self): 204 | for line in self.code: 205 | print (', '.join(repr(n) for n in line)).replace("u'", "'") 206 | 207 | def print_ast(self, start=0, end=None, highlight=None): 208 | if end is None: 209 | end = len(self.code) 210 | 211 | for i in xrange(max(start, 0), min(end, len(self.code))): 212 | line = self.code[i] 213 | if i == highlight - 1: 214 | print '>>>> {}'.format(line) 215 | else: 216 | print '{:3}: {}'.format(i, line) 217 | 218 | def print_stacktrace(self, num=None, vardump=False): 219 | if not num: 220 | num = self.hist_len 221 | 222 | if self.name: 223 | print '-===[ Dumping {} ]===-'.format(self.name) 224 | 225 | if self.history: 226 | print 227 | print '-===[ Stacktrace ]===-' 228 | 229 | for row, col, cur in self.history[-num:]: 230 | print ('[{}, {}]:'.format(row, col)).ljust(9), repr(cur).replace("u'", '').replace("'", '') 231 | 232 | if self.history: 233 | print 234 | 235 | print '-===[ Code (row {}, col {}) ]===-'.format(self.line, self.col) 236 | h = num / 2 237 | self.print_ast(self.line - h, self.line + h, highlight=self.line) 238 | print 239 | 240 | if vardump: 241 | print 242 | print '-===[ Variable Dump ]===-' 243 | import pprint 244 | pprint.pprint(self.vars) 245 | print 246 | 247 | def run_pgrm(self, name): 248 | for ref in os.listdir('.'): 249 | if ref.endswith('.bas'): 250 | test = ref.rsplit('.', 1)[0] 251 | if test.lower() == name.lower(): 252 | sub = Interpreter.from_file(ref) 253 | sub.execute() 254 | return 255 | raise ExecutionError('pgrm{} not found'.format(name)) 256 | 257 | class Repl(Interpreter): 258 | def __init__(self, code=[], **kwargs): 259 | super(Repl, self).__init__(code, **kwargs) 260 | self.code.insert(-2, [REPL()]) 261 | 262 | def execute(self): 263 | while not isinstance(self.cur(), EOF): 264 | try: 265 | super(Repl, self).execute() 266 | except ParseError, e: 267 | print e 268 | except: 269 | print traceback.format_exc() 270 | -------------------------------------------------------------------------------- /pitybas/io/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunixbochs/pitybas/d637eadf6f01bb5d4e9645c70d4a9e1ffb9eabce/pitybas/io/__init__.py -------------------------------------------------------------------------------- /pitybas/io/simple.py: -------------------------------------------------------------------------------- 1 | try: 2 | import readline 3 | except ImportError: 4 | pass 5 | 6 | from pitybas.parse import Parser 7 | from pitybas.common import ParseError 8 | 9 | class IO: 10 | def __init__(self, vm): 11 | self.vm = vm 12 | 13 | def __enter__(self): 14 | return self 15 | 16 | def __exit__(self, *args): 17 | pass 18 | 19 | def clear(self): 20 | print '-'*16 21 | 22 | def input(self, msg, is_str=False): 23 | while True: 24 | try: 25 | if msg: 26 | print msg, 27 | 28 | line = raw_input() 29 | if not is_str: 30 | val = Parser.parse_line(self.vm, line) 31 | else: 32 | val = line 33 | 34 | return val 35 | except ParseError: 36 | print 'ERR:DATA' 37 | print 38 | 39 | def getkey(self): 40 | raise NotImplementedError 41 | 42 | def output(self, x, y, msg): 43 | print msg 44 | 45 | def disp(self, msg=''): 46 | print msg 47 | 48 | def pause(self, msg=''): 49 | if msg: self.disp(msg) 50 | self.input('[press enter]', True) 51 | 52 | def menu(self, menu): 53 | # menu is a tuple of (title, (desc, label)), 54 | lookup = [] 55 | while True: 56 | i = 1 57 | 58 | for title, entries in menu: 59 | print '-[ %s ]-' % self.vm.get(title) 60 | for name, label in entries: 61 | print '%i: %s' % (i, self.vm.get(name)) 62 | lookup.append(label) 63 | i += 1 64 | 65 | choice = self.input('choice?', True) 66 | print 67 | if choice.isdigit() and 0 < int(choice) <= len(lookup): 68 | label = lookup[int(choice)-1] 69 | return label 70 | else: 71 | print 'invalid choice' 72 | -------------------------------------------------------------------------------- /pitybas/io/vt100.py: -------------------------------------------------------------------------------- 1 | try: 2 | import readline 3 | except ImportError: 4 | pass 5 | 6 | from pitybas.parse import Parser 7 | from pitybas.common import ParseError 8 | 9 | import select 10 | import sys 11 | import termios 12 | import time 13 | import tty 14 | 15 | keycodes = { 16 | 'left': 24, 17 | 'up': 25, 18 | 'right': 26, 19 | 'down': 34, 20 | 'A': 41, 21 | 'B': 42, 22 | 'C': 43, 23 | 'D': 51, 24 | 'E': 52, 25 | 'F': 53, 26 | 'G': 54, 27 | 'H': 55, 28 | 'I': 61, 29 | 'J': 62, 30 | 'K': 63, 31 | 'L': 64, 32 | 'M': 65, 33 | 'N': 71, 34 | 'O': 72, 35 | 'P': 73, 36 | 'Q': 74, 37 | 'R': 75, 38 | 'S': 81, 39 | 'T': 82, 40 | 'U': 83, 41 | 'V': 84, 42 | 'W': 85, 43 | 'X': 91, 44 | 'Y': 92, 45 | 'Z': 93, 46 | '"': 95, 47 | ' ': 102, 48 | ':': 103, 49 | '?': 104, 50 | 'enter': 105 51 | } 52 | 53 | class Delayed: 54 | ''' 55 | ensure at least duration time between __enter__ and __exit__ 56 | ''' 57 | def __init__(self, duration): 58 | self.duration = duration 59 | 60 | def __enter__(self): 61 | self.start = time.time() 62 | 63 | def __exit__(self, *args): 64 | diff = self.duration - (time.time() - self.start) 65 | if diff > 0: 66 | time.sleep(diff) 67 | 68 | class SafeIO: 69 | def __init__(self, fd): 70 | self.fd = fd 71 | 72 | def __enter__(self): 73 | self.old = termios.tcgetattr(self.fd) 74 | 75 | def __exit__(self, *args): 76 | termios.tcsetattr(self.fd, termios.TCSANOW, self.old) 77 | 78 | class VT: 79 | def __init__(self, width=16, height=8): 80 | self.width = width 81 | self.height = height 82 | self.clear() 83 | 84 | self.row, self.col = 1, 1 85 | self.pos_stack = [] 86 | 87 | def push(self): 88 | self.pos_stack.append((self.row, self.col)) 89 | 90 | def pop(self): 91 | self.row, self.col = self.pos_stack.pop() 92 | 93 | def e(self, *seqs): 94 | for seq in seqs: 95 | sys.stdout.write('\033'+seq) 96 | 97 | def clear(self, reset=True): 98 | self.e('[2J', '[H') 99 | self.row, self.col = 1, 1 100 | if reset: 101 | self.lines = [] 102 | for i in xrange(self.height): 103 | self.lines.append([' ']*self.width) 104 | 105 | def scroll(self): 106 | self.lines.pop(0) 107 | self.lines.append([' ']*self.width) 108 | self.row = max(1, self.row - 1) 109 | 110 | def flush(self): 111 | self.clear(reset=False) 112 | data = '\n'.join(''.join(line) for line in self.lines) + '\n' 113 | sys.stdout.write( 114 | data.encode(sys.stdout.encoding, 'replace') 115 | ) 116 | 117 | def move(self, row, col): 118 | self.row, self.col = row, col 119 | self.e('[%i;%iH' % (row, col)) 120 | 121 | def wrap(self, msg): 122 | msg = unicode(msg) 123 | first = self.width - self.col + 1 124 | first, msg = msg[:first], msg[first:] 125 | lines = [first] 126 | while msg: 127 | lines.append(msg[:self.width]) 128 | msg = msg[self.width:] 129 | 130 | return lines 131 | 132 | def write(self, msg, scroll=True): 133 | row, col = self.row, self.col 134 | self.e('[%i;%iH' % (row, col)) 135 | 136 | for line in self.wrap(msg): 137 | if row > self.height: 138 | row -= 1 139 | 140 | if scroll: 141 | self.scroll() 142 | row, col = self.row, self.col 143 | self.flush() 144 | self.move(row, 1) 145 | else: 146 | break 147 | 148 | for char in line: 149 | self.lines[row-1][col-1] = char 150 | char = char.encode(sys.stdout.encoding, 'replace') 151 | sys.stdout.write(char) 152 | col += 1 153 | 154 | col = 1 155 | row += 1 156 | sys.stdout.write('\n') 157 | 158 | self.row, self.col = row, col 159 | 160 | def output(self, row, col, msg): 161 | self.e('7') 162 | old = self.row, self.col 163 | self.move(row, col) 164 | self.write(msg) 165 | 166 | self.row, self.col = old 167 | self.e('8') 168 | 169 | def getch(self): 170 | fd = sys.stdin.fileno() 171 | 172 | with SafeIO(fd): 173 | tty.setraw(fd) 174 | 175 | with Delayed(0.1): 176 | ins, _, _ = select.select([sys.stdin], [], [], 0.1) 177 | if not ins: 178 | return 179 | 180 | ch = sys.stdin.read(1) 181 | if ch == '\003': 182 | raise KeyboardInterrupt 183 | 184 | if ch == '\033': 185 | # control sequence 186 | ch = sys.stdin.read(1) 187 | if ch == '[': 188 | ch = sys.stdin.read(1) 189 | if ch == 'A': 190 | return 'up' 191 | elif ch == 'B': 192 | return 'down' 193 | elif ch == 'C': 194 | return 'right' 195 | elif ch == 'D': 196 | return 'left' 197 | 198 | return None 199 | 200 | return ch 201 | 202 | class IO: 203 | def __init__(self, vm): 204 | self.vm = vm 205 | self.vt = VT() 206 | 207 | def __enter__(self): 208 | self.vt.e('[?25l') 209 | return self 210 | 211 | def __exit__(self, *args): 212 | self.vt.e('[?25h') 213 | 214 | def clear(self): 215 | self.vt.clear() 216 | 217 | def input(self, msg, is_str=False): 218 | # TODO: implement this in VT terms 219 | while True: 220 | try: 221 | self.vt.push() 222 | self.vt.move(9, 1) 223 | 224 | if msg: 225 | print msg, 226 | 227 | self.vt.e('[?25h') 228 | line = raw_input() 229 | self.vt.e('[?25l') 230 | 231 | self.vt.flush() 232 | self.vt.pop() 233 | if not is_str: 234 | val = Parser.parse_line(self.vm, line) 235 | else: 236 | val = line 237 | 238 | return val 239 | except ParseError: 240 | print 'ERR:DATA' 241 | print 242 | 243 | def getkey(self): 244 | key = self.vt.getch() 245 | if key in keycodes: 246 | return keycodes[key] 247 | else: 248 | return 0 249 | 250 | def output(self, row, col, msg): 251 | self.vt.output(row, col, msg) 252 | self.vt.flush() 253 | 254 | def disp(self, msg=''): 255 | if isinstance(msg, (complex, int, float)): 256 | msg = str(msg).rjust(16) 257 | 258 | self.vt.write(msg) 259 | 260 | def pause(self, msg=''): 261 | if msg: self.disp(msg) 262 | self.input('[press enter]', True) 263 | 264 | def menu(self, menu): 265 | # menu is a tuple of (title, (desc, label)), 266 | # TODO: implement this in VT terms 267 | 268 | lookup = [] 269 | while True: 270 | self.vt.clear(reset=False) 271 | i = 1 272 | 273 | for title, entries in menu: 274 | print '-[ %s ]-' % self.vm.get(title) 275 | for name, label in entries: 276 | print '%i: %s' % (i, self.vm.get(name)) 277 | lookup.append(label) 278 | i += 1 279 | 280 | self.vt.e('[?25h') 281 | choice = raw_input('choice? ') 282 | self.vt.e('[?25l') 283 | print 284 | if choice.isdigit() and 0 < int(choice) <= len(lookup): 285 | label = lookup[int(choice)-1] 286 | self.vt.flush() 287 | return label 288 | else: 289 | print 'invalid choice' 290 | 291 | -------------------------------------------------------------------------------- /pitybas/parse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import tokens 3 | from common import ParseError, is_number 4 | from expression import Expression, Bracketed, FunctionArgs, Tuple, ParenExpr, ListExpr, MatrixExpr 5 | from expression import Base as BaseExpression 6 | 7 | class Parser: 8 | LOOKUP = {} 9 | LOOKUP.update(tokens.Token.tokens) 10 | LOOKUP.update(tokens.Variable.tokens) 11 | LOOKUP.update(tokens.Function.tokens) 12 | 13 | SYMBOLS = [] 14 | TOKENS = tokens.Token.tokens.keys() 15 | VARIABLES = tokens.Variable.tokens.keys() 16 | FUNCTIONS = tokens.Function.tokens.keys() 17 | OPERATORS = tokens.Operator.tokens.keys() 18 | TOKENS += VARIABLES + FUNCTIONS 19 | 20 | TOKENS.sort() 21 | TOKENS.reverse() 22 | 23 | for t in TOKENS: 24 | if not t[0] in SYMBOLS and not t.isalpha(): 25 | SYMBOLS.append(t[0]) 26 | 27 | def __init__(self, source): 28 | self.source = unicode(source) 29 | self.length = len(source) 30 | self.pos = 0 31 | self.line = 0 32 | self.lines = [] 33 | 34 | self.stack = [] 35 | 36 | @staticmethod 37 | def parse_line(vm, line): 38 | if not line: return 39 | 40 | parser = Parser(line) 41 | parser.TOKENS = parser.VARIABLES + parser.FUNCTIONS + parser.OPERATORS 42 | 43 | parser.SYMBOLS = [] 44 | for t in parser.TOKENS: 45 | if not t[0] in parser.SYMBOLS and not t.isalpha(): 46 | parser.SYMBOLS.append(t[0]) 47 | 48 | return vm.get(parser.parse()[0][0]) 49 | 50 | def clean(self): 51 | self.source = self.source.replace('\r\n', '\n').replace('\r', '\n') 52 | 53 | def error(self, msg): 54 | raise ParseError(msg) 55 | 56 | def inc(self, n=1): 57 | self.pos += n 58 | 59 | def more(self, pos=None): 60 | if pos is None: pos = self.pos 61 | return pos < self.length 62 | 63 | def post(self): 64 | for line in self.lines: 65 | if line: 66 | new = [] 67 | expr = None 68 | for token in line: 69 | if token.priority > tokens.Pri.INVALID: 70 | expr = expr or Expression() 71 | expr.append(token) 72 | else: 73 | if expr: 74 | new.append(expr) 75 | 76 | expr = None 77 | new.append(token) 78 | 79 | if expr: 80 | new.append(expr) 81 | 82 | if new: 83 | # implied expressions need to be added to tuples in their entirety, instead of just their last element 84 | pops = [] 85 | for i in xrange(0, len(new)-1): 86 | e, t = new[i], new[i+1] 87 | if isinstance(e, Expression) and isinstance(t, Tuple): 88 | pops.append(i) 89 | e.append(t.contents[0].flatten()) 90 | t.contents[0] = e 91 | 92 | for p in reversed(sorted(pops)): 93 | new.pop(p) 94 | 95 | # tokens with the absorb mechanic can steal the next token from the line if it matches a list of types 96 | last = new[0] 97 | pops = [] 98 | for i in xrange(1, len(new)): 99 | token = new[i] 100 | if isinstance(token, last.absorbs): 101 | if isinstance(token, BaseExpression): 102 | token = token.flatten() 103 | 104 | last.absorb(token) 105 | pops.append(i) 106 | 107 | last = token 108 | 109 | for p in reversed(sorted(pops)): 110 | new.pop(p) 111 | 112 | yield new 113 | 114 | def parse(self): 115 | while self.more(): 116 | char = self.source[self.pos] 117 | result = None 118 | if self.lines and self.lines[-1]: 119 | token = self.lines[-1][-1] 120 | else: 121 | token = None 122 | 123 | if token and hasattr(token, 'dynamic') and hasattr(token.dynamic, '__call__') and token.dynamic(char): 124 | self.inc() 125 | continue 126 | elif char in ('\n', ':'): 127 | self.close_brackets() 128 | 129 | self.inc() 130 | self.line += 1 131 | continue 132 | elif char in ' \t': 133 | self.inc() 134 | continue 135 | elif char in '([{': 136 | if char == '(': 137 | cls = ParenExpr 138 | elif char == '[': 139 | if self.more(self.pos+1) and self.source[self.pos+1].isalpha(): 140 | result = self.matrix() 141 | else: 142 | cls = MatrixExpr 143 | elif char == '{': 144 | cls = ListExpr 145 | 146 | if result is None: 147 | self.stack.append(cls(char)) 148 | self.inc() 149 | continue 150 | elif char in ')]}': 151 | if self.stack: 152 | stacks = [] 153 | l = len(self.stack) 154 | for i in xrange(l): 155 | stack = self.stack.pop(l-i-1) 156 | if isinstance(stack, Bracketed): 157 | if stack.close(char): 158 | for s in stacks: 159 | stack.append(s) 160 | 161 | if not isinstance(stack, FunctionArgs): 162 | result = stack 163 | 164 | stack.finish() 165 | self.inc() 166 | break 167 | elif char != stack.end: 168 | self.error('tried to end \'%s\' with: "%s" (expecting "%s")' % (stack, char, stack.end)) 169 | else: 170 | stacks.append(stack) 171 | else: 172 | stacks.append(stack) 173 | else: 174 | self.error('encountered "%s" but we have no expression on the stack to terminate' % char) 175 | elif char == ',': 176 | if len(self.stack) > 1 and isinstance(self.stack[-2], Tuple)\ 177 | and not isinstance(self.stack[-1], Tuple): 178 | expr = self.stack.pop() 179 | tup = self.stack[-1] 180 | tup.append(expr) 181 | tup.sep() 182 | elif self.stack and isinstance(self.stack[-1], Tuple): 183 | self.stack[-1].sep() 184 | elif self.stack: 185 | raise ParseError('comma encountered with an unclosed non-tuple expression on the stack') 186 | else: 187 | if self.lines[-1]: 188 | token = self.lines[-1].pop() 189 | else: 190 | self.error('Encountered comma, but cannot find anything to put in the tuple') 191 | 192 | tup = Tuple() 193 | tup.append(token) 194 | self.stack.append(tup) 195 | tup.sep() 196 | 197 | if isinstance(self.stack[-1], FunctionArgs): 198 | self.stack.append(Expression()) 199 | 200 | self.inc() 201 | continue 202 | elif '0' <= char <= '9' or char == '.'\ 203 | or isinstance(self.token(sub=True, inc=False), tokens.Minus) and self.number(test=True): 204 | result = tokens.Value(self.number()) 205 | elif char in u'l∟' and self.more(self.pos+1) and self.source[self.pos+1] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789': 206 | result = self.list() 207 | elif char.isalpha(): 208 | result = self.token() 209 | elif char in self.SYMBOLS: 210 | result = self.symbol() 211 | elif char == '"': 212 | result = tokens.Value(self.string()) 213 | else: 214 | self.error('could not tokenize: %s' % repr(char)) 215 | 216 | if isinstance(result, tokens.Stor): 217 | self.close_brackets() 218 | 219 | if result is not None: 220 | self.add(result) 221 | 222 | argument = False 223 | if isinstance(result, tokens.Function): 224 | argument = True 225 | 226 | if isinstance(result, (tokens.List, tokens.Matrix)): 227 | if self.more() and self.source[self.pos] == '(': 228 | self.inc() 229 | argument = True 230 | 231 | # we were told to push the stack into argument mode 232 | if argument: 233 | args = FunctionArgs('(') 234 | self.stack.append(args) 235 | self.stack.append(Expression()) 236 | result.absorb(args) 237 | 238 | self.close_brackets() 239 | return [line for line in self.post()] 240 | 241 | def add(self, token): 242 | # TODO: cannot add Pri.INVALID unless there's no expr on the stack 243 | if self.stack: 244 | stack = self.stack[-1] 245 | stack.append(token) 246 | elif not isinstance(token, FunctionArgs): 247 | while self.line >= len(self.lines): 248 | self.lines.append([]) 249 | 250 | self.lines[self.line].append(token) 251 | 252 | def close_brackets(self): 253 | while self.stack: 254 | self.add(self.stack.pop()) 255 | 256 | def symbol(self): 257 | token = self.token(True) 258 | if token: 259 | return token 260 | else: 261 | char = self.source[self.pos] 262 | if char in self.LOOKUP: 263 | self.inc() 264 | return self.LOOKUP[char]() 265 | else: 266 | # a second time to throw the error 267 | self.token() 268 | 269 | def token(self, sub=False, inc=True): 270 | remaining = self.source[self.pos:] 271 | for token in self.TOKENS: 272 | if remaining.startswith(token): 273 | if inc: 274 | self.inc(len(token)) 275 | return self.LOOKUP[token]() 276 | else: 277 | if not sub: 278 | near = remaining[:8].split('\n',1)[0] 279 | self.error('no token found at pos %i near "%s"' % (self.pos, repr(near))) 280 | 281 | def number(self, dot=True, test=False, inc=True): 282 | num = '' 283 | first = True 284 | pos = self.pos 285 | while self.more(pos): 286 | char = self.source[pos] 287 | if char == '-' and first: pass 288 | elif not char.isdigit(): 289 | break 290 | 291 | first = False 292 | num += char 293 | pos += 1 294 | 295 | if char == '.' and dot: 296 | num += '.' 297 | pos += 1 298 | 299 | self.pos, tmp = pos, self.pos 300 | try: 301 | num += str(self.number(dot=False)) 302 | except ParseError: 303 | pass 304 | 305 | pos, self.pos = self.pos, tmp 306 | 307 | if inc and not test: self.pos = pos 308 | 309 | if is_number(num): 310 | if test: return True 311 | try: 312 | n = int(num) 313 | except ValueError: 314 | n = float(num) 315 | 316 | return n 317 | else: 318 | if test: return False 319 | lines = self.source[:pos] 320 | line = lines.count('\n') + 1 321 | col = max(self.pos - lines.rfind('\n'), 0) 322 | raise ParseError('invalid number ending at {}:{}: {}'.format(line, col, num)) 323 | 324 | def string(self): 325 | ret = '' 326 | self.inc() 327 | 328 | while self.more(): 329 | char = self.source[self.pos] 330 | if char == '"': 331 | self.inc() 332 | break 333 | 334 | elif char == '\n': 335 | break 336 | 337 | ret += char 338 | self.inc() 339 | 340 | return ret 341 | 342 | def all(self, match='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): 343 | ret = '' 344 | while self.more(): 345 | char = self.source[self.pos] 346 | if char in match: 347 | ret += char 348 | self.inc() 349 | else: 350 | break 351 | 352 | return ret 353 | 354 | def list(self): 355 | self.inc() 356 | name = self.all() 357 | 358 | return tokens.List(name) 359 | 360 | def matrix(self): 361 | self.inc() 362 | name = self.all() 363 | 364 | assert self.more() and self.source[self.pos] == ']' 365 | self.inc() 366 | 367 | return tokens.Matrix(name) 368 | -------------------------------------------------------------------------------- /pitybas/tokens.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | import decimal 4 | import fractions 5 | import math 6 | import random 7 | import string 8 | 9 | from common import Pri, ExecutionError, StopError, ReturnError 10 | from expression import Tuple, Expression, Arguments, ListExpr, MatrixExpr 11 | 12 | # helpers 13 | 14 | def add_class(name, *args, **kwargs): 15 | globals()[name] = type(name, args, kwargs) 16 | 17 | # decorators 18 | 19 | def get(f): 20 | def run(self, vm, left, right): 21 | return f(self, vm, vm.get(left), vm.get(right)) 22 | 23 | return run 24 | 25 | # magic classes 26 | 27 | class Tracker(type): 28 | def __new__(self, name, bases, attrs): 29 | if not 'token' in attrs: 30 | attrs['token'] = name 31 | 32 | attrs.update({ 33 | 'can_run': False, 34 | 'can_get': False, 35 | 'can_set': False, 36 | 'can_fill_left': False, 37 | 'can_fill_right': False 38 | }) 39 | 40 | cls = type.__new__(self, name, bases, attrs) 41 | 42 | if 'run' in dir(cls): 43 | cls.can_run = True 44 | 45 | if 'get' in dir(cls): 46 | cls.can_get = True 47 | 48 | if 'set' in dir(cls): 49 | cls.can_set = True 50 | 51 | if 'fill_left' in dir(cls): 52 | cls.can_fill_left = True 53 | 54 | if 'fill_right' in dir(cls): 55 | cls.can_fill_right = True 56 | 57 | return cls 58 | 59 | def __init__(cls, name, bases, attrs): 60 | if bases: 61 | bases[-1].add(cls, name, attrs) 62 | 63 | class InvalidOperation(Exception): 64 | pass 65 | 66 | class Parent: 67 | __metaclass__ = Tracker 68 | 69 | @classmethod 70 | def add(cls, sub, name, attrs): 71 | if 'token' in attrs: 72 | name = attrs['token'] 73 | 74 | if name and not cls == Parent: 75 | cls.tokens[name] = sub 76 | 77 | can_run = False 78 | can_get = False 79 | can_set = False 80 | absorbs = () 81 | arg = None 82 | 83 | # used for evaluation order inside expressions 84 | priority = Pri.INVALID 85 | 86 | def absorb(self, token): 87 | if isinstance(token, Expression): 88 | flat = token.flatten() 89 | for typ in self.absorbs: 90 | if isinstance(flat, typ): 91 | token = flat 92 | 93 | self.arg = token 94 | self.absorbs = () 95 | 96 | def __cmp__(self, token): 97 | try: 98 | if self.priority < token.priority: 99 | return -1 100 | elif self.priority == token.priority: 101 | return 0 102 | elif self.priority > token.priority: 103 | return 1 104 | else: 105 | raise AttributeError 106 | except AttributeError: 107 | return NotImplemented 108 | 109 | def __repr__(self): 110 | return repr(self.token) 111 | 112 | class Stub: 113 | @classmethod 114 | def add(cls, sub, name, attrs): pass 115 | 116 | class Token(Parent): 117 | tokens = {} 118 | 119 | def run(self, vm): 120 | raise NotImplementedError 121 | 122 | def __repr__(self): 123 | if self.arg: 124 | return '%s %s' % (repr(self.token), repr(self.arg)) 125 | else: 126 | return repr(self.token) 127 | 128 | class StubToken(Token, Stub): 129 | def run(self, vm): pass 130 | 131 | class Variable(Parent): 132 | priority = Pri.NONE 133 | tokens = {} 134 | 135 | def get(self, vm): 136 | raise NotImplementedError 137 | 138 | class Function(Parent): 139 | priority = Pri.NONE 140 | tokens = {} 141 | 142 | absorbs = (Arguments,) 143 | 144 | @classmethod 145 | def add(cls, sub, name, attrs): 146 | if 'token' in attrs: 147 | name = attrs['token'] 148 | 149 | if name: 150 | name += '(' 151 | cls.tokens[name] = sub 152 | 153 | def __init__(self): 154 | if self.can_run: 155 | self.priority = Pri.INVALID 156 | 157 | Parent.__init__(self) 158 | 159 | def get(self, vm): 160 | return self.call(vm, vm.get(self.arg)) 161 | 162 | def call(self, vm, args): 163 | raise NotImplementedError 164 | 165 | def __repr__(self): 166 | if self.arg: 167 | return '%s%s' % (repr(self.token), repr(self.arg).replace('A', '', 1)) 168 | else: 169 | return '%s()' % repr(self.token) 170 | 171 | class StubFunction(Function, Stub): 172 | def call(self, vm, args): pass 173 | 174 | # variables 175 | 176 | class EOF(Token, Stub): 177 | def run(self, vm): 178 | raise StopError 179 | 180 | class Const(Variable, Stub): 181 | def __init__(self, value): 182 | super(Const, self).__init__() 183 | self.value = value 184 | 185 | def get(self, vm): 186 | return self.value 187 | 188 | class Const(Variable, Stub): 189 | value = None 190 | 191 | def set(self, vm, value): raise InvalidOperation 192 | def get(self, vm): return self.value 193 | 194 | class Value(Const, Stub): 195 | def __init__(self, value): 196 | self.value = value 197 | Variable.__init__(self) 198 | 199 | def get(self, vm): return self.value 200 | 201 | def __repr__(self): 202 | return repr(self.value) 203 | 204 | # list/matrix 205 | 206 | class List(Variable, Stub): 207 | absorbs = (Arguments,) 208 | 209 | def __init__(self, name=None): 210 | self.name = name 211 | super(List, self).__init__() 212 | 213 | def dim(self, vm, value=None): 214 | if value is not None: 215 | assert isinstance(value, (int, long)) 216 | 217 | try: 218 | l = vm.get_list(self.name) 219 | except KeyError: 220 | l = [] 221 | 222 | l = l[:value] + ([0] * (value - len(l))) 223 | vm.set_list(self.name, l) 224 | else: 225 | return len(vm.get(self)) 226 | 227 | def get(self, vm): 228 | if self.arg: 229 | arg = vm.get(self.arg)[0] 230 | assert isinstance(arg, (int, long)) 231 | return vm.get_list(self.name)[arg-1] 232 | 233 | return vm.get_list(self.name) 234 | 235 | def set(self, vm, value): 236 | if self.arg: 237 | arg = vm.get(self.arg)[0] 238 | assert isinstance(arg, (int, long)) 239 | assert isinstance(value, (int, long, float, complex)) 240 | 241 | l = vm.get_list(self.name) 242 | i = arg - 1 243 | 244 | if i == len(l): 245 | l.append(value) 246 | else: 247 | l[i] = value 248 | vm.set_list(self.name, l) 249 | else: 250 | assert isinstance(value, list) 251 | vm.set_list(self.name, value[:]) 252 | 253 | return value 254 | 255 | def __repr__(self): 256 | if self.arg: 257 | return 'l%s(%s)' % (self.name, self.arg) 258 | 259 | return 'l%s' % self.name 260 | 261 | class ListToken(List): 262 | token = u'∟' 263 | 264 | class Matrix(Variable, Stub): 265 | absorbs = (Arguments,) 266 | 267 | def __init__(self, name=None): 268 | self.name = name 269 | 270 | def dim(self, vm, value=None): 271 | if value is not None: 272 | assert isinstance(value, list) and len(value) == 2 273 | 274 | a, b = value 275 | try: 276 | m = vm.get_matrix(self.name) 277 | except KeyError: 278 | m = [[]] 279 | 280 | m = m[:a] 281 | for i in xrange(len(m), a): 282 | n = ([0] * b) 283 | m.append(n) 284 | 285 | m = [l[:b] + ([0] * (b - len(l))) for l in m] 286 | vm.set_matrix(self.name, m) 287 | else: 288 | val = vm.get_matrix(self.name) 289 | return [len(val), len(val[0])] 290 | 291 | def get(self, vm): 292 | if self.arg: 293 | arg = vm.get(self.arg) 294 | assert isinstance(arg, list) and len(arg) == 2 295 | return vm.get_matrix(self.name)[arg[0]-1][arg[1]-1] 296 | 297 | return vm.get_matrix(self.name) 298 | 299 | def set(self, vm, value): 300 | if self.arg: 301 | arg = vm.get(self.arg) 302 | assert isinstance(arg, list) and len(arg) == 2 303 | assert isinstance(value, (int, long, float, complex)) 304 | 305 | m = vm.get_matrix(self.name) 306 | m[arg[0]-1][arg[1]-1] = value 307 | else: 308 | assert isinstance(value, list) 309 | vm.set_matrix(self.name, value) 310 | 311 | return value 312 | 313 | def __repr__(self): 314 | return '[%s]' % self.name 315 | 316 | class dim(Function): 317 | def get(self, vm): 318 | assert self.arg and len(self.arg) == 1 319 | 320 | arg = self.arg.contents[0].flatten() 321 | assert isinstance(arg, (List, Matrix)) 322 | return arg.dim(vm) 323 | 324 | def set(self, vm, value): 325 | assert self.arg and len(self.arg) == 1 326 | 327 | arg = self.arg.contents[0].flatten() 328 | assert isinstance(arg, (List, Matrix)) 329 | name = arg.name 330 | 331 | arg.dim(vm, value) 332 | return value 333 | 334 | class augment(Function): 335 | def get(self, vm): 336 | assert self.arg and len(self.arg) == 2 337 | a = self.arg.contents[0].flatten() 338 | b = self.arg.contents[1].flatten() 339 | if isinstance(a, (List, ListExpr)) and isinstance(b, (List, ListExpr)): 340 | return vm.get(a) + vm.get(b) 341 | elif isinstance(a, (Matrix, MatrixExpr)) and isinstance(b, (Matrix, MatrixExpr)): 342 | a = vm.get(a) 343 | b = vm.get(b) 344 | assert len(a) == len(b) 345 | return [left + b[i] for i, left in enumerate(a)] 346 | else: 347 | raise ExecutionError('augment() requires List, List or Matrix, Matrix') 348 | 349 | class Fill(Function): 350 | def run(self, vm): 351 | assert self.arg and len(self.arg) == 2 352 | num, var = self.arg.contents 353 | var = var.flatten() 354 | num = vm.get(num) 355 | 356 | assert isinstance(num, (int, long, float, complex)) 357 | assert isinstance(var, (List, Matrix)) 358 | 359 | if isinstance(var, List): 360 | l = [num for i in xrange(len(vm.get(var)))] 361 | var.set(vm, l) 362 | elif isinstance(var, Matrix): 363 | m = [] 364 | o = vm.get(var) 365 | for a in o: 366 | c = [] 367 | for b in a: 368 | c.append(num) 369 | 370 | m.append(c) 371 | 372 | var.set(vm, m) 373 | 374 | class seq(Function): 375 | def get(self, vm): 376 | assert self.arg and len(self.arg) in (4, 5) 377 | arg = self.arg.contents 378 | expr = arg[0] 379 | var = arg[1].flatten() 380 | assert isinstance(var, Variable) 381 | assert isinstance(expr, Expression) 382 | step = 1 383 | if len(arg) == 5: 384 | step = vm.get(arg[4]) 385 | out = [] 386 | start, end = vm.get(arg[2]), vm.get(arg[3]) 387 | for i in xrange(start, end + 1, step): 388 | vm.set_var(var.token, i) 389 | out.append(vm.get(expr)) 390 | return out 391 | 392 | class Sum(Function): 393 | token = 'sum' 394 | 395 | def get(self, vm): 396 | assert self.arg and len(self.arg) == 1 397 | arg = self.arg.flatten() 398 | return sum(vm.get(arg)) 399 | 400 | class Ans(Const): 401 | def get(self, vm): return vm.get_var('Ans') 402 | 403 | class Pi(Const): 404 | token = u'π' 405 | value = math.pi 406 | 407 | class e(Const): 408 | token = 'e' 409 | value = math.e 410 | 411 | class SimpleVar(Variable, Stub): 412 | def set(self, vm, value): return vm.set_var(self.token, value) 413 | def get(self, vm): return vm.get_var(self.token) 414 | 415 | class NumVar(SimpleVar, Stub): 416 | def get(self, vm): 417 | return vm.get_var(self.token, 0) 418 | 419 | class StrVar(SimpleVar, Stub): 420 | def get(self, vm): 421 | return vm.get_var(self.token, '') 422 | 423 | class Theta(NumVar): 424 | token = u'\u03b8' 425 | 426 | class THETA(NumVar): 427 | def set(self, vm, value): 428 | return vm.set_var(Theta.token, value) 429 | 430 | def get(self, vm): 431 | return vm.get_var(Theta.token) 432 | 433 | for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': 434 | add_class(c, NumVar) 435 | 436 | class Str(SimpleVar, Stub): 437 | pass 438 | 439 | for i in xrange(10): 440 | add_class('Str%i' % i, StrVar) 441 | 442 | # operators 443 | 444 | class Stor(Token): 445 | token = u'→' 446 | priority = Pri.SET 447 | 448 | def run(self, vm, left, right): 449 | ans = vm.get(left) 450 | right.set(vm, ans) 451 | return ans 452 | 453 | class Store(Stor): token = '->' 454 | 455 | class Operator(Token, Stub): 456 | @get 457 | def run(self, vm, left, right): 458 | return self.op(left, right) 459 | 460 | class FloatOperator(Operator, Stub): 461 | @get 462 | def run(self, vm, left, right): 463 | # TODO: be smarter about when to coerce to float 464 | if isinstance(left, (int, long)) or isinstance(right, (int, long)): 465 | decimal.getcontext().prec = max(len(str(left)), len(str(right))) 466 | left = decimal.Decimal(left) 467 | right = decimal.Decimal(right) 468 | 469 | ans = self.op(left, right) 470 | # 14 digits of precision? 471 | if abs(ans - int(ans)) < 0.00000000000001: 472 | ans = int(ans) 473 | 474 | return ans 475 | 476 | class AddSub(Operator, Stub): priority = Pri.ADDSUB 477 | class MultDiv(FloatOperator, Stub): priority = Pri.MULTDIV 478 | class Exponent(Operator, Stub): priority = Pri.EXPONENT 479 | class RightExponent(Exponent, Stub): 480 | def fill_right(self): 481 | return Value(None) 482 | 483 | class Bool(Operator, Stub): 484 | priority = Pri.BOOL 485 | 486 | @get 487 | def run(self, vm, left, right): 488 | return int(bool(self.bool(left, right))) 489 | 490 | # a Function expecting a single Expression as the argument 491 | class MathExprFunction(Function, Stub): 492 | def get(self, vm): 493 | assert len(self.arg) == 1 494 | args = vm.get(self.arg) 495 | return self.call(vm, args[0]) 496 | 497 | class Logic(Bool): priority = Pri.LOGIC 498 | 499 | # math 500 | 501 | class Plus(AddSub): 502 | token = '+' 503 | 504 | def op(self, left, right): 505 | return left + right 506 | 507 | class Minus(AddSub): 508 | token = '-' 509 | 510 | def op(self, left, right): 511 | return left - right 512 | 513 | class Mult(MultDiv): 514 | token = '*' 515 | 516 | def op(self, left, right): 517 | return left * right 518 | 519 | class Div(MultDiv): 520 | token = '/' 521 | 522 | def op(self, left, right): 523 | return left / right 524 | 525 | class Pow(Exponent): 526 | token = '^' 527 | 528 | def op(self, left, right): 529 | return left ** right 530 | 531 | class transpose(RightExponent): 532 | token = '_T' 533 | 534 | def op(self, left, right): 535 | vm = {'tmp': left} 536 | rows, cols = Matrix('tmp').dim(vm) 537 | out = [[0] * rows for i in xrange(cols)] 538 | for y in xrange(rows): 539 | for x in xrange(cols): 540 | out[x][y] = left[y][x] 541 | return out 542 | 543 | # TODO: -¹, ², ³, √(, ³√(, ×√ 544 | class Square(RightExponent): 545 | token = u'²' 546 | 547 | def op(self, left, right): 548 | return left ** 2 549 | 550 | class Cube(RightExponent): 551 | token = u'³' 552 | 553 | def op(self, left, right): 554 | return left ** 3 555 | 556 | class Sqrt(MathExprFunction): 557 | token = u'√' 558 | 559 | def call(self, vm, arg): 560 | return math.sqrt(arg) 561 | 562 | class sqrt(Sqrt): pass 563 | 564 | class CubeRoot(MathExprFunction): 565 | token = u'³√' 566 | 567 | def call(self, vm, arg): 568 | # TODO: proper accuracy. maybe write a numpy addon, to increase accuracy if numpy is installed? 569 | 570 | # round to within our accuracy bounds 571 | # this allows us to reverse a cube properly, as long as we don't pass 14 significant digits 572 | i = arg ** (1.0/3) 573 | places = 14 - len(str(int(math.floor(i)))) 574 | if places > 0: 575 | return round(i, places) 576 | else: 577 | # oh well 578 | return i 579 | 580 | class SciNot(Operator): 581 | priority = Pri.EXPONENT 582 | token = u'ᴇ' 583 | 584 | def fill_left(self): 585 | return Value(1) 586 | 587 | def op(self, left, right): 588 | return left * (10 ** right) 589 | 590 | class Abs(MathExprFunction): 591 | token = 'abs' 592 | 593 | def call(self, vm, arg): 594 | return abs(arg) 595 | 596 | class gcd(Function): 597 | def call(self, vm, args): 598 | assert len(args) == 1 and isinstance(args[0], list) or len(args) == 2 599 | if len(args) == 1: 600 | return reduce(lambda a, b: fractions.gcd(a, b), args[0]) 601 | else: 602 | return fractions.gcd(*args) 603 | 604 | # TODO: list support 605 | @staticmethod 606 | def gcd(a, b): 607 | while b: 608 | a, b = b, (a % b) 609 | return a 610 | 611 | class lcm(Function): 612 | def call(self, vm, args): 613 | assert len(args) == 2 614 | a, b = args 615 | if isinstance(a, list): 616 | a = self.lcm_list(*a) 617 | if isinstance(b, list): 618 | a = self.lcm_list(*b) 619 | return self.lcm(a, b) 620 | 621 | @staticmethod 622 | def lcm(a, b): 623 | return a * b / gcd.gcd(a, b) 624 | 625 | @classmethod 626 | def lcm_list(cls, *args): 627 | args = list(args) 628 | a = args.pop(0) 629 | while args: 630 | b = args.pop(0) 631 | a = cls.lcm(a, b) 632 | return a 633 | 634 | class Min(Function): 635 | token = 'min' 636 | 637 | def call(self, vm, args): 638 | assert len(args) == 2 639 | return min(*args) 640 | 641 | class Max(Function): 642 | token = 'max' 643 | 644 | def call(self, vm, args): 645 | assert len(args) in (1, 2) 646 | if len(args) == 1: 647 | assert isinstance(args[0], list) 648 | return max(args[0]) 649 | else: 650 | a1, a2 = args 651 | if not isinstance(a1, list): 652 | a1 = [a1] 653 | if not isinstance(a2, list): 654 | a2 = [a2] 655 | return max(a1 + a2) 656 | 657 | class Round(Function): 658 | token = 'round' 659 | 660 | def call(self, vm, args): 661 | assert len(args) in (1, 2) 662 | 663 | if len(args) == 2: 664 | places = args[1] 665 | else: 666 | places = 9 667 | 668 | return round(args[0], places) 669 | 670 | class Int(MathExprFunction): 671 | token = 'int' 672 | 673 | def call(self, vm, arg): 674 | return math.floor(arg) 675 | 676 | class iPart(MathExprFunction): 677 | def call(self, vm, arg): 678 | return int(arg) 679 | 680 | class fPart(MathExprFunction): 681 | def call(self, vm, arg): 682 | return math.modf(arg)[1] 683 | 684 | class floor(MathExprFunction): 685 | def call(self, vm, arg): 686 | return math.floor(arg) 687 | 688 | class ceiling(MathExprFunction): 689 | def call(self, vm, arg): 690 | return math.ceil(arg) 691 | 692 | class mod(Function): 693 | def call(self, vm, args): 694 | assert len(args) == 2 695 | return args[0] % args[1] 696 | 697 | class expr(MathExprFunction): 698 | def call(self, vm, arg): 699 | from parse import Parser, ParseError 700 | return Parser.parse_line(vm, arg) 701 | 702 | # trig 703 | 704 | class sin(MathExprFunction): 705 | def call(self, vm, arg): return math.sin(arg) 706 | 707 | class cos(MathExprFunction): 708 | def call(self, vm, arg): return math.cos(arg) 709 | 710 | class tan(MathExprFunction): 711 | def call(self, vm, arg): return math.tan(arg) 712 | 713 | # TODO: subclass these inverse functions with the unicode -1 token, and probably add support for that in the parser for ints too 714 | class asin(MathExprFunction): 715 | token = 'sin-1' 716 | 717 | def call(self, vm, arg): return math.asin(arg) 718 | 719 | class acos(MathExprFunction): 720 | token = 'cos-1' 721 | 722 | def call(self, vm, arg): return math.acos(arg) 723 | 724 | class atan(MathExprFunction): 725 | token = 'tan-1' 726 | 727 | def call(self, vm, arg): return math.atan(arg) 728 | 729 | class sinh(MathExprFunction): 730 | def call(self, vm, arg): return math.sinh(arg) 731 | 732 | class cosh(MathExprFunction): 733 | def call(self, vm, arg): return math.cosh(arg) 734 | 735 | class tanh(MathExprFunction): 736 | def call(self, vm, arg): return math.tanh(arg) 737 | 738 | class asinh(MathExprFunction): 739 | token = 'sin-1' 740 | 741 | def call(self, vm, arg): return math.asinh(arg) 742 | 743 | class acosh(MathExprFunction): 744 | token = 'cos-1' 745 | 746 | def call(self, vm, arg): return math.acosh(arg) 747 | 748 | class atanh(MathExprFunction): 749 | token = 'tan-1' 750 | 751 | def call(self, vm, arg): return math.atanh(arg) 752 | 753 | # probability 754 | 755 | class nPr(Operator): 756 | # TODO: nPr and nCr should support lists 757 | priority = Pri.PROB 758 | 759 | def op(self, left, right): 760 | return math.factorial(left) / math.factorial((left - right)) 761 | 762 | class nCr(Operator): 763 | priority = Pri.PROB 764 | 765 | def op(self, left, right): 766 | return math.fact(left) / (math.fact(right) * math.fact((left - right))) 767 | 768 | class Factorial(Exponent): 769 | token = '!' 770 | 771 | def op(self, left, right): 772 | return math.factorial(left) 773 | 774 | def fill_right(self): 775 | return Value(None) 776 | 777 | # random numbers 778 | 779 | class rand(Variable): 780 | def get(self, vm): 781 | return random.random() 782 | 783 | def set(self, vm, value): 784 | random.seed(value) 785 | 786 | class rand(Function): 787 | def call(self, vm, args): 788 | assert len(args) == 1 789 | return [random.random() for i in xrange(args[0])] 790 | 791 | class randInt(Function): 792 | def call(self, vm, args): 793 | assert len(args) in (2, 3) 794 | 795 | if args[0] > args[1]: 796 | args[0], args[1] = args[1], args[0] 797 | 798 | if len(args) == 2: 799 | return random.randint(*args) 800 | 801 | return [random.randint(*args[:2]) for i in xrange(args[2])] 802 | 803 | class randNorm(Function): 804 | def call(self, vm, args): 805 | assert len(args) in (2, 3) 806 | 807 | if len(args) == 3: 808 | args, n = args[:2], args[2] 809 | else: 810 | n = 1 811 | 812 | return [random.normalvariate(*args) for i in xrange(n)] 813 | 814 | class randBin(Function): 815 | def call(self, vm, args): 816 | raise NotImplementedError # numpy.random has a binomial distribution, or I could write my own... 817 | 818 | class randM(Function): 819 | def call(self, vm, args): 820 | raise NotImplementedError # I don't know how I'm going to do lists and matricies yet 821 | 822 | # boolean 823 | 824 | class And(Bool): 825 | token = 'and' 826 | 827 | def bool(self, left, right): 828 | return left and right 829 | 830 | class Or(Bool): 831 | token = 'or' 832 | 833 | def bool(self, left, right): 834 | return left or right 835 | 836 | class xor(Bool): 837 | def bool(self, left, right): 838 | return left ^ right 839 | 840 | class Not(Function): 841 | token = 'not' 842 | 843 | def get(self, vm): 844 | args = vm.get(self.arg) 845 | assert len(args) == 1 846 | 847 | return int(bool(not args[0])) 848 | 849 | # logic 850 | 851 | class Equals(Logic): 852 | token = '=' 853 | 854 | def bool(self, left, right): 855 | return left == right 856 | 857 | class NotEquals(Logic): 858 | token = '~=' 859 | 860 | def bool(self, left, right): 861 | return left != right 862 | 863 | class NotEqualsToken(NotEquals): 864 | token = u'≠' 865 | 866 | class LessThan(Logic): 867 | token = '<' 868 | 869 | def bool(self, left, right): 870 | return left < right 871 | 872 | class GreaterThan(Logic): 873 | token = '>' 874 | 875 | def bool(self, left, right): 876 | return left > right 877 | 878 | class LessOrEquals(Logic): 879 | token = '<=' 880 | 881 | def bool(self, left, right): 882 | return left <= right 883 | 884 | class LessOrEqualsToken(LessOrEquals): 885 | token = u'≤' 886 | 887 | class GreaterOrEquals(Logic): 888 | token = '>=' 889 | 890 | def bool(self, left, right): 891 | return left >= right 892 | 893 | class GreaterOrEqualsToken(GreaterOrEquals): 894 | token = u'≥' 895 | 896 | # string manipulation 897 | 898 | class inString(Function): 899 | def call(self, vm, args): 900 | assert len(args) == 2 or len(args) == 3 and isinstance(args[2], (int, long)) 901 | assert isinstance(args[0], basestring) and isinstance(args[1], basestring) 902 | haystack = args[0] 903 | needle = args[1] 904 | skip = 0 905 | if len(args) == 3: 906 | skip = args[2] 907 | return haystack.find(needle, skip) 908 | 909 | class sub(Function): 910 | def call(self, vm, args): 911 | assert len(args) == 3 912 | s = args[0] 913 | a, b = args[1], args[2] 914 | assert a > 0 and b < len(s) 915 | return s[a - 1:a - 1 + b] 916 | 917 | class length(Function): 918 | def call(self, vm, args): 919 | assert len(args) == 1 920 | return len(args[0]) 921 | 922 | # control flow 923 | 924 | class Block(StubToken): 925 | absorbs = (Expression, Value) 926 | 927 | def find_end(self, vm, or_else=False, cur=False): 928 | tokens = vm.find(Block, Then, Else, End, wrap=False) 929 | blocks = [] 930 | thens = 0 931 | for row, col, token in tokens: 932 | if not cur and token is self: 933 | continue 934 | 935 | if isinstance(token, If): 936 | continue 937 | 938 | if isinstance(token, Then): 939 | thens += 1 940 | blocks.append(token) 941 | elif isinstance(token, Block): 942 | blocks.append(token) 943 | elif isinstance(token, End): 944 | if (thens == 0 or not or_else) and not blocks: 945 | return row, col, token 946 | else: 947 | b = blocks.pop(0) 948 | if isinstance(b, Then): 949 | thens -= 1 950 | elif or_else and isinstance(token, Else): 951 | if thens == 0: 952 | return row, col, token 953 | 954 | class If(Block): 955 | def run(self, vm): 956 | if self.arg == None: 957 | raise ExecutionError('If statement without condition') 958 | 959 | true = bool(vm.get(self.arg)) 960 | 961 | cur = vm.cur() 962 | if isinstance(cur, Then): 963 | vm.push_block() 964 | vm.inc() 965 | 966 | if not true: 967 | end = self.find_end(vm, or_else=True) 968 | if end: 969 | row, col, end = end 970 | if isinstance(end, End): 971 | vm.pop_block() 972 | 973 | vm.goto(row, col) 974 | vm.inc() 975 | else: 976 | raise StopError('If/Then could not find End on negative expression') 977 | elif true: 978 | vm.run(cur) 979 | else: 980 | vm.inc_row() 981 | 982 | def resume(self, vm, row, col): pass 983 | def stop(self, vm, row, col): pass 984 | 985 | class Then(Token): 986 | def run(self, vm): 987 | raise ExecutionError('cannot execute a standalone Then statement') 988 | 989 | class Else(Token): 990 | def run(self, vm): 991 | row, col, block = vm.pop_block() 992 | assert isinstance(block, If) 993 | end = block.find_end(vm) 994 | if end: 995 | row, col, end = end 996 | else: 997 | raise StopError('Else could not find End') 998 | 999 | vm.goto(row, col) 1000 | vm.inc() 1001 | 1002 | class Loop(Block, Stub): 1003 | def run(self, vm): 1004 | if self.arg == None: 1005 | raise ExecutionError('%s statement without condition' % self.token) 1006 | 1007 | row, col, _ = vm.running[-1] 1008 | self.resume(vm, row, col) 1009 | 1010 | def loop(self, vm): 1011 | return True 1012 | 1013 | def resume(self, vm, row, col): 1014 | vm.goto(row, col) 1015 | if self.loop(vm): 1016 | vm.push_block((row, col, self)) 1017 | vm.inc() 1018 | else: 1019 | self.stop(vm, row, col) 1020 | 1021 | def stop(self, vm, row, col): 1022 | vm.goto(row, col) 1023 | end = self.find_end(vm) 1024 | if end: 1025 | row, col, end = end 1026 | else: 1027 | raise StopError('%s could not find End' % self.token) 1028 | 1029 | vm.goto(row, col) 1030 | vm.inc() 1031 | 1032 | class While(Loop): 1033 | def loop(self, vm): 1034 | return bool(vm.get(self.arg)) 1035 | 1036 | class Repeat(Loop): 1037 | def loop(self, vm): 1038 | return not bool(vm.get(self.arg)) 1039 | 1040 | class For(Loop, Function): 1041 | pos = None 1042 | 1043 | def loop(self, vm): 1044 | if len(self.arg) in (3, 4): 1045 | var = self.arg.contents[0] 1046 | args = self.arg.contents[1:] 1047 | 1048 | if len(args) == 3: 1049 | inc = vm.get(args[2]) 1050 | args = args[:2] 1051 | else: 1052 | inc = 1 1053 | forward = inc > 0 1054 | start, end = args 1055 | 1056 | if self.pos is None: 1057 | self.pos = vm.get(start) 1058 | else: 1059 | self.pos += inc 1060 | 1061 | var.set(vm, self.pos) 1062 | if forward and self.pos > vm.get(end) or not forward and self.pos < vm.get(end): 1063 | return False 1064 | else: 1065 | return True 1066 | else: 1067 | raise ExecutionError('incorrect arguments to For loop') 1068 | 1069 | def stop(self, vm, row, col): 1070 | self.pos = None 1071 | Loop.stop(self, vm, row, col) 1072 | 1073 | class End(Token): 1074 | def run(self, vm): 1075 | try: 1076 | row, col, block = vm.pop_block() 1077 | block.resume(vm, row, col) 1078 | except ExecutionError: 1079 | pass 1080 | 1081 | class Continue(Token): 1082 | def run(self, vm): 1083 | for row, col, block in reversed(vm.blocks): 1084 | if not isinstance(block, If): 1085 | block.resume(vm, row, col) 1086 | break 1087 | else: 1088 | raise ExecutionError('Continue could not find a block to continue') 1089 | 1090 | class Break(Token): 1091 | def run(self, vm): 1092 | for i, (row, col, block) in enumerate(reversed(vm.blocks)): 1093 | if not isinstance(block, If): 1094 | block.stop(vm, row, col) 1095 | vm.blocks = vm.blocks[:-i - 1] 1096 | break 1097 | else: 1098 | raise ExecutionError('Break could not find a block to end') 1099 | 1100 | class Lbl(StubToken): 1101 | absorbs = (Expression, Value) 1102 | 1103 | @staticmethod 1104 | def guess_label(vm, arg): 1105 | label = None 1106 | if isinstance(arg, Expression): 1107 | arg = arg.flatten() 1108 | 1109 | if isinstance(arg, Value): 1110 | label = vm.get(arg) 1111 | elif isinstance(arg, Variable): 1112 | label = arg.token 1113 | elif isinstance(arg, Expression): 1114 | label = unicode(arg.flatten()) 1115 | 1116 | return unicode(label) 1117 | 1118 | def get_label(self, vm): 1119 | return Lbl.guess_label(vm, self.arg) 1120 | 1121 | class Goto(Token): 1122 | absorbs = (Expression, Value) 1123 | def run(self, vm): 1124 | Goto.goto(vm, self.arg) 1125 | 1126 | @staticmethod 1127 | def goto(vm, token): 1128 | label = Lbl.guess_label(vm, token) 1129 | if label: 1130 | for row, col, token in vm.find(Lbl, wrap=True): 1131 | if token.get_label(vm) == label: 1132 | vm.goto(row, col) 1133 | return 1134 | 1135 | raise ExecutionError('could not find a label to Goto: %s' % token) 1136 | 1137 | class Menu(Function): 1138 | def run(self, vm): 1139 | args = self.arg.contents[:] 1140 | l = len(args) 1141 | if l >= 3 and (l - 3) % 2 == 0: 1142 | title = args.pop(0) 1143 | 1144 | menu = (title, zip(args[::2], args[1::2])), 1145 | 1146 | label = vm.io.menu(menu) 1147 | Goto.goto(vm, label) 1148 | else: 1149 | raise ExecutionError('Invalid arguments to Menu(): %s' % args) 1150 | 1151 | class Pause(Token): 1152 | absorbs = (Expression, Variable) 1153 | 1154 | def run(self, vm): 1155 | cur = self.arg 1156 | if cur: 1157 | vm.io.pause(vm.get(cur)) 1158 | else: 1159 | vm.io.pause() 1160 | 1161 | class Stop(Token): 1162 | def run(self, vm): 1163 | raise StopError 1164 | 1165 | class Return(Token): 1166 | def run(self, vm): 1167 | raise ReturnError 1168 | 1169 | # input/output 1170 | 1171 | class ClrHome(Token): 1172 | def run(self, vm): 1173 | vm.io.clear() 1174 | 1175 | class Float(Token): 1176 | def run(self, vm): 1177 | vm.fixed = -1 1178 | 1179 | class Fix(Token): 1180 | absorbs = (Value, Expression) 1181 | 1182 | def run(self, vm): 1183 | assert self.arg is not None 1184 | arg = vm.get(self.arg) 1185 | assert arg >= 0 1186 | 1187 | vm.fixed = arg 1188 | 1189 | class Disp(Token): 1190 | absorbs = (Expression, Variable, Tuple) 1191 | 1192 | @staticmethod 1193 | def format_matrix(data): 1194 | if isinstance(data, int): 1195 | return data 1196 | out = '[' + str(data[0]) 1197 | for row in data[1:]: 1198 | out += '\n ' + str(row) 1199 | out += ']' 1200 | return out 1201 | 1202 | def run(self, vm): 1203 | cur = self.arg 1204 | if not cur: 1205 | self.disp(vm) 1206 | return 1207 | 1208 | data = None 1209 | if isinstance(cur, ListExpr): 1210 | data = str(vm.get(cur)) 1211 | elif isinstance(cur, (MatrixExpr, Matrix)): 1212 | data = self.format_matrix(vm.get(cur)) 1213 | elif isinstance(cur, Tuple): 1214 | items = [] 1215 | for arg in cur.contents: 1216 | data = vm.get(arg) 1217 | if isinstance(arg, ListExpr): 1218 | items.append(str(data)) 1219 | elif isinstance(arg, (MatrixExpr, Matrix)): 1220 | items.append(self.format_matrix(data)) 1221 | else: 1222 | items.append(data) 1223 | self.disp(vm, *items) 1224 | return 1225 | else: 1226 | data = vm.get(cur) 1227 | self.disp(vm, data) 1228 | 1229 | def disp(self, vm, *msgs): 1230 | if not msgs: 1231 | vm.io.disp() 1232 | return 1233 | for msg in msgs: 1234 | vm.io.disp(vm.disp_round(msg)) 1235 | 1236 | class Print(Disp): 1237 | absorbs = (Expression, Variable, Tuple) 1238 | 1239 | def disp(self, vm, *msgs): 1240 | if isinstance(msgs, (tuple, list)): 1241 | vm.io.disp(', '.join(str(vm.disp_round(x)) for x in msgs)) 1242 | else: 1243 | vm.io.disp(vm.disp_round(msgs)) 1244 | 1245 | class Output(Function): 1246 | def run(self, vm): 1247 | assert len(self.arg) == 3 1248 | row, col, msg = vm.get(self.arg) 1249 | vm.io.output(row, col, vm.disp_round(msg)) 1250 | 1251 | class Prompt(Token): 1252 | absorbs = (Expression, Variable, Tuple) 1253 | 1254 | def run(self, vm): 1255 | if not self.arg: 1256 | raise ExecutionError('%s used without arguments') 1257 | 1258 | if isinstance(self.arg, Tuple): 1259 | for var in self.arg.contents: 1260 | self.prompt(vm, var) 1261 | else: 1262 | self.prompt(vm, self.arg) 1263 | 1264 | def prompt(self, vm, var): 1265 | if isinstance(var, Expression): 1266 | var = var.flatten() 1267 | val = vm.io.input(var.token + '?') 1268 | var.set(vm, val) 1269 | 1270 | def __repr__(self): 1271 | return 'Prompt(%s)' % repr(self.arg) 1272 | 1273 | class Input(Token): 1274 | # TODO: how is string input handled on calc? been too long 1275 | absorbs = (Expression, Variable, Tuple) 1276 | 1277 | def run(self, vm): 1278 | arg = self.arg 1279 | if not arg: 1280 | raise ExecutionError('Input used without arguments') 1281 | 1282 | if isinstance(arg, Tuple) and len(arg) == 1 or isinstance(arg, Variable): 1283 | self.prompt(vm, arg) 1284 | elif isinstance(arg, Tuple) and len(arg) == 2: 1285 | self.prompt(vm, arg.contents[1], vm.get(arg.contents[0])) 1286 | else: 1287 | raise ExecutionError('Input used with wrong number of arguments') 1288 | 1289 | def prompt(self, vm, var, msg='?'): 1290 | if isinstance(var, Str): 1291 | is_str = True 1292 | else: 1293 | is_str = False 1294 | 1295 | val = vm.io.input(msg, is_str) 1296 | var.set(vm, val) 1297 | 1298 | class getKey(Variable): 1299 | def get(self, vm): 1300 | return vm.io.getkey() 1301 | 1302 | class pgrm(Token): 1303 | done = False 1304 | 1305 | def dynamic(self, char): 1306 | if not self.done and char in string.uppercase: 1307 | self.name += char 1308 | return True 1309 | self.done = True 1310 | return False 1311 | 1312 | def __init__(self): 1313 | self.name = '' 1314 | 1315 | def run(self, vm): 1316 | vm.run_pgrm(self.name) 1317 | 1318 | def __repr__(self): 1319 | return 'pgrm' + self.name 1320 | 1321 | class REPL(Token): 1322 | def run(self, vm): 1323 | from parse import Parser, ParseError 1324 | 1325 | if vm.repl_serial != vm.serial: 1326 | vm.repl_serial = vm.serial 1327 | ans = vm.vars.get('Ans') 1328 | if ans is not None: 1329 | d = Disp() 1330 | d.arg = Ans() 1331 | d.run(vm) 1332 | 1333 | code = None 1334 | while not code: 1335 | repl_line = None 1336 | while not repl_line: 1337 | try: 1338 | repl_line = raw_input('>>> ') 1339 | except KeyboardInterrupt: 1340 | print 1341 | except EOFError: 1342 | code = [[EOF()]] 1343 | break 1344 | 1345 | if not code: 1346 | try: 1347 | code = Parser(repl_line + '\n').parse() 1348 | except ParseError, e: 1349 | print e 1350 | 1351 | for line in reversed(code): 1352 | vm.code.insert(self.line, line) 1353 | 1354 | vm.line, vm.col = self.line, self.col 1355 | 1356 | # date commands 1357 | 1358 | class dayOfWk(Function): 1359 | def call(self, vm, args): 1360 | assert len(args) == 3 1361 | date = datetime.datetime(year=args[0], month=args[1], day=args[2]) 1362 | return date.isoweekday() % 7 + 1 1363 | 1364 | # file IO (not in original TI-Basic) 1365 | 1366 | class ReadFile(Function): 1367 | def call(self, vm, args): 1368 | assert len(args) == 1 1369 | return open(args[0], 'r').read() 1370 | -------------------------------------------------------------------------------- /tests/blocks.bas: -------------------------------------------------------------------------------- 1 | If 1 2 | Then 3 | While 0 4 | If 1 5 | Then 6 | Disp "umm 7 | End 8 | Disp "nooo 9 | End 10 | Disp "yes! 11 | End 12 | 13 | For(A,5,1,-1) 14 | Disp A 15 | End 16 | 17 | 1->A 18 | While A<5 19 | Disp A 20 | A+1->A 21 | End 22 | 23 | 1->A 24 | Repeat A>5 25 | Disp A 26 | A+1->A 27 | End 28 | -------------------------------------------------------------------------------- /tests/circle.bas: -------------------------------------------------------------------------------- 1 | max(0,abs(R->R 2 | If R=0 3 | Then 4 | Pt-On(R,0,0 5 | Return 6 | End 7 | max((Xmax-Xmin)/94,(Ymax-Ymin)/62->T 8 | 45/RT->T 9 | R^2->S 10 | 2πR->P 11 | For(E,1(1/90)R,R3/4,PT/285 12 | E+C->A 13 | sqrt(S-E^2->B 14 | Pt-On(A,B 15 | Pt-On(-A,-B 16 | Pt-On(B,A 17 | Pt-On(-B,-A 18 | Pt-On(A,-B 19 | Pt-On(-A,B 20 | Pt-On(B,-A 21 | Pt-On(-B,A 22 | End 23 | 24 | -------------------------------------------------------------------------------- /tests/generic.bas: -------------------------------------------------------------------------------- 1 | Disp "string constant:" 2 | Disp "spam" 3 | Disp 4 | 5 | Disp "string concatenation:" 6 | Disp "eggs" + " foo" 7 | Disp 8 | 9 | Disp "expression with parens" 10 | Disp ((1 + 2) + 3) * 4 11 | Disp 12 | 13 | Disp "unterminated brackets" 14 | Disp ((1 + 2 15 | Disp 16 | 17 | Disp "logic operators" 18 | Disp 1 < 3 and 4 > 3 19 | Disp 20 | 21 | Disp "variable set, implied multiplication" 22 | 2->A 23 | 1->B 24 | Disp AB 25 | Disp 26 | 27 | Disp "-> should close all brackets (this should Disp 6)" 28 | 3+(1+2->C 29 | Disp C 30 | Disp 31 | 32 | Disp "->D->E" 33 | 1->D->E 34 | Disp D + E 35 | Disp 36 | 37 | Disp "comma Disp test" 38 | Disp 1, 2, 3, "four!" 39 | Disp 40 | 41 | Disp "goto test 42 | Goto A 43 | Disp "this should not display" 44 | Lbl A 45 | Disp "this should display" 46 | Disp 47 | 48 | Disp "If without Then test" 49 | If 1 50 | Disp "phase one succeeded" 51 | If 0 52 | Disp "phase two failed" 53 | Disp -------------------------------------------------------------------------------- /tests/getkey.bas: -------------------------------------------------------------------------------- 1 | 1->X 2 | 1->Y 3 | While 1 4 | 5 | Output(Y, X, "O") 6 | getKey->K 7 | 8 | If K>=24 and K<=26 or K=34 9 | Output(Y, X, " ") 10 | 11 | If K=24 12 | max(1,X-1)->X 13 | 14 | If K=25 15 | max(1,Y-1)->Y 16 | 17 | If K=26 18 | min(16,X+1)->X 19 | 20 | If K=34 21 | min(8,Y+1)->Y 22 | 23 | End -------------------------------------------------------------------------------- /tests/listmat.bas: -------------------------------------------------------------------------------- 1 | Disp "basic list" 2 | {1,2,3}->lTEST 3 | Disp lTEST 4 | 5->lTEST(1) 5 | Disp lTEST 6 | 7 | Disp "list copy" 8 | {1}->l1 9 | l1->l2 10 | 2->l2(1) 11 | Disp l1 12 | Disp l2 13 | 14 | Disp "basic matrix" 15 | [[1,2,3,4]]->[A] 16 | Disp [A] 17 | 2->[A](1,1) 18 | Disp [A] 19 | 20 | Disp "dim get" 21 | Disp dim(lTEST), dim([A]) 22 | 23 | Disp "dim set" 24 | 3->dim(l1) 25 | Disp dim(l1), l1 26 | 27 | {1,1}->dim([A]) 28 | Disp dim([A]), [A] 29 | -------------------------------------------------------------------------------- /tests/logic.bas: -------------------------------------------------------------------------------- 1 | Disp not(1) 2 | Disp not(0) 3 | 4 | Disp 1 and 2 and not(0) 5 | Disp 1 < 3 6 | Disp 3 > 1 7 | Disp 1 > 2 and 2 < 3 8 | -------------------------------------------------------------------------------- /tests/math.bas: -------------------------------------------------------------------------------- 1 | Disp abs(-1) 2 | Disp min(2, 1) 3 | Disp max(5, 3) 4 | Disp 4 nPr 3 5 | 6 | Disp 7 | round(rand*12000+16000, 0)->C 8 | Disp C 9 | 10 | Disp "factorial 11 | Disp 2! * 5! 12 | 13 | Disp "trig 14 | Disp sin(1), cos(1), tan(1) 15 | 16 | Disp lcm(10, 5) 17 | Disp lcm(5, 10) 18 | Disp gcd(40, 30) 19 | 20 | 4^3->C 21 | Disp C 22 | ³√(C->R 23 | Disp R 24 | 25 | Disp 2.0 26 | Disp 1ᴇ2 27 | -------------------------------------------------------------------------------- /tests/menu.bas: -------------------------------------------------------------------------------- 1 | Lbl S 2 | Menu("title", "e1", A, "e2", B, "e3", C 3 | Lbl A 4 | Disp "A 5 | Goto S 6 | Lbl B 7 | Disp "B 8 | Goto S 9 | Lbl C 10 | Disp "C 11 | Goto S 12 | -------------------------------------------------------------------------------- /tests/trig.bas: -------------------------------------------------------------------------------- 1 | Prompt A, B 2 | √(A^2 + B^2)->C 3 | 4 | Disp C 5 | If C > 10 + 1 6 | Then 7 | Disp "Larger than 10 8 | Else 9 | Disp "Smaller than 11 10 | End 11 | 12 | C-1->D->E 13 | E+1->E 14 | Disp D 15 | Disp E 16 | --------------------------------------------------------------------------------