206 lines
4.7 KiB
Go
206 lines
4.7 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"
|
|
"os"
|
|
"strings"
|
|
|
|
ln "github.com/peterh/liner"
|
|
)
|
|
|
|
const (
|
|
version float64 = 0.2
|
|
logo string = ` _
|
|
|, _ |. __ _
|
|
| (/,||_) (/,`
|
|
)
|
|
|
|
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-replace", "slice", "stackdepth", "net-get",
|
|
}
|
|
|
|
//go:embed lib/std.fe
|
|
var stdImport string
|
|
|
|
//go:embed lib/math.fe
|
|
var mathImport string
|
|
|
|
//go:embed lib/stack.fe
|
|
var stackImport string
|
|
|
|
func prompt(l *ln.State, cont bool) string {
|
|
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() {
|
|
fmt.Printf("%s\nfelise version %s(c) 2023 sloum, all rights reserved\nLicensed under the terms of the Floodgap Free Software License\n(`bye` or `exit` to quit)\n\n", logo,VersionString())
|
|
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
|
|
}
|
|
if strings.HasSuffix(in, "\\") {
|
|
cont = true
|
|
text.WriteString(in[:len(in)-1])
|
|
text.WriteRune('\n')
|
|
} else {
|
|
cont = false
|
|
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)
|
|
}
|
|
}
|
|
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
|
|
} 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.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()
|
|
}
|
|
}
|