Files
Nim/compiler/llstream.nim
lit 3a9920d8fd repl: support eof, define object with fields (#24784)
For `nim secret`:

- **fix(repl): eof(ctrl-D/Z) and ctrl-C were ignored**
- **feat(repl): continueLine  figures section, constr, bool ops**

---------

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
(cherry picked from commit d573578b28)
2025-03-25 09:44:55 +01:00

265 lines
7.2 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Low-level streams for high performance.
import
pathutils
import std/strutils
when defined(nimPreviewSlimSystem):
import std/syncio
# support `useGnuReadline`, `useLinenoise` for backwards compatibility
const hasRstdin = (defined(nimUseLinenoise) or defined(useLinenoise) or defined(useGnuReadline)) and
not defined(windows)
when hasRstdin: import std/rdstdin
type
TLLRepl* = proc (s: PLLStream, buf: pointer, bufLen: int): int
OnPrompt* = proc() {.closure.}
TLLStreamKind* = enum # enum of different stream implementations
llsNone, # null stream: reading and writing has no effect
llsString, # stream encapsulates a string
llsFile, # stream encapsulates a file
llsStdIn # stream encapsulates stdin
TLLStream* = object of RootObj
kind*: TLLStreamKind # accessible for low-level access (lexbase uses this)
f*: File
s*: string
rd*, wr*: int # for string streams
lineOffset*: int # for fake stdin line numbers
repl*: TLLRepl # gives stdin control to clients
onPrompt*: OnPrompt
PLLStream* = ref TLLStream
proc llStreamOpen*(data: sink string): PLLStream =
PLLStream(kind: llsString, s: data)
proc llStreamOpen*(f: File): PLLStream =
PLLStream(kind: llsFile, f: f)
proc llStreamOpen*(filename: AbsoluteFile, mode: FileMode): PLLStream =
result = PLLStream(kind: llsFile)
if not open(result.f, filename.string, mode): result = nil
proc llStreamOpen*(): PLLStream =
PLLStream(kind: llsNone)
proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int
proc llStreamOpenStdIn*(r: TLLRepl = llReadFromStdin, onPrompt: OnPrompt = nil): PLLStream =
PLLStream(kind: llsStdIn, s: "", lineOffset: -1, repl: r, onPrompt: onPrompt)
proc llStreamClose*(s: PLLStream) =
case s.kind
of llsNone, llsString, llsStdIn:
discard
of llsFile:
close(s.f)
when not declared(readLineFromStdin):
# fallback implementation:
proc readLineFromStdin(prompt: string, line: var string): bool =
stdout.write(prompt)
stdout.flushFile()
result = readLine(stdin, line)
if not result:
stdout.write("\n")
quit(0)
proc endsWith*(x: string, s: set[char]): bool =
var i = x.len-1
while i >= 0 and x[i] == ' ': dec(i)
if i >= 0 and x[i] in s:
result = true
else:
result = false
const
LineContinuationOprs = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^',
'|', '%', '&', '$', '@', '~', ','}
AdditionalLineContinuationOprs = {'#', ':', '='}
LineContinuationTokens = [
"let", "var", "const", "type", # section
"object", "tuple",
# from ./layouter.oprSet
"div", "mod", "shl", "shr", "in", "notin", "is",
"isnot", "not", "of", "as", "from", "..", "and", "or", "xor",
] # must be all `nimIdentNormalized`-ed
proc eqIdent(a, bNormalized: string): bool =
a.nimIdentNormalize == bNormalized
proc endsWithIdent(s, subs: string): bool =
let le = subs.len
if le > s.len: return false
s[^le .. ^1].eqIdent subs
proc continuesWithIdent(s, subs: string, start: int): bool =
s.substr(start, start+subs.high).eqIdent subs
proc endsWithIdent(s, subs: string, endIdx: var int): bool =
endIdx.dec subs.len
result = s.continuesWithIdent(subs, endIdx+1)
proc containsObjectOf(x: string): bool =
const sep = ' '
var idx = x.rfind(sep)
if idx == -1: return
template eatWord(word) =
while x[idx] == sep: idx.dec
result = x.endsWithIdent(word, idx)
if not result: return
eatWord "of"
eatWord "object"
result = true
proc endsWithLineContinuationToken(x: string): bool =
result = false
for tok in LineContinuationTokens:
if x.endsWithIdent(tok):
return true
result = x.containsObjectOf
proc endsWithOpr*(x: string): bool =
result = x.endsWith(LineContinuationOprs)
proc continueLine(line: string, inTripleString: bool): bool {.inline.} =
result = inTripleString or line.len > 0 and (
line[0] == ' ' or
line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) or
line.endsWithLineContinuationToken()
)
proc countTriples(s: string): int =
result = 0
var i = 0
while i+2 < s.len:
if s[i] == '"' and s[i+1] == '"' and s[i+2] == '"':
inc result
inc i, 2
inc i
proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int =
s.s = ""
s.rd = 0
var line = newStringOfCap(120)
var triples = 0
while true:
if not readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line):
# now readLineFromStdin meets EOF (ctrl-D/Z) or ctrl-C
quit()
s.s.add(line)
s.s.add("\n")
inc triples, countTriples(line)
if not continueLine(line, (triples and 1) == 1): break
inc(s.lineOffset)
result = min(bufLen, s.s.len - s.rd)
if result > 0:
copyMem(buf, addr(s.s[s.rd]), result)
inc(s.rd, result)
proc llStreamRead*(s: PLLStream, buf: pointer, bufLen: int): int =
case s.kind
of llsNone:
result = 0
of llsString:
result = min(bufLen, s.s.len - s.rd)
if result > 0:
copyMem(buf, addr(s.s[0 + s.rd]), result)
inc(s.rd, result)
of llsFile:
result = readBuffer(s.f, buf, bufLen)
of llsStdIn:
if s.onPrompt!=nil: s.onPrompt()
result = s.repl(s, buf, bufLen)
proc llStreamReadLine*(s: PLLStream, line: var string): bool =
setLen(line, 0)
case s.kind
of llsNone:
result = true
of llsString:
while s.rd < s.s.len:
case s.s[s.rd]
of '\r':
inc(s.rd)
if s.s[s.rd] == '\n': inc(s.rd)
break
of '\n':
inc(s.rd)
break
else:
line.add(s.s[s.rd])
inc(s.rd)
result = line.len > 0 or s.rd < s.s.len
of llsFile:
result = readLine(s.f, line)
of llsStdIn:
result = readLine(stdin, line)
proc llStreamWrite*(s: PLLStream, data: string) =
case s.kind
of llsNone, llsStdIn:
discard
of llsString:
s.s.add(data)
inc(s.wr, data.len)
of llsFile:
write(s.f, data)
proc llStreamWriteln*(s: PLLStream, data: string) =
llStreamWrite(s, data)
llStreamWrite(s, "\n")
proc llStreamWrite*(s: PLLStream, data: char) =
var c: char
case s.kind
of llsNone, llsStdIn:
discard
of llsString:
s.s.add(data)
inc(s.wr)
of llsFile:
c = data
discard writeBuffer(s.f, addr(c), sizeof(c))
proc llStreamWrite*(s: PLLStream, buf: pointer, buflen: int) =
case s.kind
of llsNone, llsStdIn:
discard
of llsString:
if buflen > 0:
setLen(s.s, s.s.len + buflen)
copyMem(addr(s.s[0 + s.wr]), buf, buflen)
inc(s.wr, buflen)
of llsFile:
discard writeBuffer(s.f, buf, buflen)
proc llStreamReadAll*(s: PLLStream): string =
const
bufSize = 2048
case s.kind
of llsNone, llsStdIn:
result = ""
of llsString:
if s.rd == 0: result = s.s
else: result = substr(s.s, s.rd)
s.rd = s.s.len
of llsFile:
result = newString(bufSize)
var bytes = readBuffer(s.f, addr(result[0]), bufSize)
var i = bytes
while bytes == bufSize:
setLen(result, i + bufSize)
bytes = readBuffer(s.f, addr(result[i + 0]), bufSize)
inc(i, bytes)
setLen(result, i)