felise/main.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()
}
}