/
util.py
170 lines (149 loc) · 5.09 KB
/
util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""
PC-BASIC - util.py
Token stream utilities
(c) 2013, 2014, 2015, 2016 Rob Hagemans
This file is released under the GNU GPL version 3 or later.
"""
from functools import partial
import string
from . import error
from . import vartypes
from . import basictoken as tk
###############################################################################
# stream utilities
def peek(ins, n=1):
""" Peek next char in stream. """
d = ins.read(n)
ins.seek(-len(d), 1)
return d
def skip_read(ins, skip_range, n=1):
""" Skip chars in skip_range, then read next. """
while True:
d = ins.read(1)
# skip_range must not include ''
if d == '' or d not in skip_range:
return d + ins.read(n-1)
def skip(ins, skip_range, n=1):
""" Skip chars in skip_range, then peek next. """
d = skip_read(ins, skip_range, n)
ins.seek(-len(d), 1)
return d
def backskip_white(ins):
""" Skip whitespace backwards, then peek next. """
while True:
ins.seek(-1, 1)
d = peek(ins)
# skip_range must not include ''
if d == '' or d not in tk.whitespace:
return d
# skip whitespace, then read next
skip_white_read = partial(skip_read, skip_range=tk.whitespace)
# skip whitespace, then peek next
skip_white = partial(skip, skip_range=tk.whitespace)
def skip_white_read_if(ins, in_range):
""" Skip whitespace, then read if next char is in range. """
return read_if(ins, skip_white(ins, n=len(in_range[0])), in_range)
def read_if(ins, d, in_range):
""" Read if next char is in range. """
if d != '' and d in in_range:
ins.read(len(d))
return True
return False
def skip_to(ins, findrange, break_on_first_char=True):
""" Skip until character is in findrange. """
literal = False
rem = False
while True:
c = ins.read(1)
if c == '':
break
elif c == '"':
literal = not literal
elif c == tk.REM:
rem = True
elif c == '\0':
literal = False
rem = False
if literal or rem:
continue
if c in findrange:
if break_on_first_char:
ins.seek(-1, 1)
break
else:
break_on_first_char = True
# not elif! if not break_on_first_char, c needs to be properly processed.
if c == '\0': # offset and line number follow
literal = False
off = ins.read(2)
if len(off) < 2 or off == '\0\0':
break
ins.read(2)
elif c in tk.plus_bytes:
ins.read(tk.plus_bytes[c])
def skip_to_read(ins, findrange):
""" Skip until character is in findrange, then read. """
skip_to(ins, findrange)
return ins.read(1)
###############################################################################
# parsing utilities
def require_read(ins, in_range, err=error.STX):
""" Skip whitespace, read and raise error if not in range. """
if not skip_white_read_if(ins, in_range):
raise error.RunError(err)
def require(ins, rnge, err=error.STX):
""" Skip whitespace, peek and raise error if not in range. """
a = skip_white(ins, n=len(rnge[0]))
if a not in rnge:
raise error.RunError(err)
def parse_line_number(ins):
""" Parse line number and leave pointer at first char of line. """
# if end of program or truncated, leave pointer at start of line number C0 DE or 00 00
off = ins.read(2)
if off == '\0\0' or len(off) < 2:
ins.seek(-len(off), 1)
return -1
off = ins.read(2)
if len(off) < 2:
ins.seek(-len(off)-2, 1)
return -1
else:
return vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(off))
def parse_jumpnum(ins, allow_empty=False, err=error.STX):
""" Parses a line number pointer as in GOTO, GOSUB, LIST, RENUM, EDIT, etc. """
if skip_white_read_if(ins, (tk.T_UINT,)):
return vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(ins.read(2)))
else:
if allow_empty:
return -1
# Syntax error
raise error.RunError(err)
def read_name(ins, allow_empty=False, err=error.STX):
""" Read a variable name """
name = ''
d = skip_white_read(ins)
if not d:
pass
elif d not in string.ascii_letters:
# variable name must start with a letter
ins.seek(-len(d), 1)
else:
while d and d in tk.name_chars:
name += d
d = ins.read(1)
if d in vartypes.sigils:
name += d
else:
ins.seek(-len(d), 1)
if not name and not allow_empty:
raise error.RunError(err)
return name
def range_check(lower, upper, *allvars):
""" Check if all variables in list are within the given inclusive range. """
for v in allvars:
if v is not None and not (lower <= v <= upper):
raise error.RunError(error.IFC)
def range_check_err(lower, upper, v, err=error.IFC):
""" Check if variable is within the given inclusive range. """
if v is not None and not (lower <= v <= upper):
raise error.RunError(err)