Skip to content

Commit

Permalink
repl: line editing with history and completion
Browse files Browse the repository at this point in the history
  • Loading branch information
ncw committed Jun 17, 2015
1 parent 5148b8e commit 5775015
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 14 deletions.
43 changes: 43 additions & 0 deletions notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,49 @@ IDEA replace all .(cast) with CheckExactString or whatever python calls it...

Then can use CheckString later...

Instead of using TypeCall etc, just implement all the __methods__ for
Type. Then there is one and only one way of calling the __methods__.

How subclass a builtin type? Methods etc should work fine, but want
CheckString to return the "string" out of the superclass.

Things to do before release
===========================

* Line numbers
* compile single
* interactive interpreter
* Subclass builtins
* pygen

FIXME recursive types for __repr__ in list, dict, tuple
>>> L=[0]
>>> L[0]=L
>>> L
[[...]]

interactive interpreter
calls compile(..."single) until doesn't get "SyntaxError: unexpected EOF while parsing" ?
see pythonrun.c
PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)
...
mod = PyParser_ASTFromFileObject(fp, filename, enc,
Py_single_input, ps1, ps2,
flags, &errcode, arena);
Py_XDECREF(v);
Py_XDECREF(w);
Py_XDECREF(oenc);
if (mod == NULL) {
PyArena_Free(arena);
if (errcode == E_EOF) {
PyErr_Clear();
return E_EOF;
}
PyErr_Print();
return -1;
}


Limitations & Missing parts
===========================
* string keys only in dictionaries
Expand Down
143 changes: 129 additions & 14 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,151 @@
package repl

import (
"bufio"
"fmt"
"io"
"log"
"os"
"os/user"
"path/filepath"
"sort"
"strings"

"github.com/ncw/gpython/compile"
"github.com/ncw/gpython/py"
"github.com/ncw/gpython/vm"
"github.com/peterh/liner"
)

const HistoryFileName = ".gpyhistory"

// homeDirectory finds the home directory or returns ""
func homeDirectory() string {
usr, err := user.Current()
if err == nil {
return usr.HomeDir
}
// Fall back to reading $HOME - work around user.Current() not
// working for cross compiled binaries on OSX.
// https://github.com/golang/go/issues/6376
return os.Getenv("HOME")
}

// Holds state for readline services
type readline struct {
*liner.State
historyFile string
module *py.Module
}

// newReadline creates a new instance of readline
func newReadline(module *py.Module) *readline {
rl := &readline{
State: liner.NewLiner(),
module: module,
}
home := homeDirectory()
if home != "" {
rl.historyFile = filepath.Join(home, HistoryFileName)
}
rl.SetTabCompletionStyle(liner.TabPrints)
rl.SetWordCompleter(rl.Completer)
return rl
}

// readHistory reads the history into the term
func (rl *readline) ReadHistory() error {
f, err := os.Open(rl.historyFile)
if err != nil {
return err
}
defer f.Close()
_, err = rl.State.ReadHistory(f)
if err != nil {
return err
}
return nil
}

// writeHistory writes the history from the term
func (rl *readline) WriteHistory() error {
f, err := os.OpenFile(rl.historyFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return err
}
defer f.Close()
_, err = rl.State.WriteHistory(f)
if err != nil {
return err
}
return nil
}

// Close the readline and write history
func (rl *readline) Close() error {
err := rl.State.Close()
if err != nil {
return err
}
if rl.historyFile != "" {
err := rl.WriteHistory()
if err != nil {
return err
}
}
return nil
}

// WordCompleter takes the currently edited line with the cursor
// position and returns the completion candidates for the partial word
// to be completed. If the line is "Hello, wo!!!" and the cursor is
// before the first '!', ("Hello, wo!!!", 9) is passed to the
// completer which may returns ("Hello, ", {"world", "Word"}, "!!!")
// to have "Hello, world!!!".
func (rl *readline) Completer(line string, pos int) (head string, completions []string, tail string) {
head = line[:pos]
tail = line[pos:]
lastSpace := strings.LastIndex(head, " ")
head, partial := line[:lastSpace+1], line[lastSpace+1:]
// log.Printf("head = %q, partial = %q, tail = %q", head, partial, tail)
found := make(map[string]struct{})
match := func(d py.StringDict) {
for k := range d {
if strings.HasPrefix(k, partial) {
if _, ok := found[k]; !ok {
completions = append(completions, k)
found[k] = struct{}{}
}
}
}
}
match(rl.module.Globals)
match(py.Builtins.Globals)
sort.Strings(completions)
return head, completions, tail
}

func Run() {
fmt.Printf("Gpython 3.4.0\n")
bio := bufio.NewReader(os.Stdin)
module := py.NewModule("__main__", "", nil, nil)
rl := newReadline(module)
defer rl.Close()
err := rl.ReadHistory()
if err != nil {
fmt.Printf("Failed to open history: %v\n", err)
}

fmt.Printf("Gpython 3.4.0\n")
prog := "<stdin>"
module.Globals["__file__"] = py.String(prog)
for {
fmt.Printf(">>> ")
line, hasMoreInLine, err := bio.ReadLine()
if err == io.EOF {
break
}
line, err := rl.Prompt(">>> ")
if err != nil {
log.Printf("Error: %v", err)
break
}
if hasMoreInLine {
log.Printf("Line truncated")
if err == io.EOF {
fmt.Printf("\n")
break
}
fmt.Printf("Problem reading line: %v\n", err)
continue
}
rl.AppendHistory(line)
// FIXME need +"\n" because "single" is broken
obj, err := compile.Compile(string(line)+"\n", prog, "single", 0, true)
if err != nil {
Expand Down

0 comments on commit 5775015

Please sign in to comment.