blob: b370e9b13d35d478694e9b2208f18a4b921eb8c7 [file] [log] [blame] [raw]
/*******************************************************************************
* 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.vm3.lib;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import org.luaj.vm3.Globals;
import org.luaj.vm3.LuaString;
import org.luaj.vm3.LuaTable;
import org.luaj.vm3.LuaValue;
import org.luaj.vm3.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.vm3.lib.jse.JseIoLib} for the JSE platform, and
* {@link org.luaj.vm3.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);
}
}
}