Coverage for C:\leo.repo\leo-editor\leo\plugins\importers\php.py: 79%

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

124 statements  

1#@+leo-ver=5-thin 

2#@+node:ekr.20140723122936.18148: * @file ../plugins/importers/php.py 

3"""The @auto importer for the php language.""" 

4import re 

5from typing import Any, Dict, List 

6from leo.core import leoGlobals as g 

7from leo.plugins.importers import linescanner 

8Importer = linescanner.Importer 

9#@+others 

10#@+node:ekr.20161129213243.2: ** class Php_Importer 

11class Php_Importer(Importer): 

12 """The importer for the php lanuage.""" 

13 

14 def __init__(self, importCommands, **kwargs): 

15 """Php_Importer.__init__""" 

16 super().__init__( 

17 importCommands, 

18 language='php', 

19 state_class=Php_ScanState, 

20 strict=False, 

21 ) 

22 self.here_doc_pattern = re.compile(r'<<<\s*([\w_]+)') 

23 self.here_doc_target = None 

24 

25 #@+others 

26 #@+node:ekr.20161129213243.4: *3* php_i.clean_headline 

27 def clean_headline(self, s, p=None): 

28 """Return a cleaned up headline s.""" 

29 return s.rstrip('{').strip() 

30 #@+node:ekr.20161129213808.1: *3* php_i.get_new_dict 

31 #@@nobeautify 

32 

33 def get_new_dict(self, context): 

34 """ 

35 Return a *general* state dictionary for the given context. 

36 Subclasses may override... 

37 """ 

38 trace = 'importers' in g.app.debug 

39 comment, block1, block2 = self.single_comment, self.block1, self.block2 

40 

41 def add_key(d, key, data): 

42 aList = d.get(key,[]) 

43 aList.append(data) 

44 d[key] = aList 

45 

46 d: Dict[str, List[Any]] 

47 

48 if context: 

49 d = { 

50 # key kind pattern ends? 

51 '\\': [('len+1', '\\', None),], 

52 '"': [('len', '"', context == '"'),], 

53 "'": [('len', "'", context == "'"),], 

54 

55 } 

56 if block1 and block2: 

57 add_key(d, block2[0], ('len', block2, True)) # #1717. 

58 else: 

59 # Not in any context. 

60 d = { 

61 # key kind pattern new-ctx deltas 

62 '\\':[('len+1', '\\', context, None),], 

63 '<': [('<<<', '<<<', '<<<', None),], 

64 '"': [('len', '"', '"', None),], 

65 "'": [('len', "'", "'", None),], 

66 '{': [('len', '{', context, (1,0,0)),], 

67 '}': [('len', '}', context, (-1,0,0)),], 

68 '(': [('len', '(', context, (0,1,0)),], 

69 ')': [('len', ')', context, (0,-1,0)),], 

70 '[': [('len', '[', context, (0,0,1)),], 

71 ']': [('len', ']', context, (0,0,-1)),], 

72 } 

73 if comment: 

74 add_key(d, comment[0], ('all', comment, '', None)) 

75 if block1 and block2: 

76 add_key(d, block1[0], ('len', block1, block1, None)) 

77 if trace: 

78 g.trace(f"{comment!r} {block1!r} {block2!r}") 

79 g.printObj(d, tag=f"scan table for context {context!r}") 

80 return d 

81 #@+node:ekr.20161129214803.1: *3* php_i.scan_dict (supports here docs) 

82 def scan_dict(self, context, i, s, d): 

83 """ 

84 i.scan_dict: Scan at position i of s with the give context and dict. 

85 Return the 6-tuple: (new_context, i, delta_c, delta_p, delta_s, bs_nl) 

86 """ 

87 found = False 

88 delta_c = delta_p = delta_s = 0 

89 if self.here_doc_target: 

90 assert i == 0, repr(i) 

91 n = len(self.here_doc_target) 

92 if self.here_doc_target.lower() == s[:n].lower(): 

93 self.here_doc_target = None 

94 i = n 

95 return '', i, 0, 0, 0, False 

96 # Skip the rest of the line 

97 return '', len(s), 0, 0, 0, False 

98 ch = s[i] # For traces. 

99 aList = d.get(ch) 

100 if aList and context: 

101 # In context. 

102 for data in aList: 

103 kind, pattern, ends = data 

104 if self.match(s, i, pattern): 

105 if ends is None: 

106 found = True 

107 new_context = context 

108 break 

109 elif ends: 

110 found = True 

111 new_context = '' 

112 break 

113 else: 

114 pass # Ignore this match. 

115 elif aList: 

116 # Not in context. 

117 for data in aList: 

118 kind, pattern, new_context, deltas = data 

119 if self.match(s, i, pattern): 

120 found = True 

121 if deltas: 

122 delta_c, delta_p, delta_s = deltas 

123 break 

124 if found: 

125 if kind == 'all': 

126 i = len(s) 

127 elif kind == 'len+1': 

128 i += (len(pattern) + 1) 

129 elif kind == '<<<': # Special flag for here docs. 

130 new_context = context # here_doc_target is a another kind of context. 

131 m = self.here_doc_pattern.match(s[i:]) 

132 if m: 

133 i = len(s) # Skip the rest of the line. 

134 self.here_doc_target = '%s;' % m.group(1) 

135 else: 

136 i += 3 

137 else: 

138 assert kind == 'len', (kind, self.name) 

139 i += len(pattern) 

140 bs_nl = pattern == '\\\n' 

141 return new_context, i, delta_c, delta_p, delta_s, bs_nl 

142 # 

143 # No match: stay in present state. All deltas are zero. 

144 new_context = context 

145 return new_context, i + 1, 0, 0, 0, False 

146 #@+node:ekr.20161130044051.1: *3* php_i.skip_heredoc_string (not used) 

147 # EKR: This is Dave Hein's heredoc code from the old PHP scanner. 

148 # I have included it for reference in case heredoc problems arise. 

149 # 

150 # php_i.scan dict uses r'<<<\s*([\w_]+)' instead of the more complex pattern below. 

151 # This is likely good enough. Importers can assume that code is well formed. 

152 

153 def skip_heredoc_string(self, s, i): 

154 #@+<< skip_heredoc docstrig >> 

155 #@+node:ekr.20161130044051.2: *4* << skip_heredoc docstrig >> 

156 #@@nocolor-node 

157 """ 

158 08-SEP-2002 DTHEIN: added function skip_heredoc_string 

159 A heredoc string in PHP looks like: 

160 

161 <<<EOS 

162 This is my string. 

163 It is mine. I own it. 

164 No one else has it. 

165 EOS 

166 

167 It begins with <<< plus a token (naming same as PHP variable names). 

168 It ends with the token on a line by itself (must start in first position. 

169 """ 

170 #@-<< skip_heredoc docstrig >> 

171 j = i 

172 assert g.match(s, i, "<<<") 

173 m = re.match(r"\<\<\<([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)", s[i:]) 

174 if m is None: 

175 i += 3 

176 return i 

177 # 14-SEP-2002 DTHEIN: needed to add \n to find word, not just string 

178 delim = m.group(1) + '\n' 

179 i = g.skip_line(s, i) # 14-SEP-2002 DTHEIN: look after \n, not before 

180 n = len(s) 

181 while i < n and not g.match(s, i, delim): 

182 i = g.skip_line(s, i) # 14-SEP-2002 DTHEIN: move past \n 

183 if i >= n: 

184 g.scanError("Run on string: " + s[j:i]) 

185 elif g.match(s, i, delim): 

186 i += len(delim) 

187 return i 

188 #@-others 

189#@+node:ekr.20161129213243.6: ** class Php_ScanState 

190class Php_ScanState: 

191 """A class representing the state of the php line-oriented scan.""" 

192 

193 def __init__(self, d=None): 

194 """Php_ScanState.__init__""" 

195 if d: 

196 prev = d.get('prev') 

197 self.context = prev.context 

198 self.curlies = prev.curlies 

199 else: 

200 self.context = '' 

201 self.curlies = 0 

202 

203 def __repr__(self): 

204 """Php_ScanState.__repr__""" 

205 return "Php_ScanState context: %r curlies: %s" % ( 

206 self.context, self.curlies) 

207 

208 __str__ = __repr__ 

209 

210 #@+others 

211 #@+node:ekr.20161129213243.7: *3* php_state.level 

212 def level(self): 

213 """Php_ScanState.level.""" 

214 return self.curlies 

215 

216 #@+node:ekr.20161129213243.8: *3* php_state.update 

217 def update(self, data): 

218 """ 

219 Php_ScanState.update 

220 

221 Update the state using the 6-tuple returned by i.scan_line. 

222 Return i = data[1] 

223 """ 

224 trace = 'importers' in g.app.debug 

225 context, i, delta_c, delta_p, delta_s, bs_nl = data 

226 if trace: 

227 g.trace( 

228 f"context: {context!s} " 

229 f"delta_c: {delta_c} " 

230 f"delta_s: {delta_s} " 

231 f"bs_nl: {bs_nl}") 

232 # All ScanState classes must have a context ivar. 

233 self.context = context 

234 self.curlies += delta_c 

235 return i 

236 #@-others 

237#@-others 

238importer_dict = { 

239 'func': Php_Importer.do_import(), 

240 'extensions': ['.php'], 

241} 

242#@@language python 

243#@@tabwidth -4 

244#@-leo