tile38/vendor/github.com/yuin/gopher-lua/iolib.go

744 lines
15 KiB
Go
Raw Normal View History

package lua
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"syscall"
)
var ioFuncs = map[string]LGFunction{
"close": ioClose,
"flush": ioFlush,
"lines": ioLines,
"input": ioInput,
"output": ioOutput,
"open": ioOpenFile,
"popen": ioPopen,
"read": ioRead,
"type": ioType,
"tmpfile": ioTmpFile,
"write": ioWrite,
}
const lFileClass = "FILE*"
type lFile struct {
fp *os.File
pp *exec.Cmd
writer io.Writer
reader *bufio.Reader
closed bool
}
type lFileType int
const (
lFileFile lFileType = iota
lFileProcess
)
const fileDefOutIndex = 1
const fileDefInIndex = 2
const fileDefaultWriteBuffer = 4096
const fileDefaultReadBuffer = 4096
func checkFile(L *LState) *lFile {
ud := L.CheckUserData(1)
if file, ok := ud.Value.(*lFile); ok {
return file
}
L.ArgError(1, "file expected")
return nil
}
func errorIfFileIsClosed(L *LState, file *lFile) {
if file.closed {
L.ArgError(1, "file is closed")
}
}
func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) {
ud := L.NewUserData()
var err error
if file == nil {
file, err = os.OpenFile(path, flag, perm)
if err != nil {
return nil, err
}
}
lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, closed: false}
ud.Value = lfile
if writable {
lfile.writer = file
}
if readable {
lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer)
}
L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
return ud, nil
}
func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) {
ud := L.NewUserData()
c, args := popenArgs(cmd)
pp := exec.Command(c, args...)
lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, closed: false}
ud.Value = lfile
var err error
if writable {
lfile.writer, err = pp.StdinPipe()
}
if readable {
var reader io.Reader
reader, err = pp.StdoutPipe()
lfile.reader = bufio.NewReaderSize(reader, fileDefaultReadBuffer)
}
if err != nil {
return nil, err
}
err = pp.Start()
if err != nil {
return nil, err
}
L.SetMetatable(ud, L.GetTypeMetatable(lFileClass))
return ud, nil
}
func (file *lFile) Type() lFileType {
if file.fp == nil {
return lFileProcess
}
return lFileFile
}
func (file *lFile) Name() string {
switch file.Type() {
case lFileFile:
return fmt.Sprintf("file %s", file.fp.Name())
case lFileProcess:
return fmt.Sprintf("process %s", file.pp.Path)
}
return ""
}
func (file *lFile) AbandonReadBuffer() error {
if file.Type() == lFileFile && file.reader != nil {
_, err := file.fp.Seek(-int64(file.reader.Buffered()), 1)
if err != nil {
return err
}
file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer)
}
return nil
}
func fileDefOut(L *LState) *LUserData {
return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData)
}
func fileDefIn(L *LState) *LUserData {
return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData)
}
func fileIsWritable(L *LState, file *lFile) int {
if file.writer == nil {
L.Push(LNil)
L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name())))
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
return 3
}
return 0
}
func fileIsReadable(L *LState, file *lFile) int {
if file.reader == nil {
L.Push(LNil)
L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name())))
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
return 3
}
return 0
}
var stdFiles = []struct {
name string
file *os.File
writable bool
readable bool
}{
{"stdout", os.Stdout, true, false},
{"stdin", os.Stdin, false, true},
{"stderr", os.Stderr, true, false},
}
func OpenIo(L *LState) int {
mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable)
mt := L.NewTypeMetatable(lFileClass)
mt.RawSetString("__index", mt)
L.SetFuncs(mt, fileMethods)
mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter)))
for _, finfo := range stdFiles {
file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable)
mod.RawSetString(finfo.name, file)
}
uv := L.CreateTable(2, 0)
uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout"))
uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin"))
for name, fn := range ioFuncs {
mod.RawSetString(name, L.NewClosure(fn, uv))
}
mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv)))
// Modifications are being made in-place rather than returned?
L.Push(mod)
return 1
}
var fileMethods = map[string]LGFunction{
"__tostring": fileToString,
"write": fileWrite,
"close": fileClose,
"flush": fileFlush,
"lines": fileLines,
"read": fileRead,
"seek": fileSeek,
"setvbuf": fileSetVBuf,
}
func fileToString(L *LState) int {
file := checkFile(L)
if file.Type() == lFileFile {
if file.closed {
L.Push(LString("file (closed)"))
} else {
L.Push(LString("file"))
}
} else {
if file.closed {
L.Push(LString("process (closed)"))
} else {
L.Push(LString("process"))
}
}
return 1
}
func fileWriteAux(L *LState, file *lFile, idx int) int {
if n := fileIsWritable(L, file); n != 0 {
return n
}
errorIfFileIsClosed(L, file)
top := L.GetTop()
out := file.writer
var err error
for i := idx; i <= top; i++ {
L.CheckTypes(i, LTNumber, LTString)
s := LVAsString(L.Get(i))
if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil {
goto errreturn
}
}
file.AbandonReadBuffer()
L.Push(LTrue)
return 1
errreturn:
file.AbandonReadBuffer()
L.Push(LNil)
L.Push(LString(err.Error()))
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
return 3
}
func fileCloseAux(L *LState, file *lFile) int {
file.closed = true
var err error
if file.writer != nil {
if bwriter, ok := file.writer.(*bufio.Writer); ok {
if err = bwriter.Flush(); err != nil {
goto errreturn
}
}
}
file.AbandonReadBuffer()
switch file.Type() {
case lFileFile:
if err = file.fp.Close(); err != nil {
goto errreturn
}
L.Push(LTrue)
return 1
case lFileProcess:
err = file.pp.Wait()
var exitStatus int // Initialised to zero value = 0
if err != nil {
if e2, ok := err.(*exec.ExitError); ok {
if s, ok := e2.Sys().(syscall.WaitStatus); ok {
exitStatus = s.ExitStatus()
} else {
err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.")
}
}
} else {
exitStatus = 0
}
L.Push(LNumber(exitStatus))
return 1
}
errreturn:
L.RaiseError(err.Error())
return 0
}
func fileFlushAux(L *LState, file *lFile) int {
if n := fileIsWritable(L, file); n != 0 {
return n
}
errorIfFileIsClosed(L, file)
if bwriter, ok := file.writer.(*bufio.Writer); ok {
if err := bwriter.Flush(); err != nil {
L.Push(LNil)
L.Push(LString(err.Error()))
return 2
}
}
L.Push(LTrue)
return 1
}
func fileReadAux(L *LState, file *lFile, idx int) int {
if n := fileIsReadable(L, file); n != 0 {
return n
}
errorIfFileIsClosed(L, file)
if L.GetTop() == idx-1 {
L.Push(LString("*l"))
}
var err error
top := L.GetTop()
for i := idx; i <= top; i++ {
switch lv := L.Get(i).(type) {
case LNumber:
size := int64(lv)
if size == 0 {
_, err = file.reader.ReadByte()
if err == io.EOF {
L.Push(LNil)
goto normalreturn
}
file.reader.UnreadByte()
}
var buf []byte
var iseof bool
buf, err, iseof = readBufioSize(file.reader, size)
if iseof {
L.Push(LNil)
goto normalreturn
}
if err != nil {
goto errreturn
}
L.Push(LString(string(buf)))
case LString:
options := L.CheckString(i)
if len(options) > 0 && options[0] != '*' {
L.ArgError(2, "invalid options:"+options)
}
for _, opt := range options[1:] {
switch opt {
case 'n':
var v LNumber
_, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v)
if err == io.EOF {
L.Push(LNil)
goto normalreturn
}
if err != nil {
goto errreturn
}
L.Push(v)
case 'a':
var buf []byte
buf, err = ioutil.ReadAll(file.reader)
if err == io.EOF {
L.Push(LString(""))
goto normalreturn
}
if err != nil {
goto errreturn
}
L.Push(LString(string(buf)))
case 'l':
var buf []byte
var iseof bool
buf, err, iseof = readBufioLine(file.reader)
if iseof {
L.Push(LNil)
goto normalreturn
}
if err != nil {
goto errreturn
}
L.Push(LString(string(buf)))
default:
L.ArgError(2, "invalid options:"+string(opt))
}
}
}
}
normalreturn:
return L.GetTop() - top
errreturn:
L.RaiseError(err.Error())
//L.Push(LNil)
//L.Push(LString(err.Error()))
return 2
}
var fileSeekOptions = []string{"set", "cur", "end"}
func fileSeek(L *LState) int {
file := checkFile(L)
if file.Type() != lFileFile {
L.Push(LNil)
L.Push(LString("can not seek a process."))
return 2
}
top := L.GetTop()
if top == 1 {
L.Push(LString("cur"))
L.Push(LNumber(0))
} else if top == 2 {
L.Push(LNumber(0))
}
var pos int64
var err error
err = file.AbandonReadBuffer()
if err != nil {
goto errreturn
}
pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions))
if err != nil {
goto errreturn
}
L.Push(LNumber(pos))
return 1
errreturn:
L.Push(LNil)
L.Push(LString(err.Error()))
return 2
}
func fileWrite(L *LState) int {
return fileWriteAux(L, checkFile(L), 2)
}
func fileClose(L *LState) int {
return fileCloseAux(L, checkFile(L))
}
func fileFlush(L *LState) int {
return fileFlushAux(L, checkFile(L))
}
func fileLinesIter(L *LState) int {
var file *lFile
if ud, ok := L.Get(1).(*LUserData); ok {
file = ud.Value.(*lFile)
} else {
file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
}
buf, _, err := file.reader.ReadLine()
if err != nil {
if err == io.EOF {
L.Push(LNil)
return 1
}
L.RaiseError(err.Error())
}
L.Push(LString(string(buf)))
return 1
}
func fileLines(L *LState) int {
file := checkFile(L)
ud := L.CheckUserData(1)
if n := fileIsReadable(L, file); n != 0 {
return 0
}
L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud))
return 1
}
func fileRead(L *LState) int {
return fileReadAux(L, checkFile(L), 2)
}
var filebufOptions = []string{"no", "full"}
func fileSetVBuf(L *LState) int {
var err error
var writer io.Writer
file := checkFile(L)
if n := fileIsWritable(L, file); n != 0 {
return n
}
switch filebufOptions[L.CheckOption(2, filebufOptions)] {
case "no":
switch file.Type() {
case lFileFile:
file.writer = file.fp
case lFileProcess:
file.writer, err = file.pp.StdinPipe()
if err != nil {
goto errreturn
}
}
case "full", "line": // TODO line buffer not supported
bufsize := L.OptInt(3, fileDefaultWriteBuffer)
switch file.Type() {
case lFileFile:
file.writer = bufio.NewWriterSize(file.fp, bufsize)
case lFileProcess:
writer, err = file.pp.StdinPipe()
if err != nil {
goto errreturn
}
file.writer = bufio.NewWriterSize(writer, bufsize)
}
}
L.Push(LTrue)
return 1
errreturn:
L.Push(LNil)
L.Push(LString(err.Error()))
return 2
}
func ioInput(L *LState) int {
if L.GetTop() == 0 {
L.Push(fileDefIn(L))
return 1
}
switch lv := L.Get(1).(type) {
case LString:
file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true)
if err != nil {
L.RaiseError(err.Error())
}
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file)
L.Push(file)
return 1
case *LUserData:
if _, ok := lv.Value.(*lFile); ok {
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv)
L.Push(lv)
return 1
}
}
L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
return 0
}
func ioClose(L *LState) int {
if L.GetTop() == 0 {
return fileCloseAux(L, fileDefOut(L).Value.(*lFile))
}
return fileClose(L)
}
func ioFlush(L *LState) int {
return fileFlushAux(L, fileDefOut(L).Value.(*lFile))
}
func ioLinesIter(L *LState) int {
var file *lFile
toclose := false
if ud, ok := L.Get(1).(*LUserData); ok {
file = ud.Value.(*lFile)
} else {
file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile)
toclose = true
}
buf, _, err := file.reader.ReadLine()
if err != nil {
if err == io.EOF {
if toclose {
fileCloseAux(L, file)
}
L.Push(LNil)
return 1
}
L.RaiseError(err.Error())
}
L.Push(LString(string(buf)))
return 1
}
func ioLines(L *LState) int {
if L.GetTop() == 0 {
L.Push(L.Get(UpvalueIndex(2)))
L.Push(fileDefIn(L))
return 2
}
path := L.CheckString(1)
ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true)
if err != nil {
return 0
}
L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud))
return 1
}
var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"}
func ioOpenFile(L *LState) int {
path := L.CheckString(1)
if L.GetTop() == 1 {
L.Push(LString("r"))
}
mode := os.O_RDONLY
perm := 0600
writable := true
readable := true
switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] {
case "r", "rb":
mode = os.O_RDONLY
writable = false
case "w", "wb":
mode = os.O_WRONLY | os.O_CREATE
readable = false
case "a", "ab":
mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
case "r+", "rb+":
mode = os.O_RDWR
case "w+", "wb+":
mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE
case "a+", "ab+":
mode = os.O_APPEND | os.O_RDWR | os.O_CREATE
}
file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable)
if err != nil {
L.Push(LNil)
L.Push(LString(err.Error()))
L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack
return 3
}
L.Push(file)
return 1
}
var ioPopenOptions = []string{"r", "w"}
func ioPopen(L *LState) int {
cmd := L.CheckString(1)
if L.GetTop() == 1 {
L.Push(LString("r"))
}
var file *LUserData
var err error
switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] {
case "r":
file, err = newProcess(L, cmd, false, true)
case "w":
file, err = newProcess(L, cmd, true, false)
}
if err != nil {
L.Push(LNil)
L.Push(LString(err.Error()))
return 2
}
L.Push(file)
return 1
}
func ioRead(L *LState) int {
return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1)
}
func ioType(L *LState) int {
ud, udok := L.Get(1).(*LUserData)
if !udok {
L.Push(LNil)
return 1
}
file, ok := ud.Value.(*lFile)
if !ok {
L.Push(LNil)
return 1
}
if file.closed {
L.Push(LString("closed file"))
return 1
}
L.Push(LString("file"))
return 1
}
func ioTmpFile(L *LState) int {
file, err := ioutil.TempFile("", "")
if err != nil {
L.Push(LNil)
L.Push(LString(err.Error()))
return 2
}
L.G.tempFiles = append(L.G.tempFiles, file)
ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true)
L.Push(ud)
return 1
}
func ioOutput(L *LState) int {
if L.GetTop() == 0 {
L.Push(fileDefOut(L))
return 1
}
switch lv := L.Get(1).(type) {
case LString:
file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false)
if err != nil {
L.RaiseError(err.Error())
}
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file)
L.Push(file)
return 1
case *LUserData:
if _, ok := lv.Value.(*lFile); ok {
L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv)
L.Push(lv)
return 1
}
}
L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String())
return 0
}
func ioWrite(L *LState) int {
return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1)
}
//