layouter.nim 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  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: TokType
  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 renderTokens*(em: var Emitter): string =
  146. ## Render Emitter tokens to a string of code
  147. template defaultCase() =
  148. content.add em.tokens[i]
  149. inc lineLen, em.tokens[i].len
  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. return content
  225. proc writeOut*(em: Emitter, content: string) =
  226. ## Write to disk
  227. let outFile = em.config.absOutFile
  228. if fileExists(outFile) and readFile(outFile.string) == content:
  229. discard "do nothing, see #9499"
  230. return
  231. var f = llStreamOpen(outFile, fmWrite)
  232. if f == nil:
  233. rawMessage(em.config, errGenerated, "cannot open file: " & outFile.string)
  234. return
  235. f.llStreamWrite content
  236. llStreamClose(f)
  237. proc closeEmitter*(em: var Emitter) =
  238. ## Renders emitter tokens and write to a file
  239. let content = renderTokens(em)
  240. em.writeOut(content)
  241. proc wr(em: var Emitter; x: string; lt: LayoutToken) =
  242. em.tokens.add x
  243. em.kinds.add lt
  244. inc em.col, x.len
  245. assert em.tokens.len == em.kinds.len
  246. proc wrNewline(em: var Emitter; kind = ltCrucialNewline) =
  247. em.tokens.add "\L"
  248. em.kinds.add kind
  249. em.col = 0
  250. proc newlineWasSplitting*(em: var Emitter) =
  251. if em.kinds.len >= 3 and em.kinds[^3] == ltCrucialNewline:
  252. em.kinds[^3] = ltSplittingNewline
  253. #[
  254. Splitting newlines can occur:
  255. - after commas, semicolon, '[', '('.
  256. - after binary operators, '='.
  257. - after ':' type
  258. We only need parser support for the "after type" case.
  259. ]#
  260. proc wrSpaces(em: var Emitter; spaces: int) =
  261. if spaces > 0:
  262. wr(em, strutils.repeat(' ', spaces), ltSpaces)
  263. proc wrSpace(em: var Emitter) =
  264. wr(em, " ", ltSpaces)
  265. proc wrTab(em: var Emitter) =
  266. wr(em, " ", ltTab)
  267. proc beginSection*(em: var Emitter) =
  268. let pos = max(0, em.tokens.len-2)
  269. em.tokens.insert "", pos
  270. em.kinds.insert ltBeginSection, pos
  271. inc em.inSection
  272. #wr(em, "", ltBeginSection)
  273. proc endSection*(em: var Emitter) =
  274. em.tokens.insert "", em.tokens.len-2
  275. em.kinds.insert ltEndSection, em.kinds.len-2
  276. dec em.inSection
  277. #wr(em, "", ltEndSection)
  278. proc removeSpaces(em: var Emitter) =
  279. while em.kinds.len > 0 and em.kinds[^1] == ltSpaces:
  280. let tokenLen = em.tokens[^1].len
  281. setLen(em.tokens, em.tokens.len-1)
  282. setLen(em.kinds, em.kinds.len-1)
  283. dec em.col, tokenLen
  284. const
  285. openPars = {tkParLe, tkParDotLe,
  286. tkBracketLe, tkBracketDotLe, tkBracketLeColon,
  287. tkCurlyDotLe, tkCurlyLe}
  288. closedPars = {tkParRi, tkParDotRi,
  289. tkBracketRi, tkBracketDotRi,
  290. tkCurlyDotRi, tkCurlyRi}
  291. splitters = openPars + {tkComma, tkSemiColon} # do not add 'tkColon' here!
  292. oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
  293. tkIsnot, tkNot, tkOf, tkAs, tkFrom, tkDotDot, tkAnd, tkOr, tkXor}
  294. template goodCol(col): bool = col >= em.maxLineLen div 2
  295. template moreIndent(em): int =
  296. if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth
  297. template rememberSplit(kind) =
  298. if goodCol(em.col) and not em.inquote:
  299. let spaces = em.indentLevel+moreIndent(em)
  300. if spaces < em.col and spaces > 0:
  301. wr(em, strutils.repeat(' ', spaces), ltOptionalNewline)
  302. #em.altSplitPos[kind] = em.tokens.len
  303. proc emitMultilineComment(em: var Emitter, lit: string, col: int; dontIndent: bool) =
  304. # re-align every line in the multi-line comment:
  305. var i = 0
  306. var lastIndent = if em.keepIndents > 0: em.indentLevel else: em.indentStack[^1]
  307. var b = 0
  308. var dontIndent = dontIndent
  309. var hasEmptyLine = false
  310. for commentLine in splitLines(lit):
  311. if i == 0 and (commentLine.endsWith("\\") or commentLine.endsWith("[")):
  312. dontIndent = true
  313. wr em, commentLine, ltComment
  314. elif dontIndent:
  315. if i > 0: wrNewline em
  316. wr em, commentLine, ltComment
  317. else:
  318. let stripped = commentLine.strip()
  319. if i == 0:
  320. if em.kinds.len > 0 and em.kinds[^1] != ltTab:
  321. wr(em, "", ltTab)
  322. elif stripped.len == 0:
  323. wrNewline em
  324. hasEmptyLine = true
  325. else:
  326. var a = 0
  327. while a < commentLine.len and commentLine[a] == ' ': inc a
  328. if a > lastIndent:
  329. b += em.indWidth
  330. lastIndent = a
  331. elif a < lastIndent:
  332. b -= em.indWidth
  333. lastIndent = a
  334. wrNewline em
  335. if not hasEmptyLine or col + b < 15:
  336. if col + b > 0:
  337. wr(em, repeat(' ', col+b), ltTab)
  338. else:
  339. wr(em, "", ltTab)
  340. else:
  341. wr(em, repeat(' ', a), ltSpaces)
  342. wr em, stripped, ltComment
  343. inc i
  344. proc lastChar(s: string): char =
  345. result = if s.len > 0: s[s.high] else: '\0'
  346. proc endsInWhite(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.kinds[i] in {ltSpaces, ltCrucialNewline, ltSplittingNewline, ltTab} else: true
  350. proc endsInNewline(em: Emitter): bool =
  351. var i = em.tokens.len-1
  352. while i >= 0 and em.kinds[i] in {ltBeginSection, ltEndSection, ltSpaces}: dec(i)
  353. result = if i >= 0: em.kinds[i] in {ltCrucialNewline, ltSplittingNewline, ltTab} else: true
  354. proc endsInAlpha(em: Emitter): bool =
  355. var i = em.tokens.len-1
  356. while i >= 0 and em.kinds[i] in {ltBeginSection, ltEndSection}: dec(i)
  357. result = if i >= 0: em.tokens[i].lastChar in SymChars+{'_'} else: false
  358. proc emitComment(em: var Emitter; tok: Token; dontIndent: bool) =
  359. var col = em.col
  360. let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB)
  361. em.lineSpan = countNewlines(lit)
  362. if em.lineSpan > 0: calcCol(em, lit)
  363. if em.lineSpan == 0:
  364. if not endsInNewline(em):
  365. wrTab em
  366. wr em, lit, ltComment
  367. else:
  368. if not endsInWhite(em):
  369. wrTab em
  370. inc col
  371. emitMultilineComment(em, lit, col, dontIndent)
  372. proc emitTok*(em: var Emitter; L: Lexer; tok: Token) =
  373. template wasExportMarker(em): bool =
  374. em.kinds.len > 0 and em.kinds[^1] == ltExportMarker
  375. if tok.tokType == tkComment and tok.literal.startsWith("#!nimpretty"):
  376. case tok.literal
  377. of "#!nimpretty off":
  378. inc em.keepIndents
  379. wrNewline em
  380. em.lastLineNumber = tok.line + 1
  381. of "#!nimpretty on":
  382. dec em.keepIndents
  383. em.lastLineNumber = tok.line
  384. wrNewline em
  385. wr em, tok.literal, ltComment
  386. em.col = 0
  387. em.lineSpan = 0
  388. return
  389. var preventComment = false
  390. if tok.tokType == tkComment and tok.line == em.lastLineNumber:
  391. # we have an inline comment so handle it before the indentation token:
  392. emitComment(em, tok, dontIndent = (em.inSection == 0))
  393. preventComment = true
  394. em.fixedUntil = em.tokens.high
  395. elif tok.indent >= 0:
  396. var newlineKind = ltCrucialNewline
  397. if em.keepIndents > 0:
  398. em.indentLevel = tok.indent
  399. elif (em.lastTok in (splitters + oprSet) and
  400. tok.tokType notin (closedPars - {tkBracketDotRi})):
  401. if tok.tokType in openPars and tok.indent > em.indentStack[^1]:
  402. while em.indentStack[^1] < tok.indent:
  403. em.indentStack.add(em.indentStack[^1] + em.indWidth)
  404. while em.indentStack[^1] > tok.indent:
  405. discard em.indentStack.pop()
  406. # aka: we are in an expression context:
  407. let alignment = max(tok.indent - em.indentStack[^1], 0)
  408. em.indentLevel = alignment + em.indentStack.high * em.indWidth
  409. newlineKind = ltSplittingNewline
  410. else:
  411. if tok.indent > em.indentStack[^1]:
  412. em.indentStack.add tok.indent
  413. else:
  414. # dedent?
  415. while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent:
  416. discard em.indentStack.pop()
  417. em.indentLevel = em.indentStack.high * em.indWidth
  418. #[ we only correct the indentation if it is not in an expression context,
  419. so that code like
  420. const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe,
  421. tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
  422. tkCurlyLe}
  423. is not touched.
  424. ]#
  425. # remove trailing whitespace:
  426. removeSpaces em
  427. wrNewline em, newlineKind
  428. for i in 2..tok.line - em.lastLineNumber: wrNewline(em)
  429. wrSpaces em, em.indentLevel
  430. em.fixedUntil = em.tokens.high
  431. var lastTokWasTerse = false
  432. case tok.tokType
  433. of tokKeywordLow..tokKeywordHigh:
  434. if endsInAlpha(em):
  435. wrSpace em
  436. elif not em.inquote and not endsInWhite(em) and
  437. em.lastTok notin (openPars+{tkOpr, tkDotDot}) and not em.lastTokWasTerse:
  438. #and tok.tokType in oprSet
  439. wrSpace em
  440. if not em.inquote:
  441. wr(em, TokTypeToStr[tok.tokType], ltKeyword)
  442. if tok.tokType in {tkAnd, tkOr, tkIn, tkNotin}:
  443. rememberSplit(splitIn)
  444. wrSpace em
  445. else:
  446. # keywords in backticks are not normalized:
  447. wr(em, tok.ident.s, ltIdent)
  448. of tkColon:
  449. wr(em, TokTypeToStr[tok.tokType], ltOther)
  450. wrSpace em
  451. of tkSemiColon, tkComma:
  452. wr(em, TokTypeToStr[tok.tokType], ltOther)
  453. rememberSplit(splitComma)
  454. wrSpace em
  455. of openPars:
  456. if tok.strongSpaceA > 0 and not em.endsInWhite and
  457. (not em.wasExportMarker or tok.tokType == tkCurlyDotLe):
  458. wrSpace em
  459. wr(em, TokTypeToStr[tok.tokType], ltSomeParLe)
  460. rememberSplit(splitParLe)
  461. of closedPars:
  462. wr(em, TokTypeToStr[tok.tokType], ltSomeParRi)
  463. of tkColonColon:
  464. wr(em, TokTypeToStr[tok.tokType], ltOther)
  465. of tkDot:
  466. lastTokWasTerse = true
  467. wr(em, TokTypeToStr[tok.tokType], ltOther)
  468. of tkEquals:
  469. if not em.inquote and not em.endsInWhite: wrSpace(em)
  470. wr(em, TokTypeToStr[tok.tokType], ltOther)
  471. if not em.inquote: wrSpace(em)
  472. of tkOpr, tkDotDot:
  473. if em.inquote or ((tok.strongSpaceA == 0 and tok.strongSpaceB == 0) and
  474. tok.ident.s notin ["<", ">", "<=", ">=", "==", "!="]):
  475. # bug #9504: remember to not spacify a keyword:
  476. lastTokWasTerse = true
  477. # if not surrounded by whitespace, don't produce any whitespace either:
  478. wr(em, tok.ident.s, ltOpr)
  479. else:
  480. if not em.endsInWhite: wrSpace(em)
  481. wr(em, tok.ident.s, ltOpr)
  482. template isUnary(tok): bool =
  483. tok.strongSpaceB == 0 and tok.strongSpaceA > 0
  484. if not isUnary(tok):
  485. rememberSplit(splitBinary)
  486. wrSpace(em)
  487. of tkAccent:
  488. if not em.inquote and endsInAlpha(em): wrSpace(em)
  489. wr(em, TokTypeToStr[tok.tokType], ltOther)
  490. em.inquote = not em.inquote
  491. of tkComment:
  492. if not preventComment:
  493. emitComment(em, tok, dontIndent = false)
  494. of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit:
  495. let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB)
  496. if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wrSpace(em)
  497. em.lineSpan = countNewlines(lit)
  498. if em.lineSpan > 0: calcCol(em, lit)
  499. wr em, lit, ltLit
  500. of tkEof: discard
  501. else:
  502. let lit = if tok.ident != nil: tok.ident.s else: tok.literal
  503. if endsInAlpha(em): wrSpace(em)
  504. wr em, lit, ltIdent
  505. em.lastTok = tok.tokType
  506. em.lastTokWasTerse = lastTokWasTerse
  507. em.lastLineNumber = tok.line + em.lineSpan
  508. em.lineSpan = 0
  509. proc endsWith(em: Emitter; k: varargs[string]): bool =
  510. if em.tokens.len < k.len: return false
  511. for i in 0..high(k):
  512. if em.tokens[em.tokens.len - k.len + i] != k[i]: return false
  513. return true
  514. proc rfind(em: Emitter, t: string): int =
  515. for i in 1..5:
  516. if em.tokens[^i] == t:
  517. return i
  518. proc starWasExportMarker*(em: var Emitter) =
  519. if em.endsWith(" ", "*", " "):
  520. setLen(em.tokens, em.tokens.len-3)
  521. setLen(em.kinds, em.kinds.len-3)
  522. em.tokens.add("*")
  523. em.kinds.add ltExportMarker
  524. dec em.col, 2
  525. proc commaWasSemicolon*(em: var Emitter) =
  526. if em.semicolons == detectSemicolonKind:
  527. em.semicolons = if em.rfind(";") > 0: useSemicolon else: dontTouch
  528. if em.semicolons == useSemicolon:
  529. let commaPos = em.rfind(",")
  530. if commaPos > 0:
  531. em.tokens[^commaPos] = ";"
  532. proc curlyRiWasPragma*(em: var Emitter) =
  533. if em.endsWith("}"):
  534. em.tokens[^1] = ".}"
  535. inc em.col