55 "io"
66 "io/ioutil"
77 "os"
8+ "os/exec"
89)
910
1011const fileHandle = "FILE*"
@@ -13,6 +14,8 @@ const output = "_IO_output"
1314
1415type stream struct {
1516 f * os.File
17+ r io.Reader
18+ w io.Writer
1619 close Function
1720}
1821
@@ -27,15 +30,31 @@ func toFile(l *State) *os.File {
2730 return s .f
2831}
2932
30- func newStream (l * State , f * os.File , close Function ) * stream {
31- s := & stream {f : f , close : close }
33+ func toReader (l * State ) io.Reader {
34+ s := toStream (l )
35+ if s .r != nil {
36+ return s .r
37+ }
38+ return s .f
39+ }
40+
41+ func toWriter (l * State ) io.Writer {
42+ s := toStream (l )
43+ if s .w != nil {
44+ return s .w
45+ }
46+ return s .f
47+ }
48+
49+ func newStream (l * State , f * os.File , r io.Reader , w io.Writer , close Function ) * stream {
50+ s := & stream {f : f , r : r , w : w , close : close }
3251 l .PushUserData (s )
3352 SetMetaTableNamed (l , fileHandle )
3453 return s
3554}
3655
3756func newFile (l * State ) * stream {
38- return newStream (l , nil , func (l * State ) int { return FileResult (l , toStream (l ).f .Close (), "" ) })
57+ return newStream (l , nil , nil , nil , func (l * State ) int { return FileResult (l , toStream (l ).f .Close (), "" ) })
3958}
4059
4160func ioFile (l * State , name string ) * os.File {
@@ -85,17 +104,17 @@ func close(l *State) int {
85104 if l .IsNone (1 ) {
86105 l .Field (RegistryIndex , output )
87106 }
88- toFile (l )
89107 return closeHelper (l )
90108}
91109
92110func write (l * State , f * os.File , argIndex int ) int {
93111 var err error
112+ writer := toWriter (l )
94113 for argCount := l .Top (); argIndex < argCount && err == nil ; argIndex ++ {
95114 if n , ok := l .ToNumber (argIndex ); ok {
96- _ , err = f . WriteString ( numberToString (n ))
115+ _ , err = writer . Write ([] byte ( numberToString (n ) ))
97116 } else {
98- _ , err = f . WriteString ( CheckString (l , argIndex ))
117+ _ , err = writer . Write ([] byte ( CheckString (l , argIndex ) ))
99118 }
100119 }
101120 if err == nil {
@@ -104,6 +123,16 @@ func write(l *State, f *os.File, argIndex int) int {
104123 return FileResult (l , err , "" )
105124}
106125
126+ func read (l * State , f * os.File , argIndex int ) int {
127+ reader := toReader (l )
128+ buf , err := io .ReadAll (reader )
129+ if err != nil && err != io .EOF {
130+ return FileResult (l , err , "" )
131+ }
132+ l .PushString (string (buf ))
133+ return 1
134+ }
135+
107136func readNumber (l * State , f * os.File ) (err error ) {
108137 var n float64
109138 if _ , err = fmt .Fscanf (f , "%f" , & n ); err == nil {
@@ -114,25 +143,6 @@ func readNumber(l *State, f *os.File) (err error) {
114143 return
115144}
116145
117- func read (l * State , f * os.File , argIndex int ) int {
118- resultCount := 0
119- var err error
120- if argCount := l .Top () - 1 ; argCount == 0 {
121- // err = readLineHelper(l, f, true)
122- resultCount = argIndex + 1
123- } else {
124- // TODO
125- }
126- if err != nil {
127- return FileResult (l , err , "" )
128- }
129- if err == io .EOF {
130- l .Pop (1 )
131- l .PushNil ()
132- }
133- return resultCount - argIndex
134- }
135-
136146func readLine (l * State ) int {
137147 s := l .ToUserData (UpValueIndex (1 )).(* stream )
138148 argCount , _ := l .ToInteger (UpValueIndex (2 ))
@@ -227,7 +237,58 @@ var ioLibrary = []RegistryFunction{
227237 return FileResult (l , err , name )
228238 }},
229239 {"output" , ioFileHelper (output , "w" )},
230- {"popen" , func (l * State ) int { Errorf (l , "'popen' not supported" ); panic ("unreachable" ) }},
240+ {"popen" , func (l * State ) int {
241+ cmdStr := CheckString (l , 1 )
242+ mode := OptString (l , 2 , "r" )
243+ var r io.Reader
244+ var w io.Writer
245+ var closer io.Closer
246+ var cmd * exec.Cmd
247+ var err error
248+
249+ switch mode {
250+ case "r" :
251+ cmd = exec .Command ("sh" , "-c" , cmdStr )
252+ stdout , e := cmd .StdoutPipe ()
253+ if e != nil {
254+ l .PushNil ()
255+ l .PushString (e .Error ())
256+ return 2
257+ }
258+ if err = cmd .Start (); err != nil {
259+ l .PushNil ()
260+ l .PushString (err .Error ())
261+ return 2
262+ }
263+ r = stdout
264+ closer = stdout
265+ case "w" :
266+ cmd = exec .Command ("sh" , "-c" , cmdStr )
267+ stdin , e := cmd .StdinPipe ()
268+ if e != nil {
269+ l .PushNil ()
270+ l .PushString (e .Error ())
271+ return 2
272+ }
273+ if err = cmd .Start (); err != nil {
274+ l .PushNil ()
275+ l .PushString (err .Error ())
276+ return 2
277+ }
278+ w = stdin
279+ closer = stdin
280+ default :
281+ Errorf (l , "'popen' only supports 'r' or 'w' mode" )
282+ panic ("unreachable" )
283+ }
284+
285+ newStream (l , nil , r , w , func (l * State ) int {
286+ err := closer .Close ()
287+ cmd .Wait ()
288+ return FileResult (l , err , "" )
289+ })
290+ return 1
291+ }},
231292 {"read" , func (l * State ) int { return read (l , ioFile (l , input ), 1 ) }},
232293 {"tmpfile" , func (l * State ) int {
233294 s := newFile (l )
@@ -250,13 +311,17 @@ var ioLibrary = []RegistryFunction{
250311 return 1
251312 }},
252313 {"write" , func (l * State ) int { return write (l , ioFile (l , output ), 1 ) }},
314+ // Register standard files directly in ioLibrary
315+ {"stdin" , func (l * State ) int { newStream (l , os .Stdin , nil , nil , dontClose ); return 1 }},
316+ {"stdout" , func (l * State ) int { newStream (l , os .Stdout , nil , nil , dontClose ); return 1 }},
317+ {"stderr" , func (l * State ) int { newStream (l , os .Stderr , nil , nil , dontClose ); return 1 }},
253318}
254319
255320var fileHandleMethods = []RegistryFunction {
256321 {"close" , close },
257322 {"flush" , func (l * State ) int { return FileResult (l , toFile (l ).Sync (), "" ) }},
258323 {"lines" , func (l * State ) int { toFile (l ); lines (l , false ); return 1 }},
259- {"read" , func (l * State ) int { return read (l , toFile ( l ) , 2 ) }},
324+ {"read" , func (l * State ) int { return read (l , nil , 2 ) }},
260325 {"seek" , func (l * State ) int {
261326 whence := []int {os .SEEK_SET , os .SEEK_CUR , os .SEEK_END }
262327 f := toFile (l )
@@ -272,13 +337,10 @@ var fileHandleMethods = []RegistryFunction{
272337 return 1
273338 }},
274339 {"setvbuf" , func (l * State ) int { // Files are unbuffered in Go. Fake support for now.
275- // f := toFile(l)
276- // op := CheckOption(l, 2, "", []string{"no", "full", "line"})
277- // size := OptInteger(l, 3, 1024)
278- // TODO err := setvbuf(f, nil, mode[op], size)
340+ // TODO: Implement setvbuf if needed in the future
279341 return FileResult (l , nil , "" )
280342 }},
281- {"write" , func (l * State ) int { l .PushValue (1 ); return write (l , toFile ( l ) , 2 ) }},
343+ {"write" , func (l * State ) int { l .PushValue (1 ); return write (l , nil , 2 ) }},
282344 // {"__gc", },
283345 {"__tostring" , func (l * State ) int {
284346 if s := toStream (l ); s .close == nil {
@@ -297,28 +359,17 @@ func dontClose(l *State) int {
297359 return 2
298360}
299361
300- func registerStdFile (l * State , f * os.File , reg , name string ) {
301- newStream (l , f , dontClose )
302- if reg != "" {
303- l .PushValue (- 1 )
304- l .SetField (RegistryIndex , reg )
305- }
306- l .SetField (- 2 , name )
307- }
308-
309362// IOOpen opens the io library. Usually passed to Require.
310363func IOOpen (l * State ) int {
311- NewLibrary (l , ioLibrary )
312-
364+ // First create the file handle metatable
313365 NewMetaTable (l , fileHandle )
314366 l .PushValue (- 1 )
315367 l .SetField (- 2 , "__index" )
316368 SetFunctions (l , fileHandleMethods , 0 )
317369 l .Pop (1 )
318-
319- registerStdFile (l , os .Stdin , input , "stdin" )
320- registerStdFile (l , os .Stdout , output , "stdout" )
321- registerStdFile (l , os .Stderr , "" , "stderr" )
370+
371+ // Then create the io library
372+ NewLibrary (l , ioLibrary )
322373
323374 return 1
324375}
0 commit comments