diff --git a/README.html b/README.html index 41dd95a0..1247837e 100644 --- a/README.html +++ b/README.html @@ -968,6 +968,8 @@ Files are no longer hosted at LuaForge.
  • Pass user-supplied ScriptContext to script engine evaluation (fixes issue #21).
  • Autoflush and encode written bytes in script contexts (fixes issue #20).
  • Rename Globals.FINDER to Globals.finder.
  • +
  • Fix bug in Globals.UTF8Stream affecting loading from Readers.
  • +
  • Add buffered input for compiling and loading of scripts.
  • diff --git a/src/core/org/luaj/vm2/Globals.java b/src/core/org/luaj/vm2/Globals.java index 0eee46d6..943f373e 100644 --- a/src/core/org/luaj/vm2/Globals.java +++ b/src/core/org/luaj/vm2/Globals.java @@ -231,7 +231,7 @@ public class Globals extends LuaTable { if (undumper == null) error("No undumper."); if (!is.markSupported()) - is = new MarkStream(is); + is = new BufferedStream(is); is.mark(4); final Prototype p = undumper.undump(is, chunkname); if (p != null) @@ -277,7 +277,8 @@ public class Globals extends LuaTable { /** Reader implementation to read chars from a String in JME or JSE. */ static class StrReader extends Reader { final String s; - int i = 0, n; + int i = 0; + final int n; StrReader(String s) { this.s = s; n = s.length(); @@ -285,6 +286,9 @@ public class Globals extends LuaTable { public void close() throws IOException { i = n; } + public int read() throws IOException { + return i < n ? s.charAt(i++) : -1; + } public int read(char[] cbuf, int off, int len) throws IOException { int j = 0; for (; j < len && i < n; ++j, ++i) @@ -293,54 +297,115 @@ public class Globals extends LuaTable { } } + /* Abstract base class to provide basic buffered input storage and delivery. + * This class may be moved to its own package in the future. + */ + public abstract static class AbstractBufferedStream extends InputStream { + protected byte[] b; + protected int i = 0, j = 0; + protected AbstractBufferedStream(int buflen) { + this.b = new byte[buflen]; + } + abstract protected int avail() throws IOException; + public int read() throws IOException { + int a = avail(); + return (a <= 0 ? -1 : 0xff & b[i++]); + } + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + public int read(byte[] b, int i0, int n) throws IOException { + int a = avail(); + if (a <= 0) return -1; + final int n_read = Math.min(a, n); + System.arraycopy(this.b, i, b, i0, n_read); + i += n_read; + return n_read; + } + public long skip(long n) throws IOException { + final long k = Math.min(n, j - i); + i += k; + return k; + } + public int available() throws IOException { + return j - i; + } + } + /** Simple converter from Reader to InputStream using UTF8 encoding that will work * on both JME and JSE. + * This class may be moved to its own package in the future. */ - static class UTF8Stream extends InputStream { - final char[] c = new char[32]; - final byte[] b = new byte[96]; - int i = 0, j = 0; - final Reader r; + static class UTF8Stream extends AbstractBufferedStream { + private final char[] c = new char[32]; + private final Reader r; UTF8Stream(Reader r) { + super(96); this.r = r; } - public int read() throws IOException { - if (i < j) - return c[i++]; + protected int avail() throws IOException { + if (i < j) return j - i; int n = r.read(c); if (n < 0) return -1; + if (n == 0) { + int u = r.read(); + if (u < 0) + return -1; + c[0] = (char) u; + n = 1; + } j = LuaString.encodeToUtf8(c, n, b, i = 0); - return b[i++]; + return j; + } + public void close() throws IOException { + r.close(); } } - /** Simple InputStream that supports mark. + /** Simple buffered InputStream that supports mark. * Used to examine an InputStream for a 4-byte binary lua signature, - * and fall back to text input when the signature is not found. + * and fall back to text input when the signature is not found, + * as well as speed up normal compilation and reading of lua scripts. + * This class may be moved to its own package in the future. */ - static class MarkStream extends InputStream { - private int[] b; - private int i = 0, j = 0; + public static class BufferedStream extends AbstractBufferedStream { private final InputStream s; - MarkStream(InputStream s) { + public BufferedStream(InputStream s) { + this(128, s); + } + BufferedStream(int buflen, InputStream s) { + super(buflen); this.s = s; } - public int read() throws IOException { - if (i < j) - return b[i++]; - final int c = s.read(); - if (c < 0) + protected int avail() throws IOException { + if (i < j) return j - i; + if (j >= b.length) i = j = 0; + // leave previous bytes in place to implement mark()/reset(). + int n = s.read(b, j, b.length - j); + if (n < 0) return -1; - if (j < b.length) { - b[j++] = c; - i = j; + if (n == 0) { + int u = s.read(); + if (u < 0) + return -1; + b[j] = (byte) u; + n = 1; } - return c; + j += n; + return n; + } + public void close() throws IOException { + s.close(); } public synchronized void mark(int n) { - b = new int[n]; - i = j = 0; + if (i > 0 || n > b.length) { + byte[] dest = n > b.length ? new byte[n] : b; + System.arraycopy(b, i, dest, 0, j - i); + j -= i; + i = 0; + b = dest; + } } public boolean markSupported() { return true; @@ -349,5 +414,4 @@ public class Globals extends LuaTable { i = 0; } } - } diff --git a/src/jse/lua.java b/src/jse/lua.java index f89719fa..39e488f2 100644 --- a/src/jse/lua.java +++ b/src/jse/lua.java @@ -20,6 +20,7 @@ * THE SOFTWARE. ******************************************************************************/ +import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileInputStream; @@ -195,6 +196,7 @@ public class lua { try { LuaValue c; try { + script = new BufferedInputStream(script); c = encoding != null? globals.load(new InputStreamReader(script, encoding), chunkname): globals.load(script, chunkname, "bt", globals); diff --git a/src/jse/luac.java b/src/jse/luac.java index c9f5440e..f0af43fe 100644 --- a/src/jse/luac.java +++ b/src/jse/luac.java @@ -20,6 +20,7 @@ * THE SOFTWARE. ******************************************************************************/ +import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -170,6 +171,7 @@ public class luac { private void processScript( Globals globals, InputStream script, String chunkname, OutputStream out ) throws IOException { try { // create the chunk + script = new BufferedInputStream(script); Prototype chunk = encoding != null? globals.compilePrototype(new InputStreamReader(script, encoding), chunkname): globals.compilePrototype(script, chunkname); diff --git a/test/junit/org/luaj/vm2/AllTests.java b/test/junit/org/luaj/vm2/AllTests.java index 1ee05265..253f5393 100644 --- a/test/junit/org/luaj/vm2/AllTests.java +++ b/test/junit/org/luaj/vm2/AllTests.java @@ -67,6 +67,12 @@ public class AllTests { TestSuite bytecodetests = FragmentsTest.suite(); suite.addTest(bytecodetests); + // I/O tests + TestSuite io = new TestSuite("I/O Tests"); + io.addTestSuite(BufferedStreamTest.class); + io.addTestSuite(UTF8StreamTest.class); + suite.addTest(io); + // prototype compiler TestSuite compiler = new TestSuite("Lua Compiler Tests"); compiler.addTestSuite(CompilerUnitTests.class); diff --git a/test/junit/org/luaj/vm2/BufferedStreamTest.java b/test/junit/org/luaj/vm2/BufferedStreamTest.java new file mode 100644 index 00000000..45161a2e --- /dev/null +++ b/test/junit/org/luaj/vm2/BufferedStreamTest.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2014 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; + +import java.io.ByteArrayInputStream; + +import junit.framework.TestCase; + +import org.luaj.vm2.Globals.BufferedStream; + + +public class BufferedStreamTest extends TestCase { + + public BufferedStreamTest() {} + + private BufferedStream NewBufferedStream(int buflen, String contents) { + return new BufferedStream(buflen, new ByteArrayInputStream(contents.getBytes())); + } + + protected void setUp() throws Exception { + super.setUp(); + } + + public void testReadEmptyStream() throws java.io.IOException { + BufferedStream bs = NewBufferedStream(4, ""); + assertEquals(-1, bs.read()); + assertEquals(-1, bs.read(new byte[10])); + assertEquals(-1, bs.read(new byte[10], 0, 10)); + } + + public void testReadByte() throws java.io.IOException { + BufferedStream bs = NewBufferedStream(2, "abc"); + assertEquals('a', bs.read()); + assertEquals('b', bs.read()); + assertEquals('c', bs.read()); + assertEquals(-1, bs.read()); + } + + public void testReadByteArray() throws java.io.IOException { + byte[] array = new byte[3]; + BufferedStream bs = NewBufferedStream(4, "abcdef"); + assertEquals(3, bs.read(array)); + assertEquals("abc", new String(array)); + assertEquals(1, bs.read(array)); + assertEquals("d", new String(array, 0, 1)); + assertEquals(2, bs.read(array)); + assertEquals("ef", new String(array, 0, 2)); + assertEquals(-1, bs.read()); + } + + public void testReadByteArrayOffsetLength() throws java.io.IOException { + byte[] array = new byte[10]; + BufferedStream bs = NewBufferedStream(8, "abcdefghijklmn"); + assertEquals(4, bs.read(array, 0, 4)); + assertEquals("abcd", new String(array, 0, 4)); + assertEquals(4, bs.read(array, 2, 8)); + assertEquals("efgh", new String(array, 2, 4)); + assertEquals(6, bs.read(array, 0, 10)); + assertEquals("ijklmn", new String(array, 0, 6)); + assertEquals(-1, bs.read()); + } + + public void testMarkOffsetBeginningOfStream() throws java.io.IOException { + byte[] array = new byte[4]; + BufferedStream bs = NewBufferedStream(8, "abcdefghijkl"); + assertEquals(true, bs.markSupported()); + bs.mark(4); + assertEquals(4, bs.read(array)); + assertEquals("abcd", new String(array)); + bs.reset(); + assertEquals(4, bs.read(array)); + assertEquals("abcd", new String(array)); + assertEquals(4, bs.read(array)); + assertEquals("efgh", new String(array)); + assertEquals(4, bs.read(array)); + assertEquals("ijkl", new String(array)); + assertEquals(-1, bs.read()); + } + + public void testMarkOffsetMiddleOfStream() throws java.io.IOException { + byte[] array = new byte[4]; + BufferedStream bs = NewBufferedStream(8, "abcdefghijkl"); + assertEquals(true, bs.markSupported()); + assertEquals(4, bs.read(array)); + assertEquals("abcd", new String(array)); + bs.mark(4); + assertEquals(4, bs.read(array)); + assertEquals("efgh", new String(array)); + bs.reset(); + assertEquals(4, bs.read(array)); + assertEquals("efgh", new String(array)); + assertEquals(4, bs.read(array)); + assertEquals("ijkl", new String(array)); + assertEquals(-1, bs.read()); + } +} diff --git a/test/junit/org/luaj/vm2/UTF8StreamTest.java b/test/junit/org/luaj/vm2/UTF8StreamTest.java new file mode 100644 index 00000000..1d9f3d23 --- /dev/null +++ b/test/junit/org/luaj/vm2/UTF8StreamTest.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2014 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; + +import junit.framework.TestCase; + +import org.luaj.vm2.lib.jse.JsePlatform; + +public class UTF8StreamTest extends TestCase { + + public void testUtf8CharsInStream() { + String script = "x = \"98\u00b0: today's temp!\"\n" + + "print('x = ', x)\n" + + "return x"; + Globals globals = JsePlatform.standardGlobals(); + LuaValue chunk = globals.load(script); + LuaValue result = chunk.call(); + String str = result.tojstring(); + assertEquals("98\u00b0: today's temp!", str); + } + +}