fsh/src/fsh.nim

169 lines
4.5 KiB
Nim

# fsh - Forka shell
# 2021 - Seculum Forka
# This program is licensed under the agpl v3.0 only license
# imports
import streams
import osproc
# forward declarations
proc eval(cmd: string): string
# skipSpaces reads the stream until a non-space character is found
proc skipSpaces(strm: Stream): int =
while not strm.atEnd():
if strm.peekChar() == ' ':
discard strm.readChar()
result.inc
else: break
# readLiterals parses the string passed in as a literal, preserving all braces except the first and last ones
proc readLiteral(strm: Stream): string =
var numbraces=1
while not strm.atEnd() and numbraces > 0:
let c = strm.readChar()
case c:
of '{':
numbraces.inc
of '}':
numbraces = numbraces - 1
of '\\':
if strm.peekChar notin {'{', '}'}: result.add(strm.readChar)
result.add(strm.readChar)
else:
result.add(c)
# a forward declaration. Fuck this shit
proc readSubstitution(strm: Stream): string
# readInterpelation reads the interpelation in etween [ and ]
proc readInterpelation(strm: Stream): string =
while not strm.atEnd():
let c = strm.readChar()
case c:
of '{':
result.add('{')
result.add(strm.readLiteral)
result.add('}')
of '[':
result.add('[')
result.add(strm.readInterpelation)
result.add(']')
of '"':
result.add("\"")
result.add(strm.readSubstitution)
result.add("\"")
of '\\':
result.add("\\")
result.add(strm.readChar())
of ']':
break
else: result.add(c)
# readSubstitution reads a substitutionbetween "quotes"
proc readSubstitution(strm: Stream): string =
while not strm.atEnd():
let c = strm.readChar()
case c:
of '{':
result.add('{')
result.add(strm.readLiteral)
result.add('}')
of '[':
result.add('[')
result.add(strm.readInterpelation)
result.add(']')
of '"':
break
of '\\':
result.add("\\")
result.add(strm.readChar())
else: result.add(c)
# readCommand reads a command from stream
proc readCommand(strm: Stream): string =
while not strm.atEnd():
let c = strm.readChar()
case c:
of '{':
result.add('{')
result.add(strm.readLiteral)
result.add('}')
of '[':
result.add('[')
result.add(strm.readInterpelation)
result.add(']')
of '"':
result.add("\"")
result.add(strm.readSubstitution)
result.add("\"")
of '\\':
result.add("\\")
result.add(strm.readChar())
of '\n', '\r', ';': break
else: result.add(c)
# substitute does a substitution on the stream passed in
proc substitute(strm: Stream, delim=";\p"): string =
while not strm.atEnd():
let c = strm.readChar()
if c in delim: break
case c:
of '{':
result.add(strm.readLiteral)
of '}':
stderr.write("Extra closing brace")
of '[':
result.add(strm.readInterpelation.eval)
of ']':
stderr.write("Extra closing bracket")
of '\\':
result.add(strm.readChar())
else:
result.add(c)
# parseCommand parses the string given into a list representing a command
proc parseCommand(cmd: string): seq[string] =
var newitem = ""
let strm = newStringStream(cmd)
while true:
if strm.atEnd():
result.add(newitem)
break
let c = strm.readChar()
case c:
of '{':
newitem.add(strm.readLiteral)
of '[':
newitem.add(strm.readInterpelation.eval)
of '"':
newitem.add(strm.readSubstitution.newStringStream.substitute)
of ' ':
result.add(newitem)
newitem = ""
discard strm.skipSpaces()
else: newitem.add(c)
# executes executes the command. For now it involves catting the command and printing it
proc execute(cmd: string): int =
let parsed = cmd.parseCommand
let progname = parsed[0]
let args = parsed[1..parsed.high]
let ps = startProcess(progname, args=args, options={poUsePath, poParentStreams})
result = ps.waitForExit
ps.close
# eval evaluates the given string
proc eval(cmd: string): string =
let parsed = cmd.parseCommand
let progname=parsed[0]
let args = parsed[1..parsed.high]
let ps = startProcess(progname, args=args, options={poUsePath})
result=ps.outputStream.readAll
ps.close
when isMainModule:
let stdinstrm = stdin.newFileStream
while not stdinstrm.atEnd():
stdinstrm.readCommand.execute.echo