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#@+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, 

22 # Not used: This class overrides i.scan_line. 

23 strict=True 

24 ) 

25 self.errors = 0 

26 self.root = None 

27 self.tab_width = None 

28 # NOT the same as self.c.tabwidth. Set in run(). 

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

30 #@@nobeautify 

31 

32 def get_new_dict(self, context): 

33 """ 

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

35 Subclasses may override... 

36 """ 

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

38 

39 def add_key(d, key, data): 

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

41 aList.append(data) 

42 d[key] = aList 

43 

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

45 

46 if context: 

47 d = { 

48 # key kind pattern ends? 

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

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

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

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

53 } 

54 if block1 and block2: 

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

56 else: 

57 # Not in any context. 

58 d = { 

59 # key kind pattern new-ctx deltas 

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

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

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

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

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

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

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

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

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

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

70 } 

71 if comment: 

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

73 if block1 and block2: 

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

75 return d 

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

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

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

79 """ 

80 Handle an unusual case: an underindented tail line. 

81 

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

83 *terminates* the previous block. 

84 """ 

85 top = stack[-1] 

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

87 self.cut_stack(new_state, stack) 

88 top = stack[-1] 

89 self.add_line(top.p, line) 

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

91 return tail_p 

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

93 def cut_stack(self, new_state, stack): 

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

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

96 while stack: 

97 top_state = stack[-1].state 

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

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

100 stack.pop() 

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

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

103 stack.pop() 

104 break 

105 else: 

106 # This happens often in valid coffescript programs. 

107 break 

108 # Restore the guard entry if necessary. 

109 if len(stack) == 1: 

110 stack.append(stack[-1]) 

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

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

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

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

115 assert not new_state.in_context(), new_state 

116 line = lines[i] 

117 top = stack[-1] 

118 # Adjust the stack. 

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

120 pass 

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

122 stack.pop() 

123 else: 

124 self.cut_stack(new_state, stack) 

125 # Create the child. 

126 top = stack[-1] 

127 parent = top.p 

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

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

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

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

132 pattern_table = [ 

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

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

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

136 ] 

137 

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

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

140 if prev_state.in_context(): 

141 return False 

142 line = lines[i] 

143 for pattern in self.pattern_table: 

144 if pattern.match(line): 

145 return True 

146 return False 

147 

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

149 def post_pass(self, parent): 

150 """Massage the created nodes.""" 

151 # 

152 # Generic: use base Importer methods... 

153 self.clean_all_headlines(parent) 

154 self.clean_all_nodes(parent) 

155 # 

156 # Specific to coffeescript... 

157 self.move_trailing_lines(parent) 

158 # 

159 # Generic: use base Importer methods... 

160 self.unindent_all_nodes(parent) 

161 # 

162 # This sub-pass must follow unindent_all_nodes. 

163 self.promote_trailing_underindented_lines(parent) 

164 # 

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

166 self.delete_all_empty_nodes(parent) 

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

168 def move_trailing_lines(self, parent): 

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

170 return # Not ready yet, and maybe never. 

171 # pylint: disable=unreachable 

172 prev_lines = [] 

173 last = None 

174 for p in parent.subtree(): 

175 trailing_lines = self.delete_trailing_lines(p) 

176 if prev_lines: 

177 self.prepend_lines(p, prev_lines) 

178 prev_lines = trailing_lines 

179 last = p.copy() 

180 if prev_lines: 

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

182 lines = self.get_lines(parent) 

183 for i, s in enumerate(lines): 

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

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

186 self.set_lines(parent, lines) 

187 break 

188 else: 

189 # Fall back. 

190 assert last, "move_trailing_lines" 

191 self.set_lines(last, prev_lines) 

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

193 def delete_trailing_lines(self, p): 

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

195 body_lines, trailing_lines = [], [] 

196 for s in self.get_lines(p): 

197 if s.isspace(): 

198 trailing_lines.append(s) 

199 else: 

200 body_lines.extend(trailing_lines) 

201 body_lines.append(s) 

202 trailing_lines = [] 

203 # Clear trailing lines if they are all blank. 

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

205 trailing_lines = [] 

206 self.set_lines(p, body_lines) 

207 return trailing_lines 

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

209 def undent_coffeescript_body(self, s): 

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

211 lines = g.splitLines(s) 

212 # Undent all leading whitespace or comment lines. 

213 leading_lines = [] 

214 for line in lines: 

215 if self.is_ws_line(line): 

216 # Tricky. Stipping a black line deletes it. 

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

218 else: 

219 break 

220 i = len(leading_lines) 

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

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

223 # Remove all blank lines from leading lines. 

224 if 0: 

225 for i, line in enumerate(leading_lines): 

226 if not line.isspace(): 

227 leading_lines = leading_lines[i:] 

228 break 

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

230 return result 

231 #@-others 

232 @classmethod 

233 def do_import(cls): 

234 def f(c, s, parent): 

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

236 return f 

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

238class CS_ScanState: 

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

240 

241 def __init__(self, d=None): 

242 """CS_ScanState ctor.""" 

243 if d: 

244 indent = d.get('indent') 

245 is_ws_line = d.get('is_ws_line') 

246 prev = d.get('prev') 

247 assert indent is not None and is_ws_line is not None 

248 self.bs_nl = False 

249 self.context = prev.context 

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

251 else: 

252 self.bs_nl = False 

253 self.context = '' 

254 self.indent = 0 

255 

256 #@+others 

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

258 def __repr__(self): 

259 """CS_State.__repr__""" 

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

261 

262 __str__ = __repr__ 

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

264 def level(self): 

265 """CS_ScanState.level.""" 

266 return self.indent 

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

268 def in_context(self): 

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

270 return self.context or self.bs_nl 

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

272 def update(self, data): 

273 """ 

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

275 Return i = data[1] 

276 """ 

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

278 # self.bs_nl = bs_nl 

279 self.context = context 

280 # self.curlies += delta_c 

281 # self.parens += delta_p 

282 # self.squares += delta_s 

283 return i 

284 

285 #@-others 

286#@-others 

287importer_dict = { 

288 'func': CS_Importer.do_import(), 

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

290} 

291#@@language python 

292#@@tabwidth -4 

293#@-leo