Skip to content

Commit a12f7b7

Browse files
committed
py: implement input
1 parent e20a7a4 commit a12f7b7

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

py/run.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ var (
105105
// Compiles a python buffer into a py.Code object.
106106
// Returns a py.Code object or otherwise an error.
107107
Compile func(src, srcDesc string, mode CompileMode, flags int, dont_inherit bool) (*Code, error)
108+
109+
// InputHook is an optional function that can be set to provide a custom input
110+
// mechanism for the input() builtin. If nil, input() reads from sys.stdin.
111+
// This is used by the REPL to integrate with the liner library.
112+
InputHook func(prompt string) (string, error)
108113
)
109114

110115
// RunFile resolves the given pathname, compiles as needed, executes the code in the given module, and returns the Module to indicate success.

repl/cli/cli.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/user"
1313
"path/filepath"
1414

15+
"github.com/go-python/gpython/py"
1516
"github.com/go-python/gpython/repl"
1617
"github.com/peterh/liner"
1718
)
@@ -124,6 +125,13 @@ func RunREPL(replCtx *repl.REPL) error {
124125
rl := newReadline(replCtx)
125126
replCtx.SetUI(rl)
126127
defer rl.Close()
128+
129+
// Set up InputHook for the input() builtin function
130+
py.InputHook = func(prompt string) (string, error) {
131+
return rl.Prompt(prompt)
132+
}
133+
defer func() { py.InputHook = nil }()
134+
127135
err := rl.ReadHistory()
128136
if err != nil {
129137
if !os.IsNotExist(err) {

stdlib/builtin/builtin.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
package builtin
77

88
import (
9+
"bufio"
910
"fmt"
11+
"io"
1012
"math/big"
1113
"strconv"
1214
"unicode/utf8"
@@ -44,7 +46,7 @@ func init() {
4446
// py.MustNewMethod("hash", builtin_hash, 0, hash_doc),
4547
py.MustNewMethod("hex", builtin_hex, 0, hex_doc),
4648
// py.MustNewMethod("id", builtin_id, 0, id_doc),
47-
// py.MustNewMethod("input", builtin_input, 0, input_doc),
49+
py.MustNewMethod("input", builtin_input, 0, input_doc),
4850
py.MustNewMethod("isinstance", builtin_isinstance, 0, isinstance_doc),
4951
// py.MustNewMethod("issubclass", builtin_issubclass, 0, issubclass_doc),
5052
py.MustNewMethod("iter", builtin_iter, 0, iter_doc),
@@ -1181,6 +1183,82 @@ func builtin_chr(self py.Object, args py.Tuple) (py.Object, error) {
11811183
return py.String(buf[:n]), nil
11821184
}
11831185

1186+
const input_doc = `input([prompt]) -> string
1187+
1188+
Read a string from standard input. The trailing newline is stripped.
1189+
The prompt string, if given, is printed to standard output without a
1190+
trailing newline before reading input.
1191+
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
1192+
On *nix systems, GNU readline is used if enabled.`
1193+
1194+
func builtin_input(self py.Object, args py.Tuple) (py.Object, error) {
1195+
var prompt py.Object = py.None
1196+
1197+
err := py.UnpackTuple(args, nil, "input", 0, 1, &prompt)
1198+
if err != nil {
1199+
return nil, err
1200+
}
1201+
1202+
// Use InputHook if available (e.g., in REPL mode)
1203+
if py.InputHook != nil {
1204+
promptStr := ""
1205+
if prompt != py.None {
1206+
promptStr = string(prompt.(py.String))
1207+
}
1208+
line, err := py.InputHook(promptStr)
1209+
if err != nil {
1210+
if err == io.EOF {
1211+
return nil, py.ExceptionNewf(py.EOFError, "EOF when reading a line")
1212+
}
1213+
return nil, err
1214+
}
1215+
return py.String(line), nil
1216+
}
1217+
1218+
sysModule, err := self.(*py.Module).Context.GetModule("sys")
1219+
if err != nil {
1220+
return nil, err
1221+
}
1222+
1223+
stdin := sysModule.Globals["stdin"]
1224+
stdout := sysModule.Globals["stdout"]
1225+
1226+
if prompt != py.None {
1227+
write, err := py.GetAttrString(stdout, "write")
1228+
if err != nil {
1229+
return nil, err
1230+
}
1231+
_, err = py.Call(write, py.Tuple{prompt}, nil)
1232+
if err != nil {
1233+
return nil, err
1234+
}
1235+
1236+
flush, err := py.GetAttrString(stdout, "flush")
1237+
if err == nil {
1238+
py.Call(flush, nil, nil)
1239+
}
1240+
}
1241+
1242+
file := stdin.(*py.File)
1243+
reader := bufio.NewReader(file.File)
1244+
line, err := reader.ReadString('\n')
1245+
if err != nil {
1246+
if err.Error() == "EOF" {
1247+
return nil, py.ExceptionNewf(py.EOFError, "EOF when reading a line")
1248+
}
1249+
return nil, err
1250+
}
1251+
1252+
if len(line) > 0 && line[len(line)-1] == '\n' {
1253+
line = line[:len(line)-1]
1254+
if len(line) > 0 && line[len(line)-1] == '\r' {
1255+
line = line[:len(line)-1]
1256+
}
1257+
}
1258+
1259+
return py.String(line), nil
1260+
}
1261+
11841262
const locals_doc = `locals() -> dictionary
11851263
11861264
Update and return a dictionary containing the current scope's local variables.`

0 commit comments

Comments
 (0)