Coverage for C:\leo.repo\leo-editor\leo\plugins\importers\rust.py: 22%

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

130 statements  

1#@+leo-ver=5-thin 

2#@+node:ekr.20200316100818.1: * @file ../plugins/importers/rust.py 

3"""The @auto importer for rust.""" 

4import re 

5from typing import Any, Dict, List 

6from leo.core import leoGlobals as g 

7from leo.plugins.importers import linescanner 

8assert g 

9Importer = linescanner.Importer 

10Target = linescanner.Target 

11#@+others 

12#@+node:ekr.20200316101240.2: ** class Rust_Importer 

13class Rust_Importer(Importer): 

14 

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

16 """rust_Importer.__init__""" 

17 # Init the base class. 

18 super().__init__( 

19 importCommands, 

20 language='rust', 

21 state_class=Rust_ScanState, 

22 ) 

23 self.headline = None 

24 

25 #@+others 

26 #@+node:ekr.20200317114526.1: *3* rust_i.clean_headline 

27 arg_pat = re.compile(r'(\(.*?\))') 

28 type_pat = re.compile(r'(\s*->.*)') 

29 life_pat = re.compile(r'(\<.*\>)') 

30 body_pat = re.compile(r'(\{.*\})') 

31 

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

33 """ 

34 Remove argument list and return value. 

35 """ 

36 s = s.strip() 

37 m = self.func_pattern.match(s) 

38 if not m: 

39 return s 

40 g1 = m.group(1) or '' 

41 g2 = m.group(2) or '' 

42 head = f"{g1} {g2}".strip() 

43 # Remove the argument list and return value. 

44 tail = m.group(3) or ''.strip() 

45 tail = re.sub(self.arg_pat, '', tail, count=1) 

46 tail = re.sub(self.type_pat, '', tail, count=1) 

47 tail = re.sub(self.body_pat, '', tail, count=1) 

48 # Clean lifetime specs except for impl. 

49 if not head.startswith('impl'): 

50 tail = re.sub(self.life_pat, '', tail, count=1) 

51 # Remove trailing '(' or '{' 

52 tail = tail.strip() 

53 while tail.endswith(('{', '(', ',', ')')): 

54 tail = tail[:-1].rstrip() 

55 # Remove trailing '>' sometimes. 

56 while '<' not in tail and tail.endswith('>'): 

57 tail = tail[:-1].rstrip() 

58 return f"{head} {tail}".strip().replace(' ', ' ') 

59 #@+node:ekr.20200316101240.4: *3* rust_i.match_start_patterns 

60 # clean_headline also uses this pattern. 

61 func_pattern = re.compile(r'\s*(pub )?\s*(enum|fn|impl|mod|struct|trait)\b(.*)') 

62 

63 def match_start_patterns(self, line): 

64 """ 

65 True if line matches any block-starting pattern. 

66 If true, set self.headline. 

67 """ 

68 m = self.func_pattern.match(line) 

69 if m: 

70 self.headline = line.strip() 

71 return bool(m) 

72 #@+node:ekr.20200623083608.1: *3* rust_i.promote_last_lines 

73 def promote_last_lines(self, parent): 

74 """ 

75 Move trailing comment and macro lines to the start of the next node. 

76 

77 For now, @others anywhere in a node prevents all moves. 

78 """ 

79 for p in parent.subtree(): 

80 next = p.threadNext() 

81 if not next: 

82 continue 

83 lines = self.get_lines(p) 

84 if '@others' in ''.join(lines): 

85 # Don't move anything. 

86 continue 

87 comment_lines: List[str] = [] 

88 for line in reversed(lines): 

89 if line.strip().startswith(('//', '#[', '#!')): 

90 comment_lines.insert(0, line) 

91 lines.pop() 

92 elif line.strip(): 

93 break 

94 else: 

95 lines.pop() 

96 if ''.join(comment_lines).strip(): 

97 next_lines = self.get_lines(next) 

98 self.set_lines(next, comment_lines + next_lines) 

99 self.set_lines(p, lines) 

100 #@+node:ekr.20200316101240.5: *3* rust_i.start_new_block 

101 def start_new_block(self, i, lines, new_state, prev_state, stack): 

102 """Create a child node and update the stack.""" 

103 line = lines[i] 

104 target = stack[-1] 

105 # Insert the reference in *this* node. 

106 h = self.gen_ref(line, target.p, target) 

107 # Create a new child and associated target. 

108 if self.headline: 

109 h = self.headline 

110 if new_state.level() > prev_state.level(): 

111 child = self.create_child_node(target.p, line, h) 

112 else: 

113 # We may not have seen the { yet, so adjust. 

114 # Without this, the new block becomes a child of the preceding. 

115 new_state = Rust_ScanState() 

116 new_state.curlies = prev_state.curlies + 1 

117 child = self.create_child_node(target.p, line, h) 

118 stack.append(Target(child, new_state)) 

119 # Add all additional lines of the signature. 

120 skip = self.skip # Don't change the ivar! 

121 while skip > 0: 

122 skip -= 1 

123 i += 1 

124 assert i < len(lines), (i, len(lines)) 

125 line = lines[i] 

126 self.add_line(child, lines[i]) 

127 #@+node:ekr.20200316101240.6: *3* rust_i.starts_block 

128 def starts_block(self, i, lines, new_state, prev_state): 

129 """True if the new state starts a block.""" 

130 self.headline = None 

131 line = lines[i] 

132 if prev_state.context: 

133 return False 

134 if not self.match_start_patterns(line): 

135 return False 

136 # Must not be a complete statement. 

137 if line.find(';') > -1: 

138 return False 

139 return True 

140 #@+node:ekr.20200316114132.1: *3* rust_i.get_new_dict 

141 #@@nobeautify 

142 

143 def get_new_dict(self, context): 

144 """ 

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

146 Subclasses may override... 

147 """ 

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

149 

150 def add_key(d, pattern, data): 

151 key = pattern[0] 

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

153 aList.append(data) 

154 d[key] = aList 

155 # 

156 # About context dependent lifetime tokens: 

157 # https://doc.rust-lang.org/stable/reference/tokens.html#lifetimes-and-loop-labels 

158 # 

159 # It looks like we can just ignore 'x' and 'x tokens. 

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

161 

162 if context: 

163 d = { 

164 # key kind pattern ends? 

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

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

167 # "'": [('len', "'", context == "'"),], 

168 } 

169 if block1 and block2: 

170 add_key(d, block2, ('len', block2, True)) 

171 else: 

172 # Not in any context. 

173 d = { 

174 # key kind pattern new-ctx deltas 

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

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

177 # "'": [('len', "'", "'", None)], 

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

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

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

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

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

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

184 } 

185 if comment: 

186 add_key(d, comment, ('all', comment, '', None)) 

187 if block1 and block2: 

188 add_key(d, block1, ('len', block1, block1, None)) 

189 return d 

190 #@-others 

191#@+node:ekr.20200316101240.7: ** class Rust_ScanState 

192class Rust_ScanState: 

193 """A class representing the state of the line-oriented scan for rust.""" 

194 

195 def __init__(self, d=None): 

196 """Rust_ScanSate ctor""" 

197 if d: 

198 prev = d.get('prev') 

199 self.context = prev.context 

200 self.curlies = prev.curlies 

201 self.parens = prev.parens 

202 else: 

203 self.context = '' 

204 self.curlies = 0 

205 self.parens = 0 

206 

207 def __repr__(self): 

208 """Rust_ScanState.__repr__""" 

209 return ( 

210 f"<Rust_ScanState " 

211 f"context: {self.context!r} " 

212 f"curlies: {self.curlies} " 

213 f"parens: {self.parens}>") 

214 

215 __str__ = __repr__ 

216 

217 #@+others 

218 #@+node:ekr.20200316101240.8: *3* rust_state.level 

219 def level(self): 

220 """Rust_ScanState.level.""" 

221 # return self.curlies 

222 return (self.curlies, self.parens) 

223 #@+node:ekr.20200316101240.9: *3* rust_state.update 

224 def update(self, data): 

225 """ 

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

227 Return i = data[1] 

228 """ 

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

230 self.context = context 

231 self.curlies += delta_c 

232 self.parens += delta_p 

233 return i 

234 

235 #@-others 

236 

237#@-others 

238importer_dict = { 

239 'func': Rust_Importer.do_import(), 

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

241} 

242#@@language python 

243#@@tabwidth -4 

244#@-leo