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

162 statements  

1# encoding: utf-8 

2 

3import re 

4 

5from .constants import NEXT_ID_RE, WHITESPACE 

6 

7 

8class ParserException(Exception): 

9 pass 

10 

11 

12class Parser(object): 

13 

14 def __init__(self): 

15 self.string = None 

16 self.index = 0 

17 

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).""") 

33 

34 self.index = 0 

35 

36 value = self.read_value() 

37 if self.next(): 

38 raise ParserException("unable to parse rison string %r" % (string,)) 

39 return value 

40 

41 def read_value(self): 

42 c = self.next() 

43 

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() 

52 

53 # fell through table, parse as an id 

54 s = self.string 

55 i = self.index-1 

56 

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 

62 

63 if c: 

64 raise ParserException("invalid character: '" + c + "'") 

65 raise ParserException("empty expression") 

66 

67 def parse_array(self): 

68 ar = [] 

69 while 1: 

70 c = self.next() 

71 if c == ')': 

72 return ar 

73 

74 if c is None: 

75 raise ParserException("unmatched '!('") 

76 

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) 

86 

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) 

98 

99 return x 

100 

101 def parse_open_paren(self): 

102 count = 0 

103 o = {} 

104 

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() 

117 

118 if self.next() != ':': 

119 raise ParserException("missing ':'") 

120 v = self.read_value() 

121 

122 o[k] = v 

123 count += 1 

124 

125 def parse_single_quote(self): 

126 s = self.string 

127 i = self.index 

128 start = i 

129 segments = [] 

130 

131 while 1: 

132 if i >= len(s): 

133 raise ParserException('unmatched "\'"') 

134 

135 c = s[i] 

136 i += 1 

137 if c == "'": 

138 break 

139 

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+'"') 

149 

150 start = i 

151 

152 if start < i-1: 

153 segments.append(s[start:i-1]) 

154 self.index = i 

155 return ''.join(segments) 

156 

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 } 

169 

170 while 1: 

171 if i >= len(s): 

172 i += 1 

173 break 

174 

175 c = s[i] 

176 i += 1 

177 

178 if '0' <= c <= '9': 

179 continue 

180 

181 if permitted_signs.find(c) >= 0: 

182 permitted_signs = '' 

183 continue 

184 

185 state = transitions.get(state + '+' + c.lower(), None) 

186 if state is None: 

187 break 

188 if state == 'exp': 

189 permitted_signs = '-' 

190 

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) 

198 

199 # return the next non-whitespace character, or undefined 

200 def next(self): 

201 s = self.string 

202 i = self.index 

203 

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 

211 

212 self.index = i 

213 return c 

214 

215 bangs = { 

216 't': True, 

217 'f': False, 

218 'n': None, 

219 '(': parse_array 

220 } 

221 

222 

223def loads(s, format=str): 

224 return Parser().parse(s, format=format)