Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/ast.py: 3%

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

95 statements  

1# mako/ast.py 

2# Copyright 2006-2026 the Mako authors and contributors <see AUTHORS file> 

3# 

4# This module is part of Mako and is released under 

5# the MIT License: http://www.opensource.org/licenses/mit-license.php 

6 

7"""utilities for analyzing expressions and blocks of Python 

8code, as well as generating Python from AST nodes""" 

9 

10import re 

11 

12from mako import exceptions 

13from mako import pyparser 

14 

15 

16class PythonCode: 

17 """represents information about a string containing Python code""" 

18 

19 def __init__(self, code, lineno_offset=0, **exception_kwargs): 

20 self.code = code 

21 

22 # represents all identifiers which are assigned to at some point in 

23 # the code 

24 self.declared_identifiers = set() 

25 

26 # represents all identifiers which are referenced before their 

27 # assignment, if any 

28 self.undeclared_identifiers = set() 

29 

30 # note that an identifier can be in both the undeclared and declared 

31 # lists. 

32 

33 # using AST to parse instead of using code.co_varnames, 

34 # code.co_names has several advantages: 

35 # - we can locate an identifier as "undeclared" even if 

36 # its declared later in the same block of code 

37 # - AST is less likely to break with version changes 

38 # (for example, the behavior of co_names changed a little bit 

39 # in python version 2.5) 

40 if isinstance(code, str): 

41 stripped = code.lstrip() 

42 

43 # the code of a <% %> block usually begins on the line after the 

44 # tag; count the lines that are stripped off so that a syntax 

45 # error is reported against the line it is on 

46 lineno_offset += code[: len(code) - len(stripped)].count("\n") 

47 

48 expr = pyparser.parse( 

49 stripped, 

50 "exec", 

51 lineno_offset=lineno_offset, 

52 **exception_kwargs, 

53 ) 

54 else: 

55 expr = code 

56 

57 f = pyparser.FindIdentifiers(self, **exception_kwargs) 

58 f.visit(expr) 

59 

60 

61class ArgumentList: 

62 """parses a fragment of code as a comma-separated list of expressions""" 

63 

64 def __init__(self, code, **exception_kwargs): 

65 self.codeargs = [] 

66 self.args = [] 

67 self.declared_identifiers = set() 

68 self.undeclared_identifiers = set() 

69 if isinstance(code, str): 

70 if re.match(r"\S", code) and not re.match(r",\s*$", code): 

71 # if theres text and no trailing comma, insure its parsed 

72 # as a tuple by adding a trailing comma 

73 code += "," 

74 expr = pyparser.parse(code, "exec", **exception_kwargs) 

75 else: 

76 expr = code 

77 

78 f = pyparser.FindTuple(self, PythonCode, **exception_kwargs) 

79 f.visit(expr) 

80 

81 

82class PythonFragment(PythonCode): 

83 """extends PythonCode to provide identifier lookups in partial control 

84 statements 

85 

86 e.g.:: 

87 

88 for x in 5: 

89 elif y==9: 

90 except (MyException, e): 

91 

92 """ 

93 

94 def __init__(self, code, **exception_kwargs): 

95 m = re.match(r"^(\w+)(?:\s+(.*?))?:\s*(#|$)", code.strip(), re.S) 

96 if not m: 

97 raise exceptions.CompileException( 

98 "Fragment '%s' is not a partial control statement" % code, 

99 **exception_kwargs, 

100 ) 

101 if m.group(3): 

102 code = code[: m.start(3)] 

103 keyword, expr = m.group(1, 2) 

104 

105 # a statement that is only valid as a continuation is completed by 

106 # a line placed before it; the line the fragment is on is that many 

107 # lines further along than the code which is parsed 

108 lineno_offset = 0 

109 

110 if keyword in ["for", "if", "while"]: 

111 code = code + "pass" 

112 elif keyword == "try": 

113 code = code + "pass\nexcept:pass" 

114 elif keyword in ["elif", "else"]: 

115 code = "if False:pass\n" + code + "pass" 

116 lineno_offset = -1 

117 elif keyword == "except": 

118 code = "try:pass\n" + code + "pass" 

119 lineno_offset = -1 

120 elif keyword == "with": 

121 code = code + "pass" 

122 else: 

123 raise exceptions.CompileException( 

124 "Unsupported control keyword: '%s'" % keyword, 

125 **exception_kwargs, 

126 ) 

127 super().__init__(code, lineno_offset=lineno_offset, **exception_kwargs) 

128 

129 

130class FunctionDecl: 

131 """function declaration""" 

132 

133 def __init__(self, code, allow_kwargs=True, **exception_kwargs): 

134 self.code = code 

135 expr = pyparser.parse(code, "exec", **exception_kwargs) 

136 

137 f = pyparser.ParseFunc(self, **exception_kwargs) 

138 f.visit(expr) 

139 if not hasattr(self, "funcname"): 

140 raise exceptions.CompileException( 

141 "Code '%s' is not a function declaration" % code, 

142 **exception_kwargs, 

143 ) 

144 if not allow_kwargs and self.kwargs: 

145 raise exceptions.CompileException( 

146 "'**%s' keyword argument not allowed here" 

147 % self.kwargnames[-1], 

148 **exception_kwargs, 

149 ) 

150 

151 def get_argument_expressions(self, as_call=False): 

152 """Return the argument declarations of this FunctionDecl as a printable 

153 list. 

154 

155 By default the return value is appropriate for writing in a ``def``; 

156 set `as_call` to true to build arguments to be passed to the function 

157 instead (assuming locals with the same names as the arguments exist). 

158 """ 

159 

160 namedecls = [] 

161 

162 # Build in reverse order, since defaults and slurpy args come last 

163 argnames = self.argnames[::-1] 

164 kwargnames = self.kwargnames[::-1] 

165 defaults = self.defaults[::-1] 

166 kwdefaults = self.kwdefaults[::-1] 

167 

168 # Named arguments 

169 if self.kwargs: 

170 namedecls.append("**" + kwargnames.pop(0)) 

171 

172 for name in kwargnames: 

173 # Keyword-only arguments must always be used by name, so even if 

174 # this is a call, print out `foo=foo` 

175 if as_call: 

176 namedecls.append("%s=%s" % (name, name)) 

177 elif kwdefaults: 

178 default = kwdefaults.pop(0) 

179 if default is None: 

180 # The AST always gives kwargs a default, since you can do 

181 # `def foo(*, a=1, b, c=3)` 

182 namedecls.append(name) 

183 else: 

184 namedecls.append( 

185 "%s=%s" 

186 % (name, pyparser.ExpressionGenerator(default).value()) 

187 ) 

188 else: 

189 namedecls.append(name) 

190 

191 # Positional arguments 

192 if self.varargs: 

193 namedecls.append("*" + argnames.pop(0)) 

194 

195 for name in argnames: 

196 if as_call or not defaults: 

197 namedecls.append(name) 

198 else: 

199 default = defaults.pop(0) 

200 namedecls.append( 

201 "%s=%s" 

202 % (name, pyparser.ExpressionGenerator(default).value()) 

203 ) 

204 

205 namedecls.reverse() 

206 return namedecls 

207 

208 @property 

209 def allargnames(self): 

210 return tuple(self.argnames) + tuple(self.kwargnames) 

211 

212 

213class FunctionArgs(FunctionDecl): 

214 """the argument portion of a function declaration""" 

215 

216 def __init__(self, code, **kwargs): 

217 super().__init__("def ANON(%s):pass" % code, **kwargs)