169 lines
4.5 KiB
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
|