| /******************************************************************************* |
| * Copyright (c) 2009-2011 Luaj.org. All rights reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| ******************************************************************************/ |
| package org.luaj.vm2.lib; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.EOFException; |
| import java.io.IOException; |
| |
| import org.luaj.vm2.Globals; |
| import org.luaj.vm2.LuaString; |
| import org.luaj.vm2.LuaTable; |
| import org.luaj.vm2.LuaValue; |
| import org.luaj.vm2.Varargs; |
| |
| /** |
| * Abstract base class extending {@link LibFunction} which implements the |
| * core of the lua standard {@code io} library. |
| * <p> |
| * It contains the implementation of the io library support that is common to |
| * the JSE and JME platforms. |
| * In practice on of the concrete IOLib subclasses is chosen: |
| * {@link org.luaj.vm2.lib.jse.JseIoLib} for the JSE platform, and |
| * {@link org.luaj.vm2.lib.jme.JmeIoLib} for the JME platform. |
| * <p> |
| * The JSE implementation conforms almost completely to the C-based lua library, |
| * while the JME implementation follows closely except in the area of random-access files, |
| * which are difficult to support properly on JME. |
| * <p> |
| * Typically, this library is included as part of a call to either |
| * {@link JsePlatform#standardGlobals()} or {@link JmePlatform#standardGlobals()} |
| * <pre> {@code |
| * Globals globals = JsePlatform.standardGlobals(); |
| * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n")); |
| * } </pre> |
| * In this example the platform-specific {@link JseIoLib} library will be loaded, which will include |
| * the base functionality provided by this class, whereas the {@link JsePlatform} would load the |
| * {@link JseIoLib}. |
| * <p> |
| * To instantiate and use it directly, |
| * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: |
| * <pre> {@code |
| * Globals globals = new Globals(); |
| * globals.load(new JseBaseLib()); |
| * globals.load(new PackageLib()); |
| * globals.load(new OsLib()); |
| * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n")); |
| * } </pre> |
| * <p> |
| * This has been implemented to match as closely as possible the behavior in the corresponding library in C. |
| * @see LibFunction |
| * @see JsePlatform |
| * @see JmePlatform |
| * @see JseIoLib |
| * @see JmeIoLib |
| * @see <a href="http://www.lua.org/manual/5.1/manual.html#5.7">http://www.lua.org/manual/5.1/manual.html#5.7</a> |
| */ |
| abstract |
| public class IoLib extends TwoArgFunction { |
| |
| abstract |
| protected class File extends LuaValue{ |
| abstract public void write( LuaString string ) throws IOException; |
| abstract public void flush() throws IOException; |
| abstract public boolean isstdfile(); |
| abstract public void close() throws IOException; |
| abstract public boolean isclosed(); |
| // returns new position |
| abstract public int seek(String option, int bytecount) throws IOException; |
| abstract public void setvbuf(String mode, int size); |
| // get length remaining to read |
| abstract public int remaining() throws IOException; |
| // peek ahead one character |
| abstract public int peek() throws IOException, EOFException; |
| // return char if read, -1 if eof, throw IOException on other exception |
| abstract public int read() throws IOException, EOFException; |
| // return number of bytes read if positive, false if eof, throw IOException on other exception |
| abstract public int read(byte[] bytes, int offset, int length) throws IOException; |
| |
| // delegate method access to file methods table |
| public LuaValue get( LuaValue key ) { |
| return filemethods.get(key); |
| } |
| |
| // essentially a userdata instance |
| public int type() { |
| return LuaValue.TUSERDATA; |
| } |
| public String typename() { |
| return "userdata"; |
| } |
| |
| // displays as "file" type |
| public String tojstring() { |
| return "file: " + Integer.toHexString(hashCode()); |
| } |
| } |
| |
| /** Enumerated value representing stdin */ |
| protected static final int FTYPE_STDIN = 0; |
| /** Enumerated value representing stdout */ |
| protected static final int FTYPE_STDOUT = 1; |
| /** Enumerated value representing stderr */ |
| protected static final int FTYPE_STDERR = 2; |
| /** Enumerated value representing a file type for a named file */ |
| protected static final int FTYPE_NAMED = 3; |
| |
| /** |
| * Wrap the standard input. |
| * @return File |
| * @throws IOException |
| */ |
| abstract protected File wrapStdin() throws IOException; |
| |
| /** |
| * Wrap the standard output. |
| * @return File |
| * @throws IOException |
| */ |
| abstract protected File wrapStdout() throws IOException; |
| |
| /** |
| * Wrap the standard error output. |
| * @return File |
| * @throws IOException |
| */ |
| abstract protected File wrapStderr() throws IOException; |
| |
| /** |
| * Open a file in a particular mode. |
| * @param filename |
| * @param readMode true if opening in read mode |
| * @param appendMode true if opening in append mode |
| * @param updateMode true if opening in update mode |
| * @param binaryMode true if opening in binary mode |
| * @return File object if successful |
| * @throws IOException if could not be opened |
| */ |
| abstract protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException; |
| |
| /** |
| * Open a temporary file. |
| * @return File object if successful |
| * @throws IOException if could not be opened |
| */ |
| abstract protected File tmpFile() 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; |
| |
| private File infile = null; |
| private File outfile = null; |
| private File errfile = null; |
| |
| private static final LuaValue STDIN = valueOf("stdin"); |
| private static final LuaValue STDOUT = valueOf("stdout"); |
| private static final LuaValue STDERR = valueOf("stderr"); |
| private static final LuaValue FILE = valueOf("file"); |
| private static final LuaValue CLOSED_FILE = valueOf("closed file"); |
| |
| private static final int IO_CLOSE = 0; |
| private static final int IO_FLUSH = 1; |
| private static final int IO_INPUT = 2; |
| private static final int IO_LINES = 3; |
| private static final int IO_OPEN = 4; |
| private static final int IO_OUTPUT = 5; |
| private static final int IO_POPEN = 6; |
| private static final int IO_READ = 7; |
| private static final int IO_TMPFILE = 8; |
| private static final int IO_TYPE = 9; |
| private static final int IO_WRITE = 10; |
| |
| private static final int FILE_CLOSE = 11; |
| private static final int FILE_FLUSH = 12; |
| private static final int FILE_LINES = 13; |
| private static final int FILE_READ = 14; |
| private static final int FILE_SEEK = 15; |
| private static final int FILE_SETVBUF = 16; |
| private static final int FILE_WRITE = 17; |
| |
| private static final int IO_INDEX = 18; |
| private static final int LINES_ITER = 19; |
| |
| public static final String[] IO_NAMES = { |
| "close", |
| "flush", |
| "input", |
| "lines", |
| "open", |
| "output", |
| "popen", |
| "read", |
| "tmpfile", |
| "type", |
| "write", |
| }; |
| |
| public static final String[] FILE_NAMES = { |
| "close", |
| "flush", |
| "lines", |
| "read", |
| "seek", |
| "setvbuf", |
| "write", |
| }; |
| |
| LuaTable filemethods; |
| |
| protected Globals globals; |
| |
| public LuaValue call(LuaValue modname, LuaValue env) { |
| globals = env.checkglobals(); |
| |
| // io lib functions |
| LuaTable t = new LuaTable(); |
| bind(t, IoLibV.class, IO_NAMES ); |
| |
| // create file methods table |
| filemethods = new LuaTable(); |
| bind(filemethods, IoLibV.class, FILE_NAMES, FILE_CLOSE ); |
| |
| // set up file metatable |
| LuaTable mt = new LuaTable(); |
| bind(mt, IoLibV.class, new String[] { "__index" }, IO_INDEX ); |
| t.setmetatable( mt ); |
| |
| // all functions link to library instance |
| setLibInstance( t ); |
| setLibInstance( filemethods ); |
| setLibInstance( mt ); |
| |
| // return the table |
| env.set("io", t); |
| env.get("package").get("loaded").set("io", t); |
| return t; |
| } |
| |
| private void setLibInstance(LuaTable t) { |
| LuaValue[] k = t.keys(); |
| for ( int i=0, n=k.length; i<n; i++ ) |
| ((IoLibV) t.get(k[i])).iolib = this; |
| } |
| |
| static final class IoLibV extends VarArgFunction { |
| private File f; |
| public IoLib iolib; |
| public IoLibV() { |
| } |
| public IoLibV(File f, String name, int opcode, IoLib iolib) { |
| super(); |
| this.f = f; |
| this.name = name; |
| this.opcode = opcode; |
| this.iolib = iolib; |
| } |
| |
| public Varargs invoke(Varargs args) { |
| try { |
| switch ( opcode ) { |
| case IO_FLUSH: return iolib._io_flush(); |
| case IO_TMPFILE: return iolib._io_tmpfile(); |
| case IO_CLOSE: return iolib._io_close(args.arg1()); |
| case IO_INPUT: return iolib._io_input(args.arg1()); |
| case IO_OUTPUT: return iolib._io_output(args.arg1()); |
| case IO_TYPE: return iolib._io_type(args.arg1()); |
| case IO_POPEN: return iolib._io_popen(args.checkjstring(1),args.optjstring(2,"r")); |
| case IO_OPEN: return iolib._io_open(args.checkjstring(1), args.optjstring(2,"r")); |
| case IO_LINES: return iolib._io_lines(args.isvalue(1)? args.checkjstring(1): null); |
| case IO_READ: return iolib._io_read(args); |
| case IO_WRITE: return iolib._io_write(args); |
| |
| case FILE_CLOSE: return iolib._file_close(args.arg1()); |
| case FILE_FLUSH: return iolib._file_flush(args.arg1()); |
| case FILE_SETVBUF: return iolib._file_setvbuf(args.arg1(),args.checkjstring(2),args.optint(3,1024)); |
| case FILE_LINES: return iolib._file_lines(args.arg1()); |
| case FILE_READ: return iolib._file_read(args.arg1(),args.subargs(2)); |
| case FILE_SEEK: return iolib._file_seek(args.arg1(),args.optjstring(2,"cur"),args.optint(3,0)); |
| case FILE_WRITE: return iolib._file_write(args.arg1(),args.subargs(2)); |
| |
| case IO_INDEX: return iolib._io_index(args.arg(2)); |
| case LINES_ITER: return iolib._lines_iter(f); |
| } |
| } catch ( IOException ioe ) { |
| return errorresult(ioe); |
| } |
| return NONE; |
| } |
| } |
| |
| private File input() { |
| return infile!=null? infile: (infile=ioopenfile(FTYPE_STDIN, "-","r")); |
| } |
| |
| // io.flush() -> bool |
| public Varargs _io_flush() throws IOException { |
| checkopen(output()); |
| outfile.flush(); |
| return LuaValue.TRUE; |
| } |
| |
| // io.tmpfile() -> file |
| public Varargs _io_tmpfile() throws IOException { |
| return tmpFile(); |
| } |
| |
| // io.close([file]) -> void |
| public Varargs _io_close(LuaValue file) throws IOException { |
| File f = file.isnil()? output(): checkfile(file); |
| checkopen(f); |
| return ioclose(f); |
| } |
| |
| // io.input([file]) -> file |
| public Varargs _io_input(LuaValue file) { |
| infile = file.isnil()? input(): |
| file.isstring()? ioopenfile(FTYPE_NAMED, file.checkjstring(),"r"): |
| checkfile(file); |
| return infile; |
| } |
| |
| // io.output(filename) -> file |
| public Varargs _io_output(LuaValue filename) { |
| outfile = filename.isnil()? output(): |
| filename.isstring()? ioopenfile(FTYPE_NAMED, filename.checkjstring(),"w"): |
| checkfile(filename); |
| return outfile; |
| } |
| |
| // io.type(obj) -> "file" | "closed file" | nil |
| public Varargs _io_type(LuaValue obj) { |
| File f = optfile(obj); |
| return f!=null? |
| f.isclosed()? CLOSED_FILE: FILE: |
| NIL; |
| } |
| |
| // io.popen(prog, [mode]) -> file |
| public Varargs _io_popen(String prog, String mode) throws IOException { |
| return openProgram(prog, mode); |
| } |
| |
| // io.open(filename, [mode]) -> file | nil,err |
| public Varargs _io_open(String filename, String mode) throws IOException { |
| return rawopenfile(FTYPE_NAMED, filename, mode); |
| } |
| |
| // io.lines(filename) -> iterator |
| public Varargs _io_lines(String filename) { |
| infile = filename==null? input(): ioopenfile(FTYPE_NAMED, filename,"r"); |
| checkopen(infile); |
| return lines(infile); |
| } |
| |
| // io.read(...) -> (...) |
| public Varargs _io_read(Varargs args) throws IOException { |
| checkopen(input()); |
| return ioread(infile,args); |
| } |
| |
| // io.write(...) -> void |
| public Varargs _io_write(Varargs args) throws IOException { |
| checkopen(output()); |
| return iowrite(outfile,args); |
| } |
| |
| // file:close() -> void |
| public Varargs _file_close(LuaValue file) throws IOException { |
| return ioclose(checkfile(file)); |
| } |
| |
| // file:flush() -> void |
| public Varargs _file_flush(LuaValue file) throws IOException { |
| checkfile(file).flush(); |
| return LuaValue.TRUE; |
| } |
| |
| // file:setvbuf(mode,[size]) -> void |
| public Varargs _file_setvbuf(LuaValue file, String mode, int size) { |
| checkfile(file).setvbuf(mode,size); |
| return LuaValue.TRUE; |
| } |
| |
| // file:lines() -> iterator |
| public Varargs _file_lines(LuaValue file) { |
| return lines(checkfile(file)); |
| } |
| |
| // file:read(...) -> (...) |
| public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException { |
| return ioread(checkfile(file),subargs); |
| } |
| |
| // file:seek([whence][,offset]) -> pos | nil,error |
| public Varargs _file_seek(LuaValue file, String whence, int offset) throws IOException { |
| return valueOf( checkfile(file).seek(whence,offset) ); |
| } |
| |
| // file:write(...) -> void |
| public Varargs _file_write(LuaValue file, Varargs subargs) throws IOException { |
| return iowrite(checkfile(file),subargs); |
| } |
| |
| // __index, returns a field |
| public Varargs _io_index(LuaValue v) { |
| return v.equals(STDOUT)?output(): |
| v.equals(STDIN)? input(): |
| v.equals(STDERR)? errput(): NIL; |
| } |
| |
| // lines iterator(s,var) -> var' |
| public Varargs _lines_iter(LuaValue file) throws IOException { |
| return freadline(checkfile(file)); |
| } |
| |
| private File output() { |
| return outfile!=null? outfile: (outfile=ioopenfile(FTYPE_STDOUT,"-","w")); |
| } |
| |
| private File errput() { |
| return errfile!=null? errfile: (errfile=ioopenfile(FTYPE_STDERR,"-","w")); |
| } |
| |
| private File ioopenfile(int filetype, String filename, String mode) { |
| try { |
| return rawopenfile(filetype, filename, mode); |
| } catch ( Exception e ) { |
| error("io error: "+e.getMessage()); |
| return null; |
| } |
| } |
| |
| private static Varargs ioclose(File f) throws IOException { |
| if ( f.isstdfile() ) |
| return errorresult("cannot close standard file"); |
| else { |
| f.close(); |
| return successresult(); |
| } |
| } |
| |
| private static Varargs successresult() { |
| return LuaValue.TRUE; |
| } |
| |
| private static Varargs errorresult(Exception ioe) { |
| String s = ioe.getMessage(); |
| return errorresult("io error: "+(s!=null? s: ioe.toString())); |
| } |
| |
| private static Varargs errorresult(String errortext) { |
| return varargsOf(NIL, valueOf(errortext)); |
| } |
| |
| private Varargs lines(final File f) { |
| try { |
| return new IoLibV(f,"lnext",LINES_ITER,this); |
| } catch ( Exception e ) { |
| return error("lines: "+e); |
| } |
| } |
| |
| private static Varargs iowrite(File f, Varargs args) throws IOException { |
| for ( int i=1, n=args.narg(); i<=n; i++ ) |
| f.write( args.checkstring(i) ); |
| return f; |
| } |
| |
| private Varargs ioread(File f, Varargs args) throws IOException { |
| int i,n=args.narg(); |
| LuaValue[] v = new LuaValue[n]; |
| LuaValue ai,vi; |
| LuaString fmt; |
| for ( i=0; i<n; ) { |
| item: switch ( (ai = args.arg(i+1)).type() ) { |
| case LuaValue.TNUMBER: |
| vi = freadbytes(f,ai.toint()); |
| break item; |
| case LuaValue.TSTRING: |
| fmt = ai.checkstring(); |
| if ( fmt.m_length == 2 && fmt.m_bytes[fmt.m_offset] == '*' ) { |
| switch ( fmt.m_bytes[fmt.m_offset+1] ) { |
| case 'n': vi = freadnumber(f); break item; |
| case 'l': vi = freadline(f); break item; |
| case 'a': vi = freadall(f); break item; |
| } |
| } |
| default: |
| return argerror( i+1, "(invalid format)" ); |
| } |
| if ( (v[i++] = vi).isnil() ) |
| break; |
| } |
| return i==0? NIL: varargsOf(v, 0, i); |
| } |
| |
| private static File checkfile(LuaValue val) { |
| File f = optfile(val); |
| if ( f == null ) |
| argerror(1,"file"); |
| checkopen( f ); |
| return f; |
| } |
| |
| private static File optfile(LuaValue val) { |
| return (val instanceof File)? (File) val: null; |
| } |
| |
| private static File checkopen(File file) { |
| if ( file.isclosed() ) |
| error("attempt to use a closed file"); |
| return file; |
| } |
| |
| private File rawopenfile(int filetype, String filename, String mode) throws IOException { |
| switch (filetype) { |
| case FTYPE_STDIN: return wrapStdin(); |
| case FTYPE_STDOUT: return wrapStdout(); |
| case FTYPE_STDERR: return wrapStderr(); |
| } |
| boolean isreadmode = mode.startsWith("r"); |
| boolean isappend = mode.startsWith("a"); |
| boolean isupdate = mode.indexOf("+") > 0; |
| boolean isbinary = mode.endsWith("b"); |
| return openFile( filename, isreadmode, isappend, isupdate, isbinary ); |
| } |
| |
| |
| // ------------- file reading utilitied ------------------ |
| |
| public static LuaValue freadbytes(File f, int count) throws IOException { |
| byte[] b = new byte[count]; |
| int r; |
| if ( ( r = f.read(b,0,b.length) ) < 0 ) |
| return NIL; |
| return LuaString.valueOf(b, 0, r); |
| } |
| public static LuaValue freaduntil(File f,boolean lineonly) throws IOException { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| int c; |
| try { |
| if ( lineonly ) { |
| loop: while ( (c = f.read()) > 0 ) { |
| switch ( c ) { |
| case '\r': break; |
| case '\n': break loop; |
| default: baos.write(c); break; |
| } |
| } |
| } else { |
| while ( (c = f.read()) > 0 ) |
| baos.write(c); |
| } |
| } catch ( EOFException e ) { |
| c = -1; |
| } |
| return ( c < 0 && baos.size() == 0 )? |
| (LuaValue) NIL: |
| (LuaValue) LuaString.valueOf(baos.toByteArray()); |
| } |
| public static LuaValue freadline(File f) throws IOException { |
| return freaduntil(f,true); |
| } |
| public static LuaValue freadall(File f) throws IOException { |
| int n = f.remaining(); |
| if ( n >= 0 ) { |
| return freadbytes(f, n); |
| } else { |
| return freaduntil(f,false); |
| } |
| } |
| public static LuaValue freadnumber(File f) throws IOException { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| freadchars(f," \t\r\n",null); |
| freadchars(f,"-+",baos); |
| //freadchars(f,"0",baos); |
| //freadchars(f,"xX",baos); |
| freadchars(f,"0123456789",baos); |
| freadchars(f,".",baos); |
| freadchars(f,"0123456789",baos); |
| //freadchars(f,"eEfFgG",baos); |
| // freadchars(f,"+-",baos); |
| //freadchars(f,"0123456789",baos); |
| String s = baos.toString(); |
| return s.length()>0? valueOf( Double.parseDouble(s) ): NIL; |
| } |
| private static void freadchars(File f, String chars, ByteArrayOutputStream baos) throws IOException { |
| int c; |
| while ( true ) { |
| c = f.peek(); |
| if ( chars.indexOf(c) < 0 ) { |
| return; |
| } |
| f.read(); |
| if ( baos != null ) |
| baos.write( c ); |
| } |
| } |
| |
| |
| |
| } |