| /******************************************************************************* |
| * Copyright (c) 2009 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.compiler; |
| |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| |
| import org.luaj.vm3.Globals; |
| import org.luaj.vm3.LoadState; |
| import org.luaj.vm3.LocVars; |
| import org.luaj.vm3.Prototype; |
| import org.luaj.vm3.LuaString; |
| import org.luaj.vm3.LuaValue; |
| |
| /** Class to dump a {@link Prototype} into an output stream, as part of compiling. |
| * <p> |
| * Generally, this class is not used directly, but rather indirectly via a command |
| * line interface tool such as {@link luac}. |
| * <p> |
| * A lua binary file is created via {@link DumpState#dump}: |
| * <pre> {@code |
| * Globals globals = JsePlatform.standardGlobals(); |
| * Prototype p = globals.compilePrototype(new StringReader("print('hello, world')"), "main.lua"); |
| * ByteArrayOutputStream o = new ByteArrayOutputStream(); |
| * DumpState.dump(p, o, false); |
| * byte[] lua_binary_file_bytes = o.toByteArray(); |
| * } </pre> |
| * |
| * The {@link LoadState} may be used directly to undump these bytes: |
| * <pre> {@code |
| * Prototypep = LoadState.instance.undump(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua"); |
| * LuaClosure c = new LuaClosure(p, globals); |
| * c.call(); |
| * } </pre> |
| * |
| * |
| * More commonly, the {@link Globals#undumper} may be used to undump them: |
| * <pre> {@code |
| * Prototype p = globals.loadPrototype(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua", "b"); |
| * LuaClosure c = new LuaClosure(p, globals); |
| * c.call(); |
| * } </pre> |
| * |
| * @see luac |
| * @see LoadState |
| * @see Globals |
| * @see Prototype |
| */ |
| public class DumpState { |
| |
| /** set true to allow integer compilation */ |
| public static boolean ALLOW_INTEGER_CASTING = false; |
| |
| /** format corresponding to non-number-patched lua, all numbers are floats or doubles */ |
| public static final int NUMBER_FORMAT_FLOATS_OR_DOUBLES = 0; |
| |
| /** format corresponding to non-number-patched lua, all numbers are ints */ |
| public static final int NUMBER_FORMAT_INTS_ONLY = 1; |
| |
| /** format corresponding to number-patched lua, all numbers are 32-bit (4 byte) ints */ |
| public static final int NUMBER_FORMAT_NUM_PATCH_INT32 = 4; |
| |
| /** default number format */ |
| public static final int NUMBER_FORMAT_DEFAULT = NUMBER_FORMAT_FLOATS_OR_DOUBLES; |
| |
| // header fields |
| private boolean IS_LITTLE_ENDIAN = false; |
| private int NUMBER_FORMAT = NUMBER_FORMAT_DEFAULT; |
| private int SIZEOF_LUA_NUMBER = 8; |
| private static final int SIZEOF_INT = 4; |
| private static final int SIZEOF_SIZET = 4; |
| private static final int SIZEOF_INSTRUCTION = 4; |
| |
| DataOutputStream writer; |
| boolean strip; |
| int status; |
| |
| public DumpState(OutputStream w, boolean strip) { |
| this.writer = new DataOutputStream(w); |
| this.strip = strip; |
| this.status = 0; |
| } |
| |
| void dumpBlock(final byte[] b, int size) throws IOException { |
| writer.write(b, 0, size); |
| } |
| |
| void dumpChar(int b) throws IOException { |
| writer.write(b); |
| } |
| |
| void dumpInt(int x) throws IOException { |
| if (IS_LITTLE_ENDIAN) { |
| writer.writeByte(x & 0xff); |
| writer.writeByte((x >> 8) & 0xff); |
| writer.writeByte((x >> 16) & 0xff); |
| writer.writeByte((x >> 24) & 0xff); |
| } else { |
| writer.writeInt(x); |
| } |
| } |
| |
| void dumpString(LuaString s) throws IOException { |
| final int len = s.len().toint(); |
| dumpInt(len + 1); |
| s.write(writer, 0, len); |
| writer.write(0); |
| } |
| |
| void dumpDouble(double d) throws IOException { |
| long l = Double.doubleToLongBits(d); |
| if (IS_LITTLE_ENDIAN) { |
| dumpInt((int) l); |
| dumpInt((int) (l >> 32)); |
| } else { |
| writer.writeLong(l); |
| } |
| } |
| |
| void dumpCode(final Prototype f) throws IOException { |
| final int[] code = f.code; |
| int n = code.length; |
| dumpInt(n); |
| for (int i = 0; i < n; i++) |
| dumpInt(code[i]); |
| } |
| |
| void dumpConstants(final Prototype f) throws IOException { |
| final LuaValue[] k = f.k; |
| int i, n = k.length; |
| dumpInt(n); |
| for (i = 0; i < n; i++) { |
| final LuaValue o = k[i]; |
| switch (o.type()) { |
| case LuaValue.TNIL: |
| writer.write(LuaValue.TNIL); |
| break; |
| case LuaValue.TBOOLEAN: |
| writer.write(LuaValue.TBOOLEAN); |
| dumpChar(o.toboolean() ? 1 : 0); |
| break; |
| case LuaValue.TNUMBER: |
| switch (NUMBER_FORMAT) { |
| case NUMBER_FORMAT_FLOATS_OR_DOUBLES: |
| writer.write(LuaValue.TNUMBER); |
| dumpDouble(o.todouble()); |
| break; |
| case NUMBER_FORMAT_INTS_ONLY: |
| if (!ALLOW_INTEGER_CASTING && !o.isint()) |
| throw new java.lang.IllegalArgumentException("not an integer: " + o); |
| writer.write(LuaValue.TNUMBER); |
| dumpInt(o.toint()); |
| break; |
| case NUMBER_FORMAT_NUM_PATCH_INT32: |
| if (o.isint()) { |
| writer.write(LuaValue.TINT); |
| dumpInt(o.toint()); |
| } else { |
| writer.write(LuaValue.TNUMBER); |
| dumpDouble(o.todouble()); |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("number format not supported: " + NUMBER_FORMAT); |
| } |
| break; |
| case LuaValue.TSTRING: |
| writer.write(LuaValue.TSTRING); |
| dumpString((LuaString) o); |
| break; |
| default: |
| throw new IllegalArgumentException("bad type for " + o); |
| } |
| } |
| n = f.p.length; |
| dumpInt(n); |
| for (i = 0; i < n; i++) |
| dumpFunction(f.p[i]); |
| } |
| |
| void dumpUpvalues(final Prototype f) throws IOException { |
| int n = f.upvalues.length; |
| dumpInt(n); |
| for (int i = 0; i < n; i++) { |
| writer.writeByte(f.upvalues[i].instack ? 1 : 0); |
| writer.writeByte(f.upvalues[i].idx); |
| } |
| } |
| |
| void dumpDebug(final Prototype f) throws IOException { |
| int i, n; |
| if (strip) |
| dumpInt(0); |
| else |
| dumpString(f.source); |
| n = strip ? 0 : f.lineinfo.length; |
| dumpInt(n); |
| for (i = 0; i < n; i++) |
| dumpInt(f.lineinfo[i]); |
| n = strip ? 0 : f.locvars.length; |
| dumpInt(n); |
| for (i = 0; i < n; i++) { |
| LocVars lvi = f.locvars[i]; |
| dumpString(lvi.varname); |
| dumpInt(lvi.startpc); |
| dumpInt(lvi.endpc); |
| } |
| n = strip ? 0 : f.upvalues.length; |
| dumpInt(n); |
| for (i = 0; i < n; i++) |
| dumpString(f.upvalues[i].name); |
| } |
| |
| void dumpFunction(final Prototype f) throws IOException { |
| dumpInt(f.linedefined); |
| dumpInt(f.lastlinedefined); |
| dumpChar(f.numparams); |
| dumpChar(f.is_vararg); |
| dumpChar(f.maxstacksize); |
| dumpCode(f); |
| dumpConstants(f); |
| dumpUpvalues(f); |
| dumpDebug(f); |
| } |
| |
| void dumpHeader() throws IOException { |
| writer.write(LoadState.LUA_SIGNATURE); |
| writer.write(LoadState.LUAC_VERSION); |
| writer.write(LoadState.LUAC_FORMAT); |
| writer.write(IS_LITTLE_ENDIAN ? 1 : 0); |
| writer.write(SIZEOF_INT); |
| writer.write(SIZEOF_SIZET); |
| writer.write(SIZEOF_INSTRUCTION); |
| writer.write(SIZEOF_LUA_NUMBER); |
| writer.write(NUMBER_FORMAT); |
| writer.write(LoadState.LUAC_TAIL); |
| } |
| |
| /* |
| ** dump Lua function as precompiled chunk |
| */ |
| public static int dump(Prototype f, OutputStream w, boolean strip) throws IOException { |
| DumpState D = new DumpState(w, strip); |
| D.dumpHeader(); |
| D.dumpFunction(f); |
| return D.status; |
| } |
| |
| /** |
| * |
| * @param f the function to dump |
| * @param w the output stream to dump to |
| * @param stripDebug true to strip debugging info, false otherwise |
| * @param numberFormat one of NUMBER_FORMAT_FLOATS_OR_DOUBLES, NUMBER_FORMAT_INTS_ONLY, NUMBER_FORMAT_NUM_PATCH_INT32 |
| * @param littleendian true to use little endian for numbers, false for big endian |
| * @return 0 if dump succeeds |
| * @throws IOException |
| * @throws IllegalArgumentException if the number format it not supported |
| */ |
| public static int dump(Prototype f, OutputStream w, boolean stripDebug, int numberFormat, boolean littleendian) throws IOException { |
| switch (numberFormat) { |
| case NUMBER_FORMAT_FLOATS_OR_DOUBLES: |
| case NUMBER_FORMAT_INTS_ONLY: |
| case NUMBER_FORMAT_NUM_PATCH_INT32: |
| break; |
| default: |
| throw new IllegalArgumentException("number format not supported: " + numberFormat); |
| } |
| DumpState D = new DumpState(w, stripDebug); |
| D.IS_LITTLE_ENDIAN = littleendian; |
| D.NUMBER_FORMAT = numberFormat; |
| D.SIZEOF_LUA_NUMBER = (numberFormat == NUMBER_FORMAT_INTS_ONLY ? 4 : 8); |
| D.dumpHeader(); |
| D.dumpFunction(f); |
| return D.status; |
| } |
| } |