pitybas 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pitybas/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
pitybas/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from pitybas.cli import main
2
+
3
+ if __name__ == '__main__':
4
+ main()
pitybas/cli.py ADDED
@@ -0,0 +1,68 @@
1
+ import sys
2
+ import traceback
3
+ from optparse import OptionParser
4
+
5
+ from .interpret import Interpreter, Repl
6
+ from .common import Error
7
+ from pitybas.io.vt100 import IO as vt100
8
+
9
+
10
+ def main(argv=None):
11
+ parser = OptionParser(usage='Usage: pb [options] filename')
12
+ parser.add_option('-a', '--ast', dest="ast", action="store_true", help="parse, print ast, and quit")
13
+ parser.add_option('-d', '--dump', dest="vardump", action="store_true", help="dump variables in stacktrace")
14
+ parser.add_option('-s', '--stacktrace', dest="stacktrace", action="store_true", help="always stacktrace")
15
+ parser.add_option('-v', '--verbose', dest="verbose", action="store_true", help="verbose output")
16
+ parser.add_option('-i', '--io', dest="io", help="select an IO system: simple (default), vt100")
17
+
18
+ (options, args) = parser.parse_args(argv)
19
+
20
+ if len(args) > 1:
21
+ parser.print_help()
22
+ sys.exit(1)
23
+
24
+ io = None
25
+ if options.io == 'vt100':
26
+ io = vt100
27
+
28
+ if args:
29
+ vm = Interpreter.from_file(args[0], history=20, io=io)
30
+ else:
31
+ print('Welcome to pitybas. Press Ctrl-D to exit.')
32
+ print()
33
+ vm = Repl(history=20, io=io)
34
+
35
+ if options.verbose:
36
+ vm.print_tokens()
37
+ print()
38
+ if args:
39
+ print('-===[ Running %s ]===-' % args[0])
40
+
41
+ if options.ast:
42
+ vm.print_ast()
43
+ sys.exit(0)
44
+
45
+ try:
46
+ vm.execute()
47
+ if options.stacktrace:
48
+ vm.print_stacktrace(options.vardump)
49
+ except KeyboardInterrupt:
50
+ print()
51
+ vm.print_stacktrace(options.vardump)
52
+ except Exception as e:
53
+ print()
54
+ print()
55
+ vm.print_stacktrace(options.vardump)
56
+
57
+ print('%s on line %i:' % (e.__class__.__name__, vm.line), end=' ')
58
+
59
+ if isinstance(e, Error):
60
+ print(e.msg)
61
+ else:
62
+ print()
63
+ print('-===[ Python traceback ]===-')
64
+ print(traceback.format_exc())
65
+
66
+
67
+ if __name__ == '__main__':
68
+ main()
pitybas/common.py ADDED
@@ -0,0 +1,41 @@
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()
pitybas/expression.py ADDED
@@ -0,0 +1,285 @@
1
+ from . 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 range(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 range(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 range(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 range(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 sorted(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
+
pitybas/interpret.py ADDED
@@ -0,0 +1,269 @@
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', encoding='utf8').read()
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 range(pos, len(self.code)):
124
+ ret = y(i)
125
+ if ret: yield ret
126
+
127
+ if wrap:
128
+ for i in range(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, 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 as e:
195
+ if e.args:
196
+ print()
197
+ print('Stopped:', e.args[0])
198
+ except ReturnError as e:
199
+ if e.args:
200
+ print()
201
+ print('Returned:', e.args[0])
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 range(max(start, 0), min(end, len(self.code))):
212
+ line = self.code[i]
213
+ if highlight is not None and 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 as e:
267
+ print(e)
268
+ except:
269
+ print(traceback.format_exc())
pitybas/io/__init__.py ADDED
File without changes