PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/csep/src/com/aptana/editor/coffee/parsing/lexer/CoffeeRewriter.java

https://bitbucket.org/adamschmideg/coffeescript-eclipse
Java | 871 lines | 779 code | 59 blank | 33 comment | 251 complexity | 38bf69a5a4d41adf0498a9607ba012f5 MD5 | raw file
  1. /**
  2. * Aptana Studio
  3. * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
  4. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
  5. * Please see the license.html included with this distribution for details.
  6. * Any modifications to this file must keep this entire header intact.
  7. */
  8. package com.aptana.editor.coffee.parsing.lexer;
  9. import java.text.MessageFormat;
  10. import java.util.ArrayList;
  11. import java.util.HashMap;
  12. import java.util.HashSet;
  13. import java.util.List;
  14. import java.util.Map;
  15. import java.util.Set;
  16. import java.util.Stack;
  17. import com.aptana.editor.coffee.parsing.Terminals;
  18. @SuppressWarnings("nls")
  19. public class CoffeeRewriter
  20. {
  21. private static Map<Short, Short> BALANCED_PAIRS = new HashMap<Short, Short>();
  22. static
  23. {
  24. BALANCED_PAIRS.put(Terminals.LPAREN, Terminals.RPAREN);
  25. BALANCED_PAIRS.put(Terminals.LBRACKET, Terminals.RBRACKET);
  26. BALANCED_PAIRS.put(Terminals.LCURLY, Terminals.RCURLY);
  27. BALANCED_PAIRS.put(Terminals.INDENT, Terminals.OUTDENT);
  28. BALANCED_PAIRS.put(Terminals.CALL_START, Terminals.CALL_END);
  29. BALANCED_PAIRS.put(Terminals.PARAM_START, Terminals.PARAM_END);
  30. BALANCED_PAIRS.put(Terminals.INDEX_START, Terminals.INDEX_END);
  31. }
  32. private static Map<Short, Short> INVERSES = new HashMap<Short, Short>();
  33. private static Set<Short> EXPRESSION_START = new HashSet<Short>();
  34. private static Set<Short> EXPRESSION_END = new HashSet<Short>();
  35. static
  36. {
  37. for (Map.Entry<Short, Short> entry : BALANCED_PAIRS.entrySet())
  38. {
  39. Short left = entry.getKey();
  40. Short right = entry.getValue();
  41. INVERSES.put(right, left);
  42. INVERSES.put(left, right);
  43. EXPRESSION_START.add(left);
  44. EXPRESSION_END.add(right);
  45. }
  46. }
  47. private static Set<Short> EXPRESSION_CLOSE = new HashSet<Short>();
  48. static
  49. {
  50. EXPRESSION_CLOSE.add(Terminals.CATCH);
  51. EXPRESSION_CLOSE.add(Terminals.WHEN);
  52. EXPRESSION_CLOSE.add(Terminals.ELSE);
  53. EXPRESSION_CLOSE.add(Terminals.FINALLY);
  54. EXPRESSION_CLOSE.addAll(EXPRESSION_END);
  55. }
  56. private static Set<Short> SINGLE_LINERS = new HashSet<Short>();
  57. static
  58. {
  59. SINGLE_LINERS.add(Terminals.ELSE);
  60. SINGLE_LINERS.add(Terminals.FUNC_ARROW);
  61. SINGLE_LINERS.add(Terminals.BOUND_FUNC_ARROW);
  62. SINGLE_LINERS.add(Terminals.TRY);
  63. SINGLE_LINERS.add(Terminals.FINALLY);
  64. SINGLE_LINERS.add(Terminals.THEN);
  65. }
  66. private static Set<Short> SINGLE_CLOSERS = new HashSet<Short>();
  67. static
  68. {
  69. SINGLE_CLOSERS.add(Terminals.TERMINATOR);
  70. SINGLE_CLOSERS.add(Terminals.CATCH);
  71. SINGLE_CLOSERS.add(Terminals.FINALLY);
  72. SINGLE_CLOSERS.add(Terminals.ELSE);
  73. SINGLE_CLOSERS.add(Terminals.OUTDENT);
  74. SINGLE_CLOSERS.add(Terminals.LEADING_WHEN);
  75. }
  76. private static Set<Short> IMPLICIT_FUNC = new HashSet<Short>();
  77. static
  78. {
  79. IMPLICIT_FUNC.add(Terminals.IDENTIFIER);
  80. IMPLICIT_FUNC.add(Terminals.SUPER);
  81. IMPLICIT_FUNC.add(Terminals.RPAREN);
  82. IMPLICIT_FUNC.add(Terminals.CALL_END);
  83. IMPLICIT_FUNC.add(Terminals.RBRACKET);
  84. IMPLICIT_FUNC.add(Terminals.INDEX_END);
  85. IMPLICIT_FUNC.add(Terminals.AT_SIGIL);
  86. IMPLICIT_FUNC.add(Terminals.THIS);
  87. }
  88. private static Set<Short> IMPLICIT_CALL = new HashSet<Short>();
  89. static
  90. {
  91. IMPLICIT_CALL.add(Terminals.IDENTIFIER);
  92. IMPLICIT_CALL.add(Terminals.NUMBER);
  93. IMPLICIT_CALL.add(Terminals.STRING);
  94. IMPLICIT_CALL.add(Terminals.JS);
  95. IMPLICIT_CALL.add(Terminals.REGEX);
  96. IMPLICIT_CALL.add(Terminals.NEW);
  97. IMPLICIT_CALL.add(Terminals.PARAM_START);
  98. IMPLICIT_CALL.add(Terminals.CLASS);
  99. IMPLICIT_CALL.add(Terminals.IF);
  100. IMPLICIT_CALL.add(Terminals.TRY);
  101. IMPLICIT_CALL.add(Terminals.SWITCH);
  102. IMPLICIT_CALL.add(Terminals.THIS);
  103. IMPLICIT_CALL.add(Terminals.BOOL);
  104. IMPLICIT_CALL.add(Terminals.UNARY);
  105. IMPLICIT_CALL.add(Terminals.SUPER);
  106. IMPLICIT_CALL.add(Terminals.AT_SIGIL);
  107. IMPLICIT_CALL.add(Terminals.FUNC_ARROW);
  108. IMPLICIT_CALL.add(Terminals.BOUND_FUNC_ARROW);
  109. IMPLICIT_CALL.add(Terminals.LBRACKET);
  110. IMPLICIT_CALL.add(Terminals.LPAREN);
  111. IMPLICIT_CALL.add(Terminals.LCURLY);
  112. IMPLICIT_CALL.add(Terminals.MINUS_MINUS);
  113. IMPLICIT_CALL.add(Terminals.PLUS_PLUS);
  114. }
  115. private static Set<Short> IMPLICIT_UNSPACED_CALL = new HashSet<Short>();
  116. static
  117. {
  118. IMPLICIT_UNSPACED_CALL.add(Terminals.PLUS);
  119. IMPLICIT_UNSPACED_CALL.add(Terminals.MINUS);
  120. }
  121. private static Set<Short> IMPLICIT_BLOCK = new HashSet<Short>();
  122. static
  123. {
  124. IMPLICIT_BLOCK.add(Terminals.FUNC_ARROW);
  125. IMPLICIT_BLOCK.add(Terminals.BOUND_FUNC_ARROW);
  126. IMPLICIT_BLOCK.add(Terminals.LCURLY);
  127. IMPLICIT_BLOCK.add(Terminals.LBRACKET);
  128. IMPLICIT_BLOCK.add(Terminals.COMMA);
  129. }
  130. private static Set<Short> IMPLICIT_END = new HashSet<Short>();
  131. static
  132. {
  133. IMPLICIT_END.add(Terminals.POST_IF);
  134. IMPLICIT_END.add(Terminals.FOR);
  135. IMPLICIT_END.add(Terminals.WHILE);
  136. IMPLICIT_END.add(Terminals.UNTIL);
  137. IMPLICIT_END.add(Terminals.WHEN);
  138. IMPLICIT_END.add(Terminals.BY);
  139. IMPLICIT_END.add(Terminals.LOOP);
  140. IMPLICIT_END.add(Terminals.TERMINATOR);
  141. IMPLICIT_END.add(Terminals.INDENT);
  142. }
  143. private static Set<Short> LINEBREAKS = new HashSet<Short>();
  144. static
  145. {
  146. LINEBREAKS.add(Terminals.TERMINATOR);
  147. LINEBREAKS.add(Terminals.INDENT);
  148. LINEBREAKS.add(Terminals.OUTDENT);
  149. }
  150. private static Set<Short> CHECK_FOR_IMPLICIT_INDENTATION = new HashSet<Short>();
  151. static
  152. {
  153. CHECK_FOR_IMPLICIT_INDENTATION.add(Terminals.OUTDENT);
  154. CHECK_FOR_IMPLICIT_INDENTATION.add(Terminals.TERMINATOR);
  155. CHECK_FOR_IMPLICIT_INDENTATION.add(Terminals.FINALLY);
  156. }
  157. private static Set<Short> IMPLICIT_PARENS_CHECK_1 = new HashSet<Short>();
  158. static
  159. {
  160. IMPLICIT_PARENS_CHECK_1.add(Terminals.IF);
  161. IMPLICIT_PARENS_CHECK_1.add(Terminals.ELSE);
  162. IMPLICIT_PARENS_CHECK_1.add(Terminals.FUNC_ARROW);
  163. IMPLICIT_PARENS_CHECK_1.add(Terminals.BOUND_FUNC_ARROW);
  164. }
  165. private static Set<Short> IMPLICIT_PARENS_CHECK_2 = new HashSet<Short>();
  166. static
  167. {
  168. IMPLICIT_PARENS_CHECK_2.add(Terminals.DOT);
  169. IMPLICIT_PARENS_CHECK_2.add(Terminals.QUESTION_DOT);
  170. IMPLICIT_PARENS_CHECK_2.add(Terminals.DOUBLE_COLON);
  171. }
  172. private static Set<Short> IMPLICIT_BRACES = new HashSet<Short>();
  173. static
  174. {
  175. IMPLICIT_BRACES.add(Terminals.IDENTIFIER);
  176. IMPLICIT_BRACES.add(Terminals.NUMBER);
  177. IMPLICIT_BRACES.add(Terminals.STRING);
  178. IMPLICIT_BRACES.add(Terminals.AT_SIGIL);
  179. IMPLICIT_BRACES.add(Terminals.TERMINATOR);
  180. IMPLICIT_BRACES.add(Terminals.OUTDENT);
  181. }
  182. private List<CoffeeSymbol> fTokens;
  183. private boolean seenSingle;
  184. List<CoffeeSymbol> rewrite(List<CoffeeSymbol> tokens) throws SyntaxError
  185. {
  186. this.fTokens = tokens;
  187. removeLeadingNewlines();
  188. removeMidExpressionNewlines();
  189. closeOpenCalls();
  190. closeOpenIndexes();
  191. addImplicitIndentation();
  192. tagPostfixConditionals();
  193. addImplicitBraces();
  194. addImplicitParentheses();
  195. ensureBalance(BALANCED_PAIRS);
  196. rewriteClosingParens();
  197. return this.fTokens;
  198. }
  199. private void removeLeadingNewlines()
  200. {
  201. // Remove leading TERMINATORs
  202. while (!fTokens.isEmpty())
  203. {
  204. CoffeeSymbol sym = fTokens.get(0);
  205. if (Terminals.TERMINATOR != sym.getId())
  206. {
  207. break;
  208. }
  209. fTokens.remove(0);
  210. }
  211. }
  212. private void removeMidExpressionNewlines()
  213. {
  214. for (int i = 0; i < this.fTokens.size();)
  215. {
  216. CoffeeSymbol sym = this.fTokens.get(i);
  217. CoffeeSymbol next = null;
  218. if (i + 1 < this.fTokens.size())
  219. {
  220. next = this.fTokens.get(i + 1);
  221. }
  222. if (sym.getId() == Terminals.TERMINATOR && next != null && EXPRESSION_CLOSE.contains(next.getId()))
  223. {
  224. this.fTokens.remove(i);
  225. continue;
  226. }
  227. i++;
  228. }
  229. }
  230. private void closeOpenCalls()
  231. {
  232. // scanTokens
  233. for (int i = 0; i < this.fTokens.size(); i++)
  234. {
  235. CoffeeSymbol sym = this.fTokens.get(i);
  236. if (Terminals.CALL_START == sym.getId())
  237. {
  238. // detectEnd
  239. int levels = 0;
  240. for (int j = i + 1; j < this.fTokens.size(); j++)
  241. {
  242. CoffeeSymbol token = this.fTokens.get(j);
  243. if (levels == 0
  244. && ((token.getId() == Terminals.RPAREN || token.getId() == Terminals.CALL_END) || (token
  245. .getId() == Terminals.OUTDENT && this.fTokens.get(j - 1).getId() == Terminals.RPAREN)))
  246. {
  247. int index = token.getId() == Terminals.OUTDENT ? j - 1 : j;
  248. this.fTokens.get(index).setId(Terminals.CALL_END);
  249. break;
  250. }
  251. if (token == null || levels < 0)
  252. {
  253. int index = token.getId() == Terminals.OUTDENT ? j - 2 : j - 1;
  254. this.fTokens.get(index).setId(Terminals.CALL_END);
  255. break;
  256. }
  257. if (EXPRESSION_START.contains(token.getId()))
  258. {
  259. levels++;
  260. }
  261. else if (EXPRESSION_END.contains(token.getId()))
  262. {
  263. levels--;
  264. }
  265. }
  266. }
  267. }
  268. }
  269. private void closeOpenIndexes()
  270. {
  271. // scanTokens
  272. for (int i = 0; i < this.fTokens.size(); i++)
  273. {
  274. CoffeeSymbol sym = this.fTokens.get(i);
  275. if (Terminals.INDEX_START == sym.getId())
  276. {
  277. // detectEnd
  278. int levels = 0;
  279. for (int j = i + 1; j < this.fTokens.size(); j++)
  280. {
  281. CoffeeSymbol token = this.fTokens.get(j);
  282. if (levels == 0 && (token.getId() == Terminals.RBRACKET || token.getId() == Terminals.INDEX_END))
  283. {
  284. token.setId(Terminals.INDEX_END);
  285. break;
  286. }
  287. if (token == null || levels < 0)
  288. {
  289. token.setId(Terminals.INDEX_END);
  290. break;
  291. }
  292. if (EXPRESSION_START.contains(token.getId()))
  293. {
  294. levels++;
  295. }
  296. else if (EXPRESSION_END.contains(token.getId()))
  297. {
  298. levels--;
  299. }
  300. }
  301. }
  302. }
  303. }
  304. private void addImplicitBraces() throws SyntaxError
  305. {
  306. Stack<CoffeeSymbol> stack = new Stack<CoffeeSymbol>();
  307. // scanTokens
  308. for (int i = 0; i < this.fTokens.size();)
  309. {
  310. CoffeeSymbol token = this.fTokens.get(i);
  311. if (EXPRESSION_START.contains(token.getId()))
  312. {
  313. short id;
  314. if (Terminals.INDENT == token.getId() && i > 0 && Terminals.LCURLY == this.fTokens.get(i - 1).getId())
  315. {
  316. id = Terminals.LCURLY;
  317. }
  318. else
  319. {
  320. id = token.getId();
  321. }
  322. stack.add(new CoffeeSymbol(id, i));
  323. i++;
  324. continue;
  325. }
  326. if (EXPRESSION_END.contains(token.getId()))
  327. {
  328. if (stack.isEmpty())
  329. {
  330. errorAtToken(MessageFormat.format("Closing {0} without opening pair",
  331. getTerminalNameForShort(token.getId())), i);
  332. }
  333. else
  334. {
  335. stack.pop();
  336. i++;
  337. continue;
  338. }
  339. }
  340. short endOfStack = -1;
  341. if (!stack.isEmpty())
  342. {
  343. endOfStack = stack.get(stack.size() - 1).getId();
  344. }
  345. if (!(Terminals.COLON == token.getId() && ((i >= 2 && Terminals.COLON == this.fTokens.get(i - 2).getId()) || (Terminals.LCURLY != endOfStack))))
  346. {
  347. i++;
  348. continue;
  349. }
  350. stack.push(new CoffeeSymbol(Terminals.LCURLY, "{"));
  351. int idx = ((i >= 2 && Terminals.AT_SIGIL == this.fTokens.get(i - 2).getId()) ? i - 2 : i - 1);
  352. while (idx >= 2 && this.fTokens.get(idx - 2).getId() == Terminals.HERECOMMENT)
  353. {
  354. idx -= 2;
  355. }
  356. // Grab the end of the token before this implicit curly, and use that as our offset
  357. int offsetToUse = 0;
  358. if (idx >= 1)
  359. {
  360. CoffeeSymbol replacing = this.fTokens.get(idx - 1);
  361. offsetToUse = replacing.getEnd();
  362. }
  363. CoffeeSymbol tok = new CoffeeSymbol(Terminals.LCURLY, offsetToUse, offsetToUse, "{");
  364. tok.generated = true;
  365. this.fTokens.add(idx, tok);
  366. // detectEnd
  367. int levels = 0;
  368. for (int j = i + 2; j < this.fTokens.size(); j++)
  369. {
  370. CoffeeSymbol innerToken = this.fTokens.get(j);
  371. if (levels == 0 && addImplicitBrace(innerToken, j))
  372. {
  373. CoffeeSymbol toAdd = new CoffeeSymbol(Terminals.RCURLY, innerToken.getStart(),
  374. innerToken.getStart(), "}");
  375. toAdd.generated = true;
  376. this.fTokens.add(j, toAdd);
  377. break;
  378. }
  379. if (innerToken == null || levels < 0)
  380. {
  381. CoffeeSymbol toAdd = new CoffeeSymbol(Terminals.RCURLY, innerToken.getStart(),
  382. innerToken.getStart(), "}");
  383. toAdd.generated = true;
  384. this.fTokens.add(j, toAdd);
  385. break;
  386. }
  387. if (EXPRESSION_START.contains(innerToken.getId()))
  388. {
  389. levels++;
  390. }
  391. else if (EXPRESSION_END.contains(innerToken.getId()))
  392. {
  393. levels--;
  394. }
  395. }
  396. i += 2;
  397. }
  398. }
  399. private boolean addImplicitBrace(CoffeeSymbol token, int i)
  400. {
  401. Short oneId = -1;
  402. if (i + 1 < this.fTokens.size())
  403. {
  404. oneId = this.fTokens.get(i + 1).getId();
  405. }
  406. if (Terminals.HERECOMMENT == oneId)
  407. {
  408. return false;
  409. }
  410. short tag = token.getId();
  411. Short twoId = -2;
  412. if (i + 2 < this.fTokens.size())
  413. {
  414. twoId = this.fTokens.get(i + 2).getId();
  415. }
  416. Short threeId = -3;
  417. if (i + 3 < this.fTokens.size())
  418. {
  419. threeId = this.fTokens.get(i + 3).getId();
  420. }
  421. // @formatter:off
  422. return (
  423. (Terminals.TERMINATOR == tag || Terminals.OUTDENT == tag) &&
  424. !(Terminals.COLON == twoId || Terminals.AT_SIGIL == oneId && Terminals.COLON == threeId)
  425. )
  426. ||
  427. (Terminals.COMMA == tag && !IMPLICIT_BRACES.contains(oneId));
  428. // @formatter:on
  429. }
  430. private List<CoffeeSymbol> indentation(CoffeeSymbol token)
  431. {
  432. List<CoffeeSymbol> symbols = new ArrayList<CoffeeSymbol>();
  433. symbols.add(new CoffeeSymbol(Terminals.INDENT, token.getStart(), token.getStart(), 2));
  434. symbols.add(new CoffeeSymbol(Terminals.OUTDENT, token.getStart(), token.getStart(), 2));
  435. return symbols;
  436. }
  437. private void addImplicitIndentation()
  438. {
  439. // scanTokens
  440. for (int i = 0; i < this.fTokens.size();)
  441. {
  442. CoffeeSymbol token = this.fTokens.get(i);
  443. short tag = token.getId();
  444. if (Terminals.TERMINATOR == tag && i + 1 < this.fTokens.size()
  445. && Terminals.THEN == this.fTokens.get(i + 1).getId())
  446. {
  447. this.fTokens.remove(i);
  448. continue;
  449. }
  450. if (Terminals.ELSE == tag && Terminals.OUTDENT != this.fTokens.get(i - 1).getId())
  451. {
  452. this.fTokens.addAll(i, indentation(token));
  453. i += 2;
  454. continue;
  455. }
  456. if (Terminals.CATCH == tag && CHECK_FOR_IMPLICIT_INDENTATION.contains(this.fTokens.get(i + 2).getId()))
  457. {
  458. this.fTokens.addAll(i + 2, indentation(token));
  459. i += 4;
  460. continue;
  461. }
  462. if (SINGLE_LINERS.contains(tag) && Terminals.INDENT != this.fTokens.get(i + 1).getId()
  463. && (!(Terminals.ELSE == tag && Terminals.IF == this.fTokens.get(i + 1).getId())))
  464. {
  465. short starter = tag;
  466. List<CoffeeSymbol> indents = indentation(token);
  467. CoffeeSymbol indent = indents.get(0);
  468. CoffeeSymbol outdent = indents.get(1);
  469. indent.fromThen = (Terminals.THEN == starter);
  470. indent.generated = true;
  471. outdent.generated = true;
  472. this.fTokens.add(i + 1, indent);
  473. // detectEnd
  474. int levels = 0;
  475. for (int j = i + 2; j < this.fTokens.size(); j++)
  476. {
  477. CoffeeSymbol innerToken = this.fTokens.get(j);
  478. if (levels == 0 && addImplicitIndent(innerToken, starter, j))
  479. {
  480. int index = j;
  481. if (Terminals.COMMA == this.fTokens.get(j - 1).getId())
  482. {
  483. index = j - 1;
  484. }
  485. // Fix the outdent offsets!
  486. outdent.setLocation(innerToken.getStart(), innerToken.getStart());
  487. this.fTokens.add(index, outdent);
  488. break;
  489. }
  490. if (innerToken == null || levels < 0)
  491. {
  492. int index = j;
  493. if (Terminals.COMMA == this.fTokens.get(j - 1).getId())
  494. {
  495. index = j - 1;
  496. }
  497. // Fix the outdent offsets!
  498. outdent.setLocation(innerToken.getEnd(), innerToken.getEnd());
  499. this.fTokens.add(index, outdent);
  500. break;
  501. }
  502. if (EXPRESSION_START.contains(innerToken.getId()))
  503. {
  504. levels++;
  505. }
  506. else if (EXPRESSION_END.contains(innerToken.getId()))
  507. {
  508. levels--;
  509. }
  510. }
  511. if (Terminals.THEN == tag)
  512. {
  513. this.fTokens.remove(i);
  514. }
  515. i++;
  516. continue;
  517. }
  518. i++;
  519. }
  520. }
  521. private boolean addImplicitIndent(CoffeeSymbol token, short starter, int i)
  522. {
  523. Set<Short> toCheck = new HashSet<Short>();
  524. toCheck.add(Terminals.IF);
  525. toCheck.add(Terminals.THEN);
  526. return (!";".equals(token.getValue()) && SINGLE_CLOSERS.contains(token.getId()) && !(Terminals.ELSE == token
  527. .getId() && !toCheck.contains(starter)));
  528. }
  529. private void addImplicitParentheses()
  530. {
  531. boolean noCall = false;
  532. // scanTokens
  533. for (int i = 0; i < this.fTokens.size();)
  534. {
  535. CoffeeSymbol token = this.fTokens.get(i);
  536. short tag = token.getId();
  537. if (Terminals.CLASS == tag || Terminals.IF == tag)
  538. {
  539. noCall = true;
  540. }
  541. CoffeeSymbol prev = null;
  542. if (i - 1 >= 0)
  543. {
  544. prev = this.fTokens.get(i - 1);
  545. }
  546. CoffeeSymbol next = null;
  547. if (i + 1 < this.fTokens.size())
  548. {
  549. next = this.fTokens.get(i + 1);
  550. }
  551. boolean callObject = (!noCall && Terminals.INDENT == tag && next != null && next.generated
  552. && Terminals.LCURLY == next.getId() && prev != null && IMPLICIT_FUNC.contains(prev.getId()));
  553. this.seenSingle = false;
  554. if (LINEBREAKS.contains(tag))
  555. {
  556. noCall = false;
  557. }
  558. if (prev != null && !prev.spaced && Terminals.QUESTION == tag)
  559. {
  560. token.call = true;
  561. }
  562. if (token.fromThen)
  563. {
  564. i++;
  565. continue;
  566. }
  567. if (!(callObject || (prev != null ? prev.spaced : false)
  568. && (prev.call || IMPLICIT_FUNC.contains(prev.getId()))
  569. && (IMPLICIT_CALL.contains(tag) || !(token.spaced || token.newLine)
  570. && IMPLICIT_UNSPACED_CALL.contains(tag))))
  571. {
  572. i++;
  573. continue;
  574. }
  575. this.fTokens.add(i, new CoffeeSymbol(Terminals.CALL_START, token.getStart(), token.getStart(), "("));
  576. // detectEnd
  577. int levels = 0;
  578. for (int j = i + 1; j < this.fTokens.size(); j++)
  579. {
  580. CoffeeSymbol innerToken = this.fTokens.get(j);
  581. if (levels == 0 && addImplicitParens(innerToken, j))
  582. {
  583. int idx = (Terminals.OUTDENT == innerToken.getId() ? j + 1 : j);
  584. this.fTokens.add(idx, new CoffeeSymbol(Terminals.CALL_END, innerToken.getStart(),
  585. innerToken.getStart(), ")"));
  586. break;
  587. }
  588. if (innerToken == null || levels < 0)
  589. {
  590. int idx = (Terminals.OUTDENT == innerToken.getId() ? j + 1 : j);
  591. this.fTokens.add(idx, new CoffeeSymbol(Terminals.CALL_END, innerToken.getStart(),
  592. innerToken.getStart(), ")"));
  593. break;
  594. }
  595. if (EXPRESSION_START.contains(innerToken.getId()))
  596. {
  597. levels++;
  598. }
  599. else if (EXPRESSION_END.contains(innerToken.getId()))
  600. {
  601. levels--;
  602. }
  603. }
  604. if (Terminals.QUESTION == prev.getId())
  605. {
  606. prev.setId(Terminals.FUNC_EXIST);
  607. }
  608. i += 2;
  609. }
  610. }
  611. private boolean addImplicitParens(CoffeeSymbol token, int i)
  612. {
  613. if (!seenSingle && token.fromThen)
  614. {
  615. return true;
  616. }
  617. short tag = token.getId();
  618. if (IMPLICIT_PARENS_CHECK_1.contains(tag))
  619. {
  620. seenSingle = true;
  621. }
  622. CoffeeSymbol prev = null;
  623. if (i - 1 >= 0)
  624. {
  625. prev = this.fTokens.get(i - 1);
  626. }
  627. if (IMPLICIT_PARENS_CHECK_2.contains(tag) && prev != null && Terminals.OUTDENT == prev.getId())
  628. {
  629. return true;
  630. }
  631. CoffeeSymbol post = null;
  632. if (i + 1 < this.fTokens.size())
  633. {
  634. post = this.fTokens.get(i + 1);
  635. }
  636. return (!token.generated && Terminals.COMMA != prev.getId() && IMPLICIT_END.contains(tag) && (Terminals.INDENT != tag || (Terminals.CLASS != this.fTokens
  637. .get(i - 2).getId() && !IMPLICIT_BLOCK.contains(prev.getId()) && !(post != null && post.generated && Terminals.LCURLY == post
  638. .getId()))));
  639. }
  640. private void tagPostfixConditionals()
  641. {
  642. // scanTokens
  643. for (int i = 0; i < this.fTokens.size();)
  644. {
  645. CoffeeSymbol token = this.fTokens.get(i);
  646. if (Terminals.IF != token.getId())
  647. {
  648. i++;
  649. continue;
  650. }
  651. // detectEnd
  652. int levels = 0;
  653. for (int j = i + 1; j < this.fTokens.size(); j++)
  654. {
  655. CoffeeSymbol innerToken = this.fTokens.get(j);
  656. if (levels == 0
  657. && (Terminals.TERMINATOR == innerToken.getId() || Terminals.INDENT == innerToken.getId()))
  658. {
  659. if (Terminals.INDENT != innerToken.getId())
  660. {
  661. token.setId(Terminals.POST_IF);
  662. }
  663. break;
  664. }
  665. if (innerToken == null || levels < 0)
  666. {
  667. if (Terminals.INDENT != innerToken.getId())
  668. {
  669. token.setId(Terminals.POST_IF);
  670. }
  671. break;
  672. }
  673. if (EXPRESSION_START.contains(innerToken.getId()))
  674. {
  675. levels++;
  676. }
  677. else if (EXPRESSION_END.contains(innerToken.getId()))
  678. {
  679. levels--;
  680. }
  681. }
  682. i++;
  683. }
  684. }
  685. private void ensureBalance(Map<Short, Short> pairs) throws SyntaxError
  686. {
  687. Map<Short, Integer> levels = new HashMap<Short, Integer>();
  688. Map<Short, Integer> openLine = new HashMap<Short, Integer>();
  689. Map<Integer, Integer> offsetToTokenIndex = new HashMap<Integer, Integer>();
  690. for (int i = 0; i < fTokens.size(); i++)
  691. {
  692. CoffeeSymbol token = fTokens.get(i);
  693. for (Map.Entry<Short, Short> pair : pairs.entrySet())
  694. {
  695. short open = pair.getKey();
  696. short close = pair.getValue();
  697. if (!levels.containsKey(open))
  698. {
  699. levels.put(open, 0);
  700. }
  701. if (open == token.getId())
  702. {
  703. int level = levels.get(open);
  704. if (level == 0)
  705. {
  706. // FIXME Put line as value, not offset!
  707. openLine.put(open, token.getStart());
  708. offsetToTokenIndex.put(token.getStart(), i);
  709. }
  710. level++;
  711. levels.put(open, level);
  712. }
  713. else if (close == token.getId())
  714. {
  715. int level = levels.get(open);
  716. level--;
  717. levels.put(open, level);
  718. if (level < 0)
  719. {
  720. // FIXME When we store lines, spit out line number here, not offset
  721. errorAtToken(MessageFormat.format("too many {0} at offset {1}",
  722. getTerminalNameForShort(close), token.getStart()), i);
  723. }
  724. }
  725. }
  726. }
  727. for (Map.Entry<Short, Integer> entry : levels.entrySet())
  728. {
  729. Integer level = entry.getValue();
  730. if (level > 0)
  731. {
  732. Short open = entry.getKey();
  733. int offset = openLine.get(open);
  734. int tokenIndex = offsetToTokenIndex.get(offset);
  735. errorAtToken(MessageFormat.format("unclosed {0} at offset {1}",
  736. getTerminalNameForShort(open), offset), tokenIndex);
  737. }
  738. }
  739. }
  740. private String getTerminalNameForShort(Short id)
  741. {
  742. return Terminals.getNameForValue(id);
  743. }
  744. private void rewriteClosingParens()
  745. {
  746. Stack<CoffeeSymbol> stack = new Stack<CoffeeSymbol>();
  747. Map<Short, Integer> debt = new HashMap<Short, Integer>();
  748. for (Short key : INVERSES.keySet())
  749. {
  750. debt.put(key, 0);
  751. }
  752. // scanTokens
  753. for (int i = 0; i < this.fTokens.size();)
  754. {
  755. CoffeeSymbol token = this.fTokens.get(i);
  756. short tag = token.getId();
  757. if (EXPRESSION_START.contains(tag))
  758. {
  759. stack.push(token);
  760. i++;
  761. continue;
  762. }
  763. if (!EXPRESSION_END.contains(tag))
  764. {
  765. i++;
  766. continue;
  767. }
  768. Short inv = INVERSES.get(tag);
  769. int invValue = debt.get(inv);
  770. if (invValue > 0)
  771. {
  772. invValue--;
  773. debt.put(inv, invValue);
  774. this.fTokens.remove(i);
  775. continue;
  776. }
  777. CoffeeSymbol match = stack.pop();
  778. short mtag = match.getId();
  779. short oppos = INVERSES.get(mtag);
  780. if (tag == oppos)
  781. {
  782. i++;
  783. continue;
  784. }
  785. int mtagValue = debt.get(mtag);
  786. mtagValue++;
  787. debt.put(mtag, mtagValue);
  788. // Use start offset of next token as our start and end offset
  789. CoffeeSymbol val = new CoffeeSymbol(oppos, token.getStart(), token.getStart(),
  790. Terminals.INDENT == mtag ? match.getValue() : Terminals.getValue(oppos));
  791. if (mtag == this.fTokens.get(i + 2).getId())
  792. {
  793. this.fTokens.add(i + 3, val);
  794. stack.push(match);
  795. }
  796. else
  797. {
  798. this.fTokens.add(i, val);
  799. }
  800. i++;
  801. }
  802. }
  803. /**
  804. * Remove all tokens starting at <var>tokenIndex</var>, then throw an exception with <var>errorMessage</var>
  805. * @throws SyntaxError
  806. */
  807. private void errorAtToken(String errorMessage, int tokenIndex) throws SyntaxError
  808. {
  809. fTokens.subList(tokenIndex, fTokens.size()).clear();
  810. throw new SyntaxError(errorMessage);
  811. }
  812. }