package main import ( "errors" "flag" "fmt" "os" "path/filepath" "strings" "github.com/peterh/liner" ) type libraryFiles []string func (l *libraryFiles) String() string { return strings.Join(*l, ", ") } func (l *libraryFiles) Set(value string) error { *l = append(*l, strings.TrimSpace(value)) return nil } func historyFn() string { dataHome := os.Getenv("XDG_DATA_HOME") home := os.Getenv("HOME") if dataHome == "" { dataHome = filepath.Join(home, ".local/share/") } prosperDir := filepath.Join(dataHome, "prosper") os.MkdirAll(prosperDir, 0755) return filepath.Join(prosperDir, ".history") } func completer(d Dictionary) liner.Completer { return func(line string) []string { candidates := []string{} words := strings.Split(line, " ") for w := range d { if strings.HasPrefix(w, words[len(words)-1]) { // must reconstruct the _full_ line to return for the completer pref := strings.Join(words[:len(words)-1], " ") if len(pref) != 0 { candidates = append(candidates, pref+" "+w) } else { candidates = append(candidates, w) } } } return candidates } } func main() { // flag setup var libs libraryFiles flag.Var(&libs, "l", "propser library file (may be repeated)") debug := flag.Bool("debug", false, "output debugging information") flag.Parse() // create main context stack := Stack{values: []int{}} rstack := Stack{values: []int{}} ifstack := Stack{values: []int{}} dict := Dictionary{} c := Context{ Dictionary: dict, Stack: &stack, RStack: &rstack, IfStack: &ifstack, Flags: Flags{ "Immediate": true, }, Words: []string{}, Memory: Memory{ intern: map[int]int{}, nextFree: 1, }, StringBuffer: "", Output: os.Stdout, } defineBuiltIns(&c) // load libraries for l := range libs { w, _ := c.Dictionary.GetWord("LOAD") c.StringBuffer = libs[l] err := w.Impl("") if err != nil { fmt.Fprintf(c.Output, "error in library %s: %v\n", libs[l], err) } else { fmt.Fprintf(c.Output, "loaded %s\n", libs[l]) } } // set up liner line := liner.NewLiner() line.SetCtrlCAborts(true) line.SetCompleter(completer(dict)) historyFile := historyFn() if f, err := os.Open(historyFile); err == nil { _, err := line.ReadHistory(f) if err != nil && *debug { fmt.Printf("error reading line history: %v", err) } f.Close() } defer func() { if f, err := os.Create(historyFile); err != nil { if *debug { fmt.Printf("Error writing history file: %v", err) } } else { line.WriteHistory(f) f.Close() } line.Close() }() // Welcome banner fmt.Print("prosper\n") // read loop for { p := "> " if !c.Flags["Immediate"] { p = " " } // read line l, err := line.Prompt(p) if err == liner.ErrPromptAborted { fmt.Println("ctrl-C caught (try BYE)") continue } else if err != nil { fmt.Println(err.Error()) os.Exit(1) } // parse line l = strings.TrimSpace(l) line.AppendHistory(l) err = c.Eval(l + " ") // append a space to make sure we always close out our parse loop if errors.Is(err, ErrExit) { fmt.Printf("bye\n") break } else if err != nil { fmt.Printf("error in evaluation: %v\n", err) } else if c.Flags["Immediate"] { fmt.Print("ok\n") } } } func defineBuiltIns(c *Context) { b := &Builtins{} // word definitions c.Dictionary.AddWord(":", Word{Name: ":", Impl: b.Colon(c)}) c.Dictionary.AddWord(";", Word{Name: ";", Impl: b.Semicolon(c), Immediate: true}) // comments and strings c.Dictionary.AddWord("(", Word{Name: "(", Impl: b.OpenComment(c), Immediate: true}) c.Dictionary.AddWord(")", Word{Name: ")", Impl: b.CloseComment(c), Immediate: true}) c.Dictionary.AddWord(`\`, Word{Name: `\`, Impl: b.EOLComment(c), Immediate: true}) c.Dictionary.AddWord(`."`, Word{Name: `."`, Impl: b.OpenQuote(c.Output, c, '"')}) c.Dictionary.AddWord(`"`, Word{Name: `"`, Impl: b.CloseQuote(c)}) c.Dictionary.AddWord(`.(`, Word{Name: `.(`, Impl: b.OpenQuote(c.Output, c, ')'), Immediate: true}) c.Dictionary.AddWord(`S"`, Word{Name: `S"`, Impl: b.StringBuffer(c, '"')}) c.Dictionary.AddWord("LOAD", Word{Name: "LOAD", Impl: b.Load(c)}) // math c.Dictionary.AddWord("+", Word{Name: "+", Impl: b.Add(c.Stack)}) c.Dictionary.AddWord("-", Word{Name: "-", Impl: b.Sub(c.Stack)}) c.Dictionary.AddWord("*", Word{Name: "*", Impl: b.Mul(c.Stack)}) c.Dictionary.AddWord("/", Word{Name: "/", Impl: b.Div(c.Stack)}) c.Dictionary.AddWord("MOD", Word{Name: "MOD", Impl: b.Mod(c.Stack)}) c.Dictionary.AddWord("/MOD", Word{Name: "/MOD", Source: []string{"2DUP", "MOD", "ROT", "ROT", "/"}}) c.Dictionary.AddWord("1+", Word{Name: "1+", Source: []string{"1", "+"}}) c.Dictionary.AddWord("1-", Word{Name: "1-", Source: []string{"1", "-"}}) c.Dictionary.AddWord("ABS", Word{Name: "ABS", Source: []string{"DUP", "0<", "IF", "NEGATE", "THEN"}}) c.Dictionary.AddWord("NEGATE", Word{Name: "NEGATE", Source: []string{"-1", "*"}}) c.Dictionary.AddWord("MAX", Word{Name: "MAX", Source: []string{"2DUP", "<", "IF", "SWAP", "THEN", "DROP"}}) c.Dictionary.AddWord("MIN", Word{Name: "MIN", Source: []string{"2DUP", ">", "IF", "SWAP", "THEN", "DROP"}}) // output c.Dictionary.AddWord(".", Word{Name: ".", Impl: b.Print(c.Output, c.Stack)}) c.Dictionary.AddWord("EMIT", Word{Name: "EMIT", Impl: b.Emit(c.Output, c.Stack)}) c.Dictionary.AddWord("CR", Word{Name: "CR", Source: []string{"10", "EMIT"}}) // emit a newline // logic c.Dictionary.AddWord("=", Word{Name: "=", Impl: b.Eq(c.Stack)}) c.Dictionary.AddWord("0=", Word{Name: "0=", Source: []string{"0", "="}}) c.Dictionary.AddWord("<>", Word{Name: "<>", Impl: b.NEq(c.Stack)}) c.Dictionary.AddWord("0<>", Word{Name: "0<>", Source: []string{"0=", "0="}}) c.Dictionary.AddWord(">", Word{Name: ">", Impl: b.Gt(c.Stack)}) c.Dictionary.AddWord("<", Word{Name: "<", Impl: b.Lt(c.Stack)}) c.Dictionary.AddWord(">=", Word{Name: ">=", Impl: b.GtEq(c.Stack)}) c.Dictionary.AddWord("<=", Word{Name: "<=", Impl: b.LtEq(c.Stack)}) c.Dictionary.AddWord("0<", Word{Name: "0<", Source: []string{"0", "<"}}) c.Dictionary.AddWord("0>", Word{Name: "0>", Source: []string{"0", ">"}}) // stack manipulation c.Dictionary.AddWord("DUP", Word{Name: "DUP", Impl: b.Dup(c.Stack)}) c.Dictionary.AddWord("?DUP", Word{Name: "?DUP", Source: []string{"DUP", "0<>", "IF", "DUP", "THEN"}}) c.Dictionary.AddWord("SWAP", Word{Name: "SWAP", Impl: b.Swap(c.Stack)}) c.Dictionary.AddWord("OVER", Word{Name: "OVER", Impl: b.Over(c.Stack)}) c.Dictionary.AddWord("DROP", Word{Name: "DROP", Impl: b.Drop(c.Stack)}) c.Dictionary.AddWord("ROT", Word{Name: "ROT", Impl: b.Rot(c.Stack)}) c.Dictionary.AddWord("PICK", Word{Name: "PICK", Impl: b.Pick(c.Stack)}) c.Dictionary.AddWord("NIP", Word{Name: "NIP", Source: []string{"SWAP", "DROP"}}) c.Dictionary.AddWord("TUCK", Word{Name: "TUCK", Source: []string{"SWAP", "OVER"}}) // paired stack manipulation c.Dictionary.AddWord("2DROP", Word{Name: "2DROP", Source: []string{"DROP", "DROP"}}) c.Dictionary.AddWord("2DUP", Word{Name: "2DUP", Source: []string{"OVER", "OVER"}}) c.Dictionary.AddWord("2OVER", Word{Name: "2OVER", Source: []string{"3", "PICK", "3", "PICK"}}) // memory access with variables and constants c.Dictionary.AddWord("VARIABLE", Word{Name: "VARIABLE", Impl: b.Variable(c)}) c.Dictionary.AddWord("CONSTANT", Word{Name: "CONSTANT", Impl: b.Constant(c)}) c.Dictionary.AddWord("!", Word{Name: "!", Impl: b.Store(c)}) c.Dictionary.AddWord("@", Word{Name: "@", Impl: b.Fetch(c)}) // debugging c.Dictionary.AddWord("WORDS", Word{Name: "WORDS", Impl: b.Words(c.Output, c.Dictionary)}) c.Dictionary.AddWord("SEE", Word{Name: "SEE", Impl: b.See(c.Output, c.RStack, c.Dictionary)}) c.Dictionary.AddWord("FLAGS", Word{Name: "FLAGS", Impl: b.Flags(c.Output, c)}) c.Dictionary.AddWord(".S", Word{Name: ".S", Impl: b.Debug(c.Output, c.Stack)}) c.Dictionary.AddWord(".R", Word{Name: ".R", Impl: b.Debug(c.Output, c.RStack)}) c.Dictionary.AddWord(".I", Word{Name: ".I", Impl: b.Debug(c.Output, c.IfStack)}) c.Dictionary.AddWord("DEPTH", Word{Name: "DEPTH", Impl: b.Depth(c.Stack)}) c.Dictionary.AddWord("R>", Word{Name: "R>", Impl: b.RFrom(c.Stack, c.RStack)}) c.Dictionary.AddWord(">R", Word{Name: ">R", Impl: b.ToR(c.Stack, c.RStack)}) c.Dictionary.AddWord("R@", Word{Name: "R@", Impl: b.RFetch(c.Stack, c.RStack)}) // branching c.Dictionary.AddWord("IF", Word{Name: "IF", Impl: b.If(c.Stack, c.IfStack), BranchCheck: true}) c.Dictionary.AddWord("ELSE", Word{Name: "ELSE", Impl: b.Else(c.IfStack), BranchCheck: true}) c.Dictionary.AddWord("THEN", Word{Name: "THEN", Impl: b.Then(c.IfStack), BranchCheck: true}) c.Dictionary.AddWord("DO", Word{Name: "DO", Impl: b.Do(c.Stack, c.RStack)}) c.Dictionary.AddWord("LOOP", Word{Name: "LOOP", Impl: b.Loop(c.Stack, c.RStack)}) c.Dictionary.AddWord("I", Word{Name: "I", Impl: b.I(c.Stack, c.RStack)}) // exit c.Dictionary.AddWord("BYE", Word{Name: "BYE", Impl: b.Quit()}) }