246 lines
6.2 KiB
Go
246 lines
6.2 KiB
Go
/*
|
|
_
|
|
|, _ |. __ _
|
|
| (/,||_) (/,
|
|
|
|
Copyright (C) 2023 Brian Evans (aka sloum). All rights reserved.
|
|
|
|
This source code is available under the terms of the ffsl, or,
|
|
Floodgap Free Software License. A copy of the license has been
|
|
provided as the file 'LICENSE' in the same folder as this source
|
|
code file. If for some reason it is not present, you can find the
|
|
terms of version 1 of the FFSL at the following URL:
|
|
|
|
https://www.floodgap.com/software/ffsl/license.html
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
_ "embed"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
ln "github.com/peterh/liner"
|
|
)
|
|
|
|
const (
|
|
version float64 = 0.46
|
|
logo string = ` _
|
|
|, _ |. __ _
|
|
| (/,||_) (/,`
|
|
)
|
|
|
|
var rawInput bool = false
|
|
var line *ln.State
|
|
var initialTerm ln.ModeApplier = nil
|
|
var linerTerm ln.ModeApplier = nil
|
|
var flagArgs []string
|
|
var completions = []string{
|
|
"var!", "set!", "scoped-var!", "scoped-set!", "proc", "proc!", "length",
|
|
"dup", "drop", "over", "swap", "cast", "type", "while", "return",
|
|
"dowhile", "if", "else", "and", "or", "stackdump", "clearstack",
|
|
"->", "<-", "=>", "->!", "=>!", "append", "append!", "list-get",
|
|
"list-set", "list-set!", "split", "join", "file-write", "file-remove",
|
|
"file-exists?", "file-read", "docstring!", "input", "re-match?",
|
|
"re-find", "re-find-all", "re-replace", "slice", "stackdepth", "net-get", "try", "throw",
|
|
"catch", "import", "rot", "each!", "filter!", "INT", "STRING", "FLOAT",
|
|
"LIST", "BOOL", "TYPE", "DICT", "words", "time", "char-conv", "end", "env-get",
|
|
"env-set", "exec", "<-exec", "<-exec->", "exec->", "cd", "pwd",
|
|
}
|
|
|
|
//go:embed lib/std.fe
|
|
var stdImport string
|
|
|
|
//go:embed lib/math.fe
|
|
var mathImport string
|
|
|
|
//go:embed lib/stack.fe
|
|
var stackImport string
|
|
|
|
//go:embed lib/paths.fe
|
|
var pathsImport string
|
|
|
|
//go:embed lib/strings.fe
|
|
var stringsImport string
|
|
|
|
func prompt(l *ln.State, cont bool) string {
|
|
var val string
|
|
var err error
|
|
if rawInput {
|
|
p := "\033[95m#\033[0m "
|
|
if cont {
|
|
p = "\033[94m+\033[0m "
|
|
}
|
|
fmt.Print(p)
|
|
bytesIn, err := io.ReadAll(os.Stdin)
|
|
if err != nil {
|
|
// TODO handle this better
|
|
panic(err)
|
|
}
|
|
val = string(bytesIn)
|
|
} else {
|
|
p := "> "
|
|
|
|
if cont {
|
|
p = "+ "
|
|
}
|
|
val, err = l.Prompt(p)
|
|
if err == ln.ErrPromptAborted {
|
|
return ""
|
|
}
|
|
l.AppendHistory(val)
|
|
}
|
|
return val
|
|
}
|
|
|
|
func VersionString() string {
|
|
return fmt.Sprintf("%.2f\n", version)
|
|
}
|
|
|
|
func loadStdLib(en *env) error {
|
|
err := lexParseInterpret(stdImport, en, "std")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
en.imports["std"] = true
|
|
return nil
|
|
}
|
|
|
|
func repl() {
|
|
replmode := "\033[33mRunning in standard-repl mode:\n\tTo submit multiple lines of text, end a line that you want\n\tto continue with a \033[1m\\\033[0m\n\n"
|
|
if rawInput {
|
|
replmode = "\033[33mRunning in raw-repl mode:\n\tTo submit text for execution use \033[1m^D\033[0m\n\n"
|
|
}
|
|
fmt.Printf("%s\nfelise version %s(c) 2024 sloum, all rights reserved\nLicensed under the terms of the Floodgap Free Software License\n(`bye` or `exit` to quit)\n\n%s", logo, VersionString(), replmode)
|
|
|
|
if !rawInput {
|
|
initialTerm, _ = ln.TerminalMode()
|
|
line = ln.NewLiner()
|
|
linerTerm, _ = ln.TerminalMode()
|
|
defer line.Close()
|
|
|
|
line.SetCompleter(func(line string) (c []string) {
|
|
index := strings.LastIndexAny(line, " ")
|
|
for _, n := range completions {
|
|
if index < 0 && strings.HasPrefix(string(n), line) {
|
|
c = append(c, string(n))
|
|
} else if len(line) > index+1 && strings.HasPrefix(string(n), line[index+1:]) {
|
|
start := len(line) - index - 1
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
c = append(c, line+string(n)[start:])
|
|
}
|
|
}
|
|
return
|
|
})
|
|
}
|
|
|
|
var text strings.Builder
|
|
var cont bool
|
|
baseEnv := NewEnv(nil)
|
|
err := loadStdLib(baseEnv)
|
|
if err != nil {
|
|
processInterpretError(err, "std")
|
|
}
|
|
|
|
for {
|
|
if linerTerm != nil {
|
|
linerTerm.ApplyMode()
|
|
}
|
|
in := prompt(line, cont)
|
|
if initialTerm != nil {
|
|
initialTerm.ApplyMode()
|
|
}
|
|
if s := strings.TrimSpace(in); s == "" {
|
|
continue
|
|
} else if s == "bye" || s == "exit" {
|
|
break
|
|
}
|
|
in = strings.TrimSpace(in)
|
|
if strings.HasSuffix(in, "\\") {
|
|
cont = true
|
|
text.WriteString(in[:len(in)-1])
|
|
text.WriteRune('\n')
|
|
} else {
|
|
cont = false
|
|
text.WriteRune(' ')
|
|
text.WriteString(in)
|
|
}
|
|
if !cont {
|
|
processInterpretError(lexParseInterpret(text.String(), baseEnv, ""), "")
|
|
text.Reset()
|
|
}
|
|
}
|
|
}
|
|
|
|
func lexParseInterpret(s string, en *env, file string) error {
|
|
lexedTokens, err := lex(s, file)
|
|
if err == nil {
|
|
r := NewTokenReader(lexedTokens)
|
|
var parsedTokens []token
|
|
parsedTokens, err = parse(r)
|
|
if err == nil {
|
|
err = interpret(parsedTokens, en)
|
|
}
|
|
}
|
|
sort.Strings(completions)
|
|
return err
|
|
}
|
|
|
|
func processInterpretError(err error, file string) {
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "ERROR | %s\n", err.Error())
|
|
if file == "" {
|
|
globalStack.sp = 0
|
|
resetParseCounters()
|
|
} else {
|
|
os.Exit(1)
|
|
}
|
|
} else if file == "" {
|
|
fmt.Println("\n\033[0mOk.")
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
verFlag := flag.Bool("v", false, "Print version information and exit")
|
|
flag.BoolVar(&debug, "debug", false, "Turns on debug mode")
|
|
flag.BoolVar(&rawInput, "r", false, "Use raw stdin input in repl. Will accept newline, ec. and requires ^D to submit input")
|
|
flag.Parse()
|
|
flagArgs = flag.Args()
|
|
|
|
if *verFlag {
|
|
fmt.Print(VersionString())
|
|
} else if len(flagArgs) > 0 {
|
|
p := ExpandedAbsFilepath(flagArgs[0])
|
|
s, err := readFile(p)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "felise could not read the given input file:\n `%s`\n", flagArgs[0])
|
|
os.Exit(1)
|
|
}
|
|
en := NewEnv(nil)
|
|
err = loadStdLib(en)
|
|
if err != nil {
|
|
processInterpretError(err, "std")
|
|
}
|
|
if len(flagArgs) > 1 {
|
|
t := make([]token, len(flagArgs)-1)
|
|
for i, s := range flagArgs[1:] {
|
|
t[i] = token{STRING, s, -1, "built-in"}
|
|
}
|
|
en.Add("sys-args", token{LIST, list{t}, -1, "built-in"})
|
|
} else {
|
|
t := make([]token, 0)
|
|
en.Add("sys-args", token{LIST, list{t}, -1, "built-in"})
|
|
}
|
|
processInterpretError(lexParseInterpret(s, en, flagArgs[0]), flagArgs[0])
|
|
} else {
|
|
repl()
|
|
}
|
|
}
|