292 lines
6.9 KiB
Go
292 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"syscall"
|
|
|
|
ln "github.com/peterh/liner"
|
|
)
|
|
|
|
const globalLibPath = "/usr/local/lib/slope/modules/"
|
|
|
|
var openFiles []*IOHandle
|
|
var replCounter int = 0
|
|
var line *ln.State
|
|
var VersionHash string
|
|
var initialTerm ln.ModeApplier = nil
|
|
var linerTerm ln.ModeApplier = nil
|
|
var panicOnException = true // default to `(exception-mode-panic)`
|
|
var ModBaseDir string = ""
|
|
var PreloadDir string = ""
|
|
|
|
// Used in the repl so that input can continue,
|
|
// but potentially useful in other situations
|
|
func RecoverError() {
|
|
if r := recover(); r != nil {
|
|
if line == nil {
|
|
fmt.Fprintf(os.Stderr, "\033[1mpanic:\033[0m %s\n", r)
|
|
SafeExit(1)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\033[31mOops! %s\033[0m\n", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Init() {
|
|
openFiles = make([]*IOHandle, 0, 5)
|
|
addGUIToLib() // Will combine guiLib and stdLib for use in creation of global env
|
|
globalenv = env{stdLibrary, nil}
|
|
ModBaseDir = ExpandedAbsFilepath(getModBaseDir())
|
|
PreloadDir = ExpandedAbsFilepath(getPreloadDir())
|
|
createDataDirs(ModBaseDir)
|
|
createDataDirs(globalLibPath)
|
|
createDataDirs(PreloadDir)
|
|
c := make(chan os.Signal)
|
|
signal.Notify(c, syscall.SIGINT)
|
|
go handleSignals(c)
|
|
}
|
|
|
|
func outputResult(txt string) {
|
|
defer RecoverError()
|
|
parsed := Parse(txt)
|
|
for i := range parsed.([]expression) {
|
|
replCounter++
|
|
evaluated := eval(parsed.([]expression)[i], &globalenv)
|
|
if e, ok := evaluated.(exception); panicOnException && ok {
|
|
panic(string(e))
|
|
}
|
|
fmt.Printf("#%d=> %s\n", replCounter, String(evaluated, true))
|
|
}
|
|
}
|
|
|
|
func Repl() {
|
|
initialTerm, _ = ln.TerminalMode()
|
|
line = ln.NewLiner()
|
|
line.SetCtrlCAborts(true)
|
|
linerTerm, _ = ln.TerminalMode()
|
|
defer line.Close()
|
|
|
|
histFile := ExpandedAbsFilepath(filepath.Join(getModBaseDir(), "..", historyFilename))
|
|
if f, e := os.Open(histFile); e == nil {
|
|
line.ReadHistory(f)
|
|
f.Close()
|
|
}
|
|
|
|
// Set up completion
|
|
line.SetTabCompletionStyle(ln.TabCircular)
|
|
line.SetCompleter(func(l string) (c []string) {
|
|
if len(l) == 0 {
|
|
return
|
|
}
|
|
lastIndex := strings.LastIndexAny(l, "( \n")
|
|
c = append(c, completeFromMap(usageStrings, l, lastIndex)...)
|
|
for _, v := range moduleUsageStrings {
|
|
c = append(c, completeFromMap(v, l, lastIndex)...)
|
|
}
|
|
sort.Strings(c)
|
|
return
|
|
})
|
|
|
|
var text strings.Builder
|
|
var cont bool
|
|
for {
|
|
globalenv.vars[symbol("slope-interactive?")] = true
|
|
if linerTerm != nil {
|
|
linerTerm.ApplyMode()
|
|
}
|
|
in := prompt(line, cont)
|
|
if initialTerm != nil {
|
|
initialTerm.ApplyMode()
|
|
}
|
|
if len(strings.TrimSpace(in)) == 0 {
|
|
continue
|
|
}
|
|
text.WriteString(in)
|
|
text.WriteRune('\n')
|
|
if !stringParensMatch(text.String()) {
|
|
cont = true
|
|
} else {
|
|
cont = false
|
|
outputResult(text.String())
|
|
text.Reset()
|
|
}
|
|
}
|
|
}
|
|
|
|
func RunCommand(s string) {
|
|
if strings.Count(s, "(") != strings.Count(s, ")") {
|
|
fmt.Fprintf(os.Stderr, "Invalid input string, uneven parens")
|
|
SafeExit(1)
|
|
}
|
|
globalenv.vars[symbol("slope-interactive?")] = false
|
|
p := Parse(s)
|
|
for i := range p.([]expression) {
|
|
replCounter++
|
|
v := eval(p.([]expression)[i], &globalenv)
|
|
if e, ok := v.(exception); panicOnException && ok {
|
|
panic(string(e))
|
|
}
|
|
}
|
|
SafeExit(0)
|
|
}
|
|
|
|
func RunModule(path string, relative bool) (env, error) {
|
|
modDir := ModBaseDir
|
|
modEnv := env{make(map[symbol]expression), &globalenv}
|
|
revertToThisDir, err := os.Getwd()
|
|
if err != nil {
|
|
return modEnv, fmt.Errorf("System error during 'load-mod', could not get current working directory")
|
|
}
|
|
globalenv.vars[symbol("slope-interactive?")] = false
|
|
|
|
var s string
|
|
if !relative {
|
|
// This branch is run from 'load-mod'
|
|
err = os.Chdir(filepath.Join(modDir, path))
|
|
if err != nil {
|
|
err = os.Chdir(filepath.Join(globalLibPath, path))
|
|
if err != nil {
|
|
return modEnv, err
|
|
}
|
|
modDir = globalLibPath
|
|
}
|
|
fp := filepath.Join(modDir, path, "main.slo")
|
|
b, err := ioutil.ReadFile(fp)
|
|
if err != nil {
|
|
_ = os.Chdir(revertToThisDir)
|
|
return modEnv, fmt.Errorf("Module's main.slo file could not be read")
|
|
}
|
|
s = string(b)
|
|
} else {
|
|
// This branch is run from 'load-mod-file'
|
|
p, _ := filepath.Abs(path)
|
|
if !strings.HasPrefix(p, revertToThisDir) {
|
|
return modEnv, fmt.Errorf("An attempt to load a file out of the module directory was made.")
|
|
}
|
|
b, err := ioutil.ReadFile(p)
|
|
if err != nil {
|
|
_ = os.Chdir(revertToThisDir)
|
|
return modEnv, fmt.Errorf("Could not read module sub-file: %q", path)
|
|
}
|
|
s = string(b)
|
|
}
|
|
p := Parse(s)
|
|
for i := range p.([]expression) {
|
|
exp, ok := p.([]expression)[i].([]expression)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
proc, ok := exp[0].(symbol)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch proc {
|
|
case symbol("define"), symbol("load-mod"), symbol("load-mod-file"):
|
|
v := eval(p.([]expression)[i], &modEnv)
|
|
if e, ok := v.(exception); panicOnException && ok {
|
|
_ = os.Chdir(revertToThisDir)
|
|
panic(string(e))
|
|
} else if ok {
|
|
_ = os.Chdir(revertToThisDir)
|
|
return modEnv, fmt.Errorf(string(e))
|
|
}
|
|
}
|
|
}
|
|
_ = os.Chdir(revertToThisDir)
|
|
return modEnv, nil
|
|
}
|
|
|
|
func RunFile(path string, load bool) error {
|
|
replLine := replCounter
|
|
globalenv.vars[symbol("slope-interactive?")] = false
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
if load {
|
|
return err
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Cannot read file %s", path)
|
|
SafeExit(1)
|
|
}
|
|
s := string(b)
|
|
p := Parse(s)
|
|
for i := range p.([]expression) {
|
|
replCounter++
|
|
v := eval(p.([]expression)[i], &globalenv)
|
|
if e, ok := v.(exception); panicOnException && ok {
|
|
panic(string(e))
|
|
}
|
|
}
|
|
if load {
|
|
replCounter = replLine
|
|
return nil
|
|
}
|
|
SafeExit(0)
|
|
return nil // meaningless return since exit will occur
|
|
}
|
|
|
|
func prompt(l *ln.State, cont bool) string {
|
|
p := "> "
|
|
if cont {
|
|
p = "+ "
|
|
}
|
|
val, err := l.Prompt(p)
|
|
if err == ln.ErrPromptAborted {
|
|
SafeExit(1)
|
|
}
|
|
l.AppendHistory(val)
|
|
return val
|
|
}
|
|
|
|
func PrintVersion() {
|
|
logo := "\033[7m┌─┐┬ ┌─┐┌─┐┌─┐\033[0m\n\033[7m└─┐│ │ │├─┘├┤ \033[0m\n\033[7m└─┘┴─┘└─┘┴ └─┘\033[0m"
|
|
if VersionHash != "" {
|
|
fmt.Printf("\033[1m%s\033[0m\nBuild Version Hash/Commit: %s %s\n(c) 2021 sloum, see license with: (license)\n", logo, VersionHash, guiIsOn)
|
|
} else {
|
|
fmt.Printf("\033[1m%s\033[0m\nBuild Version Hash/Commit: Custom Build (not hash based)\n(c) 2021 sloum, see license with: \033[1m(license)\033[0m\n", logo)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
defer RecoverError()
|
|
verFlag := flag.Bool("v", false, "Print version information and exit")
|
|
runCommand := flag.String("run", "", "A quoted string of commands to run and get output from")
|
|
preloadEnv := flag.Bool("L", false, "Preload all files in the slope preload directory")
|
|
|
|
flag.Parse()
|
|
args := flag.Args()
|
|
|
|
if *verFlag {
|
|
PrintVersion()
|
|
os.Exit(0)
|
|
}
|
|
|
|
Init()
|
|
|
|
if *runCommand != "" {
|
|
if *preloadEnv {
|
|
preloadFiles()
|
|
}
|
|
RunCommand(*runCommand)
|
|
} else if len(args) > 0 {
|
|
if *preloadEnv {
|
|
preloadFiles()
|
|
}
|
|
RunFile(args[0], false)
|
|
} else {
|
|
PrintVersion()
|
|
fmt.Printf("Type \033[1m(exit)\033[0m to quit\n\n")
|
|
if *preloadEnv {
|
|
preloadFiles()
|
|
}
|
|
Repl()
|
|
}
|
|
}
|