llstream.nim 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Low-level streams for high performance.
  10. import
  11. pathutils
  12. import std/strutils
  13. when defined(nimPreviewSlimSystem):
  14. import std/syncio
  15. # support `useGnuReadline`, `useLinenoise` for backwards compatibility
  16. const hasRstdin = (defined(nimUseLinenoise) or defined(useLinenoise) or defined(useGnuReadline)) and
  17. not defined(windows)
  18. when hasRstdin: import std/rdstdin
  19. type
  20. TLLRepl* = proc (s: PLLStream, buf: pointer, bufLen: int): int
  21. OnPrompt* = proc() {.closure.}
  22. TLLStreamKind* = enum # enum of different stream implementations
  23. llsNone, # null stream: reading and writing has no effect
  24. llsString, # stream encapsulates a string
  25. llsFile, # stream encapsulates a file
  26. llsStdIn # stream encapsulates stdin
  27. TLLStream* = object of RootObj
  28. kind*: TLLStreamKind # accessible for low-level access (lexbase uses this)
  29. f*: File
  30. s*: string
  31. rd*, wr*: int # for string streams
  32. lineOffset*: int # for fake stdin line numbers
  33. repl*: TLLRepl # gives stdin control to clients
  34. onPrompt*: OnPrompt
  35. PLLStream* = ref TLLStream
  36. proc llStreamOpen*(data: sink string): PLLStream =
  37. PLLStream(kind: llsString, s: data)
  38. proc llStreamOpen*(f: File): PLLStream =
  39. PLLStream(kind: llsFile, f: f)
  40. proc llStreamOpen*(filename: AbsoluteFile, mode: FileMode): PLLStream =
  41. result = PLLStream(kind: llsFile)
  42. if not open(result.f, filename.string, mode): result = nil
  43. proc llStreamOpen*(): PLLStream =
  44. PLLStream(kind: llsNone)
  45. proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int
  46. proc llStreamOpenStdIn*(r: TLLRepl = llReadFromStdin, onPrompt: OnPrompt = nil): PLLStream =
  47. PLLStream(kind: llsStdIn, s: "", lineOffset: -1, repl: r, onPrompt: onPrompt)
  48. proc llStreamClose*(s: PLLStream) =
  49. case s.kind
  50. of llsNone, llsString, llsStdIn:
  51. discard
  52. of llsFile:
  53. close(s.f)
  54. when not declared(readLineFromStdin):
  55. # fallback implementation:
  56. proc readLineFromStdin(prompt: string, line: var string): bool =
  57. stdout.write(prompt)
  58. stdout.flushFile()
  59. result = readLine(stdin, line)
  60. if not result:
  61. stdout.write("\n")
  62. quit(0)
  63. proc endsWith*(x: string, s: set[char]): bool =
  64. var i = x.len-1
  65. while i >= 0 and x[i] == ' ': dec(i)
  66. if i >= 0 and x[i] in s:
  67. result = true
  68. else:
  69. result = false
  70. const
  71. LineContinuationOprs = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^',
  72. '|', '%', '&', '$', '@', '~', ','}
  73. AdditionalLineContinuationOprs = {'#', ':', '='}
  74. LineContinuationTokens = [
  75. "let", "var", "const", "type", # section
  76. "object", "tuple",
  77. # from ./layouter.oprSet
  78. "div", "mod", "shl", "shr", "in", "notin", "is",
  79. "isnot", "not", "of", "as", "from", "..", "and", "or", "xor",
  80. ] # must be all `nimIdentNormalized`-ed
  81. proc eqIdent(a, bNormalized: string): bool =
  82. a.nimIdentNormalize == bNormalized
  83. proc endsWithIdent(s, subs: string): bool =
  84. let le = subs.len
  85. if le > s.len: return false
  86. s[^le .. ^1].eqIdent subs
  87. proc continuesWithIdent(s, subs: string, start: int): bool =
  88. s.substr(start, start+subs.high).eqIdent subs
  89. proc endsWithIdent(s, subs: string, endIdx: var int): bool =
  90. endIdx.dec subs.len
  91. result = s.continuesWithIdent(subs, endIdx+1)
  92. proc containsObjectOf(x: string): bool =
  93. const sep = ' '
  94. var idx = x.rfind(sep)
  95. if idx == -1: return
  96. template eatWord(word) =
  97. while x[idx] == sep: idx.dec
  98. result = x.endsWithIdent(word, idx)
  99. if not result: return
  100. eatWord "of"
  101. eatWord "object"
  102. result = true
  103. proc endsWithLineContinuationToken(x: string): bool =
  104. result = false
  105. for tok in LineContinuationTokens:
  106. if x.endsWithIdent(tok):
  107. return true
  108. result = x.containsObjectOf
  109. proc endsWithOpr*(x: string): bool =
  110. result = x.endsWith(LineContinuationOprs)
  111. proc continueLine(line: string, inTripleString: bool): bool {.inline.} =
  112. result = inTripleString or line.len > 0 and (
  113. line[0] == ' ' or
  114. line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) or
  115. line.endsWithLineContinuationToken()
  116. )
  117. proc countTriples(s: string): int =
  118. result = 0
  119. var i = 0
  120. while i+2 < s.len:
  121. if s[i] == '"' and s[i+1] == '"' and s[i+2] == '"':
  122. inc result
  123. inc i, 2
  124. inc i
  125. proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int =
  126. s.s = ""
  127. s.rd = 0
  128. var line = newStringOfCap(120)
  129. var triples = 0
  130. while true:
  131. if not readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line):
  132. # now readLineFromStdin meets EOF (ctrl-D/Z) or ctrl-C
  133. quit()
  134. s.s.add(line)
  135. s.s.add("\n")
  136. inc triples, countTriples(line)
  137. if not continueLine(line, (triples and 1) == 1): break
  138. inc(s.lineOffset)
  139. result = min(bufLen, s.s.len - s.rd)
  140. if result > 0:
  141. copyMem(buf, addr(s.s[s.rd]), result)
  142. inc(s.rd, result)
  143. proc llStreamRead*(s: PLLStream, buf: pointer, bufLen: int): int =
  144. case s.kind
  145. of llsNone:
  146. result = 0
  147. of llsString:
  148. result = min(bufLen, s.s.len - s.rd)
  149. if result > 0:
  150. copyMem(buf, addr(s.s[0 + s.rd]), result)
  151. inc(s.rd, result)
  152. of llsFile:
  153. result = readBuffer(s.f, buf, bufLen)
  154. of llsStdIn:
  155. if s.onPrompt!=nil: s.onPrompt()
  156. result = s.repl(s, buf, bufLen)
  157. proc llStreamReadLine*(s: PLLStream, line: var string): bool =
  158. setLen(line, 0)
  159. case s.kind
  160. of llsNone:
  161. result = true
  162. of llsString:
  163. while s.rd < s.s.len:
  164. case s.s[s.rd]
  165. of '\r':
  166. inc(s.rd)
  167. if s.s[s.rd] == '\n': inc(s.rd)
  168. break
  169. of '\n':
  170. inc(s.rd)
  171. break
  172. else:
  173. line.add(s.s[s.rd])
  174. inc(s.rd)
  175. result = line.len > 0 or s.rd < s.s.len
  176. of llsFile:
  177. result = readLine(s.f, line)
  178. of llsStdIn:
  179. result = readLine(stdin, line)
  180. proc llStreamWrite*(s: PLLStream, data: string) =
  181. case s.kind
  182. of llsNone, llsStdIn:
  183. discard
  184. of llsString:
  185. s.s.add(data)
  186. inc(s.wr, data.len)
  187. of llsFile:
  188. write(s.f, data)
  189. proc llStreamWriteln*(s: PLLStream, data: string) =
  190. llStreamWrite(s, data)
  191. llStreamWrite(s, "\n")
  192. proc llStreamWrite*(s: PLLStream, data: char) =
  193. var c: char
  194. case s.kind
  195. of llsNone, llsStdIn:
  196. discard
  197. of llsString:
  198. s.s.add(data)
  199. inc(s.wr)
  200. of llsFile:
  201. c = data
  202. discard writeBuffer(s.f, addr(c), sizeof(c))
  203. proc llStreamWrite*(s: PLLStream, buf: pointer, buflen: int) =
  204. case s.kind
  205. of llsNone, llsStdIn:
  206. discard
  207. of llsString:
  208. if buflen > 0:
  209. setLen(s.s, s.s.len + buflen)
  210. copyMem(addr(s.s[0 + s.wr]), buf, buflen)
  211. inc(s.wr, buflen)
  212. of llsFile:
  213. discard writeBuffer(s.f, buf, buflen)
  214. proc llStreamReadAll*(s: PLLStream): string =
  215. const
  216. bufSize = 2048
  217. case s.kind
  218. of llsNone, llsStdIn:
  219. result = ""
  220. of llsString:
  221. if s.rd == 0: result = s.s
  222. else: result = substr(s.s, s.rd)
  223. s.rd = s.s.len
  224. of llsFile:
  225. result = newString(bufSize)
  226. var bytes = readBuffer(s.f, addr(result[0]), bufSize)
  227. var i = bytes
  228. while bytes == bufSize:
  229. setLen(result, i + bufSize)
  230. bytes = readBuffer(s.f, addr(result[i + 0]), bufSize)
  231. inc(i, bytes)
  232. setLen(result, i)