Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# mako/ast.py 

2# Copyright 2006-2020 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 compat 

13from mako import exceptions 

14from mako import pyparser 

15 

16 

17class PythonCode(object): 

18 

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

20 

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

22 self.code = code 

23 

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

25 # the code 

26 self.declared_identifiers = set() 

27 

28 # represents all identifiers which are referenced before their 

29 # assignment, if any 

30 self.undeclared_identifiers = set() 

31 

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

33 # lists. 

34 

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

36 # code.co_names has several advantages: 

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

38 # its declared later in the same block of code 

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

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

41 # in python version 2.5) 

42 if isinstance(code, compat.string_types): 

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

44 else: 

45 expr = code 

46 

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

48 f.visit(expr) 

49 

50 

51class ArgumentList(object): 

52 

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

54 

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

56 self.codeargs = [] 

57 self.args = [] 

58 self.declared_identifiers = set() 

59 self.undeclared_identifiers = set() 

60 if isinstance(code, compat.string_types): 

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

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

63 # as a tuple by adding a trailing comma 

64 code += "," 

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

66 else: 

67 expr = code 

68 

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

70 f.visit(expr) 

71 

72 

73class PythonFragment(PythonCode): 

74 

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

76 statements 

77 

78 e.g.:: 

79 

80 for x in 5: 

81 elif y==9: 

82 except (MyException, e): 

83 

84 """ 

85 

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

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

88 if not m: 

89 raise exceptions.CompileException( 

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

91 **exception_kwargs 

92 ) 

93 if m.group(3): 

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

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

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

97 code = code + "pass" 

98 elif keyword == "try": 

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

100 elif keyword == "elif" or keyword == "else": 

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

102 elif keyword == "except": 

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

104 elif keyword == "with": 

105 code = code + "pass" 

106 else: 

107 raise exceptions.CompileException( 

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

109 **exception_kwargs 

110 ) 

111 super(PythonFragment, self).__init__(code, **exception_kwargs) 

112 

113 

114class FunctionDecl(object): 

115 

116 """function declaration""" 

117 

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

119 self.code = code 

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

121 

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

123 f.visit(expr) 

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

125 raise exceptions.CompileException( 

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

127 **exception_kwargs 

128 ) 

129 if not allow_kwargs and self.kwargs: 

130 raise exceptions.CompileException( 

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

132 % self.kwargnames[-1], 

133 **exception_kwargs 

134 ) 

135 

136 def get_argument_expressions(self, as_call=False): 

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

138 list. 

139 

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

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

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

143 """ 

144 

145 namedecls = [] 

146 

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

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

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

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

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

152 

153 # Named arguments 

154 if self.kwargs: 

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

156 

157 for name in kwargnames: 

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

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

160 if as_call: 

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

162 elif kwdefaults: 

163 default = kwdefaults.pop(0) 

164 if default is None: 

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

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

167 namedecls.append(name) 

168 else: 

169 namedecls.append( 

170 "%s=%s" 

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

172 ) 

173 else: 

174 namedecls.append(name) 

175 

176 # Positional arguments 

177 if self.varargs: 

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

179 

180 for name in argnames: 

181 if as_call or not defaults: 

182 namedecls.append(name) 

183 else: 

184 default = defaults.pop(0) 

185 namedecls.append( 

186 "%s=%s" 

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

188 ) 

189 

190 namedecls.reverse() 

191 return namedecls 

192 

193 @property 

194 def allargnames(self): 

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

196 

197 

198class FunctionArgs(FunctionDecl): 

199 

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

201 

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

203 super(FunctionArgs, self).__init__( 

204 "def ANON(%s):pass" % code, **kwargs 

205 )