Coverage for C:\Repos\leo-editor\leo\plugins\importers\rust.py: 22%
130 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
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):
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
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'(\{.*\})')
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(.*)')
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.
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
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
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]]
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."""
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
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}>")
215 __str__ = __repr__
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
235 #@-others
237#@-others
238importer_dict = {
239 'func': Rust_Importer.do_import(),
240 'extensions': ['.rs',],
241}
242#@@language python
243#@@tabwidth -4
244#@-leo