Coverage for C:\leo.repo\leo-editor\leo\plugins\importers\coffeescript.py: 65%

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

138 statements  

1#@+leo-ver=5-thin 

2#@+node:ekr.20160505094722.1: * @file ../plugins/importers/coffeescript.py 

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

4import re 

5from typing import Any, Dict, List 

6from leo.core import leoGlobals as g 

7from leo.plugins.importers import linescanner 

8Importer = linescanner.Importer 

9Target = linescanner.Target 

10#@+others 

11#@+node:ekr.20160505094722.2: ** class CS_Importer(Importer) 

12class CS_Importer(Importer): 

13 

14 #@+others 

15 #@+node:ekr.20160505101118.1: *3* coffee_i.__init__ 

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

17 """Ctor for CoffeeScriptScanner class.""" 

18 super().__init__( 

19 importCommands, 

20 language='coffeescript', 

21 state_class=CS_ScanState, # Not used: This class overrides i.scan_line. 

22 strict=True 

23 ) 

24 self.errors = 0 

25 self.root = None 

26 self.tab_width = None # NOT the same as self.c.tabwidth. Set in run(). 

27 #@+node:ekr.20161129024357.1: *3* coffee_i.get_new_dict 

28 #@@nobeautify 

29 

30 def get_new_dict(self, context): 

31 """ 

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

33 Subclasses may override... 

34 """ 

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

36 

37 def add_key(d, key, data): 

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

39 aList.append(data) 

40 d[key] = aList 

41 

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

43 

44 if context: 

45 d = { 

46 # key kind pattern ends? 

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

48 '#': [('len', '###', context == '###'),], 

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

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

51 } 

52 if block1 and block2: 

53 add_key(d, block2[0], ('len', block1, True)) 

54 else: 

55 # Not in any context. 

56 d = { 

57 # key kind pattern new-ctx deltas 

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

59 '#': [('len','###','###', None),], # Docstring 

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

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

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

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

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

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

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

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

68 } 

69 if comment: 

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

71 if block1 and block2: 

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

73 return d 

74 #@+node:ekr.20161119170345.1: *3* coffee_i.Overrides for i.gen_lines 

75 #@+node:ekr.20161118134555.2: *4* coffee_i.end_block 

76 def end_block(self, line, new_state, stack): 

77 """ 

78 Handle an unusual case: an underindented tail line. 

79 

80 line is **not** a class/def line. It *is* underindented so it 

81 *terminates* the previous block. 

82 """ 

83 top = stack[-1] 

84 assert new_state.indent < top.state.indent, (new_state, top.state) 

85 self.cut_stack(new_state, stack) 

86 top = stack[-1] 

87 self.add_line(top.p, line) 

88 tail_p = None if top.at_others_flag else top.p 

89 return tail_p 

90 #@+node:ekr.20161118134555.3: *4* coffee_i.cut_stack (Same as Python) 

91 def cut_stack(self, new_state, stack): 

92 """Cut back the stack until stack[-1] matches new_state.""" 

93 assert len(stack) > 1 # Fail on entry. 

94 while stack: 

95 top_state = stack[-1].state 

96 if new_state.level() < top_state.level(): 

97 assert len(stack) > 1, stack # < 

98 stack.pop() 

99 elif top_state.level() == new_state.level(): 

100 assert len(stack) > 1, stack # == 

101 stack.pop() 

102 break 

103 else: 

104 # This happens often in valid coffescript programs. 

105 break 

106 # Restore the guard entry if necessary. 

107 if len(stack) == 1: 

108 stack.append(stack[-1]) 

109 assert len(stack) > 1 # Fail on exit. 

110 #@+node:ekr.20161118134555.6: *4* coffee_i.start_new_block 

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

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

113 assert not new_state.in_context(), new_state 

114 line = lines[i] 

115 top = stack[-1] 

116 # Adjust the stack. 

117 if new_state.indent > top.state.indent: 

118 pass 

119 elif new_state.indent == top.state.indent: 

120 stack.pop() 

121 else: 

122 self.cut_stack(new_state, stack) 

123 # Create the child. 

124 top = stack[-1] 

125 parent = top.p 

126 h = self.gen_ref(line, parent, top) 

127 child = self.create_child_node(parent, line, h) 

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

129 #@+node:ekr.20161118134555.7: *4* coffee_i.starts_block 

130 pattern_table = [ 

131 re.compile(r'^\s*class'), 

132 re.compile(r'^\s*(.+):(.*)->'), 

133 re.compile(r'^\s*(.+)=(.*)->'), 

134 ] 

135 

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

137 """True if the line starts with the patterns above outside any context.""" 

138 if prev_state.in_context(): 

139 return False 

140 line = lines[i] 

141 for pattern in self.pattern_table: 

142 if pattern.match(line): 

143 return True 

144 return False 

145 

146 #@+node:ekr.20161108181857.1: *3* coffee_i.post_pass & helpers 

147 def post_pass(self, parent): 

148 """Massage the created nodes.""" 

149 # 

150 # Generic: use base Importer methods... 

151 self.clean_all_headlines(parent) 

152 self.clean_all_nodes(parent) 

153 # 

154 # Specific to coffeescript... 

155 self.move_trailing_lines(parent) 

156 # 

157 # Generic: use base Importer methods... 

158 self.unindent_all_nodes(parent) 

159 # 

160 # This sub-pass must follow unindent_all_nodes. 

161 self.promote_trailing_underindented_lines(parent) 

162 # 

163 # This probably should be the last sub-pass. 

164 self.delete_all_empty_nodes(parent) 

165 #@+node:ekr.20160505170558.1: *4* coffee_i.move_trailing_lines & helper (not ready) 

166 def move_trailing_lines(self, parent): 

167 """Move trailing lines into the following node.""" 

168 return # Not ready yet, and maybe never. 

169 # pylint: disable=unreachable 

170 prev_lines = [] 

171 last = None 

172 for p in parent.subtree(): 

173 trailing_lines = self.delete_trailing_lines(p) 

174 if prev_lines: 

175 self.prepend_lines(p, prev_lines) 

176 prev_lines = trailing_lines 

177 last = p.copy() 

178 if prev_lines: 

179 # These should go after the @others lines in the parent. 

180 lines = self.get_lines(parent) 

181 for i, s in enumerate(lines): 

182 if s.strip().startswith('@others'): 

183 lines = lines[: i + 1] + prev_lines + lines[i + 2 :] 

184 self.set_lines(parent, lines) 

185 break 

186 else: 

187 # Fall back. 

188 assert last, "move_trailing_lines" 

189 self.set_lines(last, prev_lines) 

190 #@+node:ekr.20160505173347.1: *5* coffee_i.delete_trailing_lines 

191 def delete_trailing_lines(self, p): 

192 """Delete the trailing lines of p and return them.""" 

193 body_lines, trailing_lines = [], [] 

194 for s in self.get_lines(p): 

195 if s.isspace(): 

196 trailing_lines.append(s) 

197 else: 

198 body_lines.extend(trailing_lines) 

199 body_lines.append(s) 

200 trailing_lines = [] 

201 # Clear trailing lines if they are all blank. 

202 if all(z.isspace() for z in trailing_lines): 

203 trailing_lines = [] 

204 self.set_lines(p, body_lines) 

205 return trailing_lines 

206 #@+node:ekr.20160505180032.1: *4* coffee_i.undent_coffeescript_body 

207 def undent_coffeescript_body(self, s): 

208 """Return the undented body of s.""" 

209 lines = g.splitLines(s) 

210 # Undent all leading whitespace or comment lines. 

211 leading_lines = [] 

212 for line in lines: 

213 if self.is_ws_line(line): 

214 # Tricky. Stipping a black line deletes it. 

215 leading_lines.append(line if line.isspace() else line.lstrip()) 

216 else: 

217 break 

218 i = len(leading_lines) 

219 # Don't unindent the def/class line! It prevents later undents. 

220 tail = self.undent_body_lines(lines[i:], ignoreComments=True) 

221 # Remove all blank lines from leading lines. 

222 if 0: 

223 for i, line in enumerate(leading_lines): 

224 if not line.isspace(): 

225 leading_lines = leading_lines[i:] 

226 break 

227 result = ''.join(leading_lines) + tail 

228 return result 

229 #@-others 

230 @classmethod 

231 def do_import(cls): 

232 def f(c, s, parent): 

233 return cls(c.importCommands).run(s, parent) 

234 return f 

235#@+node:ekr.20161110045131.1: ** class CS_ScanState 

236class CS_ScanState: 

237 """A class representing the state of the coffeescript line-oriented scan.""" 

238 

239 def __init__(self, d=None): 

240 """CS_ScanState ctor.""" 

241 if d: 

242 indent = d.get('indent') 

243 is_ws_line = d.get('is_ws_line') 

244 prev = d.get('prev') 

245 assert indent is not None and is_ws_line is not None 

246 self.bs_nl = False 

247 self.context = prev.context 

248 self.indent = prev.indent if prev.bs_nl else indent 

249 else: 

250 self.bs_nl = False 

251 self.context = '' 

252 self.indent = 0 

253 

254 #@+others 

255 #@+node:ekr.20161118064325.1: *3* cs_state.__repr__ 

256 def __repr__(self): 

257 """CS_State.__repr__""" 

258 return '<CSState %r indent: %s>' % (self.context, self.indent) 

259 

260 __str__ = __repr__ 

261 #@+node:ekr.20161119115413.1: *3* cs_state.level 

262 def level(self): 

263 """CS_ScanState.level.""" 

264 return self.indent 

265 #@+node:ekr.20161118140100.1: *3* cs_state.in_context 

266 def in_context(self): 

267 """True if in a special context.""" 

268 return self.context or self.bs_nl 

269 #@+node:ekr.20161119052920.1: *3* cs_state.update 

270 def update(self, data): 

271 """ 

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

273 Return i = data[1] 

274 """ 

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

276 # self.bs_nl = bs_nl 

277 self.context = context 

278 # self.curlies += delta_c 

279 # self.parens += delta_p 

280 # self.squares += delta_s 

281 return i 

282 

283 #@-others 

284#@-others 

285importer_dict = { 

286 'func': CS_Importer.do_import(), 

287 'extensions': ['.coffee',], 

288} 

289#@@language python 

290#@@tabwidth -4 

291#@-leo