123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- package zygo
- import (
- "bufio"
- "bytes"
- "encoding/gob"
- "fmt"
- "io"
- "os"
- "runtime/pprof"
- "strconv"
- "strings"
- )
- var precounts map[string]int
- var postcounts map[string]int
- func CountPreHook(env *Zlisp, name string, args []Sexp) {
- precounts[name] += 1
- }
- func CountPostHook(env *Zlisp, name string, retval Sexp) {
- postcounts[name] += 1
- }
- func getLine(reader *bufio.Reader) (string, error) {
- line := make([]byte, 0)
- for {
- linepart, hasMore, err := reader.ReadLine()
- if err != nil {
- return "", err
- }
- line = append(line, linepart...)
- if !hasMore {
- break
- }
- }
- return string(line), nil
- }
- // NB at the moment this doesn't track comment and strings state,
- // so it will fail if unbalanced '(' are found in either.
- func isBalanced(str string) bool {
- parens := 0
- squares := 0
- for _, c := range str {
- switch c {
- case '(':
- parens++
- case ')':
- parens--
- case '[':
- squares++
- case ']':
- squares--
- }
- }
- return parens == 0 && squares == 0
- }
- var continuationPrompt = "... "
- func (pr *Prompter) getExpressionOrig(reader *bufio.Reader) (readin string, err error) {
- line, err := getLine(reader)
- if err != nil {
- return "", err
- }
- for !isBalanced(line) {
- fmt.Printf(continuationPrompt)
- nextline, err := getLine(reader)
- if err != nil {
- return "", err
- }
- line += "\n" + nextline
- }
- return line, nil
- }
- // liner reads Stdin only. If noLiner, then we read from reader.
- func (pr *Prompter) getExpressionWithLiner(env *Zlisp, reader *bufio.Reader, noLiner bool) (readin string, xs []Sexp, err error) {
- var line, nextline string
- if noLiner {
- fmt.Printf(pr.prompt)
- line, err = getLine(reader)
- } else {
- line, err = pr.Getline(nil)
- }
- if err != nil {
- return "", nil, err
- }
- err = UnexpectedEnd
- var x []Sexp
- // test parse, but don't load or generate bytecode
- env.parser.ResetAddNewInput(bytes.NewBuffer([]byte(line + "\n")))
- x, err = env.parser.ParseTokens()
- //P("\n after ResetAddNewInput, err = %v. x = '%s'\n", err, SexpArray(x).SexpString())
- if len(x) > 0 {
- xs = append(xs, x...)
- }
- for err == ErrMoreInputNeeded || err == UnexpectedEnd || err == ResetRequested {
- if noLiner {
- fmt.Printf(continuationPrompt)
- nextline, err = getLine(reader)
- } else {
- nextline, err = pr.Getline(&continuationPrompt)
- }
- if err != nil {
- return "", nil, err
- }
- env.parser.NewInput(bytes.NewBuffer([]byte(nextline + "\n")))
- x, err = env.parser.ParseTokens()
- if len(x) > 0 {
- for i := range x {
- if x[i] == SexpEnd {
- P("found an SexpEnd token, omitting it")
- continue
- }
- xs = append(xs, x[i])
- }
- }
- switch err {
- case nil:
- line += "\n" + nextline
- Q("no problem parsing line '%s' into '%s', proceeding...\n", line, (&SexpArray{Val: x, Env: env}).SexpString(nil))
- return line, xs, nil
- case ResetRequested:
- continue
- case ErrMoreInputNeeded:
- continue
- default:
- return "", nil, fmt.Errorf("Error on line %d: %v\n", env.parser.Linenum(), err)
- }
- }
- return line, xs, nil
- }
- func processDumpCommand(env *Zlisp, args []string) {
- if len(args) == 0 {
- env.DumpEnvironment()
- } else {
- err := env.DumpFunctionByName(args[0])
- if err != nil {
- fmt.Println(err)
- }
- }
- }
- func Repl(env *Zlisp, cfg *ZlispConfig) {
- var reader *bufio.Reader
- if cfg.NoLiner {
- // reader is used if one wishes to drop the liner library.
- // Useful for not full terminal env, like under test.
- reader = bufio.NewReader(os.Stdin)
- }
- if cfg.Trace {
- // debug tracing
- env.debugExec = true
- }
- if !cfg.Quiet {
- if cfg.Sandboxed {
- fmt.Printf("zygo [sandbox mode] version %s\n", Version())
- } else {
- fmt.Printf("zygo version %s\n", Version())
- }
- fmt.Printf("press tab (repeatedly) to get completion suggestions. Shift-tab goes back. Ctrl-d to exit.\n")
- }
- var pr *Prompter // can be nil if noLiner
- if !cfg.NoLiner {
- pr = NewPrompter(cfg.Prompt)
- defer pr.Close()
- } else {
- pr = &Prompter{prompt: cfg.Prompt}
- }
- infixSym := env.MakeSymbol("infix")
- for {
- line, exprsInput, err := pr.getExpressionWithLiner(env, reader, cfg.NoLiner)
- //Q("\n exprsInput(len=%d) = '%v'\n line = '%s'\n", len(exprsInput), (&SexpArray{Val: exprsInput}).SexpString(nil), line)
- if err != nil {
- fmt.Println(err)
- if err == io.EOF {
- os.Exit(0)
- }
- env.Clear()
- continue
- }
- parts := strings.Split(strings.Trim(line, " "), " ")
- //parts := strings.Split(line, " ")
- if len(parts) == 0 {
- continue
- }
- first := strings.Trim(parts[0], " ")
- if first == ".quit" {
- break
- }
- if first == ".cd" {
- if len(parts) < 2 {
- fmt.Printf("provide directory path to change to.\n")
- continue
- }
- err := os.Chdir(parts[1])
- if err != nil {
- fmt.Printf("error: %s\n", err)
- continue
- }
- pwd, err := os.Getwd()
- if err == nil {
- fmt.Printf("cur dir: %s\n", pwd)
- } else {
- fmt.Printf("error: %s\n", err)
- }
- continue
- }
- // allow & at the repl to take the address of an expression
- if len(first) > 0 && first[0] == '&' {
- //P("saw & at repl, first='%v', parts='%#v'. exprsInput = '%#v'", first, parts, exprsInput)
- exprsInput = []Sexp{MakeList(exprsInput)}
- }
- // allow * at the repl to dereference a pointer and print
- if len(first) > 0 && first[0] == '*' {
- //P("saw * at repl, first='%v', parts='%#v'. exprsInput = '%#v'", first, parts, exprsInput)
- exprsInput = []Sexp{MakeList(exprsInput)}
- }
- if first == ".dump" {
- processDumpCommand(env, parts[1:])
- continue
- }
- if first == ".gls" {
- fmt.Printf("\nScopes:\n")
- prev := env.showGlobalScope
- env.showGlobalScope = true
- err = env.ShowStackStackAndScopeStack()
- env.showGlobalScope = prev
- if err != nil {
- fmt.Printf("%s\n", err)
- }
- continue
- }
- if first == ".ls" {
- err := env.ShowStackStackAndScopeStack()
- if err != nil {
- fmt.Println(err)
- }
- continue
- }
- if first == ".verb" {
- Verbose = !Verbose
- fmt.Printf("verbose: %v.\n", Verbose)
- continue
- }
- if first == ".debug" {
- env.debugExec = true
- fmt.Printf("instruction debugging on.\n")
- continue
- }
- if first == ".undebug" {
- env.debugExec = false
- fmt.Printf("instruction debugging off.\n")
- continue
- }
- var expr Sexp
- n := len(exprsInput)
- if n > 0 {
- infixWrappedSexp := MakeList([]Sexp{infixSym, &SexpArray{Val: exprsInput, Env: env}})
- expr, err = env.EvalExpressions([]Sexp{infixWrappedSexp})
- } else {
- line = env.ReplLineInfixWrap(line)
- expr, err = env.EvalString(line + " ") // print standalone variables
- }
- switch err {
- case nil:
- case NoExpressionsFound:
- env.Clear()
- continue
- default:
- fmt.Print(env.GetStackTrace(err))
- env.Clear()
- continue
- }
- if expr != SexpNull {
- // try to print strings more elegantly!
- switch e := expr.(type) {
- case *SexpStr:
- if e.backtick {
- fmt.Printf("`%s`\n", e.S)
- } else {
- fmt.Printf("%s\n", strconv.Quote(e.S))
- }
- default:
- switch sym := expr.(type) {
- case Selector:
- Q("repl calling RHS() on Selector")
- rhs, err := sym.RHS(env)
- if err != nil {
- Q("repl problem in call to RHS() on SexpSelector: '%v'", err)
- fmt.Print(env.GetStackTrace(err))
- env.Clear()
- continue
- } else {
- Q("got back rhs of type %T", rhs)
- fmt.Println(rhs.SexpString(nil))
- continue
- }
- case *SexpSymbol:
- if sym.isDot {
- resolved, err := dotGetSetHelper(env, sym.name, nil)
- if err != nil {
- fmt.Print(env.GetStackTrace(err))
- env.Clear()
- continue
- }
- fmt.Println(resolved.SexpString(nil))
- continue
- }
- }
- fmt.Println(expr.SexpString(nil))
- }
- }
- }
- }
- func runScript(env *Zlisp, fname string, cfg *ZlispConfig) {
- file, err := os.Open(fname)
- if err != nil {
- fmt.Println(err)
- return
- }
- defer file.Close()
- err = env.LoadFile(file)
- if err != nil {
- fmt.Println(err)
- if cfg.ExitOnFailure {
- os.Exit(-1)
- }
- return
- }
- _, err = env.Run()
- if cfg.CountFuncCalls {
- fmt.Println("Pre:")
- for name, count := range precounts {
- fmt.Printf("\t%s: %d\n", name, count)
- }
- fmt.Println("Post:")
- for name, count := range postcounts {
- fmt.Printf("\t%s: %d\n", name, count)
- }
- }
- if err != nil {
- fmt.Print(env.GetStackTrace(err))
- if cfg.ExitOnFailure {
- os.Exit(-1)
- }
- Repl(env, cfg)
- }
- }
- func (env *Zlisp) StandardSetup() {
- env.ImportBaseTypes()
- env.ImportEval()
- env.ImportTime()
- env.ImportPackageBuilder()
- env.ImportMsgpackMap()
- defmap := `(defmac defmap [name] ^(defn ~name [& rest] (msgmap (quote ~name) rest)))`
- _, err := env.EvalString(defmap)
- panicOn(err)
- // colonOp := `(defmac : [key hmap & def] ^(hget ~hmap (quote ~key) ~@def))`
- // _, err = env.EvalString(colonOp)
- // panicOn(err)
- rangeMacro := `(defmac range [key value myhash & body]
- ^(let [n (len ~myhash)]
- (for [(def i 0) (< i n) (def i (+ i 1))]
- (begin
- (mdef (quote ~key) (quote ~value) (hpair ~myhash i))
- ~@body))))`
- _, err = env.EvalString(rangeMacro)
- panicOn(err)
- reqMacro := `(defmac req [a] ^(source (sym2str (quote ~a))))`
- _, err = env.EvalString(reqMacro)
- panicOn(err)
- incrMacro := `(defmac ++ [a] ^(set ~a (+ ~a 1)))`
- _, err = env.EvalString(incrMacro)
- panicOn(err)
- incrEqMacro := `(defmac += [a b] ^(set ~a (+ ~a ~b)))`
- _, err = env.EvalString(incrEqMacro)
- panicOn(err)
- decrMacro := `(defmac -- [a] ^(set ~a (- ~a 1)))`
- _, err = env.EvalString(decrMacro)
- panicOn(err)
- decrEqMacro := `(defmac -= [a b] ^(set ~a (- ~a ~b)))`
- _, err = env.EvalString(decrEqMacro)
- panicOn(err)
- env.ImportChannels()
- env.ImportGoroutines()
- env.ImportRegex()
- env.ImportRandom()
- gob.Register(SexpHash{})
- gob.Register(SexpArray{})
- }
- // like main() for a standalone repl, now in library
- func ReplMain(cfg *ZlispConfig) {
- var env *Zlisp
- if cfg.LoadDemoStructs {
- RegisterDemoStructs()
- }
- if cfg.Sandboxed {
- env = NewZlispSandbox()
- } else {
- env = NewZlisp()
- }
- env.StandardSetup()
- if cfg.LoadDemoStructs {
- // avoid data conflicts by only loading these in demo mode.
- env.ImportDemoData()
- }
- if cfg.CpuProfile != "" {
- f, err := os.Create(cfg.CpuProfile)
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
- err = pprof.StartCPUProfile(f)
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
- defer pprof.StopCPUProfile()
- }
- precounts = make(map[string]int)
- postcounts = make(map[string]int)
- if cfg.CountFuncCalls {
- env.AddPreHook(CountPreHook)
- env.AddPostHook(CountPostHook)
- }
- if cfg.Command != "" {
- _, err := env.EvalString(cfg.Command)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%v\n", err)
- os.Exit(1)
- }
- os.Exit(0)
- }
- runRepl := true
- args := cfg.Flags.Args()
- if len(args) > 0 {
- runRepl = false
- runScript(env, args[0], cfg)
- if cfg.AfterScriptDontExit {
- runRepl = true
- }
- }
- if runRepl {
- Repl(env, cfg)
- }
- if cfg.MemProfile != "" {
- f, err := os.Create(cfg.MemProfile)
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
- defer f.Close()
- err = pprof.Lookup("heap").WriteTo(f, 1)
- if err != nil {
- fmt.Println(err)
- os.Exit(-1)
- }
- }
- }
- func (env *Zlisp) ReplLineInfixWrap(line string) string {
- return "{" + line + "}"
- }
|