5
5
"io"
6
6
"io/ioutil"
7
7
"os"
8
+ "os/exec"
8
9
)
9
10
10
11
const fileHandle = "FILE*"
@@ -13,6 +14,8 @@ const output = "_IO_output"
13
14
14
15
type stream struct {
15
16
f * os.File
17
+ r io.Reader
18
+ w io.Writer
16
19
close Function
17
20
}
18
21
@@ -27,15 +30,31 @@ func toFile(l *State) *os.File {
27
30
return s .f
28
31
}
29
32
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 }
32
51
l .PushUserData (s )
33
52
SetMetaTableNamed (l , fileHandle )
34
53
return s
35
54
}
36
55
37
56
func 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 (), "" ) })
39
58
}
40
59
41
60
func ioFile (l * State , name string ) * os.File {
@@ -85,17 +104,17 @@ func close(l *State) int {
85
104
if l .IsNone (1 ) {
86
105
l .Field (RegistryIndex , output )
87
106
}
88
- toFile (l )
89
107
return closeHelper (l )
90
108
}
91
109
92
110
func write (l * State , f * os.File , argIndex int ) int {
93
111
var err error
112
+ writer := toWriter (l )
94
113
for argCount := l .Top (); argIndex < argCount && err == nil ; argIndex ++ {
95
114
if n , ok := l .ToNumber (argIndex ); ok {
96
- _ , err = f . WriteString ( numberToString (n ))
115
+ _ , err = writer . Write ([] byte ( numberToString (n ) ))
97
116
} else {
98
- _ , err = f . WriteString ( CheckString (l , argIndex ))
117
+ _ , err = writer . Write ([] byte ( CheckString (l , argIndex ) ))
99
118
}
100
119
}
101
120
if err == nil {
@@ -104,6 +123,16 @@ func write(l *State, f *os.File, argIndex int) int {
104
123
return FileResult (l , err , "" )
105
124
}
106
125
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
+
107
136
func readNumber (l * State , f * os.File ) (err error ) {
108
137
var n float64
109
138
if _ , err = fmt .Fscanf (f , "%f" , & n ); err == nil {
@@ -114,25 +143,6 @@ func readNumber(l *State, f *os.File) (err error) {
114
143
return
115
144
}
116
145
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
-
136
146
func readLine (l * State ) int {
137
147
s := l .ToUserData (UpValueIndex (1 )).(* stream )
138
148
argCount , _ := l .ToInteger (UpValueIndex (2 ))
@@ -227,7 +237,58 @@ var ioLibrary = []RegistryFunction{
227
237
return FileResult (l , err , name )
228
238
}},
229
239
{"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
+ }},
231
292
{"read" , func (l * State ) int { return read (l , ioFile (l , input ), 1 ) }},
232
293
{"tmpfile" , func (l * State ) int {
233
294
s := newFile (l )
@@ -250,13 +311,17 @@ var ioLibrary = []RegistryFunction{
250
311
return 1
251
312
}},
252
313
{"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 }},
253
318
}
254
319
255
320
var fileHandleMethods = []RegistryFunction {
256
321
{"close" , close },
257
322
{"flush" , func (l * State ) int { return FileResult (l , toFile (l ).Sync (), "" ) }},
258
323
{"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 ) }},
260
325
{"seek" , func (l * State ) int {
261
326
whence := []int {os .SEEK_SET , os .SEEK_CUR , os .SEEK_END }
262
327
f := toFile (l )
@@ -272,13 +337,10 @@ var fileHandleMethods = []RegistryFunction{
272
337
return 1
273
338
}},
274
339
{"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
279
341
return FileResult (l , nil , "" )
280
342
}},
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 ) }},
282
344
// {"__gc", },
283
345
{"__tostring" , func (l * State ) int {
284
346
if s := toStream (l ); s .close == nil {
@@ -297,28 +359,17 @@ func dontClose(l *State) int {
297
359
return 2
298
360
}
299
361
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
-
309
362
// IOOpen opens the io library. Usually passed to Require.
310
363
func IOOpen (l * State ) int {
311
- NewLibrary (l , ioLibrary )
312
-
364
+ // First create the file handle metatable
313
365
NewMetaTable (l , fileHandle )
314
366
l .PushValue (- 1 )
315
367
l .SetField (- 2 , "__index" )
316
368
SetFunctions (l , fileHandleMethods , 0 )
317
369
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 )
322
373
323
374
return 1
324
375
}
0 commit comments