Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prison/decoder.py: 52%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# encoding: utf-8
3import re
5from .constants import NEXT_ID_RE, WHITESPACE
8class ParserException(Exception):
9 pass
12class Parser(object):
14 def __init__(self):
15 self.string = None
16 self.index = 0
18 """
19 This parser supports RISON, RISON-A and RISON-O.
20 """
21 def parse(self, string, format=str):
22 if string == "(":
23 raise ParserException("unmatched '('")
24 if format in [list, 'A']:
25 self.string = "!({0})".format(string)
26 elif format in [dict, 'O']:
27 self.string = "({0})".format(string)
28 elif format is str:
29 self.string = string
30 else:
31 raise ValueError("""Parse format should be one of str, list, dict,
32 'A' (alias for list), '0' (alias for dict).""")
34 self.index = 0
36 value = self.read_value()
37 if self.next():
38 raise ParserException("unable to parse rison string %r" % (string,))
39 return value
41 def read_value(self):
42 c = self.next()
44 if c == '!':
45 return self.parse_bang()
46 if c == '(':
47 return self.parse_open_paren()
48 if c == "'":
49 return self.parse_single_quote()
50 if c in '-0123456789':
51 return self.parse_number()
53 # fell through table, parse as an id
54 s = self.string
55 i = self.index-1
57 m = NEXT_ID_RE.match(s, i)
58 if m:
59 _id = m.group(0)
60 self.index = i + len(_id)
61 return _id
63 if c:
64 raise ParserException("invalid character: '" + c + "'")
65 raise ParserException("empty expression")
67 def parse_array(self):
68 ar = []
69 while 1:
70 c = self.next()
71 if c == ')':
72 return ar
74 if c is None:
75 raise ParserException("unmatched '!('")
77 if len(ar):
78 if c != ',':
79 raise ParserException("missing ','")
80 elif c == ',':
81 raise ParserException("extra ','")
82 else:
83 self.index -= 1
84 n = self.read_value()
85 ar.append(n)
87 def parse_bang(self):
88 s = self.string
89 c = s[self.index]
90 self.index += 1
91 if c is None:
92 raise ParserException('"!" at end of input')
93 if c not in self.bangs:
94 raise ParserException('unknown literal: "!' + c + '"')
95 x = self.bangs[c]
96 if callable(x):
97 return x(self)
99 return x
101 def parse_open_paren(self):
102 count = 0
103 o = {}
105 while 1:
106 c = self.next()
107 if c == ')':
108 return o
109 if count:
110 if c != ',':
111 raise ParserException("missing ','")
112 elif c == ',':
113 raise ParserException("extra ','")
114 else:
115 self.index -= 1
116 k = self.read_value()
118 if self.next() != ':':
119 raise ParserException("missing ':'")
120 v = self.read_value()
122 o[k] = v
123 count += 1
125 def parse_single_quote(self):
126 s = self.string
127 i = self.index
128 start = i
129 segments = []
131 while 1:
132 if i >= len(s):
133 raise ParserException('unmatched "\'"')
135 c = s[i]
136 i += 1
137 if c == "'":
138 break
140 if c == '!':
141 if start < i-1:
142 segments.append(s[start:i-1])
143 c = s[i]
144 i += 1
145 if c in "!'":
146 segments.append(c)
147 else:
148 raise ParserException('invalid string escape: "!'+c+'"')
150 start = i
152 if start < i-1:
153 segments.append(s[start:i-1])
154 self.index = i
155 return ''.join(segments)
157 # Also any number start (digit or '-')
158 def parse_number(self):
159 s = self.string
160 i = self.index
161 start = i-1
162 state = 'int'
163 permitted_signs = '-'
164 transitions = {
165 'int+.': 'frac',
166 'int+e': 'exp',
167 'frac+e': 'exp'
168 }
170 while 1:
171 if i >= len(s):
172 i += 1
173 break
175 c = s[i]
176 i += 1
178 if '0' <= c <= '9':
179 continue
181 if permitted_signs.find(c) >= 0:
182 permitted_signs = ''
183 continue
185 state = transitions.get(state + '+' + c.lower(), None)
186 if state is None:
187 break
188 if state == 'exp':
189 permitted_signs = '-'
191 self.index = i - 1
192 s = s[start:self.index]
193 if s == '-':
194 raise ParserException("invalid number")
195 if re.search('[.e]', s):
196 return float(s)
197 return int(s)
199 # return the next non-whitespace character, or undefined
200 def next(self):
201 s = self.string
202 i = self.index
204 while 1:
205 if i == len(s):
206 return None
207 c = s[i]
208 i += 1
209 if c not in WHITESPACE:
210 break
212 self.index = i
213 return c
215 bangs = {
216 't': True,
217 'f': False,
218 'n': None,
219 '(': parse_array
220 }
223def loads(s, format=str):
224 return Parser().parse(s, format=format)