diff --git a/src/core/org/luaj/lib/IoLib.java b/src/core/org/luaj/lib/IoLib.java index 1d0c3487..4180e49b 100644 --- a/src/core/org/luaj/lib/IoLib.java +++ b/src/core/org/luaj/lib/IoLib.java @@ -63,6 +63,15 @@ public class IoLib extends LFunction { */ abstract protected File openFile(String filename, String mode) throws IOException; + /** + * Start a new process and return a file for input or output + * @param prog the program to execute + * @param mode "r" to read, "w" to write + * @return File to read to or write from + * @throws IOException if an i/o exception occurs + */ + abstract protected File openProgram(String prog, String mode) throws IOException; + public static final String[] NAMES = { "io", @@ -130,10 +139,6 @@ public class IoLib extends LFunction { PackageLib.setIsLoaded("io", io); } - private static final char DEFVAL_NONE = 0; - private static final char DEFVAL_STDIN = 'r'; - private static final char DEFVAL_STDOUT= 'w'; - private final int id; protected IoLib() { @@ -182,7 +187,6 @@ public class IoLib extends LFunction { public boolean luaStackCall( LuaState vm ) { File f; - String s; int n; try { switch ( id ) { @@ -191,7 +195,10 @@ public class IoLib extends LFunction { initialize(vm._G); break; case IO_CLOSE: - optfile(vm,2,DEFVAL_STDOUT).close(); + f = vm.isnoneornil(2)? + output(vm): + checkfile(vm,2); + f.close(); vm.resettop(); vm.pushboolean(true); break; @@ -202,15 +209,17 @@ public class IoLib extends LFunction { vm.pushboolean(true); break; case IO_INPUT: - INPUT = ((s = vm.optstring(1, null)) != null)? - ioopenfile(vm,s,"r"): - optfile(vm,2,DEFVAL_STDIN); + INPUT = vm.isnoneornil(2)? + input(vm): + vm.isstring(2)? + ioopenfile(vm,vm.checkstring(2),"r"): + checkfile(vm,2); setresult(vm, INPUT); break; case IO_LINES: - INPUT = ((s = vm.optstring(1, null)) != null)? - ioopenfile(vm,s,"r"): - optfile(vm,2,DEFVAL_STDIN); + INPUT = vm.isnoneornil(2)? + input(vm): + ioopenfile(vm,vm.checkstring(2),"r"); vm.resettop(); vm.pushlvalue(lines(INPUT)); break; @@ -218,12 +227,15 @@ public class IoLib extends LFunction { setresult(vm, openFile(vm.checkstring(2), vm.optstring(3,"r"))); break; case IO_OUTPUT: - OUTPUT = ((s = vm.optstring(2, null)) != null)? - ioopenfile(vm,s,"w"): - optfile(vm,2,DEFVAL_STDOUT); + OUTPUT = vm.isnoneornil(2)? + output(vm): + vm.isstring(2)? + ioopenfile(vm,vm.checkstring(2),"w"): + checkfile(vm,2); setresult(vm, OUTPUT); break; case IO_POPEN: + setresult(vm, openProgram(vm.checkstring(2),vm.optstring(3, "r"))); break; case IO_READ: ioread( vm, INPUT ); @@ -231,7 +243,7 @@ public class IoLib extends LFunction { case IO_TMPFILE: break; case IO_TYPE: - f = optfile(vm,2,DEFVAL_NONE); + f = optfile(vm,2); vm.resettop(); if ( f != null ) vm.pushstring(f.isclosed()? "closed file": "file"); @@ -262,7 +274,9 @@ public class IoLib extends LFunction { ioread(vm, f); break; case FILE_SEEK: - n = checkfile(vm,2).seek(vm.optstring(1,"cur"),vm.optint(3, 0)); + f = checkfile(vm,2); + vm.remove(2); + n = f.seek(vm.optstring(2,"cur"),vm.optint(3, 0)); vm.resettop(); vm.pushinteger(n); break; @@ -303,7 +317,7 @@ public class IoLib extends LFunction { private static void iowrite(LuaState vm, File f) throws IOException { checkopen(vm,f); for ( int i=2, n=vm.gettop(); i<=n; i++ ) - f.write( vm.tolstring(i) ); + f.write( vm.checklstring(i) ); vm.resettop(); vm.pushboolean(true); } @@ -334,12 +348,9 @@ public class IoLib extends LFunction { return (File) vm.checkudata(index, File.class); } - private File optfile(LuaState vm, int index, char defval) { + private File optfile(LuaState vm, int index) { Object u = vm.touserdata(index); - return (u instanceof File? (File) u: - defval==DEFVAL_STDOUT? output(vm): - defval==DEFVAL_STDIN? input(vm): - null); + return (u instanceof File? (File) u: null); } private static void checkopen(LuaState vm, File file) { diff --git a/src/j2se/org/luaj/lib/j2se/J2seIoLib.java b/src/j2se/org/luaj/lib/j2se/J2seIoLib.java index d1a251d2..8cc8cd20 100644 --- a/src/j2se/org/luaj/lib/j2se/J2seIoLib.java +++ b/src/j2se/org/luaj/lib/j2se/J2seIoLib.java @@ -22,11 +22,6 @@ package org.luaj.lib.j2se; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -69,8 +64,12 @@ public class J2seIoLib extends IoLib { // TODO: handle update mode // boolean isupdate = mode.endsWith("+"); RandomAccessFile f = new RandomAccessFile(filename,isreadmode? "r": "rw"); - if ( isappend ) + if ( isappend ) { f.seek(f.length()); + } else { + if ( ! isreadmode ) + f.setLength(0); + } return new FileImpl( f ); } @@ -82,41 +81,42 @@ public class J2seIoLib extends IoLib { private final RandomAccessFile file; private final InputStream is; private final OutputStream os; - private final Closeable closer; - private final DataInput din; - private final DataOutput dout; private boolean closed = false; - private FileImpl( RandomAccessFile file, InputStream is, OutputStream os, DataInput din, DataOutput dout, Closeable closer ) { + private FileImpl( RandomAccessFile file, InputStream is, OutputStream os ) { this.file = file; this.is = is!=null? is.markSupported()? is: new BufferedInputStream(is): null; this.os = os; - this.din = din; - this.dout = dout; - this.closer = closer; } private FileImpl( RandomAccessFile f ) { - this( f, null, null, f, f, f ); + this( f, null, null ); } private FileImpl( InputStream i ) { - this( null, i, null, new DataInputStream(i), null, i ); + this( null, i, null ); } private FileImpl( OutputStream o ) { - this( null, null, o, null, new DataOutputStream(o), o ); + this( null, null, o ); } public String toString() { return "file ("+this.hashCode()+")"; } public void close() throws IOException { closed = true; - closer.close(); + if ( file != null ) + file.close(); + else if ( os != null ) + os.close(); + else if ( is != null ) + is.close(); } public void flush() throws IOException { if ( os != null ) os.flush(); } public void write(LString s) throws IOException { - if ( dout != null ) - dout.write( s.m_bytes, s.m_offset, s.m_length ); + if ( os != null ) + os.write( s.m_bytes, s.m_offset, s.m_length ); + else if ( file != null ) + file.write( s.m_bytes, s.m_offset, s.m_length ); else notimplemented(); } @@ -125,21 +125,24 @@ public class J2seIoLib extends IoLib { } public int seek(String option, int pos) throws IOException { if ( file != null ) { - if ( "set".equals(option) ) + if ( "set".equals(option) ) { file.seek(pos); - else if ( "end".equals(option) ) - file.seek(file.length()+pos); - else + return (int) file.getFilePointer(); + } else if ( "end".equals(option) ) { + file.seek(file.length()+1+pos); + return (int) file.length()+1; + } else { file.seek(file.getFilePointer()+pos); - return (int) file.getFilePointer(); + return (int) file.getFilePointer(); + } } notimplemented(); return 0; } public LValue readBytes(int count) throws IOException { - if ( din != null ) { + if ( file != null ) { byte[] b = new byte[count]; - din.readFully(b); + file.readFully(b); return new LString(b); } notimplemented(); @@ -208,4 +211,10 @@ public class J2seIoLib extends IoLib { } } + protected File openProgram(String prog, String mode) throws IOException { + final Process p = Runtime.getRuntime().exec(prog); + return "w".equals(mode)? + new FileImpl( p.getOutputStream() ): + new FileImpl( p.getInputStream() ); + } } diff --git a/src/test/errors/iolibargs.lua b/src/test/errors/iolibargs.lua new file mode 100644 index 00000000..1718bf67 --- /dev/null +++ b/src/test/errors/iolibargs.lua @@ -0,0 +1,68 @@ +package.path = "?.lua;src/test/errors/?.lua" +require 'args' + +-- arg type tests for io library functions +local f + +-- io.close ([file]) +banner('io.close') +f = io.open("abc.txt","w") +checkallpass('io.close',{{f}}) +checkallerrors('io.close',{notanil},'bad argument #1') + +-- io.input ([file]) +checkallpass('io.input',{{nil,f,"abc.txt"}}) +checkallerrors('io.input',{nonstring},'bad argument #1') + +-- io.lines ([filename]) +io.input("abc.txt") +checkallpass('io.lines',{{nil,"abc.txt"}}) +checkallerrors('io.lines',{{f}},'bad argument #1') +checkallerrors('io.lines',{nonstring},'bad argument #1') + +-- io.open (filename [, mode]) +checkallpass('io.open',{{"abc.txt"},{nil,"r","w","a","r+","w+","a+"}}) +checkallerrors('io.open',{notastring},'bad argument #1') +checkallerrors('io.open',{{"abc.txt"},{nonstring}},'bad argument #2') + +-- io.output ([file]) +checkallpass('io.output',{{nil,f,"abc.txt"}}) +checkallerrors('io.output',{nonstring},'bad argument #1') + +-- io.popen (prog [, mode]) +checkallpass('io.popen',{{"hostname"},{nil,"r","w","a","r+","w+","a+"}}) +checkallerrors('io.popen',{notastring},'bad argument #1') +checkallerrors('io.popen',{{"hostname"},{nonstring}},'bad argument #2') + +-- io.read (···) +local areadfmt = {2,"*n","*a","*l","3"} +checkallpass('io.read',{}) +checkallpass('io.read',{areadfmt}) +checkallpass('io.read',{areadfmt,areadfmt}) +checkallerrors('io.read',{{aboolean,afunction,atable}},'bad argument #1') + +-- io.read (···) +checkallpass('io.write',{}) +checkallpass('io.write',{somestring}) +checkallpass('io.write',{somestring,somestring}) +checkallerrors('io.write',{nonstring},'bad argument #1') +checkallerrors('io.write',{somestring,nonstring},'bad argument #2') + +-- file:write () +file = io.open("seektest.txt","w") +checkallpass('file.write',{{file},somestring}) +checkallpass('file.write',{{file},somestring,somestring}) +checkallerrors('file.write',{},'bad argument #1') +checkallerrors('file.write',{{file},nonstring},'bad argument #1') +checkallerrors('file.write',{{file},somestring,nonstring},'bad argument #2') +pcall( file.close, file ) + +-- file:seek ([whence] [, offset]) +file = io.open("seektest.txt","r") +checkallpass('file.seek',{{file}}) +checkallpass('file.seek',{{file},{"set","cur","end"}}) +checkallpass('file.seek',{{file},{"set","cur","end"},{2,"3"}}) +checkallerrors('file.seek',{},'bad argument #1') +checkallerrors('file.seek',{{file},nonstring},'bad argument #1') +checkallerrors('file.seek',{{file},{"set","cur","end"},nonnumber},'bad argument #2') + diff --git a/src/test/res/iolib.lua b/src/test/res/iolib.lua index d7f01521..f99b767d 100644 --- a/src/test/res/iolib.lua +++ b/src/test/res/iolib.lua @@ -24,3 +24,40 @@ for i,v in ipairs(t) do print( string.format("%q",tostring(v)), type(v)) end +local h = io.open("abc.txt", "a") +print( 'h', io.type(h) ) +print( 'write', h:write('\nmore text\neven more text\n') ) +print( 'close', h:close() ) + +local j = io.open( "abc.txt", "r" ) +print( 'j', io.type(j) ) +print( 'seek', j:seek("set", 3) ) +print( 'read', j:read(4), j:read(3) ) +print( 'seek', j:seek("set", 2) ) +print( 'read', j:read(4), j:read(3) ) +print( 'seek', j:seek("cur", -8 ) ) +print( 'read', j:read(4), j:read(3) ) +print( 'seek(cur,0)', j:seek("cur",0) ) +print( 'seek(cur,20)', j:seek("cur",20) ) +print( 'seek(end,-5)', j:seek("end", -5) ) +print( 'read(4)', string.format("%q", tostring(j:read(4))) ) +print( 'read(4)', string.format("%q", tostring(j:read(4))) ) +print( 'read(4)', string.format("%q", tostring(j:read(4))) ) + +for l in io.lines("abc.txt") do + print( string.format('%q',l) ) +end +io.input("abc.txt") +for l in io.lines() do + print( string.format('%q',l) ) +end +io.input(io.open("abc.txt","r")) +for l in io.lines() do + print( string.format('%q',l) ) +end +io.input("abc.txt") +io.input(io.input()) +for l in io.lines() do + print( string.format('%q',l) ) +end +