github.com/hirochachacha/plua@v0.0.0-20170217012138-c82f520cc725/stdlib/io/io.go (about)

     1  package io
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"os/exec"
     7  	"strings"
     8  
     9  	"github.com/hirochachacha/plua/internal/file"
    10  	"github.com/hirochachacha/plua/internal/version"
    11  	"github.com/hirochachacha/plua/object"
    12  	"github.com/hirochachacha/plua/object/fnutil"
    13  )
    14  
    15  func Open(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
    16  	fileIndex := th.NewTableSize(0, 7)
    17  
    18  	fileIndex.Set(object.String("close"), object.GoFunction(fclose))
    19  	fileIndex.Set(object.String("flush"), object.GoFunction(fflush))
    20  	fileIndex.Set(object.String("lines"), object.GoFunction(flines))
    21  	fileIndex.Set(object.String("read"), object.GoFunction(fread))
    22  	fileIndex.Set(object.String("seek"), object.GoFunction(fseek))
    23  	fileIndex.Set(object.String("setvbuf"), object.GoFunction(fsetvbuf))
    24  	fileIndex.Set(object.String("write"), object.GoFunction(fwrite))
    25  
    26  	mt := th.NewTableSize(0, 3)
    27  
    28  	mt.Set(object.String("__index"), fileIndex)
    29  	mt.Set(object.String("__tostring"), object.GoFunction(ftostring))
    30  	mt.Set(object.TM_NAME, object.String("FILE*"))
    31  
    32  	stdin := &object.Userdata{
    33  		Value:     file.NewFile(os.Stdin, os.O_RDONLY, true),
    34  		Metatable: mt,
    35  	}
    36  
    37  	stdout := &object.Userdata{
    38  		Value:     file.NewFile(os.Stdout, os.O_WRONLY, true),
    39  		Metatable: mt,
    40  	}
    41  
    42  	stderr := &object.Userdata{
    43  		Value:     file.NewFile(os.Stderr, os.O_WRONLY, true),
    44  		Metatable: mt,
    45  	}
    46  
    47  	var _input = stdin
    48  	var _output = stdout
    49  
    50  	// close([file])
    51  	var _close = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
    52  		if len(args) == 0 {
    53  			return fileResult(th, _output.Value.(file.File).Close())
    54  		}
    55  
    56  		return fclose(th, args...)
    57  	}
    58  
    59  	var flush = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
    60  		if len(args) == 0 {
    61  			return fileResult(th, _output.Value.(file.File).Flush())
    62  		}
    63  
    64  		return fflush(th, args...)
    65  	}
    66  
    67  	// input([file])
    68  	var input = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
    69  		if len(args) == 0 {
    70  			return []object.Value{_input}, nil
    71  		}
    72  
    73  		ap := fnutil.NewArgParser(th, args)
    74  
    75  		var ud *object.Userdata
    76  
    77  		if fname, err := ap.ToGoString(0); err == nil {
    78  			f, e := file.OpenFile(fname, os.O_RDONLY, 0644)
    79  			if e != nil {
    80  				return nil, object.NewRuntimeError(e.Error())
    81  			}
    82  
    83  			ud = &object.Userdata{
    84  				Value:     f,
    85  				Metatable: mt,
    86  			}
    87  		} else {
    88  			var err *object.RuntimeError
    89  			ud, err = ap.ToFullUserdata(0)
    90  			if err != nil {
    91  				return nil, ap.TypeError(0, "FILE*")
    92  			}
    93  
    94  			_, ok := ud.Value.(file.File)
    95  			if !ok {
    96  				return nil, ap.TypeError(0, "FILE*")
    97  			}
    98  		}
    99  
   100  		_input = ud
   101  
   102  		return []object.Value{ud}, nil
   103  	}
   104  
   105  	// lines([filename, ...])
   106  	var lines = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   107  		var f file.File
   108  		var off int
   109  		var doClose bool
   110  
   111  		switch {
   112  		case len(args) == 0:
   113  			f = _input.Value.(file.File)
   114  
   115  			off = 0
   116  			doClose = false
   117  		case args[0] == nil:
   118  			f = _input.Value.(file.File)
   119  
   120  			off = 1
   121  			doClose = false
   122  		default:
   123  			ap := fnutil.NewArgParser(th, args)
   124  
   125  			fname, err := ap.ToGoString(0)
   126  			if err != nil {
   127  				return nil, err
   128  			}
   129  
   130  			var e error
   131  			f, e = file.OpenFile(fname, os.O_RDONLY, 0644)
   132  			if e != nil {
   133  				return nil, object.NewRuntimeError(e.Error())
   134  			}
   135  
   136  			off = 1
   137  			doClose = true
   138  		}
   139  
   140  		if len(args)-off > version.MAXARGLINE {
   141  			return nil, object.NewRuntimeError("too many arguments")
   142  		}
   143  
   144  		fnargs := append([]object.Value{}, args...)
   145  
   146  		retfn := func(_ object.Thread, _ ...object.Value) ([]object.Value, *object.RuntimeError) {
   147  			return _read(th, fnargs, f, off, doClose, true)
   148  		}
   149  
   150  		return []object.Value{object.GoFunction(retfn)}, nil
   151  	}
   152  
   153  	// open(filename, [, mode])
   154  	var open = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   155  		ap := fnutil.NewArgParser(th, args)
   156  
   157  		fname, err := ap.ToGoString(0)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  
   162  		mode, err := ap.OptGoString(1, "r")
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  
   167  		if len(mode) > 0 && mode[len(mode)-1] == 'b' {
   168  			mode = mode[:len(mode)-1]
   169  		}
   170  
   171  		var f file.File
   172  		var e error
   173  
   174  		switch mode {
   175  		case "r":
   176  			f, e = file.OpenFile(fname, os.O_RDONLY, 0644)
   177  		case "w":
   178  			f, e = file.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
   179  		case "a":
   180  			f, e = file.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
   181  		case "r+":
   182  			f, e = file.OpenFile(fname, os.O_RDWR, 0644)
   183  		case "w+":
   184  			f, e = file.OpenFile(fname, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644)
   185  		case "a+":
   186  			f, e = file.OpenFile(fname, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
   187  		default:
   188  			return nil, ap.ArgError(1, "invalid mode")
   189  		}
   190  		if e != nil {
   191  			return fileResult(th, e)
   192  		}
   193  
   194  		ud := &object.Userdata{
   195  			Value:     f,
   196  			Metatable: mt,
   197  		}
   198  
   199  		return []object.Value{ud}, nil
   200  	}
   201  
   202  	// output([file])
   203  	var output = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   204  		if len(args) == 0 {
   205  			return []object.Value{_output}, nil
   206  		}
   207  
   208  		ap := fnutil.NewArgParser(th, args)
   209  
   210  		var ud *object.Userdata
   211  
   212  		if fname, err := ap.ToGoString(0); err == nil {
   213  			f, e := file.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
   214  			if e != nil {
   215  				return nil, object.NewRuntimeError(e.Error())
   216  			}
   217  
   218  			ud = &object.Userdata{
   219  				Value:     f,
   220  				Metatable: mt,
   221  			}
   222  		} else {
   223  			var err *object.RuntimeError
   224  			ud, err = ap.ToFullUserdata(0)
   225  			if err != nil {
   226  				return nil, ap.TypeError(0, "FILE*")
   227  			}
   228  
   229  			_, ok := ud.Value.(file.File)
   230  			if !ok {
   231  				return nil, ap.TypeError(0, "FILE*")
   232  			}
   233  		}
   234  
   235  		_output = ud
   236  
   237  		return []object.Value{ud}, nil
   238  	}
   239  
   240  	var popen = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   241  		ap := fnutil.NewArgParser(th, args)
   242  
   243  		prog, err := ap.ToGoString(0)
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  
   248  		mode, err := ap.OptGoString(1, "r")
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  
   253  		progArgs := strings.Fields(prog)
   254  
   255  		cmd := exec.Command(progArgs[0], progArgs[1:]...)
   256  
   257  		var ud *object.Userdata
   258  
   259  		switch mode {
   260  		case "r":
   261  			r, e := cmd.StdoutPipe()
   262  			if e != nil {
   263  				return execResult(th, e)
   264  			}
   265  
   266  			f, ok := r.(*os.File)
   267  			if !ok {
   268  				return nil, object.NewRuntimeError("pipe is not *os.File")
   269  			}
   270  
   271  			ud = &object.Userdata{
   272  				Value:     file.NewFile(f, os.O_RDONLY, false),
   273  				Metatable: mt,
   274  			}
   275  		case "w":
   276  			w, e := cmd.StdinPipe()
   277  			if e != nil {
   278  				return execResult(th, e)
   279  			}
   280  
   281  			f, ok := w.(*os.File)
   282  			if !ok {
   283  				return nil, object.NewRuntimeError("pipe is not *os.File")
   284  			}
   285  
   286  			ud = &object.Userdata{
   287  				Value:     file.NewFile(f, os.O_WRONLY, false),
   288  				Metatable: mt,
   289  			}
   290  		default:
   291  			return nil, ap.OptionError(1, mode)
   292  		}
   293  
   294  		return []object.Value{ud}, nil
   295  	}
   296  
   297  	var read = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   298  		f := _input.Value.(file.File)
   299  
   300  		if f.IsClosed() {
   301  			return nil, object.NewRuntimeError("standard input file is closed")
   302  		}
   303  
   304  		return _read(th, args, f, 0, false, false)
   305  	}
   306  
   307  	var tmpfile = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   308  		f, err := ioutil.TempFile("", "plua")
   309  		if err != nil {
   310  			return fileResult(th, err)
   311  		}
   312  
   313  		ud := &object.Userdata{
   314  			Value:     file.NewFile(f, os.O_RDWR, false),
   315  			Metatable: mt,
   316  		}
   317  
   318  		return []object.Value{ud}, nil
   319  	}
   320  
   321  	var _type = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   322  		ap := fnutil.NewArgParser(th, args)
   323  
   324  		ud, err := ap.ToFullUserdata(0)
   325  		if err != nil {
   326  			return nil, nil
   327  		}
   328  
   329  		f, ok := ud.Value.(file.File)
   330  		if !ok {
   331  			return nil, nil
   332  		}
   333  
   334  		if f.IsClosed() {
   335  			return []object.Value{object.String("closed file")}, nil
   336  		}
   337  
   338  		return []object.Value{object.String("file")}, nil
   339  	}
   340  
   341  	var write = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) {
   342  		f := _output.Value.(file.File)
   343  
   344  		if f.IsClosed() {
   345  			return nil, object.NewRuntimeError("standard output file is closed")
   346  		}
   347  
   348  		ap := fnutil.NewArgParser(th, args)
   349  
   350  		for i := range args {
   351  			s, err := ap.ToGoString(i)
   352  			if err != nil {
   353  				return nil, err
   354  			}
   355  
   356  			_, e := f.WriteString(s)
   357  			if e != nil {
   358  				return fileResult(th, e)
   359  			}
   360  		}
   361  
   362  		return []object.Value{_output}, nil
   363  	}
   364  
   365  	m := th.NewTableSize(0, 14)
   366  
   367  	m.Set(object.String("stdin"), stdin)
   368  	m.Set(object.String("stdout"), stdout)
   369  	m.Set(object.String("stderr"), stderr)
   370  
   371  	m.Set(object.String("close"), object.GoFunction(_close))
   372  	m.Set(object.String("flush"), object.GoFunction(flush))
   373  	m.Set(object.String("input"), object.GoFunction(input))
   374  	m.Set(object.String("lines"), object.GoFunction(lines))
   375  	m.Set(object.String("open"), object.GoFunction(open))
   376  	m.Set(object.String("output"), object.GoFunction(output))
   377  	m.Set(object.String("popen"), object.GoFunction(popen))
   378  	m.Set(object.String("read"), object.GoFunction(read))
   379  	m.Set(object.String("tmpfile"), object.GoFunction(tmpfile))
   380  	m.Set(object.String("type"), object.GoFunction(_type))
   381  	m.Set(object.String("write"), object.GoFunction(write))
   382  
   383  	return []object.Value{m}, nil
   384  }