layouter.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2018 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Layouter for nimpretty.
  10. import idents, lexer, lineinfos, llstream, options, msgs, strutils, pathutils
  11. const
  12. MinLineLen = 15
  13. type
  14. SplitKind = enum
  15. splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary
  16. SemicolonKind = enum
  17. detectSemicolonKind, useSemicolon, dontTouch
  18. LayoutToken = enum
  19. ltSpaces,
  20. ltCrucialNewline, ## a semantically crucial newline (indentation!)
  21. ltSplittingNewline, ## newline used for splitting up long
  22. ## expressions (like after a comma or a binary operator)
  23. ltTab,
  24. ltOptionalNewline, ## optional newline introduced by nimpretty
  25. ltComment, ltLit, ltKeyword, ltExportMarker, ltIdent,
  26. ltOther, ltOpr, ltSomeParLe, ltSomeParRi,
  27. ltBeginSection, ltEndSection
  28. Emitter* = object
  29. config: ConfigRef
  30. fid: FileIndex
  31. lastTok: TTokType
  32. inquote, lastTokWasTerse: bool
  33. semicolons: SemicolonKind
  34. col, lastLineNumber, lineSpan, indentLevel, indWidth*, inSection: int
  35. keepIndents*: int
  36. doIndentMore*: int
  37. kinds: seq[LayoutToken]
  38. tokens: seq[string]
  39. indentStack: seq[int]
  40. fixedUntil: int # marks where we must not go in the content
  41. altSplitPos: array[SplitKind, int] # alternative split positions
  42. maxLineLen*: int
  43. proc openEmitter*(em: var Emitter, cache: IdentCache;
  44. config: ConfigRef, fileIdx: FileIndex) =
  45. let fullPath = AbsoluteFile config.toFullPath(fileIdx)
  46. if em.indWidth == 0:
  47. em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead),
  48. cache, config)
  49. if em.indWidth == 0: em.indWidth = 2
  50. em.config = config
  51. em.fid = fileIdx
  52. em.lastTok = tkInvalid
  53. em.inquote = false
  54. em.col = 0
  55. em.indentStack = newSeqOfCap[int](30)
  56. em.indentStack.add 0
  57. em.lastLineNumber = 1
  58. proc computeMax(em: Emitter; pos: int): int =
  59. var p = pos
  60. var extraSpace = 0
  61. result = 0
  62. while p < em.tokens.len and em.kinds[p] != ltEndSection:
  63. var lhs = 0
  64. var lineLen = 0
  65. var foundTab = false
  66. while p < em.tokens.len and em.kinds[p] != ltEndSection:
  67. if em.kinds[p] in {ltCrucialNewline, ltSplittingNewline}:
  68. if foundTab and lineLen <= em.maxLineLen:
  69. result = max(result, lhs + extraSpace)
  70. inc p
  71. break
  72. if em.kinds[p] == ltTab:
  73. extraSpace = if em.kinds[p-1] == ltSpaces: 0 else: 1
  74. foundTab = true
  75. else:
  76. if not foundTab:
  77. inc lhs, em.tokens[p].len
  78. inc lineLen, em.tokens[p].len
  79. inc p
  80. proc computeRhs(em: Emitter; pos: int): int =
  81. var p = pos
  82. result = 0
  83. while p < em.tokens.len and em.kinds[p] notin {ltCrucialNewline, ltSplittingNewline}:
  84. inc result, em.tokens[p].len
  85. inc p
  86. proc isLongEnough(lineLen, startPos, endPos: int): bool =
  87. result = lineLen > MinLineLen and endPos > startPos + 4
  88. proc findNewline(em: Emitter; p, lineLen: var int) =
  89. while p < em.tokens.len and em.kinds[p] notin {ltCrucialNewline, ltSplittingNewline}:
  90. inc lineLen, em.tokens[p].len
  91. inc p
  92. proc countNewlines(s: string): int =
  93. result = 0
  94. for i in 0..<s.len:
  95. if s[i] == '\L': inc result
  96. proc calcCol(em: var Emitter; s: string) =
  97. var i = s.len-1
  98. em.col = 0
  99. while i >= 0 and s[i] != '\L':
  100. dec i
  101. inc em.col
  102. proc optionalIsGood(em: var Emitter; pos, currentLen: int): bool =
  103. let ourIndent = em.tokens[pos].len
  104. var p = pos+1
  105. var lineLen = 0
  106. em.findNewline(p, lineLen)
  107. if p == pos+1: # optionalNewline followed by another newline
  108. result = false
  109. elif em.kinds[p-1] == ltComment and currentLen+lineLen < em.maxLineLen+MinLineLen:
  110. result = false
  111. elif p+1 < em.tokens.len and em.kinds[p+1] == ltSpaces and
  112. em.kinds[p-1] == ltOptionalNewline:
  113. if em.tokens[p+1].len == ourIndent:
  114. # concatenate lines with the same indententation
  115. var nlPos = p
  116. var lineLenTotal = lineLen
  117. inc p
  118. em.findNewline(p, lineLenTotal)
  119. if isLongEnough(lineLenTotal, nlPos, p):
  120. em.kinds[nlPos] = ltOptionalNewline
  121. if em.kinds[nlPos+1] == ltSpaces:
  122. # inhibit extra spaces when concatenating two lines
  123. em.tokens[nlPos+1] = if em.tokens[nlPos-2] == ",": " " else: ""
  124. result = true
  125. elif em.tokens[p+1].len < ourIndent:
  126. result = isLongEnough(lineLen, pos, p)
  127. elif em.kinds[pos+1] in {ltOther, ltSomeParLe, ltSomeParRi}: # note: pos+1, not p+1
  128. result = false
  129. else:
  130. result = isLongEnough(lineLen, pos, p)
  131. proc lenOfNextTokens(em: Emitter; pos: int): int =
  132. result = 0
  133. for i in 1 ..< em.tokens.len-pos:
  134. if em.kinds[pos+i] in {ltCrucialNewline, ltSplittingNewline, ltOptionalNewline}: break
  135. inc result, em.tokens[pos+i].len
  136. proc guidingInd(em: Emitter; pos: int): int =
  137. var i = pos - 1
  138. while i >= 0 and em.kinds[i] != ltSomeParLe:
  139. dec i
  140. while i+1 <= em.kinds.high and em.kinds[i] != ltSomeParRi:
  141. if em.kinds[i] == ltSplittingNewline and em.kinds[i+1] == ltSpaces:
  142. return em.tokens[i+1].len
  143. inc i
  144. result = -1
  145. proc closeEmitter*(em: var Emitter) =
  146. template defaultCase() =
  147. content.add em.tokens[i]
  148. inc lineLen, em.tokens[i].len
  149. let outFile = em.config.absOutFile
  150. var content = newStringOfCap(16_000)
  151. var maxLhs = 0
  152. var lineLen = 0
  153. var lineBegin = 0
  154. var openPars = 0
  155. var i = 0
  156. while i <= em.tokens.high:
  157. when defined(debug):
  158. echo (token: em.tokens[i], kind: em.kinds[i])
  159. case em.kinds[i]
  160. of ltBeginSection:
  161. maxLhs = computeMax(em, lineBegin)
  162. of ltEndSection:
  163. maxLhs = 0
  164. lineBegin = i+1
  165. of ltTab:
  166. if i >= 2 and em.kinds[i-2] in {ltCrucialNewline, ltSplittingNewline} and
  167. em.kinds[i-1] in {ltCrucialNewline, ltSplittingNewline, ltSpaces}:
  168. # a previous section has ended
  169. maxLhs = 0
  170. if maxLhs == 0:
  171. if em.kinds[i-1] != ltSpaces:
  172. content.add em.tokens[i]
  173. inc lineLen, em.tokens[i].len
  174. else:
  175. # pick the shorter indentation token:
  176. var spaces = maxLhs - lineLen
  177. if spaces < em.tokens[i].len or computeRhs(em, i+1)+maxLhs <= em.maxLineLen+MinLineLen:
  178. if spaces <= 0 and content[^1] notin {' ', '\L'}: spaces = 1
  179. for j in 1..spaces: content.add ' '
  180. inc lineLen, spaces
  181. else:
  182. content.add em.tokens[i]
  183. inc lineLen, em.tokens[i].len
  184. of ltCrucialNewline, ltSplittingNewline:
  185. content.add em.tokens[i]
  186. lineLen = 0
  187. lineBegin = i+1
  188. of ltOptionalNewline:
  189. let totalLineLen = lineLen + lenOfNextTokens(em, i)
  190. if totalLineLen > em.maxLineLen and optionalIsGood(em, i, lineLen):
  191. if i-1 >= 0 and em.kinds[i-1] == ltSpaces:
  192. let spaces = em.tokens[i-1].len
  193. content.setLen(content.len - spaces)
  194. content.add "\L"
  195. let guide = if openPars > 0: guidingInd(em, i) else: -1
  196. if guide >= 0:
  197. content.add repeat(' ', guide)
  198. lineLen = guide
  199. else:
  200. content.add em.tokens[i]
  201. lineLen = em.tokens[i].len
  202. lineBegin = i+1
  203. if i+1 < em.kinds.len and em.kinds[i+1] == ltSpaces:
  204. # inhibit extra spaces at the start of a new line
  205. inc i
  206. of ltLit:
  207. let lineSpan = countNewlines(em.tokens[i])
  208. if lineSpan > 0:
  209. em.calcCol(em.tokens[i])
  210. lineLen = em.col
  211. else:
  212. inc lineLen, em.tokens[i].len
  213. content.add em.tokens[i]
  214. of ltSomeParLe:
  215. inc openPars
  216. defaultCase()
  217. of ltSomeParRi:
  218. doAssert openPars > 0
  219. dec openPars
  220. defaultCase()
  221. else:
  222. defaultCase()
  223. inc i
  224. if fileExists(outFile) and readFile(outFile.string) == content:
  225. discard "do nothing, see #9499"
  226. return
  227. var f = llStreamOpen(outFile, fmWrite)
  228. if f == nil:
  229. rawMessage(em.config, errGenerated, "cannot open file: " & outFile.string)
  230. return
  231. f.llStreamWrite content
  232. llStreamClose(f)
  233. proc wr(em: var Emitter; x: string; lt: LayoutToken) =
  234. em.tokens.add x
  235. em.kinds.add lt
  236. inc em.col, x.len
  237. assert em.tokens.len == em.kinds.len
  238. proc wrNewline(em: var Emitter; kind = ltCrucialNewline) =
  239. em.tokens.add "\L"
  240. em.kinds.add kind
  241. em.col = 0
  242. proc newlineWasSplitting*(em: var Emitter) =
  243. if em.kinds.len >= 3 and em.kinds[^3] == ltCrucialNewline:
  244. em.kinds[^3] = ltSplittingNewline
  245. #[
  246. Splitting newlines can occur:
  247. - after commas, semicolon, '[', '('.
  248. - after binary operators, '='.
  249. - after ':' type
  250. We only need parser support for the "after type" case.
  251. ]#
  252. proc wrSpaces(em: var Emitter; spaces: int) =
  253. if spaces > 0:
  254. wr(em, strutils.repeat(' ', spaces), ltSpaces)
  255. proc wrSpace(em: var Emitter) =
  256. wr(em, " ", ltSpaces)
  257. proc wrTab(em: var Emitter) =
  258. wr(em, " ", ltTab)
  259. proc beginSection*(em: var Emitter) =
  260. let pos = max(0, em.tokens.len-2)
  261. em.tokens.insert "", pos
  262. em.kinds.insert ltBeginSection, pos
  263. inc em.inSection
  264. #wr(em, "", ltBeginSection)
  265. proc endSection*(em: var Emitter) =
  266. em.tokens.insert "", em.tokens.len-2
  267. em.kinds.insert ltEndSection, em.kinds.len-2
  268. dec em.inSection
  269. #wr(em, "", ltEndSection)
  270. proc removeSpaces(em: var Emitter) =
  271. while em.kinds.len > 0 and em.kinds[^1] == ltSpaces:
  272. let tokenLen = em.tokens[^1].len
  273. setLen(em.tokens, em.tokens.len-1)
  274. setLen(em.kinds, em.kinds.len-1)
  275. dec em.col, tokenLen
  276. const
  277. openPars = {tkParLe, tkParDotLe,
  278. tkBracketLe, tkBracketDotLe, tkBracketLeColon,
  279. tkCurlyDotLe, tkCurlyLe}
  280. closedPars = {tkParRi, tkParDotRi,
  281. tkBracketRi, tkBracketDotRi,
  282. tkCurlyDotRi, tkCurlyRi}
  283. splitters = openPars + {tkComma, tkSemiColon} # do not add 'tkColon' here!
  284. oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
  285. tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor}
  286. template goodCol(col): bool = col >= em.maxLineLen div 2
  287. template moreIndent(em): int =
  288. if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth
  289. template rememberSplit(kind) =
  290. if goodCol(em.col) and not em.inquote:
  291. let spaces = em.indentLevel+moreIndent(em)
  292. if spaces < em.col and spaces > 0:
  293. wr(em, strutils.repeat(' ', spaces), ltOptionalNewline)
  294. #em.altSplitPos[kind] = em.tokens.len
  295. proc emitMultilineComment(em: var Emitter, lit: string, col: int; dontIndent: bool) =
  296. # re-align every line in the multi-line comment:
  297. var i = 0
  298. var lastIndent = if em.keepIndents > 0: em.indentLevel else: em.indentStack[^1]
  299. var b = 0
  300. var dontIndent = dontIndent
  301. var hasEmptyLine = false
  302. for commentLine in splitLines(lit):
  303. if i == 0 and (commentLine.endsWith("\\") or commentLine.endsWith("[")):
  304. dontIndent = true
  305. wr em, commentLine, ltComment
  306. elif dontIndent:
  307. if i > 0: wrNewline em
  308. wr em, commentLine, ltComment
  309. else:
  310. let stripped = commentLine.strip()
  311. if i == 0:
  312. if em.kinds.len > 0 and em.kinds[^1] != ltTab:
  313. wr(em, "", ltTab)
  314. elif stripped.len == 0:
  315. wrNewline em
  316. hasEmptyLine = true
  317. else:
  318. var a = 0
  319. while a < commentLine.len and commentLine[a] == ' ': inc a
  320. if a > lastIndent:
  321. b += em.indWidth
  322. lastIndent = a
  323. elif a < lastIndent:
  324. b -= em.indWidth
  325. lastIndent = a
  326. wrNewline em
  327. if not hasEmptyLine or col + b < 15:
  328. if col + b > 0:
  329. wr(em, repeat(' ', col+b), ltTab)
  330. else:
  331. wr(em, "", ltTab)
  332. else:
  333. wr(em, repeat(' ', a), ltSpaces)
  334. wr em, stripped, ltComment
  335. inc i
  336. proc lastChar(s: string): char =
  337. result = if s.len > 0: s[s.high] else: '\0'
  338. proc endsInWhite(em: Emitter): bool =
  339. var i = em.tokens.len-1
  340. while i >= 0 and em.kinds[i] in {ltBeginSection, ltEndSection}: dec(i)
  341. result = if i >= 0: em.kinds[i] in {ltSpaces, ltCrucialNewline, ltSplittingNewline, ltTab} else: true
  342. proc endsInNewline(em: Emitter): bool =
  343. var i = em.tokens.len-1
  344. while i >= 0 and em.kinds[i] in {ltBeginSection, ltEndSection, ltSpaces}: dec(i)
  345. result = if i >= 0: em.kinds[i] in {ltCrucialNewline, ltSplittingNewline, ltTab} else: true
  346. proc endsInAlpha(em: Emitter): bool =
  347. var i = em.tokens.len-1
  348. while i >= 0 and em.kinds[i] in {ltBeginSection, ltEndSection}: dec(i)
  349. result = if i >= 0: em.tokens[i].lastChar in SymChars+{'_'} else: false
  350. proc emitComment(em: var Emitter; tok: TToken; dontIndent: bool) =
  351. var col = em.col
  352. let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB)
  353. em.lineSpan = countNewlines(lit)
  354. if em.lineSpan > 0: calcCol(em, lit)
  355. if em.lineSpan == 0:
  356. if not endsInNewline(em):
  357. wrTab em
  358. wr em, lit, ltComment
  359. else:
  360. if not endsInWhite(em):
  361. wrTab em
  362. inc col
  363. emitMultilineComment(em, lit, col, dontIndent)
  364. proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
  365. template wasExportMarker(em): bool =
  366. em.kinds.len > 0 and em.kinds[^1] == ltExportMarker
  367. if tok.tokType == tkComment and tok.literal.startsWith("#!nimpretty"):
  368. case tok.literal
  369. of "#!nimpretty off":
  370. inc em.keepIndents
  371. wrNewline em
  372. em.lastLineNumber = tok.line + 1
  373. of "#!nimpretty on":
  374. dec em.keepIndents
  375. em.lastLineNumber = tok.line
  376. wrNewline em
  377. wr em, tok.literal, ltComment
  378. em.col = 0
  379. em.lineSpan = 0
  380. return
  381. var preventComment = false
  382. if tok.tokType == tkComment and tok.line == em.lastLineNumber:
  383. # we have an inline comment so handle it before the indentation token:
  384. emitComment(em, tok, dontIndent = (em.inSection == 0))
  385. preventComment = true
  386. em.fixedUntil = em.tokens.high
  387. elif tok.indent >= 0:
  388. var newlineKind = ltCrucialNewline
  389. if em.keepIndents > 0:
  390. em.indentLevel = tok.indent
  391. elif (em.lastTok in (splitters + oprSet) and
  392. tok.tokType notin (closedPars - {tkBracketDotRi})):
  393. if tok.tokType in openPars and tok.indent > em.indentStack[^1]:
  394. while em.indentStack[^1] < tok.indent:
  395. em.indentStack.add(em.indentStack[^1] + em.indWidth)
  396. # aka: we are in an expression context:
  397. let alignment = max(tok.indent - em.indentStack[^1], 0)
  398. em.indentLevel = alignment + em.indentStack.high * em.indWidth
  399. newlineKind = ltSplittingNewline
  400. else:
  401. if tok.indent > em.indentStack[^1]:
  402. em.indentStack.add tok.indent
  403. else:
  404. # dedent?
  405. while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent:
  406. discard em.indentStack.pop()
  407. em.indentLevel = em.indentStack.high * em.indWidth
  408. #[ we only correct the indentation if it is not in an expression context,
  409. so that code like
  410. const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe,
  411. tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
  412. tkCurlyLe}
  413. is not touched.
  414. ]#
  415. # remove trailing whitespace:
  416. removeSpaces em
  417. wrNewline em, newlineKind
  418. for i in 2..tok.line - em.lastLineNumber: wrNewline(em)
  419. wrSpaces em, em.indentLevel
  420. em.fixedUntil = em.tokens.high
  421. var lastTokWasTerse = false
  422. case tok.tokType
  423. of tokKeywordLow..tokKeywordHigh:
  424. if endsInAlpha(em):
  425. wrSpace em
  426. elif not em.inquote and not endsInWhite(em) and
  427. em.lastTok notin (openPars+{tkOpr, tkDotDot}) and not em.lastTokWasTerse:
  428. #and tok.tokType in oprSet
  429. wrSpace em
  430. if not em.inquote:
  431. wr(em, TokTypeToStr[tok.tokType], ltKeyword)
  432. if tok.tokType in {tkAnd, tkOr, tkIn, tkNotin}:
  433. rememberSplit(splitIn)
  434. wrSpace em
  435. else:
  436. # keywords in backticks are not normalized:
  437. wr(em, tok.ident.s, ltIdent)
  438. of tkColon:
  439. wr(em, TokTypeToStr[tok.tokType], ltOther)
  440. wrSpace em
  441. of tkSemiColon, tkComma:
  442. wr(em, TokTypeToStr[tok.tokType], ltOther)
  443. rememberSplit(splitComma)
  444. wrSpace em
  445. of openPars:
  446. if tok.strongSpaceA > 0 and not em.endsInWhite and
  447. (not em.wasExportMarker or tok.tokType == tkCurlyDotLe):
  448. wrSpace em
  449. wr(em, TokTypeToStr[tok.tokType], ltSomeParLe)
  450. rememberSplit(splitParLe)
  451. of closedPars:
  452. wr(em, TokTypeToStr[tok.tokType], ltSomeParRi)
  453. of tkColonColon:
  454. wr(em, TokTypeToStr[tok.tokType], ltOther)
  455. of tkDot:
  456. lastTokWasTerse = true
  457. wr(em, TokTypeToStr[tok.tokType], ltOther)
  458. of tkEquals:
  459. if not em.inquote and not em.endsInWhite: wrSpace(em)
  460. wr(em, TokTypeToStr[tok.tokType], ltOther)
  461. if not em.inquote: wrSpace(em)
  462. of tkOpr, tkDotDot:
  463. if em.inquote or ((tok.strongSpaceA == 0 and tok.strongSpaceB == 0) and
  464. tok.ident.s notin ["<", ">", "<=", ">=", "==", "!="]):
  465. # bug #9504: remember to not spacify a keyword:
  466. lastTokWasTerse = true
  467. # if not surrounded by whitespace, don't produce any whitespace either:
  468. wr(em, tok.ident.s, ltOpr)
  469. else:
  470. if not em.endsInWhite: wrSpace(em)
  471. wr(em, tok.ident.s, ltOpr)
  472. template isUnary(tok): bool =
  473. tok.strongSpaceB == 0 and tok.strongSpaceA > 0
  474. if not isUnary(tok):
  475. rememberSplit(splitBinary)
  476. wrSpace(em)
  477. of tkAccent:
  478. if not em.inquote and endsInAlpha(em): wrSpace(em)
  479. wr(em, TokTypeToStr[tok.tokType], ltOther)
  480. em.inquote = not em.inquote
  481. of tkComment:
  482. if not preventComment:
  483. emitComment(em, tok, dontIndent = false)
  484. of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit:
  485. let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB)
  486. if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wrSpace(em)
  487. em.lineSpan = countNewlines(lit)
  488. if em.lineSpan > 0: calcCol(em, lit)
  489. wr em, lit, ltLit
  490. of tkEof: discard
  491. else:
  492. let lit = if tok.ident != nil: tok.ident.s else: tok.literal
  493. if endsInAlpha(em): wrSpace(em)
  494. wr em, lit, ltIdent
  495. em.lastTok = tok.tokType
  496. em.lastTokWasTerse = lastTokWasTerse
  497. em.lastLineNumber = tok.line + em.lineSpan
  498. em.lineSpan = 0
  499. proc endsWith(em: Emitter; k: varargs[string]): bool =
  500. if em.tokens.len < k.len: return false
  501. for i in 0..high(k):
  502. if em.tokens[em.tokens.len - k.len + i] != k[i]: return false
  503. return true
  504. proc rfind(em: Emitter, t: string): int =
  505. for i in 1 .. 5:
  506. if em.tokens[^i] == t:
  507. return i
  508. proc starWasExportMarker*(em: var Emitter) =
  509. if em.endsWith(" ", "*", " "):
  510. setLen(em.tokens, em.tokens.len-3)
  511. setLen(em.kinds, em.kinds.len-3)
  512. em.tokens.add("*")
  513. em.kinds.add ltExportMarker
  514. dec em.col, 2
  515. proc commaWasSemicolon*(em: var Emitter) =
  516. if em.semicolons == detectSemicolonKind:
  517. em.semicolons = if em.rfind(";") > 0: useSemicolon else: dontTouch
  518. if em.semicolons == useSemicolon:
  519. let commaPos = em.rfind(",")
  520. if commaPos > 0:
  521. em.tokens[^commaPos] = ";"
  522. proc curlyRiWasPragma*(em: var Emitter) =
  523. if em.endsWith("}"):
  524. em.tokens[^1] = ".}"
  525. inc em.col