layouter.nim 19 KB

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